From 7322634780c707948b739682b7b2fc3eb753797a Mon Sep 17 00:00:00 2001 From: NovaRain Date: Tue, 6 Jun 2023 21:46:03 +0800 Subject: [PATCH] Backported three game hooks from 4.x: * ROLLCHECK, BESTWEAPON, CANUSEWEAPON Updated documents. --- artifacts/ddraw.ini | 2 +- artifacts/scripting/headers/sfall.h | 6 ++ artifacts/scripting/hookscripts.md | 59 +++++++++++ sfall/FalloutEngine/FunctionOffsets_def.h | 5 +- sfall/FalloutEngine/Functions_def.h | 1 + sfall/Game/combatAI.cpp | 8 +- sfall/Game/combatAI.h | 2 + sfall/Modules/HookScripts.cpp | 5 + sfall/Modules/HookScripts.h | 5 + sfall/Modules/HookScripts/CombatHs.cpp | 116 +++++++++++++++++++++- sfall/Modules/HookScripts/CombatHs.h | 5 + sfall/Modules/HookScripts/MiscHs.cpp | 65 ++++++++++++ sfall/Modules/HookScripts/MiscHs.h | 1 + 13 files changed, 276 insertions(+), 4 deletions(-) diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 03649039..0591d684 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -131,7 +131,7 @@ OverrideMusicDir=1 ;This will slightly increase the startup time of the game on older computers AutoSearchSFX=1 -;Uncomment these lines to override the names of sound files for game modes +;Uncomment these lines to override the names of sound files used by the engine ;Filenames are limited to 8 characters (without extension) ;MainMenuMusic=07desert ;WorldMapMusic=23world diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index 08d73f76..0b01db33 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -70,6 +70,11 @@ #define HOOK_STDPROCEDURE_END (41) #define HOOK_TARGETOBJECT (42) #define HOOK_ENCOUNTER (43) +//#define HOOK_ADJUSTPOISON (44) // unimplemented +//#define HOOK_ADJUSTRADS (45) // unimplemented +#define HOOK_ROLLCHECK (46) +#define HOOK_BESTWEAPON (47) +#define HOOK_CANUSEWEAPON (48) // Valid arguments to list_begin #define LIST_CRITTERS (0) @@ -261,6 +266,7 @@ #define check_pid(pid) (get_proto_data(pid, 0) != -1) // sets the status of an unusable weapon that cannot be used in combat +// use the HOOK_CANUSEWEAPON hook with weapon_is_unusable macro to override the engine value #define set_weapon_unusable(item) set_object_data(item, OBJ_DATA_MISC_FLAGS, get_object_data(item, OBJ_DATA_MISC_FLAGS) bwor 0x00000010) #define set_weapon_usable(item) set_object_data(item, OBJ_DATA_MISC_FLAGS, get_object_data(item, OBJ_DATA_MISC_FLAGS) bwand 0xFFFFFFEF) #define weapon_is_unusable(item) (get_object_data(item, OBJ_DATA_MISC_FLAGS) bwand 0x00000010) diff --git a/artifacts/scripting/hookscripts.md b/artifacts/scripting/hookscripts.md index eb774f94..4ae39022 100644 --- a/artifacts/scripting/hookscripts.md +++ b/artifacts/scripting/hookscripts.md @@ -795,3 +795,62 @@ int arg2 - 1 when the encounter occurs is a special encounter, 0 otherwise int ret0 - overrides the map ID, or pass -1 for event type 0 to cancel the encounter and continue traveling int ret1 - pass 1 to cancel the encounter and load the specified map from the ret0 (only for event type 0) ``` + +------------------------------------------- + +#### `HOOK_ROLLCHECK (hs_rollcheck.int)` + +Runs when a game event performs a random roll to check the chance of success or failure. + +``` +int arg0 - event type: + 1 - checks the chance of an attack hitting the target + 2 - checks the chance of a bullet from a burst hitting the target (for burst attacks) + 3 - checks the chance when using skills (not listed below) + 4 - check the chance of using Repair skill + 5 - check the chance of using Doctor skill + 6 - check the chance of using Steal skill for the thief (usually the player) + 7 - the second Steal skill chance check for the target to catch the thief, in which the target's failure is the thief's success result +int arg1 - the value of roll result (see ROLL_* constants), which is calculated as: + for ROLL_CRITICAL_SUCCESS: random(1, 100) <= (random_chance / 10) + bonus + for ROLL_CRITICAL_FAILURE: random(1, 100) <= -random_chance / 10 +int arg2 - the chance value +int arg3 - the bonus value, used when checking critical success +int arg4 - random chance, calculated as: (chance - random(1, 100)), where a negative value is a failure check (ROLL_FAILURE) + +int ret0 - overrides the roll result +``` + +------------------------------------------- + +#### `HOOK_BESTWEAPON (hs_bestweapon.int)` + +Runs when the AI decides which weapon is the best while searching the inventory for a weapon to equip in combat.\ +This also runs when the player presses the "Use Best Weapon" button on the party member control panel. + +``` +Critter arg0 - the critter searching for a weapon +Item arg1 - the best weapon chosen from two items +Item arg2 - the first choice of weapon +Item arg3 - the second choice of weapon +Critter arg4 - the target of the critter (can be 0) + +Item ret0 - overrides the chosen best weapon +``` + +------------------------------------------- + +#### `HOOK_CANUSEWEAPON (hs_canuseweapon.int)` + +Run when the AI checks whether it can use a weapon, or when the game checks whether the player can use an item (weapon) in hand slot.\ +For AI, this mostly happens when NPCs try to find weapons in their inventory or on the map.\ +For the player, this happens when the game updates the item data for active item slots on the interface bar. + +``` +Critter arg0 - the critter doing the check +Item arg1 - the item being checked +int arg2 - attack type (see ATKTYPE_* constants), or -1 for dude_obj +int arg3 - original result of engine function: 1 - can use, 0 - cannot use + +int ret0 - overrides the result of engine function. Any non-zero value allows using the weapon +``` diff --git a/sfall/FalloutEngine/FunctionOffsets_def.h b/sfall/FalloutEngine/FunctionOffsets_def.h index 2a59069e..cd4db600 100644 --- a/sfall/FalloutEngine/FunctionOffsets_def.h +++ b/sfall/FalloutEngine/FunctionOffsets_def.h @@ -47,7 +47,8 @@ FUNC(action_use_skill_on_, 0x41255C) FUNC(add_bar_box_, 0x4616F0) FUNC(adjust_ac_, 0x4715F8) FUNC(adjust_fid_, 0x4716E8) -FUNC(ai_can_use_weapon_, 0x4298EC) // (GameObject *aCritter, int aWeapon, int a2Or3) returns 1 or 0 +FUNC(ai_best_weapon_, 0x4293BC) +FUNC(ai_can_use_weapon_, 0x4298EC) // (fo::GameObject *aCritter, int aWeapon, int a2Or3) returns 1 or 0 FUNC(ai_cap_, 0x4280B4) FUNC(ai_check_drugs_, 0x428480) FUNC(ai_danger_source_, 0x428F4C) @@ -630,6 +631,8 @@ FUNC(register_object_turn_towards_, 0x414C50) // int aObj, int aTile FUNC(remove_bk_process_, 0x4C8DC4) FUNC(report_explosion_, 0x413144) FUNC(reset_box_bar_win_, 0x4614A0) +FUNC(roll_check_, 0x4A3000) +FUNC(roll_check_critical_, 0x4A3030) FUNC(roll_random_, 0x4A30C0) FUNC(runProgram_, 0x46E154) // eax - programPtr, called once for each program after first loaded - hooks program to game and UI events FUNC(scr_build_lookup_table_, 0x4A49D0) diff --git a/sfall/FalloutEngine/Functions_def.h b/sfall/FalloutEngine/Functions_def.h index 8f1496d6..8e7e92c2 100644 --- a/sfall/FalloutEngine/Functions_def.h +++ b/sfall/FalloutEngine/Functions_def.h @@ -20,6 +20,7 @@ changed to stdcall wrappers instead for stability */ WRAP_WATCOM_FUNC4(long, _word_wrap, const char*, text, int, maxWidth, DWORD*, buf, BYTE*, count) +WRAP_WATCOM_FUNC3(bool, ai_can_use_weapon, fo::GameObject*, critter, fo::GameObject*, item, DWORD, hitMode) WRAP_WATCOM_FUNC3(long, ai_have_ammo, fo::GameObject*, critter, fo::GameObject*, item, fo::GameObject**, outAmmo) WRAP_WATCOM_FUNC3(long, ai_pick_hit_mode, fo::GameObject*, source, fo::GameObject*, item, fo::GameObject*, target) WRAP_WATCOM_FUNC3(long, ai_magic_hands, fo::GameObject*, source, fo::GameObject*, object, long, msgNumber) diff --git a/sfall/Game/combatAI.cpp b/sfall/Game/combatAI.cpp index 0f99c973..45f94fbe 100644 --- a/sfall/Game/combatAI.cpp +++ b/sfall/Game/combatAI.cpp @@ -19,7 +19,13 @@ namespace game namespace sf = sfall; static const long aiUseItemAPCost = 2; - +/* +// Implementation of ai_can_use_weapon_ engine function with the HOOK_CANUSEWEAPON hook +bool CombatAI::ai_can_use_weapon(fo::GameObject* source, fo::GameObject* weapon, long hitMode) { + bool result = fo::func::ai_can_use_weapon(source, weapon, hitMode); + return sf::CanUseWeaponHook_Invoke(result, source, weapon, hitMode); +} +*/ static long drugUsePerfFixMode; void __stdcall CombatAI::ai_check_drugs(fo::GameObject* source) { diff --git a/sfall/Game/combatAI.h b/sfall/Game/combatAI.h index 5ffd1b65..5e4a62e2 100644 --- a/sfall/Game/combatAI.h +++ b/sfall/Game/combatAI.h @@ -15,6 +15,8 @@ class CombatAI { public: static void init(); + //static bool ai_can_use_weapon(fo::GameObject* source, fo::GameObject* weapon, long hitMode); + static void __stdcall ai_check_drugs(fo::GameObject* source); }; diff --git a/sfall/Modules/HookScripts.cpp b/sfall/Modules/HookScripts.cpp index a3c37762..0afffa0c 100644 --- a/sfall/Modules/HookScripts.cpp +++ b/sfall/Modules/HookScripts.cpp @@ -106,6 +106,11 @@ static HooksInjectInfo injectHooks[] = { {HOOK_STDPROCEDURE_END, Inject_ScriptProcedureHook2, false}, {HOOK_TARGETOBJECT, Inject_TargetObjectHook, false}, {HOOK_ENCOUNTER, Inject_EncounterHook, false}, + {-1, nullptr, false}, // dummy + {-1, nullptr, false}, // dummy + {HOOK_ROLLCHECK, Inject_RollCheckHook, false}, + {HOOK_BESTWEAPON, Inject_BestWeaponHook, false}, + {HOOK_CANUSEWEAPON, Inject_CanUseWeaponHook, false}, }; void HookScripts::InjectingHook(int hookId) { diff --git a/sfall/Modules/HookScripts.h b/sfall/Modules/HookScripts.h index 10e4448b..ae03c6f2 100644 --- a/sfall/Modules/HookScripts.h +++ b/sfall/Modules/HookScripts.h @@ -69,6 +69,11 @@ enum HookType HOOK_STDPROCEDURE_END = 41, HOOK_TARGETOBJECT = 42, HOOK_ENCOUNTER = 43, +// HOOK_ADJUSTPOISON = 44, // unimplemented +// HOOK_ADJUSTRADS = 45, // unimplemented + HOOK_ROLLCHECK = 46, + HOOK_BESTWEAPON = 47, + HOOK_CANUSEWEAPON = 48, HOOK_COUNT }; diff --git a/sfall/Modules/HookScripts/CombatHs.cpp b/sfall/Modules/HookScripts/CombatHs.cpp index a05b3326..3997fd30 100644 --- a/sfall/Modules/HookScripts/CombatHs.cpp +++ b/sfall/Modules/HookScripts/CombatHs.cpp @@ -228,7 +228,11 @@ static void __fastcall FindTargetHook_Script(DWORD* target, fo::GameObject* atta } EndHook(); } - +/* +void FindTargetHook_Invoke(fo::GameObject* targets[], fo::GameObject* attacker) { + if (HookScripts::HookHasScript(HOOK_FINDTARGET)) FindTargetHook_Script((DWORD*)targets, attacker); +} +*/ static void __declspec(naked) FindTargetHook() { __asm { push eax; @@ -426,6 +430,99 @@ default: } } +static fo::GameObject* __stdcall BestWeaponHook_Script(fo::GameObject* bestWeapon, fo::GameObject* source, fo::GameObject* weapon1, fo::GameObject* weapon2, fo::GameObject* target) { + BeginHook(); + argCount = 5; + + args[0] = (DWORD)source; + args[1] = (DWORD)bestWeapon; + args[2] = (DWORD)weapon1; + args[3] = (DWORD)weapon2; + args[4] = (DWORD)target; + + RunHookScript(HOOK_BESTWEAPON); + + if (cRet > 0) bestWeapon = (fo::GameObject*)rets[0]; + + EndHook(); + return bestWeapon; +} + +static void __declspec(naked) ai_search_inven_weap_hook() { + __asm { + push ecx; // target + push ebx; // weapon2 (secondary) + push edx; // weapon1 (primary) + push eax; // source + call fo::funcoffs::ai_best_weapon_; + push eax; // bestWeapon + call BestWeaponHook_Script; + retn; + } +} +/* +fo::GameObject* BestWeaponHook_Invoke(fo::GameObject* bestWeapon, fo::GameObject* source, fo::GameObject* weapon1, fo::GameObject* weapon2, fo::GameObject* target) { + return (HookScripts::HookHasScript(HOOK_BESTWEAPON)) + ? BestWeaponHook_Script(bestWeapon, source, weapon1, weapon2, target) + : bestWeapon; +} +*/ +static bool __stdcall CanUseWeaponHook_Script(bool result, fo::GameObject* source, fo::GameObject* weapon, long hitMode) { + BeginHook(); + argCount = 4; + + args[0] = (DWORD)source; + args[1] = (DWORD)weapon; + args[2] = hitMode; + args[3] = result; + + RunHookScript(HOOK_CANUSEWEAPON); + + if (cRet > 0) result = (rets[0]) ? true : false; + + EndHook(); + return result; // only 0 and 1 +} + +static void __declspec(naked) AICanUseWeaponHook() { + __asm { + push ecx; + push ebx; // hitMode + push edx; // weapon + push eax; // source + call fo::funcoffs::ai_can_use_weapon_; + push eax; // result + call CanUseWeaponHook_Script; + //and eax, 1; + pop ecx; + retn; + } +} + +static void __declspec(naked) CanUseWeaponHook() { + __asm { + push ecx; + push edx; + push -1; // hitMode (indefinite) + push eax; // weapon + push ds:[FO_VAR_obj_dude]; // source + call fo::funcoffs::can_use_weapon_; // 1 - can't use + xor al, 1; + push eax; // result + call CanUseWeaponHook_Script; + xor al, 1; // invert + pop edx; + pop ecx; + retn; + } +} +/* +bool CanUseWeaponHook_Invoke(bool result, fo::GameObject* source, fo::GameObject* weapon, long hitMode) { + return (HookScripts::HookHasScript(HOOK_CANUSEWEAPON)) + ? CanUseWeaponHook_Script(result, source, weapon, hitMode) + : result; +} +*/ //////////////////////////////////////////////////////////////////////////////// void Inject_ToHitHook() { @@ -502,6 +599,21 @@ void Inject_TargetObjectHook() { SafeWrite8(0x44C26E, 0x17); } +void Inject_BestWeaponHook() { + HookCall(0x429A59, ai_search_inven_weap_hook); +} + +void Inject_CanUseWeaponHook() { + const DWORD aiCanUseWpnHkAddr[] = { + 0x429A1B, // ai_search_inven_weap_ + 0x429CF2, // ai_search_environ_ + 0x429E1C // ai_pick_hit_mode_ + }; + HookCalls(AICanUseWeaponHook, aiCanUseWpnHkAddr); + const DWORD canUseWpnHkAddr[] = {0x45F05E, 0x45F1C1, 0x45F203, 0x45F36A}; // intface_update_items_ + HookCalls(CanUseWeaponHook, canUseWpnHkAddr); +} + void InitCombatHookScripts() { HookScripts::LoadHookScript("hs_tohit", HOOK_TOHIT); HookScripts::LoadHookScript("hs_afterhitroll", HOOK_AFTERHITROLL); @@ -512,6 +624,8 @@ void InitCombatHookScripts() { HookScripts::LoadHookScript("hs_ammocost", HOOK_AMMOCOST); HookScripts::LoadHookScript("hs_onexplosion", HOOK_ONEXPLOSION); HookScripts::LoadHookScript("hs_targetobject", HOOK_TARGETOBJECT); + HookScripts::LoadHookScript("hs_bestweapon", HOOK_BESTWEAPON); + HookScripts::LoadHookScript("hs_canuseweapon", HOOK_CANUSEWEAPON); } } diff --git a/sfall/Modules/HookScripts/CombatHs.h b/sfall/Modules/HookScripts/CombatHs.h index f88a0a50..809254c4 100644 --- a/sfall/Modules/HookScripts/CombatHs.h +++ b/sfall/Modules/HookScripts/CombatHs.h @@ -16,9 +16,14 @@ void Inject_ItemDamageHook(); void Inject_AmmoCostHook(); void Inject_OnExplosionHook(); void Inject_TargetObjectHook(); +void Inject_BestWeaponHook(); +void Inject_CanUseWeaponHook(); int __fastcall AmmoCostHook_Script(DWORD hookType, fo::GameObject* weapon, DWORD &rounds); long CalcApCostHook_Invoke(fo::GameObject* source, long hitMode, long isCalled, long cost, fo::GameObject* weapon); +//void FindTargetHook_Invoke(fo::GameObject* targets[], fo::GameObject* attacker); +//fo::GameObject* BestWeaponHook_Invoke(fo::GameObject* bestWeapon, fo::GameObject* source, fo::GameObject* weapon1, fo::GameObject* weapon2, fo::GameObject* target); +//bool CanUseWeaponHook_Invoke(bool result, fo::GameObject* source, fo::GameObject* weapon, long hitMode); } diff --git a/sfall/Modules/HookScripts/MiscHs.cpp b/sfall/Modules/HookScripts/MiscHs.cpp index e2591dee..363dba25 100644 --- a/sfall/Modules/HookScripts/MiscHs.cpp +++ b/sfall/Modules/HookScripts/MiscHs.cpp @@ -631,6 +631,66 @@ cancelEnc: } } +static long __stdcall RollCheckHook_Script(long roll, long chance, long bonus, long randomChance, long calledFrom) { + long hookType; + switch (calledFrom - 5) { + case 0x42388E: // compute_attack_ + case 0x4234D1: // compute_spray_ + hookType = 1; // single and burst attack hit event + break; + case 0x42356C: // compute_spray_ + hookType = 2; // burst attack bullet hit event + break; + case 0x4AAB29: // skill_result_ + hookType = 3; // common skill check event + break; + case 0x4AB3B6: // skill_use_ + hookType = 4; // SKILL_REPAIR + break; + case 0x4AB8B5: // skill_use_ + hookType = 5; // SKILL_DOCTOR + break; + case 0x4ABC9F: // skill_check_stealing_ + hookType = 6; // SKILL_STEAL - source stealing check event + break; + case 0x4ABCE6: // skill_check_stealing_ + hookType = 7; // SKILL_STEAL - target stealing check event (fail for success stealing) + break; + default: + return roll; // unsupported hook + } + + BeginHook(); + argCount = 5; + + args[0] = hookType; + args[1] = roll; + args[2] = chance; + args[3] = bonus; + args[4] = randomChance; + + RunHookScript(HOOK_ROLLCHECK); + if (cRet) roll = rets[0]; + + EndHook(); + return roll; +} + +static void __declspec(naked) roll_check_hook() { + __asm { + push ecx; + push [esp + 0xC + 8]; // calledFrom + push ebx; // random chance value + push esi; // bonus + push edi; // chance value + call fo::funcoffs::roll_check_critical_; + push eax; // roll result + call RollCheckHook_Script; + pop ecx; + retn; + } +} + void Inject_BarterPriceHook() { const DWORD barterPriceHkAddr[] = { 0x474D4C, // barter_attempt_transaction_ (offers button) @@ -710,6 +770,10 @@ void Inject_EncounterHook() { HookCall(0x4C095C, wmRndEncounterOccurred_hook); } +void Inject_RollCheckHook() { + HookCall(0x4A3020, roll_check_hook); +} + void InitMiscHookScripts() { HookScripts::LoadHookScript("hs_barterprice", HOOK_BARTERPRICE); HookScripts::LoadHookScript("hs_useskillon", HOOK_USESKILLON); @@ -722,6 +786,7 @@ void InitMiscHookScripts() { HookScripts::LoadHookScript("hs_resttimer", HOOK_RESTTIMER); HookScripts::LoadHookScript("hs_explosivetimer", HOOK_EXPLOSIVETIMER); HookScripts::LoadHookScript("hs_encounter", HOOK_ENCOUNTER); + HookScripts::LoadHookScript("hs_rollcheck", HOOK_ROLLCHECK); } } diff --git a/sfall/Modules/HookScripts/MiscHs.h b/sfall/Modules/HookScripts/MiscHs.h index e7fbbcd3..f300ef73 100644 --- a/sfall/Modules/HookScripts/MiscHs.h +++ b/sfall/Modules/HookScripts/MiscHs.h @@ -17,6 +17,7 @@ void Inject_SetGlobalVarHook(); void Inject_RestTimerHook(); void Inject_ExplosiveTimerHook(); void Inject_EncounterHook(); +void Inject_RollCheckHook(); long PerceptionRangeHook_Invoke(fo::GameObject* watcher, fo::GameObject* target, long type, long result);