diff --git a/artifacts/scripting/hookscripts.txt b/artifacts/scripting/hookscripts.txt index 088660a7..c4406bc4 100644 --- a/artifacts/scripting/hookscripts.txt +++ b/artifacts/scripting/hookscripts.txt @@ -349,6 +349,7 @@ int arg2 - Ammo cost calculated by original function (this is basically 2 fo int arg3 - Type of hook (0 - when subtracting ammo after single shot attack, 1 - when checking for "out of ammo" before attack, 2 - when calculating number of burst rounds, 3 - when subtracting ammo after burst attack) int ret0 - new ammo cost value (set to 0 for unlimited ammo) + NOTE: for hook type 2, you should return the new cost value based on the original ammo cost instead of the number of bullets ------------------------------------------- diff --git a/sfall/Modules/AI.cpp b/sfall/Modules/AI.cpp index 35306933..3150c6b6 100644 --- a/sfall/Modules/AI.cpp +++ b/sfall/Modules/AI.cpp @@ -346,14 +346,14 @@ static long tempReloadCost; static long __fastcall item_weapon_reload_cost_fix(fo::GameObject* source, fo::GameObject* weapon, fo::GameObject** outAmmo) { long reloadCost = game::Items::item_weapon_mp_cost(source, weapon, fo::AttackType::ATKTYPE_RWEAPON_RELOAD, 0); - if (reloadCost > source->critter.movePoints) return -1; // no action points + if (reloadCost > source->critter.movePoints) return -1; // not enough action points tempReloadCost = reloadCost; return fo::func::ai_have_ammo(source, weapon, outAmmo); // 0 - no ammo } static void __declspec(naked) ai_try_attack_hook_cost_reload() { - static const DWORD ai_try_attack_hook_unwield_Ret = 0x42AACD; + static const DWORD ai_try_attack_hook_goNext_Ret = 0x42A9F2; __asm { push ebx; // ammoObj ref mov ecx, eax; // source @@ -361,9 +361,10 @@ static void __declspec(naked) ai_try_attack_hook_cost_reload() { cmp eax, -1; je noAPs; retn; -noAPs: +noAPs: // not enough action points add esp, 4; // destroy ret - jmp ai_try_attack_hook_unwield_Ret; // unwield weapon (default) + mov edi, 10; + jmp ai_try_attack_hook_goNext_Ret; // end ai_try_attack_ } } diff --git a/sfall/Modules/Combat.cpp b/sfall/Modules/Combat.cpp index 7117e983..e826886f 100644 --- a/sfall/Modules/Combat.cpp +++ b/sfall/Modules/Combat.cpp @@ -89,34 +89,39 @@ static bool hookedAimedShot; static std::vector disabledAS; static std::vector forcedAS; -DWORD __fastcall Combat::check_item_ammo_cost(fo::GameObject* weapon, DWORD hitMode) { - DWORD rounds = 1; +static bool checkWeaponAmmoCost; - long anim = fo::func::item_w_anim_weap(weapon, (fo::AttackType)hitMode); +// Compares the cost (required count of rounds) for one shot with the current amount of ammo to make an attack or other checks +long __fastcall Combat::check_item_ammo_cost(fo::GameObject* weapon, fo::AttackType hitMode) { + long currAmmo = fo::func::item_w_curr_ammo(weapon); + if (!checkWeaponAmmoCost || currAmmo <= 0) return currAmmo; + + long rounds = 1; // default ammo for single shot + + long anim = fo::func::item_w_anim_weap(weapon, hitMode); if (anim == fo::Animation::ANIM_fire_burst || anim == fo::Animation::ANIM_fire_continuous) { rounds = fo::func::item_w_rounds(weapon); // ammo in burst } - if (HookScripts::IsInjectHook(HOOK_AMMOCOST)) { - AmmoCostHook_Script(1, weapon, rounds); // get rounds cost from hook - } else if (rounds == 1) { - fo::func::item_w_compute_ammo_cost(weapon, &rounds); - } - long currAmmo = fo::func::item_w_curr_ammo(weapon); - long cost = 1; // default cost - if (currAmmo > 0) { - cost = rounds / currAmmo; - if (rounds % currAmmo) cost++; // round up + DWORD newRounds = rounds; + + if (HookScripts::IsInjectHook(HOOK_AMMOCOST)) { + AmmoCostHook_Script(1, weapon, newRounds); // newRounds returns the new "ammo" value multiplied by the cost + } else if (rounds == 1) { // NOTE: it doesn't make sense to call item_w_compute_ammo_cost for rounds greater than one + fo::func::item_w_compute_ammo_cost(weapon, &newRounds); } - return (cost > currAmmo) ? 0 : 1; // 0 - this will force "Out of ammo", 1 - this will force success (enough ammo) + + // calculate the cost + long cost = (newRounds != rounds) ? newRounds / rounds : 1; // 1 - default cost + return (cost > currAmmo) ? 0 : currAmmo; // 0 - this will force "Not Enough Ammo" } // adds check for weapons which require more than 1 ammo for single shot (super cattle prod & mega power fist) and burst rounds static void __declspec(naked) combat_check_bad_shot_hook() { __asm { push edx; - push ecx; // weapon - mov edx, edi; // hitMode + push ecx; // weapon + mov edx, edi; // hitMode call Combat::check_item_ammo_cost; pop ecx; pop edx; @@ -129,9 +134,9 @@ static void __declspec(naked) ai_search_inven_weap_hook() { using namespace fo; __asm { push ecx; - mov ecx, eax; // weapon - mov edx, ATKTYPE_RWEAPON_PRIMARY; // hitMode - call Combat::check_item_ammo_cost; // enough ammo? + mov edx, ATKTYPE_RWEAPON_PRIMARY; // hitMode + mov ecx, eax; // weapon + call Combat::check_item_ammo_cost; // enough ammo? pop ecx; retn; } @@ -160,34 +165,36 @@ tryAttack: } } -static DWORD __fastcall divide_burst_rounds_by_ammo_cost(fo::GameObject* weapon, register DWORD currAmmo, DWORD burstRounds) { - DWORD rounds = 1; // default multiply +/*** The return value should set the correct count of rounds for the burst calculated based on the cost ***/ +static long __fastcall divide_burst_rounds_by_ammo_cost(long currAmmo, fo::GameObject* weapon, long burstRounds) { + DWORD roundsCost = 1; // default burst cost if (HookScripts::IsInjectHook(HOOK_AMMOCOST)) { - rounds = burstRounds; // rounds in burst - AmmoCostHook_Script(2, weapon, rounds); + roundsCost = burstRounds; // rounds in burst (the number of rounds fired in the burst) + AmmoCostHook_Script(2, weapon, roundsCost); // roundsCost returns the new cost } - DWORD cost = burstRounds * rounds; // so much ammo is required for this burst + long cost = burstRounds * roundsCost; // amount of ammo required for this burst (multiplied by 1 or by the value returned from HOOK_AMMOCOST) if (cost > currAmmo) cost = currAmmo; // if cost ammo more than current ammo, set it to current - return (cost / rounds); // divide back to get proper number of rounds for damage calculations + return (cost / roundsCost); // divide back to get proper number of rounds for damage calculations } static void __declspec(naked) compute_spray_hack() { - __asm { - push edx; // weapon - push ecx; // current ammo in weapon - xchg ecx, edx; - push eax; // eax - rounds in burst attack, need to set ebp + __asm { // ebp = current ammo +// push edx; // weapon +// push ecx; // current ammo in weapon + push eax; // rounds in burst attack, need to set ebp call divide_burst_rounds_by_ammo_cost; - mov ebp, eax; // overwriten code - pop ecx; - pop edx; + mov ebp, eax; // overwriten code +// pop ecx; +// pop edx; retn; } } +//////////////////////////////////////////////////////////////////////////////// + static double ApplyModifiers(std::vector* mods, fo::GameObject* object, double val) { for (size_t i = 0; i < mods->size(); i++) { KnockbackModifier* mod = &(*mods)[i]; @@ -271,10 +278,11 @@ bool Combat::IsBurstDisabled(fo::GameObject* critter) { } static long __fastcall CheckDisableBurst(fo::GameObject* critter, fo::GameObject* weapon) { - if (fo::func::item_w_anim_weap(weapon, fo::AttackType::ATKTYPE_RWEAPON_SECONDARY) == fo::Animation::ANIM_fire_burst && - Combat::IsBurstDisabled(critter)) - { - return 10; // Disable Burst (area_attack_mode - non-existent value) + if (Combat::IsBurstDisabled(critter)) { + long anim = fo::func::item_w_anim_weap(weapon, fo::AttackType::ATKTYPE_RWEAPON_SECONDARY); + if (anim == fo::Animation::ANIM_fire_burst || anim == fo::Animation::ANIM_fire_continuous) { + return 10; // Disable Burst (area_attack_mode - non-existent value) + } } return 0; } @@ -520,10 +528,11 @@ void Combat::init() { // Disables secondary burst attacks for the critter MakeCall(0x429E44, ai_pick_hit_mode_hack_noBurst, 1); - if (GetConfigInt("Misc", "CheckWeaponAmmoCost", 0)) { + checkWeaponAmmoCost = (GetConfigInt("Misc", "CheckWeaponAmmoCost", 0) != 0); + if (checkWeaponAmmoCost) { MakeCall(0x4234B3, compute_spray_hack, 1); HookCall(0x4266E9, combat_check_bad_shot_hook); - HookCall(0x429A37, ai_search_inven_weap_hook); + HookCall(0x429A37, ai_search_inven_weap_hook); // check if there is enough ammo to shoot HookCall(0x42A95D, ai_try_attack_hook); // jz func } diff --git a/sfall/Modules/Combat.h b/sfall/Modules/Combat.h index 870442e4..2ad8bdc0 100644 --- a/sfall/Modules/Combat.h +++ b/sfall/Modules/Combat.h @@ -49,7 +49,7 @@ public: static long determineHitChance; - static DWORD __fastcall check_item_ammo_cost(fo::GameObject* weapon, DWORD hitMode); + static long __fastcall check_item_ammo_cost(fo::GameObject* weapon, fo::AttackType hitMode); static bool IsBurstDisabled(fo::GameObject* critter); }; diff --git a/sfall/Modules/HookScripts/CombatHs.cpp b/sfall/Modules/HookScripts/CombatHs.cpp index 0980d798..e9e08f52 100644 --- a/sfall/Modules/HookScripts/CombatHs.cpp +++ b/sfall/Modules/HookScripts/CombatHs.cpp @@ -343,7 +343,7 @@ int __fastcall AmmoCostHook_Script(DWORD hookType, fo::GameObject* weapon, DWORD rounds = 1; // set default multiply for check burst attack } else { result = fo::func::item_w_compute_ammo_cost(weapon, &rounds); - if (result == -1) goto failed; // failed computed + if (result == -1) goto failed; // computation failed } args[2] = rounds; // rounds as computed by game (cost) @@ -367,7 +367,7 @@ static void __declspec(naked) AmmoCostHook() { mov ecx, 3; // hook type burst skip: xchg eax, edx; - push eax; // rounds in attack + push eax; // rounds in attack ref call AmmoCostHook_Script; // edx - weapon retn; } @@ -634,7 +634,7 @@ void Inject_ItemDamageHook() { } void Inject_AmmoCostHook() { - HookCall(0x423A7C, AmmoCostHook); + HookCall(0x423A7C, AmmoCostHook); // compute_attack_ } void Inject_CombatTurnHook() {