From 334957d45815294cfb1cfcfa7266a24417d02997 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Thu, 23 Sep 2021 10:43:38 +0800 Subject: [PATCH] Backported Unarmed module and UnarmedFile option from 4.x --- artifacts/config_files/Unarmed.ini | 64 +++++ artifacts/ddraw.ini | 7 +- sfall/BugFixes.cpp | 6 +- sfall/DamageMod.cpp | 18 +- sfall/DamageMod.h | 2 + sfall/FalloutEngine.cpp | 53 ++-- sfall/FalloutEngine.h | 3 + sfall/FalloutFuncOffs_def.h | 1 + sfall/HookScripts.cpp | 39 ++- sfall/Perks.cpp | 20 +- sfall/ReplacementFuncs.cpp | 131 ++++++--- sfall/ReplacementFuncs.h | 6 +- sfall/Unarmed.cpp | 443 +++++++++++++++++++++++++++++ sfall/Unarmed.h | 24 ++ sfall/ddraw.vcxproj | 4 +- sfall/ddraw.vcxproj.filters | 6 + sfall/main.cpp | 5 + 17 files changed, 732 insertions(+), 100 deletions(-) create mode 100644 artifacts/config_files/Unarmed.ini create mode 100644 sfall/Unarmed.cpp create mode 100644 sfall/Unarmed.h diff --git a/artifacts/config_files/Unarmed.ini b/artifacts/config_files/Unarmed.ini new file mode 100644 index 00000000..83693beb --- /dev/null +++ b/artifacts/config_files/Unarmed.ini @@ -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] diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 7ad2c8b0..4839f8c1 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -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 diff --git a/sfall/BugFixes.cpp b/sfall/BugFixes.cpp index bfc7076a..f6a1970f 100644 --- a/sfall/BugFixes.cpp +++ b/sfall/BugFixes.cpp @@ -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); //} diff --git a/sfall/DamageMod.cpp b/sfall/DamageMod.cpp index fb93580a..6aaa24f7 100644 --- a/sfall/DamageMod.cpp +++ b/sfall/DamageMod.cpp @@ -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); } diff --git a/sfall/DamageMod.h b/sfall/DamageMod.h index 6089974d..ffe43f96 100644 --- a/sfall/DamageMod.h +++ b/sfall/DamageMod.h @@ -19,3 +19,5 @@ #pragma once void DamageMod_Init(); + +long DamageMod_GetHtHMinDamageBonus(TGameObj* source); diff --git a/sfall/FalloutEngine.cpp b/sfall/FalloutEngine.cpp index 77a49cd4..df793ea4 100644 --- a/sfall/FalloutEngine.cpp +++ b/sfall/FalloutEngine.cpp @@ -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) { diff --git a/sfall/FalloutEngine.h b/sfall/FalloutEngine.h index de42e0b7..3e4ffbda 100644 --- a/sfall/FalloutEngine.h +++ b/sfall/FalloutEngine.h @@ -591,6 +591,9 @@ long& GetActiveItemMode(); TGameObj* GetActiveItem(); +// Hand: 0 - left, 1 - right +AttackType GetSlotHitMode(HandSlot hand); + long GetCurrentAttackMode(); AttackSubType GetWeaponType(DWORD weaponFlag); diff --git a/sfall/FalloutFuncOffs_def.h b/sfall/FalloutFuncOffs_def.h index 54f38287..7715f45f 100644 --- a/sfall/FalloutFuncOffs_def.h +++ b/sfall/FalloutFuncOffs_def.h @@ -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) diff --git a/sfall/HookScripts.cpp b/sfall/HookScripts.cpp index 0daabce8..55c81093 100644 --- a/sfall/HookScripts.cpp +++ b/sfall/HookScripts.cpp @@ -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 diff --git a/sfall/Perks.cpp b/sfall/Perks.cpp index 8e64954a..bfcee6ba 100644 --- a/sfall/Perks.cpp +++ b/sfall/Perks.cpp @@ -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); } diff --git a/sfall/ReplacementFuncs.cpp b/sfall/ReplacementFuncs.cpp index 8bc8a01c..60f2f9db 100644 --- a/sfall/ReplacementFuncs.cpp +++ b/sfall/ReplacementFuncs.cpp @@ -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 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 diff --git a/sfall/ReplacementFuncs.h b/sfall/ReplacementFuncs.h index e3e2f599..9116c9c5 100644 --- a/sfall/ReplacementFuncs.h +++ b/sfall/ReplacementFuncs.h @@ -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); diff --git a/sfall/Unarmed.cpp b/sfall/Unarmed.cpp new file mode 100644 index 00000000..4e0cbfe4 --- /dev/null +++ b/sfall/Unarmed.cpp @@ -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 . + */ + +#include + +#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(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() { +//} diff --git a/sfall/Unarmed.h b/sfall/Unarmed.h new file mode 100644 index 00000000..50b46418 --- /dev/null +++ b/sfall/Unarmed.h @@ -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 . + */ + +#pragma once + +void Unarmed_Init(); +//void Unarmed_Exit(); + +long Unarmed_GetHitAPCost(AttackType hit); diff --git a/sfall/ddraw.vcxproj b/sfall/ddraw.vcxproj index 93e2a5ce..d0ab4814 100644 --- a/sfall/ddraw.vcxproj +++ b/sfall/ddraw.vcxproj @@ -124,7 +124,7 @@ false Use Level3 - ProgramDatabase + None Cdecl stdafx.h;%(ForcedIncludeFiles) true @@ -285,6 +285,7 @@ + @@ -355,6 +356,7 @@ + diff --git a/sfall/ddraw.vcxproj.filters b/sfall/ddraw.vcxproj.filters index 2abb8fdf..d60e7151 100644 --- a/sfall/ddraw.vcxproj.filters +++ b/sfall/ddraw.vcxproj.filters @@ -265,6 +265,9 @@ Headers + + Headers + @@ -445,6 +448,9 @@ Source + + Source + diff --git a/sfall/main.cpp b/sfall/main.cpp index f8a12dd6..80d9f4b3 100644 --- a/sfall/main.cpp +++ b/sfall/main.cpp @@ -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();