diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 570e81c7..1a76e084 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -75,7 +75,8 @@ Use32BitHeadGraphics=0 ;Requires graphics mode 4 or 5 AllowDShowMovies=0 -;Fade effect time length percentage modifier +;Fade effect time percentage modifier +;Default is 100. Decrease/increase this value to speed up/slow down fade effects FadeMultiplier=100 ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX @@ -631,6 +632,13 @@ DisplaySwiftLearnerExp=1 ;Set to 1 to display party member's current level/AC/addict flag on the combat control panel PartyMemberExtraInfo=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 apply his/her skills when you use First Aid/Doctor skills 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 + ;Set to 1 to skip loading game settings from a saved game, except for the game/combat difficulty settings ;Set to 2 to also skip loading the game/combat difficulty settings SkipLoadingGameSettings=0 diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index 7a94942a..45c169af 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -55,6 +55,8 @@ #define HOOK_GAMEMODECHANGE (31) #define HOOK_USEANIMOBJ (32) #define HOOK_EXPLOSIVETIMER (33) +#define HOOK_DESCRIPTIONOBJ (34) +#define HOOK_USESKILLON (35) //Valid arguments to list_begin #define LIST_CRITTERS (0) diff --git a/artifacts/scripting/hookscripts.txt b/artifacts/scripting/hookscripts.txt index 67329cf0..fd4f781c 100644 --- a/artifacts/scripting/hookscripts.txt +++ b/artifacts/scripting/hookscripts.txt @@ -544,3 +544,17 @@ An example usage would be to add an additional description to the item based on Obj arg1 - the item int ret1 - a pointer to the new text received by using "get_string_pointer" function + +------------------------------------------- + +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 arg1 - The user critter (usually dude_obj) +Obj arg2 - the target object/critter +int arg3 - skill being used + +int ret1 - a new critter to override the user critter. Pass -1 to cancel the skill use, pass 0 to skip this return value +int ret2 - pass 1 to allow the skill being used in combat (only for dude_obj or critter being controlled by the player) diff --git a/sfall/Modules/HookScripts.cpp b/sfall/Modules/HookScripts.cpp index b25b1d37..70259f78 100644 --- a/sfall/Modules/HookScripts.cpp +++ b/sfall/Modules/HookScripts.cpp @@ -79,6 +79,7 @@ static HooksInjectInfo injectHooks[] = { {HOOK_USEANIMOBJ, Inject_UseAnimateObjHook, false}, {HOOK_EXPLOSIVETIMER, Inject_ExplosiveTimerHook, false}, {HOOK_DESCRIPTIONOBJ, Inject_DescriptionObjHook, false}, + {HOOK_USESKILLON, Inject_UseSkillOnHook, false}, }; bool HookScripts::injectAllHooks; @@ -227,6 +228,7 @@ void HookScripts::init() { OnKeyPressed() += KeyPressHook; OnMouseClick() += MouseClickHook; LoadGameHook::OnGameModeChange() += GameModeChangeHook; + LoadGameHook::OnAfterGameStarted() += SourceUseSkillOnInit; HookScripts::injectAllHooks = (isDebug && (GetConfigInt("Debugging", "InjectAllGameHooks", 0) != 0)); } diff --git a/sfall/Modules/HookScripts.h b/sfall/Modules/HookScripts.h index 7421dd6f..dd841a9e 100644 --- a/sfall/Modules/HookScripts.h +++ b/sfall/Modules/HookScripts.h @@ -61,6 +61,7 @@ enum HookType HOOK_USEANIMOBJ = 32, HOOK_EXPLOSIVETIMER = 33, HOOK_DESCRIPTIONOBJ = 34, + HOOK_USESKILLON = 35, HOOK_COUNT }; diff --git a/sfall/Modules/HookScripts/MiscHs.cpp b/sfall/Modules/HookScripts/MiscHs.cpp index eba793fc..f804bf38 100644 --- a/sfall/Modules/HookScripts/MiscHs.cpp +++ b/sfall/Modules/HookScripts/MiscHs.cpp @@ -392,6 +392,85 @@ end: } } +static fo::GameObject* sourceSkillOn = nullptr; +void SourceUseSkillOnInit() { sourceSkillOn = fo::var::obj_dude; } + +static char resultSkillOn; +static long bakupCombatState; +static void __cdecl 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::var::obj_dude; + resultSkillOn = 0; + bakupCombatState = -1; + + RunHookScript(HOOK_USESKILLON); + EndHook(); + + 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::var::combat_state; + fo::var::combat_state = 0; + } + } +} + +static void __declspec(naked) UseSkillOnHook() { + __asm { + push ecx; + push ebx; // skill id + push edx; // target + push eax; // user + call UseSkillOnHook_Script; + pop eax; + pop edx; + add esp, 4; // ebx + pop ecx; + 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; + 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; + } +} + void Inject_BarterPriceHook() { HookCalls(BarterPriceHook, { 0x474D4C, @@ -440,6 +519,16 @@ void Inject_ExplosiveTimerHook() { HookCall(0x49BDC4, ExplosiveTimerHook); } +void Inject_UseSkillOnHook() { + HookCalls(UseSkillOnHook, { 0x44C3CA, 0x44C81C }); + MakeCall(0x4127BA, UseSkillOnHack); + SafeWrite8(0x4127BF, 0x90); + MakeCalls(skill_use_hack, {0x4AB05D, 0x4AB558, 0x4ABA60}); // fix checking obj_dude's target + // replace source skill user + SafeWriteBatch((DWORD)&sourceSkillOn, {0x4AAF47, 0x4AB051, 0x4AB3FB, 0x4AB550, 0x4AB8FA, 0x4ABA54}); + SafeWriteBatch((DWORD)&sourceSkillOn, {0x4AB0EF, 0x4AB5C0, 0x4ABAF2}); // fix for time +} + void InitMiscHookScripts() { LoadHookScript("hs_barterprice", HOOK_BARTERPRICE); @@ -450,6 +539,7 @@ void InitMiscHookScripts() { LoadHookScript("hs_setglobalvar", HOOK_SETGLOBALVAR); LoadHookScript("hs_resttimer", HOOK_RESTTIMER); LoadHookScript("hs_explosivetimer", HOOK_EXPLOSIVETIMER); + LoadHookScript("hs_useskillon", HOOK_USESKILLON); } } diff --git a/sfall/Modules/HookScripts/MiscHs.h b/sfall/Modules/HookScripts/MiscHs.h index cbeead4b..1da9f753 100644 --- a/sfall/Modules/HookScripts/MiscHs.h +++ b/sfall/Modules/HookScripts/MiscHs.h @@ -3,6 +3,7 @@ namespace sfall { void InitMiscHookScripts(); + void SourceUseSkillOnInit(); void Inject_BarterPriceHook(); void Inject_UseSkillHook(); @@ -12,4 +13,5 @@ namespace sfall void Inject_SetGlobalVarHook(); void Inject_RestTimerHook(); void Inject_ExplosiveTimerHook(); + void Inject_UseSkillOnHook(); } diff --git a/sfall/Modules/MiscPatches.cpp b/sfall/Modules/MiscPatches.cpp index 971cfa9e..de6f52d4 100644 --- a/sfall/Modules/MiscPatches.cpp +++ b/sfall/Modules/MiscPatches.cpp @@ -448,6 +448,24 @@ 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 const DWORD EncounterTableSize[] = { 0x4BD1A3, 0x4BD1D9, 0x4BD270, 0x4BD604, 0x4BDA14, 0x4BDA44, 0x4BE707, 0x4C0815, 0x4C0D4A, 0x4C0FD4, @@ -855,6 +873,21 @@ void KeepWeaponSelectModePatch() { } } +void PartyMemberSkillPatch() { + // Fixed getting distance from source to target when using skills + // Note: this will cause the party member to apply his/her skill when you use First Aid/Doctor skill 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 (GetConfigInt("Misc", "PartyMemberSkillFix", 0) != 0) { + dlog("Applying party member using First Aid/Doctor skill patch.", 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 +} + void SkipLoadingGameSettingsPatch() { int skipLoading = GetConfigInt("Misc", "SkipLoadingGameSettings", 0); if (skipLoading) { @@ -969,6 +1002,8 @@ void MiscPatches::init() { DisplaySecondWeaponRangePatch(); KeepWeaponSelectModePatch(); + PartyMemberSkillPatch(); + SkipLoadingGameSettingsPatch(); InterfaceDontMoveOnTopPatch(); } diff --git a/sfall/main.cpp b/sfall/main.cpp index eaeb9d9e..5f6de84b 100644 --- a/sfall/main.cpp +++ b/sfall/main.cpp @@ -279,7 +279,7 @@ static bool LoadOriginalDll(DWORD dwReason) { switch (dwReason) { case DLL_PROCESS_ATTACH: char path[MAX_PATH]; - CopyMemory(path + GetSystemDirectory(path , MAX_PATH - 10), "\\ddraw.dll", 11); // path to original dll + CopyMemory(path + GetSystemDirectoryA(path , MAX_PATH - 10), "\\ddraw.dll", 11); // path to original dll ddraw.dll = LoadLibrary(path); if (!ddraw.dll || ddraw.dll == INVALID_HANDLE_VALUE) { MessageBox(0, "Cannot load the original ddraw.dll library.", "sfall", MB_ICONERROR);