diff --git a/artifacts/scripting/hookscripts.txt b/artifacts/scripting/hookscripts.txt index 221881cd..088660a7 100644 --- a/artifacts/scripting/hookscripts.txt +++ b/artifacts/scripting/hookscripts.txt @@ -118,8 +118,8 @@ You can get the weapon object by checking item slot based on attack type (ATKTYP Critter arg0 - The critter performing the action int arg1 - Attack Type (see ATKTYPE_* constants) int arg2 - Is aimed attack (1 or 0) -int arg3 - The normal AP cost -Item arg4 - The weapon +int arg3 - The default AP cost +Item arg4 - The weapon for which the cost is calculated. If it is 0, the pointer to the weapon can still be obtained by the aforementioned method int ret0 - The new AP cost diff --git a/sfall/FalloutEngine/Functions_def.h b/sfall/FalloutEngine/Functions_def.h index 0b12b27c..59b46def 100644 --- a/sfall/FalloutEngine/Functions_def.h +++ b/sfall/FalloutEngine/Functions_def.h @@ -19,6 +19,7 @@ because the compiler builds the better/optimized code when calling the engine functions */ WRAP_WATCOM_FFUNC4(long, _word_wrap, const char*, text, int, maxWidth, DWORD*, buf, BYTE*, count) +WRAP_WATCOM_FFUNC3(long, ai_have_ammo, fo::GameObject*, critter, fo::GameObject*, item, fo::GameObject**, outAmmo) WRAP_WATCOM_FFUNC3(long, ai_pick_hit_mode, fo::GameObject*, source, fo::GameObject*, item, fo::GameObject*, target) WRAP_WATCOM_FFUNC3(fo::GameObject*, ai_search_inven_weap, fo::GameObject*, source, long, apCheck, fo::GameObject*, target) WRAP_WATCOM_FFUNC3(void, check_for_death, fo::GameObject*, critter, long, amountDamage, long*, flags) @@ -33,6 +34,7 @@ WRAP_WATCOM_FFUNC3(void, intface_update_items, long, animate, long, modeLeft, lo WRAP_WATCOM_FFUNC3(fo::GameObject*, inven_find_type, fo::GameObject*, critter, long, itemType, DWORD*, slot) WRAP_WATCOM_FFUNC3(long, inven_wield, fo::GameObject*, critter, fo::GameObject*, item, long, slot) WRAP_WATCOM_FFUNC3(long, item_add_force, fo::GameObject*, critter, fo::GameObject*, item, long, count) +WRAP_WATCOM_FFUNC3(long, item_mp_cost, fo::GameObject*, source, long, hitMode, long, isCalled) WRAP_WATCOM_FFUNC3(long, item_w_mp_cost, fo::GameObject*, source, long, hitMode, long, isCalled) WRAP_WATCOM_FFUNC7(void, make_straight_path_func, fo::GameObject*, objFrom, DWORD, tileFrom, DWORD, tileTo, void*, rotationPtr, DWORD*, result, long, flags, void*, func) WRAP_WATCOM_FFUNC3(long, message_find, DWORD*, msgFile, long, msgNumber, DWORD*, outBuf) diff --git a/sfall/Game/items.cpp b/sfall/Game/items.cpp index 7cbb3e60..78d98447 100644 --- a/sfall/Game/items.cpp +++ b/sfall/Game/items.cpp @@ -4,7 +4,7 @@ * */ -//#include "..\main.h" +#include "..\main.h" #include "..\FalloutEngine\Fallout2.h" #include "..\Modules\HookScripts\CombatHs.h" @@ -19,6 +19,8 @@ namespace game namespace sf = sfall; +static constexpr int reloadCostAP = 2; // engine default reload AP cost + long Items::item_weapon_range(fo::GameObject* source, fo::GameObject* weapon, long hitMode) { fo::Proto* wProto; if (!GetProto(weapon->protoId, &wProto)) return 0; @@ -43,10 +45,8 @@ long Items::item_weapon_range(fo::GameObject* source, fo::GameObject* weapon, lo return range; } -// TODO: replace all item_w_primary_mp_cost/item_w_secondary_mp_cost in engine with item_weapon_mp_cost function - // 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 Items::item_weapon_mp_cost(fo::GameObject* source, fo::GameObject* weapon, long hitMode, long isCalled) { +long __fastcall Items::item_weapon_mp_cost(fo::GameObject* source, fo::GameObject* weapon, long hitMode, long isCalled) { long cost = 0; switch (hitMode) { @@ -60,16 +60,16 @@ long Items::item_weapon_mp_cost(fo::GameObject* source, fo::GameObject* weapon, break; case fo::AttackType::ATKTYPE_LWEAPON_RELOAD: case fo::AttackType::ATKTYPE_RWEAPON_RELOAD: - if (source->protoId != fo::ProtoID::PID_SOLAR_SCORCHER && weapon) { - cost = 2; // default reload AP cost + if (weapon && weapon->protoId != fo::ProtoID::PID_SOLAR_SCORCHER) { // Solar Scorcher has no reload AP cost + cost = reloadCostAP; if (fo::GetProto(weapon->protoId)->item.weapon.perk == fo::Perk::PERK_weapon_fast_reload) { cost--; } } } if (hitMode < fo::AttackType::ATKTYPE_LWEAPON_RELOAD) { - if (cost == -1) cost = 0; if (isCalled) cost++; + if (cost < 0) cost = 0; long type = fo::func::item_w_subtype(weapon, hitMode); @@ -92,13 +92,26 @@ long Items::item_weapon_mp_cost(fo::GameObject* source, fo::GameObject* weapon, } // Implementation of item_w_mp_cost_ engine function with the HOOK_CALCAPCOST hook -long __fastcall Items::item_w_mp_cost(fo::GameObject* source, long hitMode, long isCalled) { +long Items::item_w_mp_cost(fo::GameObject* source, long hitMode, long isCalled) { long cost = fo::func::item_w_mp_cost(source, hitMode, isCalled); return sf::CalcApCostHook_Invoke(source, hitMode, isCalled, cost, nullptr); } +static void __declspec(naked) ai_search_inven_weap_hook() { + using namespace fo; + __asm { + push 0; // no called + push ATKTYPE_RWEAPON_PRIMARY; + mov edx, esi; // found weapon + mov ecx, edi; // source + call Items::item_weapon_mp_cost; + retn; + } +} + void Items::init() { - + // Replace the item_w_primary_mp_cost_ function with the sfall implementation + sf::HookCall(0x429A08, ai_search_inven_weap_hook); } } diff --git a/sfall/Game/items.h b/sfall/Game/items.h index 1e1cafdd..dd2352b5 100644 --- a/sfall/Game/items.h +++ b/sfall/Game/items.h @@ -16,10 +16,12 @@ public: static long item_weapon_range(fo::GameObject* source, fo::GameObject* weapon, long hitMode); // Implementation of item_w_primary_mp_cost_ and item_w_secondary_mp_cost_ engine functions in a single function with the HOOK_CALCAPCOST hook - static long item_weapon_mp_cost(fo::GameObject* source, fo::GameObject* weapon, long hitMode, long isCalled); + // Note: Use only for weapons + static long __fastcall item_weapon_mp_cost(fo::GameObject* source, fo::GameObject* weapon, long hitMode, long isCalled); // Implementation of item_w_mp_cost_ engine function with the HOOK_CALCAPCOST hook - static long __fastcall item_w_mp_cost(fo::GameObject* source, long hitMode, long isCalled); + // Note: Use the generic item_mp_cost function which has a hook call + static long item_w_mp_cost(fo::GameObject* source, long hitMode, long isCalled); }; } \ No newline at end of file diff --git a/sfall/Modules/AI.cpp b/sfall/Modules/AI.cpp index caa65626..ba26aa61 100644 --- a/sfall/Modules/AI.cpp +++ b/sfall/Modules/AI.cpp @@ -22,7 +22,6 @@ #include "..\FalloutEngine\Fallout2.h" #include "LoadGameHook.h" -//#include "HookScripts\CombatHS.h" #include "..\Game\items.h" #include "AI.h" @@ -287,6 +286,8 @@ end: } } +//////////////////////////////////////////////////////////////////////////////// + static long __fastcall ai_weapon_reload_fix(fo::GameObject* weapon, fo::GameObject* ammo, fo::GameObject* critter) { fo::Proto* proto = nullptr; long result = -1; @@ -339,11 +340,59 @@ skip: } } +//////////////////////////////////////////////////////////////////////////////// + +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 + 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; + __asm { + push ebx; // ammoObj ref + mov ecx, eax; // source + call item_weapon_reload_cost_fix; // edx - weapon + cmp eax, -1; + je noAPs; + retn; +noAPs: + add esp, 4; // destroy ret + jmp ai_try_attack_hook_unwield_Ret; // unwield weapon (default) + } +} + +static void __declspec(naked) ai_try_attack_hook_cost1() { + __asm { + xor ebx, ebx; + sub edx, tempReloadCost; // curr.mp - reload cost + cmovg ebx, edx; // if curr.mp > 0 + retn; + } +} + +static void __declspec(naked) ai_try_attack_hook_cost2() { + __asm { + xor ecx, ecx; + sub ebx, tempReloadCost; // curr.mp - reload cost + cmovg ecx, ebx; // if curr.mp > 0 + retn; + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + static long __fastcall CheckWeaponRangeAndApCost(fo::GameObject* source, fo::GameObject* target) { long weaponRange = fo::func::item_w_range(source, fo::ATKTYPE_RWEAPON_SECONDARY); long targetDist = fo::func::obj_dist(source, target); if (targetDist > weaponRange) return 0; // don't use secondary mode + //return (source->critter.movePoints >= fo::func::item_mp_cost(source, fo::ATKTYPE_RWEAPON_SECONDARY, 0)); return (source->critter.movePoints >= game::Items::item_w_mp_cost(source, fo::ATKTYPE_RWEAPON_SECONDARY, 0)); // 1 - allow secondary mode } @@ -376,15 +425,15 @@ fix: // check result static void __declspec(naked) cai_perform_distance_prefs_hack() { using namespace fo; __asm { - mov ebx, eax; // current distance to target - mov ecx, esi; - push 0; // no called shot + mov ecx, eax; // current distance to target + xor ebx, ebx; // no called shot mov edx, ATKTYPE_RWEAPON_PRIMARY; - call game::Items::item_w_mp_cost; + mov eax, esi; + call fo::funcoffs::item_mp_cost_; mov edx, [esi + movePoints]; sub edx, eax; // ap - cost = free AP's jle moveAway; // <= 0 - lea edx, [edx + ebx - 1]; + lea edx, [edx + ecx - 1]; cmp edx, 5; // minimum threshold distance jge skipMove; // distance >= 5? // check combat rating @@ -504,6 +553,11 @@ void AI::init() { 0x42A970, 0x42AA56, // ai_try_attack_ }); + // Fix incorrect AP check and cost for AI when reloading a weapon + HookCall(0x42A955, ai_try_attack_hook_cost_reload); + MakeCall(0x42A9DE, ai_try_attack_hook_cost1); + MakeCall(0x42AABC, ai_try_attack_hook_cost2, 4); + // Adds a check for the weapon range and the AP cost when AI is choosing weapon attack modes HookCall(0x429F6D, ai_pick_hit_mode_hook); diff --git a/sfall/Modules/Criticals.cpp b/sfall/Modules/Criticals.cpp index 651327cc..8615517e 100644 --- a/sfall/Modules/Criticals.cpp +++ b/sfall/Modules/Criticals.cpp @@ -158,7 +158,6 @@ static void CriticalTableOverride() { if (mode == 2 || mode == 3) { // bug fixes using namespace fo; - // Children SetEntry(2, LegRight, 1, FlagsFail, 0); SetEntry(2, LegRight, 1, Message, 5216); diff --git a/sfall/ReplacementFuncs.h b/sfall/ReplacementFuncs.h index 9e0adeca..e87efd0c 100644 --- a/sfall/ReplacementFuncs.h +++ b/sfall/ReplacementFuncs.h @@ -7,6 +7,7 @@ #pragma once #include "Game\inventory.h" +#include "Game\items.h" #include "Game\render.h" #include "Game\skills.h" #include "Game\stats.h" diff --git a/sfall/main.cpp b/sfall/main.cpp index 871cc5e1..0786d486 100644 --- a/sfall/main.cpp +++ b/sfall/main.cpp @@ -172,6 +172,7 @@ int SetConfigInt(const char* section, const char* setting, int value) { void InitReplacementHacks() { game::Inventory::init(); + game::Items::init(); game::Render::init(); game::Skills::init(); game::Stats::init();