Backported three more hooks from 4.1:

* USESKILLON, ONEXPLOSION, SETLIGHTING.

Backported PartyMemberSkillFix option from 4.1.
Updated documents.
This commit is contained in:
NovaRain
2022-12-01 08:41:05 +08:00
parent bb13e3f219
commit 67b46cd4a6
15 changed files with 338 additions and 7 deletions
+7
View File
@@ -762,6 +762,13 @@ AIBestWeaponFix=1
;chem_primary_desire also works as a priority list of drug items the NPC will try to pick up in combat when they are on the ground
AIDrugUsePerfFix=0
;Set to 1 to fix the bug of using First Aid/Doctor skills when using them on the player
;This will cause the party member to perform First Aid/Doctor skills when you use them on the player, but only if
;the player is standing next to the party member
;Note that because the related engine function is not fully implemented, enabling this option without a global script
;that overrides First Aid/Doctor functions has very limited usefulness
PartyMemberSkillFix=0
;Overrides the global variable number used to show the special death message of the Modoc toilet explosion
;Set to -1 to disable the special death message when the global variable is set
SpecialDeathGVAR=491
+4
View File
@@ -60,6 +60,10 @@
#define HOOK_USEANIMOBJ (32)
#define HOOK_EXPLOSIVETIMER (33)
#define HOOK_DESCRIPTIONOBJ (34)
#define HOOK_USESKILLON (35)
#define HOOK_ONEXPLOSION (36)
//#define HOOK_SUBCOMBATDAMAGE (37) // unimplemented
#define HOOK_SETLIGHTING (38)
#define HOOK_SNEAK (39)
#define HOOK_STDPROCEDURE (40)
#define HOOK_STDPROCEDURE_END (41)
+51
View File
@@ -679,6 +679,57 @@ int ret0 - a pointer to the new text received by using the get_string_pointe
-------------------------------------------
#### `HOOK_USESKILLON (hs_useskillon.int)`
Runs before using any skill on any object. Lets you override the critter that uses the skill.
__NOTE:__ The user critter can't be overridden when using Steal skill.
```
Critter arg0 - the user critter (usually dude_obj)
Obj arg1 - the target object/critter
int arg2 - skill being used
int ret0 - a new critter to override the user critter. Pass -1 to cancel the skill use, pass 0 to skip this return value
int ret1 - pass 1 to allow the skill to be used in combat (only for dude_obj or critter being controlled by the player)
```
-------------------------------------------
#### `HOOK_ONEXPLOSION (hs_onexplosion.int)`
Runs when Fallout is checking all the tiles within the explosion radius for targets before an explosion occurs.\
The tile checking will be interrupted when 6 additional targets (critters) are received.
```
int arg0 - event type: 1 - when checking objects within the explosion radius without causing damage (e.g. the player drops an active explosive), 0 - otherwise
Critter arg1 - the attacker
int arg2 - the tile on which the explosion occurs
int arg3 - checked tile within the explosion radius
Obj arg4 - first found object on the checked tile as an additional target
Critter arg5 - the target critter, may be 0 or equal to the attacker
int arg6 - 1 when using throwing weapons (e.g. grenades), 0 otherwise
int ret0 - overrides the found object on the checked tile, pass 0 to skip the object
```
-------------------------------------------
#### `HOOK_SETLIGHTING (hs_setlighting.int)`
Runs before setting the light level for an object or a map. You can override the result.
```
Obj arg0 - the object being set, or -1 when setting the light level for a map
int arg1 - the light intensity
int arg2 - the light radius, or -1 when setting the light level for a map
int ret0 - overrides the light intensity. Intensity range is from 0 to 65536
int ret1 - overrides the light radius. Radius range is from 0 to 8 (works only for the object)
```
-------------------------------------------
#### `HOOK_SNEAK (hs_sneak.int)`
Runs when the Sneak skill is activated, or when the game rolls another Sneak check after the duration for the current one is over.\
@@ -42,6 +42,7 @@ FUNC(_word_wrap_, 0x4BC6F0)
FUNC(action_get_an_object_, 0x412134)
FUNC(action_loot_container_, 0x4123E8)
FUNC(action_use_an_item_on_object_, 0x411F2C)
FUNC(action_use_skill_on_, 0x41255C)
FUNC(add_bar_box_, 0x4616F0)
FUNC(adjust_ac_, 0x4715F8)
FUNC(adjust_fid_, 0x4716E8)
+3 -3
View File
@@ -97,10 +97,10 @@ static HooksInjectInfo injectHooks[] = {
{HOOK_USEANIMOBJ, Inject_UseAnimateObjHook, false},
{HOOK_EXPLOSIVETIMER, Inject_ExplosiveTimerHook, false},
{HOOK_DESCRIPTIONOBJ, Inject_DescriptionObjHook, false},
{HOOK_USESKILLON, Inject_UseSkillOnHook, false},
{HOOK_ONEXPLOSION, Inject_OnExplosionHook, false},
{-1, nullptr, false}, // dummy
{-1, nullptr, false}, // dummy
{-1, nullptr, false}, // dummy
{-1, nullptr, false}, // dummy
{HOOK_SETLIGHTING, Inject_SetLightingHook, false},
{HOOK_SNEAK, Inject_SneakCheckHook, false},
{HOOK_STDPROCEDURE, Inject_ScriptProcedureHook, false},
{HOOK_STDPROCEDURE_END, Inject_ScriptProcedureHook2, false},
+5 -1
View File
@@ -52,7 +52,7 @@ enum HookType
HOOK_INVENTORYMOVE = 24,
HOOK_INVENWIELD = 25,
HOOK_ADJUSTFID = 26,
HOOK_COMBATTURN = 27, // unimplemented
// HOOK_COMBATTURN = 27, // unimplemented
HOOK_CARTRAVEL = 28,
HOOK_SETGLOBALVAR = 29,
HOOK_RESTTIMER = 30,
@@ -60,6 +60,10 @@ enum HookType
HOOK_USEANIMOBJ = 32,
HOOK_EXPLOSIVETIMER = 33,
HOOK_DESCRIPTIONOBJ = 34,
HOOK_USESKILLON = 35,
HOOK_ONEXPLOSION = 36,
// HOOK_SUBCOMBATDAMAGE = 37, // unimplemented
HOOK_SETLIGHTING = 38,
HOOK_SNEAK = 39,
HOOK_STDPROCEDURE = 40,
HOOK_STDPROCEDURE_END = 41,
+54
View File
@@ -317,6 +317,55 @@ skip:
}
}
fo::GameObject* __fastcall ComputeExplosionOnExtrasHook_Script(fo::GameObject* object, DWORD isCheck, DWORD checkTile, fo::ComputeAttackResult* ctdSource, DWORD isThrowing, fo::GameObject* who) {
fo::GameObject* result = object;
BeginHook();
argCount = 7;
args[0] = isCheck;
args[1] = (DWORD)ctdSource->attacker;
args[2] = ctdSource->targetTile;
args[3] = checkTile;
args[4] = (DWORD)object;
args[5] = (DWORD)who;
args[6] = isThrowing;
RunHookScript(HOOK_ONEXPLOSION);
if (cRet > 0) result = (fo::GameObject*)rets[0]; // override object
EndHook();
return result;
}
static void __declspec(naked) ComputeExplosionOnExtrasHook() {
__asm {
cmp dword ptr [esp + 0x34 + 4], 0x429533; // skip hook when AI assesses the situation in choosing the best weapon
jz end;
cmp dword ptr [esp + 0x34 + 4], 0x4296BC;
jnz hook;
end:
jmp fo::funcoffs::obj_blocking_at_;
hook:
push ecx;
push eax; // who (target/source)
call fo::funcoffs::obj_blocking_at_;
push [esp + 0x34 - 0x24 + 12]; // isThrowing
xor edx, edx;
cmp dword ptr [esp + 0x34 + 16], 0x412F11; // check called from action_explode_
jz skip;
mov edx, [esp + 0x34 - 0x34 + 16]; // isCheck (bypass damage)
skip:
push esi; // source ctd
push edi; // check tile
mov ecx, eax; // object
call ComputeExplosionOnExtrasHook_Script; // edx - isCheck
pop ecx;
retn;
}
}
static DWORD targetRet = 0;
static bool targetObjHookHasRet = false;
@@ -441,6 +490,10 @@ void Inject_AmmoCostHook() {
HookCall(0x423A7C, AmmoCostHook); // compute_attack_
}
void Inject_OnExplosionHook() {
HookCall(0x423D70, ComputeExplosionOnExtrasHook);
}
void Inject_TargetObjectHook() {
MakeCall(0x44BB16, gmouse_bk_process_hook, 1);
SafeWrite8(0x44BB00, 0x15);
@@ -457,6 +510,7 @@ void InitCombatHookScripts() {
HookScripts::LoadHookScript("hs_findtarget", HOOK_FINDTARGET);
HookScripts::LoadHookScript("hs_itemdamage", HOOK_ITEMDAMAGE);
HookScripts::LoadHookScript("hs_ammocost", HOOK_AMMOCOST);
HookScripts::LoadHookScript("hs_onexplosion", HOOK_ONEXPLOSION);
HookScripts::LoadHookScript("hs_targetobject", HOOK_TARGETOBJECT);
}
+1
View File
@@ -14,6 +14,7 @@ void Inject_CombatDamageHook();
void Inject_FindTargetHook();
void Inject_ItemDamageHook();
void Inject_AmmoCostHook();
void Inject_OnExplosionHook();
void Inject_TargetObjectHook();
int __fastcall AmmoCostHook_Script(DWORD hookType, fo::GameObject* weapon, DWORD &rounds);
+1 -1
View File
@@ -67,7 +67,7 @@ static bool CheckRecursiveHooks(DWORD hook) {
if (hook == currentRunHook) {
switch (hook) {
case HOOK_SETGLOBALVAR:
// case HOOK_SETLIGHTING:
case HOOK_SETLIGHTING:
return true;
default:
if (isDebug) fo::func::debug_printf("\nWARNING: A recursive hook with ID %d was running.", hook);
+96
View File
@@ -94,6 +94,86 @@ static void __declspec(naked) OverrideCost_BarterPriceHook() {
}
}
static fo::GameObject* sourceSkillOn = nullptr;
void SourceUseSkillOnInit() { sourceSkillOn = *fo::ptr::obj_dude; }
static char resultSkillOn; // -1 - cancel handler, 1 - replace user
static long bakupCombatState;
static void __fastcall UseSkillOnHook_Script(DWORD source, DWORD target, register DWORD skillId) {
BeginHook();
argCount = 3;
args[0] = source; // user
args[1] = target; // target
args[2] = skillId; // skill id
sourceSkillOn = *fo::ptr::obj_dude;
resultSkillOn = 0;
bakupCombatState = -1;
RunHookScript(HOOK_USESKILLON);
if (skillId != fo::Skill::SKILL_STEAL && cRet > 0) { // not work for steal skill
if (rets[0] != 0) {
resultSkillOn = (rets[0] == -1) ? -1 : 1;
if (resultSkillOn == 1) {
sourceSkillOn = (fo::GameObject*)rets[0];
}
}
if (resultSkillOn != -1 && cRet > 1 && rets[1] == 1) {
bakupCombatState = *fo::ptr::combat_state;
*fo::ptr::combat_state = 0;
}
}
EndHook();
}
static void __declspec(naked) UseSkillOnHook() {
__asm {
push eax;
push ecx;
push edx;
push ebx; // skill id
mov ecx, eax; // user
call UseSkillOnHook_Script; // edx - target
pop edx;
pop ecx;
pop eax;
cmp resultSkillOn, -1; // skip handler
jnz handler;
retn;
handler:
jmp fo::funcoffs::action_use_skill_on_;
}
}
static void __declspec(naked) UseSkillOnHack() {
__asm {
cmp bakupCombatState, -1;
jz skip;
mov ebp, bakupCombatState;
mov dword ptr ds:[FO_VAR_combat_state], ebp;
skip:
cmp resultSkillOn, 0;
jz default;
mov edi, sourceSkillOn; // skill user (override from hook)
retn; // flag ZF = 0
default:
// engine code
cmp eax, dword ptr ds:[FO_VAR_obj_dude];
retn;
}
}
static void __declspec(naked) skill_use_hack() {
__asm {
cmp ebp, dword ptr ds:[FO_VAR_obj_dude];
setnz al;
retn;
}
}
static void __declspec(naked) UseSkillHook() {
__asm {
HookBegin;
@@ -610,6 +690,21 @@ void Inject_ExplosiveTimerHook() {
HookCall(0x49BDC4, ExplosiveTimerHook);
}
void Inject_UseSkillOnHook() {
const DWORD useSkillOnHkAddr[] = {0x44C3CA, 0x44C81C};
HookCalls(UseSkillOnHook, useSkillOnHkAddr);
MakeCall(0x4127BA, UseSkillOnHack, 1);
const DWORD skillUseTgtAddr[] = {0x4AB05D, 0x4AB558, 0x4ABA60};
MakeCalls(skill_use_hack, skillUseTgtAddr); // fix checking obj_dude's target
// replace _obj_dude with source skill user (skill_use_ function)
const DWORD skillUseSrcAddr[] = {
0x4AAF47, 0x4AB051, 0x4AB3FB, 0x4AB550, 0x4AB8FA, 0x4ABA54,
0x4AB0EF, 0x4AB5C0, 0x4ABAF2 // fix for time increment
};
SafeWriteBatch<DWORD>((DWORD)&sourceSkillOn, skillUseSrcAddr);
}
void Inject_EncounterHook() {
HookCall(0x4C02AF, wmWorldMap_hook);
HookCall(0x4C095C, wmRndEncounterOccurred_hook);
@@ -617,6 +712,7 @@ void Inject_EncounterHook() {
void InitMiscHookScripts() {
HookScripts::LoadHookScript("hs_barterprice", HOOK_BARTERPRICE);
HookScripts::LoadHookScript("hs_useskillon", HOOK_USESKILLON);
HookScripts::LoadHookScript("hs_useskill", HOOK_USESKILL);
HookScripts::LoadHookScript("hs_steal", HOOK_STEAL);
HookScripts::LoadHookScript("hs_sneak", HOOK_SNEAK);
+2
View File
@@ -4,8 +4,10 @@ namespace sfall
{
void InitMiscHookScripts();
void SourceUseSkillOnInit();
void Inject_BarterPriceHook();
void Inject_UseSkillOnHook();
void Inject_UseSkillHook();
void Inject_StealCheckHook();
void Inject_SneakCheckHook();
+73
View File
@@ -185,6 +185,72 @@ skip:
}
}
static bool __fastcall SetLightingHook_Script(DWORD &intensity, DWORD &radius, DWORD object) {
BeginHook();
argCount = 3;
args[0] = object;
args[1] = intensity;
args[2] = radius;
RunHookScript(HOOK_SETLIGHTING);
bool result = false;
if (cRet > 0) {
int light = rets[0];
if (light < 0) light = 0;
intensity = light;
if (cRet > 1 && object != -1) {
int dist = rets[1];
if (dist < 0) dist = 0;
radius = dist;
}
result = true;
}
EndHook();
return result;
}
static void __declspec(naked) SetObjectLightHook() {
__asm {
pushadc;
push ebp;
mov edx, esp; // &radius
push ebx;
mov ecx, esp; // &intensity
push esi; // object
call SetLightingHook_Script;
test al, al;
jz skip;
pop ebx; // return intensity value
pop ebp; // return radius value
jmp end;
skip:
add esp, 8;
end:
popadc;
jmp fo::funcoffs::obj_turn_off_light_;
}
}
static void __declspec(naked) SetMapLightHook() {
__asm {
push ecx;
push ebx;
mov ecx, esp; // &intensity
push -1; // no object (it's a map)
mov edx, esp; // no radius
call SetLightingHook_Script;
add esp, 4;
test al, al;
jz skip;
mov ebx, [esp - 4]; // return intensity value
skip:
pop ecx;
cmp ebx, dword ptr ds:[0x47A93D]; // get miminal ambient light intensity (16384)
retn;
}
}
static DWORD __fastcall StdProcedureHook_Script(long numHandler, fo::ScriptInstance* script, DWORD procTable) {
BeginHook();
argCount = 4;
@@ -282,6 +348,11 @@ void Inject_DescriptionObjHook() {
HookCall(0x49AE28, DescriptionObjHook);
}
void Inject_SetLightingHook() {
HookCall(0x48ACA0, SetObjectLightHook);
MakeCall(0x47A934, SetMapLightHook, 1);
}
void Inject_ScriptProcedureHook() {
MakeCall(0x4A491F, ScriptStdProcedureHook);
}
@@ -293,7 +364,9 @@ void Inject_ScriptProcedureHook2() {
void InitObjectHookScripts() {
HookScripts::LoadHookScript("hs_useobjon", HOOK_USEOBJON);
HookScripts::LoadHookScript("hs_useobj", HOOK_USEOBJ);
HookScripts::LoadHookScript("hs_useanimobj", HOOK_USEANIMOBJ);
HookScripts::LoadHookScript("hs_descriptionobj", HOOK_DESCRIPTIONOBJ);
HookScripts::LoadHookScript("hs_setlighting", HOOK_SETLIGHTING);
HookScripts::LoadHookScript("hs_stdprocedure", HOOK_STDPROCEDURE); // combo hook
HookScripts::LoadHookScript("hs_stdprocedure", HOOK_STDPROCEDURE_END);
}
+1
View File
@@ -9,6 +9,7 @@ void Inject_UseObjOnHook();
void Inject_UseObjHook();
void Inject_UseAnimateObjHook();
void Inject_DescriptionObjHook();
void Inject_SetLightingHook();
void Inject_ScriptProcedureHook();
void Inject_ScriptProcedureHook2();
+2
View File
@@ -36,6 +36,7 @@
#include "Graphics.h"
#include "HeroAppearance.h"
#include "HookScripts\Common.h"
#include "HookScripts\MiscHs.h"
#include "Interface.h"
#include "Inventory.h"
#include "LoadOrder.h"
@@ -141,6 +142,7 @@ static void __stdcall RunOnBeforeGameStart() {
static void __stdcall RunOnAfterGameStarted() {
if (femaleMsgs) CheckPlayerGender();
if (disableHorrigan) *fo::ptr::Meet_Frank_Horrigan = true;
SourceUseSkillOnInit();
InitGlobalScripts(); // running sfall scripts
}
+35
View File
@@ -332,6 +332,24 @@ static void __declspec(naked) intface_update_items_hack_end() {
}
}
static void __declspec(naked) action_use_skill_on_hook() {
__asm { // eax = dude_obj, edx = target, ebp = party_member
cmp eax, edx;
jnz end; // jump if target != dude_obj
mov edx, ebp;
call fo::funcoffs::obj_dist_; // check distance between dude_obj and party_member
cmp eax, 1; // if the distance is greater than 1, then reset the register
jg skip;
inc eax;
retn;
skip:
xor eax, eax;
retn;
end:
jmp fo::funcoffs::obj_dist_;
}
}
static void __declspec(naked) endgame_movie_hook() {
__asm {
cmp [esp + 16], 0x45C563; // call from op_endgame_movie_
@@ -714,6 +732,21 @@ static void KeepSelectModePatch() {
//}
}
static void PartyMemberSkillPatch() {
// Fix getting distance from source to target when using skills
// Note: this will cause the party member to perform First Aid/Doctor skills when you use them on the player, but only if
// the player is standing next to the party member. Because the related engine function is not fully implemented, enabling
// this option without a global script that overrides First Aid/Doctor functions has very limited usefulness
if (IniReader::GetConfigInt("Misc", "PartyMemberSkillFix", 0)) {
dlog("Applying First Aid/Doctor skill use patch for party members.", DL_INIT);
HookCall(0x412836, action_use_skill_on_hook);
dlogr(" Done", DL_INIT);
}
// Small code patch for HOOK_USESKILLON (change obj_dude to source)
SafeWrite32(0x4128F3, 0x90909090);
SafeWrite16(0x4128F7, 0xFE39); // cmp esi, _obj_dude -> cmp esi, edi
}
#pragma pack(push, 1)
struct CodeData {
DWORD dd;
@@ -1085,6 +1118,8 @@ void MiscPatches::init() {
DisplaySecondWeaponRangePatch();
KeepSelectModePatch();
PartyMemberSkillPatch();
SkipLoadingGameSettingsPatch();
UseWalkDistancePatch();