diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index 09af7215..44510f71 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -51,6 +51,7 @@ #define HOOK_WITHINPERCEPTION (23) #define HOOK_INVENTORYMOVE (24) #define HOOK_INVENWIELD (25) +#define HOOK_ADJUSTFID (26) #define HOOK_GAMEMODECHANGE (31) //Valid arguments to list_begin diff --git a/artifacts/scripting/hookscripts.txt b/artifacts/scripting/hookscripts.txt index 135da603..871dda65 100644 --- a/artifacts/scripting/hookscripts.txt +++ b/artifacts/scripting/hookscripts.txt @@ -465,6 +465,20 @@ int ret0 - overrides hard-coded handler (-1 - use engine handler, any other ------------------------------------------- +HOOK_ADJUSTFID (hs_adjustfid.int) + +Runs after calculating character figure FID on the inventory screen, whenever the game decides that character appearance might change. +Also happens on other screens, like barter. + +NOTE: FID has following format: 0x0ABBCDDD, where A is object type, BB - animation code (always 0 in this case), C - weapon code, DDD - FRM index in LST file. + +int arg0 - the vanilla FID calculated by the engine according to critter base FID and armor/weapon being used +int arg1 - the modified FID calculated by the internal sfall code (like Hero Appearance Mod) + +int ret0 - overrides the calculated FID with provided value + +------------------------------------------- + HOOK_GAMEMODECHANGE (hs_gamemodechange.int) Runs once every time when the game mode was changed, like opening/closing the inventory, character screen, pipboy, etc. diff --git a/sfall/FalloutFuncOffs_def.h b/sfall/FalloutFuncOffs_def.h index f12cb2cf..ea21b548 100644 --- a/sfall/FalloutFuncOffs_def.h +++ b/sfall/FalloutFuncOffs_def.h @@ -552,6 +552,7 @@ FUNC(win_register_button_disable_, 0x4D8674) FUNC(win_register_button_sound_func_, 0x4D87F8) FUNC(win_show_, 0x4D6DAC) FUNC(win_width_, 0x4D7918) +FUNC(windowCheckRegion_, 0x4B6858) FUNC(windowDisplayBuf_, 0x4B8EF0) FUNC(windowDisplayTransBuf_, 0x4B8F64) FUNC(windowGetBuffer_, 0x4B82DC) diff --git a/sfall/FalloutFuncs_def.h b/sfall/FalloutFuncs_def.h index 202afca4..0f7684a8 100644 --- a/sfall/FalloutFuncs_def.h +++ b/sfall/FalloutFuncs_def.h @@ -118,6 +118,7 @@ WRAP_WATCOM_FUNC2(long, interpretFindProcedure, TProgram*, scriptPtr, const char WRAP_WATCOM_FUNC1(DWORD, interpretPopShort, TProgram*, scriptPtr) // Pops value type from Data stack (must be followed by InterpretPopLong) WRAP_WATCOM_FUNC1(DWORD, interpretPopLong, TProgram*, scriptPtr) // Pops value from Data stack (must be preceded by InterpretPopShort) WRAP_WATCOM_FUNC2(long, intface_get_attack, DWORD*, hitMode, DWORD*, isSecondary) +WRAP_WATCOM_FUNC0(long, intface_is_item_right_hand) WRAP_WATCOM_FUNC0(long, intface_is_hidden) WRAP_WATCOM_FUNC0(void, intface_redraw) // Redraws the main game interface windows (useful after changing some data like active hand, etc.) WRAP_WATCOM_FUNC0(void, intface_toggle_item_state) diff --git a/sfall/HeroAppearance.cpp b/sfall/HeroAppearance.cpp index d7441e1d..107589e7 100644 --- a/sfall/HeroAppearance.cpp +++ b/sfall/HeroAppearance.cpp @@ -20,6 +20,7 @@ #include "main.h" #include "FalloutEngine.h" +#include "Inventory.h" #include "LoadGameHook.h" #include "LoadOrder.h" #include "Message.h" @@ -266,21 +267,12 @@ static void __declspec(naked) AdjustHeroBaseArt() { } // adjust armor art if num below hero art range -static void __declspec(naked) AdjustHeroArmorArt() { - __asm { - pop ebx; // get ret addr - mov dword ptr ss:[esp], ebx; // reinsert ret addr in old (push 0) - xor ebx, ebx; - push 0; - call art_id_; // call load frm func - mov dx, ax; - and dh, 0x0F; // mask out current weapon flag - cmp edx, critterListSize; // check if critter art in PC range - jg endFunc; - add eax, critterListSize; // shift critter art index up into hero range -endFunc: - //mov dword ptr ds:[FO_VAR_i_fid], eax; - retn; +void __stdcall AdjustHeroArmorArt(DWORD fid) { + if ((fid & 0xF000000) == (OBJ_TYPE_CRITTER << 24) && !PartyControl_IsNpcControlled()) { + DWORD fidBase = fid & 0xFFF; + if (fidBase <= critterListSize) { + *ptr_i_fid += critterListSize; + } } } @@ -367,76 +359,45 @@ static void DrawPC() { } // scan inventory items for armor and weapons currently being worn or wielded and setup matching FrmID for PC -void __stdcall RefreshPCArt() { - __asm { - call proto_dude_update_gender_; // refresh PC base model art +void UpdateHeroArt() { + TGameObj* iD = *ptr_inven_dude; + TGameObj* iR = *ptr_i_rhand; + TGameObj* iL = *ptr_i_lhand; + TGameObj* iW = *ptr_i_worn; - push dword ptr ds:[FO_VAR_inven_dude]; - push dword ptr ds:[FO_VAR_i_rhand]; // item2 - push dword ptr ds:[FO_VAR_i_lhand]; // item1 - push dword ptr ds:[FO_VAR_i_worn]; // armor + *ptr_i_rhand = 0; + *ptr_i_lhand = 0; + *ptr_i_worn = 0; - mov eax, dword ptr ds:[FO_VAR_obj_dude]; // PC state struct - mov dword ptr ds:[FO_VAR_inven_dude], eax; // inventory temp pointer to PC state struct - mov eax, dword ptr ds:[FO_VAR_inven_dude]; - lea edx, dword ptr ds:[eax + 0x2C]; - mov dword ptr ds:[FO_VAR_pud], edx; // PC inventory + *ptr_inven_dude = *ptr_obj_dude; + int invenSize = (*ptr_obj_dude)->invenSize; + //*ptr_pud = invenSize; - xor eax, eax; - xor edx, edx; // itemListOffset - xor ebx, ebx; // itemNum + for (int itemNum = 0; itemNum < invenSize; itemNum++) { + TGameObj* item = (*ptr_obj_dude)->invenTable[itemNum].object; // PC inventory item list + itemListOffset - mov dword ptr ds:[FO_VAR_i_rhand], eax; // item2 - mov dword ptr ds:[FO_VAR_i_lhand], eax; // item1 - mov dword ptr ds:[FO_VAR_i_worn], eax; // armor - jmp LoopStart; - -CheckNextItem: - mov eax, dword ptr ds:[eax + 0x8]; // PC inventory item list - mov eax, dword ptr ds:[edx + eax]; // PC inventory item list + itemListOffset - - test byte ptr ds:[eax + 0x27], 1; // if item in item1 slot - jnz IsItem1; - test byte ptr ds:[eax + 0x27], 2; // if item in item2 slot - jnz IsItem2; - test byte ptr ds:[eax + 0x27], 4; // if item in armor slot - jnz IsArmor; - jmp SetNextItem; - -IsItem1: - mov dword ptr ds:[FO_VAR_i_lhand], eax; // set item1 - test byte ptr ds:[eax + 0x27], 2; // check if same item type also in item2 slot - jz SetNextItem; - -IsItem2: - mov dword ptr ds:[FO_VAR_i_rhand], eax; // set item2 - jmp SetNextItem; - -IsArmor: - mov dword ptr ds:[FO_VAR_i_worn], eax; // set armor - -SetNextItem: - inc ebx; // itemNum++ - add edx, 8; // itemListOffset + itemsize -LoopStart: - mov eax, dword ptr ds:[FO_VAR_pud]; // PC inventory - cmp ebx, dword ptr ds:[eax]; // size of item list - jl CheckNextItem; - - // inventory function - setup pc FrmID and store at address _i_fid - call adjust_fid_; - - // copy new FrmID to hero state struct - mov edx, dword ptr ds:[FO_VAR_i_fid]; - mov eax, dword ptr ds:[FO_VAR_inven_dude]; - mov dword ptr ds:[eax + 0x20], edx; - //call obj_change_fid_; - - pop dword ptr ds:[FO_VAR_i_worn]; // armor - pop dword ptr ds:[FO_VAR_i_lhand]; // item1 - pop dword ptr ds:[FO_VAR_i_rhand]; // item2 - pop dword ptr ds:[FO_VAR_inven_dude]; + if (item->flags & ObjectFlag::Right_Hand) { + *ptr_i_rhand = item; + } else if (item->flags & ObjectFlag::Left_Hand) { + *ptr_i_lhand = item; + } else if (item->flags & ObjectFlag::Worn) { + *ptr_i_worn = item; + } } + + // inventory function - setup pc FrmID and store at address _i_fid + (*ptr_obj_dude)->artFid = Inventory_adjust_fid(); // adjust_fid_ + + *ptr_inven_dude = iD; + *ptr_i_rhand = iR; + *ptr_i_lhand = iL; + *ptr_i_worn = iW; +} + +void __stdcall RefreshPCArt() { + fo_proto_dude_update_gender(); // refresh PC base model art + + UpdateHeroArt(); DrawPC(); } @@ -1462,7 +1423,7 @@ static void EnableHeroAppearanceMod() { MakeCall(0x49F9DA, AdjustHeroBaseArt); // Adjust hero art index offset when changing armor (adjust_fid_) - HookCall(0x4717D1, AdjustHeroArmorArt); + /* HookCall(0x4717D1, AdjustHeroArmorArt); - the engine function is replaced by the sfall code */ // Hijack Save Hero State Structure fuction address 9CD54800 // Return hero art index offset back to normal before saving diff --git a/sfall/HeroAppearance.h b/sfall/HeroAppearance.h index 018b7f58..bd85c963 100644 --- a/sfall/HeroAppearance.h +++ b/sfall/HeroAppearance.h @@ -18,9 +18,13 @@ #pragma once +extern bool appModEnabled; + void HeroAppearance_Init(); void HeroAppearance_Exit(); +void __stdcall AdjustHeroArmorArt(DWORD fid); + void __stdcall HeroSelectWindow(int raceStyleFlag); void __stdcall SetHeroStyle(int newStyleVal); void __stdcall SetHeroRace(int newRaceVal); diff --git a/sfall/HookScripts.cpp b/sfall/HookScripts.cpp index dd9d58a1..cc733199 100644 --- a/sfall/HookScripts.cpp +++ b/sfall/HookScripts.cpp @@ -1586,6 +1586,21 @@ long __stdcall CorrectFidForRemovedItem_wHook(TGameObj* critter, TGameObj* item, return result; } +// 4.x backport +void __stdcall AdjustFidHook(DWORD vanillaFid) { + BeginHook(); + argCount = 2; + + args[0] = vanillaFid; + args[1] = *ptr_i_fid; // modified FID by sfall code + RunHookScript(HOOK_ADJUSTFID); + + if (cRet > 0) { + *ptr_i_fid = rets[0]; + } + EndHook(); +} + // 4.x backport static unsigned long previousGameMode = 0; @@ -1635,7 +1650,7 @@ void __stdcall SetHSReturn(DWORD value) { } void __stdcall RegisterHook(TProgram* script, int id, int procNum, bool specReg) { - if (id >= numHooks || (id > HOOK_INVENWIELD && id < HOOK_GAMEMODECHANGE)) return; + if (id >= numHooks || (id > HOOK_ADJUSTFID && id < HOOK_GAMEMODECHANGE)) return; for (std::vector::iterator it = hooks[id].begin(); it != hooks[id].end(); ++it) { if (it->prog.ptr == script) { if (procNum == 0) hooks[id].erase(it); // unregister @@ -1873,6 +1888,7 @@ static void HookScriptInit() { HookCall(0x45C4F6, op_move_obj_inven_to_obj_hook); MakeCall(0x4778AF, item_drop_all_hack, 3); + LoadHookScript("hs_adjustfid", HOOK_ADJUSTFID); LoadHookScript("hs_gamemodechange", HOOK_GAMEMODECHANGE); fo_db_free_file_list(&filenames, 0); diff --git a/sfall/HookScripts.h b/sfall/HookScripts.h index 3057f3c4..eac0f3e5 100644 --- a/sfall/HookScripts.h +++ b/sfall/HookScripts.h @@ -46,6 +46,7 @@ enum HookType HOOK_WITHINPERCEPTION = 23, HOOK_INVENTORYMOVE = 24, HOOK_INVENWIELD = 25, + HOOK_ADJUSTFID = 26, // 4.x backport HOOK_GAMEMODECHANGE = 31, // 4.x backport HOOK_COUNT }; @@ -65,17 +66,20 @@ void HookScriptClear(); extern DWORD initingHookScripts; -int __fastcall AmmoCostHook_Script(DWORD hookType, TGameObj* weapon, DWORD &rounds); void __stdcall RunHookScriptsAtProc(DWORD procId); void __stdcall GameModeChangeHook(DWORD exit); void __stdcall KeyPressHook(DWORD* dxKey, bool pressed, DWORD vKey); void __stdcall MouseClickHook(DWORD button, bool pressed); +int __fastcall AmmoCostHook_Script(DWORD hookType, TGameObj* weapon, DWORD &rounds); + long __stdcall CorrectFidForRemovedItem_wHook(TGameObj* critter, TGameObj* item, long flags); void InvenUnwield_HookDrop(); void InvenUnwield_HookMove(); +void __stdcall AdjustFidHook(DWORD vanillaFid); + // Implementation of item_w_mp_cost_ engine function with the hook long __fastcall sf_item_w_mp_cost(TGameObj* source, long hitMode, long isCalled); diff --git a/sfall/Inventory.cpp b/sfall/Inventory.cpp index 3967b37c..f6daf3b2 100644 --- a/sfall/Inventory.cpp +++ b/sfall/Inventory.cpp @@ -20,8 +20,11 @@ #include "main.h" #include "FalloutEngine.h" +#include "HeroAppearance.h" +#include "HookScripts.h" #include "InputFuncs.h" #include "LoadGameHook.h" +#include "PartyControl.h" #include "Inventory.h" @@ -58,9 +61,9 @@ void InventoryKeyPressedHook(DWORD dxKey, bool pressed) { } } -///////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// -DWORD __stdcall sf_item_total_size(TGameObj* critter) { +DWORD __stdcall Inventory_item_total_size(TGameObj* critter) { int totalSize = fo_item_c_curr_size(critter); if (critter->TypeFid() == OBJ_TYPE_CRITTER) { @@ -82,6 +85,70 @@ DWORD __stdcall sf_item_total_size(TGameObj* critter) { return totalSize; } +// Reimplementation of adjust_fid engine function +// Differences from vanilla: +// - doesn't use art_vault_guy_num as default art, uses current critter FID instead +// - calls AdjustFidHook that allows to hook into FID calculation +DWORD __stdcall Inventory_adjust_fid() { + DWORD fid; + if ((*ptr_inven_dude)->TypeFid() == OBJ_TYPE_CRITTER) { + DWORD indexNum; + DWORD weaponAnimCode = 0; + if (PartyControl_IsNpcControlled()) { + // if NPC is under control, use current FID of critter + indexNum = (*ptr_inven_dude)->artFid & 0xFFF; + } else { + // vanilla logic: + indexNum = *ptr_art_vault_guy_num; + auto critterPro = GetProto(*ptr_inven_pid); + if (critterPro != nullptr) { + indexNum = critterPro->fid & 0xFFF; + } + if (*ptr_i_worn != nullptr) { + auto armorPro = GetProto((*ptr_i_worn)->protoId); + DWORD armorFid = fo_stat_level(*ptr_inven_dude, STAT_gender) == GENDER_FEMALE + ? armorPro->item.armor.femaleFID + : armorPro->item.armor.maleFID; + + if (armorFid != -1) { + indexNum = armorFid; + } + } + } + auto itemInHand = fo_intface_is_item_right_hand() + ? *ptr_i_rhand + : *ptr_i_lhand; + + if (itemInHand != nullptr) { + auto itemPro = GetProto(itemInHand->protoId); + if (itemPro->item.type == item_type_weapon) { + weaponAnimCode = itemPro->item.weapon.animationCode; + } + } + fid = fo_art_id(OBJ_TYPE_CRITTER, indexNum, 0, weaponAnimCode, 0); + } else { + fid = (*ptr_inven_dude)->artFid; + } + *ptr_i_fid = fid; + // OnAdjustFid + if (appModEnabled) AdjustHeroArmorArt(fid); + AdjustFidHook(fid); // should be called last + return *ptr_i_fid; +} + +static void __declspec(naked) adjust_fid_hack() { + __asm { + push ecx; + push edx; + call Inventory_adjust_fid; // return fid + pop edx; + pop ecx; + retn; + } +} + +//////////////////////////////////////////////////////////////////////////////// + static int __stdcall CritterGetMaxSize(TGameObj* critter) { if (critter == *ptr_obj_dude) return invSizeMaxLimit; @@ -108,7 +175,7 @@ static __declspec(naked) void critterIsOverloaded_hack() { jz skip; push ebx; mov ebx, eax; // ebx = MaxSize - call sf_item_total_size; + call Inventory_item_total_size; cmp eax, ebx; setg al; // if CurrSize > MaxSize and eax, 0xFF; @@ -122,8 +189,8 @@ end: static int __fastcall CanAddedItems(TGameObj* critter, TGameObj* item, int count) { int sizeMax = CritterGetMaxSize(critter); if (sizeMax > 0) { - int itemsSize = fo_item_size(item) * count; - if (itemsSize + (int)sf_item_total_size(critter) > sizeMax) return -6; // TODO: Switch this to a lower number, and add custom error messages. + int totalSize = Inventory_item_total_size(critter) + (fo_item_size(item) * count); + if (totalSize > sizeMax) return -6; // TODO: Switch this to a lower number, and add custom error messages. } return 0; } @@ -168,10 +235,10 @@ static int __fastcall BarterAttemptTransaction(TGameObj* critter, TGameObj* tabl int size = CritterGetMaxSize(critter); if (size == 0) return 1; - int sizeTable = sf_item_total_size(table); + int sizeTable = Inventory_item_total_size(table); if (sizeTable == 0) return 1; - size -= sf_item_total_size(critter); + size -= Inventory_item_total_size(critter); return (sizeTable <= size) ? 1 : 0; } @@ -246,7 +313,7 @@ static void __cdecl DisplaySizeStats(TGameObj* critter, const char* &message, DW } sizeMax = limitMax; - size = sf_item_total_size(critter); + size = Inventory_item_total_size(critter); const char* msg = MsgSearch(ptr_inventry_message_file, 35); message = (msg != nullptr) ? msg : ""; @@ -312,14 +379,15 @@ static void __declspec(naked) gdControlUpdateInfo_hack() { call CritterGetMaxSize; push eax; // sizeMax push ebx; - call sf_item_total_size; + call Inventory_item_total_size; push eax; // size mov eax, ebx; mov edx, STAT_carry_amt; jmp ControlUpdateInfoRet; } } -///////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// static char superStimMsg[128]; static int __fastcall SuperStimFix(TGameObj* item, TGameObj* target) { @@ -634,6 +702,9 @@ void InventoryReset() { } void Inventory_Init() { + // Replace adjust_fid_ function + MakeJump(adjust_fid_, adjust_fid_hack); // 0x4716E8 + long widthWeight = 135; sizeLimitMode = GetConfigInt("Misc", "CritterInvSizeLimitMode", 0); diff --git a/sfall/Inventory.h b/sfall/Inventory.h index b227c953..82844d89 100644 --- a/sfall/Inventory.h +++ b/sfall/Inventory.h @@ -24,4 +24,14 @@ void InventoryKeyPressedHook(DWORD dxKey, bool pressed); long __stdcall GetInvenApCost(); void __fastcall SetInvenApCost(int cost); -DWORD __stdcall sf_item_total_size(TGameObj* critter); + +// This function returns the size of the occupied inventory space for the object or critter +// - difference from the item_c_curr_size_ function: includes the size of equipped items for the critter +// - does not return the size of nested items +DWORD __stdcall Inventory_item_total_size(TGameObj* critter); + +// Reimplementation of adjust_fid engine function +// Differences from vanilla: +// - doesn't use art_vault_guy_num as default art, uses current critter FID instead +// - calls AdjustFidHook that allows to hook into FID calculation +DWORD __stdcall Inventory_adjust_fid(); diff --git a/sfall/ScriptOps/ObjectsOps.hpp b/sfall/ScriptOps/ObjectsOps.hpp index 3ec5560e..69adb07d 100644 --- a/sfall/ScriptOps/ObjectsOps.hpp +++ b/sfall/ScriptOps/ObjectsOps.hpp @@ -524,7 +524,7 @@ static void mf_set_unjam_locks_time() { static void mf_get_current_inven_size() { TGameObj* obj = opHandler.arg(0).asObject(); if (obj) { - opHandler.setReturn(sf_item_total_size(obj)); + opHandler.setReturn(Inventory_item_total_size(obj)); } else { OpcodeInvalidArgs("get_current_inven_size"); opHandler.setReturn(0);