From 2f491fa6d594d2f7610069f022dc27d8b81f3ecc Mon Sep 17 00:00:00 2001 From: Mike Klaas Date: Wed, 29 Apr 2026 20:37:43 -0700 Subject: [PATCH] HOOK_EXPLOSIVETIMER (#423) --- SFALL_COMPATIBILITY.md | 2 +- src/proto_instance.cc | 4 +++- src/sfall_script_hooks.cc | 46 +++++++++++++++++++++++++++++++++++++++ src/sfall_script_hooks.h | 1 + 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/SFALL_COMPATIBILITY.md b/SFALL_COMPATIBILITY.md index baf21786..47c759c0 100644 --- a/SFALL_COMPATIBILITY.md +++ b/SFALL_COMPATIBILITY.md @@ -111,7 +111,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/) | RestTimer | `HOOK_RESTTIMER` | ✅ | CE is slightly more strict: only `ret0 == 1` interrupts. Ticks wrap every 6.8y; do not rely on them for absolute game time. | | GameModeChange | `HOOK_GAMEMODECHANGE` | ✅ | - | | UseAnimObj | `HOOK_USEANIMOBJ` | 🚫 | Et tu; (maybe) | -| ExplosiveTimer | `HOOK_EXPLOSIVETIMER` | 🚫 | - | +| ExplosiveTimer | `HOOK_EXPLOSIVETIMER` | ✅ | - | | DescriptionObj | `HOOK_DESCRIPTIONOBJ` | 🚫 | Et tu | | UseSkillOn | `HOOK_USESKILLON` | 🚫 | Et tu | | OnExplosion | `HOOK_ONEXPLOSION` | 🚫 | (maybe) | diff --git a/src/proto_instance.cc b/src/proto_instance.cc index 74c9fdab..df2aa4b4 100644 --- a/src/proto_instance.cc +++ b/src/proto_instance.cc @@ -919,7 +919,9 @@ static UseItemResultCode _obj_use_explosive(Object* explosive) break; } - queueAddEvent(delay, explosive, nullptr, eventType); + if (scriptHooks_ExplosiveTimer(explosive, 10 * seconds, eventType) == -1) { + queueAddEvent(delay, explosive, nullptr, eventType); + } } } diff --git a/src/sfall_script_hooks.cc b/src/sfall_script_hooks.cc index 017ce6e8..06a596e9 100644 --- a/src/sfall_script_hooks.cc +++ b/src/sfall_script_hooks.cc @@ -9,6 +9,7 @@ #include "db.h" #include "debug.h" +#include "queue.h" #include "random.h" #include "scripts.h" @@ -248,6 +249,51 @@ bool scriptHooks_RestTimer(unsigned int gameTime, RestEventType eventType, int h return false; } +/* +Runs after Fallout has decided how long an explosive timer should run and whether setting it was a success or failure. +The hook can override both the queued delay and the coarse outcome. Critical success/failure collapse to success/failure, +matching sfall's queue event rewrite semantics. + +int arg0 - The explosive delay in ticks before any failure penalty is applied +Obj arg1 - The explosive object +int arg2 - The result of engine calculation: 1 - failure, 2 - success + +int ret0 - The new delay in ticks (maximum 18000). Negative values use engine behavior. +int ret1 - The new result: 0/1 - failure, 2/3 - success. Other values use engine behavior. +*/ +int scriptHooks_ExplosiveTimer(Object* explosive, int delay, int eventType) +{ + assert(explosive != nullptr); + assert(delay >= 0); + assert(eventType == EVENT_TYPE_EXPLOSION || eventType == EVENT_TYPE_EXPLOSION_FAILURE); + + int hookResult = eventType == EVENT_TYPE_EXPLOSION_FAILURE ? ROLL_FAILURE : ROLL_SUCCESS; + + ScriptHookCall hook(HOOK_EXPLOSIVETIMER, 2, { delay, explosive, hookResult }); + hook.call(); + + if (hook.numReturnValues() <= 0) { + return -1; + } + + int overrideDelay = hook.getReturnValueAt(0).asInt(); + if (overrideDelay < 0) { + return -1; + } + + delay = std::min(overrideDelay, 18000); + if (hook.numReturnValues() > 1) { + int overrideResult = hook.getReturnValueAt(1).asInt(); + if (overrideResult >= ROLL_CRITICAL_FAILURE && overrideResult <= ROLL_FAILURE) { + eventType = EVENT_TYPE_EXPLOSION_FAILURE; + } else if (overrideResult >= ROLL_SUCCESS && overrideResult <= ROLL_CRITICAL_SUCCESS) { + eventType = EVENT_TYPE_EXPLOSION; + } + } + + return queueAddEvent(delay, explosive, nullptr, eventType); +} + /* Runs when calculating ammo cost for a weapon. diff --git a/src/sfall_script_hooks.h b/src/sfall_script_hooks.h index 47e0991d..ea883d83 100644 --- a/src/sfall_script_hooks.h +++ b/src/sfall_script_hooks.h @@ -274,6 +274,7 @@ void scriptHooksExit(); void scriptHooks_GameModeChange(int exit, int previousGameMode); bool scriptHooks_RestTimer(unsigned int gameTime, RestEventType eventType, int hours, int minutes); void scriptHooks_OnDeath(Object* critter); +int scriptHooks_ExplosiveTimer(Object* explosive, int delay, int eventType); bool scriptHooks_InventoryMove(HookInventoryMoveType actionType, Object* item, Object* targetItem); bool scriptHooks_CombatTurnStart(Object* critter, bool reloadedDuringCombat); bool scriptHooks_CombatTurnEnd(Object* critter, int turnResult, bool reloadedDuringCombat);