using System; using System.Collections.Generic; using System.IO; using System.Linq; using Server.Network; using Server.Utilities; using QueuePool = Server.Utilities.RefPool>; namespace Server.Items { public delegate void OnItemConsumed(Item item, int amount); public delegate int CheckItemGroup(Item a, Item b); public delegate void ContainerSnoopHandler(Container cont, Mobile from); public class Container : Item { private static readonly QueuePool m_QueuePool = new QueuePool(QueueRef.Generate, 2, 5); private static readonly List m_FindItemsList = new List(); private ContainerData m_ContainerData; private int m_DropSound; private int m_GumpID; internal List m_Items; private int m_MaxItems; private int m_TotalGold; private int m_TotalItems; private int m_TotalWeight; public Container(int itemID) : base(itemID) { m_GumpID = -1; m_DropSound = -1; m_MaxItems = -1; UpdateContainerData(); } public Container(Serial serial) : base(serial) { } public static ContainerSnoopHandler SnoopHandler { get; set; } public ContainerData ContainerData { get { if (m_ContainerData == null) { UpdateContainerData(); } return m_ContainerData; } set => m_ContainerData = value; } [CommandProperty(AccessLevel.GameMaster)] public override int ItemID { get => base.ItemID; set { var oldID = ItemID; base.ItemID = value; if (ItemID != oldID) { UpdateContainerData(); } } } [CommandProperty(AccessLevel.GameMaster)] public int GumpID { get => m_GumpID == -1 ? DefaultGumpID : m_GumpID; set => m_GumpID = value; } [CommandProperty(AccessLevel.GameMaster)] public int DropSound { get => m_DropSound == -1 ? DefaultDropSound : m_DropSound; set => m_DropSound = value; } [CommandProperty(AccessLevel.GameMaster)] public int MaxItems { get => m_MaxItems == -1 ? DefaultMaxItems : m_MaxItems; set { m_MaxItems = value; InvalidateProperties(); } } [CommandProperty(AccessLevel.GameMaster)] public virtual int MaxWeight { get { if (Parent is Container container && container.MaxWeight == 0) { return 0; } return DefaultMaxWeight; } } [CommandProperty(AccessLevel.GameMaster)] public bool LiftOverride { get; set; } public virtual Rectangle2D Bounds => ContainerData.Bounds; [CommandProperty(AccessLevel.GameMaster)] public virtual int DefaultGumpID => ContainerData.GumpID; [CommandProperty(AccessLevel.GameMaster)] public virtual int DefaultDropSound => ContainerData.DropSound; public virtual int DefaultMaxItems => GlobalMaxItems; public virtual int DefaultMaxWeight => GlobalMaxWeight; public virtual bool IsDecoContainer => !Movable && !IsLockedDown && !IsSecure && Parent == null && !LiftOverride; public static int GlobalMaxItems { get; set; } = 125; public static int GlobalMaxWeight { get; set; } = 400; public virtual bool DisplaysContent => true; public List Openers { get; set; } public virtual bool IsPublicContainer => false; public virtual void UpdateContainerData() { ContainerData = ContainerData.GetData(ItemID); } public virtual int GetDroppedSound(Item item) { var dropSound = item.GetDropSound(); return dropSound != -1 ? dropSound : DropSound; } public override void OnSnoop(Mobile from) { SnoopHandler?.Invoke(this, from); } public override bool CheckLift(Mobile from, Item item, ref LRReason reject) { if (from.AccessLevel < AccessLevel.GameMaster && IsDecoContainer) { reject = LRReason.CannotLift; return false; } return base.CheckLift(from, item, ref reject); } public override bool CheckItemUse(Mobile from, Item item) { if (item != this && from.AccessLevel < AccessLevel.GameMaster && IsDecoContainer) { from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that. return false; } return base.CheckItemUse(from, item); } public bool CheckHold(Mobile m, Item item, bool message) => CheckHold(m, item, message, true, 0, 0); public bool CheckHold(Mobile m, Item item, bool message, bool checkItems) => CheckHold(m, item, message, checkItems, 0, 0); public virtual bool CheckHold(Mobile m, Item item, bool message, bool checkItems, int plusItems, int plusWeight) { if (m == null || m.AccessLevel < AccessLevel.GameMaster) { if (IsDecoContainer) { if (message) { SendCantStoreMessage(m, item); } return false; } var maxItems = MaxItems; if (checkItems && maxItems != 0 && TotalItems + plusItems + item.TotalItems + (item.IsVirtualItem ? 0 : 1) > maxItems) { if (message) { SendFullItemsMessage(m, item); } return false; } if (MaxWeight != 0 && TotalWeight + plusWeight + item.TotalWeight + item.PileWeight > MaxWeight) { if (message) { SendFullWeightMessage(m, item); } return false; } } var parent = Parent; while (parent != null) { if (parent is Container container) { return container.CheckHold(m, item, message, checkItems, plusItems, plusWeight); } if (!(parent is Item parentItem)) { break; } parent = parentItem.Parent; } return true; } public virtual void SendFullItemsMessage(Mobile to, Item item) { to.SendMessage("That container cannot hold more items."); } public virtual void SendFullWeightMessage(Mobile to, Item item) { to.SendMessage("That container cannot hold more weight."); } public virtual void SendCantStoreMessage(Mobile to, Item item) { to.SendLocalizedMessage(500176); // That is not your container, you can't store things here. } public virtual bool OnDragDropInto(Mobile from, Item item, Point3D p) { if (!CheckHold(from, item, true, true)) { return false; } item.Location = new Point3D(p.m_X, p.m_Y, 0); AddItem(item); from.SendSound(GetDroppedSound(item), GetWorldLocation()); return true; } private static bool InTypeList(Item item, Type[] types) { var t = item.GetType(); for (var i = 0; i < types.Length; ++i) { if (types[i].IsAssignableFrom(t)) { return true; } } return false; } private static void SetSaveFlag(ref SaveFlag flags, SaveFlag toSet, bool setIf) { if (setIf) { flags |= toSet; } } private static bool GetSaveFlag(SaveFlag flags, SaveFlag toGet) => (flags & toGet) != 0; public override void Serialize(IGenericWriter writer) { base.Serialize(writer); writer.Write(2); // version var flags = SaveFlag.None; SetSaveFlag(ref flags, SaveFlag.MaxItems, m_MaxItems != -1); SetSaveFlag(ref flags, SaveFlag.GumpID, m_GumpID != -1); SetSaveFlag(ref flags, SaveFlag.DropSound, m_DropSound != -1); SetSaveFlag(ref flags, SaveFlag.LiftOverride, LiftOverride); writer.Write((byte)flags); if (GetSaveFlag(flags, SaveFlag.MaxItems)) { writer.WriteEncodedInt(m_MaxItems); } if (GetSaveFlag(flags, SaveFlag.GumpID)) { writer.WriteEncodedInt(m_GumpID); } if (GetSaveFlag(flags, SaveFlag.DropSound)) { writer.WriteEncodedInt(m_DropSound); } } public override void Deserialize(IGenericReader reader) { base.Deserialize(reader); var version = reader.ReadInt(); switch (version) { case 2: { var flags = (SaveFlag)reader.ReadByte(); if (GetSaveFlag(flags, SaveFlag.MaxItems)) { m_MaxItems = reader.ReadEncodedInt(); } else { m_MaxItems = -1; } if (GetSaveFlag(flags, SaveFlag.GumpID)) { m_GumpID = reader.ReadEncodedInt(); } else { m_GumpID = -1; } if (GetSaveFlag(flags, SaveFlag.DropSound)) { m_DropSound = reader.ReadEncodedInt(); } else { m_DropSound = -1; } LiftOverride = GetSaveFlag(flags, SaveFlag.LiftOverride); break; } case 1: { m_MaxItems = reader.ReadInt(); goto case 0; } case 0: { if (version < 1) { m_MaxItems = GlobalMaxItems; } m_GumpID = reader.ReadInt(); m_DropSound = reader.ReadInt(); if (m_GumpID == DefaultGumpID) { m_GumpID = -1; } if (m_DropSound == DefaultDropSound) { m_DropSound = -1; } if (m_MaxItems == DefaultMaxItems) { m_MaxItems = -1; } // m_Bounds = new Rectangle2D( reader.ReadPoint2D(), reader.ReadPoint2D() ); reader.ReadPoint2D(); reader.ReadPoint2D(); break; } } UpdateContainerData(); } public override int GetTotal(TotalType type) { return type switch { TotalType.Gold => m_TotalGold, TotalType.Items => m_TotalItems, TotalType.Weight => m_TotalWeight, _ => base.GetTotal(type) }; } public override void UpdateTotal(Item sender, TotalType type, int delta) { if (sender != this && delta != 0 && !sender.IsVirtualItem) { switch (type) { case TotalType.Gold: m_TotalGold += delta; break; case TotalType.Items: m_TotalItems += delta; InvalidateProperties(); break; case TotalType.Weight: m_TotalWeight += delta; InvalidateProperties(); break; } } base.UpdateTotal(sender, type, delta); } public override void UpdateTotals() { m_TotalGold = 0; m_TotalItems = 0; m_TotalWeight = 0; var items = m_Items; if (items == null) { return; } for (var i = 0; i < items.Count; ++i) { var item = items[i]; item.UpdateTotals(); if (item.IsVirtualItem) { continue; } m_TotalGold += item.TotalGold; m_TotalItems += item.TotalItems + 1; m_TotalWeight += item.TotalWeight + item.PileWeight; } } public virtual bool OnStackAttempt(Mobile from, Item stack, Item dropped) => CheckHold(from, dropped, true, false) && stack.StackWith(from, dropped); public override bool OnDragDrop(Mobile from, Item dropped) { if (TryDropItem(from, dropped, true)) { from.SendSound(GetDroppedSound(dropped), GetWorldLocation()); return true; } return false; } public virtual bool TryDropItem(Mobile from, Item dropped, bool sendFullMessage) => TryDropItem(from, dropped, sendFullMessage, false); public virtual bool TryDropItem(Mobile from, Item dropped, bool sendFullMessage, bool playSound) { var list = Items; for (var i = 0; i < list.Count; ++i) { var item = list[i]; if (!(item is Container) && CheckHold(from, dropped, false, false) && item.StackWith(from, dropped, playSound)) { return true; } } if (CheckHold(from, dropped, sendFullMessage, true)) { DropItem(dropped); return true; } return false; } public virtual bool TryDropItems(Mobile from, bool sendFullMessage, params Item[] droppedItems) { var dropItems = new List(); var stackItems = new List(); var extraItems = 0; var extraWeight = 0; for (var i = 0; i < droppedItems.Length; i++) { var dropped = droppedItems[i]; var list = Items; var stacked = false; for (var j = 0; j < list.Count; ++j) { var item = list[j]; if (!(item is Container) && CheckHold(from, dropped, false, false, 0, extraWeight) && item.CanStackWith(dropped)) { stackItems.Add(new ItemStackEntry(item, dropped)); extraWeight += (int)Math.Ceiling(item.Weight * (item.Amount + dropped.Amount)) - item.PileWeight; // extra weight delta, do not need TotalWeight as we do not have hybrid stackable container types stacked = true; break; } } if (!stacked && CheckHold(from, dropped, false, true, extraItems, extraWeight)) { dropItems.Add(dropped); extraItems++; extraWeight += dropped.TotalWeight + dropped.PileWeight; } } if (dropItems.Count + stackItems.Count == droppedItems.Length) // All good { for (var i = 0; i < dropItems.Count; i++) { DropItem(dropItems[i]); } for (var i = 0; i < stackItems.Count; i++) { stackItems[i].m_StackItem.StackWith(from, stackItems[i].m_DropItem, false); } return true; } return false; } public virtual void Destroy() { var loc = GetWorldLocation(); var map = Map; for (var i = Items.Count - 1; i >= 0; --i) { if (i < Items.Count) { Items[i].SetLastMoved(); Items[i].MoveToWorld(loc, map); } } Delete(); } public virtual void DropItem(Item dropped) { if (dropped == null) { return; } AddItem(dropped); var bounds = dropped.GetGraphicBounds(); var ourBounds = Bounds; int x, y; if (bounds.Width >= ourBounds.Width) { x = (ourBounds.Width - bounds.Width) / 2; } else { x = Utility.Random(ourBounds.Width - bounds.Width); } if (bounds.Height >= ourBounds.Height) { y = (ourBounds.Height - bounds.Height) / 2; } else { y = Utility.Random(ourBounds.Height - bounds.Height); } x += ourBounds.X; x -= bounds.X; y += ourBounds.Y; y -= bounds.Y; dropped.Location = new Point3D(x, y, 0); } public override void OnDoubleClickSecureTrade(Mobile from) { if (from.InRange(GetWorldLocation(), 2)) { DisplayTo(from); var trade = GetSecureTradeCont()?.Trade; if (trade != null) { if (trade.From.Mobile == from) { DisplayTo(trade.To.Mobile); } else if (trade.To.Mobile == from) { DisplayTo(trade.From.Mobile); } } } else { from.SendLocalizedMessage(500446); // That is too far away. } } public virtual bool CheckContentDisplay(Mobile from) => DisplaysContent && RootParent == null || RootParent is Item || RootParent == from || from.AccessLevel > AccessLevel.Player; public override void OnSingleClick(Mobile from) { base.OnSingleClick(from); if (CheckContentDisplay(from)) { LabelTo(from, "({0} item{2}, {1} stones)", TotalItems, TotalWeight, TotalItems != 1 ? "s" : string.Empty); } // LabelTo( from, 1050044, String.Format( "{0}\t{1}", TotalItems.ToString(), TotalWeight.ToString() ) ); } public override void OnDelete() { base.OnDelete(); Openers = null; } public virtual void DisplayTo(Mobile to) { ProcessOpeners(to); var ns = to.NetState; if (ns != null) { if (ns.HighSeas) { to.Send(new ContainerDisplayHS(Serial, GumpID)); } else { to.Send(new ContainerDisplay(Serial, GumpID)); } SendContentTo(ns); if (ObjectPropertyList.Enabled) { var items = Items; for (var i = 0; i < items.Count; ++i) { to.Send(items[i].OPLPacket); } } } } public void ProcessOpeners(Mobile opener) { if (IsPublicContainer) { return; } var contains = false; if (Openers != null) { var worldLoc = GetWorldLocation(); var map = Map; for (var i = 0; i < Openers.Count; ++i) { var mob = Openers[i]; if (mob == opener) { contains = true; } else { var range = GetUpdateRange(mob); if (mob.Map != map || !mob.InRange(worldLoc, range)) { Openers.RemoveAt(i--); } } } } if (!contains) { Openers ??= new List(); Openers.Add(opener); } else if (Openers?.Count == 0) { Openers = null; } } public virtual void SendContentTo(NetState state) { if (state == null) { return; } if (state.ContainerGridLines) { state.Send(new ContainerContent6017(state.Mobile, this)); } else { state.Send(new ContainerContent(state.Mobile, this)); } } public override void GetProperties(ObjectPropertyList list) { base.GetProperties(list); if (DisplaysContent) // CheckContentDisplay( from )) { if (Core.ML) { if (ParentsContain()) // Root Parent is the Mobile. Parent could be another containter. { list.Add( 1073841, // Contents: ~1_COUNT~/~2_MAXCOUNT~ items, ~3_WEIGHT~ stones "{0}\t{1}\t{2}", TotalItems, MaxItems, TotalWeight ); } else { list.Add( 1072241, // Contents: ~1_COUNT~/~2_MAXCOUNT~ items, ~3_WEIGHT~/~4_MAXWEIGHT~ stones "{0}\t{1}\t{2}\t{3}", TotalItems, MaxItems, TotalWeight, MaxWeight ); } // TODO: Where do the other clilocs come into play? 1073839 & 1073840? } else { // ~1_COUNT~ items, ~2_WEIGHT~ stones list.Add(1050044, "{0}\t{1}", TotalItems, TotalWeight); } } } public override void OnDoubleClick(Mobile from) { if (from.AccessLevel > AccessLevel.Player || from.InRange(GetWorldLocation(), 2)) { DisplayTo(from); } else { from.SendLocalizedMessage(500446); // That is too far away. } } public bool ConsumeTotalGrouped(Type type, int amount, bool recurse, OnItemConsumed callback, CheckItemGroup grouper) { if (grouper == null) { throw new ArgumentNullException(nameof(grouper)); } var typedItems = FindItemsByType(type, recurse); var groups = new List>(); var idx = 0; while (idx < typedItems.Length) { var a = typedItems[idx++]; var group = new List(); group.Add(a); while (idx < typedItems.Length) { var b = typedItems[idx]; var v = grouper(a, b); if (v == 0) { group.Add(b); } else { break; } ++idx; } groups.Add(group); } var items = new Item[groups.Count][]; var totals = new int[groups.Count]; var hasEnough = false; for (var i = 0; i < groups.Count; ++i) { items[i] = groups[i].ToArray(); for (var j = 0; j < items[i].Length; ++j) { totals[i] += items[i][j].Amount; } if (totals[i] >= amount) { hasEnough = true; } } if (!hasEnough) { return false; } for (var i = 0; i < items.Length; ++i) { if (totals[i] >= amount) { var need = amount; for (var j = 0; j < items[i].Length; ++j) { var item = items[i][j]; var theirAmount = item.Amount; if (theirAmount < need) { callback?.Invoke(item, theirAmount); item.Consume(theirAmount); need -= theirAmount; } else { callback?.Invoke(item, need); item.Consume(need); break; } } break; } } return true; } public int ConsumeTotalGrouped( Type[] types, int[] amounts, bool recurse, OnItemConsumed callback, CheckItemGroup grouper ) { if (types.Length != amounts.Length) { throw new ArgumentException("length of types and amounts must match"); } if (grouper == null) { throw new ArgumentNullException(nameof(grouper)); } var items = new Item[types.Length][][]; var totals = new int[types.Length][]; for (var i = 0; i < types.Length; ++i) { var typedItems = FindItemsByType(types[i], recurse); var groups = new List>(); var idx = 0; while (idx < typedItems.Length) { var a = typedItems[idx++]; var group = new List(); group.Add(a); while (idx < typedItems.Length) { var b = typedItems[idx]; var v = grouper(a, b); if (v == 0) { group.Add(b); } else { break; } ++idx; } groups.Add(group); } items[i] = new Item[groups.Count][]; totals[i] = new int[groups.Count]; var hasEnough = false; for (var j = 0; j < groups.Count; ++j) { items[i][j] = groups[j].ToArray(); for (var k = 0; k < items[i][j].Length; ++k) { totals[i][j] += items[i][j][k].Amount; } if (totals[i][j] >= amounts[i]) { hasEnough = true; } } if (!hasEnough) { return i; } } for (var i = 0; i < items.Length; ++i) { for (var j = 0; j < items[i].Length; ++j) { if (totals[i][j] >= amounts[i]) { var need = amounts[i]; for (var k = 0; k < items[i][j].Length; ++k) { var item = items[i][j][k]; var theirAmount = item.Amount; if (theirAmount < need) { callback?.Invoke(item, theirAmount); item.Consume(theirAmount); need -= theirAmount; } else { callback?.Invoke(item, need); item.Consume(need); break; } } break; } } } return -1; } public int ConsumeTotalGrouped( Type[][] types, int[] amounts, bool recurse, OnItemConsumed callback, CheckItemGroup grouper ) { if (types.Length != amounts.Length) { throw new ArgumentException("length of types and amounts must match"); } if (grouper == null) { throw new ArgumentNullException(nameof(grouper)); } var items = new Item[types.Length][][]; var totals = new int[types.Length][]; for (var i = 0; i < types.Length; ++i) { var typedItems = FindItemsByType(types[i], recurse); var groups = new List>(); var idx = 0; while (idx < typedItems.Length) { var a = typedItems[idx++]; var group = new List(); group.Add(a); while (idx < typedItems.Length) { var b = typedItems[idx]; var v = grouper(a, b); if (v == 0) { group.Add(b); } else { break; } ++idx; } groups.Add(group); } items[i] = new Item[groups.Count][]; totals[i] = new int[groups.Count]; var hasEnough = false; for (var j = 0; j < groups.Count; ++j) { items[i][j] = groups[j].ToArray(); for (var k = 0; k < items[i][j].Length; ++k) { totals[i][j] += items[i][j][k].Amount; } if (totals[i][j] >= amounts[i]) { hasEnough = true; } } if (!hasEnough) { return i; } } for (var i = 0; i < items.Length; ++i) { for (var j = 0; j < items[i].Length; ++j) { if (totals[i][j] >= amounts[i]) { var need = amounts[i]; for (var k = 0; k < items[i][j].Length; ++k) { var item = items[i][j][k]; var theirAmount = item.Amount; if (theirAmount < need) { callback?.Invoke(item, theirAmount); item.Consume(theirAmount); need -= theirAmount; } else { callback?.Invoke(item, need); item.Consume(need); break; } } break; } } } return -1; } public int ConsumeTotal(Type[][] types, int[] amounts, bool recurse = true, OnItemConsumed callback = null) { if (types.Length != amounts.Length) { throw new ArgumentException("length of types and amounts must match"); } var items = new Item[types.Length][]; var totals = new int[types.Length]; for (var i = 0; i < types.Length; ++i) { items[i] = FindItemsByType(types[i], recurse); for (var j = 0; j < items[i].Length; ++j) { totals[i] += items[i][j].Amount; } if (totals[i] < amounts[i]) { return i; } } for (var i = 0; i < types.Length; ++i) { var need = amounts[i]; for (var j = 0; j < items[i].Length; ++j) { var item = items[i][j]; var theirAmount = item.Amount; if (theirAmount < need) { callback?.Invoke(item, theirAmount); item.Consume(theirAmount); need -= theirAmount; } else { callback?.Invoke(item, need); item.Consume(need); break; } } } return -1; } public int ConsumeTotal(Type[] types, int[] amounts, bool recurse = true, OnItemConsumed callback = null) { if (types.Length != amounts.Length) { throw new ArgumentException("length of types and amounts must match"); } var items = new Item[types.Length][]; var totals = new int[types.Length]; for (var i = 0; i < types.Length; ++i) { items[i] = FindItemsByType(types[i], recurse); for (var j = 0; j < items[i].Length; ++j) { totals[i] += items[i][j].Amount; } if (totals[i] < amounts[i]) { return i; } } for (var i = 0; i < types.Length; ++i) { var need = amounts[i]; for (var j = 0; j < items[i].Length; ++j) { var item = items[i][j]; var theirAmount = item.Amount; if (theirAmount < need) { callback?.Invoke(item, theirAmount); item.Consume(theirAmount); need -= theirAmount; } else { callback?.Invoke(item, need); item.Consume(need); break; } } } return -1; } public bool ConsumeTotal(Type type, int amount = 1, bool recurse = true, OnItemConsumed callback = null) { var items = FindItemsByType(type, recurse); // First pass, compute total var total = 0; for (var i = 0; i < items.Length; ++i) { total += items[i].Amount; } if (total >= amount) { // We've enough, so consume it var need = amount; for (var i = 0; i < items.Length; ++i) { var item = items[i]; var theirAmount = item.Amount; if (theirAmount < need) { callback?.Invoke(item, theirAmount); item.Consume(theirAmount); need -= theirAmount; } else { callback?.Invoke(item, need); item.Consume(need); return true; } } } return false; } public int ConsumeUpTo(Type type, int amount, bool recurse = true) { var consumed = 0; var toDelete = new Queue(); RecurseConsumeUpTo(this, type, amount, recurse, ref consumed, toDelete); while (toDelete.Count > 0) { toDelete.Dequeue().Delete(); } return consumed; } private static void RecurseConsumeUpTo( Item current, Type type, int amount, bool recurse, ref int consumed, Queue toDelete ) { if (current == null || current.Items.Count == 0) { return; } var list = current.Items; for (var i = 0; i < list.Count; ++i) { var item = list[i]; if (type.IsInstanceOfType(item)) { var need = amount - consumed; var theirAmount = item.Amount; if (theirAmount <= need) { toDelete.Enqueue(item); consumed += theirAmount; } else { item.Amount -= need; consumed += need; return; } } else if (recurse && item is Container) { RecurseConsumeUpTo(item, type, amount, true, ref consumed, toDelete); } } } public int GetBestGroupAmount(Type type, bool recurse, CheckItemGroup grouper) { if (grouper == null) { throw new ArgumentNullException(nameof(grouper)); } var best = 0; var typedItems = FindItemsByType(type, recurse); var groups = new List>(); var idx = 0; while (idx < typedItems.Length) { var a = typedItems[idx++]; var group = new List(); group.Add(a); while (idx < typedItems.Length) { var b = typedItems[idx]; var v = grouper(a, b); if (v == 0) { group.Add(b); } else { break; } ++idx; } groups.Add(group); } for (var i = 0; i < groups.Count; ++i) { var items = groups[i].ToArray(); var total = 0; for (var j = 0; j < items.Length; ++j) { total += items[j].Amount; } if (total >= best) { best = total; } } return best; } public int GetBestGroupAmount(Type[] types, bool recurse, CheckItemGroup grouper) { if (grouper == null) { throw new ArgumentNullException(nameof(grouper)); } var best = 0; var typedItems = FindItemsByType(types, recurse); var groups = new List>(); var idx = 0; while (idx < typedItems.Length) { var a = typedItems[idx++]; var group = new List(); group.Add(a); while (idx < typedItems.Length) { var b = typedItems[idx]; var v = grouper(a, b); if (v == 0) { group.Add(b); } else { break; } ++idx; } groups.Add(group); } for (var j = 0; j < groups.Count; ++j) { var items = groups[j].ToArray(); var total = items.Sum(t => t.Amount); if (total >= best) { best = total; } } return best; } public int GetBestGroupAmount(Type[][] types, bool recurse, CheckItemGroup grouper) { if (grouper == null) { throw new ArgumentNullException(nameof(grouper)); } var best = 0; for (var i = 0; i < types.Length; ++i) { var typedItems = FindItemsByType(types[i], recurse); var groups = new List>(); var idx = 0; while (idx < typedItems.Length) { var a = typedItems[idx++]; var group = new List(); group.Add(a); while (idx < typedItems.Length) { var b = typedItems[idx]; var v = grouper(a, b); if (v == 0) { group.Add(b); } else { break; } ++idx; } groups.Add(group); } for (var j = 0; j < groups.Count; ++j) { var items = groups[j].ToArray(); var total = 0; for (var k = 0; k < items.Length; ++k) { total += items[k].Amount; } if (total >= best) { best = total; } } } return best; } public int GetAmount(Type type, bool recurse = true) => FindItemsByType(type, recurse).Sum(t => t.Amount); public int GetAmount(Type[] types, bool recurse = true) => FindItemsByType(types, recurse).Sum(t => t.Amount); public Item[] FindItemsByType(Type type, bool recurse = true) { if (m_FindItemsList.Count > 0) { m_FindItemsList.Clear(); } RecurseFindItemsByType(this, type, recurse, m_FindItemsList); return m_FindItemsList.ToArray(); } private static void RecurseFindItemsByType(Item current, Type type, bool recurse, List list) { if (current == null || current.Items.Count == 0) { return; } var items = current.Items; for (var i = 0; i < items.Count; ++i) { var item = items[i]; if (type.IsInstanceOfType(item)) { list.Add(item); } if (recurse && item is Container) { RecurseFindItemsByType(item, type, true, list); } } } public Item[] FindItemsByType(Type[] types, bool recurse = true) { if (m_FindItemsList.Count > 0) { m_FindItemsList.Clear(); } RecurseFindItemsByType(this, types, recurse, m_FindItemsList); return m_FindItemsList.ToArray(); } private static void RecurseFindItemsByType(Item current, Type[] types, bool recurse, List list) { if (current == null || current.Items.Count == 0) { return; } var items = current.Items; for (var i = 0; i < items.Count; ++i) { var item = items[i]; if (InTypeList(item, types)) { list.Add(item); } if (recurse && item is Container) { RecurseFindItemsByType(item, types, true, list); } } } public Item FindItemByType(Type type, bool recurse = true) => RecurseFindItemByType(this, type, recurse); private static Item RecurseFindItemByType(Item current, Type type, bool recurse) { if (current == null || current.Items.Count == 0) { return null; } var list = current.Items; for (var i = 0; i < list.Count; ++i) { var item = list[i]; if (type.IsInstanceOfType(item)) { return item; } if (recurse && item is Container) { var check = RecurseFindItemByType(item, type, true); if (check != null) { return check; } } } return null; } public Item FindItemByType(Type[] types, bool recurse = true) => RecurseFindItemByType(this, types, recurse); private static Item RecurseFindItemByType(Item current, Type[] types, bool recurse) { if (current == null || current.Items.Count == 0) { return null; } var list = current.Items; for (var i = 0; i < list.Count; ++i) { var item = list[i]; if (InTypeList(item, types)) { return item; } if (recurse && item is Container) { var check = RecurseFindItemByType(item, types, true); if (check != null) { return check; } } } return null; } public List FindItemsByType(Predicate predicate) where T : Item => FindItemsByType(true, predicate); /// /// Performs a Breadth-First search through all the s and /// nested s within this . /// /// Type of objects being searched for /// /// Optional: If true, the search will recursively /// check any nested s; otherwise, nested /// s will not be searched. /// /// /// Optional: A predicate to check if the /// of type is one of the targets of the search. /// /// /// A list of s of type that matche the optional /// . /// public List FindItemsByType(bool recurse = true, Predicate predicate = null) where T : Item { using (var queue = m_QueuePool.Get()) { queue.Enqueue(this); var items = new List(); while (queue.Count > 0) { var container = queue.Dequeue(); foreach (var item in container.Items) { if (item is T typedItem && predicate?.Invoke(typedItem) != false) { items.Add(typedItem); } else if (recurse && item is Container itemContainer) { queue.Enqueue(itemContainer); } } } return items; } } /// /// Performs a Breadth-First search through all the s and /// nested s within this . /// /// Type of object being searched for /// /// Optional: If true, the search will recursively /// check any nested s; otherwise, nested /// s will not be searched. /// /// /// Optional: A predicate to check if the /// of type is the target of the search. /// /// /// The first of type that matches the optional /// . /// public T FindItemByType(bool recurse = true, Predicate predicate = null) where T : Item { using (var queue = m_QueuePool.Get()) { queue.Enqueue(this); while (queue.Count > 0) { var container = queue.Dequeue(); foreach (var item in container.Items) { if (item is T typedItem && predicate?.Invoke(typedItem) != false) { return typedItem; } if (recurse && item is Container itemContainer) { queue.Enqueue(itemContainer); } } } return null; } } private class GroupComparer : IComparer { private readonly CheckItemGroup m_Grouper; public GroupComparer(CheckItemGroup grouper) => m_Grouper = grouper; public int Compare(Item a, Item b) => m_Grouper(a, b); } [Flags] private enum SaveFlag : byte { None = 0x00000000, MaxItems = 0x00000001, GumpID = 0x00000002, DropSound = 0x00000004, LiftOverride = 0x00000008 } private struct ItemStackEntry { public readonly Item m_StackItem; public readonly Item m_DropItem; public ItemStackEntry(Item stack, Item drop) { m_StackItem = stack; m_DropItem = drop; } } } public class ContainerData { private static readonly Dictionary m_Table; static ContainerData() { m_Table = new Dictionary(); var path = Path.Combine(Core.BaseDirectory, "Data/containers.cfg"); if (!File.Exists(path)) { Default = new ContainerData(0x3C, new Rectangle2D(44, 65, 142, 94), 0x48); return; } using (var reader = new StreamReader(path)) { string line; while ((line = reader.ReadLine()) != null) { line = line.Trim(); if (line.Length == 0 || line.StartsWith("#")) { continue; } try { var split = line.Split('\t'); if (split.Length >= 3) { var gumpID = Utility.ToInt32(split[0]); var aRect = split[1].Split(' '); if (aRect.Length < 4) { continue; } var x = Utility.ToInt32(aRect[0]); var y = Utility.ToInt32(aRect[1]); var width = Utility.ToInt32(aRect[2]); var height = Utility.ToInt32(aRect[3]); var bounds = new Rectangle2D(x, y, width, height); var dropSound = Utility.ToInt32(split[2]); var data = new ContainerData(gumpID, bounds, dropSound); Default ??= data; if (split.Length >= 4) { var aIDs = split[3].Split(','); for (var i = 0; i < aIDs.Length; i++) { var id = Utility.ToInt32(aIDs[i]); if (m_Table.ContainsKey(id)) { Console.WriteLine(@"Warning: double ItemID entry in Data\containers.cfg"); } else { m_Table[id] = data; } } } } } catch { // ignored } } } Default ??= new ContainerData(0x3C, new Rectangle2D(44, 65, 142, 94), 0x48); } public ContainerData(int gumpID, Rectangle2D bounds, int dropSound) { GumpID = gumpID; Bounds = bounds; DropSound = dropSound; } public static ContainerData Default { get; set; } public int GumpID { get; } public Rectangle2D Bounds { get; } public int DropSound { get; } public static ContainerData GetData(int itemID) { m_Table.TryGetValue(itemID, out var data); return data ?? Default; } } }