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.
This commit is contained in:
NovaRain
2019-07-01 12:15:32 +08:00
parent 2ffdc43869
commit ff2ebdc82a
8 changed files with 114 additions and 52 deletions
+2 -2
View File
@@ -462,9 +462,9 @@ HOOK_COMBATTURN (hs_combatturn.int)
Runs before and after each turn in combat (for both PC and NPC). 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 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 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
+1
View File
@@ -33,6 +33,7 @@
#define FO_VAR_combat_free_move 0x56D39C #define FO_VAR_combat_free_move 0x56D39C
#define FO_VAR_combat_list 0x56D390 #define FO_VAR_combat_list 0x56D390
#define FO_VAR_combat_state 0x510944 #define FO_VAR_combat_state 0x510944
#define FO_VAR_combat_turn_obj 0x56D388
#define FO_VAR_combat_turn_running 0x51093C #define FO_VAR_combat_turn_running 0x51093C
#define FO_VAR_combatNumTurns 0x510940 #define FO_VAR_combatNumTurns 0x510940
#define FO_VAR_crit_succ_eff 0x510978 #define FO_VAR_crit_succ_eff 0x510978
+1
View File
@@ -20,6 +20,7 @@ VAR_(carCurrentArea, DWORD)
VAR_(carGasAmount, long) // from 0 to 80000 VAR_(carGasAmount, long) // from 0 to 80000
VAR_(cmap, DWORD) VAR_(cmap, DWORD)
VAR_(colorTable, DWORD) VAR_(colorTable, DWORD)
VAR_(combat_end_due_to_load, DWORD)
VAR_(combat_free_move, DWORD) VAR_(combat_free_move, DWORD)
VAR_(combat_list, DWORD) VAR_(combat_list, DWORD)
VAR_(combat_state, DWORD) VAR_(combat_state, DWORD)
+80 -35
View File
@@ -340,56 +340,98 @@ skip:
} }
// hooks combat_turn function // hooks combat_turn function
static void __declspec(naked) CombatTurnHook() { static long combatTurnResult = 0;
__asm { static long __fastcall CombatTurnHook_Script(fo::GameObject* critter, long dudeBegin) {
HookBegin; BeginHook();
mov args[0], 1; // turn begin
mov args[4], eax; // critter
mov args[8], edx; // unknown (1 = dude turn)
pushad;
}
argCount = 3; 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 RunHookScript(HOOK_COMBATTURN); // Start of turn
_asm popad; combatTurnResult = 0;
if (cRet > 0) { if (cRet > 0 && rets[0] == 1) { // skip turn
_asm mov eax, rets[0]; goto endHook; // exit hook
HookEnd;
_asm retn; // exit hook
}
// set_sfall_return not used, proceed normally
__asm {
call fo::funcoffs::combat_turn_;
mov args[0], eax;
pushad;
} }
// 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 //cRet = 0; // reset number of return values
RunHookScript(HOOK_COMBATTURN); // End of turn 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 { __asm {
popad; push ecx;
cmp cRet, 1; mov ecx, eax;
cmovnb eax, rets[0]; // override result of turn call CombatTurnHook_Script; // edx - dudeBegin
HookEnd; pop ecx;
retn; 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 // 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 long countCombat = 0;
static void __declspec(naked) CombatAddNoncoms_CombatTurnHack() { static void __declspec(naked) CombatTurnHook_AddNoncoms() {
__asm { __asm {
call CombatTurnHook; push ecx;
mov ecx, eax;
call CombatTurnHook_Script; // edx - dudeBegin
pop ecx;
cmp eax, -1; cmp eax, -1;
jne normalTurn; je endCombat;
mov ecx, FO_VAR_list_com; retn;
mov dword ptr [ecx], 0; endCombat:
mov ecx, [esp]; mov ecx, [esp + 4]; // list
normalTurn: xor edx, edx;
jmp CombatHack_add_noncoms_back; 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() { void Inject_CombatTurnHook() {
MakeJump(0x422354, CombatAddNoncoms_CombatTurnHack); HookCall(0x422354, CombatTurnHook_AddNoncoms);
HookCalls(CombatTurnHook, { 0x422D87, 0x422E20 }); HookCalls(CombatTurnHook, { 0x422D87, 0x422E20 });
HookCall(0x422E85, CombatTurnHook_End);
HookCall(0x422E4D, combat_hook_fix_load);
} }
void Inject_OnExplosionHook() { void Inject_OnExplosionHook() {
+21 -6
View File
@@ -68,6 +68,7 @@ static struct DudeState {
long tag_skill[4]; long tag_skill[4];
//DWORD bbox_sneak; //DWORD bbox_sneak;
long* extendAddictGvar = nullptr; long* extendAddictGvar = nullptr;
bool isSaved = false;
} realDude; } realDude;
static void SaveAddictGvarState() { static void SaveAddictGvarState() {
@@ -127,11 +128,17 @@ static void SaveRealDudeState() {
realDude.addictGvar[7] = fo::var::game_global_vars[fo::var::drugInfoList[8].addictGvar]; realDude.addictGvar[7] = fo::var::game_global_vars[fo::var::drugInfoList[8].addictGvar];
if (realDude.extendAddictGvar) SaveAddictGvarState(); if (realDude.extendAddictGvar) SaveAddictGvarState();
realDude.isSaved = true;
if (skipCounterAnim) SafeWriteBatch<BYTE>(0, {0x422BDE, 0x4229EC}); // no animate if (skipCounterAnim) SafeWriteBatch<BYTE>(0, {0x422BDE, 0x4229EC}); // no animate
if (isDebug) fo::func::debug_printf("\n[SFALL] Save dude state.");
} }
// take control of the NPC // take control of the NPC
static void SetCurrentDude(fo::GameObject* npc) { static void SetCurrentDude(fo::GameObject* npc) {
if (isDebug) fo::func::debug_printf("\n[SFALL] Take control of critter.");
// remove skill tags // remove skill tags
long tagSkill[4]; long tagSkill[4];
std::fill(std::begin(tagSkill), std::end(tagSkill), -1); std::fill(std::begin(tagSkill), std::end(tagSkill), -1);
@@ -226,7 +233,7 @@ static void SetCurrentDude(fo::GameObject* npc) {
} }
// restores the real dude state // restores the real dude state
static void RestoreRealDudeState() { static void RestoreRealDudeState(bool redraw = true) {
assert(realDude.obj_dude != nullptr); assert(realDude.obj_dude != nullptr);
fo::var::map_elevation = realDude.obj_dude->elevation; fo::var::map_elevation = realDude.obj_dude->elevation;
@@ -259,9 +266,13 @@ static void RestoreRealDudeState() {
if (realDude.extendAddictGvar) RestoreAddictGvarState(); if (realDude.extendAddictGvar) RestoreAddictGvarState();
if (skipCounterAnim) SafeWriteBatch<BYTE>(1, {0x422BDE, 0x4229EC}); // restore if (skipCounterAnim) SafeWriteBatch<BYTE>(1, {0x422BDE, 0x4229EC}); // restore
fo::func::intface_redraw();
if (redraw) fo::func::intface_redraw();
realDude.isSaved = false;
isControllingNPC = false; isControllingNPC = false;
if (isDebug) fo::func::debug_printf("\n[SFALL] Restore control to dude.");
} }
static void __stdcall DisplayCantDoThat() { static void __stdcall DisplayCantDoThat() {
@@ -323,8 +334,10 @@ end:
void __stdcall PartyControlReset() { void __stdcall PartyControlReset() {
if (realDude.obj_dude != nullptr && isControllingNPC) { if (realDude.obj_dude != nullptr && isControllingNPC) {
RestoreRealDudeState(); RestoreRealDudeState(false);
} }
realDude.obj_dude = nullptr;
realDude.isSaved = false;
weaponState.clear(); weaponState.clear();
} }
@@ -387,15 +400,17 @@ void PartyControl::SwitchToCritter(fo::GameObject* critter) {
} }
} }
SaveWeaponMode(isSwap); SaveWeaponMode(isSwap);
if (critter == nullptr || critter == realDude.obj_dude) RestoreRealDudeState(); if (critter == nullptr || critter == realDude.obj_dude) RestoreRealDudeState(); // return control to dude
} else { } else if (critter != nullptr && realDude.isSaved == false) {
SaveRealDudeState(); SaveRealDudeState();
} }
if (critter != nullptr && critter != realDude.obj_dude) { if (critter != nullptr && critter != PartyControl::RealDudeObject()) {
SetCurrentDude(critter); SetCurrentDude(critter);
if (switchHandHookInjected) return; if (switchHandHookInjected) return;
switchHandHookInjected = true; switchHandHookInjected = true;
if (!HookScripts::IsInjectHook(HOOK_INVENTORYMOVE)) Inject_SwitchHandHook(); if (!HookScripts::IsInjectHook(HOOK_INVENTORYMOVE)) Inject_SwitchHandHook();
// Gets dude perks and traits from script while controlling another NPC // 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 // 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_ HookCall(0x458242, GetRealDudePerk); // op_has_trait_
@@ -96,7 +96,7 @@ static const SfallMetarule metarules[] = {
{"item_weight", sf_item_weight, 1, 1, {ARG_OBJECT}}, {"item_weight", sf_item_weight, 1, 1, {ARG_OBJECT}},
{"lock_is_jammed", sf_lock_is_jammed, 1, 1, {ARG_OBJECT}}, {"lock_is_jammed", sf_lock_is_jammed, 1, 1, {ARG_OBJECT}},
{"loot_obj", sf_get_loot_object, 0, 0}, {"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}}, {"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}}, {"obj_under_cursor", sf_get_obj_under_cursor, 2, 2, {ARG_INT, ARG_INT}},
{"outlined_object", sf_outlined_object, 0, 0}, {"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_car_intface_art", sf_set_car_intface_art, 1, 1, {ARG_INT}},
{"set_cursor_mode", sf_set_cursor_mode, 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_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_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_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}}, {"set_flags", sf_set_flags, 2, 2, {ARG_OBJECT, ARG_INT}},
+2 -2
View File
@@ -364,8 +364,8 @@ void sf_item_weight(OpcodeContext& ctx) {
void sf_set_dude_obj(OpcodeContext& ctx) { void sf_set_dude_obj(OpcodeContext& ctx) {
auto obj = ctx.arg(0).asObject(); auto obj = ctx.arg(0).asObject();
if (obj->Type() == fo::OBJ_TYPE_CRITTER) { if (obj == nullptr || obj->Type() == fo::OBJ_TYPE_CRITTER) {
//if (!InCombat && obj != PartyControl::RealDudeObject()) { //if (!InCombat && obj && obj != PartyControl::RealDudeObject()) {
// ctx.printOpcodeError("%s() - controlling of the critter is only allowed in combat mode.", ctx.getMetaruleName()); // ctx.printOpcodeError("%s() - controlling of the critter is only allowed in combat mode.", ctx.getMetaruleName());
//} else { //} else {
PartyControl::SwitchToCritter(obj); PartyControl::SwitchToCritter(obj);