From 5353ab0ad60b67ea74bc1372c0e3a62c1f5ad7e7 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Tue, 14 May 2019 11:10:28 +0800 Subject: [PATCH] Backported the code of four hookscripts from 4.x. * FINDTARGET, BARTERPRICE, WITHINPERCEPTION, and INVENTORYMOVE. For unifying the behavior of common hooks between 3.8 and 4.x. Added the example script of WITHINPERCEPTION from 4.x. Updated gl_highlighting_lite. --- .../gl_highlighting_lite.int | Bin 3502 -> 3648 bytes .../gl_highlighting_lite.ssl | 6 + .../ItemHighlight_Lite/readme.txt | 2 +- .../WithinPerception/gl_withinperception.ssl | 60 ++ artifacts/scripting/hookscripts.txt | 15 +- sfall/FalloutEngine.cpp | 41 +- sfall/FalloutEngine.h | 10 + sfall/HookScripts.cpp | 740 +++++++++++------- 8 files changed, 602 insertions(+), 272 deletions(-) create mode 100644 artifacts/example_mods/WithinPerception/gl_withinperception.ssl diff --git a/artifacts/example_mods/ItemHighlight_Lite/gl_highlighting_lite.int b/artifacts/example_mods/ItemHighlight_Lite/gl_highlighting_lite.int index e33b567288e67d858996de4c139c6cc2f2c47e24..36899a4c118510f7408cb24aa89dfc44b8471287 100644 GIT binary patch delta 983 zcmZ1{eLzN_f$0Dv0|SFl122eXahoV0ThGP7z`(|U0$8R%xmr*fN`b|h85kJMpaM{e zRfvIs0VM7Q6@XH#8yFZEm>3usf}jFWip>hj_`twW4`qTWwibv6Mh-9+POz_HU|?Wm zU|^Jj3xOG&AHWm?qYi|Ik=&0UEJhay4I_D=4rg>@5YU5w&BBawjACM*d1a}2CHX~_ zzWHUT9*KD=IjKbqoAsFtnV5LYCMU5tGya&ojHOm`8Ytixm>Y}^F#iAl|9^vVgEUBl zwPmsmYl#S(0*J%F)L_)$3Xx#rn|y@Tmoa6s5Syp-B(N+)gAr8jO@l;(euETPlo3hK zu?C3-SGWvogAm-bHK+wlS7<~gR6lBB*b}cau877+I^a5e}v z>;S3fj)RDB$AIMvAUp;RxcoGT2=^3_y!a^ykAVTCmw~B47({cQnB2r|ro$r#;xI75 zLY9XM60(g?z~bx;#tr)5u)5IrWO4+%h7F%aV+te^cp@N5n*_j0SsKhCLCI49$!t*j zSQ=O%5zaGf@)vG>##573c=Q>6O%CDFW8&4C+ybJaCU4=V@S1Zgvrbx|f$0Dv0|SFl122eX5t}F=ThGbBz`(|U0$5U@TrDUKrNH9M3=9lrPyr~# z@`!=rNk6n{7X>tYoM8=lMTpYgADNuDr6F{+huc@rTq(L7f#*s9+hf{s> zOb&4__FD}sAR*3qlXr30GtQg*i^EsC4kW?=a`JB|3u+Z-$mAqWO~#tZJ)HK8rzW4_ z440k-Rbd3BghNF9TD9 zFo@=!F?kcWnF{Z;#uP}Ha$kXjX_Ej*nt`Rk91=3zZzd=3h%<^!uHeyUbep__N1w55 z@(mt6CZ1)JIe7J$c&<&h;MHejnVi9^%BVBBk5`{DZ1O%{4JO`($uD@#CoA&^0{}S6 Bs`vl^ diff --git a/artifacts/example_mods/ItemHighlight_Lite/gl_highlighting_lite.ssl b/artifacts/example_mods/ItemHighlight_Lite/gl_highlighting_lite.ssl index 457fc9d9..8ec71524 100644 --- a/artifacts/example_mods/ItemHighlight_Lite/gl_highlighting_lite.ssl +++ b/artifacts/example_mods/ItemHighlight_Lite/gl_highlighting_lite.ssl @@ -100,6 +100,11 @@ procedure KeyPressHandler begin end end +procedure InventoryMoveHandler begin + // remove item outline when player picks up the item + if (isHighlight and get_sfall_arg == 7) then set_outline(get_sfall_arg, 0); +end + procedure start begin if game_loaded and (sfall_ver_major < 4) then begin call InitConfigs; @@ -116,5 +121,6 @@ procedure start begin highlightFailMsg2 := Translate("HighlightFail2", "Your motion sensor is out of charge."); register_hook_proc(HOOK_KEYPRESS, KeyPressHandler); + register_hook_proc(HOOK_INVENTORYMOVE, InventoryMoveHandler); end end diff --git a/artifacts/example_mods/ItemHighlight_Lite/readme.txt b/artifacts/example_mods/ItemHighlight_Lite/readme.txt index 1238a37a..02bd8f58 100644 --- a/artifacts/example_mods/ItemHighlight_Lite/readme.txt +++ b/artifacts/example_mods/ItemHighlight_Lite/readme.txt @@ -17,5 +17,5 @@ Also, you should disable the built-in function in ddraw.ini (ToggleItemHighlight Note that due to the lack of newer game hooks in sfall 3.8.x, there are some minor visual glitches with the lite version: - items will be kept highlighted when entering combat while holding the highlight key. -- when you pick up items while holding the highlight key, they will be kept highlighted if you drop them on the ground. +- when you pick up items while holding the highlight key, they will be kept highlighted if you drop them on the ground (fixed for 3.8.18+). Both glitches can bo solved by pressing and releasing the highlight key again. diff --git a/artifacts/example_mods/WithinPerception/gl_withinperception.ssl b/artifacts/example_mods/WithinPerception/gl_withinperception.ssl new file mode 100644 index 00000000..36690172 --- /dev/null +++ b/artifacts/example_mods/WithinPerception/gl_withinperception.ssl @@ -0,0 +1,60 @@ +/* + Example implementation of the algorithm of how the game engine checks if one critter sees another critter. + + NOTE: the AllowUnsafeScripting option must be enabled. +*/ + +#include "..\headers\define.h" +#include "..\headers\command.h" +#include "..\headers\sfall\sfall.h" +#include "..\headers\sfall\define_extra.h" + +#define can_see_(source, target) call_offset_r2(0x412BEC, source, target) +#define obj_dist_(target, source) call_offset_r2(0x48BBD4, target, source) + +procedure start; + +procedure start begin + if game_loaded then begin + register_hook(HOOK_WITHINPERCEPTION); + end else begin + variable + source := get_sfall_arg, + target := get_sfall_arg, + original := get_sfall_arg, + hookType := get_sfall_arg, /* new arg */ + result := 0, + distance; + + if target then begin + distance := get_critter_stat(source, STAT_pe); + + if can_see_(source, target) then begin + distance *= 5; + if (get_flags(target) bwand FLAG_TRANSGLASS) then distance /= 2; + end else if combat_is_initialized then begin + distance *= 2; + end + + if (target == dude_obj) then begin + if sneak_success then begin + distance /= 4; + if has_skill(target, SKILL_SNEAK) > 120 then distance -= 1; + end else if dude_is_sneaking then begin + distance := distance * 2 / 3; + end + end + + if obj_dist_(target, source) <= distance then result := 1; + + // example + if (result) then begin + display_msg("hs_withinperception: " + obj_name(source) + " sees " + obj_name(target) + " [original: " + original + " script: " + result + "]"); + end else begin + display_msg("hs_withinperception: " + obj_name(source) + " does not see " + obj_name(target) + " [original: " + original + " script: " + result + "]"); + end + end + + //set_sfall_return(result); + end +end diff --git a/artifacts/scripting/hookscripts.txt b/artifacts/scripting/hookscripts.txt index 1b0a086c..f11eeb6c 100644 --- a/artifacts/scripting/hookscripts.txt +++ b/artifacts/scripting/hookscripts.txt @@ -189,7 +189,7 @@ Critter arg1 - The critter that just died HOOK_FINDTARGET (hs_findtarget.int) -Runs when the ai is trying to pick a target in combat. Fallout first chooses a list of 4 likely suspects, then normally sorts them in order of weakness/distance/etc depending on the ai caps of the attacker. This hook replaces that sorting function, allowing you to sort the targets in some arbitrary way. Use sfall_return to give the 4 targets, in order of preference. All 4 must be given if you want to override normal sorting; if you want to specify less than 4 targets, fill in the extra spaces with 0's. If you do not give 4 targets, the NPCs normal sorting mechanism will be used. +Runs when the ai is trying to pick a target in combat. Fallout first chooses a list of 4 likely suspects, then normally sorts them in order of weakness/distance/etc depending on the ai caps of the attacker. This hook replaces that sorting function, allowing you to sort the targets in some arbitrary way. Use sfall_return to give the 4 targets, in order of preference. If you want to specify less than 4 targets, fill in the extra spaces with 0's. Pass -1 to skip the return value. The return values can include critters that weren't in the list of possible targets, but the additional targets may still be discarded later on in the combat turn if they are out of the attackers perception or the chance of a successful hit is too low. The list of possible targets often includes duplicated entries. @@ -266,7 +266,8 @@ critter arg7 - table of offered goods (being sold to NPC) int arg8 - the total cost of the goods offered by the player int arg9 - set 1 if the "offers" button was pressed (not for a party member), otherwise 0 -int ret1 - the modified value of all of the goods +int ret1 - the modified value of all of the goods (pass -1 if you just want to modify offered goods) +int ret2 - the modified value of all offered goods ------------------------------------------- @@ -404,6 +405,7 @@ This is fired after the default calculation is made. Critter arg1 - Watcher object Obj arg2 - Target object int arg3 - Result of vanilla function: 1 - within perception range, 0 - otherwise +int arg4 - Type of hook: 1 - when being called from obj_can_see_obj script function, 2 when being called from obj_can_hear_obj script function (need to set ObjCanHearObjFix=1 in ddraw.ini), 0 for all other cases int ret1 - overrides the returned result of the function: 0 - not in range (can't see), 1 - in range (will see if not blocked), 2 - forced detection (will see regardless, only used in obj_can_see_obj scripting function which is called by every critter in the game) @@ -414,17 +416,16 @@ HOOK_INVENTORYMOVE (hs_inventorymove.int) Runs before moving items between inventory slots in dude interface. You can override the action. What you can NOT do with this hook: - force moving items to inappropriate slots (like gun in armor slot) -- block picking up items What you can do: - restrict player from using specific weapons or armors - add AP costs for all inventory movement including reloading - apply or remove some special scripted effects depending on PC's armor -int arg1 - Target slot (0 - main backpack, 1 - left hand, 2 - right hand, 3 - armor slot, 4 - weapon, when reloading it by dropping ammo) +int arg1 - Target slot (0 - main backpack, 1 - left hand, 2 - right hand, 3 - armor slot, 4 - weapon, when reloading it by dropping ammo, 5 - container, like bag/backpack, 6 - dropping on the ground, 7 - picking up item, 8 - dropping item on the character portrait) Item arg2 - Item being moved -Item arg3 - Item being replaced or weapon being reloaded (can be 0) +Item arg3 - Item being replaced, weapon being reloaded, or container being filled (can be 0) -int ret1 - Override setting (-1 - use engine handler, any other value - prevent relocation of item/reloading weapon) +int ret1 - Override setting (-1 - use engine handler, any other value - prevent relocation of item/reloading weapon/picking up item) ------------------------------------------- @@ -436,7 +437,7 @@ NOTE: when replacing a previously wielded armor or weapon, the unwielding hook w If you need to rely on this, try checking if armor/weapon is already equipped when wielding hook is executed. Critter arg1 - critter -Obj arg2 - item being wielded or unwielded (weapon/armor), may be 0 when unwielding (object is valid only if the item is unwielded by move_obj_inven_to_obj() or rm_obj_from_inven()) +Obj arg2 - item being wielded or unwielded (weapon/armor) int arg3 - slot (INVEN_TYPE_*) int arg4 - 1 when wielding, 0 when unwielding diff --git a/sfall/FalloutEngine.cpp b/sfall/FalloutEngine.cpp index f4a230da..2d583e9c 100644 --- a/sfall/FalloutEngine.cpp +++ b/sfall/FalloutEngine.cpp @@ -334,6 +334,7 @@ const DWORD DOSCmdLineDestroy_ = 0x4E3D3C; const DWORD DrawCard_ = 0x43AAEC; const DWORD DrawFolder_ = 0x43410C; const DWORD DrawInfoWin_ = 0x4365AC; +const DWORD drop_into_container_ = 0x476464; const DWORD dude_stand_ = 0x418378; const DWORD editor_design_ = 0x431DF8; const DWORD elapsed_time_ = 0x4C93E0; @@ -503,6 +504,7 @@ const DWORD obj_connect_ = 0x489EC4; const DWORD obj_destroy_ = 0x49B9A0; const DWORD obj_dist_ = 0x48BBD4; const DWORD obj_dist_with_tile_ = 0x48BC08; +const DWORD obj_drop_ = 0x49B8B0; const DWORD obj_erase_object_ = 0x48B0FC; const DWORD obj_find_first_at_ = 0x48B48C; const DWORD obj_find_first_at_tile_ = 0x48B5A8; @@ -881,6 +883,22 @@ TGameObj* __stdcall InvenWorn(TGameObj* critter) { } } +__declspec(noinline) TGameObj* __stdcall GetItemPtrSlot(TGameObj* critter, long slot) { + TGameObj* itemPtr = nullptr; + switch (slot) { + case INVEN_TYPE_LEFT_HAND: + itemPtr = InvenLeftHand(critter); + break; + case INVEN_TYPE_RIGHT_HAND: + itemPtr = InvenRightHand(critter); + break; + case INVEN_TYPE_WORN: + itemPtr = InvenWorn(critter); + break; + } + return itemPtr; +} + TGameObj* __stdcall InvenLeftHand(TGameObj* critter) { __asm { mov eax, critter; @@ -1012,7 +1030,6 @@ void __stdcall MapDirErase(const char* folder, const char* ext) { } } -// for the backported AmmoCostHook from 4.x long __stdcall ItemWAnimWeap(TGameObj* item, DWORD hitMode) { __asm { mov edx, hitMode; @@ -1042,3 +1059,25 @@ long __stdcall ItemWRounds(TGameObj* item) { call item_w_rounds_; } } + +long __stdcall BarterComputeValue(TGameObj* source, TGameObj* target) { + __asm { + mov edx, target; + mov eax, source; + call barter_compute_value_; + } +} + +long __stdcall ItemCapsTotal(TGameObj* object) { + __asm { + mov eax, object; + call item_caps_total_; + } +} + +long __stdcall ItemTotalCost(TGameObj* object) { + __asm { + mov eax, object; + call item_total_cost_; + } +} diff --git a/sfall/FalloutEngine.h b/sfall/FalloutEngine.h index 8b37038f..af132f65 100644 --- a/sfall/FalloutEngine.h +++ b/sfall/FalloutEngine.h @@ -70,6 +70,7 @@ #define _dialogue_state 0x518714 #define _dialogue_switch_mode 0x518718 #define _displayMapList 0x41B560 +#define _dropped_explosive 0x5190E0 #define _drugInfoList 0x5191CC #define _edit_win 0x57060C #define _Educated 0x57082C @@ -556,6 +557,7 @@ extern const DWORD DOSCmdLineDestroy_; extern const DWORD DrawCard_; extern const DWORD DrawFolder_; extern const DWORD DrawInfoWin_; +extern const DWORD drop_into_container_; extern const DWORD dude_stand_; extern const DWORD editor_design_; extern const DWORD elapsed_time_; @@ -730,6 +732,7 @@ extern const DWORD obj_connect_; extern const DWORD obj_destroy_; extern const DWORD obj_dist_; extern const DWORD obj_dist_with_tile_; +extern const DWORD obj_drop_; extern const DWORD obj_erase_object_; extern const DWORD obj_find_first_at_; extern const DWORD obj_find_first_at_tile_; // (int elevation, int tile) @@ -977,6 +980,8 @@ TGameObj* __stdcall InvenLeftHand(TGameObj* critter); // item in critter's right hand slot TGameObj* __stdcall InvenRightHand(TGameObj* critter); +__declspec(noinline) TGameObj* __stdcall GetItemPtrSlot(TGameObj* critter, long slot); + // pops value type from Data stack (must be followed by InterpretPopLong) DWORD __stdcall InterpretPopShort(TProgram* scriptPtr); @@ -1034,3 +1039,8 @@ long __stdcall ItemWAnimWeap(TGameObj* item, DWORD hitMode); long __stdcall ItemWComputeAmmoCost(TGameObj* item, DWORD* rounds); long __stdcall ItemWCurrAmmo(TGameObj* item); long __stdcall ItemWRounds(TGameObj* item); + +// for the backported BarterPriceHook from 4.x +long __stdcall BarterComputeValue(TGameObj* source, TGameObj* target); +long __stdcall ItemCapsTotal(TGameObj* object); +long __stdcall ItemTotalCost(TGameObj* object); diff --git a/sfall/HookScripts.cpp b/sfall/HookScripts.cpp index 36f47472..96669735 100644 --- a/sfall/HookScripts.cpp +++ b/sfall/HookScripts.cpp @@ -69,6 +69,8 @@ static DWORD cRetTmp; // how many return values were set by specific hook script #define hookbegin(a) pushadc __asm call BeginHook popadc __asm mov argCount, a #define hookend pushadc __asm call EndHook popadc +#define HookBegin pushadc __asm call BeginHook popadc +//#define HookEnd pushadc __asm call EndHook popadc static void _stdcall BeginHook() { if (callDepth && callDepth <= maxDepth) { @@ -466,38 +468,35 @@ static void __declspec(naked) OnDeathHook2() { } } +// 4.x backport +static void __fastcall FindTargetHook_Script(DWORD* target, DWORD attacker) { + BeginHook(); + argCount = 5; + + args[0] = attacker; + args[1] = target[0]; + args[2] = target[1]; + args[3] = target[2]; + args[4] = target[3]; + + RunHookScript(HOOK_FINDTARGET); + + if (cRet > 0) { + if (rets[0] != -1) target[0] = rets[0]; + if (cRet > 1 && rets[1] != -1) target[1] = rets[1]; + if (cRet > 2 && rets[2] != -1) target[2] = rets[2]; + if (cRet > 3 && rets[3] != -1) target[3] = rets[3]; + } + EndHook(); +} + static void __declspec(naked) FindTargetHook() { __asm { - hookbegin(5); - mov args[0], esi; //attacker - mov edi, [eax+0]; - mov args[4], edi; - mov edi, [eax+4]; - mov args[8], edi; - mov edi, [eax+8]; - mov args[12], edi; - mov edi, [eax+12]; - mov args[16], edi; - pushad; - push HOOK_FINDTARGET; - call RunHookScript; - popad; - cmp cRet, 4; - jge cont; + push eax; call qsort_; - jmp end; -cont: - mov edi, rets[0]; - mov [eax+0], edi; - mov edi, rets[4]; - mov [eax+4], edi; - mov edi, rets[8]; - mov [eax+8], edi; - mov edi, rets[12]; - mov [eax+12], edi; -end: - hookend; - retn; + pop ecx; // targets (base) + mov edx, esi; // attacker + jmp FindTargetHook_Script; } } @@ -597,46 +596,84 @@ static void __declspec(naked) RemoveObjHook() { } } +// 4.x backport +static DWORD __fastcall BarterPriceHook_Script(register TGameObj* source, register TGameObj* target, DWORD callAddr) { + int computeCost = BarterComputeValue(source, target); + + BeginHook(); + argCount = 9; + + args[0] = (DWORD)source; + args[1] = (DWORD)target; + args[2] = computeCost; + + TGameObj* bTable = (TGameObj*)*ptr_btable; + args[3] = (DWORD)bTable; + args[4] = ItemCapsTotal(bTable); + args[5] = ItemTotalCost(bTable); + + TGameObj* pTable = (TGameObj*)*ptr_ptable; + args[6] = (DWORD)pTable; + int pcCost = ItemTotalCost(pTable); + args[7] = pcCost; + + args[8] = (DWORD)(callAddr == 0x474D51); // check offers button + + RunHookScript(HOOK_BARTERPRICE); + + bool isPCHook = (callAddr == 0x47551F); + int cost = isPCHook ? pcCost : computeCost; + if (cRet > 0) { + if (isPCHook) { + if (cRet > 1) cost = rets[1]; // new cost for pc + } else if ((int)rets[0] > -1) { + cost = rets[0]; // new cost for trader + } + } + EndHook(); + + return cost; +} + static void __declspec(naked) BarterPriceHook() { __asm { - hookbegin(9); - mov args[0], eax; - mov args[4], edx; - call barter_compute_value_; - mov edx, ds:[_btable] - mov args[8], eax; - mov args[12], edx; - xchg eax, edx; - call item_caps_total_; - mov args[16], eax; - mov eax, ds:[_btable]; - call item_total_cost_; - mov args[20], eax; - mov eax, ds:[_ptable]; - mov args[24], eax; - call item_total_cost_; - mov args[28], eax; - xor eax, eax; - mov edx, [esp]; // check offers button - cmp edx, 0x474D51; // last address on call stack - jne skip; - inc eax; -skip: - mov args[32], eax; - mov eax, args[8]; - pushad; - push HOOK_BARTERPRICE; - call RunHookScript; - popad; - cmp cRet, 1; - jl end; - mov eax, rets[0]; -end: - hookend; + push edx; + push ecx; + //------- + push [esp + 8]; // address on call stack + mov ecx, eax; // source + call BarterPriceHook_Script; // edx - target + pop ecx; + pop edx; retn; } } +static DWORD offersGoodsCost; // keep last cost +static void __declspec(naked) PC_BarterPriceHook() { + __asm { + push edx; + push ecx; + //------- + push [esp + 8]; // address on call stack + mov ecx, dword ptr ds:[_obj_dude]; // source + mov edx, dword ptr ds:[_target_stack]; // target + call BarterPriceHook_Script; + pop ecx; + pop edx; + mov offersGoodsCost, eax; + retn; + } +} + +static const DWORD OverrideCostRet = 0x474D44; +static void __declspec(naked) OverrideCost_BarterPriceHack() { + __asm { + mov eax, offersGoodsCost; + jmp OverrideCostRet; + } +} + static void __declspec(naked) MoveCostHook() { __asm { hookbegin(3); @@ -903,43 +940,67 @@ end: } } +// 4.x backport +static long __stdcall PerceptionRangeHook_Script(int type) { + long result; + __asm { + HookBegin; + mov args[0], eax; // watcher + mov args[4], edx; // target + call is_within_perception_; + mov result, eax; // check result + } + args[2] = result; + args[3] = type; + + argCount = 4; + RunHookScript(HOOK_WITHINPERCEPTION); + + if (cRet > 0) result = rets[0]; + EndHook(); + + return result; +} + static void __declspec(naked) PerceptionRangeHook() { __asm { - hookbegin(3); - mov args[0], eax; // watcher - mov args[4], edx; // target - call is_within_perception_; - mov args[8], eax; // check result - pushad; - push HOOK_WITHINPERCEPTION; - call RunHookScript; - popad; - cmp cRet, 1; - jl end; - mov eax, rets[0]; -end: - hookend; + push ecx; + push 0; + call PerceptionRangeHook_Script; + pop ecx; retn; } } -// jmp here, not call -static const DWORD PerceptionRangeBonusHack_back = 0x456BA7; -static const DWORD PerceptionRangeBonusHack_skip_blocking_check = 0x456BDC; -static void __declspec(naked) PerceptionRangeBonusHack() { +static void __declspec(naked) PerceptionRangeSeeHook() { __asm { - call PerceptionRangeHook; - cmp eax, 2; - jne nevermind; - mov dword ptr [esp+16], 1; - jmp PerceptionRangeBonusHack_skip_blocking_check; + push ecx; + push 1; + call PerceptionRangeHook_Script; + pop ecx; + cmp eax, 2; + jne nevermind; // normal return + dec eax; + mov dword ptr[esp + 0x2C - 0x1C + 4], eax; // set 1, skip blocking check + dec eax; nevermind: - jmp PerceptionRangeBonusHack_back; + retn; } } +static void __declspec(naked) PerceptionRangeHearHook() { + __asm { + push ecx; + push 2; + call PerceptionRangeHook_Script; + pop ecx; + retn; + } +} + +// 4.x backport static int __fastcall SwitchHandHook_Script(TGameObj* item, TGameObj* itemReplaced, DWORD addr) { - if (itemReplaced && ItemGetType(itemReplaced) == 3 && ItemGetType(item) == 4) { + if (itemReplaced && ItemGetType(itemReplaced) == item_type_weapon && ItemGetType(item) == item_type_ammo) { return -1; // to prevent inappropriate hook call after dropping ammo on weapon } @@ -966,7 +1027,7 @@ static int __fastcall SwitchHandHook_Script(TGameObj* item, TGameObj* itemReplac This hook is called every time an item is placed into either hand slot via inventory screen drag&drop If switch_hand_ function is not called, item is not placed anywhere (it remains in main inventory) */ -static void _declspec(naked) SwitchHandHook() { +static void __declspec(naked) SwitchHandHook() { __asm { pushadc; mov ecx, eax; // item being moved @@ -983,182 +1044,321 @@ skip: } } -static const DWORD UseArmorHack_back = 0x4713A9; // normal operation +/* Common inventory move hook */ +static int __fastcall InventoryMoveHook_Script(DWORD itemReplace, DWORD item, int type) { + BeginHook(); + argCount = 3; + + args[0] = type; // event type + args[1] = item; // item being dropped + args[2] = itemReplace; // item being replaced here + + RunHookScript(HOOK_INVENTORYMOVE); + + int result = (cRet > 0) ? rets[0] : -1; + EndHook(); + + return result; +} + +static const DWORD UseArmorHack_back = 0x4713AF; // normal operation (old 0x4713A9) static const DWORD UseArmorHack_skip = 0x471481; // skip code, prevent wearing armor // This hack is called when an armor is dropped into the armor slot at inventory screen -static void _declspec(naked) UseArmorHack() { +static void __declspec(naked) UseArmorHack() { __asm { - cmp eax, 0; - jne skip; // not armor - hookbegin(3); - mov args[0], 3; - mov eax, [esp+24]; // item - mov args[4], eax; - mov eax, ds:[_i_worn] - mov args[8], eax; - pushad; - push HOOK_INVENTORYMOVE; - call RunHookScript; - popad; - cmp cRet, 1; - jl back; - cmp rets[0], -1; - jne skip; -back: - hookend; - jmp UseArmorHack_back; + mov ecx, ds:[_i_worn]; // replacement item (override code) + mov edx, [esp + 0x58 - 0x40]; // item + push ecx; + push 3; // event: armor slot + call InventoryMoveHook_Script; // ecx - replacement item + cmp eax, -1; // ret value + pop ecx; + jne skip; + jmp UseArmorHack_back; skip: - hookend; - jmp UseArmorHack_skip; + jmp UseArmorHack_skip; } } -static void _declspec(naked) MoveInventoryHook() { +static void __declspec(naked) MoveInventoryHook() { __asm { - hookbegin(3); - mov args[0], 0; - mov args[4], edx; - mov args[8], 0; // no item being replaced here.. - pushad; - push HOOK_INVENTORYMOVE; - call RunHookScript; - popad; - cmp cRet, 1; - jl skipcheck; - cmp rets[0], -1; - jne skipcall; -skipcheck: - call item_add_force_; -skipcall: - hookend; - retn; - } -} - -static void _declspec(naked) invenWieldFunc_Hook() { - __asm { - hookbegin(4); - mov args[0], eax; // critter - mov args[4], edx; // item - mov args[8], ebx; // slot - mov args[12], 1; // wield flag - pushad; - cmp ebx, 1; // right hand slot? - je skip; - mov eax, edx; - call item_get_type_; - cmp eax, item_type_armor; - jz skip; - mov args[8], 2; // INVEN_TYPE_LEFT_HAND + pushadc; + xor eax, eax; + mov ecx, eax; // no item replace + cmp dword ptr ds:[_curr_stack], 0; + jle noCont; + mov ecx, eax; // contaner ptr + mov eax, 5; +noCont: + push eax; // event: 0 - main backpack, 5 - contaner + call InventoryMoveHook_Script; // edx - item + cmp eax, -1; // ret value + popadc; + jne skip; + jmp item_add_force_; skip: - push HOOK_INVENWIELD; - call RunHookScript; - popad; - cmp cRet, 1; - jl defaulthandler; - cmp rets[0], -1; - je defaulthandler; - mov eax, -1; - jmp end; -defaulthandler: - call invenWieldFunc_; -end: - hookend; retn; } } -// called when unwielding weapons -static void _declspec(naked) invenUnwieldFunc_Hook() { +// Hooks into dropping item from inventory to ground +// - allows to prevent item being dropped if 0 is returned with set_sfall_return +// - called for every item when dropping multiple items in stack (except caps) +// - when dropping caps it called always once +// - if 0 is returned while dropping caps, selected amount - 1 will still disappear from inventory (fixed) +static DWORD nextHookDropSkip = 0; +static int dropResult = -1; +static const DWORD InvenActionObjDropRet = 0x473874; +static void __declspec(naked) InvenActionCursorObjDropHook() { + if (nextHookDropSkip) { + nextHookDropSkip = 0; + goto skipHook; + } else { + __asm { + pushadc; + xor ecx, ecx; // no itemReplace + push 6; // event: item drop ground + call InventoryMoveHook_Script; // edx - item + mov dropResult, eax; // ret value + popadc; + cmp dword ptr [esp], 0x47379A + 5; // caps call address + jz capsMultiDrop; + } + } + + if (dropResult == -1) { +skipHook: + _asm call obj_drop_; + } + _asm retn; + +/* for only caps multi drop */ +capsMultiDrop: + if (dropResult == -1) { + nextHookDropSkip = 1; + _asm call item_remove_mult_; + _asm retn; + } + _asm add esp, 4; + _asm jmp InvenActionObjDropRet; // no caps drop +} + +static void __declspec(naked) InvenActionExplosiveDropHack() { __asm { - hookbegin(4); - mov args[0], eax; // critter - mov args[4], 0; // item - mov args[8], edx; // slot - mov args[12], 0; // wield flag - cmp edx, 0; // left hand slot? - jne notlefthand; - mov args[8], 2; // left hand -notlefthand: - pushad; - push HOOK_INVENWIELD; - call RunHookScript; - popad; - cmp cRet, 1; - jl defaulthandler; - cmp rets[0], -1; - je defaulthandler; - mov eax, -1; - jmp end; -defaulthandler: - call invenUnwieldFunc_; -end: - hookend; + pushadc; + xor ecx, ecx; // no itemReplace + push 6; // event: item drop ground + call InventoryMoveHook_Script; // edx - item + cmp eax, -1; // ret value + popadc; + jnz noDrop; + mov dword ptr ds:[_dropped_explosive], ebp; // overwritten engine code (ebp = 1) + mov nextHookDropSkip, ebp; retn; +noDrop: + add esp, 4; + jmp InvenActionObjDropRet; // no drop } } -static void _declspec(naked) correctFidForRemovedItem_Hook() { +static int __fastcall DropIntoContainer(DWORD ptrCont, DWORD item, DWORD addrCall) { + int type = 5; // event: move to container (Crafty compatibility) + + if (addrCall == 0x47147C + 5) { // drop out contaner + ptrCont = 0; + type = 0; // event: move to main backpack + } + return InventoryMoveHook_Script(ptrCont, item, type); +} + +static const DWORD DropIntoContainer_back = 0x47649D; // normal operation +static const DWORD DropIntoContainer_skip = 0x476503; +static void __declspec(naked) DropIntoContainerHack() { __asm { - hookbegin(4); - mov args[0], eax; // critter - mov args[4], edx; // item - mov args[8], 0; // slot - mov args[12], 0 // wield flag (armor by default) - test ebx, 0x02000000; // right hand slot? - jz notrighthand; - mov args[8], 1; // right hand -notrighthand: - test ebx, 0x01000000; // left hand slot? - jz notlefthand; - mov args[8], 2; // left hand -notlefthand: - pushad; - push HOOK_INVENWIELD; - call RunHookScript; - popad; - cmp cRet, 1; - jl defaulthandler; - cmp rets[0], -1; - je defaulthandler; - mov eax, -1; - jmp end; -defaulthandler: - call correctFidForRemovedItem_; -end: - hookend; - retn; + pushadc; + mov ecx, ebp; // contaner ptr + mov edx, esi; // item + mov eax, [esp + 0x10 + 12]; // call address + push eax; + call DropIntoContainer; + cmp eax, -1; // ret value + popadc; + jne skipdrop; + jmp DropIntoContainer_back; +skipdrop: + mov eax, -1; + jmp DropIntoContainer_skip; + } +} + +static const DWORD DropIntoContainerRet = 0x471481; +static void __declspec(naked) DropIntoContainerHandSlotHack() { + __asm { + call drop_into_container_; + jmp DropIntoContainerRet; } } //static const DWORD DropAmmoIntoWeaponHack_back = 0x47658D; // proceed with reloading static const DWORD DropAmmoIntoWeaponHack_return = 0x476643; -static void _declspec(naked) DropAmmoIntoWeaponHook() { +static void __declspec(naked) DropAmmoIntoWeaponHook() { __asm { - hookbegin(3); - mov args[0], 4; - mov eax, [esp+4]; // item var: ammo_ - mov args[4], eax; - mov args[8], ebp; // weapon ptr - pushad; - push HOOK_INVENTORYMOVE; - call RunHookScript; - popad; - cmp cRet, 1; - jl proceedreloading; - cmp rets[0], -1; - jne donothing; -proceedreloading: - hookend; - mov eax, ebp; // weapon, edx - ammo - jmp item_w_can_reload_; - //mov ebx, 1; // overwritten code - //jmp DropAmmoIntoWeaponHack_back; + pushadc; + mov ecx, ebp; // weapon ptr + mov edx, [esp + 16]; // item var: ammo_ + push 4; // event: weapon reloading + call InventoryMoveHook_Script; + cmp eax, -1; // ret value + popadc; + jne donothing; + jmp item_w_can_reload_; + //mov ebx, 1; // overwritten code + //jmp DropAmmoIntoWeaponHack_back; donothing: - hookend; - add esp, 4; // destroy return address - xor eax, eax; // result 0 - jmp DropAmmoIntoWeaponHack_return; + add esp, 4; // destroy return address + xor eax, eax; // result 0 + jmp DropAmmoIntoWeaponHack_return; + } +} + +static void __declspec(naked) PickupObjectHack() { + __asm { + cmp edi, ds:[_obj_dude]; + je runHook; + xor edx, edx; // engine handler + retn; +runHook: + push eax; + push ecx; + mov edx, ecx; + xor ecx, ecx; // no itemReplace + push 7; // event: item pickup + call InventoryMoveHook_Script; // edx - item + mov edx, eax; // ret value + pop ecx; + pop eax; + inc edx; // 0 - engine handler, otherwise cancel pickup + retn; + } +} + +static void __declspec(naked) InvenPickupHook() { + __asm { + call mouse_click_in_; + test eax, eax; + jnz runHook; + retn; +runHook: + cmp dword ptr ds:[_curr_stack], 0; + jnz skip; + mov edx, [esp + 0x58 - 0x40 + 4]; // item + xor ecx, ecx; // no itemReplace + push 8; // event: drop item on character portrait + call InventoryMoveHook_Script; + cmp eax, -1; // ret value + je skip; + xor eax, eax; // 0 - cancel, otherwise engine handler +skip: + retn; + } +} + +/* Common InvenWield hook */ +static bool InvenWieldHook_Script(int flag) { + argCount = 4; + args[3] = flag; // invenwield flag + + RunHookScript(HOOK_INVENWIELD); + + bool result = (cRet == 0 || rets[0] == -1); + EndHook(); + + return result; // True - use engine handler +} + +static void __declspec(naked) InvenWieldFuncHook() { + __asm { + HookBegin; + mov args[0], eax; // critter + mov args[4], edx; // item + mov args[8], ebx; // slot + pushad; + } + + // right hand slot? + if (args[2] != INVEN_TYPE_RIGHT_HAND && ItemGetType((TGameObj*)args[1]) != item_type_armor) { + args[2] = INVEN_TYPE_LEFT_HAND; + } + + InvenWieldHook_Script(1); // wield flag + + __asm { + test al, al; + popad; + jz skip; + jmp invenWieldFunc_; +skip: + mov eax, -1; + retn; + } +} + +// called when unwielding weapons +static void __declspec(naked) InvenUnwieldFuncHook() { + __asm { + HookBegin; + mov args[0], eax; // critter + mov args[8], edx; // slot + pushad; + } + + // set slot + if (args[2] == 0) { // left hand slot? + args[2] = INVEN_TYPE_LEFT_HAND; + } + args[1] = (DWORD)GetItemPtrSlot((TGameObj*)args[0], args[2]); // get item + + InvenWieldHook_Script(0); // unwield flag + + __asm { + test al, al; + popad; + jz skip; + jmp invenUnwieldFunc_; +skip: + mov eax, -1; + retn; + } +} + +static void __declspec(naked) CorrectFidForRemovedItemHook() { + __asm { + HookBegin; + mov args[0], eax; // critter + mov args[4], edx; // item + mov args[8], ebx; // item flag + pushad; + } + + // set slot + if (args[2] & 0x2000000) { // right hand slot + args[2] = INVEN_TYPE_RIGHT_HAND; + } else if (args[2] & 0x1000000) { // left hand slot + args[2] = INVEN_TYPE_LEFT_HAND; + } else { + args[2] = INVEN_TYPE_WORN; // armor slot + } + + InvenWieldHook_Script(0); // unwield flag (armor by default) + + __asm { + test al, al; + popad; + jz skip; + jmp correctFidForRemovedItem_; +skip: + mov eax, -1; + retn; } } @@ -1309,7 +1509,7 @@ static void HookScriptInit2() { HookCall(0x425161, &OnDeathHook2); LoadHookScript("hs_findtarget", HOOK_FINDTARGET); - HookCall(0x429143, &FindTargetHook); + HookCall(0x429143, FindTargetHook); LoadHookScript("hs_useobjon", HOOK_USEOBJON); HookCall(0x49C606, &UseObjOnHook); @@ -1324,9 +1524,11 @@ static void HookScriptInit2() { MakeJump(0x477492, RemoveObjHook); // old 0x477490 LoadHookScript("hs_barterprice", HOOK_BARTERPRICE); - HookCall(0x474D4C, &BarterPriceHook); - HookCall(0x475735, &BarterPriceHook); - HookCall(0x475762, &BarterPriceHook); + HookCall(0x474D4C, BarterPriceHook); + HookCall(0x475735, BarterPriceHook); + HookCall(0x475762, BarterPriceHook); + HookCall(0X47551A, PC_BarterPriceHook); + MakeJump(0x474D3F, OverrideCost_BarterPriceHack); // just overrides cost of offered goods LoadHookScript("hs_movecost", HOOK_MOVECOST); HookCall(0x417665, &MoveCostHook); @@ -1350,7 +1552,7 @@ static void HookScriptInit2() { HookCall(0x478560, &ItemDamageHook); LoadHookScript("hs_ammocost", HOOK_AMMOCOST); - HookCall(0x423A7C, &AmmoCostHook); + HookCall(0x423A7C, AmmoCostHook); LoadHookScript("hs_useobj", HOOK_USEOBJ); HookCall(0x42AEBF, &UseObjHook); @@ -1369,29 +1571,41 @@ static void HookScriptInit2() { HookCall(0x474A69, &StealCheckHook); LoadHookScript("hs_withinperception", HOOK_WITHINPERCEPTION); - HookCall(0x429157, &PerceptionRangeHook); - HookCall(0x42B4ED, &PerceptionRangeHook); - HookCall(0x42BC87, &PerceptionRangeHook); - HookCall(0x42BC9F, &PerceptionRangeHook); - HookCall(0x42BD04, &PerceptionRangeHook); - MakeJump(0x456BA2, PerceptionRangeBonusHack); - HookCall(0x458403, &PerceptionRangeHook); + HookCall(0x429157, PerceptionRangeHook); + HookCall(0x42B4ED, PerceptionRangeHook); + HookCall(0x42BC87, PerceptionRangeHook); + HookCall(0x42BC9F, PerceptionRangeHook); + HookCall(0x42BD04, PerceptionRangeHook); + HookCall(0x456BA2, PerceptionRangeSeeHook); + HookCall(0x458403, PerceptionRangeHearHook); LoadHookScript("hs_inventorymove", HOOK_INVENTORYMOVE); - HookCall(0x4712E3, &SwitchHandHook); // left slot - HookCall(0x47136D, &SwitchHandHook); // right slot - MakeJump(0x4713A3, UseArmorHack); - HookCall(0x471200, &MoveInventoryHook); - HookCall(0x476549, &DropAmmoIntoWeaponHook); // old 0x476588 + HookCall(0x4712E3, SwitchHandHook); // left slot + HookCall(0x47136D, SwitchHandHook); // right slot + MakeJump(0x4713A9, UseArmorHack); // old 0x4713A3 + MakeJump(0x476491, DropIntoContainerHack); + MakeJump(0x471338, DropIntoContainerHandSlotHack); + MakeJump(0x4712AB, DropIntoContainerHandSlotHack); + HookCall(0x471200, MoveInventoryHook); + HookCall(0x476549, DropAmmoIntoWeaponHook); // old 0x476588 + HookCall(0x473851, InvenActionCursorObjDropHook); + HookCall(0x47386F, InvenActionCursorObjDropHook); + HookCall(0x47379A, InvenActionCursorObjDropHook); // caps multi drop + MakeCall(0x473807, InvenActionExplosiveDropHack, 1); // drop active explosives + MakeCall(0x49B660, PickupObjectHack); + SafeWrite32(0x49B665, 0x850FD285); // test edx, edx + SafeWrite32(0x49B669, 0xC2); // jnz 0x49B72F + SafeWrite8(0x49B66E, 0xFE); // cmp edi > cmp esi + HookCall(0x471457, InvenPickupHook); LoadHookScript("hs_invenwield", HOOK_INVENWIELD); - HookCall(0x47275E, &invenWieldFunc_Hook); - HookCall(0x495FDF, &invenWieldFunc_Hook); - HookCall(0x45967D, &invenUnwieldFunc_Hook); - HookCall(0x472A5A, &invenUnwieldFunc_Hook); - HookCall(0x495F0B, &invenUnwieldFunc_Hook); - HookCall(0x45680C, &correctFidForRemovedItem_Hook); - HookCall(0x45C4EA, &correctFidForRemovedItem_Hook); + HookCall(0x47275E, InvenWieldFuncHook); // inven_wield_ + HookCall(0x495FDF, InvenWieldFuncHook); // partyMemberCopyLevelInfo_ + HookCall(0x45967D, InvenUnwieldFuncHook); // op_metarule_ + HookCall(0x472A5A, InvenUnwieldFuncHook); // inven_unwield_ + HookCall(0x495F0B, InvenUnwieldFuncHook); // partyMemberCopyLevelInfo_ + HookCall(0x45680C, CorrectFidForRemovedItemHook); // op_rm_obj_from_inven_ + HookCall(0x45C4EA, CorrectFidForRemovedItemHook); // op_move_obj_inven_to_obj_ __asm { xor edx, edx