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 ;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 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 ;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 ;Set to -1 to disable the special death message when the global variable is set
SpecialDeathGVAR=491 SpecialDeathGVAR=491
+4
View File
@@ -60,6 +60,10 @@
#define HOOK_USEANIMOBJ (32) #define HOOK_USEANIMOBJ (32)
#define HOOK_EXPLOSIVETIMER (33) #define HOOK_EXPLOSIVETIMER (33)
#define HOOK_DESCRIPTIONOBJ (34) #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_SNEAK (39)
#define HOOK_STDPROCEDURE (40) #define HOOK_STDPROCEDURE (40)
#define HOOK_STDPROCEDURE_END (41) #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)` #### `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.\ 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_get_an_object_, 0x412134)
FUNC(action_loot_container_, 0x4123E8) FUNC(action_loot_container_, 0x4123E8)
FUNC(action_use_an_item_on_object_, 0x411F2C) FUNC(action_use_an_item_on_object_, 0x411F2C)
FUNC(action_use_skill_on_, 0x41255C)
FUNC(add_bar_box_, 0x4616F0) FUNC(add_bar_box_, 0x4616F0)
FUNC(adjust_ac_, 0x4715F8) FUNC(adjust_ac_, 0x4715F8)
FUNC(adjust_fid_, 0x4716E8) FUNC(adjust_fid_, 0x4716E8)
+3 -3
View File
@@ -97,10 +97,10 @@ static HooksInjectInfo injectHooks[] = {
{HOOK_USEANIMOBJ, Inject_UseAnimateObjHook, false}, {HOOK_USEANIMOBJ, Inject_UseAnimateObjHook, false},
{HOOK_EXPLOSIVETIMER, Inject_ExplosiveTimerHook, false}, {HOOK_EXPLOSIVETIMER, Inject_ExplosiveTimerHook, false},
{HOOK_DESCRIPTIONOBJ, Inject_DescriptionObjHook, 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 {HOOK_SETLIGHTING, Inject_SetLightingHook, false},
{-1, nullptr, false}, // dummy
{-1, nullptr, false}, // dummy
{HOOK_SNEAK, Inject_SneakCheckHook, false}, {HOOK_SNEAK, Inject_SneakCheckHook, false},
{HOOK_STDPROCEDURE, Inject_ScriptProcedureHook, false}, {HOOK_STDPROCEDURE, Inject_ScriptProcedureHook, false},
{HOOK_STDPROCEDURE_END, Inject_ScriptProcedureHook2, false}, {HOOK_STDPROCEDURE_END, Inject_ScriptProcedureHook2, false},
+5 -1
View File
@@ -52,7 +52,7 @@ enum HookType
HOOK_INVENTORYMOVE = 24, HOOK_INVENTORYMOVE = 24,
HOOK_INVENWIELD = 25, HOOK_INVENWIELD = 25,
HOOK_ADJUSTFID = 26, HOOK_ADJUSTFID = 26,
HOOK_COMBATTURN = 27, // unimplemented // HOOK_COMBATTURN = 27, // unimplemented
HOOK_CARTRAVEL = 28, HOOK_CARTRAVEL = 28,
HOOK_SETGLOBALVAR = 29, HOOK_SETGLOBALVAR = 29,
HOOK_RESTTIMER = 30, HOOK_RESTTIMER = 30,
@@ -60,6 +60,10 @@ enum HookType
HOOK_USEANIMOBJ = 32, HOOK_USEANIMOBJ = 32,
HOOK_EXPLOSIVETIMER = 33, HOOK_EXPLOSIVETIMER = 33,
HOOK_DESCRIPTIONOBJ = 34, HOOK_DESCRIPTIONOBJ = 34,
HOOK_USESKILLON = 35,
HOOK_ONEXPLOSION = 36,
// HOOK_SUBCOMBATDAMAGE = 37, // unimplemented
HOOK_SETLIGHTING = 38,
HOOK_SNEAK = 39, HOOK_SNEAK = 39,
HOOK_STDPROCEDURE = 40, HOOK_STDPROCEDURE = 40,
HOOK_STDPROCEDURE_END = 41, 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 DWORD targetRet = 0;
static bool targetObjHookHasRet = false; static bool targetObjHookHasRet = false;
@@ -441,6 +490,10 @@ void Inject_AmmoCostHook() {
HookCall(0x423A7C, AmmoCostHook); // compute_attack_ HookCall(0x423A7C, AmmoCostHook); // compute_attack_
} }
void Inject_OnExplosionHook() {
HookCall(0x423D70, ComputeExplosionOnExtrasHook);
}
void Inject_TargetObjectHook() { void Inject_TargetObjectHook() {
MakeCall(0x44BB16, gmouse_bk_process_hook, 1); MakeCall(0x44BB16, gmouse_bk_process_hook, 1);
SafeWrite8(0x44BB00, 0x15); SafeWrite8(0x44BB00, 0x15);
@@ -457,6 +510,7 @@ void InitCombatHookScripts() {
HookScripts::LoadHookScript("hs_findtarget", HOOK_FINDTARGET); HookScripts::LoadHookScript("hs_findtarget", HOOK_FINDTARGET);
HookScripts::LoadHookScript("hs_itemdamage", HOOK_ITEMDAMAGE); HookScripts::LoadHookScript("hs_itemdamage", HOOK_ITEMDAMAGE);
HookScripts::LoadHookScript("hs_ammocost", HOOK_AMMOCOST); HookScripts::LoadHookScript("hs_ammocost", HOOK_AMMOCOST);
HookScripts::LoadHookScript("hs_onexplosion", HOOK_ONEXPLOSION);
HookScripts::LoadHookScript("hs_targetobject", HOOK_TARGETOBJECT); HookScripts::LoadHookScript("hs_targetobject", HOOK_TARGETOBJECT);
} }
+1
View File
@@ -14,6 +14,7 @@ void Inject_CombatDamageHook();
void Inject_FindTargetHook(); void Inject_FindTargetHook();
void Inject_ItemDamageHook(); void Inject_ItemDamageHook();
void Inject_AmmoCostHook(); void Inject_AmmoCostHook();
void Inject_OnExplosionHook();
void Inject_TargetObjectHook(); void Inject_TargetObjectHook();
int __fastcall AmmoCostHook_Script(DWORD hookType, fo::GameObject* weapon, DWORD &rounds); 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) { if (hook == currentRunHook) {
switch (hook) { switch (hook) {
case HOOK_SETGLOBALVAR: case HOOK_SETGLOBALVAR:
// case HOOK_SETLIGHTING: case HOOK_SETLIGHTING:
return true; return true;
default: default:
if (isDebug) fo::func::debug_printf("\nWARNING: A recursive hook with ID %d was running.", hook); 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() { static void __declspec(naked) UseSkillHook() {
__asm { __asm {
HookBegin; HookBegin;
@@ -610,6 +690,21 @@ void Inject_ExplosiveTimerHook() {
HookCall(0x49BDC4, 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() { void Inject_EncounterHook() {
HookCall(0x4C02AF, wmWorldMap_hook); HookCall(0x4C02AF, wmWorldMap_hook);
HookCall(0x4C095C, wmRndEncounterOccurred_hook); HookCall(0x4C095C, wmRndEncounterOccurred_hook);
@@ -617,6 +712,7 @@ void Inject_EncounterHook() {
void InitMiscHookScripts() { void InitMiscHookScripts() {
HookScripts::LoadHookScript("hs_barterprice", HOOK_BARTERPRICE); HookScripts::LoadHookScript("hs_barterprice", HOOK_BARTERPRICE);
HookScripts::LoadHookScript("hs_useskillon", HOOK_USESKILLON);
HookScripts::LoadHookScript("hs_useskill", HOOK_USESKILL); HookScripts::LoadHookScript("hs_useskill", HOOK_USESKILL);
HookScripts::LoadHookScript("hs_steal", HOOK_STEAL); HookScripts::LoadHookScript("hs_steal", HOOK_STEAL);
HookScripts::LoadHookScript("hs_sneak", HOOK_SNEAK); HookScripts::LoadHookScript("hs_sneak", HOOK_SNEAK);
+2
View File
@@ -4,8 +4,10 @@ namespace sfall
{ {
void InitMiscHookScripts(); void InitMiscHookScripts();
void SourceUseSkillOnInit();
void Inject_BarterPriceHook(); void Inject_BarterPriceHook();
void Inject_UseSkillOnHook();
void Inject_UseSkillHook(); void Inject_UseSkillHook();
void Inject_StealCheckHook(); void Inject_StealCheckHook();
void Inject_SneakCheckHook(); 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) { static DWORD __fastcall StdProcedureHook_Script(long numHandler, fo::ScriptInstance* script, DWORD procTable) {
BeginHook(); BeginHook();
argCount = 4; argCount = 4;
@@ -282,6 +348,11 @@ void Inject_DescriptionObjHook() {
HookCall(0x49AE28, DescriptionObjHook); HookCall(0x49AE28, DescriptionObjHook);
} }
void Inject_SetLightingHook() {
HookCall(0x48ACA0, SetObjectLightHook);
MakeCall(0x47A934, SetMapLightHook, 1);
}
void Inject_ScriptProcedureHook() { void Inject_ScriptProcedureHook() {
MakeCall(0x4A491F, ScriptStdProcedureHook); MakeCall(0x4A491F, ScriptStdProcedureHook);
} }
@@ -293,7 +364,9 @@ void Inject_ScriptProcedureHook2() {
void InitObjectHookScripts() { void InitObjectHookScripts() {
HookScripts::LoadHookScript("hs_useobjon", HOOK_USEOBJON); HookScripts::LoadHookScript("hs_useobjon", HOOK_USEOBJON);
HookScripts::LoadHookScript("hs_useobj", HOOK_USEOBJ); HookScripts::LoadHookScript("hs_useobj", HOOK_USEOBJ);
HookScripts::LoadHookScript("hs_useanimobj", HOOK_USEANIMOBJ);
HookScripts::LoadHookScript("hs_descriptionobj", HOOK_DESCRIPTIONOBJ); 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); // combo hook
HookScripts::LoadHookScript("hs_stdprocedure", HOOK_STDPROCEDURE_END); HookScripts::LoadHookScript("hs_stdprocedure", HOOK_STDPROCEDURE_END);
} }
+1
View File
@@ -9,6 +9,7 @@ void Inject_UseObjOnHook();
void Inject_UseObjHook(); void Inject_UseObjHook();
void Inject_UseAnimateObjHook(); void Inject_UseAnimateObjHook();
void Inject_DescriptionObjHook(); void Inject_DescriptionObjHook();
void Inject_SetLightingHook();
void Inject_ScriptProcedureHook(); void Inject_ScriptProcedureHook();
void Inject_ScriptProcedureHook2(); void Inject_ScriptProcedureHook2();
+2
View File
@@ -36,6 +36,7 @@
#include "Graphics.h" #include "Graphics.h"
#include "HeroAppearance.h" #include "HeroAppearance.h"
#include "HookScripts\Common.h" #include "HookScripts\Common.h"
#include "HookScripts\MiscHs.h"
#include "Interface.h" #include "Interface.h"
#include "Inventory.h" #include "Inventory.h"
#include "LoadOrder.h" #include "LoadOrder.h"
@@ -141,6 +142,7 @@ static void __stdcall RunOnBeforeGameStart() {
static void __stdcall RunOnAfterGameStarted() { static void __stdcall RunOnAfterGameStarted() {
if (femaleMsgs) CheckPlayerGender(); if (femaleMsgs) CheckPlayerGender();
if (disableHorrigan) *fo::ptr::Meet_Frank_Horrigan = true; if (disableHorrigan) *fo::ptr::Meet_Frank_Horrigan = true;
SourceUseSkillOnInit();
InitGlobalScripts(); // running sfall scripts InitGlobalScripts(); // running sfall scripts
} }
+37 -2
View File
@@ -261,8 +261,8 @@ static void __fastcall SwapHandSlots(fo::GameObject* item, fo::GameObject* &toSl
} else { // swap slots } else { // swap slots
fo::ItemButtonItem hands[2]; fo::ItemButtonItem hands[2];
std::memcpy(hands, fo::ptr::itemButtonItems, sizeof(fo::ItemButtonItem) * 2); std::memcpy(hands, fo::ptr::itemButtonItems, sizeof(fo::ItemButtonItem) * 2);
hands[fo::HandSlot::Left].primaryAttack = fo::AttackType::ATKTYPE_RWEAPON_PRIMARY; hands[fo::HandSlot::Left].primaryAttack = fo::AttackType::ATKTYPE_RWEAPON_PRIMARY;
hands[fo::HandSlot::Left].secondaryAttack = fo::AttackType::ATKTYPE_RWEAPON_SECONDARY; hands[fo::HandSlot::Left].secondaryAttack = fo::AttackType::ATKTYPE_RWEAPON_SECONDARY;
hands[fo::HandSlot::Right].primaryAttack = fo::AttackType::ATKTYPE_LWEAPON_PRIMARY; hands[fo::HandSlot::Right].primaryAttack = fo::AttackType::ATKTYPE_LWEAPON_PRIMARY;
hands[fo::HandSlot::Right].secondaryAttack = fo::AttackType::ATKTYPE_LWEAPON_SECONDARY; hands[fo::HandSlot::Right].secondaryAttack = fo::AttackType::ATKTYPE_LWEAPON_SECONDARY;
@@ -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() { static void __declspec(naked) endgame_movie_hook() {
__asm { __asm {
cmp [esp + 16], 0x45C563; // call from op_endgame_movie_ 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) #pragma pack(push, 1)
struct CodeData { struct CodeData {
DWORD dd; DWORD dd;
@@ -1085,6 +1118,8 @@ void MiscPatches::init() {
DisplaySecondWeaponRangePatch(); DisplaySecondWeaponRangePatch();
KeepSelectModePatch(); KeepSelectModePatch();
PartyMemberSkillPatch();
SkipLoadingGameSettingsPatch(); SkipLoadingGameSettingsPatch();
UseWalkDistancePatch(); UseWalkDistancePatch();