Backported game hooks from 4.0.x

* COMBATTURN is not implemented.

Updated documents.
This commit is contained in:
NovaRain
2022-11-26 09:57:01 +08:00
parent f4666dad18
commit 4f34217625
12 changed files with 276 additions and 32 deletions
+1 -1
View File
@@ -146,7 +146,7 @@
//#define RMOBJ_AI_USE_DRUG_ON_2 4360176 // same as RMOBJ_AI_USE_DRUG_ON (obsolete, use only for sfall before 4.3.1/3.8.31)
#define RMOBJ_BARTER_ARMOR 4675656 // removing armor from NPC's slot before entering the barter screen
#define RMOBJ_BARTER_WEAPON 4675722 // removing weapon from NPC's slot before entering the barter screen
#define RMOBJ_INVEN_DROP_CAPS 4667295 // if money/caps are dropped manually by the player from the inventory screen
#define RMOBJ_INVEN_DROP_CAPS 4667295 // if multiple money/caps are dropped manually by the player from the inventory screen
#define RMOBJ_DROP_INTO_CONTAINER 4678833 // when dropping items into a container item (bag/backpack)
// old defines
#define RMOBJ_RM_MULT_OBJS RMOBJ_ITEM_REMOVED_MULTI
+4
View File
@@ -52,6 +52,10 @@
#define HOOK_INVENTORYMOVE (24)
#define HOOK_INVENWIELD (25)
#define HOOK_ADJUSTFID (26)
//#define HOOK_COMBATTURN (27) // unimplemented
#define HOOK_CARTRAVEL (28)
#define HOOK_SETGLOBALVAR (29)
#define HOOK_RESTTIMER (30)
#define HOOK_GAMEMODECHANGE (31)
// Valid arguments to list_begin
+42
View File
@@ -582,6 +582,48 @@ int ret0 - overrides the calculated FID with provided value
-------------------------------------------
#### `HOOK_CARTRAVEL (hs_cartravel.int)`
Runs continuously during worldmap travel on car.
```
int arg0 - vanilla car speed (between 3 and 8 "steps")
int arg1 - vanilla fuel consumption (100 and below)
int ret0 - car speed override (pass -1 if you just want to override fuel consumption)
int ret1 - fuel consumption override
```
-------------------------------------------
#### `HOOK_SETGLOBALVAR (hs_setglobalvar.int)`
Runs when setting the value of a global variable.
```
int arg0 - the index number of the global variable being set
int arg1 - the set value of the global variable
int ret0 - overrides the value of the global variable
```
-------------------------------------------
#### `HOOK_RESTTIMER (hs_resttimer.int)`
Runs continuously while the player is resting (using pipboy alarm clock).
```
int arg0 - the game time in ticks
int arg1 - event type: 1 - when the resting ends normally, -1 - when pressing ESC to cancel the timer, 0 - otherwise
int arg2 - the hour part of the length of resting time
int arg3 - the minute part of the length of resting time
int ret0 - pass 1 to interrupt the resting, pass 0 to continue the rest if it was interrupted by pressing ESC key
```
-------------------------------------------
#### `HOOK_GAMEMODECHANGE (hs_gamemodechange.int)`
Runs once every time when the game mode was changed, like opening/closing the inventory, character screen, pipboy, etc.
@@ -732,6 +732,7 @@ FUNC(windowOutput_, 0x4B80A4)
FUNC(windowShow_, 0x4B7648)
FUNC(windowWidth_, 0x4B7734)
FUNC(windowWrapLineWithSpacing_, 0x4B8854)
FUNC(wmCarUseGas_, 0x4C4DA4)
FUNC(wmDrawCursorStopped_, 0x4C41EC)
FUNC(wmEvalTileNumForPlacement_, 0x4C1A64)
FUNC(wmFindCurSubTileFromPos_, 0x4C0C00)
+5 -2
View File
@@ -58,8 +58,6 @@ WRAP_WATCOM_FFUNC4(void, register_object_call, long*, target, long*, source, voi
WRAP_WATCOM_FFUNC3(long, scr_get_local_var, long, sid, long, varId, long*, value)
WRAP_WATCOM_FFUNC3(long, scr_set_local_var, long, sid, long, varId, long, value)
WRAP_WATCOM_FFUNC6(long, text_object_create, fo::GameObject*, object, const char*, text, long, font, long, colorText, long, colorOutline, fo::BoundRect*, rect)
WRAP_WATCOM_FFUNC3(long, tile_coord, long, tile, long*, outX, long*, outY) // the fourth argument of the function is not used
WRAP_WATCOM_FFUNC3(long, tile_num_in_direction, long, tile, long, rotation, long, distance)
WRAP_WATCOM_FFUNC8(void, trans_cscale, void*, fromBuff, long, width, long, height, long, fromPitch, void*, toBuff, long, toWidth, long, toHeight, long, toPitch)
WRAP_WATCOM_FFUNC3(void, win_clip, fo::Window*, window, fo::RectList**, rects, void*, buffer)
WRAP_WATCOM_FFUNC6(void, win_print, long, winID, const char*, text, long, textWidth, long, xPos, long, yPos, long, colorFlags)
@@ -99,6 +97,7 @@ WRAP_WATCOM_FUNC2(void, executeProcedure, fo::Program*, sptr, long, procNum)
WRAP_WATCOM_FUNC1(long, folder_print_line, const char*, text)
WRAP_WATCOM_FUNC1(const char*, findCurrentProc, fo::Program*, program) // Returns the name of current procedure by program pointer
WRAP_WATCOM_FUNC1(long, FMtext_width, const char*, text)
WRAP_WATCOM_FUNC1(long, game_get_global_var, long, globalVar)
WRAP_WATCOM_FUNC0(long, get_input)
// Searches for message ID in given message file and places result in result argument
WRAP_WATCOM_FUNC3(const char*, getmsg, const fo::MessageList*, fileAddr, fo::MessageNode*, result, long, messageId)
@@ -222,9 +221,11 @@ WRAP_WATCOM_FUNC2(void, skill_set_tags, long*, tags, long, num)
WRAP_WATCOM_FUNC2(long, stat_level, fo::GameObject*, critter, long, statId)
WRAP_WATCOM_FUNC1(void, stat_pc_add_experience, long, amount) // Adds experience points to PC
WRAP_WATCOM_FUNC1(long, text_font, long, fontNum)
WRAP_WATCOM_FUNC3(long, tile_coord, long, tile, long*, outX, long*, outY) // the fourth argument of the function is not used
WRAP_WATCOM_FUNC2(long, tile_dist, long, scrTile, long, dstTile)
WRAP_WATCOM_FUNC2(long, tile_dir, long, scrTile, long, dstTile)
WRAP_WATCOM_FUNC2(long, tile_idistance, long, sourceTile, long, targetTile)
WRAP_WATCOM_FUNC3(long, tile_num_in_direction, long, tile, long, rotation, long, distance)
WRAP_WATCOM_FUNC1(long, tile_on_edge, long, tile)
WRAP_WATCOM_FUNC0(void, tile_refresh_display) // Redraws the whole screen
WRAP_WATCOM_FUNC2(void, tile_refresh_rect, fo::BoundRect*, boundRect, long, elevation) // Redraws the given rectangle on screen
@@ -237,7 +238,9 @@ WRAP_WATCOM_FUNC1(BYTE*, win_get_buf, DWORD, winRef)
WRAP_WATCOM_FUNC1(void, win_hide, DWORD, winRef)
WRAP_WATCOM_FUNC1(void, win_show, DWORD, winRef)
WRAP_WATCOM_FUNC0(long, windowWidth)
WRAP_WATCOM_FUNC1(void, wmCarUseGas, long, gasAmount)
WRAP_WATCOM_FUNC1(long, wmEvalTileNumForPlacement, long, tile)
WRAP_WATCOM_FUNC0(void, wmPartyWalkingStep)
WRAP_WATCOM_FUNC1(void, wmRefreshInterfaceOverlay, long, isRedraw)
/* Database functions */
+3 -3
View File
@@ -90,9 +90,9 @@ static HooksInjectInfo injectHooks[] = {
{HOOK_INVENWIELD, Inject_InvenWieldHook, false},
{HOOK_ADJUSTFID, nullptr, true}, // always embedded to the engine
{-1, nullptr, false}, // dummy
{-1, nullptr, false}, // dummy
{-1, nullptr, false}, // dummy
{-1, nullptr, false}, // dummy
{HOOK_CARTRAVEL, Inject_CarTravelHook, false},
{HOOK_SETGLOBALVAR, Inject_SetGlobalVarHook, false},
{HOOK_RESTTIMER, Inject_RestTimerHook, false},
{HOOK_GAMEMODECHANGE, nullptr, true}, // always embedded to the engine
};
+6 -2
View File
@@ -51,8 +51,12 @@ enum HookType
HOOK_WITHINPERCEPTION = 23,
HOOK_INVENTORYMOVE = 24,
HOOK_INVENWIELD = 25,
HOOK_ADJUSTFID = 26, // 4.x backport
HOOK_GAMEMODECHANGE = 31, // 4.x backport
HOOK_ADJUSTFID = 26,
HOOK_COMBATTURN = 27, // unimplemented
HOOK_CARTRAVEL = 28,
HOOK_SETGLOBALVAR = 29,
HOOK_RESTTIMER = 30,
HOOK_GAMEMODECHANGE = 31,
HOOK_COUNT
};
+20 -4
View File
@@ -62,6 +62,20 @@ void __stdcall HookCommon::SetHSReturn(DWORD value) {
}
}
// List of hooks that are not allowed to be called recursively
static bool CheckRecursiveHooks(DWORD hook) {
if (hook == currentRunHook) {
switch (hook) {
case HOOK_SETGLOBALVAR:
// case HOOK_SETLIGHTING:
return true;
default:
if (isDebug) fo::func::debug_printf("\nWARNING: A recursive hook with ID %d was running.", hook);
}
}
return false;
}
void __stdcall BeginHook() {
if (callDepth && callDepth <= maxDepth) {
// save all values of the current hook if another hook was called during the execution of the current hook
@@ -97,10 +111,12 @@ static void __stdcall RunSpecificHookScript(HookScript *hook) {
void __stdcall RunHookScript(DWORD hook) {
cRet = 0;
if (!hooks[hook].empty()) {
if (callDepth > 8) {
fo::func::debug_printf("\n[SFALL] The hook ID: %d cannot be executed.", hook);
dlog_f("The hook %d cannot be executed due to exceeding depth limit\n", DL_MAIN, hook);
return;
if (callDepth > 1) {
if (CheckRecursiveHooks(hook) || callDepth > 8) {
fo::func::debug_printf("\n[SFALL] The hook ID: %d cannot be executed.", hook);
dlog_f("The hook %d cannot be executed due to exceeding depth limit or disallowed recursive calls\n", DL_MAIN, hook);
return;
}
}
currentRunHook = hook;
size_t hooksCount = hooks[hook].size();
+180
View File
@@ -226,6 +226,167 @@ static void __declspec(naked) PerceptionSearchTargetHook() {
}
}
static const long maxGasAmount = 80000;
static void CarTravelHook_Script() {
BeginHook();
argCount = 2;
// calculate vanilla speed
int carSpeed = 3;
if (fo::func::game_get_global_var(fo::GVAR_CAR_BLOWER)) {
carSpeed += 1;
}
if (fo::func::game_get_global_var(fo::GVAR_NEW_RENO_CAR_UPGRADE)) {
carSpeed += 1;
}
if (fo::func::game_get_global_var(fo::GVAR_NEW_RENO_SUPER_CAR)) {
carSpeed += 3;
}
// calculate vanilla fuel consumption
int originalGas = *fo::ptr::carGasAmount;
*fo::ptr::carGasAmount = maxGasAmount;
fo::func::wmCarUseGas(100);
int consumption = maxGasAmount - *fo::ptr::carGasAmount;
// run script
args[0] = carSpeed;
args[1] = consumption;
RunHookScript(HOOK_CARTRAVEL);
// override travel speed
if (cRet > 0 && static_cast<long>(rets[0]) >= 0) {
carSpeed = rets[0];
}
// move car on map
for (int i = 0; i < carSpeed; ++i) {
fo::func::wmPartyWalkingStep();
}
// override fuel consumption
if (cRet > 1) {
consumption = static_cast<long>(rets[1]);
}
// consume fuel
*fo::ptr::carGasAmount = max(0, originalGas - consumption); // Note: the reverse breaks the result in VS2010
EndHook();
}
static void __declspec(naked) CarTravelHack() {
static const DWORD CarTravelHack_back = 0x4BFF43;
__asm {
pushad;
call CarTravelHook_Script;
popad;
jmp CarTravelHack_back;
}
}
static long __fastcall GlobalVarHook_Script(register DWORD number, register int value) {
int old = (*fo::ptr::game_global_vars)[number];
if (IsGameLoaded() && HookScripts::HookHasScript(HOOK_SETGLOBALVAR)) { // IsGameLoaded - don't execute hook until loading sfall scripts
BeginHook();
argCount = 2;
args[0] = number;
args[1] = value;
RunHookScript(HOOK_SETGLOBALVAR);
if (cRet > 0) value = rets[0];
EndHook();
}
if (number == fo::GVAR_PLAYER_REPUTATION && displayKarmaChanges) {
int diff = value - old;
if (diff != 0) Karma::DisplayKarma(diff);
}
return value;
}
static void __declspec(naked) SetGlobalVarHook() {
__asm {
push eax;
push ecx;
push edx;
mov ecx, eax; // number
call GlobalVarHook_Script; // edx - value
pop edx;
pop ecx;
cmp eax, edx; // if return value != set value
cmovne edx, eax;
pop eax;
jmp fo::funcoffs::game_set_global_var_;
}
}
static int restTicks;
static long __fastcall RestTimerHook_Script(DWORD hours, DWORD minutes, DWORD gameTime, DWORD addrHook) {
addrHook -= 5;
BeginHook();
argCount = 4;
args[0] = gameTime;
args[2] = hours;
args[3] = minutes;
if (addrHook == 0x499CA1 || addrHook == 0x499B63) {
args[0] = restTicks;
args[1] = -1;
} else {
restTicks = args[0];
args[1] = (addrHook == 0x499DF2 || (args[2] == 0 && addrHook == 0x499BE0)) ? 1 : 0;
}
RunHookScript(HOOK_RESTTIMER);
long result = -1;
if (cRet > 0) {
result = (rets[0] != 0) ? 1 : 0;
}
EndHook();
return result;
}
static void __declspec(naked) RestTimerLoopHook() {
__asm {
pushadc;
mov edx, [esp + 16 + 0x44]; // minutes_
mov ecx, [esp + 16 + 0x40]; // hours_
push [esp + 12]; // addrHook
push eax; // gameTime
call RestTimerHook_Script;
pop ecx;
pop edx;
test eax, eax; // result >= 0
cmovge edi, eax; // return 1 to interrupt resting
pop eax;
jmp fo::funcoffs::set_game_time_;
}
}
static void __declspec(naked) RestTimerEscapeHook() {
__asm {
mov edi, 1; // engine code
cmp eax, 0x1B; // ESC ASCII code
jnz skip;
pushadc;
mov edx, [esp + 16 + 0x44]; // minutes_
mov ecx, [esp + 16 + 0x40]; // hours_
push [esp + 12]; // addrHook
push eax; // gameTime
call RestTimerHook_Script;
pop ecx;
pop edx;
test eax, eax; // result >= 0
cmovge edi, eax; // return 0 for cancel ESC key
pop eax;
skip:
retn;
}
}
void Inject_BarterPriceHook() {
const DWORD barterPriceHkAddr[] = {
0x474D4C, // barter_attempt_transaction_ (offers button)
@@ -261,11 +422,30 @@ void Inject_WithinPerceptionHook() {
HookCall(0x458403, PerceptionRangeHearHook);
}
void Inject_CarTravelHook() {
MakeJump(0x4BFEF1, CarTravelHack);
BlockCall(0x4BFF6E); // vanilla wmCarUseGas(100) call
}
void Inject_SetGlobalVarHook() {
HookCall(0x455A6D, SetGlobalVarHook);
}
void Inject_RestTimerHook() {
const DWORD restTimerLoopHkAddr[] = {0x499B4B, 0x499BE0, 0x499D2C, 0x499DF2};
HookCalls(RestTimerLoopHook, restTimerLoopHkAddr);
const DWORD restTimerEscHkAddr[] = {0x499B63, 0x499CA1};
MakeCalls(RestTimerEscapeHook, restTimerEscHkAddr);
}
void InitMiscHookScripts() {
HookScripts::LoadHookScript("hs_barterprice", HOOK_BARTERPRICE);
HookScripts::LoadHookScript("hs_useskill", HOOK_USESKILL);
HookScripts::LoadHookScript("hs_steal", HOOK_STEAL);
HookScripts::LoadHookScript("hs_withinperception", HOOK_WITHINPERCEPTION);
HookScripts::LoadHookScript("hs_cartravel", HOOK_CARTRAVEL);
HookScripts::LoadHookScript("hs_setglobalvar", HOOK_SETGLOBALVAR);
HookScripts::LoadHookScript("hs_resttimer", HOOK_RESTTIMER);
}
}
+3
View File
@@ -9,6 +9,9 @@ void Inject_BarterPriceHook();
void Inject_UseSkillHook();
void Inject_StealCheckHook();
void Inject_WithinPerceptionHook();
void Inject_CarTravelHook();
void Inject_SetGlobalVarHook();
void Inject_RestTimerHook();
long PerceptionRangeHook_Invoke(fo::GameObject* watcher, fo::GameObject* target, long type, long result);
+7 -20
View File
@@ -23,6 +23,8 @@
#include "..\FalloutEngine\Fallout2.h"
#include "..\Translate.h"
#include "HookScripts.h"
#include "Karma.h"
namespace sfall
@@ -37,6 +39,7 @@ static std::vector<KarmaFrmSetting> karmaFrms;
static char karmaGainMsg[128];
static char karmaLossMsg[128];
bool displayKarmaChanges;
static DWORD __stdcall DrawCard() {
int reputation = (*fo::ptr::game_global_vars)[fo::GVAR_PLAYER_REPUTATION];
@@ -64,7 +67,7 @@ skip:
}
}
static void __stdcall SetKarma(int value) {
void Karma::DisplayKarma(int value) {
char buf[128];
if (value > 0) {
sprintf_s(buf, karmaGainMsg, value);
@@ -74,29 +77,13 @@ static void __stdcall SetKarma(int value) {
fo::func::display_print(buf);
}
static void __declspec(naked) SetGlobalVarWrapper() {
__asm {
test eax, eax; // Gvar number
jnz end;
pushadc;
call fo::funcoffs::game_get_global_var_;
sub edx, eax; // value -= old
jz skip;
push edx;
call SetKarma;
skip:
popadc;
end:
jmp fo::funcoffs::game_set_global_var_;
}
}
static void ApplyDisplayKarmaChangesPatch() {
if (IniReader::GetConfigInt("Misc", "DisplayKarmaChanges", 0)) {
displayKarmaChanges = IniReader::GetConfigInt("Misc", "DisplayKarmaChanges", 0) != 0;
if (displayKarmaChanges) {
dlog("Applying display karma changes patch.", DL_INIT);
Translate::Get("sfall", "KarmaGain", "You gained %d karma.", karmaGainMsg);
Translate::Get("sfall", "KarmaLoss", "You lost %d karma.", karmaLossMsg);
HookCall(0x455A6D, SetGlobalVarWrapper);
HookScripts::InjectingHook(HOOK_SETGLOBALVAR);
dlogr(" Done", DL_INIT);
}
}
+4
View File
@@ -25,6 +25,10 @@ class Karma {
public:
static const char* name() { return "Karma"; }
static void init();
static void DisplayKarma(int value);
};
extern bool displayKarmaChanges;
}