From ff2ebdc82a8b1082759a5c99094be8de3b555ec7 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Mon, 1 Jul 2019 12:03:41 +0800 Subject: [PATCH] Added "combat ends normally" event to HOOK_COMBATTURN Fixed a crash in COMBATTURN hook when loading in combat mode while controlling critters. Fixed critter control algorithm in PartyControl.cpp and set_dude_obj not accepting a null argument. --- artifacts/scripting/hookscripts.txt | 4 +- sfall/FalloutEngine/VariableOffsets.h | 1 + sfall/FalloutEngine/Variables_def.h | 1 + sfall/Modules/HookScripts/CombatHs.cpp | 115 ++++++++++++------ sfall/Modules/PartyControl.cpp | 27 +++- sfall/Modules/Scripting/Handlers/Metarule.cpp | 4 +- sfall/Modules/Scripting/Handlers/Objects.cpp | 4 +- sfall/Modules/Scripting/OpcodeContext.h | 10 +- 8 files changed, 114 insertions(+), 52 deletions(-) diff --git a/artifacts/scripting/hookscripts.txt b/artifacts/scripting/hookscripts.txt index 6cf7a176..42d29c69 100644 --- a/artifacts/scripting/hookscripts.txt +++ b/artifacts/scripting/hookscripts.txt @@ -462,9 +462,9 @@ HOOK_COMBATTURN (hs_combatturn.int) Runs before and after each turn in combat (for both PC and NPC). -int arg1 - event type: 1 - start of turn, 0 - normal end of turn, -1 - combat ended abruptly (by script or by pressing Enter during PC turn) +int arg1 - event type: 1 - start of turn, 0 - normal end of turn, -1 - combat ends abruptly (by script or by pressing Enter during PC turn), -2 - combat ends normally (hook always runs at the end of combat) int arg2 - critter doing the turn -bool arg3 - set to 1 at the start of the player's turn after the game has been loaded, 0 otherwise +bool arg3 - 1 at the start/end of the player's turn after loading a game saved in combat mode, 0 otherwise int ret1 - pass 1 at the start of turn to skip the turn, pass -1 at the end of turn to force end of combat diff --git a/sfall/FalloutEngine/VariableOffsets.h b/sfall/FalloutEngine/VariableOffsets.h index 181f7e98..38488da5 100644 --- a/sfall/FalloutEngine/VariableOffsets.h +++ b/sfall/FalloutEngine/VariableOffsets.h @@ -33,6 +33,7 @@ #define FO_VAR_combat_free_move 0x56D39C #define FO_VAR_combat_list 0x56D390 #define FO_VAR_combat_state 0x510944 +#define FO_VAR_combat_turn_obj 0x56D388 #define FO_VAR_combat_turn_running 0x51093C #define FO_VAR_combatNumTurns 0x510940 #define FO_VAR_crit_succ_eff 0x510978 diff --git a/sfall/FalloutEngine/Variables_def.h b/sfall/FalloutEngine/Variables_def.h index aa97009f..5f3fac8e 100644 --- a/sfall/FalloutEngine/Variables_def.h +++ b/sfall/FalloutEngine/Variables_def.h @@ -20,6 +20,7 @@ VAR_(carCurrentArea, DWORD) VAR_(carGasAmount, long) // from 0 to 80000 VAR_(cmap, DWORD) VAR_(colorTable, DWORD) +VAR_(combat_end_due_to_load, DWORD) VAR_(combat_free_move, DWORD) VAR_(combat_list, DWORD) VAR_(combat_state, DWORD) diff --git a/sfall/Modules/HookScripts/CombatHs.cpp b/sfall/Modules/HookScripts/CombatHs.cpp index 6b62731c..e13b6a8a 100644 --- a/sfall/Modules/HookScripts/CombatHs.cpp +++ b/sfall/Modules/HookScripts/CombatHs.cpp @@ -340,56 +340,98 @@ skip: } // hooks combat_turn function -static void __declspec(naked) CombatTurnHook() { - __asm { - HookBegin; - mov args[0], 1; // turn begin - mov args[4], eax; // critter - mov args[8], edx; // unknown (1 = dude turn) - pushad; - } - +static long combatTurnResult = 0; +static long __fastcall CombatTurnHook_Script(fo::GameObject* critter, long dudeBegin) { + BeginHook(); argCount = 3; + + args[0] = 1; // turn begin + args[1] = (DWORD)critter; // who begins turn + args[2] = dudeBegin; // true - dude begins/ends turn after loading a game saved in combat mode + RunHookScript(HOOK_COMBATTURN); // Start of turn - _asm popad; - if (cRet > 0) { - _asm mov eax, rets[0]; - HookEnd; - _asm retn; // exit hook - } - - // set_sfall_return not used, proceed normally - __asm { - call fo::funcoffs::combat_turn_; - mov args[0], eax; - pushad; + combatTurnResult = 0; + if (cRet > 0 && rets[0] == 1) { // skip turn + goto endHook; // exit hook } + // set_sfall_return is not used, proceed normally + combatTurnResult = args[0] = fo::func::combat_turn(critter, dudeBegin); + if (fo::var::combat_end_due_to_load && combatTurnResult == -1) goto endHook; // don't run end of turn hook when the game was loaded during the combat //cRet = 0; // reset number of return values RunHookScript(HOOK_COMBATTURN); // End of turn + if (cRet > 0 && rets[0] == -1) combatTurnResult = -1; // override result of turn +endHook: + EndHook(); + return combatTurnResult; +} + +static void __declspec(naked) CombatTurnHook() { __asm { - popad; - cmp cRet, 1; - cmovnb eax, rets[0]; // override result of turn - HookEnd; + push ecx; + mov ecx, eax; + call CombatTurnHook_Script; // edx - dudeBegin + pop ecx; retn; } } +static void __declspec(naked) CombatTurnHook_End() { + if (combatTurnResult >= 0) { + BeginHook(); + argCount = 3; + + args[0] = -2; // combat ended normally + args[1] = *(DWORD*)FO_VAR_combat_turn_obj; + args[2] = 0; + + RunHookScript(HOOK_COMBATTURN); + EndHook(); + } + __asm jmp fo::funcoffs::combat_over_; +} + // hack to exit from combat_add_noncoms function without crashing when you load game during NPC turn -static const DWORD CombatHack_add_noncoms_back = 0x422359; -static void __declspec(naked) CombatAddNoncoms_CombatTurnHack() { +static long countCombat = 0; +static void __declspec(naked) CombatTurnHook_AddNoncoms() { __asm { - call CombatTurnHook; + push ecx; + mov ecx, eax; + call CombatTurnHook_Script; // edx - dudeBegin + pop ecx; cmp eax, -1; - jne normalTurn; - mov ecx, FO_VAR_list_com; - mov dword ptr [ecx], 0; - mov ecx, [esp]; -normalTurn: - jmp CombatHack_add_noncoms_back; + je endCombat; + retn; +endCombat: + mov ecx, [esp + 4]; // list + xor edx, edx; + cmp ds:[FO_VAR_combat_end_due_to_load], edx; + jz skip; + mov eax, ds:[FO_VAR_list_com]; + test eax, eax; + jz skip; + mov countCombat, eax; +skip: + mov ds:[FO_VAR_list_com], edx; + retn; + } +} + +static const DWORD combat_hook_end_combat = 0x422E91; +static void __declspec(naked) combat_hook_fix_load() { + __asm { + call fo::funcoffs::combat_sequence_; + mov eax, countCombat; + test eax, eax; + jnz forceEndCombat; + retn; +forceEndCombat: + mov ds:[FO_VAR_list_com], eax; + mov countCombat, 0; + add esp, 4; + jmp combat_hook_end_combat; } } @@ -501,8 +543,11 @@ void Inject_AmmoCostHook() { } void Inject_CombatTurnHook() { - MakeJump(0x422354, CombatAddNoncoms_CombatTurnHack); + HookCall(0x422354, CombatTurnHook_AddNoncoms); HookCalls(CombatTurnHook, { 0x422D87, 0x422E20 }); + HookCall(0x422E85, CombatTurnHook_End); + + HookCall(0x422E4D, combat_hook_fix_load); } void Inject_OnExplosionHook() { diff --git a/sfall/Modules/PartyControl.cpp b/sfall/Modules/PartyControl.cpp index a565c056..084f89fa 100644 --- a/sfall/Modules/PartyControl.cpp +++ b/sfall/Modules/PartyControl.cpp @@ -68,6 +68,7 @@ static struct DudeState { long tag_skill[4]; //DWORD bbox_sneak; long* extendAddictGvar = nullptr; + bool isSaved = false; } realDude; static void SaveAddictGvarState() { @@ -127,11 +128,17 @@ static void SaveRealDudeState() { realDude.addictGvar[7] = fo::var::game_global_vars[fo::var::drugInfoList[8].addictGvar]; if (realDude.extendAddictGvar) SaveAddictGvarState(); + realDude.isSaved = true; + if (skipCounterAnim) SafeWriteBatch(0, {0x422BDE, 0x4229EC}); // no animate + + if (isDebug) fo::func::debug_printf("\n[SFALL] Save dude state."); } // take control of the NPC static void SetCurrentDude(fo::GameObject* npc) { + if (isDebug) fo::func::debug_printf("\n[SFALL] Take control of critter."); + // remove skill tags long tagSkill[4]; std::fill(std::begin(tagSkill), std::end(tagSkill), -1); @@ -226,7 +233,7 @@ static void SetCurrentDude(fo::GameObject* npc) { } // restores the real dude state -static void RestoreRealDudeState() { +static void RestoreRealDudeState(bool redraw = true) { assert(realDude.obj_dude != nullptr); fo::var::map_elevation = realDude.obj_dude->elevation; @@ -259,9 +266,13 @@ static void RestoreRealDudeState() { if (realDude.extendAddictGvar) RestoreAddictGvarState(); if (skipCounterAnim) SafeWriteBatch(1, {0x422BDE, 0x4229EC}); // restore - fo::func::intface_redraw(); + if (redraw) fo::func::intface_redraw(); + + realDude.isSaved = false; isControllingNPC = false; + + if (isDebug) fo::func::debug_printf("\n[SFALL] Restore control to dude."); } static void __stdcall DisplayCantDoThat() { @@ -323,8 +334,10 @@ end: void __stdcall PartyControlReset() { if (realDude.obj_dude != nullptr && isControllingNPC) { - RestoreRealDudeState(); + RestoreRealDudeState(false); } + realDude.obj_dude = nullptr; + realDude.isSaved = false; weaponState.clear(); } @@ -387,15 +400,17 @@ void PartyControl::SwitchToCritter(fo::GameObject* critter) { } } SaveWeaponMode(isSwap); - if (critter == nullptr || critter == realDude.obj_dude) RestoreRealDudeState(); - } else { + if (critter == nullptr || critter == realDude.obj_dude) RestoreRealDudeState(); // return control to dude + } else if (critter != nullptr && realDude.isSaved == false) { SaveRealDudeState(); } - if (critter != nullptr && critter != realDude.obj_dude) { + if (critter != nullptr && critter != PartyControl::RealDudeObject()) { SetCurrentDude(critter); + if (switchHandHookInjected) return; switchHandHookInjected = true; if (!HookScripts::IsInjectHook(HOOK_INVENTORYMOVE)) Inject_SwitchHandHook(); + // Gets dude perks and traits from script while controlling another NPC // WARNING: Handling dude perks/traits in the engine code while controlling another NPC remains impossible, this requires serious hacking of the engine code HookCall(0x458242, GetRealDudePerk); // op_has_trait_ diff --git a/sfall/Modules/Scripting/Handlers/Metarule.cpp b/sfall/Modules/Scripting/Handlers/Metarule.cpp index aac514f6..026f9faf 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.cpp +++ b/sfall/Modules/Scripting/Handlers/Metarule.cpp @@ -96,7 +96,7 @@ static const SfallMetarule metarules[] = { {"item_weight", sf_item_weight, 1, 1, {ARG_OBJECT}}, {"lock_is_jammed", sf_lock_is_jammed, 1, 1, {ARG_OBJECT}}, {"loot_obj", sf_get_loot_object, 0, 0}, - {"metarule_exist", sf_metarule_exist, 1, 1}, + {"metarule_exist", sf_metarule_exist, 1, 1}, // no arg check {"npc_engine_level_up", sf_npc_engine_level_up, 1, 1, {ARG_ANY}}, {"obj_under_cursor", sf_get_obj_under_cursor, 2, 2, {ARG_INT, ARG_INT}}, {"outlined_object", sf_outlined_object, 0, 0}, @@ -105,7 +105,7 @@ static const SfallMetarule metarules[] = { {"set_car_intface_art", sf_set_car_intface_art, 1, 1, {ARG_INT}}, {"set_cursor_mode", sf_set_cursor_mode, 1, 1, {ARG_INT}}, {"set_drugs_data", sf_set_drugs_data, 3, 3, {ARG_INT, ARG_INT, ARG_INT}}, - {"set_dude_obj", sf_set_dude_obj, 1, 1, {ARG_OBJECT}}, + {"set_dude_obj", sf_set_dude_obj, 1, 1, {ARG_INT}}, {"set_fake_perk_npc", sf_set_fake_perk_npc, 5, 5, {ARG_OBJECT, ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, {"set_fake_trait_npc", sf_set_fake_trait_npc, 5, 5, {ARG_OBJECT, ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, {"set_flags", sf_set_flags, 2, 2, {ARG_OBJECT, ARG_INT}}, diff --git a/sfall/Modules/Scripting/Handlers/Objects.cpp b/sfall/Modules/Scripting/Handlers/Objects.cpp index 2994180a..48860e7f 100644 --- a/sfall/Modules/Scripting/Handlers/Objects.cpp +++ b/sfall/Modules/Scripting/Handlers/Objects.cpp @@ -364,8 +364,8 @@ void sf_item_weight(OpcodeContext& ctx) { void sf_set_dude_obj(OpcodeContext& ctx) { auto obj = ctx.arg(0).asObject(); - if (obj->Type() == fo::OBJ_TYPE_CRITTER) { - //if (!InCombat && obj != PartyControl::RealDudeObject()) { + if (obj == nullptr || obj->Type() == fo::OBJ_TYPE_CRITTER) { + //if (!InCombat && obj && obj != PartyControl::RealDudeObject()) { // ctx.printOpcodeError("%s() - controlling of the critter is only allowed in combat mode.", ctx.getMetaruleName()); //} else { PartyControl::SwitchToCritter(obj); diff --git a/sfall/Modules/Scripting/OpcodeContext.h b/sfall/Modules/Scripting/OpcodeContext.h index 69e95eb3..e0caafbb 100644 --- a/sfall/Modules/Scripting/OpcodeContext.h +++ b/sfall/Modules/Scripting/OpcodeContext.h @@ -38,11 +38,11 @@ typedef void(*ScriptingFunctionHandler)(OpcodeContext&); // The type of argument, not the same as actual data type. Useful for validation. enum OpcodeArgumentType { ARG_ANY = 0, // no validation - ARG_INT, // integer only - ARG_NUMBER, // float OR integer - ARG_STRING, // string only - ARG_OBJECT, // integer that is not 0 - ARG_INTSTR // integer OR string + ARG_INT, // integer only + ARG_NUMBER, // float OR integer + ARG_STRING, // string only + ARG_OBJECT, // integer that is not 0 + ARG_INTSTR // integer OR string }; typedef struct SfallOpcodeInfo {