From c494fb4cc339ce57ec65f2d86425f6958f2302a1 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Tue, 17 Feb 2026 09:54:32 -0800 Subject: [PATCH] fix: Fixes container enumeration not recycling pooled arrays (#2341) ### Summary Updates all calls to container.EnumerateItems() to properly dispose of the underlying PooledRefQueue so that we are properly recycling pooled arrays. --- Projects/Server/Items/Item.Enumerable.cs | 3 +- .../ContainedCommandImplementor.cs | 3 +- .../Engines/ConPVP/Games/BombingRun.cs | 12 +- .../UOContent/Engines/ConPVP/Games/CTF.cs | 12 +- .../Engines/Factions/Core/Faction.cs | 9 +- .../ML Quests/Objectives/CollectObjective.cs | 3 +- .../ML Quests/Objectives/DeliverObjective.cs | 3 +- .../Halloween/2006/Engines/TrickOrTreat.cs | 593 +++++++++--------- .../UOContent/Items/Containers/SalvageBag.cs | 37 +- Projects/UOContent/Items/Misc/Key.cs | 3 +- .../Skill Items/Magical/Potions/BasePotion.cs | 3 +- Projects/UOContent/Mobiles/BaseCreature.cs | 35 +- .../Mobiles/Familiars/BaseFamiliar.cs | 14 +- Projects/UOContent/Mobiles/PlayerMobile.cs | 8 +- .../UOContent/Multis/Houses/MovingCrate.cs | 15 +- 15 files changed, 388 insertions(+), 365 deletions(-) diff --git a/Projects/Server/Items/Item.Enumerable.cs b/Projects/Server/Items/Item.Enumerable.cs index 35242e75e..b89d42af5 100644 --- a/Projects/Server/Items/Item.Enumerable.cs +++ b/Projects/Server/Items/Item.Enumerable.cs @@ -80,7 +80,8 @@ public partial class Item /// /// /// - /// foreach (var item in cont.EnumerateItemsByType<Item>()) + /// using var queue = cont.EnumerateItemsByType<Item>(); + /// foreach (var item in queue) /// { /// if (item.LootType is not LootType.Blessed) /// { diff --git a/Projects/UOContent/Commands/Generic/Implementors/ContainedCommandImplementor.cs b/Projects/UOContent/Commands/Generic/Implementors/ContainedCommandImplementor.cs index 225871532..7b76cbc14 100644 --- a/Projects/UOContent/Commands/Generic/Implementors/ContainedCommandImplementor.cs +++ b/Projects/UOContent/Commands/Generic/Implementors/ContainedCommandImplementor.cs @@ -67,7 +67,8 @@ namespace Server.Commands.Generic var list = new List(); - foreach (var item in cont.EnumerateItems(true, ext.IsValid)) + using var queue = cont.EnumerateItems(true, ext.IsValid); + foreach (var item in queue) { list.Add(item); } diff --git a/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs b/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs index 8a46bfa70..452193270 100644 --- a/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs +++ b/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs @@ -1599,15 +1599,19 @@ public sealed class BRGame : EventGame var hadBomb = false; - foreach (var bomb in corpse.EnumerateItemsByType(false)) + using (var queue = corpse.EnumerateItemsByType(false)) { - hadBomb = true; - bomb.DropTo(mob, killer); + foreach (var bomb in queue) + { + hadBomb = true; + bomb.DropTo(mob, killer); + } } if (mob.Backpack != null) { - foreach (var bomb in mob.Backpack.EnumerateItemsByType(false)) + using var queue = mob.Backpack.EnumerateItemsByType(false); + foreach (var bomb in queue) { hadBomb = true; bomb.DropTo(mob, killer); diff --git a/Projects/UOContent/Engines/ConPVP/Games/CTF.cs b/Projects/UOContent/Engines/ConPVP/Games/CTF.cs index d4b4f0eef..967c97474 100644 --- a/Projects/UOContent/Engines/ConPVP/Games/CTF.cs +++ b/Projects/UOContent/Engines/ConPVP/Games/CTF.cs @@ -947,15 +947,19 @@ public sealed class CTFGame : EventGame var hadFlag = false; - foreach (var flag in corpse.EnumerateItemsByType(false)) + using (var queue = corpse.EnumerateItemsByType(false)) { - hadFlag = true; - flag.DropTo(mob, killer); + foreach (var flag in queue) + { + hadFlag = true; + flag.DropTo(mob, killer); + } } if (mob.Backpack != null) { - foreach (var flag in mob.Backpack.EnumerateItemsByType(false)) + using var queue = mob.Backpack.EnumerateItemsByType(false); + foreach (var flag in queue) { hadFlag = true; flag.DropTo(mob, killer); diff --git a/Projects/UOContent/Engines/Factions/Core/Faction.cs b/Projects/UOContent/Engines/Factions/Core/Faction.cs index 2935ebabf..8aa8085ca 100644 --- a/Projects/UOContent/Engines/Factions/Core/Faction.cs +++ b/Projects/UOContent/Engines/Factions/Core/Faction.cs @@ -410,7 +410,8 @@ public abstract class Faction : IComparable if (mob.Backpack != null) { - foreach (var sigil in mob.Backpack.EnumerateItemsByType()) + using var queue = mob.Backpack.EnumerateItemsByType(); + foreach (var sigil in queue) { sigil.ReturnHome(); } @@ -1073,7 +1074,8 @@ public abstract class Faction : IComparable if (victim.Backpack != null) { - foreach (var sigil in victim.Backpack.EnumerateItemsByType()) + using var queue = victim.Backpack.EnumerateItemsByType(); + foreach (var sigil in queue) { if (killerState == null || killerPack == null) { @@ -1254,7 +1256,8 @@ public abstract class Faction : IComparable { if (m.Backpack != null) { - foreach (var sigil in m.Backpack.EnumerateItemsByType()) + using var queue = m.Backpack.EnumerateItemsByType(); + foreach (var sigil in queue) { sigil.ReturnHome(); } diff --git a/Projects/UOContent/Engines/ML Quests/Objectives/CollectObjective.cs b/Projects/UOContent/Engines/ML Quests/Objectives/CollectObjective.cs index 5f3f23904..8819406c0 100644 --- a/Projects/UOContent/Engines/ML Quests/Objectives/CollectObjective.cs +++ b/Projects/UOContent/Engines/ML Quests/Objectives/CollectObjective.cs @@ -163,7 +163,8 @@ namespace Server.Engines.MLQuests.Objectives var left = Objective.DesiredAmount; - foreach (var item in pack.EnumerateItemsByType(false, ClaimTypePredicate)) + using var queue = pack.EnumerateItemsByType(false, ClaimTypePredicate); + foreach (var item in queue) { if (item.QuestItem && Objective.CheckItem(item)) { diff --git a/Projects/UOContent/Engines/ML Quests/Objectives/DeliverObjective.cs b/Projects/UOContent/Engines/ML Quests/Objectives/DeliverObjective.cs index c50ed99e7..6ed1117dc 100644 --- a/Projects/UOContent/Engines/ML Quests/Objectives/DeliverObjective.cs +++ b/Projects/UOContent/Engines/ML Quests/Objectives/DeliverObjective.cs @@ -192,7 +192,8 @@ namespace Server.Engines.MLQuests.Objectives var left = Objective.Amount; - foreach (var item in pack.EnumerateItems(false, ClaimTypePredicate)) + using var queue = pack.EnumerateItems(false, ClaimTypePredicate); + foreach (var item in queue) { if (left == 0) { diff --git a/Projects/UOContent/Holiday Stuff/Halloween/2006/Engines/TrickOrTreat.cs b/Projects/UOContent/Holiday Stuff/Halloween/2006/Engines/TrickOrTreat.cs index af6ffc439..f2e0c1116 100644 --- a/Projects/UOContent/Holiday Stuff/Halloween/2006/Engines/TrickOrTreat.cs +++ b/Projects/UOContent/Holiday Stuff/Halloween/2006/Engines/TrickOrTreat.cs @@ -1,342 +1,345 @@ using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; using ModernUO.Serialization; +using Server.Collections; using Server.Events.Halloween; using Server.Items; using Server.Mobiles; using Server.Targeting; -namespace Server.Engines.Events +namespace Server.Engines.Events; + +public static class TrickOrTreat { - public static class TrickOrTreat + public static void Initialize() { - public static TimeSpan OneSecond = TimeSpan.FromSeconds(1); + var now = Core.Now; - public static void Initialize() + if (now >= HolidaySettings.StartHalloween && now <= HolidaySettings.FinishHalloween) { - var now = Core.Now; - - if (now >= HolidaySettings.StartHalloween && now <= HolidaySettings.FinishHalloween) - { - EventSink.Speech += EventSink_Speech; - } - } - - private static void EventSink_Speech(SpeechEventArgs e) - { - if (e.Speech.InsensitiveContains("trick or treat")) - { - e.Mobile.Target = new TrickOrTreatTarget(); - - e.Mobile.SendLocalizedMessage(1076764); /* Pick someone to Trick or Treat. */ - } - } - - public static void Bleeding(Mobile m_From) - { - if (CheckMobile(m_From)) - { - if (m_From.Location != Point3D.Zero) - { - var amount = Utility.RandomMinMax(3, 7); - - for (var i = 0; i < amount; i++) - { - new Blood(Utility.RandomMinMax(0x122C, 0x122F)).MoveToWorld( - RandomPointOneAway(m_From.X, m_From.Y, m_From.Z, m_From.Map), - m_From.Map - ); - } - } - } - } - - public static void RemoveHueMod(Mobile target) - { - if (target?.Deleted == false) - { - target.SolidHueOverride = -1; - } - } - - public static void SolidHueMobile(Mobile target) - { - if (CheckMobile(target)) - { - target.SolidHueOverride = Utility.RandomMinMax(2501, 2644); - - Timer.StartTimer(TimeSpan.FromSeconds(10), () => RemoveHueMod(target)); - } - } - - public static void MakeTwin(Mobile m_From) - { - var m_Items = new List(); - - if (CheckMobile(m_From)) - { - var twin = new NaughtyTwin(m_From); - - if (twin.Deleted) - { - return; - } - - foreach (var item in m_From.Items) - { - if (item.Layer != Layer.Backpack && item.Layer != Layer.Mount && item.Layer != Layer.Bank) - { - m_Items.Add(item); - } - } - - if (m_Items.Count > 0) - { - for (var i = 0; i < m_Items.Count; i++) /* dupe exploits start out like this ... */ - { - twin.AddItem(Mobile.LiftItemDupe(m_Items[i], 1)); - } - - foreach (var item in twin.Items) /* ... and end like this */ - { - if (item.Layer != Layer.Backpack && item.Layer != Layer.Mount && item.Layer != Layer.Bank) - { - item.Movable = false; - } - } - } - - twin.Hue = m_From.Hue; - twin.Body = m_From.Body; - twin.Kills = m_From.Kills; - - var point = RandomPointOneAway(m_From.X, m_From.Y, m_From.Z, m_From.Map); - - twin.MoveToWorld(m_From.Map.CanSpawnMobile(point) ? point : m_From.Location, m_From.Map); - - Timer.StartTimer(TimeSpan.FromSeconds(5), () => DeleteTwin(twin)); - } - } - - public static void DeleteTwin(Mobile m_Twin) - { - if (CheckMobile(m_Twin)) - { - m_Twin.Delete(); - } - } - - public static Point3D RandomPointOneAway(int x, int y, int z, Map map) - { - var loc = new Point3D(x + Utility.Random(-1, 3), y + Utility.Random(-1, 3), 0); - - loc.Z = map.CanFit(loc, 0) ? map.GetAverageZ(loc.X, loc.Y) : z; - - return loc; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool CheckMobile(Mobile mobile) => - mobile?.Map != null && !mobile.Deleted && mobile.Alive && mobile.Map != Map.Internal; - - private class TrickOrTreatTarget : Target - { - public TrickOrTreatTarget() - : base(15, false, TargetFlags.None) - { - } - - protected override void OnTarget(Mobile from, object targ) - { - if (targ == null || !CheckMobile(from)) - { - return; - } - - if (targ is not Mobile) - { - from.SendLocalizedMessage(1076781); /* There is little chance of getting candy from that! */ - return; - } - - var begged = targ as BaseVendor; - - if (begged?.Deleted != false) - { - from.SendLocalizedMessage(1076765); /* That doesn't look friendly. */ - return; - } - - var now = Core.Now; - - if (CheckMobile(begged)) - { - if (begged.NextTrickOrTreat > now) - { - from.SendLocalizedMessage(1076767); /* That doesn't appear to have any more candy. */ - return; - } - - begged.NextTrickOrTreat = now + TimeSpan.FromMinutes(Utility.RandomMinMax(5, 10)); - - if (from.Backpack?.Deleted != false) - { - return; - } - - if (Utility.RandomDouble() < 0.90) - { - begged.Say( - Utility.Random(3) switch - { - 0 => 1076768, // Oooooh, aren't you cute! - 1 => 1076779, // All right...This better not spoil your dinner! - _ => 1076778 // Here you go! Enjoy! - } - ); - - if (Utility.RandomDouble() < 0.01 && from.Skills.Begging.Value >= 100) - { - from.AddToBackpack(HolidaySettings.RandomGMBeggerItem); - - from.SendLocalizedMessage(1076777); /* You receive a special treat! */ - } - else - { - from.AddToBackpack(HolidaySettings.RandomTreat); - - from.SendLocalizedMessage(1076769); /* You receive some candy. */ - } - } - else - { - begged.Say(1076770); /* TRICK! */ - - var action = Utility.Random(4); - - if (action == 0) - { - Timer.StartTimer(OneSecond, OneSecond, 10, () => Bleeding(from)); - } - else if (action == 1) - { - Timer.StartTimer(TimeSpan.FromSeconds(2), () => SolidHueMobile(from)); - } - else - { - Timer.StartTimer(TimeSpan.FromSeconds(2), () => MakeTwin(from)); - } - } - } - } + EventSink.Speech += EventSink_Speech; } } - [SerializationGenerator(0, false)] - public partial class NaughtyTwin : BaseCreature + private static void EventSink_Speech(SpeechEventArgs e) { - private static readonly Point3D[] Felucca_Locations = + if (e.Speech.InsensitiveContains("trick or treat")) { - new(4467, 1283, 5), // Moonglow - new(1336, 1997, 5), // Britain - new(1499, 3771, 5), // Jhelom - new(771, 752, 5), // Yew - new(2701, 692, 5), // Minoc - new(1828, 2948, -20), // Trinsic - new(643, 2067, 5), // Skara Brae - new(3563, 2139, Map.Trammel.GetAverageZ(3563, 2139)) // (New) Magincia - }; + e.Mobile.Target = new TrickOrTreatTarget(); - private static readonly Point3D[] Malas_Locations = + e.Mobile.SendLocalizedMessage(1076764); /* Pick someone to Trick or Treat. */ + } + } + + public static void Bleeding(Mobile from) + { + if (!CheckMobile(from)) { - new(1015, 527, -65), // Luna - new(1997, 1386, -85) // Umbra - }; + return; + } - private static readonly Point3D[] Ilshenar_Locations = + if (from.Location == Point3D.Zero) { - new(1215, 467, -13), // Compassion - new(722, 1366, -60), // Honesty - new(744, 724, -28), // Honor - new(281, 1016, 0), // Humility - new(987, 1011, -32), // Justice - new(1174, 1286, -30), // Sacrifice - new(1532, 1340, -3), // Spirituality - new(528, 216, -45), // Valor - new(1721, 218, 96) // Chaos - }; + return; + } - private static readonly Point3D[] Tokuno_Locations = + var amount = Utility.RandomMinMax(3, 7); + + for (var i = 0; i < amount; i++) { - new(1169, 998, 41), // Isamu-Jima - new(802, 1204, 25), // Makoto-Jima - new(270, 628, 15) // Homare-Jima - }; + new Blood(Utility.RandomMinMax(0x122C, 0x122F)).MoveToWorld( + RandomPointOneAway(from.X, from.Y, from.Z, from.Map), + from.Map + ); + } + } - private readonly Mobile m_From; - - public NaughtyTwin(Mobile from) : base(AIType.AI_Melee, FightMode.None) + public static void RemoveHueMod(Mobile target) + { + if (target?.Deleted == false) { - if (TrickOrTreat.CheckMobile(from)) + target.SolidHueOverride = -1; + } + } + + public static void SolidHueMobile(Mobile target) + { + if (CheckMobile(target)) + { + target.SolidHueOverride = Utility.RandomMinMax(2501, 2644); + + Timer.StartTimer(TimeSpan.FromSeconds(10), () => RemoveHueMod(target)); + } + } + + public static void MakeTwin(Mobile from) + { + if (!CheckMobile(from)) + { + return; + } + + var twin = new NaughtyTwin(from); + + if (twin.Deleted) + { + return; + } + + using var items = PooledRefQueue.Create(); + for (var i = 0; i < from.Items.Count; i++) + { + var item = from.Items[i]; + if (item.Layer != Layer.Backpack && item.Layer != Layer.Mount && item.Layer != Layer.Bank) { - Body = from.Body; - - m_From = from; - Name = $"{from.Name}\'s Naughty Twin"; - - Timer.StartTimer(TrickOrTreat.OneSecond, () => StealCandyOrGate(m_From)); + items.Enqueue(item); } } - public override void OnThink() + if (items.Count > 0) { - if (m_From?.Deleted != false) + while (items.Count > 0) { - Delete(); + twin.AddItem(Mobile.LiftItemDupe(items.Dequeue(), 1)); } - } - public static Item FindCandyTypes(Mobile target) - { - Type[] types = - { typeof(WrappedCandy), typeof(Lollipops), typeof(NougatSwirl), typeof(Taffy), typeof(JellyBeans) }; - - return TrickOrTreat.CheckMobile(target) ? target.Backpack.FindItemByType(types) : null; - } - - public static void StealCandyOrGate(Mobile target) - { - if (TrickOrTreat.CheckMobile(target)) + for (var i = 0; i < twin.Items.Count; i++) { - if (Utility.RandomBool()) + var item = twin.Items[i]; + if (item.Layer != Layer.Backpack && item.Layer != Layer.Mount && item.Layer != Layer.Bank) { - var item = FindCandyTypes(target); + item.Movable = false; + } + } + } - target.SendLocalizedMessage(1113967); /* Your naughty twin steals some of your candy. */ + twin.Hue = from.Hue; + twin.Body = from.Body; + twin.Kills = from.Kills; - if (item?.Deleted == false) + var point = RandomPointOneAway(from.X, from.Y, from.Z, from.Map); + + twin.MoveToWorld(from.Map.CanSpawnMobile(point) ? point : from.Location, from.Map); + + Timer.StartTimer(TimeSpan.FromSeconds(5), () => DeleteTwin(twin)); + } + + public static void DeleteTwin(Mobile twin) + { + if (CheckMobile(twin)) + { + twin.Delete(); + } + } + + public static Point3D RandomPointOneAway(int x, int y, int z, Map map) + { + var loc = new Point3D(x + Utility.Random(-1, 3), y + Utility.Random(-1, 3), 0); + + loc.Z = map.CanFit(loc, 0) ? map.GetAverageZ(loc.X, loc.Y) : z; + + return loc; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CheckMobile(Mobile mobile) => + mobile?.Map != null && !mobile.Deleted && mobile.Alive && mobile.Map != Map.Internal; + + private class TrickOrTreatTarget : Target + { + public TrickOrTreatTarget() : base(15, false, TargetFlags.None) + { + } + + protected override void OnTarget(Mobile from, object targ) + { + if (targ == null || !CheckMobile(from)) + { + return; + } + + if (targ is not Mobile) + { + from.SendLocalizedMessage(1076781); /* There is little chance of getting candy from that! */ + return; + } + + var begged = targ as BaseVendor; + + if (begged?.Deleted != false) + { + from.SendLocalizedMessage(1076765); /* That doesn't look friendly. */ + return; + } + + var now = Core.Now; + + if (CheckMobile(begged)) + { + if (begged.NextTrickOrTreat > now) + { + from.SendLocalizedMessage(1076767); /* That doesn't appear to have any more candy. */ + return; + } + + begged.NextTrickOrTreat = now + TimeSpan.FromMinutes(Utility.RandomMinMax(5, 10)); + + if (from.Backpack?.Deleted != false) + { + return; + } + + if (Utility.RandomDouble() < 0.90) + { + begged.Say( + Utility.Random(3) switch + { + 0 => 1076768, // Oooooh, aren't you cute! + 1 => 1076779, // All right...This better not spoil your dinner! + _ => 1076778 // Here you go! Enjoy! + } + ); + + if (Utility.RandomDouble() < 0.01 && from.Skills.Begging.Value >= 100) { - item.Delete(); + from.AddToBackpack(HolidaySettings.RandomGMBeggerItem); + + from.SendLocalizedMessage(1076777); /* You receive a special treat! */ + } + else + { + from.AddToBackpack(HolidaySettings.RandomTreat); + + from.SendLocalizedMessage(1076769); /* You receive some candy. */ } } else { - target.SendLocalizedMessage(1113972); /* Your naughty twin teleports you away with a naughty laugh! */ - target.MoveToWorld(RandomMoongate(target), target.Map); + begged.Say(1076770); /* TRICK! */ + + var action = Utility.Random(4); + + if (action == 0) + { + Timer.StartTimer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1), 10, () => Bleeding(from)); + } + else if (action == 1) + { + Timer.StartTimer(TimeSpan.FromSeconds(2), () => SolidHueMobile(from)); + } + else + { + Timer.StartTimer(TimeSpan.FromSeconds(2), () => MakeTwin(from)); + } } } } - - public static Point3D RandomMoongate(Mobile target) - { - return target.Map.MapID switch - { - 2 => Ilshenar_Locations.RandomElement(), - 3 => Malas_Locations.RandomElement(), - 4 => Tokuno_Locations.RandomElement(), - _ => Felucca_Locations.RandomElement() - }; - } + } +} + +[SerializationGenerator(0, false)] +public partial class NaughtyTwin : BaseCreature +{ + private static readonly Point3D[] Felucca_Locations = + { + new(4467, 1283, 5), // Moonglow + new(1336, 1997, 5), // Britain + new(1499, 3771, 5), // Jhelom + new(771, 752, 5), // Yew + new(2701, 692, 5), // Minoc + new(1828, 2948, -20), // Trinsic + new(643, 2067, 5), // Skara Brae + new(3563, 2139, Map.Trammel.GetAverageZ(3563, 2139)) // (New) Magincia + }; + + private static readonly Point3D[] Malas_Locations = + { + new(1015, 527, -65), // Luna + new(1997, 1386, -85) // Umbra + }; + + private static readonly Point3D[] Ilshenar_Locations = + { + new(1215, 467, -13), // Compassion + new(722, 1366, -60), // Honesty + new(744, 724, -28), // Honor + new(281, 1016, 0), // Humility + new(987, 1011, -32), // Justice + new(1174, 1286, -30), // Sacrifice + new(1532, 1340, -3), // Spirituality + new(528, 216, -45), // Valor + new(1721, 218, 96) // Chaos + }; + + private static readonly Point3D[] Tokuno_Locations = + { + new(1169, 998, 41), // Isamu-Jima + new(802, 1204, 25), // Makoto-Jima + new(270, 628, 15) // Homare-Jima + }; + + private readonly Mobile m_From; + + public NaughtyTwin(Mobile from) : base(AIType.AI_Melee, FightMode.None) + { + if (TrickOrTreat.CheckMobile(from)) + { + Body = from.Body; + + m_From = from; + Name = $"{from.Name}\'s Naughty Twin"; + + Timer.StartTimer(TimeSpan.FromSeconds(1), () => StealCandyOrGate(m_From)); + } + } + + public override void OnThink() + { + if (m_From?.Deleted != false) + { + Delete(); + } + } + + public static Item FindCandyTypes(Mobile target) + { + Type[] types = + { typeof(WrappedCandy), typeof(Lollipops), typeof(NougatSwirl), typeof(Taffy), typeof(JellyBeans) }; + + return TrickOrTreat.CheckMobile(target) ? target.Backpack.FindItemByType(types) : null; + } + + public static void StealCandyOrGate(Mobile target) + { + if (TrickOrTreat.CheckMobile(target)) + { + if (Utility.RandomBool()) + { + var item = FindCandyTypes(target); + + target.SendLocalizedMessage(1113967); /* Your naughty twin steals some of your candy. */ + + if (item?.Deleted == false) + { + item.Delete(); + } + } + else + { + target.SendLocalizedMessage(1113972); /* Your naughty twin teleports you away with a naughty laugh! */ + target.MoveToWorld(RandomMoongate(target), target.Map); + } + } + } + + public static Point3D RandomMoongate(Mobile target) + { + return target.Map.MapID switch + { + 2 => Ilshenar_Locations.RandomElement(), + 3 => Malas_Locations.RandomElement(), + 4 => Tokuno_Locations.RandomElement(), + _ => Felucca_Locations.RandomElement() + }; } } diff --git a/Projects/UOContent/Items/Containers/SalvageBag.cs b/Projects/UOContent/Items/Containers/SalvageBag.cs index 9e2b79a72..bb20b8775 100644 --- a/Projects/UOContent/Items/Containers/SalvageBag.cs +++ b/Projects/UOContent/Items/Containers/SalvageBag.cs @@ -201,7 +201,8 @@ public partial class SalvageBag : Bag var salvaged = 0; var notSalvaged = 0; - foreach (var item in EnumerateItems()) + using var queue = EnumerateItems(); + foreach (var item in queue) { if (item?.Deleted != false) { @@ -250,31 +251,37 @@ public partial class SalvageBag : Bag var salvaged = 0; var notSalvaged = 0; - foreach (var item in EnumerateItems()) + using (var queue = EnumerateItems()) { - if (item is not IScissorable scissorable) + foreach (var item in queue) { - continue; - } + if (item is not IScissorable scissorable) + { + continue; + } - if (Scissors.CanScissor(from, scissorable) && scissorable.Scissor(from, scissors)) - { - ++salvaged; - } - else - { - ++notSalvaged; + if (Scissors.CanScissor(from, scissorable) && scissorable.Scissor(from, scissors)) + { + ++salvaged; + } + else + { + ++notSalvaged; + } } } // Salvaged: ~1_COUNT~/~2_NUM~ tailored items from.SendLocalizedMessage(1079974, $"{salvaged}\t{salvaged + notSalvaged}"); - foreach (var item in EnumerateItems()) + using (var queue = EnumerateItems()) { - if (item.InTypeList(_clothTypes)) + foreach (var item in queue) { - from.AddToBackpack(item); + if (item.InTypeList(_clothTypes)) + { + from.AddToBackpack(item); + } } } } diff --git a/Projects/UOContent/Items/Misc/Key.cs b/Projects/UOContent/Items/Misc/Key.cs index 931ccecba..2d8ae0fd8 100644 --- a/Projects/UOContent/Items/Misc/Key.cs +++ b/Projects/UOContent/Items/Misc/Key.cs @@ -74,7 +74,8 @@ public partial class Key : Item return; } - foreach (var item in cont.EnumerateItems()) + using var queue = cont.EnumerateItems(); + foreach (var item in queue) { if (item is Key key) { diff --git a/Projects/UOContent/Items/Skill Items/Magical/Potions/BasePotion.cs b/Projects/UOContent/Items/Skill Items/Magical/Potions/BasePotion.cs index ea39645a7..4e2e39a67 100644 --- a/Projects/UOContent/Items/Skill Items/Magical/Potions/BasePotion.cs +++ b/Projects/UOContent/Items/Skill Items/Magical/Potions/BasePotion.cs @@ -85,7 +85,8 @@ public abstract partial class BasePotion : Item, ICraftable, ICommodity return 1; } - foreach (var keg in pack.EnumerateItemsByType()) + using var queue = pack.EnumerateItemsByType(); + foreach (var keg in queue) { if (keg.Held is <= 0 or >= 100) { diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index c1a21616c..df25f0f22 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -4868,25 +4868,28 @@ namespace Server.Mobiles public virtual void DropBackpack() { - if (Backpack?.Items.Count > 0) + var backpack = Backpack; + if (!(backpack?.Items.Count > 0)) { - var b = new CreatureBackpack(Name); + return; + } - var list = new List(Backpack.Items); - foreach (var item in list) - { - b.DropItem(item); - } + var b = new CreatureBackpack(Name); + using var queue = backpack.EnumerateItems(); - var house = BaseHouse.FindHouseAt(this); - if (house != null) - { - b.MoveToWorld(house.BanLocation, house.Map); - } - else - { - b.MoveToWorld(Location, Map); - } + while (queue.Count > 0) + { + b.DropItem(queue.Dequeue()); + } + + var house = BaseHouse.FindHouseAt(this); + if (house != null) + { + b.MoveToWorld(house.BanLocation, house.Map); + } + else + { + b.MoveToWorld(Location, Map); } } diff --git a/Projects/UOContent/Mobiles/Familiars/BaseFamiliar.cs b/Projects/UOContent/Mobiles/Familiars/BaseFamiliar.cs index 3ecc94db2..d3b119523 100644 --- a/Projects/UOContent/Mobiles/Familiars/BaseFamiliar.cs +++ b/Projects/UOContent/Mobiles/Familiars/BaseFamiliar.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using ModernUO.Serialization; using Server.Collections; using Server.ContextMenus; @@ -151,14 +150,15 @@ public abstract partial class BaseFamiliar : BaseCreature var map = Map; var pack = Backpack; - if (map != null && map != Map.Internal && pack != null) + if (map == null || map == Map.Internal || pack == null) { - var list = new List(pack.Items); + return; + } - for (var i = 0; i < list.Count; ++i) - { - list[i].MoveToWorld(Location, map); - } + using var queue = pack.EnumerateItems(); + while (queue.Count > 0) + { + queue.Dequeue().MoveToWorld(Location, map); } } diff --git a/Projects/UOContent/Mobiles/PlayerMobile.cs b/Projects/UOContent/Mobiles/PlayerMobile.cs index 3ac802b20..c26e3f918 100644 --- a/Projects/UOContent/Mobiles/PlayerMobile.cs +++ b/Projects/UOContent/Mobiles/PlayerMobile.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using ModernUO.CodeGeneratedEvents; using Server.Accounting; using Server.Collections; @@ -2402,9 +2403,9 @@ namespace Server.Mobiles } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool FindItems_Callback(Item item) => - !item.Deleted && (item.LootType == LootType.Blessed || item.Insured) && - Backpack != item.Parent; + !item.Deleted && (item.LootType == LootType.Blessed || item.Insured) && Backpack != item.Parent; public override bool OnBeforeDeath() { @@ -2418,7 +2419,8 @@ namespace Server.Mobiles // This fixes a "bug" where players put blessed items in nested bags and they were dropped on death if (Core.AOS && Backpack?.Deleted == false) { - foreach (var item in Backpack.EnumerateItems(true, FindItems_Callback)) + using var queue = Backpack.EnumerateItems(true, FindItems_Callback); + foreach (var item in queue) { Backpack.AddItem(item); } diff --git a/Projects/UOContent/Multis/Houses/MovingCrate.cs b/Projects/UOContent/Multis/Houses/MovingCrate.cs index d3127c1a3..6e12b0035 100644 --- a/Projects/UOContent/Multis/Houses/MovingCrate.cs +++ b/Projects/UOContent/Multis/Houses/MovingCrate.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using ModernUO.Serialization; using Server.Items; using Server.Network; @@ -195,18 +194,10 @@ public partial class MovingCrate : Container _internalizeTimer = null; } - var toRemove = new List(); - foreach (var item in Items) + using var queue = EnumerateItems(predicate: item => item is PackingBox && item.Items.Count == 0); + while (queue.Count > 0) { - if (item is PackingBox && item.Items.Count == 0) - { - toRemove.Add(item); - } - } - - foreach (var item in toRemove) - { - item.Delete(); + queue.Dequeue().Delete(); } if (TotalItems == 0)