using UnityEngine; namespace Ashwild.Inventory { /// /// The single source of truth for what happens when a stack meets another stack. Both rules below /// are pure — they know nothing about containers, networking or authority — so the exact same /// decision runs for an inventory-to-inventory drag (client-side), a chest-to-chest drag /// (server-side) and an inventory-to-chest drag (server-side, against the payload in flight). /// That is why a chest transfer behaves identically to moving an item inside the inventory. /// /// There are exactly two operations, because the player expresses exactly two intents: /// when they aim at a precise destination, and when they /// let the game find a free spot (quick transfer, deposit-all, pickups). /// public static class SlotTransfer { /// /// Moves a stack onto a destination the player explicitly aimed at. Empty destination = the whole /// stack moves; same mergeable item = merge and leave the remainder in the source; anything else /// (different item, non-stackable, uses-tracked, or a full stack) = swap the two slots. Uses ride /// along with the content in every branch, so a worn item stays worn. /// Returns whether anything actually changed. /// public static bool Move(ref SlotContent source, ref SlotContent target) { if (source.IsEmpty) return false; if (target.IsEmpty) { target = source; source = SlotContent.Empty; return true; } if (CanMerge(source, target)) { int space = target.Item.MaxStackSize - target.Quantity; int moved = Mathf.Min(source.Quantity, space); target.Quantity += moved; source.Quantity -= moved; if (source.Quantity <= 0) source = SlotContent.Empty; return moved > 0; } SlotContent temp = source; source = target; target = temp; return true; } /// /// Auto-placement: pushes as much of into the target as it can accept, /// and never swaps — the player did not pick this slot, so displacing what is already there would /// be wrong. An empty target takes one instance of a uses-tracked item (each keeps its own uses /// bar) or up to a full stack otherwise. Returns whether the target changed, leaving the leftover /// in for the caller to keep placing. /// public static bool TryStack(ref SlotContent incoming, ref SlotContent target) { if (incoming.IsEmpty) return false; ItemData item = incoming.Item; if (target.IsEmpty) { int toPlace = item.HasUses || !item.IsStackable ? 1 : Mathf.Min(incoming.Quantity, item.MaxStackSize); target = SlotContent.Of(item, toPlace, incoming.Uses); incoming.Quantity -= toPlace; if (incoming.Quantity <= 0) incoming = SlotContent.Empty; return true; } if (!CanMerge(incoming, target)) return false; int space = target.Item.MaxStackSize - target.Quantity; int moved = Mathf.Min(incoming.Quantity, space); if (moved <= 0) return false; target.Quantity += moved; incoming.Quantity -= moved; if (incoming.Quantity <= 0) incoming = SlotContent.Empty; return true; } /// /// Two stacks merge only when they are the same stackable item that does not track uses and the /// target still has room. Uses-tracked items never merge: each instance owns its uses bar, so /// merging them would silently destroy one item's wear. /// private static bool CanMerge(SlotContent source, SlotContent target) { ItemData item = source.Item; return target.Item == item && item.IsStackable && !item.HasUses && target.Quantity < item.MaxStackSize; } } }