From 46a5616b341e3c3d7ad37140ee88832c79fd26cb Mon Sep 17 00:00:00 2001 From: NovaRain Date: Fri, 14 Aug 2020 20:58:43 +0800 Subject: [PATCH] Fixed and improved the behavior of nested timer events in global scripts Added a fix to prevent the execution of critter_p_proc and game events when playing movies. Added a debug message about a corrupted proto file. Some minor code/document edits. --- artifacts/scripting/headers/sfall.h | 2 +- sfall/FalloutEngine/Structs.h | 1 + sfall/Modules/BugFixes.cpp | 14 ++++++ sfall/Modules/DebugEditor.cpp | 19 +++++++- sfall/Modules/ExtraSaveSlots.cpp | 2 +- sfall/Modules/Graphics.cpp | 6 +-- sfall/Modules/HookScripts/CombatHs.cpp | 3 +- sfall/Modules/ScriptExtender.cpp | 60 ++++++++++++++++++++------ 8 files changed, 86 insertions(+), 21 deletions(-) diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index 55e84b26..b66930c7 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -281,7 +281,7 @@ /* sfall metarule3 function macros */ // sets the number of days (range 1...127) for the Frank Horrigan encounter, or disable the encounter if days is set to 0 #define set_horrigan_days(day) metarule3(200, day, 0, 0) -// clears the keyboard input buffer, use it in the HOOK_KEYPRESS hook to clear keyboard events before calling script functions that accept key input +// clears the keyboard input buffer, use it in the HOOK_KEYPRESS hook to clear keyboard events before calling functions that are waiting for keyboard input #define clear_keyboard_buffer metarule3(201, 0, 0, 0) diff --git a/sfall/FalloutEngine/Structs.h b/sfall/FalloutEngine/Structs.h index 0379dd7e..22e039f2 100644 --- a/sfall/FalloutEngine/Structs.h +++ b/sfall/FalloutEngine/Structs.h @@ -726,6 +726,7 @@ struct Window { long *drawFunc; }; +#pragma pack(1) struct sWindow { char name[32]; long wID; diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index 251ca4b8..febec675 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -2766,6 +2766,17 @@ checkTiles: } } +static void __declspec(naked) doBkProcesses_hook() { + __asm { + call fo::funcoffs::gdialogActive_; + test eax, eax; + jz skip; + retn; +skip: + jmp fo::funcoffs::gmovieIsPlaying_; + } +} + void BugFixes::init() { #ifndef NDEBUG @@ -3489,6 +3500,9 @@ void BugFixes::init() }; SafeWriteBytes(0x42A0F4, codeData1, 18); // ai_move_steps_closer_ HookCall(0x42A0F8, (void*)fo::funcoffs::obj_dist_); + + // Fix to prevent the execution of critter_p_proc and game events when playing movies (same as when the dialog is active) + HookCall(0x4A3C89, doBkProcesses_hook); } } diff --git a/sfall/Modules/DebugEditor.cpp b/sfall/Modules/DebugEditor.cpp index 5f722975..22bb0d9b 100644 --- a/sfall/Modules/DebugEditor.cpp +++ b/sfall/Modules/DebugEditor.cpp @@ -338,6 +338,20 @@ display: } } +static void __declspec(naked) proto_load_pid_hack() { + static char* proDbgMsg = "\nERROR reading prototype file: %s\n"; + __asm { + mov dword ptr [esp + 0x120 - 0x1C + 4], -1; + lea eax, [esp + 0x120 - 0x120 + 4]; // pro file + push eax; + push proDbgMsg; + call fo::funcoffs::debug_printf_; + add esp, 8; + mov eax, 0x500494; // 'iisxxxx1' + jmp fo::funcoffs::gsound_play_sfx_file_; + } +} + static void __declspec(naked) win_debug_hook() { __asm { call fo::funcoffs::debug_log_; @@ -384,7 +398,7 @@ static void DebugModePatch() { if (iniGetInt("Debugging", "HideObjIsNullMsg", 0, ::sfall::ddrawIni)) { MakeJump(0x453FD2, dbg_error_hack); } - // prints a debug message about missing art file for critters to both debug.log and the message window in sfall debugging mode + // prints a debug message about a missing critter art file to both debug.log and the message window in sfall debugging mode HookCall(0x419B65, art_data_size_hook); // Fix to prevent crashes when there is a '%' character in the printed message @@ -411,6 +425,9 @@ static void DontDeleteProtosPatch() { void DebugEditor::init() { DebugModePatch(); + // Notifies and prints a debug message about a corrupted proto file to debug.log + MakeCall(0x4A1D73, proto_load_pid_hack, 6); + if (!isDebug) return; DontDeleteProtosPatch(); diff --git a/sfall/Modules/ExtraSaveSlots.cpp b/sfall/Modules/ExtraSaveSlots.cpp index 27d195bd..4c876fe5 100644 --- a/sfall/Modules/ExtraSaveSlots.cpp +++ b/sfall/Modules/ExtraSaveSlots.cpp @@ -535,7 +535,7 @@ void ExtraSaveSlots::init() { dlogr(" Done", DL_INIT); } - // Adds the city name in the description of a save slot + // Adds the city name in the description for empty save slots MakeJump(0x47F02C, GetComment_hack); } diff --git a/sfall/Modules/Graphics.cpp b/sfall/Modules/Graphics.cpp index c68fe97c..7573970c 100644 --- a/sfall/Modules/Graphics.cpp +++ b/sfall/Modules/Graphics.cpp @@ -359,8 +359,7 @@ static void Present() { DWORD move = r.right - r2.right; windowLeft -= move; r.right -= move; - } - else if (r.left < r2.left) { + } else if (r.left < r2.left) { DWORD move = r2.left - r.left; windowLeft += move; r.right += move; @@ -369,8 +368,7 @@ static void Present() { DWORD move = r.bottom - r2.bottom; windowTop -= move; r.bottom -= move; - } - else if (r.top < r2.top) { + } else if (r.top < r2.top) { DWORD move = r2.top - r.top; windowTop += move; r.bottom += move; diff --git a/sfall/Modules/HookScripts/CombatHs.cpp b/sfall/Modules/HookScripts/CombatHs.cpp index 06858eb5..39241427 100644 --- a/sfall/Modules/HookScripts/CombatHs.cpp +++ b/sfall/Modules/HookScripts/CombatHs.cpp @@ -527,8 +527,7 @@ static long __fastcall TargetObjectHook(DWORD isValid, DWORD object, long type) targetRet = (rets[0] != 0) ? rets[0] : object; // 0 - default object, -1 - invalid target, or object override object = (targetRet != -1) ? targetRet : 0; // object can't be -1 targetObjHookHasRet = true; - } - else if (targetObjHookHasRet && type == 1) { + } else if (targetObjHookHasRet && type == 1) { targetObjHookHasRet = false; if (targetRet != -1) object = targetRet; } diff --git a/sfall/Modules/ScriptExtender.cpp b/sfall/Modules/ScriptExtender.cpp index 6d1cc2f1..291e5411 100644 --- a/sfall/Modules/ScriptExtender.cpp +++ b/sfall/Modules/ScriptExtender.cpp @@ -19,6 +19,7 @@ //#include #include #include +#include #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" @@ -84,16 +85,20 @@ struct SelfOverrideObj { } }; +// Events in global scripts cannot be saved because the scripts don't have a binding object struct TimedEvent { ScriptProgram* script; unsigned long time; long fixed_param; + bool isActive; bool operator() (const TimedEvent &a, const TimedEvent &b) { return a.time < b.time; } } *timedEvent = nullptr; +static long executeTimedEventDepth = 0; +static std::stack executeTimedEvents; static std::list timerEventScripts; static std::vector globalScriptPathList; @@ -559,6 +564,9 @@ static void ClearGlobalScripts() { selfOverrideMap.clear(); globalExportedVars.clear(); timerEventScripts.clear(); + timedEvent = nullptr; + executeTimedEventDepth = 0; + while (!executeTimedEvents.empty()) executeTimedEvents.pop(); HookScriptClear(); } @@ -637,7 +645,9 @@ static DWORD __stdcall HandleMapUpdateForScripts(const DWORD procId) { for (std::vector::const_iterator it = globalScripts.cbegin(); it != globalScripts.cend(); ++it) { fo::func::runProgram(it->prog.ptr); } - } else if (procId == fo::Scripts::ScriptProc::map_exit_p_proc) onMapExit.invoke(); + } else if (procId == fo::Scripts::ScriptProc::map_exit_p_proc) { + onMapExit.invoke(); + } RunGlobalScriptsAtProc(procId); // gl* scripts of types 0 and 3 RunHookScriptsAtProc(procId); // all hs_ scripts @@ -647,25 +657,50 @@ static DWORD __stdcall HandleMapUpdateForScripts(const DWORD procId) { static DWORD HandleTimedEventScripts() { DWORD currentTime = fo::var::fallout_game_time; - bool wasRunning = false; - auto timerIt = timerEventScripts.cbegin(); - for (; timerIt != timerEventScripts.cend(); ++timerIt) { + if (timerEventScripts.empty()) return currentTime; + + executeTimedEventDepth++; + + fo::func::dev_printf("\n[TimedEventScripts] Time: %d / Depth: %d", currentTime, executeTimedEventDepth); + + bool eventsWereRunning = false; + for (auto timerIt = timerEventScripts.cbegin(); timerIt != timerEventScripts.cend(); ++timerIt) { + fo::func::dev_printf("\n[TimedEventScripts] Event: %d", timerIt->time); + if (timerIt->isActive == false) continue; if (currentTime >= timerIt->time) { + if (timedEvent) executeTimedEvents.push(timedEvent); // store a pointer to the currently running event + timedEvent = const_cast(&(*timerIt)); - fo::func::dev_printf("\n[TimedEventScripts] run event: %d", timerIt->time); + timedEvent->isActive = false; + + fo::func::dev_printf("\n[TimedEventScripts] Run event: %d", timerIt->time); RunScriptProc(timerIt->script, fo::Scripts::ScriptProc::timed_event_p_proc); - wasRunning = true; + fo::func::dev_printf("\n[TimedEventScripts] Event done: %d", timerIt->time); + + timedEvent = nullptr; + if (!executeTimedEvents.empty()) { + timedEvent = executeTimedEvents.top(); // restore a pointer to a previously running event + executeTimedEvents.pop(); + } + eventsWereRunning = true; } else { break; } } - if (wasRunning) { - for (auto _it = timerEventScripts.cbegin(); _it != timerIt; ++_it) { - fo::func::dev_printf("\n[TimedEventScripts] delete event: %d", _it->time); + executeTimedEventDepth--; + + if (eventsWereRunning && executeTimedEventDepth == 0) { + timedEvent = nullptr; + // delete all previously executed events + for (auto it = timerEventScripts.cbegin(); it != timerEventScripts.cend();) { + if (it->isActive == false) { + fo::func::dev_printf("\n[TimedEventScripts] Remove event: %d", it->time); + it = timerEventScripts.erase(it); + } else { + ++it; + } } - timerEventScripts.erase(timerEventScripts.cbegin(), timerIt); } - timedEvent = nullptr; return currentTime; } @@ -677,7 +712,7 @@ static DWORD TimedEventNextTime() { mov nextTime, eax; push edx; } - if (!timerEventScripts.empty()) { + if (!timerEventScripts.empty() && timerEventScripts.front().isActive) { DWORD time = timerEventScripts.front().time; if (!nextTime || time < nextTime) nextTime = time; } @@ -693,6 +728,7 @@ static DWORD script_chk_timed_events_hook() { void ScriptExtender::AddTimerEventScripts(fo::Program* script, long time, long param) { ScriptProgram* scriptProg = &(sfallProgsMap.find(script)->second); TimedEvent timer; + timer.isActive = true; timer.script = scriptProg; timer.fixed_param = param; timer.time = fo::var::fallout_game_time + time;