mirror of
https://github.com/sfall-team/sfall.git
synced 2026-07-27 16:52:34 -07:00
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:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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}},
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user