From dae9ea1be0ff659aecbe59086b117b8a4ed85b23 Mon Sep 17 00:00:00 2001 From: Knack117 Date: Mon, 26 Jan 2026 22:20:18 -0500 Subject: [PATCH] Release v1.0.4 Add Shift+Right Click Trade window support with auto-fill max quantity. --- QuickTransfer.cs | 82 ++++++++++++++++++++++++++++++++++++++++---- QuickTransfer.csproj | 6 ++-- QuickTransfer.json | 4 +-- pluginmaster.json | 4 +-- 4 files changed, 82 insertions(+), 14 deletions(-) diff --git a/QuickTransfer.cs b/QuickTransfer.cs index 40cf5a2..347beef 100644 --- a/QuickTransfer.cs +++ b/QuickTransfer.cs @@ -1148,7 +1148,7 @@ public sealed unsafe class Plugin : IDalamudPlugin // Then we can recognize the correct InputNumeric without relying on prompt text. private uint pendingSplitExpectedMax; private long pendingSplitExpectedUntilMs; - private enum PendingNumericKind { None, Store, Remove, Move, Split } + private enum PendingNumericKind { None, Store, Remove, Move, Split, Trade } private PendingNumericKind pendingNumericKind; private long lastShiftSeenMs; @@ -1287,6 +1287,7 @@ public sealed unsafe class Plugin : IDalamudPlugin RemoveFromCompanyChest, Split, Sort, + Trade, } private static readonly string[] ArmouryAddonNames = @@ -1899,6 +1900,23 @@ public sealed unsafe class Plugin : IDalamudPlugin lastActionTickMs = now; if (Configuration.DebugMode) Log.Information($"[QuickTransfer] ({mode} + RClick) Selected context action '{chosenText}' (idx={chosenIndex}) via OpenForItemSlot."); + + // Set up Trade quantity auto-confirm if Trade was selected + if (mode == ModifierMode.Shift && + chosenText.Length > 0 && + ContextLabelMatches(AutoContextAction.Trade, chosenText) && + IsTradeOpen()) + { + pendingCompanyChestNumericConfirmUntilMs = now + 1500; + pendingCompanyChestNumericConfirmAttempts = 0; + pendingCompanyChestNumericArmed = true; + pendingNumericKind = PendingNumericKind.Trade; + pendingCompanyChestNumericValueSet = false; + pendingCompanyChestNumericValueSetAtMs = 0; + pendingCompanyChestNumericDesired = 0; + pendingCompanyChestNumericHalf = false; + ArmSuppressInputNumeric(now, 1500); + } } else if (Configuration.DebugMode && mode == ModifierMode.Ctrl) { @@ -2014,7 +2032,12 @@ public sealed unsafe class Plugin : IDalamudPlugin lastAltSeenMs = now; // Quantity prompt auto-confirm (best effort). - if (Configuration.AutoConfirmCompanyChestQuantity && + // Trade and Split always auto-confirm; Company Chest respects the config setting. + var shouldAutoConfirm = pendingNumericKind == PendingNumericKind.Trade || + pendingNumericKind == PendingNumericKind.Split || + (Configuration.AutoConfirmCompanyChestQuantity && pendingNumericKind != PendingNumericKind.None); + + if (shouldAutoConfirm && pendingNumericKind != PendingNumericKind.None && pendingCompanyChestNumericConfirmUntilMs > 0 && now <= pendingCompanyChestNumericConfirmUntilMs) @@ -2422,6 +2445,21 @@ public sealed unsafe class Plugin : IDalamudPlugin ? 3000 : 1500; ArmSuppressContextMenu(now, suppressMs); + if (pending.Value.Mode == ModifierMode.Shift && + chosenText.Length > 0 && + ContextLabelMatches(AutoContextAction.Trade, chosenText)) + { + // Trade: auto-confirm max quantity when InputNumeric appears + pendingCompanyChestNumericConfirmUntilMs = now + 1500; + pendingCompanyChestNumericConfirmAttempts = 0; + pendingCompanyChestNumericArmed = true; + pendingNumericKind = PendingNumericKind.Trade; + pendingCompanyChestNumericValueSet = false; + pendingCompanyChestNumericValueSetAtMs = 0; + pendingCompanyChestNumericDesired = 0; + pendingCompanyChestNumericHalf = false; + ArmSuppressInputNumeric(now, 1500); + } if (Configuration.EnableCompanyChest && pending.Value.Mode == ModifierMode.Shift && chosenText.Length > 0 && @@ -2969,8 +3007,8 @@ public sealed unsafe class Plugin : IDalamudPlugin // Single-pass: decode each label once, record first match per action. var foundAny = false; - int removeIdx = -1, addIdx = -1, placeIdx = -1, returnIdx = -1, entrustIdx = -1, retrieveIdx = -1, companyRemoveIdx = -1, splitIdx = -1; - string? removeTxt = null, addTxt = null, placeTxt = null, returnTxt = null, entrustTxt = null, retrieveTxt = null, companyRemoveTxt = null, splitTxt = null; + int removeIdx = -1, addIdx = -1, placeIdx = -1, returnIdx = -1, entrustIdx = -1, retrieveIdx = -1, companyRemoveIdx = -1, splitIdx = -1, tradeIdx = -1; + string? removeTxt = null, addTxt = null, placeTxt = null, returnTxt = null, entrustTxt = null, retrieveTxt = null, companyRemoveTxt = null, splitTxt = null, tradeTxt = null; var max = Math.Min(agent->ContextItemCount, 64); for (var i = 0; i < max; i++) @@ -3039,6 +3077,13 @@ public sealed unsafe class Plugin : IDalamudPlugin { splitIdx = i; splitTxt = text; + continue; + } + + if (tradeIdx < 0 && ContextLabelMatches(AutoContextAction.Trade, text)) + { + tradeIdx = i; + tradeTxt = text; } } @@ -3048,6 +3093,7 @@ public sealed unsafe class Plugin : IDalamudPlugin var saddlebagOpen = IsSaddlebagOpen(); var retainerOpen = IsRetainerOpen(); var companyChestOpen = IsCompanyChestOpen(); + var tradeOpen = IsTradeOpen(); // Choose the best action that exists in the menu. // @@ -3071,6 +3117,11 @@ public sealed unsafe class Plugin : IDalamudPlugin { chosen = splitIdx >= 0 ? (splitIdx, splitTxt) : (-1, (string?)null); } + else if (mode == ModifierMode.Shift && tradeOpen) + { + // Trade window: prioritize Trade action when Trade window is open + chosen = tradeIdx >= 0 ? (tradeIdx, tradeTxt) : (-1, (string?)null); + } else if (mode == ModifierMode.Shift && companyChestOpen && Configuration.EnableCompanyChest) { chosen = companyRemoveIdx >= 0 ? (companyRemoveIdx, companyRemoveTxt) : (-1, (string?)null); @@ -3122,11 +3173,11 @@ public sealed unsafe class Plugin : IDalamudPlugin GenerateCallback(contextMenuAddon, 0, chosen.idx, 0U, 0, 0); - // Some actions (notably Split) can be cancelled if we close the menu immediately. + // Some actions (notably Split and Trade) can be cancelled if we close the menu immediately. // Delay the close slightly to allow the follow-up UI (InputNumeric) to spawn. - if (chosen.txt != null && ContextLabelMatches(AutoContextAction.Split, chosen.txt)) + if (chosen.txt != null && (ContextLabelMatches(AutoContextAction.Split, chosen.txt) || ContextLabelMatches(AutoContextAction.Trade, chosen.txt))) { - // Don't close immediately: on some setups this cancels Split before InputNumeric opens. + // Don't close immediately: on some setups this cancels the action before InputNumeric opens. // We'll keep the menu invisible (via suppression) and close it later as a cleanup. pendingCloseContextMenuAtMs = Environment.TickCount64 + 3000; } @@ -4457,6 +4508,15 @@ public sealed unsafe class Plugin : IDalamudPlugin return false; if (kind == PendingNumericKind.Remove && !prompt.Contains("remove", StringComparison.OrdinalIgnoreCase)) return false; + // Trade dialogs may be localized; if we're in Trade mode and Trade window is open, accept it + // (similar to how Split works - we trust the context rather than requiring exact prompt text) + if (kind == PendingNumericKind.Trade && !prompt.Contains("trade", StringComparison.OrdinalIgnoreCase)) + { + // Fallback: if Trade window is open and we're expecting Trade, accept it anyway + // (prompt might be localized or say "How many would you like to trade?" etc.) + if (!IsTradeOpen()) + return false; + } if (minValue->Type != AtkValueType.UInt || maxValue->Type != AtkValueType.UInt || defaultValue->Type != AtkValueType.UInt) return false; @@ -5111,6 +5171,9 @@ public sealed unsafe class Plugin : IDalamudPlugin private static bool IsCompanyChestOpen() => IsAddonVisibleAnyIndex(FreeCompanyChestAddonName); + private static bool IsTradeOpen() + => IsAddonVisibleAnyIndex("Trade") || IsAddonVisibleAnyIndex("TradeWindow"); + private static bool IsCompanyChestType(FFXIVClientStructs.FFXIV.Client.Game.InventoryType inventoryType) { var name = Enum.GetName(typeof(FFXIVClientStructs.FFXIV.Client.Game.InventoryType), inventoryType); @@ -5224,6 +5287,11 @@ public sealed unsafe class Plugin : IDalamudPlugin t.Equals("Sort", StringComparison.OrdinalIgnoreCase) || t.StartsWith("Sort", StringComparison.OrdinalIgnoreCase), + AutoContextAction.Trade => + t.Equals("Trade", StringComparison.OrdinalIgnoreCase) || + t.StartsWith("Trade", StringComparison.OrdinalIgnoreCase) || + (Has(t, "Trade") && Has(t, "Item")), + _ => false, }; } diff --git a/QuickTransfer.csproj b/QuickTransfer.csproj index b692514..38b3b71 100644 --- a/QuickTransfer.csproj +++ b/QuickTransfer.csproj @@ -8,9 +8,9 @@ QuickTransfer Library false - 1.0.3 - 1.0.3.0 - 1.0.3.0 + 1.0.4 + 1.0.4.0 + 1.0.4.0