From 22aed0e6638ce175cb22a8cfc9bb79150b0c4422 Mon Sep 17 00:00:00 2001 From: phobos2077 Date: Tue, 21 May 2024 15:31:37 +0200 Subject: [PATCH 1/2] Auto balance barter tables when dragging caps around --- artifacts/ddraw.ini | 3 ++ sfall/FalloutEngine/Fallout2.h | 1 + sfall/FalloutEngine/Variables_def.h | 8 ++-- sfall/Modules/HookScripts/MiscHs.cpp | 26 ++++++++-- sfall/Modules/HookScripts/MiscHs.h | 2 + sfall/Modules/Inventory.cpp | 71 ++++++++++++++++++++++++++-- 6 files changed, 97 insertions(+), 14 deletions(-) diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index dc5af8abc..0f0f17232 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 set default value in 'Move Items' window to amount of caps that will balance 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..91f4d87cd 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 BarterGetTableCosts(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..2a5a28299 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 BarterGetTableCosts(long* outPcTableCost, long* outNpcTableCost); + } diff --git a/sfall/Modules/Inventory.cpp b/sfall/Modules/Inventory.cpp index e1d045196..c6f9cb44a 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,70 @@ static void __declspec(naked) do_move_timer_hook() { } } +static long CalculateSuggestedMoveCount(fo::GameObject* item, long maxQuantity, bool fromPlayer, bool fromInventory) { + if (maxQuantity <= 1) { + return maxQuantity; + } + long suggestedValue = 1; + if (item->protoId == fo::PID_BOTTLE_CAPS && !fo::var::dialog_target_is_party) { + // Calculate change money automatically + long totalCostPlayer; + long totalCostNpc; + BarterGetTableCosts(&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) ) { + suggestedValue = min(std::abs(balance), maxQuantity); + } + } + return suggestedValue; +} + +static bool itemCounterDefaultMax; +static bool itemCounterAutoCaps; +static long __fastcall CalculateDefaultMoveCount(fo::GameObject* item, DWORD retAddr, DWORD maxValue) { + maxValue = min(maxValue, 99999); // capped like in vanilla + if ((GetLoopFlags() & BARTER) != 0) { + if (itemCounterAutoCaps && maxValue > 0) { + 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; + } + return CalculateSuggestedMoveCount(item, 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 + pushadc; + push ebp; // max + mov edx, dword ptr[esp + 40]; // return address + mov ecx, dword ptr[esp + 28]; // item + call CalculateDefaultMoveCount; + mov ebx, eax; + popadc; retn; } } @@ -755,7 +814,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); } From 522219d72b4b2ee6c5f7fae20d931a2fb02b9385 Mon Sep 17 00:00:00 2001 From: phobos2077 Date: Tue, 21 May 2024 16:06:33 +0200 Subject: [PATCH 2/2] Code cleanup and better comments --- artifacts/ddraw.ini | 2 +- sfall/Modules/HookScripts/MiscHs.cpp | 10 +++++----- sfall/Modules/HookScripts/MiscHs.h | 2 +- sfall/Modules/Inventory.cpp | 29 +++++++++++++--------------- 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 0f0f17232..4d86e1dde 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -662,7 +662,7 @@ 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 set default value in 'Move Items' window to amount of caps that will balance the tables +;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 diff --git a/sfall/Modules/HookScripts/MiscHs.cpp b/sfall/Modules/HookScripts/MiscHs.cpp index 91f4d87cd..c97e559d9 100644 --- a/sfall/Modules/HookScripts/MiscHs.cpp +++ b/sfall/Modules/HookScripts/MiscHs.cpp @@ -100,14 +100,14 @@ static void __declspec(naked) OverrideCost_BarterPriceHook() { } } -void BarterGetTableCosts(long* outPcTableCost, long* outNpcTableCost) { +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]); + 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; + outPcTableCost = lastTableCostPC; + outNpcTableCost = lastTableCostNPC; } static fo::GameObject* sourceSkillOn = nullptr; diff --git a/sfall/Modules/HookScripts/MiscHs.h b/sfall/Modules/HookScripts/MiscHs.h index 2a5a28299..7db609744 100644 --- a/sfall/Modules/HookScripts/MiscHs.h +++ b/sfall/Modules/HookScripts/MiscHs.h @@ -21,6 +21,6 @@ void Inject_RollCheckHook(); long PerceptionRangeHook_Invoke(fo::GameObject* watcher, fo::GameObject* target, long type, long result); -void BarterGetTableCosts(long* outPcTableCost, long* outNpcTableCost); +void BarterPriceHook_GetLastCosts(long& outPcTableCost, long& outNpcTableCost); } diff --git a/sfall/Modules/Inventory.cpp b/sfall/Modules/Inventory.cpp index c6f9cb44a..f72ca1cff 100644 --- a/sfall/Modules/Inventory.cpp +++ b/sfall/Modules/Inventory.cpp @@ -620,31 +620,27 @@ static void __declspec(naked) do_move_timer_hook() { } static long CalculateSuggestedMoveCount(fo::GameObject* item, long maxQuantity, bool fromPlayer, bool fromInventory) { - if (maxQuantity <= 1) { - return maxQuantity; - } - long suggestedValue = 1; + // 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; - BarterGetTableCosts(&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) ) { - suggestedValue = min(std::abs(balance), maxQuantity); + if ((balance < 0 && fromInventory) || (balance > 0 && !fromInventory)) { + return min(std::abs(balance), maxQuantity); } } - return suggestedValue; + return 1; } static bool itemCounterDefaultMax; static bool itemCounterAutoCaps; -static long __fastcall CalculateDefaultMoveCount(fo::GameObject* item, DWORD retAddr, DWORD maxValue) { +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 > 0) { + if (itemCounterAutoCaps && maxValue > 1) { bool fromPlayer; bool fromInventory; switch (retAddr) { @@ -667,7 +663,8 @@ static long __fastcall CalculateDefaultMoveCount(fo::GameObject* item, DWORD ret default: return 1; } - return CalculateSuggestedMoveCount(item, maxValue, fromPlayer, fromInventory); + // 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; } @@ -676,13 +673,13 @@ static long __fastcall CalculateDefaultMoveCount(fo::GameObject* item, DWORD ret static void __declspec(naked) do_move_timer_hack() { __asm { - pushadc; + push ecx; push ebp; // max - mov edx, dword ptr[esp + 40]; // return address - mov ecx, dword ptr[esp + 28]; // item + mov edx, dword ptr[esp + 32]; // return address + mov ecx, dword ptr[esp + 20]; // item, potentially call CalculateDefaultMoveCount; mov ebx, eax; - popadc; + pop ecx; retn; } }