Backported Unarmed module and UnarmedFile option from 4.x

This commit is contained in:
NovaRain
2021-09-23 10:43:38 +08:00
parent 76e78fb2be
commit 334957d458
17 changed files with 732 additions and 100 deletions
+64
View File
@@ -0,0 +1,64 @@
; This file lets you configure the requirements and effects of unarmed attacks
; Strong Punch
[8]
ReqLevel=1 ; required player level
SkillLevel=55 ; required Unarmed skill level
; Required stats
Stat0=0 ; ST
Stat1=0 ; PE
Stat2=0 ; EN
Stat3=0 ; CH
Stat4=0 ; IN
Stat5=6 ; AG
Stat6=0 ; LK
MinDamage=1
MaxDamage=2
BonusDamage=3 ; bonus damage added to the minimum and maximum damage
BonusCrit=0 ; value of the critical hit chance
APCost= ; you can leave the value empty or omit the parameter to use the engine default value
Penetrate=0 ; set to 1 to make the attack reduce target's armor DT (the formula is determined by the engine or a hook script)
Secondary=0 ; set to 1 to assign the attack to the secondary mode (be careful when setting this parameter)
; Hammer Punch
[9]
; Haymaker
[10]
; Jab
[11]
; Palm Strike
[12]
; Piercing Strike
[13]
; Strong Kick
[14]
; Snap Kick
[15]
; Power Kick
[16]
; Hip Kick
[17]
; Hook Kick
[18]
; Piercing Kick
[19]
; Default attacks (any requirements are ignored)
; Punch
[4]
; Kick
[5]
+5 -2
View File
@@ -790,8 +790,11 @@ CreditsAtBottom=0
;Point to an ini file containing elevator data
;ElevatorsFile=sfall\Elevators.ini
;Allows you to change some engine parameters for the game mechanics
;See the Tweaks.ini in the modders pack for an example file
;Allows you to change the requirements and effects of unarmed attacks
;See the Unarmed.ini in the modders pack for an example file
;UnarmedFile=sfall\Unarmed.ini
;To change some engine parameters for the game mechanics, uncomment the next line
;TweaksFile=sfall\Tweaks.ini
;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+3 -3
View File
@@ -113,7 +113,7 @@ isFloat:
jmp NegateFixHack_Back;
}
}
/*
static void __declspec(naked) compute_attack_hack() {
static const DWORD UnarmedAttacksFixEnd = 0x423A0D;
__asm {
@@ -150,7 +150,7 @@ end:
jmp UnarmedAttacksFixEnd;
}
}
*/
static void __declspec(naked) SharpShooterFix() {
__asm {
call stat_level_ // Perception
@@ -3161,7 +3161,7 @@ void BugFixes_Init()
//if (GetConfigInt("Misc", "SpecialUnarmedAttacksFix", 1)) {
dlog("Applying Special Unarmed Attacks fix.", DL_FIX);
MakeJump(0x42394D, compute_attack_hack);
//MakeJump(0x42394D, compute_attack_hack); - implementation moved to Unarmed module
dlogr(" Done", DL_FIX);
//}
+15 -3
View File
@@ -21,6 +21,8 @@
#include "FalloutEngine.h"
#include "Logging.h"
#include "ReplacementFuncs.h"
static int formula;
static const DWORD DamageFunctionReturn = 0x424A63;
@@ -362,7 +364,7 @@ static __declspec(naked) void CommonDmgRngDispFix_hook() {
retn;
}
}
/*
static __declspec(naked) void HtHDamageFix1a_hack() {
__asm {
cmp ecx, dword ptr ds:[FO_VAR_obj_dude]; // Is the critter == PC?
@@ -378,7 +380,7 @@ fix:
retn;
}
}
*/
static __declspec(naked) void HtHDamageFix1b_hook() {
__asm {
call stat_level_; // Get Total_Melee_Damage
@@ -436,6 +438,14 @@ static void __declspec(naked) DisplayBonusHtHDmg2_hack() {
}
}
static bool bonusHtHDamageFix = false;
long DamageMod_GetHtHMinDamageBonus(TGameObj* source) {
return (bonusHtHDamageFix)
? sfgame_perk_level(source, PERK_bonus_hth_damage) << 1 // Rank_of_Bonus_HtH_Damage_perk *= 2
: 0;
}
void DamageMod_Init() {
if (formula = GetConfigInt("Misc", "DamageFormula", 0)) {
switch (formula) {
@@ -451,7 +461,9 @@ void DamageMod_Init() {
int BonusHtHDmgFix = GetConfigInt("Misc", "BonusHtHDamageFix", 1);
int DisplayBonusDmg = GetConfigInt("Misc", "DisplayBonusDamage", 0);
if (BonusHtHDmgFix) {
bonusHtHDamageFix = true;
dlog("Applying Bonus HtH Damage Perk fix.", DL_INIT);
if (DisplayBonusDmg == 0) { // Subtract damage from perk bonus (vanilla displaying)
const DWORD meleeDmgDispPrtAddr[] = {
@@ -465,7 +477,7 @@ void DamageMod_Init() {
};
HookCalls(CommonDmgRngDispFix_hook, commonDmgRngDispAddr);
}
MakeCall(0x478492, HtHDamageFix1a_hack); // Unarmed (item_w_damage_)
//MakeCall(0x478492, HtHDamageFix1a_hack); // Unarmed (item_w_damage_)
HookCall(0x47854C, HtHDamageFix1b_hook); // MeleeWeap (item_w_damage_)
dlogr(" Done", DL_INIT);
}
+2
View File
@@ -19,3 +19,5 @@
#pragma once
void DamageMod_Init();
long DamageMod_GetHtHMinDamageBonus(TGameObj* source);
+27 -26
View File
@@ -604,15 +604,15 @@ long GetItemType(TGameObj* item) {
__declspec(noinline) TGameObj* __stdcall GetItemPtrSlot(TGameObj* critter, InvenType slot) {
TGameObj* itemPtr = nullptr;
switch (slot) {
case INVEN_TYPE_LEFT_HAND:
itemPtr = fo_inven_left_hand(critter);
break;
case INVEN_TYPE_RIGHT_HAND:
itemPtr = fo_inven_right_hand(critter);
break;
case INVEN_TYPE_WORN:
itemPtr = fo_inven_worn(critter);
break;
case INVEN_TYPE_LEFT_HAND:
itemPtr = fo_inven_left_hand(critter);
break;
case INVEN_TYPE_RIGHT_HAND:
itemPtr = fo_inven_right_hand(critter);
break;
case INVEN_TYPE_WORN:
itemPtr = fo_inven_worn(critter);
break;
}
return itemPtr;
}
@@ -625,24 +625,25 @@ TGameObj* GetActiveItem() {
return ptr_itemButtonItems[*ptr_itemCurrentItem].item;
}
long GetCurrentAttackMode() {
long hitMode = -1;
if (*ptr_interfaceWindow != -1) {
long activeHand = *ptr_itemCurrentItem; // 0 - left, 1 - right
switch (ptr_itemButtonItems[activeHand].mode) {
case 1:
case 2: // called shot
hitMode = ptr_itemButtonItems[activeHand].primaryAttack;
break;
case 3:
case 4: // called shot
hitMode = ptr_itemButtonItems[activeHand].secondaryAttack;
break;
case 5: // reload mode
hitMode = ATKTYPE_LWEAPON_RELOAD + activeHand;
}
AttackType GetSlotHitMode(HandSlot hand) { // 0 - left, 1 - right
switch (ptr_itemButtonItems[hand].mode) {
case 1:
case 2: // called shot
return (AttackType)ptr_itemButtonItems[hand].primaryAttack;
case 3:
case 4: // called shot
return (AttackType)ptr_itemButtonItems[hand].secondaryAttack;
case 5: // reload mode
return (AttackType)(ATKTYPE_LWEAPON_RELOAD + hand);
}
return hitMode;
return ATKTYPE_PUNCH;
}
long GetCurrentAttackMode() {
if (*ptr_interfaceWindow != -1) {
return GetSlotHitMode((HandSlot)*ptr_itemCurrentItem);
}
return -1;
}
AttackSubType GetWeaponType(DWORD weaponFlag) {
+3
View File
@@ -591,6 +591,9 @@ long& GetActiveItemMode();
TGameObj* GetActiveItem();
// Hand: 0 - left, 1 - right
AttackType GetSlotHitMode(HandSlot hand);
long GetCurrentAttackMode();
AttackSubType GetWeaponType(DWORD weaponFlag);
+1
View File
@@ -281,6 +281,7 @@ FUNC(interpret_, 0x46CCA4)
FUNC(intface_disable_, 0x45EAFC)
FUNC(intface_enable_, 0x45EA64)
FUNC(intface_get_attack_, 0x45EF6C)
FUNC(intface_get_item_states_, 0x45F4B4)
FUNC(intface_hide_, 0x45E9E0)
FUNC(intface_is_hidden_, 0x45EA5C)
FUNC(intface_is_item_right_hand_, 0x45F7FC)
+19 -20
View File
@@ -263,7 +263,7 @@ long __stdcall CalcApCostHook_Invoke(TGameObj* source, long hitMode, long isCall
? CalcApCostHook_Script(source, hitMode, isCalled, cost, weapon)
: cost;
}
/*
static void __declspec(naked) CalcApCostHook() {
__asm {
HookBegin;
@@ -290,7 +290,7 @@ static void __declspec(naked) CalcApCostHook() {
retn;
}
}
*/
// this is for using non-weapon items, always 2 AP in vanilla
static void __declspec(naked) CalcApCostHook2() {
__asm {
@@ -619,7 +619,6 @@ defaultHandler:
jmp protinst_use_item_;
}
}
/*
static void RemoveInvenObjHook_Script(TGameObj* source, TGameObj* item, long count, long rmType) {
BeginHook();
@@ -639,7 +638,6 @@ void RemoveInvenObjHook_Invoke(TGameObj* source, TGameObj* item, long count, lon
if (HookHasScript(HOOK_REMOVEINVENOBJ)) RemoveInvenObjHook_Script(source, item, count, rmType);
}
*/
static long rmObjType = -1;
void __stdcall SetRemoveObjectType(long rmType) {
@@ -914,9 +912,10 @@ static void __declspec(naked) ItemDamageHook() {
}
argCount = 6;
if (args[2] == 0) { // weapon arg
args[4] += 8; // type arg
}
// tweak for 0x4784AA (obsolete)
//if (args[2] == 0) { // weapon arg
// args[4] += 8; // type arg
//}
RunHookScript(HOOK_ITEMDAMAGE);
@@ -1872,19 +1871,19 @@ void HookScripts_Init() {
MakeCall(0x423893, AfterHitRollHook);
// HOOK_CALCAPCOST
const DWORD calcApCostHkAddr[] = {
0x42307A,
0x42669F,
0x42687B,
0x42A625,
0x42A655,
0x42A686,
0x42AE32,
0x42AE71,
0x460048,
0x47807B
};
HookCalls(CalcApCostHook, calcApCostHkAddr);
//const DWORD calcApCostHkAddr[] = {
// 0x42307A,
// 0x42669F,
// 0x42687B,
// 0x42A625,
// 0x42A655,
// 0x42A686,
// 0x42AE32,
// 0x42AE71,
// 0x460048,
// 0x47807B
//};
//HookCalls(CalcApCostHook, calcApCostHkAddr);
MakeCall(0x478083, CalcApCostHook2);
// HOOK_DEATHANIM1, HOOK_DEATHANIM2
+11 -9
View File
@@ -1015,7 +1015,7 @@ static void PerkAndTraitSetup() {
HookCall(0x4248F9, BlockedTrait); // compute_damage_
break;
case TRAIT_fast_shot:
HookCall(0x478C8A, BlockedTrait); // item_w_mp_cost_
//HookCall(0x478C8A, BlockedTrait); // item_w_mp_cost_ (obsolete)
HookCall(0x478E70, BlockedTrait); // item_w_called_shot_
break;
case TRAIT_bloody_mess:
@@ -1063,7 +1063,7 @@ dlgExit:
jmp perks_dialog_Ret;
}
}
/*
static void __declspec(naked) item_w_mp_cost_hook() {
__asm {
call item_w_range_;
@@ -1078,7 +1078,7 @@ checkType:
jmp item_w_subtype_; // eax - item
}
}
*/
// Haenlomal's tweak
static void __declspec(naked) item_w_called_shot_hack() {
static const DWORD FastShotTraitFix_End = 0x478E7F;
@@ -1115,20 +1115,22 @@ static void FastShotTraitFix() {
goto fix;
case 2: {
dlog("Applying Fast Shot trait patch (Alternative behavior).", DL_INIT);
SafeWrite16(0x478C9F, 0x9090);
const DWORD fastShotFixF1[] = {0x478BB8, 0x478BC7, 0x478BD6, 0x478BEA, 0x478BF9, 0x478C08, 0x478C2F};
HookCalls((void*)0x478C7D, fastShotFixF1);
/* Implemented in sfall item_w_mp_cost function */
//SafeWrite16(0x478C9F, 0x9090); // item_w_mp_cost_
//const DWORD fastShotFixF1[] = {0x478BB8, 0x478BC7, 0x478BD6, 0x478BEA, 0x478BF9, 0x478C08, 0x478C2F};
//HookCalls((void*)0x478C7D, fastShotFixF1); // jmp 0x478C7D
goto done;
}
case 3:
dlog("Applying Fast Shot trait patch (Fallout 1 behavior).", DL_INIT);
HookCall(0x478C97, (void*)item_hit_with_);
SafeWrite16(0x478C9E, CODETYPE_JumpZ << 8); // ignore all unarmed attacks (cmp eax, 0; jz)
/* Implemented in sfall item_w_mp_cost function */
//HookCall(0x478C97, (void*)item_hit_with_);
//SafeWrite16(0x478C9E, CODETYPE_JumpZ << 8); // ignore all unarmed attacks (cmp eax, 0; jz)
goto done;
default:
dlog("Applying Fast Shot trait fix.", DL_INIT);
fix:
HookCall(0x478C97, item_w_mp_cost_hook);
//HookCall(0x478C97, item_w_mp_cost_hook); - Fix implemented in sfall item_w_mp_cost function
done:
dlogr(" Done", DL_INIT);
}
+97 -34
View File
@@ -26,6 +26,7 @@
#include "HookScripts.h"
#include "PartyControl.h"
#include "Perks.h"
#include "Unarmed.h"
#include "ReplacementFuncs.h"
@@ -295,7 +296,8 @@ static void __declspec(naked) adjust_fid_hack() {
//////////////////////////////////// ITEMS /////////////////////////////////////
static const int reloadCostAP = 2; // engine default reload AP cost
static const int reloadAPCost = 2; // engine default reload AP cost
static const int unarmedAPCost = 3; // engine default use item AP cost
static std::tr1::array<long, 3> healingItemPids = {PID_STIMPAK, PID_SUPER_STIMPAK, PID_HEALING_POWDER};
@@ -321,7 +323,7 @@ bool __fastcall sfgame_IsHealingItem(TGameObj* item) {
}
bool __stdcall sfgame_UseDrugItemFunc(TGameObj* source, TGameObj* item) {
bool result = (sfgame_item_d_take_drug(source, item) == -1);
bool result = (sfgame_item_d_take_drug(source, item) == -1); // HOOK_USEOBJON
if (result) {
fo_item_add_force(source, item, 1);
} else {
@@ -370,7 +372,7 @@ long __stdcall sfgame_item_weapon_range(TGameObj* source, TGameObj* weapon, long
long type = GetWeaponType(flagExt);
if (type == ATKSUBTYPE_THROWING) {
long heaveHoMod = fo_perk_level(source, PERK_heave_ho);
long heaveHoMod = sfgame_perk_level(source, PERK_heave_ho);
long stRange = fo_stat_level(source, STAT_st);
if (perkHeaveHoModTweak) {
@@ -393,7 +395,34 @@ long __stdcall sfgame_item_weapon_range(TGameObj* source, TGameObj* weapon, long
// return sfgame_item_weapon_range(source, fo_item_hit_with(source, hitMode), hitMode);
//}
static bool fastShotTweak = false;
static int fastShotTweak;
static long item_w_mp_cost_sub(TGameObj* source, TGameObj* item, long hitMode, long isCalled, long cost) {
if (isCalled) cost++;
if (cost < 0) cost = 0;
long type = fo_item_w_subtype(item, hitMode);
if (source->protoId == PID_Player && DudeHasTrait(TRAIT_fast_shot)) {
// Alternative behaviors of the Fast Shot trait
if (item && fastShotTweak > 2) { // Fallout 1 behavior (allowed for all weapons)
cost--;
} else if (fastShotTweak == 2) { // Alternative behavior (allowed for all attacks)
cost--;
} else if (fastShotTweak < 2 && type > ATKSUBTYPE_MELEE && fo_item_w_range(source, hitMode) >= 2) { // Fallout 2 behavior (with Haenlomal's fix)
cost--;
}
}
if ((type == ATKSUBTYPE_MELEE || type == ATKSUBTYPE_UNARMED) && sfgame_perk_level(source, PERK_bonus_hth_attacks)) {
cost--;
}
if (type == ATKSUBTYPE_GUNS && sfgame_perk_level(source, PERK_bonus_rate_of_fire)) {
cost--;
}
if (cost < 1) cost = 1;
return cost;
}
// Implementation of item_w_primary_mp_cost_ and item_w_secondary_mp_cost_ engine functions in a single function with the HOOK_CALCAPCOST hook
long __fastcall sfgame_item_weapon_mp_cost(TGameObj* source, TGameObj* weapon, long hitMode, long isCalled) {
@@ -411,40 +440,18 @@ long __fastcall sfgame_item_weapon_mp_cost(TGameObj* source, TGameObj* weapon, l
case ATKTYPE_LWEAPON_RELOAD:
case ATKTYPE_RWEAPON_RELOAD:
if (weapon && weapon->protoId != PID_SOLAR_SCORCHER) { // Solar Scorcher has no reload AP cost
cost = reloadCostAP;
cost = reloadAPCost;
if (GetProto(weapon->protoId)->item.weapon.perk == PERK_weapon_fast_reload) {
cost--;
}
}
goto endReload;
}
if (hitMode < ATKTYPE_LWEAPON_RELOAD) {
if (isCalled) cost++;
if (cost < 0) cost = 0;
long type = fo_item_w_subtype(weapon, hitMode);
cost = item_w_mp_cost_sub(source, weapon, hitMode, isCalled, cost);
if (source->protoId == PID_Player && DudeHasTrait(TRAIT_fast_shot)) {
if (fastShotTweak || // Fallout 1 behavior and Alternative behavior (allowed for all weapons)
(fo_item_w_range(source, hitMode) >= 2 && type > ATKSUBTYPE_MELEE)) // Fallout 2 behavior (with fix) and Haenlomal's tweak
{
cost--;
}
}
if ((type == ATKSUBTYPE_MELEE || type == ATKSUBTYPE_UNARMED) && fo_perk_level(source, PERK_bonus_hth_attacks)) {
cost--;
}
if (type == ATKSUBTYPE_GUNS && fo_perk_level(source, PERK_bonus_rate_of_fire)) {
cost--;
}
if (cost < 1) cost = 1;
}
return CalcApCostHook_Invoke(source, hitMode, isCalled, cost, weapon);
}
// Implementation of item_w_mp_cost_ engine function with the HOOK_CALCAPCOST hook
long __stdcall sfgame_item_w_mp_cost(TGameObj* source, long hitMode, long isCalled) {
long cost = fo_item_w_mp_cost(source, hitMode, isCalled);
return CalcApCostHook_Invoke(source, hitMode, isCalled, cost, nullptr);
endReload:
return CalcApCostHook_Invoke(source, hitMode, isCalled, cost, weapon); // return cost
}
static void __declspec(naked) ai_search_inven_weap_hook() {
@@ -458,6 +465,54 @@ static void __declspec(naked) ai_search_inven_weap_hook() {
}
}
// Implementation of item_w_mp_cost_ engine function with the HOOK_CALCAPCOST hook
long __fastcall sfgame_item_w_mp_cost(TGameObj* source, AttackType hitMode, long isCalled) {
TGameObj* handItem = nullptr;
switch (hitMode) {
case ATKTYPE_LWEAPON_PRIMARY:
case ATKTYPE_LWEAPON_SECONDARY:
case ATKTYPE_LWEAPON_RELOAD:
handItem = fo_inven_left_hand(source);
break;
case ATKTYPE_RWEAPON_PRIMARY:
case ATKTYPE_RWEAPON_SECONDARY:
case ATKTYPE_RWEAPON_RELOAD:
handItem = fo_inven_right_hand(source);
break;
default:
break;
}
if (handItem) {
return sfgame_item_weapon_mp_cost(source, handItem, hitMode, isCalled);
}
// unarmed hits
long cost = unarmedAPCost;
if (hitMode == ATKTYPE_PUNCH || hitMode == ATKTYPE_KICK || hitMode >= ATKTYPE_STRONGPUNCH) {
cost = Unarmed_GetHitAPCost(hitMode);
}
// return cost
return CalcApCostHook_Invoke(
source,
hitMode,
isCalled,
item_w_mp_cost_sub(source, nullptr, hitMode, isCalled, cost),
nullptr
);
}
static void __declspec(naked) item_w_mp_cost_hack() {
__asm {
push ebx; // isCalled
mov ecx, eax; // source
call sfgame_item_w_mp_cost;
pop ecx;
retn;
}
}
/////////////////////////////////// OBJECTS ////////////////////////////////////
// Implementation of is_within_perception_ engine function with the HOOK_WITHINPERCEPTION hook
@@ -677,6 +732,12 @@ int __stdcall sfgame_trait_level(DWORD traitID) {
return DudeHasTrait(traitID);
}
// Wrapper of perk_level_ function, for quickly skipping other critters
int __stdcall sfgame_perk_level(TGameObj* source, DWORD perkID) {
if (source != *ptr_obj_dude) return 0;
return fo_perk_level(source, perkID);
}
static int DudeGetBaseStat(DWORD statID) {
return fo_stat_get_base_direct(*ptr_obj_dude, statID);
}
@@ -911,11 +972,13 @@ void InitReplacementHacks() {
// Replace adjust_fid_ function
MakeJump(adjust_fid_, adjust_fid_hack); // 0x4716E8
// Replace the item_w_primary_mp_cost_ function with the sfall implementation
// Replace the item_w_primary_mp_cost_ function with the sfall implementation in ai_search_inven_weap_
HookCall(0x429A08, ai_search_inven_weap_hook);
int fastShotFix = GetConfigInt("Misc", "FastShotFix", 0);
fastShotTweak = (fastShotFix > 0 && fastShotFix <= 3);
// Replace the item_w_mp_cost_ function with the sfall implementation
MakeJump(item_w_mp_cost_ + 1, item_w_mp_cost_hack); // 0x478B25
fastShotTweak = GetConfigInt("Misc", "FastShotFix", 0);
// Replace the srcCopy_ function with a pure MMX implementation
MakeJump(buf_to_buf_, fo_buf_to_buf); // 0x4D36D4
+4 -2
View File
@@ -60,8 +60,8 @@ long __stdcall sfgame_item_weapon_range(TGameObj* source, TGameObj* weapon, long
long __fastcall sfgame_item_weapon_mp_cost(TGameObj* source, TGameObj* weapon, long hitMode, long isCalled);
// Implementation of item_w_mp_cost_ engine function with the HOOK_CALCAPCOST hook
// Note: Use the generic item_mp_cost function which has a hook call
long __stdcall sfgame_item_w_mp_cost(TGameObj* source, long hitMode, long isCalled);
// Note: Can use the generic item_mp_cost_ function which has a hook call
long __fastcall sfgame_item_w_mp_cost(TGameObj* source, AttackType hitMode, long isCalled);
// Implementation of is_within_perception_ engine function with the HOOK_WITHINPERCEPTION hook
long __stdcall sfgame_is_within_perception(TGameObj* watcher, TGameObj* target, long hookType);
@@ -75,6 +75,8 @@ int __stdcall sfgame_trait_adjust_skill(DWORD skillID);
int __stdcall sfgame_trait_level(DWORD traitID);
int __stdcall sfgame_perk_level(TGameObj* source, DWORD perkID);
int __stdcall sfgame_trait_adjust_stat(DWORD statID);
long __fastcall sfgame_tile_num_beyond(long sourceTile, long targetTile, long maxRange);
+443
View File
@@ -0,0 +1,443 @@
/*
* sfall
* Copyright (C) 2008-2021 The sfall team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include "main.h"
#include "FalloutEngine.h"
#include "DamageMod.h"
class Hits {
public:
static const long count = 20;
private:
struct HitsData {
long reqLevel; // intface_update_items_
long reqSkill;
long reqStat[7];
long minDamage; // default for all
long maxDamage; // default for all (maxDamage + STAT_melee_dmg)
long bonusDamage; // item_w_damage_
long bonusCrit; // compute_attack_
long apCost; // item_w_mp_cost_
bool isPenetrate; // compute_damage_
bool isSecondary;
// HitsData() : reqLevel(0), reqSkill(0), reqStat(), minDamage(1), maxDamage(2), bonusDamage(0), bonusCrit(0), costAP(3), isPenetrate(false), isSecondary(false) {}
HitsData() {
reqLevel = 0;
reqSkill = 0;
std::memset(reqStat, 0, sizeof(reqStat));
minDamage = 1;
maxDamage = 2;
bonusDamage = 0;
bonusCrit = 0;
apCost = 3;
isPenetrate = false;
isSecondary = false;
}
};
struct SortHits {
BYTE level;
BYTE hit; // index in Hits class
};
// sorted in descending order of the required stats in group hits
SortHits sortHits[Hits::count - ATKTYPE_STRONGPUNCH];
HitsData hit[Hits::count];
long skillLevel; // SKILL_UNARMED_COMBAT
long psStat[STAT_base_count];
public:
class Types{
public: enum : long {
punch = ATKTYPE_PUNCH,
kick = ATKTYPE_KICK,
strong_punch = ATKTYPE_STRONGPUNCH,
hammer_punch,
haymaker,
jab,
palm_strike,
piercing_strike,
strong_kick,
snap_kick,
power_kick,
hip_kick,
hook_kick,
piercing_kick
};
};
Hits() {
// punch primary 1
hit[Types::strong_punch].reqLevel = 1;
hit[Types::strong_punch].reqSkill = 55;
hit[Types::strong_punch].reqStat[STAT_ag] = 6;
hit[Types::strong_punch].bonusDamage = 3;
// primary 2
hit[Types::hammer_punch].reqLevel = 6;
hit[Types::hammer_punch].reqSkill = 75;
hit[Types::hammer_punch].reqStat[STAT_st] = 5;
hit[Types::hammer_punch].reqStat[STAT_ag] = 6;
hit[Types::hammer_punch].bonusDamage = 5;
hit[Types::hammer_punch].bonusCrit = 5;
// primary 3
hit[Types::haymaker].reqLevel = 9;
hit[Types::haymaker].reqSkill = 100;
hit[Types::haymaker].reqStat[STAT_st] = 5;
hit[Types::haymaker].reqStat[STAT_ag] = 7;
hit[Types::haymaker].bonusDamage = 7;
hit[Types::haymaker].bonusCrit = 15;
// punch secondary 1
hit[Types::jab].reqLevel = 5;
hit[Types::jab].reqSkill = 75;
hit[Types::jab].reqStat[STAT_st] = 5;
hit[Types::jab].reqStat[STAT_ag] = 7;
hit[Types::jab].bonusDamage = 3;
hit[Types::jab].bonusCrit = 10;
hit[Types::jab].isSecondary = true;
// secondary 2
hit[Types::palm_strike].reqLevel = 12;
hit[Types::palm_strike].reqSkill = 115;
hit[Types::palm_strike].reqStat[STAT_st] = 5;
hit[Types::palm_strike].reqStat[STAT_ag] = 7;
hit[Types::palm_strike].bonusDamage = 7;
hit[Types::palm_strike].bonusCrit = 20;
hit[Types::palm_strike].apCost = 6;
hit[Types::palm_strike].isPenetrate = true;
hit[Types::palm_strike].isSecondary = true;
// secondary 3
hit[Types::piercing_strike].reqLevel = 16;
hit[Types::piercing_strike].reqSkill = 130;
hit[Types::piercing_strike].reqStat[STAT_st] = 5;
hit[Types::piercing_strike].reqStat[STAT_ag] = 7;
hit[Types::piercing_strike].bonusDamage = 10;
hit[Types::piercing_strike].bonusCrit = 40;
hit[Types::piercing_strike].apCost = 8;
hit[Types::piercing_strike].isPenetrate = true;
hit[Types::piercing_strike].isSecondary = true;
// kick primary 1
hit[Types::strong_kick].reqLevel = 1;
hit[Types::strong_kick].reqSkill = 40;
hit[Types::strong_kick].reqStat[STAT_ag] = 6;
hit[Types::strong_kick].bonusDamage = 5;
hit[Types::strong_kick].apCost = 4;
// primary 2
hit[Types::snap_kick].reqLevel = 6;
hit[Types::snap_kick].reqSkill = 60;
hit[Types::snap_kick].reqStat[STAT_ag] = 6;
hit[Types::snap_kick].bonusDamage = 7;
hit[Types::snap_kick].apCost = 4;
// primary 3
hit[Types::power_kick].reqLevel = 9;
hit[Types::power_kick].reqSkill = 80;
hit[Types::power_kick].reqStat[STAT_st] = 6;
hit[Types::power_kick].reqStat[STAT_ag] = 6;
hit[Types::power_kick].bonusDamage = 9;
hit[Types::power_kick].bonusCrit = 5;
hit[Types::power_kick].apCost = 4;
// kick secondary 1
hit[Types::hip_kick].reqLevel = 6;
hit[Types::hip_kick].reqSkill = 60;
hit[Types::hip_kick].reqStat[STAT_st] = 6;
hit[Types::hip_kick].reqStat[STAT_ag] = 7;
hit[Types::hip_kick].bonusDamage = 7;
hit[Types::hip_kick].apCost = 7;
hit[Types::hip_kick].isSecondary = true;
// secondary 2
hit[Types::hook_kick].reqLevel = 12;
hit[Types::hook_kick].reqSkill = 100;
hit[Types::hook_kick].reqStat[STAT_st] = 6;
hit[Types::hook_kick].reqStat[STAT_ag] = 7;
hit[Types::hook_kick].bonusDamage = 9;
hit[Types::hook_kick].bonusCrit = 10;
hit[Types::hook_kick].apCost = 7;
hit[Types::hook_kick].isPenetrate = true;
hit[Types::hook_kick].isSecondary = true;
// secondary 3
hit[Types::piercing_kick].reqLevel = 15;
hit[Types::piercing_kick].reqSkill = 125;
hit[Types::piercing_kick].reqStat[STAT_st] = 6;
hit[Types::piercing_kick].reqStat[STAT_ag] = 8;
hit[Types::piercing_kick].bonusDamage = 12;
hit[Types::piercing_kick].bonusCrit = 50;
hit[Types::piercing_kick].apCost = 9;
hit[Types::piercing_kick].isPenetrate = true;
hit[Types::piercing_kick].isSecondary = true;
}
HitsData& Hit(AttackType i) { return hit[i]; }
// Get hit by index
HitsData& Hit(size_t index) { return hit[index]; }
AttackType GetSortHit(size_t index) { return (AttackType)sortHits[index].hit; }
void Sort() {
for (char i = ATKTYPE_STRONGPUNCH, j = 0; i < Hits::count; i++, j++) {
sortHits[j].level = (char)hit[i].reqLevel;
sortHits[j].hit = i;
}
auto greater_comp = [&](const SortHits &a, const SortHits &b) -> bool {
if (a.level == b.level) {
return hit[a.hit].reqSkill > hit[b.hit].reqSkill;
}
return a.level > b.level;
};
std::sort(&sortHits[0], &sortHits[6], greater_comp);
std::sort(&sortHits[6], &sortHits[12], greater_comp);
}
void GetDudeStats(long sLevel) {
skillLevel = sLevel;
for (size_t stat = 0; stat < STAT_base_count; stat++) {
psStat[stat] = fo_stat_level(*ptr_obj_dude, stat);
}
}
long SkillLevel() { return skillLevel; }
long DudeStat(long stat) { return psStat[stat]; }
};
Hits unarmed;
static bool UnarmedReqStats(AttackType hit) {
if (unarmed.SkillLevel() >= unarmed.Hit(hit).reqSkill && (long)*ptr_Level_pc >= unarmed.Hit(hit).reqLevel) {
for (size_t stat = 0; stat < STAT_base_count; stat++) {
if (unarmed.Hit(hit).reqStat[stat] == 0) continue;
if (unarmed.Hit(hit).reqStat[stat] > unarmed.DudeStat(stat)) {
return false;
}
}
return true;
}
return false;
}
static bool Punching(bool isPrimary) {
for (size_t i = 0; i < 6; i++) {
AttackType hit = unarmed.GetSortHit(i);
if (unarmed.Hit(hit).isSecondary == isPrimary) continue;
if (UnarmedReqStats(hit)) {
if (unarmed.Hit(hit).isSecondary) {
ptr_itemButtonItems[HANDSLOT_Left].secondaryAttack = hit;
} else {
ptr_itemButtonItems[HANDSLOT_Left].primaryAttack = hit;
}
return true;
}
}
return false;
}
// Punch hits
static void __fastcall check_unarmed_left_slot(long skillLevel) {
unarmed.GetDudeStats(skillLevel);
if (!Punching(true)) ptr_itemButtonItems[HANDSLOT_Left].primaryAttack = ATKTYPE_PUNCH;
if (!Punching(false)) ptr_itemButtonItems[HANDSLOT_Left].secondaryAttack = ATKTYPE_PUNCH;
}
static void __declspec(naked) intface_update_items_hack_punch() {
__asm {
push 0x45F1D7;
jmp check_unarmed_left_slot;
}
}
static bool Kicking(bool isPrimary) {
for (size_t i = 6; i < 12; i++) {
AttackType hit = unarmed.GetSortHit(i);
if (unarmed.Hit(hit).isSecondary == isPrimary) continue;
if (UnarmedReqStats(hit)) {
if (unarmed.Hit(hit).isSecondary) {
ptr_itemButtonItems[HANDSLOT_Right].secondaryAttack = hit;
} else {
ptr_itemButtonItems[HANDSLOT_Right].primaryAttack = hit;
}
return true;
}
}
return false;
}
// Kick hits
static void check_unarmed_right_slot() {
if (!Kicking(true)) ptr_itemButtonItems[HANDSLOT_Right].primaryAttack = ATKTYPE_KICK;
if (!Kicking(false)) ptr_itemButtonItems[HANDSLOT_Right].secondaryAttack = ATKTYPE_KICK;
}
static void __declspec(naked) intface_update_items_hack_kick() {
__asm {
push 0x45F380;
jmp check_unarmed_right_slot;
}
}
static long __fastcall get_unarmed_crit_chance(long &chanceOut, AttackType hit) {
chanceOut = unarmed.Hit(hit).bonusCrit;
return (chanceOut > 0)
? 0x4239F4 //
: 0x423A0D; // skip random roll
}
static void __declspec(naked) compute_attack_hack() {
__asm {
sub esp, 4;
mov ecx, esp; // chanceOut ref
call get_unarmed_crit_chance; // edx - hit
pop ecx; // chance
jmp eax;
}
}
static long __fastcall get_unarmed_damage(TGameObj* source, AttackType hit, long &minOut, long &maxOut) {
minOut = unarmed.Hit(hit).minDamage + DamageMod_GetHtHMinDamageBonus(source);
maxOut = unarmed.Hit(hit).maxDamage + fo_stat_level(source, STAT_melee_dmg);
return unarmed.Hit(hit).bonusDamage;
}
static void __declspec(naked) item_w_damage_hack() {
static DWORD item_w_damage_hack_ret = 0x478553;
__asm {
lea eax, [esp + 4]; // min_DMG
lea edx, [esp + 0]; // max_DMG
push ecx;
push edx; // maxOut ref
push eax; // minOut ref
mov edx, esi; // hit
call get_unarmed_damage;
mov ebx, eax; // bonus
pop ecx;
jmp item_w_damage_hack_ret;
}
}
static long __fastcall check_unarmed_penetrate(AttackType hit) {
return static_cast<long>(unarmed.Hit(hit).isPenetrate);
}
long Unarmed_GetHitAPCost(AttackType hit) {
return unarmed.Hit(hit).apCost;
}
////////////////////////////////////////////////////////////////////////////////
static void __declspec(naked) statPCAddExperienceCheckPMs_hook() {
__asm {
call intface_update_hit_points_;
sub esp, 8;
lea edx, [esp + 4]; // modeR
mov eax, esp; // modeL
call intface_get_item_states_;
pop edx;
pop ebx;
xor eax, eax;
jmp intface_update_items_;
}
}
void Unarmed_Init() {
// Update unarmed attack after leveling up
HookCall(0x4AFC15, statPCAddExperienceCheckPMs_hook);
unarmed = Hits();
std::string unarmedFile = GetConfigString("Misc", "UnarmedFile", "", MAX_PATH);
if (!unarmedFile.empty()) {
const char* file = unarmedFile.insert(0, ".\\").c_str();
if (GetFileAttributes(file) != INVALID_FILE_ATTRIBUTES) { // check if file exists
char stat[6] = "Stat0";
char sHit[4] = "0";
for (size_t i = 0; i < Hits::count; _itoa(++i, sHit, 10)) {
if (i < ATKTYPE_STRONGPUNCH && i != ATKTYPE_PUNCH && i != ATKTYPE_KICK) continue;
auto& hit = unarmed.Hit(i);
int val = IniGetInt(sHit, "ReqLevel", -1, file);
if (val >= 0) hit.reqLevel = val;
val = IniGetInt(sHit, "SkillLevel", -1, file);
if (val >= 0) hit.reqSkill = val;
val = IniGetInt(sHit, "MinDamage", -1, file);
if (val >= 0) hit.minDamage = val;
val = IniGetInt(sHit, "MaxDamage", -1, file);
if (val >= 0) hit.maxDamage = val;
val = IniGetInt(sHit, "BonusDamage", -1, file);
if (val >= 0) hit.bonusDamage = val;
val = IniGetInt(sHit, "BonusCrit", -1, file);
if (val >= 0) hit.bonusCrit = val;
val = IniGetInt(sHit, "APCost", -1, file);
if (val >= 0) hit.apCost = val;
val = IniGetInt(sHit, "Penetrate", -1, file);
if (val >= 0) hit.isPenetrate = (val != 0);
val = IniGetInt(sHit, "Secondary", -1, file);
if (val >= 0) hit.isSecondary = (val != 0);
for (size_t s = 0; s < STAT_base_count; _itoa(++s, &stat[4], 10)) {
val = IniGetInt(sHit, stat, -1, file);
if (val >= 0) hit.reqStat[s] = val;
}
stat[4] = '0';
}
// Only if a custom file is used, otherwise use the engine function code
MakeJump(0x45F0DF, intface_update_items_hack_punch);
MakeJump(0x45F278, intface_update_items_hack_kick);
}
}
unarmed.Sort();
// Get critical chance hack
MakeJump(0x42394D, compute_attack_hack);
SafeWrite16(0x423A03, 0xC839); // cmp eax, 50 -> cmp eax, ecx
SafeWrite8(0x423A05, CODETYPE_Nop);
// Get damage hack
MakeJump(0x478492, item_w_damage_hack);
// compute_damage_ penetrate hack
SafeWrite8(0x4248B4, 0x4E); // mov ecx, [hit]
MakeCall(0x4248B6, check_unarmed_penetrate, 5);
SafeWrite16(0x4248C1, 0x01F8); // cmp eax, 1
SafeWrite8(0x4248C8, CODETYPE_JumpShort);
}
//void Unarmed_Exit() {
//}
+24
View File
@@ -0,0 +1,24 @@
/*
* sfall
* Copyright (C) 2008-2021 The sfall team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
void Unarmed_Init();
//void Unarmed_Exit();
long Unarmed_GetHitAPCost(AttackType hit);
+3 -1
View File
@@ -124,7 +124,7 @@
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<DebugInformationFormat>None</DebugInformationFormat>
<CallingConvention>Cdecl</CallingConvention>
<ForcedIncludeFiles>stdafx.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
<FunctionLevelLinking>true</FunctionLevelLinking>
@@ -285,6 +285,7 @@
<ClInclude Include="Tiles.h" />
<ClInclude Include="SpeedPatch.h" />
<ClInclude Include="Translate.h" />
<ClInclude Include="Unarmed.h" />
<ClInclude Include="Utils.h" />
<ClInclude Include="version.h" />
<ClInclude Include="Logging.h" />
@@ -355,6 +356,7 @@
<ClCompile Include="Logging.cpp" />
<ClCompile Include="ExtraSaveSlots.cpp" />
<ClCompile Include="Translate.cpp" />
<ClCompile Include="Unarmed.cpp" />
<ClCompile Include="Utils.cpp" />
<ClCompile Include="Worldmap.cpp" />
</ItemGroup>
+6
View File
@@ -265,6 +265,9 @@
<ClInclude Include="EngineTweaks.h">
<Filter>Headers</Filter>
</ClInclude>
<ClInclude Include="Unarmed.h">
<Filter>Headers</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Console.cpp">
@@ -445,6 +448,9 @@
<ClCompile Include="EngineTweaks.cpp">
<Filter>Source</Filter>
</ClCompile>
<ClCompile Include="Unarmed.cpp">
<Filter>Source</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="exports.def" />
+5
View File
@@ -72,6 +72,7 @@
#include "TalkingHeads.h"
#include "Tiles.h"
#include "Translate.h"
#include "Unarmed.h"
#include "version.h"
#include "Worldmap.h"
@@ -127,6 +128,9 @@ static void InitModules() {
dlogr("Running Elevators_Init().", DL_INIT);
Elevators_Init();
dlogr("Running Unarmed_Init().", DL_INIT);
Unarmed_Init();
dlogr("Running Animations_Init().", DL_INIT);
Animations_Init();
@@ -244,6 +248,7 @@ static void __stdcall OnExit() {
Graphics_Exit();
//EngineTweaks_Exit();
Books_Exit();
//Unarmed_Exit();
//Animations_Exit();
BarBoxes_Exit();
//Message_Exit();