From 67b46cd4a615f259bf6d3b0f47fdf2bfc0f4e975 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Thu, 1 Dec 2022 08:39:42 +0800 Subject: [PATCH] Backported three more hooks from 4.1: * USESKILLON, ONEXPLOSION, SETLIGHTING. Backported PartyMemberSkillFix option from 4.1. Updated documents. --- artifacts/ddraw.ini | 7 ++ artifacts/scripting/headers/sfall.h | 4 + artifacts/scripting/hookscripts.md | 51 ++++++++++++ sfall/FalloutEngine/FunctionOffsets_def.h | 1 + sfall/Modules/HookScripts.cpp | 6 +- sfall/Modules/HookScripts.h | 6 +- sfall/Modules/HookScripts/CombatHs.cpp | 54 +++++++++++++ sfall/Modules/HookScripts/CombatHs.h | 1 + sfall/Modules/HookScripts/Common.cpp | 2 +- sfall/Modules/HookScripts/MiscHs.cpp | 96 +++++++++++++++++++++++ sfall/Modules/HookScripts/MiscHs.h | 2 + sfall/Modules/HookScripts/ObjectHs.cpp | 73 +++++++++++++++++ sfall/Modules/HookScripts/ObjectHs.h | 1 + sfall/Modules/LoadGameHook.cpp | 2 + sfall/Modules/MiscPatches.cpp | 39 ++++++++- 15 files changed, 338 insertions(+), 7 deletions(-) diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index e7e92424..b30feeec 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -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 diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index 62b70ef8..e6633df4 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -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) diff --git a/artifacts/scripting/hookscripts.md b/artifacts/scripting/hookscripts.md index 66f09be1..f9bb70e2 100644 --- a/artifacts/scripting/hookscripts.md +++ b/artifacts/scripting/hookscripts.md @@ -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.\ diff --git a/sfall/FalloutEngine/FunctionOffsets_def.h b/sfall/FalloutEngine/FunctionOffsets_def.h index acedcffe..ad963f2c 100644 --- a/sfall/FalloutEngine/FunctionOffsets_def.h +++ b/sfall/FalloutEngine/FunctionOffsets_def.h @@ -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) diff --git a/sfall/Modules/HookScripts.cpp b/sfall/Modules/HookScripts.cpp index bb616c67..f7673709 100644 --- a/sfall/Modules/HookScripts.cpp +++ b/sfall/Modules/HookScripts.cpp @@ -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}, diff --git a/sfall/Modules/HookScripts.h b/sfall/Modules/HookScripts.h index ab59a0a4..fcbd013d 100644 --- a/sfall/Modules/HookScripts.h +++ b/sfall/Modules/HookScripts.h @@ -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, diff --git a/sfall/Modules/HookScripts/CombatHs.cpp b/sfall/Modules/HookScripts/CombatHs.cpp index 8b08f37e..6e53e484 100644 --- a/sfall/Modules/HookScripts/CombatHs.cpp +++ b/sfall/Modules/HookScripts/CombatHs.cpp @@ -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); } diff --git a/sfall/Modules/HookScripts/CombatHs.h b/sfall/Modules/HookScripts/CombatHs.h index f93c842b..f88a0a50 100644 --- a/sfall/Modules/HookScripts/CombatHs.h +++ b/sfall/Modules/HookScripts/CombatHs.h @@ -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); diff --git a/sfall/Modules/HookScripts/Common.cpp b/sfall/Modules/HookScripts/Common.cpp index 2c3fba4c..c0b6697a 100644 --- a/sfall/Modules/HookScripts/Common.cpp +++ b/sfall/Modules/HookScripts/Common.cpp @@ -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); diff --git a/sfall/Modules/HookScripts/MiscHs.cpp b/sfall/Modules/HookScripts/MiscHs.cpp index 86c9cc09..4cd478c6 100644 --- a/sfall/Modules/HookScripts/MiscHs.cpp +++ b/sfall/Modules/HookScripts/MiscHs.cpp @@ -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)&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); diff --git a/sfall/Modules/HookScripts/MiscHs.h b/sfall/Modules/HookScripts/MiscHs.h index f535ba34..e7fbbcd3 100644 --- a/sfall/Modules/HookScripts/MiscHs.h +++ b/sfall/Modules/HookScripts/MiscHs.h @@ -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(); diff --git a/sfall/Modules/HookScripts/ObjectHs.cpp b/sfall/Modules/HookScripts/ObjectHs.cpp index 449c57fd..b2b99d8c 100644 --- a/sfall/Modules/HookScripts/ObjectHs.cpp +++ b/sfall/Modules/HookScripts/ObjectHs.cpp @@ -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); } diff --git a/sfall/Modules/HookScripts/ObjectHs.h b/sfall/Modules/HookScripts/ObjectHs.h index 904c4403..cfed0aab 100644 --- a/sfall/Modules/HookScripts/ObjectHs.h +++ b/sfall/Modules/HookScripts/ObjectHs.h @@ -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(); diff --git a/sfall/Modules/LoadGameHook.cpp b/sfall/Modules/LoadGameHook.cpp index aaf6d73a..81549049 100644 --- a/sfall/Modules/LoadGameHook.cpp +++ b/sfall/Modules/LoadGameHook.cpp @@ -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 } diff --git a/sfall/Modules/MiscPatches.cpp b/sfall/Modules/MiscPatches.cpp index 0996d782..d000f593 100644 --- a/sfall/Modules/MiscPatches.cpp +++ b/sfall/Modules/MiscPatches.cpp @@ -261,8 +261,8 @@ static void __fastcall SwapHandSlots(fo::GameObject* item, fo::GameObject* &toSl } else { // swap slots fo::ItemButtonItem hands[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].secondaryAttack = fo::AttackType::ATKTYPE_RWEAPON_SECONDARY; + hands[fo::HandSlot::Left].primaryAttack = fo::AttackType::ATKTYPE_RWEAPON_PRIMARY; + hands[fo::HandSlot::Left].secondaryAttack = fo::AttackType::ATKTYPE_RWEAPON_SECONDARY; hands[fo::HandSlot::Right].primaryAttack = fo::AttackType::ATKTYPE_LWEAPON_PRIMARY; 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() { __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();