diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs b/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs
index 33ca375e57..6f18117f1f 100644
--- a/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs
+++ b/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs
@@ -49,6 +49,9 @@ namespace BrewMonster.Scripts.Managers
[SerializeField] private Button splitDecreaseButton;
[SerializeField] private Button splitMaxButton;
+ [Header("Stack combine — merge into another stack (C++ inventory drag-merge, assign in Inspector)")]
+ [SerializeField] private Button combineStackButton;
+
private int _splitAmount = 1;
private int _splitMaxAmount = 1;
@@ -126,6 +129,7 @@ namespace BrewMonster.Scripts.Managers
view = new InventoryView();
WireBagTabButtons();
WireSplitUI();
+ WireCombineUI();
//if (currentDragImage == null)
//{
@@ -207,6 +211,15 @@ namespace BrewMonster.Scripts.Managers
}
}
+ private void WireCombineUI()
+ {
+ if (combineStackButton != null)
+ {
+ combineStackButton.onClick.RemoveAllListeners();
+ combineStackButton.onClick.AddListener(() => CombineSelectedStack());
+ }
+ }
+
private void ShowSplitPanel(bool show)
{
if (splitPanelRoot != null)
@@ -777,6 +790,100 @@ namespace BrewMonster.Scripts.Managers
return true;
}
+ ///
+ /// Merge from the selected slot into another stack of the same template (C++ CDlgInventory::ExchangeItem
+ /// branch that calls c2s_CmdMoveIvtrItem when dest matches and pile limit allows).
+ /// Picks the lowest-index compatible destination with free stack space.
+ ///
+ public bool CombineSelectedStack()
+ {
+ if (currentSelectedItem == null)
+ {
+ Debug.LogWarning("[InventoryUI] CombineSelectedStack: no item selected");
+ return false;
+ }
+
+ if (currentSelectedPackage != PKG_INVENTORY)
+ {
+ Debug.LogWarning($"[InventoryUI] CombineSelectedStack: unsupported package {currentSelectedPackage} (only PKG_INVENTORY supported)");
+ return false;
+ }
+
+ if (!TryGetInventoryMergeTarget(currentSelectedSlot, currentSelectedItem, out int dstSlot, out int moveAmount))
+ {
+ Debug.LogWarning("[InventoryUI] CombineSelectedStack: no merge target with free stack space");
+ return false;
+ }
+
+ UnityGameSession.RequestMoveIvtrItem((byte)currentSelectedSlot, (byte)dstSlot, (uint)moveAmount);
+ RefreshAll();
+ return true;
+ }
+
+ ///
+ /// Pile cap for stacking: instance (from essence pile_num_max) is authoritative;
+ /// may still be 1 if element reflection misses field names.
+ ///
+ private static int GetEffectivePileLimitForStack(int tid, EC_IvtrItem a, EC_IvtrItem b)
+ {
+ int p = Math.Max(1, EC_IvtrItem.GetPileLimit(tid));
+ if (a != null)
+ p = Math.Max(p, Math.Max(1, a.GetPileLimitInstance()));
+ if (b != null)
+ p = Math.Max(p, Math.Max(1, b.GetPileLimitInstance()));
+ return p;
+ }
+
+ /// First eligible slot (lowest index) that can accept part or all of .
+ private static bool TryGetInventoryMergeTarget(int srcSlot, EC_IvtrItem srcItem, out int dstSlot, out int moveAmount)
+ {
+ dstSlot = -1;
+ moveAmount = 0;
+ if (srcItem == null || srcItem.IsFrozen())
+ return false;
+
+ int tid = srcItem.GetTemplateID();
+ int srcCount = srcItem.GetCount();
+ if (srcCount < 1)
+ return false;
+
+ var host = CECGameRun.Instance?.GetHostPlayer();
+ var inv = host?.GetInventory(PKG_INVENTORY);
+ if (inv == null)
+ return false;
+
+ int size = inv.GetSize();
+ for (int i = 0; i < size; i++)
+ {
+ if (i == srcSlot)
+ continue;
+
+ var dst = inv.GetItem(i, false);
+ if (dst == null || dst.IsFrozen())
+ continue;
+ if (dst.GetTemplateID() != tid)
+ continue;
+
+ int pile = GetEffectivePileLimitForStack(tid, srcItem, dst);
+ if (pile <= 1)
+ continue;
+
+ int room = pile - dst.GetCount();
+ if (room <= 0)
+ continue;
+
+ int move = Math.Min(srcCount, room);
+ if (move <= 0)
+ continue;
+
+ dstSlot = i;
+ moveAmount = move;
+ return true;
+ }
+
+ return false;
+ }
+
private int FindEmptySlotInPackage(byte package)
{
var host = CECGameRun.Instance?.GetHostPlayer();
@@ -1200,7 +1307,10 @@ namespace BrewMonster.Scripts.Managers
{
EC_UIUtility.ShowPanel(detailPanelRoot.gameObject, show);
if (!show)
+ {
RefreshSplitControlsVisibility(0, null);
+ RefreshCombineControlsVisibility(0, null);
+ }
}
///
@@ -1245,6 +1355,20 @@ namespace BrewMonster.Scripts.Managers
}
}
+ private bool CanShowCombineControls(byte package, EC_IvtrItem item)
+ {
+ if (item == null || package != PKG_INVENTORY)
+ return false;
+ return TryGetInventoryMergeTarget(currentSelectedSlot, item, out _, out _);
+ }
+
+ private void RefreshCombineControlsVisibility(byte package, EC_IvtrItem item)
+ {
+ bool canCombine = CanShowCombineControls(package, item);
+ if (combineStackButton != null)
+ combineStackButton.gameObject.SetActive(canCombine);
+ }
+
private Button GetButtonForSlot(byte package, int slot)
{
List