diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index dc5af8abc..4d86e1dde 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -662,6 +662,9 @@ 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 caps auto-balancing: when dragging caps between tables in the barter screen, 'Move Items' window will be shown with correct number pre-filled that balances the tables +ItemCounterAutoCaps=0 + ;Set to 1 to leave the music playing in dialogue with talking heads EnableMusicInDialogue=0 diff --git a/sfall/FalloutEngine/Fallout2.h b/sfall/FalloutEngine/Fallout2.h index 74e985e27..a7ef739c1 100644 --- a/sfall/FalloutEngine/Fallout2.h +++ b/sfall/FalloutEngine/Fallout2.h @@ -23,6 +23,7 @@ */ #include "Enums.h" +#include "GamePids.h" #include "FunctionOffsets.h" #include "Structs.h" #include "EngineUtils.h" diff --git a/sfall/FalloutEngine/Variables_def.h b/sfall/FalloutEngine/Variables_def.h index 9ebfb0746..be3bbc604 100644 --- a/sfall/FalloutEngine/Variables_def.h +++ b/sfall/FalloutEngine/Variables_def.h @@ -16,7 +16,7 @@ VAR_(bckgnd, BYTE*) VAR_(black_palette, DWORD) VAR_(BlueColor, BYTE) VAR_(bottom_line, DWORD) -VAR_(btable, DWORD) +VAR_(btable, fo::GameObject*) VAR_(btncnt, DWORD) VAR_(buf_length_2, long) VARD(cap, fo::AIcap) // dynamic array @@ -198,7 +198,7 @@ VARA(procTableStrs, const char*, (int)fo::Scripts::ScriptProc::coun VAR_(proto_main_msg_file, fo::MessageList) VARA(proto_msg_files, fo::MessageList, 6) // array of 6 elements VARA(protoLists, fo::ProtoList, 11) -VAR_(ptable, DWORD) +VAR_(ptable, fo::GameObject*) VAR_(pud, DWORD) VAR_(queue, fo::Queue*) VAR_(quick_done, DWORD) @@ -222,7 +222,7 @@ VAR_(speech_volume, DWORD) VARA(square, DWORD*, 3) // use (square && 0xFFF) to get ground fid, and ((square >> 16) && 0xFFF) to get roof VAR_(square_rect, fo::SquareRect) // _square_y VAR_(squares, DWORD*) -VARA(stack, DWORD, 10) +VARA(stack, fo::GameObject*, 10) VARA(stack_offset, DWORD, 10) VARA(stat_data, fo::StatInfo, fo::STAT_real_max_stat) VAR_(stat_flag, DWORD) @@ -232,7 +232,7 @@ VAR_(Tag_, DWORD) VAR_(tag_skill, DWORD) VAR_(target_curr_stack, DWORD) VAR_(target_pud, DWORD*) -VARA(target_stack, DWORD, 10) +VARA(target_stack, fo::GameObject*, 10) VARA(target_stack_offset, DWORD, 10) VAR_(target_str, DWORD) VAR_(target_xpos, DWORD) diff --git a/sfall/Modules/HookScripts/MiscHs.cpp b/sfall/Modules/HookScripts/MiscHs.cpp index 9cbc70a4a..c97e559d9 100644 --- a/sfall/Modules/HookScripts/MiscHs.cpp +++ b/sfall/Modules/HookScripts/MiscHs.cpp @@ -9,6 +9,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) { @@ -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::var::btable; + fo::GameObject* bTable = fo::var::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::var::ptable; + fo::GameObject* pTable = fo::var::ptable; args[6] = (DWORD)pTable; long pcCost = 0; @@ -52,6 +55,11 @@ static DWORD __fastcall BarterPriceHook_Script(fo::GameObject* source, fo::GameO cost = rets[0]; // new cost for npc } } + if (isPCHook) { + lastTableCostPC = cost; + } else { + lastTableCostNPC = cost; + } EndHook(); return cost; } @@ -70,7 +78,6 @@ static void __declspec(naked) BarterPriceHook() { } } -static DWORD offersGoodsCost; // keep last cost for pc static void __declspec(naked) PC_BarterPriceHook() { __asm { push edx; @@ -82,18 +89,27 @@ static void __declspec(naked) PC_BarterPriceHook() { call BarterPriceHook_Script; pop ecx; pop edx; - mov offersGoodsCost, 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::var::ptable); + outNpcTableCost = fo::func::barter_compute_value(fo::var::obj_dude, fo::var::target_stack[0]); + return; + } + outPcTableCost = lastTableCostPC; + outNpcTableCost = lastTableCostNPC; +} + static fo::GameObject* sourceSkillOn = nullptr; void SourceUseSkillOnInit() { sourceSkillOn = fo::var::obj_dude; } diff --git a/sfall/Modules/HookScripts/MiscHs.h b/sfall/Modules/HookScripts/MiscHs.h index f300ef731..7db609744 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 e1d045196..f72ca1cff 100644 --- a/sfall/Modules/Inventory.cpp +++ b/sfall/Modules/Inventory.cpp @@ -22,6 +22,7 @@ #include "..\Translate.h" #include "LoadGameHook.h" +#include "HookScripts\MiscHs.h" #include "..\Game\inventory.h" #include "..\Game\items.h" @@ -618,12 +619,67 @@ static void __declspec(naked) do_move_timer_hook() { } } +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::var::dialog_target_is_party) { + // Calculate change money automatically + long totalCostPlayer; + long 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; } } @@ -755,7 +811,9 @@ void Inventory::init() { skipFromContainer = IniReader::GetConfigInt("Input", "FastMoveFromContainer", 0); } - if (IniReader::GetConfigInt("Misc", "ItemCounterDefaultMax", 0)) { + itemCounterDefaultMax = IniReader::GetConfigInt("Misc", "ItemCounterDefaultMax", 0); + itemCounterAutoCaps = IniReader::GetConfigInt("Misc", "ItemCounterAutoCaps", 0); + if (itemCounterDefaultMax || itemCounterAutoCaps) { MakeCall(0x4768A3, do_move_timer_hack); }