diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index a98a3b46..754081cd 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -670,6 +670,10 @@ ReloadReserve=-1 ;Set to 1 to change the counter in the 'Move Items' window to start with maximum number, except in the barter screen ItemCounterDefaultMax=0 +;Set to 1 to enable money/caps auto-balancing in the barter screen +;When moving money/caps to or from the table, the 'Move Items' window will be pre-filled with the correct balancing amount +ItemCounterAutoCaps=0 + ;Set to 1 to leave the music playing in dialogue with talking heads EnableMusicInDialogue=0 diff --git a/sfall/FalloutEngine/VarPointers_def.h b/sfall/FalloutEngine/VarPointers_def.h index 5a32338e..42b8c931 100644 --- a/sfall/FalloutEngine/VarPointers_def.h +++ b/sfall/FalloutEngine/VarPointers_def.h @@ -12,7 +12,7 @@ PTR_(bckgnd, BYTE*) PTR_(black_palette, DWORD) PTR_(BlueColor, BYTE) PTR_(bottom_line, DWORD) -PTR_(btable, DWORD) +PTR_(btable, fo::GameObject*) PTR_(btncnt, DWORD) PTR_(cap, fo::AIcap*) // dynamic array PTR_(carCurrentArea, DWORD) @@ -181,7 +181,7 @@ PTR_(procTableStrs, const char*) // table of procId (from define.h PTR_(proto_main_msg_file, fo::MessageList) PTR_(proto_msg_files, fo::MessageList) // array of 6 elements PTR_(protoLists, fo::ProtoList) // array of 11 elements -PTR_(ptable, DWORD) +PTR_(ptable, fo::GameObject*) PTR_(pud, DWORD) PTR_(queue, fo::Queue*) PTR_(quick_done, DWORD) @@ -203,7 +203,7 @@ PTR_(sound_music_path2, char*) PTR_(speech_volume, DWORD) PTR_(square, DWORD*) // array of 3 pointers, use (square && 0xFFF) to get ground fid, and ((square >> 16) && 0xFFF) to get roof PTR_(squares, DWORD*) -PTR_(stack, DWORD) // array of 10 DWORD +PTR_(stack, fo::GameObject*) // array of 10 GameObject* PTR_(stack_offset, DWORD) // array of 10 DWORD PTR_(stat_data, fo::StatInfo) // array of size == STAT_real_max_stat PTR_(stat_flag, DWORD) @@ -213,7 +213,7 @@ PTR_(Tag_, DWORD) PTR_(tag_skill, DWORD) PTR_(target_curr_stack, DWORD) PTR_(target_pud, DWORD*) -PTR_(target_stack, DWORD) // array of 10 DWORD +PTR_(target_stack, fo::GameObject*) // array of 10 GameObject* PTR_(target_stack_offset, DWORD) // array of 10 DWORD PTR_(target_str, DWORD) PTR_(target_xpos, DWORD) diff --git a/sfall/Modules/HookScripts/MiscHs.cpp b/sfall/Modules/HookScripts/MiscHs.cpp index 50d5c8fa..dbab882c 100644 --- a/sfall/Modules/HookScripts/MiscHs.cpp +++ b/sfall/Modules/HookScripts/MiscHs.cpp @@ -10,6 +10,9 @@ namespace sfall { +static DWORD lastTableCostPC; // keep last cost for pc +static DWORD lastTableCostNPC; + // The hook is executed twice when entering the barter screen and after transaction: the first time is for the player; the second time is for NPC static DWORD __fastcall BarterPriceHook_Script(fo::GameObject* source, fo::GameObject* target, DWORD callAddr) { bool barterIsParty = (*fo::ptr::dialog_target_is_party != 0); @@ -22,12 +25,12 @@ static DWORD __fastcall BarterPriceHook_Script(fo::GameObject* source, fo::GameO args[1] = (DWORD)target; args[2] = !barterIsParty ? computeCost : 0; - fo::GameObject* bTable = (fo::GameObject*)*fo::ptr::btable; + fo::GameObject* bTable = *fo::ptr::btable; args[3] = (DWORD)bTable; args[4] = fo::func::item_caps_total(bTable); args[5] = fo::func::item_total_cost(bTable); - fo::GameObject* pTable = (fo::GameObject*)*fo::ptr::ptable; + fo::GameObject* pTable = *fo::ptr::ptable; args[6] = (DWORD)pTable; long pcCost = 0; @@ -66,11 +69,11 @@ static void __declspec(naked) BarterPriceHook() { call BarterPriceHook_Script; // edx - target pop ecx; pop edx; + mov lastTableCostNPC, eax; retn; } } -static DWORD offersGoodsCost; // keep last cost for pc static void __declspec(naked) PC_BarterPriceHook() { __asm { push edx; @@ -82,18 +85,28 @@ static void __declspec(naked) PC_BarterPriceHook() { call BarterPriceHook_Script; pop ecx; pop edx; - mov offersGoodsCost, eax; + mov lastTableCostPC, eax; retn; } } static void __declspec(naked) OverrideCost_BarterPriceHook() { __asm { - mov eax, offersGoodsCost; + mov eax, lastTableCostPC; retn; } } +void BarterPriceHook_GetLastCosts(long& outPcTableCost, long& outNpcTableCost) { + if (!HookScripts::HookHasScript(HOOK_BARTERPRICE)) { + outPcTableCost = fo::func::item_total_cost(*fo::ptr::ptable); + outNpcTableCost = fo::func::barter_compute_value(*fo::ptr::obj_dude, fo::ptr::target_stack[0]); + return; + } + outPcTableCost = lastTableCostPC; + outNpcTableCost = lastTableCostNPC; +} + static fo::GameObject* sourceSkillOn = nullptr; void SourceUseSkillOnInit() { sourceSkillOn = *fo::ptr::obj_dude; } @@ -735,7 +748,7 @@ void Inject_BarterPriceHook() { 0x475762 // display_table_inventories_ }; HookCalls(BarterPriceHook, barterPriceHkAddr); - const DWORD pcBarterPriceHkAddr[] = {0x4754F4, 0X47551A}; // display_table_inventories_ + const DWORD pcBarterPriceHkAddr[] = {0x4754F4, 0x47551A}; // display_table_inventories_ HookCalls(PC_BarterPriceHook, pcBarterPriceHkAddr); HookCall(0x474D3F, OverrideCost_BarterPriceHook); // barter_attempt_transaction_ (just overrides cost of offered goods) } diff --git a/sfall/Modules/HookScripts/MiscHs.h b/sfall/Modules/HookScripts/MiscHs.h index f300ef73..7db60974 100644 --- a/sfall/Modules/HookScripts/MiscHs.h +++ b/sfall/Modules/HookScripts/MiscHs.h @@ -21,4 +21,6 @@ void Inject_RollCheckHook(); long PerceptionRangeHook_Invoke(fo::GameObject* watcher, fo::GameObject* target, long type, long result); +void BarterPriceHook_GetLastCosts(long& outPcTableCost, long& outNpcTableCost); + } diff --git a/sfall/Modules/Inventory.cpp b/sfall/Modules/Inventory.cpp index d18a1e91..9f7aa938 100644 --- a/sfall/Modules/Inventory.cpp +++ b/sfall/Modules/Inventory.cpp @@ -24,6 +24,7 @@ #include "HeroAppearance.h" #include "HookScripts.h" #include "LoadGameHook.h" +#include "HookScripts\MiscHs.h" #include "..\Game\inventory.h" #include "..\Game\items.h" @@ -618,12 +619,66 @@ end: } } +static long CalculateSuggestedMoveCount(fo::GameObject* item, long maxQuantity, bool fromPlayer, bool fromInventory) { + // This is an exact copy of logic from https://github.com/alexbatalov/fallout2-ce/pull/311 + if (item->protoId == fo::PID_BOTTLE_CAPS && !*fo::ptr::dialog_target_is_party) { + // Calculate change money automatically + long totalCostPlayer, totalCostNpc; + BarterPriceHook_GetLastCosts(totalCostPlayer, totalCostNpc); + // Actor's balance: negative - the actor must add money to balance the tables and vice versa + long balance = fromPlayer ? totalCostPlayer - totalCostNpc : totalCostNpc - totalCostPlayer; + if ((balance < 0 && fromInventory) || (balance > 0 && !fromInventory)) { + return min(std::abs(balance), maxQuantity); + } + } + return 1; +} + +static bool itemCounterDefaultMax; +static bool itemCounterAutoCaps; +static long __fastcall CalculateDefaultMoveCount(DWORD maybeItem, DWORD retAddr, DWORD maxValue) { + maxValue = min(maxValue, 99999); // capped like in vanilla + if ((GetLoopFlags() & BARTER) != 0) { + if (itemCounterAutoCaps && maxValue > 1) { + bool fromPlayer; + bool fromInventory; + switch (retAddr) { + case 0x474F96: // barter_move_inventory + fromPlayer = true; + fromInventory = true; + break; + case 0x475015: // barter_move_inventory + fromPlayer = false; + fromInventory = true; + break; + case 0x475261: // barter_move_from_table_inventory + fromPlayer = true; + fromInventory = false; + break; + case 0x4752DE: // barter_move_from_table_inventory + fromPlayer = false; + fromInventory = false; + break; + default: + return 1; + } + // maybeItem may not contain object pointer in all cases, but it does in all 4 from above. + return CalculateSuggestedMoveCount((fo::GameObject*)maybeItem, maxValue, fromPlayer, fromInventory); + } + return 1; + } + return itemCounterDefaultMax ? maxValue : 1; +} + static void __declspec(naked) do_move_timer_hack() { __asm { - mov ebx, 1; - call GetLoopFlags; - test eax, BARTER; - cmovz ebx, ebp; // set max when not in barter + push ecx; + push ebp; // max + mov edx, dword ptr [esp + 32]; // return address + mov ecx, dword ptr [esp + 20]; // item, potentially + call CalculateDefaultMoveCount; + mov ebx, eax; + pop ecx; retn; } } @@ -753,7 +808,9 @@ void Inventory::init() { skipFromContainer = IniReader::GetConfigInt("Input", "FastMoveFromContainer", 0); } - if (IniReader::GetConfigInt("Misc", "ItemCounterDefaultMax", 0)) { + itemCounterDefaultMax = (IniReader::GetConfigInt("Misc", "ItemCounterDefaultMax", 0) != 0); + itemCounterAutoCaps = (IniReader::GetConfigInt("Misc", "ItemCounterAutoCaps", 0) != 0); + if (itemCounterDefaultMax || itemCounterAutoCaps) { MakeCall(0x4768A3, do_move_timer_hack); }