From 7ecdfe362f1c1ce89c0d25eec44d284804ea20b0 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Sun, 27 Nov 2022 09:27:46 +0800 Subject: [PATCH] Backported three game hooks from 4.1: * USEANIMOBJ, EXPLOSIVETIMER, DESCRIPTIONOBJ Updated documents and example mods --- .../ExplosiveTimer/gl_explosivetimer.ssl | 44 ++++++++++ artifacts/scripting/headers/sfall.h | 3 + artifacts/scripting/hookscripts.md | 44 ++++++++++ artifacts/scripting/sfall function notes.md | 5 ++ sfall/FalloutEngine/FunctionOffsets_def.h | 1 + sfall/Modules/HookScripts.cpp | 3 + sfall/Modules/HookScripts.h | 3 + sfall/Modules/HookScripts/MiscHs.cpp | 51 +++++++++++ sfall/Modules/HookScripts/MiscHs.h | 1 + sfall/Modules/HookScripts/ObjectHs.cpp | 85 +++++++++++++++++++ sfall/Modules/HookScripts/ObjectHs.h | 2 + sfall/Modules/Scripting/Handlers/Metarule.cpp | 1 + sfall/Modules/Scripting/Handlers/Utils.cpp | 4 + sfall/Modules/Scripting/Handlers/Utils.h | 2 + 14 files changed, 249 insertions(+) create mode 100644 artifacts/example_mods/ExplosiveTimer/gl_explosivetimer.ssl diff --git a/artifacts/example_mods/ExplosiveTimer/gl_explosivetimer.ssl b/artifacts/example_mods/ExplosiveTimer/gl_explosivetimer.ssl new file mode 100644 index 00000000..4628693a --- /dev/null +++ b/artifacts/example_mods/ExplosiveTimer/gl_explosivetimer.ssl @@ -0,0 +1,44 @@ +/* + Example implementation of the algorithm of how the game engine sets the explosive timer. +*/ + +#include "..\..\scripting_docs\headers\sfall.h" +#include "..\..\scripting_docs\headers\define_lite.h" + +// from CONDTION.H +#define ROLL_CRITICAL_FAILURE (0) +#define ROLL_FAILURE (1) +#define ROLL_SUCCESS (2) +#define ROLL_CRITICAL_SUCCESS (3) + +procedure start; + +procedure start begin + if game_loaded then begin + register_hook(HOOK_EXPLOSIVETIMER); + end else begin + variable + time := get_sfall_arg, + result := ROLL_CRITICAL_FAILURE; + + if has_trait(TRAIT_PERK, dude_obj, PERK_demolition_expert_perk) then + result := ROLL_SUCCESS; + else begin + result := roll_vs_skill(dude_obj, SKILL_TRAPS, 0); + end + + if (result) then begin + if (result == ROLL_FAILURE) then + time /= 2; + else begin // success or critical success + result := ROLL_SUCCESS; + end + end else begin // critical failure + time := 0; + result := ROLL_FAILURE; + end + + set_sfall_return(time); + set_sfall_return(result); // failure/success + end +end diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index 8a6344df..26322674 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -57,6 +57,9 @@ #define HOOK_SETGLOBALVAR (29) #define HOOK_RESTTIMER (30) #define HOOK_GAMEMODECHANGE (31) +#define HOOK_USEANIMOBJ (32) +#define HOOK_EXPLOSIVETIMER (33) +#define HOOK_DESCRIPTIONOBJ (34) // Valid arguments to list_begin #define LIST_CRITTERS (0) diff --git a/artifacts/scripting/hookscripts.md b/artifacts/scripting/hookscripts.md index 435bced1..f8664613 100644 --- a/artifacts/scripting/hookscripts.md +++ b/artifacts/scripting/hookscripts.md @@ -632,3 +632,47 @@ Runs once every time when the game mode was changed, like opening/closing the in int arg0 - event type: 1 - when the player exits the game, 0 - otherwise int arg1 - the previous game mode ``` + +------------------------------------------- + +#### `HOOK_USEANIMOBJ (hs_useanimobj.int)` + +Runs before playing the "use" (usually "magic hands") animation when a critter uses a scenery/container object on the map, or before walking/running animation if the player is at a distance from the object. + +``` +Critter arg0 - the critter that uses an object (usually dude_obj) +Obj arg1 - the object being used +int arg2 - the animation code being used (see ANIM_* in Animcomd.h) + +int ret0 - overrides the animation code (pass -1 if you want to skip the animation) +``` + +------------------------------------------- + +#### `HOOK_EXPLOSIVETIMER (hs_explosivetimer.int)` + +Runs after setting the explosive timer. You can override the result. + +``` +int arg0 - the time in ticks set in the timer +Obj arg1 - the explosive object +int arg2 - the result of engine calculation of whether the timer was set successfully: 1 - failure, 2 - success (similar to ROLL_* in Condtion.h) + +int ret0 - overrides the time of the timer (maximum 18000 ticks) +int ret1 - overrides the result of engine calculation: 0/1 - failure, 2/3 - success (similar to ROLL_*), any other value - use engine handler +``` + +------------------------------------------- + +#### `HOOK_DESCRIPTIONOBJ (hs_descriptionobj.int)` + +Runs when using the examine action icon to display the description of an object. You can override the description text. +An example usage would be to add an additional description to the item based on player's stats/skills. + +Does not run if the script of the object overrides the description. + +``` +Obj arg0 - the object + +int ret0 - a pointer to the new text received by using the get_string_pointer function +``` diff --git a/artifacts/scripting/sfall function notes.md b/artifacts/scripting/sfall function notes.md index 4c1a8aeb..3258b7af 100644 --- a/artifacts/scripting/sfall function notes.md +++ b/artifacts/scripting/sfall function notes.md @@ -682,6 +682,11 @@ sfall_funcX metarule functions - Redraws inventory list in the inventory/use inventory item on/loot/barter screens - `invSide` specifies which side needs to be redrawn: 0 - the player, 1 - target (container/NPC in loot/barter screens), -1 - both sides (same as without argument) +---- +#### get_string_pointer +`int sfall_func1("get_string_pointer", string text)` +- Returns a pointer to a string variable or to a text + ---- #### dialog_message `void sfall_func1("dialog_message", string text)` diff --git a/sfall/FalloutEngine/FunctionOffsets_def.h b/sfall/FalloutEngine/FunctionOffsets_def.h index ad052ca0..ecba248b 100644 --- a/sfall/FalloutEngine/FunctionOffsets_def.h +++ b/sfall/FalloutEngine/FunctionOffsets_def.h @@ -543,6 +543,7 @@ FUNC(obj_turn_on_, 0x48ADF0) FUNC(obj_unjam_lock_, 0x49D480) FUNC(obj_use_book_, 0x49B9F0) FUNC(obj_use_power_on_car_, 0x49BDE8) +FUNC(object_description_, 0x48C914) FUNC(object_under_mouse_, 0x44CEC4) FUNC(palette_fade_to_, 0x493AD4) FUNC(palette_init_, 0x493A00) diff --git a/sfall/Modules/HookScripts.cpp b/sfall/Modules/HookScripts.cpp index 88338604..a552e35f 100644 --- a/sfall/Modules/HookScripts.cpp +++ b/sfall/Modules/HookScripts.cpp @@ -94,6 +94,9 @@ static HooksInjectInfo injectHooks[] = { {HOOK_SETGLOBALVAR, Inject_SetGlobalVarHook, false}, {HOOK_RESTTIMER, Inject_RestTimerHook, false}, {HOOK_GAMEMODECHANGE, nullptr, true}, // always embedded to the engine + {HOOK_USEANIMOBJ, Inject_UseAnimateObjHook, false}, + {HOOK_EXPLOSIVETIMER, Inject_ExplosiveTimerHook, false}, + {HOOK_DESCRIPTIONOBJ, Inject_DescriptionObjHook, false}, }; void HookScripts::InjectingHook(int hookId) { diff --git a/sfall/Modules/HookScripts.h b/sfall/Modules/HookScripts.h index 0e4b4afc..5f69ea0b 100644 --- a/sfall/Modules/HookScripts.h +++ b/sfall/Modules/HookScripts.h @@ -57,6 +57,9 @@ enum HookType HOOK_SETGLOBALVAR = 29, HOOK_RESTTIMER = 30, HOOK_GAMEMODECHANGE = 31, + HOOK_USEANIMOBJ = 32, + HOOK_EXPLOSIVETIMER = 33, + HOOK_DESCRIPTIONOBJ = 34, HOOK_COUNT }; diff --git a/sfall/Modules/HookScripts/MiscHs.cpp b/sfall/Modules/HookScripts/MiscHs.cpp index 58f19b74..d8fc65cb 100644 --- a/sfall/Modules/HookScripts/MiscHs.cpp +++ b/sfall/Modules/HookScripts/MiscHs.cpp @@ -387,6 +387,52 @@ skip: } } +static int __fastcall ExplosiveTimerHook_Script(DWORD &type, DWORD item, DWORD time) { + BeginHook(); + argCount = 3; + + args[0] = time; + args[1] = item; + args[2] = (type == 11) ? fo::ROLL_FAILURE : fo::ROLL_SUCCESS; + + RunHookScript(HOOK_EXPLOSIVETIMER); + + time = -1; + if (cRet > 0 && (long)rets[0] >= 0) { + time = min(rets[0], 18000); // max 30 minutes + if (cRet > 1) { + int typeRet = rets[1]; + if (typeRet >= fo::ROLL_CRITICAL_FAILURE && typeRet <= fo::ROLL_CRITICAL_SUCCESS) { + type = (typeRet > fo::ROLL_FAILURE) ? 8 : 11; // returned new type (8 = SUCCESS, 11 = FAILURE) + } + } + } + EndHook(); + return time; +} + +static void __declspec(naked) ExplosiveTimerHook() { + __asm { + push eax; // time in ticks for queue_add_ + push edx; + //------- + push ecx; + mov ecx, esp; // ptr to type + push edi; // time in ticks (w/o failure penalty) + call ExplosiveTimerHook_Script; // ecx - type, edx - item + pop ecx; + pop edx; + cmp eax, -1; // return new time in ticks + jne skip; + pop eax; + jmp end; +skip: + add esp, 4; +end: + jmp fo::funcoffs::queue_add_; + } +} + void Inject_BarterPriceHook() { const DWORD barterPriceHkAddr[] = { 0x474D4C, // barter_attempt_transaction_ (offers button) @@ -438,6 +484,10 @@ void Inject_RestTimerHook() { MakeCalls(RestTimerEscapeHook, restTimerEscHkAddr); } +void Inject_ExplosiveTimerHook() { + HookCall(0x49BDC4, ExplosiveTimerHook); +} + void InitMiscHookScripts() { HookScripts::LoadHookScript("hs_barterprice", HOOK_BARTERPRICE); HookScripts::LoadHookScript("hs_useskill", HOOK_USESKILL); @@ -446,6 +496,7 @@ void InitMiscHookScripts() { HookScripts::LoadHookScript("hs_cartravel", HOOK_CARTRAVEL); HookScripts::LoadHookScript("hs_setglobalvar", HOOK_SETGLOBALVAR); HookScripts::LoadHookScript("hs_resttimer", HOOK_RESTTIMER); + HookScripts::LoadHookScript("hs_explosivetimer", HOOK_EXPLOSIVETIMER); } } diff --git a/sfall/Modules/HookScripts/MiscHs.h b/sfall/Modules/HookScripts/MiscHs.h index e6f26297..0ca40c26 100644 --- a/sfall/Modules/HookScripts/MiscHs.h +++ b/sfall/Modules/HookScripts/MiscHs.h @@ -12,6 +12,7 @@ void Inject_WithinPerceptionHook(); void Inject_CarTravelHook(); void Inject_SetGlobalVarHook(); void Inject_RestTimerHook(); +void Inject_ExplosiveTimerHook(); long PerceptionRangeHook_Invoke(fo::GameObject* watcher, fo::GameObject* target, long type, long result); diff --git a/sfall/Modules/HookScripts/ObjectHs.cpp b/sfall/Modules/HookScripts/ObjectHs.cpp index 9452433a..216eb5fd 100644 --- a/sfall/Modules/HookScripts/ObjectHs.cpp +++ b/sfall/Modules/HookScripts/ObjectHs.cpp @@ -110,6 +110,81 @@ defaultHandler: } } +static DWORD __fastcall UseAnimateObjHook_Script(DWORD critter, DWORD animCode, DWORD object) { + BeginHook(); + argCount = 3; + + args[0] = critter; + args[1] = object; + args[2] = animCode; + + RunHookScript(HOOK_USEANIMOBJ); + + if (cRet > 0 && static_cast(rets[0]) <= 64) { + animCode = rets[0]; // new anim code + } + EndHook(); + + return animCode; +} + +// Before animation of using map object +static void __declspec(naked) UseAnimateObjHook() { + __asm { + cmp dword ptr [esp], 0x412292 + 5; + push eax; + push ecx; + jne contr; + push ebp; // map object + jmp next; +contr: + push edi; // map object +next: + mov ecx, eax; // source critter + call UseAnimateObjHook_Script; // edx - anim code + pop ecx; + cmp eax, -1; // return anim code + jle end; // goto no animate + mov edx, eax; // restore vanilla or hook anim code + pop eax; + jmp fo::funcoffs::register_object_animate_; +end: + pop eax; + retn; + } +} + +static DWORD __fastcall DescriptionObjHook_Script(DWORD object) { + BeginHook(); + argCount = 1; + + args[0] = object; + + RunHookScript(HOOK_DESCRIPTIONOBJ); + + DWORD textPrt = (cRet > 0) ? rets[0] : 0; + EndHook(); + + return textPrt; +} + +static void __declspec(naked) DescriptionObjHook() { + __asm { + push ecx; + push edx; + mov ecx, eax; // object + call DescriptionObjHook_Script; + pop edx; + pop ecx; + test eax, eax; // pointer to text + jnz skip; + mov eax, ebp; + jmp fo::funcoffs::object_description_; +skip: + retn; + } +} + void Inject_UseObjOnHook() { const DWORD useObjOnHkAddr[] = {0x49C606, 0x473619}; HookCalls(UseObjOnHook, useObjOnHkAddr); @@ -128,9 +203,19 @@ void Inject_UseObjHook() { HookCalls(UseObjHook, useObjHkAddr); } +void Inject_UseAnimateObjHook() { + const DWORD useAnimateObjHkAddr[] = {0x4120C1, 0x412292}; + HookCalls(UseAnimateObjHook, useAnimateObjHkAddr); +} + +void Inject_DescriptionObjHook() { + HookCall(0x49AE28, DescriptionObjHook); +} + void InitObjectHookScripts() { HookScripts::LoadHookScript("hs_useobjon", HOOK_USEOBJON); HookScripts::LoadHookScript("hs_useobj", HOOK_USEOBJ); + HookScripts::LoadHookScript("hs_descriptionobj", HOOK_DESCRIPTIONOBJ); } } diff --git a/sfall/Modules/HookScripts/ObjectHs.h b/sfall/Modules/HookScripts/ObjectHs.h index c06bb684..7740fdb5 100644 --- a/sfall/Modules/HookScripts/ObjectHs.h +++ b/sfall/Modules/HookScripts/ObjectHs.h @@ -7,6 +7,8 @@ void InitObjectHookScripts(); void Inject_UseObjOnHook(); void Inject_UseObjHook(); +void Inject_UseAnimateObjHook(); +void Inject_DescriptionObjHook(); long UseObjOnHook_Invoke(fo::GameObject* source, fo::GameObject* item, fo::GameObject* target); diff --git a/sfall/Modules/Scripting/Handlers/Metarule.cpp b/sfall/Modules/Scripting/Handlers/Metarule.cpp index 296381d9..5ba98eed 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.cpp +++ b/sfall/Modules/Scripting/Handlers/Metarule.cpp @@ -96,6 +96,7 @@ static const SfallMetarule metarules[] = { {"get_sfall_arg_at", mf_get_sfall_arg_at, 1, 1, 0, {ARG_INT}}, {"get_stat_max", mf_get_stat_max, 1, 2, 0, {ARG_INT, ARG_INT}}, {"get_stat_min", mf_get_stat_min, 1, 2, 0, {ARG_INT, ARG_INT}}, + {"get_string_pointer", mf_get_string_pointer, 1, 1, 0, {ARG_STRING}}, {"get_terrain_name", mf_get_terrain_name, 0, 2, -1, {ARG_INT, ARG_INT}}, {"get_text_width", mf_get_text_width, 1, 1, 0, {ARG_STRING}}, {"get_window_attribute", mf_get_window_attribute, 1, 2, -1, {ARG_INT, ARG_INT}}, diff --git a/sfall/Modules/Scripting/Handlers/Utils.cpp b/sfall/Modules/Scripting/Handlers/Utils.cpp index fd9960b4..8a07a66c 100644 --- a/sfall/Modules/Scripting/Handlers/Utils.cpp +++ b/sfall/Modules/Scripting/Handlers/Utils.cpp @@ -378,6 +378,10 @@ void mf_add_extra_msg_file(OpcodeContext& ctx) { ctx.setReturn(result); } +void mf_get_string_pointer(OpcodeContext& ctx) { + ctx.setReturn(reinterpret_cast(ctx.arg(0).strValue()), DATATYPE_INT); +} + void mf_get_text_width(OpcodeContext& ctx) { ctx.setReturn(fo::util::GetTextWidth(ctx.arg(0).strValue())); } diff --git a/sfall/Modules/Scripting/Handlers/Utils.h b/sfall/Modules/Scripting/Handlers/Utils.h index c447922a..7230af06 100644 --- a/sfall/Modules/Scripting/Handlers/Utils.h +++ b/sfall/Modules/Scripting/Handlers/Utils.h @@ -47,6 +47,8 @@ void op_message_str_game(OpcodeContext&); void mf_add_extra_msg_file(OpcodeContext&); +void mf_get_string_pointer(OpcodeContext&); + void mf_get_text_width(OpcodeContext&); void mf_string_to_case(OpcodeContext&);