diff --git a/Projects/Server.Tests/Tests/Items/ContainerTests.cs b/Projects/Server.Tests/Tests/Items/ContainerTests.cs index 0b258a717..8c165f5f6 100644 --- a/Projects/Server.Tests/Tests/Items/ContainerTests.cs +++ b/Projects/Server.Tests/Tests/Items/ContainerTests.cs @@ -8,12 +8,14 @@ namespace Server.Tests; [Collection("Sequential Server Tests")] public class ContainerTests { - [Fact] - public void TestFindItemsByType() + [Theory] + [InlineData(typeof(Container))] + [InlineData(typeof(Item))] + public void TestFindItemsByType(Type itemType) { var staticSerial = (Serial)0x3; - var container = new Container((Serial)0x1); + var container = itemType.CreateInstance((Serial)0x1); container.AddItem(new Item((Serial)0x2)); container.AddItem(new Static(staticSerial)); @@ -27,18 +29,20 @@ public class ContainerTests Assert.Equal(staticSerial, staticItem.Serial); } - [Fact] - public void TestFindItemsByTypeNested() + [Theory] + [InlineData(typeof(Container))] + [InlineData(typeof(Item))] + public void TestFindItemsByTypeNested(Type itemType) { var static1 = new Static((Serial)0x3); var static2 = new Static((Serial)0x6); - var container = new Container((Serial)0x1); + var container = itemType.CreateInstance((Serial)0x1); container.AddItem(new Item((Serial)0x2)); - var container2 = new Container((Serial)0x4); + var container2 = itemType.CreateInstance((Serial)0x4); container.AddItem(container2); - var container3 = new Container((Serial)0x5); + var container3 = itemType.CreateInstance((Serial)0x5); container2.AddItem(container3); container3.AddItem(static2); @@ -55,12 +59,14 @@ public class ContainerTests Assert.Equal(static2, statics[1]); } - [Fact] - public void TestFindItemsByTypeNotMatching() + [Theory] + [InlineData(typeof(Container))] + [InlineData(typeof(Item))] + public void TestFindItemsByTypeNotMatching(Type itemType) { - var container = new Container((Serial)0x1); + var container = itemType.CreateInstance((Serial)0x1); container.AddItem(new Item((Serial)0x2)); - var container2 = new Container((Serial)0x4); + var container2 = itemType.CreateInstance((Serial)0x4); container.AddItem(container2); container2.AddItem(new Item((Serial)0x5)); @@ -73,10 +79,12 @@ public class ContainerTests Assert.Null(staticItem); } - [Fact] - public void TestFindItemsByTypeShouldThrowWhenModified() + [Theory] + [InlineData(typeof(Container))] + [InlineData(typeof(Item))] + public void TestFindItemsByTypeShouldThrowWhenModified(Type itemType) { - var container = new Container((Serial)0x1); + var container = itemType.CreateInstance((Serial)0x1); container.AddItem(new Item((Serial)0x2)); var staticItem = new Static((Serial)0x3); container.AddItem(staticItem); @@ -96,10 +104,12 @@ public class ContainerTests ); } - [Fact] - public void TestEnumerateItemsByTypeWhenModified() + [Theory] + [InlineData(typeof(Container))] + [InlineData(typeof(Item))] + public void TestEnumerateItemsByTypeWhenModified(Type itemType) { - var container = new Container((Serial)0x1); + var container = itemType.CreateInstance((Serial)0x1); var item1 = new Item((Serial)0x2); container.AddItem(item1); diff --git a/Projects/Server/Items/Container.cs b/Projects/Server/Items/Container.cs index 3481d182c..65b910e95 100644 --- a/Projects/Server/Items/Container.cs +++ b/Projects/Server/Items/Container.cs @@ -1,3 +1,18 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2025 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Container.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + using System; using System.Collections.Generic; using System.IO; @@ -25,7 +40,7 @@ public partial class Container : Item private int m_TotalItems; private int m_TotalWeight; - private int _version; + internal int _version; [SerializableField(3)] [SerializedCommandProperty(AccessLevel.GameMaster)] @@ -281,16 +296,6 @@ public partial class Container : Item return true; } - 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; - [AfterDeserialization] private void AfterDeserialization() { diff --git a/Projects/Server/Items/Container.Enumerable.cs b/Projects/Server/Items/Item.Enumerable.cs similarity index 85% rename from Projects/Server/Items/Container.Enumerable.cs rename to Projects/Server/Items/Item.Enumerable.cs index fc652c636..c909efcc4 100644 --- a/Projects/Server/Items/Container.Enumerable.cs +++ b/Projects/Server/Items/Item.Enumerable.cs @@ -1,8 +1,8 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2023 - ModernUO Development Team * + * Copyright 2019-2025 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: Container.Enumerable.cs * + * File: Item.Enumerable.cs * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -18,13 +18,13 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Server.Collections; -namespace Server.Items; +namespace Server; -public partial class Container +public partial class Item { /// /// Performs a breadth-first search through all the s and - /// nested s within this . + /// nested s within this . /// /// /// DO NOT consume, delete, or move items while iterating with any FindItemByType or FindItems overloads @@ -42,8 +42,8 @@ public partial class Container /// Type of objects being searched for /// /// Optional: If true, the search will recursively - /// check any nested s; otherwise, nested - /// s will not be searched. + /// check any nested s; otherwise, nested + /// s will not be searched. /// /// /// Optional: A predicate to check if the @@ -71,7 +71,7 @@ public partial class Container /// /// Safely enumerates items using a breadth-first search through all the s and - /// nested s within this . + /// nested s within this . /// /// /// Use EnumerateItemsByType for situations where the item might be manipulated, consumed, or moved. @@ -92,8 +92,8 @@ public partial class Container /// Type of objects being searched for /// /// Optional: If true, the search will recursively - /// check any nested s; otherwise, nested - /// s will not be searched. + /// check any nested s; otherwise, nested + /// s will not be searched. /// /// /// Optional: A predicate to check if the @@ -203,30 +203,31 @@ public partial class Container public ref struct FindItemsByTypeEnumerator where T : Item { private const string InvalidOperation_EnumFailedVersion = - "Container was modified after enumerator was instantiated. Use Container.EnumerateItems method instead for safe enumerations."; + "Item was modified after enumerator was instantiated. Use Item.EnumerateItems method instead for safe enumerations."; - private PooledRefQueue _containers; + private PooledRefQueue _containers; private Span _items; private int _index; private T _current; private readonly bool _recurse; private readonly Predicate _predicate; - private Container _currentContainer; + private Item _currentContainer; private int _version; - public FindItemsByTypeEnumerator(Container container, bool recurse, Predicate predicate) + public FindItemsByTypeEnumerator(Item container, bool recurse, Predicate predicate) { - _containers = PooledRefQueue.Create(_recurse ? 64 : 0); + _containers = PooledRefQueue.Create(_recurse ? 64 : 0); if (container != null) { - if (container.m_Items != null) + var items = container.LookupItems(); + if (items != null) { - _items = CollectionsMarshal.AsSpan(container.m_Items); + _items = CollectionsMarshal.AsSpan(items); } _currentContainer = container; - _version = container._version; + _version = container.LookupContainerVersion(); } _current = default; @@ -244,9 +245,9 @@ public partial class Container while (_containers.TryDequeue(out var c)) { _currentContainer = c; - _items = CollectionsMarshal.AsSpan(c.m_Items); + _items = CollectionsMarshal.AsSpan(c.LookupItems()); _index = 0; - _version = c._version; + _version = c.LookupContainerVersion(); if (SetNextItem()) { @@ -260,7 +261,7 @@ public partial class Container [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool SetNextItem() { - if (_version != _currentContainer._version) + if (_version != _currentContainer.LookupContainerVersion()) { throw new InvalidOperationException(InvalidOperation_EnumFailedVersion); } @@ -268,14 +269,14 @@ public partial class Container while (_index < _items.Length) { Item item = _items[_index++]; - if (_recurse && item is Container { m_Items.Count: > 0 } c) + if (_recurse && item.LookupItems() is { Count: > 0 } items) { - _containers.Enqueue(c); + _containers.Enqueue(item); } if (item is T t && _predicate?.Invoke(t) != false) { - if (_version != _currentContainer._version) + if (_version != _currentContainer.LookupContainerVersion()) { throw new InvalidOperationException(InvalidOperation_EnumFailedVersion); } diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs index 9703fc55f..fc07d6573 100644 --- a/Projects/Server/Items/Item.cs +++ b/Projects/Server/Items/Item.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2024 - ModernUO Development Team * + * Copyright 2019-2025 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Item.cs * * * @@ -192,7 +192,7 @@ public enum ExpandFlag Spawner = 0x100 } -public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEntity, IValueLinkListNode +public partial class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEntity, IValueLinkListNode { private static readonly ILogger logger = LogFactory.GetLogger(typeof(Item)); @@ -534,6 +534,8 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt public List Items => LookupItems() ?? EmptyItems; + public int LookupContainerVersion() => (this as Container)?._version ?? LookupCompactInfo()?.Version ?? 0; + [CommandProperty(AccessLevel.GameMaster)] public IEntity RootParent { @@ -1696,11 +1698,11 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt { if (this is Container cont) { - return cont.m_Items ?? (cont.m_Items = new List()); + return cont.m_Items ??= new List(); } var info = AcquireCompactInfo(); - return info.m_Items ?? (info.m_Items = new List()); + return info.m_Items ??= new List(); } private void SetFlag(ImplFlag flag, bool value) @@ -2471,15 +2473,6 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt { } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void SetSaveFlag(ref SaveFlag flags, SaveFlag toSet, bool setIf) - { - if (setIf) - { - flags |= toSet; - } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool GetSaveFlag(SaveFlag flags, SaveFlag toGet) => (flags & toGet) != 0; @@ -3195,9 +3188,13 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt item.Map = m_Map; var items = AcquireItems(); - items.Add(item); + if (this is not Container) + { + AcquireCompactInfo().Version++; + } + if (!item.IsVirtualItem) { UpdateTotal(item, TotalType.Gold, item.TotalGold); @@ -3360,6 +3357,11 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt if (items.Remove(item)) { + if (this is not Container) + { + AcquireCompactInfo().Version++; + } + item.SendRemovePacket(); if (!item.IsVirtualItem) @@ -4321,6 +4323,8 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt public int m_TempFlags; public double m_Weight = -1; + + public int Version; } [Flags]