From 28c06c1cc0c3b1f9c0467beddf70dae43e1d373a Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sun, 29 Oct 2023 22:42:46 -0700 Subject: [PATCH] fix: Changes Map.Sector.Mobiles to link list & Fixes various related crash bugs (#1553) ### Summary Eliminates `IPooledEnumerable` and `eable.Free()` from `Map` for mobiles. This drastically simplifies code that iterates in range, for example: ```cs foreach (var m in m.GetMobilesInRange(5)) { } ``` The code above no longer requires an eable and calling `Free()`. - [X] Fixed several locations where an NPC that was damaged would cause a server crash. - [X] Removed an unnecessary allocation in guard fake calls (NPCs calling guards on you) - [X] Fixes damage precision loss in Poison Strike Spell - [X] BogThing no longer attempts to "search" for boglings to eat when it is at full health --- Projects/Server/Items/Item.cs | 27 +- Projects/Server/Maps/Map.MobileEnumerator.cs | 288 ++++++++++++++++++ Projects/Server/Maps/Map.cs | 52 ++-- Projects/Server/Maps/PooledEnumeration.cs | 28 +- Projects/Server/Mobiles/Mobile.cs | 28 +- .../Commands/Object Creation/Decorate.cs | 4 +- .../Doom/LeverPuzzle/LeverPuzzleController.cs | 36 ++- .../Items/Power Faction Items/StormsEye.cs | 13 +- .../Items/Construction/Doors/BaseDoor.cs | 26 +- Projects/UOContent/Items/Misc/Acid.cs | 2 +- Projects/UOContent/Items/Misc/Firebomb.cs | 3 +- Projects/UOContent/Items/Misc/MorphItem.cs | 3 +- .../BaseConflagrationPotion.cs | 18 +- .../BaseConfusionBlastPotion.cs | 7 +- .../Items/Skill Items/Misc/FireHorn.cs | 3 +- .../UOContent/Items/Traps/FlameSpurtTrap.cs | 2 + Projects/UOContent/Items/Traps/SpikeTrap.cs | 14 +- .../UOContent/Items/Traps/StoneFaceTrap.cs | 10 +- .../Weapons/Abilities/FrenziedWhirlwind.cs | 3 +- .../Weapons/Abilities/WhirlwindAttack.cs | 3 +- .../UOContent/Items/Weapons/BaseWeapon.cs | 11 +- Projects/UOContent/Mobiles/AI/BaseAI.cs | 8 +- Projects/UOContent/Mobiles/AI/HealerAI.cs | 8 +- .../Abilities/AreaEffectMonsterAbility.cs | 3 +- Projects/UOContent/Mobiles/BaseCreature.cs | 6 +- .../UOContent/Mobiles/Familiars/ShadowWisp.cs | 13 +- .../Monsters/Humanoid/Magic/SavageShaman.cs | 273 ++++++++--------- .../Monsters/Humanoid/Melee/OrcBrute.cs | 18 +- .../Mobiles/Monsters/LBR/Meers/MeerCaptain.cs | 4 +- .../Mobiles/Monsters/LBR/Meers/MeerEternal.cs | 3 +- .../Monsters/ML/Humanoid/Magic/MLDryad.cs | 7 +- .../Mobiles/Monsters/ML/Special/Ilhenir.cs | 27 +- .../Mobiles/Monsters/ML/Special/Meraktus.cs | 29 +- .../Monsters/Misc/Melee/BladeSpirits.cs | 17 +- .../Monsters/Misc/Melee/EnergyVortex.cs | 17 +- .../Mobiles/Monsters/Plant/Melee/BogThing.cs | 30 +- .../UOContent/Mobiles/Monsters/SE/Yamandon.cs | 12 +- Projects/UOContent/Mobiles/PlayerMobile.cs | 4 +- .../UOContent/Mobiles/Special/Barracoon.cs | 11 +- .../UOContent/Mobiles/Special/Harrower.cs | 3 +- .../Mobiles/Special/HarrowerTentacles.cs | 12 +- Projects/UOContent/Mobiles/Special/Rikktor.cs | 24 +- Projects/UOContent/Mobiles/Special/Serado.cs | 12 +- .../UOContent/Mobiles/Vendors/PlayerVendor.cs | 4 +- .../UOContent/Multis/Houses/HousePlacement.cs | 6 +- Projects/UOContent/Regions/GuardedRegion.cs | 31 +- Projects/UOContent/Skills/DetectHidden.cs | 38 +-- Projects/UOContent/Skills/Hiding.cs | 3 +- Projects/UOContent/Skills/Peacemaking.cs | 15 +- .../UOContent/Skills/Tracking/Tracking.cs | 21 +- .../Spells/Bushido/MomentumStrike.cs | 3 +- .../UOContent/Spells/Chivalry/DispelEvil.cs | 3 +- .../UOContent/Spells/Chivalry/HolyLight.cs | 9 + .../Spells/Chivalry/NobleSacrifice.cs | 3 +- .../UOContent/Spells/Eighth/Earthquake.cs | 3 +- .../UOContent/Spells/Fifth/PoisonField.cs | 14 +- Projects/UOContent/Spells/Fourth/ArchCure.cs | 3 +- .../UOContent/Spells/Fourth/ArchProtection.cs | 4 +- Projects/UOContent/Spells/Fourth/FireField.cs | 6 +- .../Spells/Mysticism/CleansingWindsSpell.cs | 18 +- .../UOContent/Spells/Necromancy/Exorcism.cs | 14 +- .../Spells/Necromancy/PoisonStrike.cs | 3 +- .../UOContent/Spells/Necromancy/Wither.cs | 5 +- .../Spells/Seventh/ChainLightning.cs | 6 +- .../UOContent/Spells/Seventh/MassDispel.cs | 5 +- .../UOContent/Spells/Seventh/MeteorSwarm.cs | 3 +- Projects/UOContent/Spells/Sixth/MassCurse.cs | 4 +- Projects/UOContent/Spells/Sixth/Reveal.cs | 8 +- .../Spells/Spellweaving/ArcaneCircle.cs | 7 +- .../Spells/Spellweaving/EssenceOfWind.cs | 13 +- .../Spells/Spellweaving/Thunderstorm.cs | 13 +- 71 files changed, 796 insertions(+), 591 deletions(-) create mode 100644 Projects/Server/Maps/Map.MobileEnumerator.cs diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs index faf042d15..267150fe0 100644 --- a/Projects/Server/Items/Item.cs +++ b/Projects/Server/Items/Item.cs @@ -2488,21 +2488,24 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt public Map.ItemBoundsEnumerable GetItemsInRange(int range) where T : Item => m_Map == null ? Map.ItemBoundsEnumerable.Empty : m_Map.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range); - public IPooledEnumerable GetMobilesInRange(int range) - { - var map = m_Map; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.MobileAtEnumerable GetMobilesAt() => GetMobilesAt(); - return map?.GetMobilesInRange(m_Parent == null ? m_Location : GetWorldLocation(), range) - ?? PooledEnumeration.NullEnumerable.Instance; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.MobileAtEnumerable GetMobilesAt() where T : Mobile => + m_Map == null ? Map.MobileAtEnumerable.Empty : m_Map.GetMobilesAt(m_Parent == null ? m_Location : GetWorldLocation()); - public IPooledEnumerable GetClientsInRange(int range) - { - var map = m_Map; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.MobileBoundsEnumerable GetMobilesInRange(int range) => GetMobilesInRange(range); - return map.GetClientsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range) - ?? PooledEnumeration.NullEnumerable.Instance; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.MobileBoundsEnumerable GetMobilesInRange(int range) where T : Mobile => + m_Map == null ? Map.MobileBoundsEnumerable.Empty : m_Map.GetMobilesInRange(m_Parent == null ? m_Location : GetWorldLocation(), range); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IPooledEnumerable GetClientsInRange(int range) => + m_Map.GetClientsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range) + ?? PooledEnumeration.NullEnumerable.Instance; public bool GetTempFlag(int flag) => ((LookupCompactInfo()?.m_TempFlags ?? 0) & flag) != 0; diff --git a/Projects/Server/Maps/Map.MobileEnumerator.cs b/Projects/Server/Maps/Map.MobileEnumerator.cs new file mode 100644 index 000000000..adcb5e40d --- /dev/null +++ b/Projects/Server/Maps/Map.MobileEnumerator.cs @@ -0,0 +1,288 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2023 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Map.MobileEnumerator.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.Runtime.CompilerServices; +using Server.Collections; + +namespace Server; + +public partial class Map +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileAtEnumerable GetMobilesAt(Point3D p) => GetMobilesAt(p); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileAtEnumerable GetMobilesAt(Point3D p) where T : Mobile => GetMobilesAt(new Point2D(p.X, p.Y)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileAtEnumerable GetMobilesAt(int x, int y) => GetMobilesAt(new Point2D(x, y)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileAtEnumerable GetMobilesAt(int x, int y) where T : Mobile => GetMobilesAt(new Point2D(x, y)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileAtEnumerable GetMobilesAt(Point2D p) => GetMobilesAt(p); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileAtEnumerable GetMobilesAt(Point2D p) where T : Mobile => new(this, p); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileBoundsEnumerable GetMobilesInRange(Point3D p) => GetMobilesInRange(p); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileBoundsEnumerable GetMobilesInRange(Point3D p, int range) => GetMobilesInRange(p, range); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileBoundsEnumerable GetMobilesInRange(Point3D p) where T : Mobile => GetMobilesInRange(p, Core.GlobalMaxUpdateRange); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileBoundsEnumerable GetMobilesInRange(Point3D p, int range) where T : Mobile => + GetMobilesInRange(p.m_X, p.m_Y, range); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileBoundsEnumerable GetMobilesInRange(Point2D p) => GetMobilesInRange(p); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileBoundsEnumerable GetMobilesInRange(Point2D p, int range) => GetMobilesInRange(p, range); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileBoundsEnumerable GetMobilesInRange(Point2D p) where T : Mobile => GetMobilesInRange(p, Core.GlobalMaxUpdateRange); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileBoundsEnumerable GetMobilesInRange(Point2D p, int range) where T : Mobile => + GetMobilesInRange(p.m_X, p.m_Y, range); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileBoundsEnumerable GetMobilesInRange(int x, int y, int range) where T : Mobile => + GetMobilesInBounds(new Rectangle2D(x - range, y - range, range * 2 + 1, range * 2 + 1)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileBoundsEnumerable GetMobilesInBounds(Rectangle2D bounds) => GetMobilesInBounds(bounds); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileBoundsEnumerable GetMobilesInBounds(Rectangle2D bounds, bool makeBoundsInclusive = false) where T : Mobile => + new(this, bounds, makeBoundsInclusive); + + public ref struct MobileAtEnumerable where T : Mobile + { + public static MobileAtEnumerable Empty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(); + } + + private Map _map; + private Point2D _location; + + public MobileAtEnumerable(Map map, Point2D loc) + { + _map = map; + _location = loc; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileAtEnumerator GetEnumerator() => new(_map, _location); + } + + public ref struct MobileAtEnumerator where T : Mobile + { + private bool _started; + private Point2D _location; + private ref readonly ValueLinkList _linkList; + private int _version; + private T _current; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileAtEnumerator(Map map, Point2D loc) + { + _started = false; + _location = loc; + _linkList = ref map.GetRealSector(loc.m_X, loc.m_Y).Mobiles; + _version = 0; + _current = null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + ref var loc = ref _location; + Mobile current; + + if (!_started) + { + current = _linkList._first; + _started = true; + _version = _linkList.Version; + + if (current is T { Deleted: false } o && o.X == loc.m_X && o.Y == loc.m_Y) + { + _current = o; + return true; + } + } + else if (_linkList.Version != _version) + { + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); + } + else + { + current = _current; + } + + while (current != null) + { + current = current.Next; + + if (current is T { Deleted: false } o && o.X == loc.m_X && o.Y == loc.m_Y) + { + _current = o; + return true; + } + } + + return false; + } + + public T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _current; + } + } + + public ref struct MobileBoundsEnumerable where T : Mobile + { + public static MobileBoundsEnumerable Empty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(null, Rectangle2D.Empty, false); + } + + private Map _map; + private Rectangle2D _bounds; + private bool _makeBoundsInclusive; + + public MobileBoundsEnumerable(Map map, Rectangle2D bounds, bool makeBoundsInclusive) + { + _map = map; + _bounds = bounds; + _makeBoundsInclusive = makeBoundsInclusive; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileEnumerator GetEnumerator() => new(_map, _bounds, _makeBoundsInclusive); + } + + public ref struct MobileEnumerator where T : Mobile + { + private Map _map; + private int _sectorStartX; + private int _sectorEndX; + private int _sectorEndY; + private Rectangle2D _bounds; + + private int _currentSectorX; + private int _currentSectorY; + + private ref readonly ValueLinkList _linkList; + private int _currentVersion; + private T _current; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MobileEnumerator(Map map, Rectangle2D bounds, bool makeBoundsInclusive) + { + _map = map; + _bounds = bounds; + + if (makeBoundsInclusive) + { + ++bounds.Width; + ++bounds.Height; + } + + _bounds = bounds; + + map.CalculateSectors(bounds, out _sectorStartX, out var _sectorStartY, out _sectorEndX, out _sectorEndY); + + // We start the X sector one short because it gets incremented immediately in MoveNext() + _currentSectorX = _sectorStartX - 1; + _currentSectorY = _sectorStartY; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + var map = _map; + + if (map == null) + { + return false; + } + + Mobile current = _current; + ref Rectangle2D bounds = ref _bounds; + var currentSectorX = _currentSectorX; + var currentSectorY = _currentSectorY; + var sectorEndX = _sectorEndX; + var sectorEndY = _sectorEndY; + + while (true) + { + current = current?.Next; + + while (current == null) + { + // Move to next sector + if (currentSectorX < sectorEndX) + { + _currentSectorX = ++currentSectorX; + } + else if (currentSectorY < sectorEndY) + { + _currentSectorX = currentSectorX = _sectorStartX; + _currentSectorY = ++currentSectorY; + } + else + { + // Ran out of sectors + return false; + } + + _linkList = ref map.GetRealSector(currentSectorX, currentSectorY).Mobiles; + _currentVersion = _linkList.Version; + current = _linkList._first; + } + + if (_linkList.Version != _currentVersion) + { + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); + } + + if (current is T { Deleted: false } o && bounds.Contains(o.Location)) + { + _current = o; + return true; + } + } + } + + public T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _current; + } + } +} diff --git a/Projects/Server/Maps/Map.cs b/Projects/Server/Maps/Map.cs index 6a5d5be1a..8d5439e82 100644 --- a/Projects/Server/Maps/Map.cs +++ b/Projects/Server/Maps/Map.cs @@ -614,8 +614,10 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void OnEnter(Mobile m) => OnEnter(m.Location, m); + public void OnEnter(Mobile m) + { + OnEnter(m.Location, m); + } public void OnEnter(Point3D p, Mobile m) { @@ -650,8 +652,10 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void OnLeave(Mobile m) => OnLeave(m.Location, m); + public void OnLeave(Mobile m) + { + OnLeave(m.Location, m); + } public void OnLeave(Point3D p, Mobile m) { @@ -877,18 +881,6 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa public IPooledEnumerable GetClientsInBounds(Rectangle2D bounds) => PooledEnumeration.GetClients(this, bounds); - public IPooledEnumerable GetMobilesInRange(Point3D p) => GetMobilesInRange(p, Core.GlobalMaxUpdateRange); - - public IPooledEnumerable GetMobilesInRange(Point3D p, int range) => GetMobilesInRange(p, range); - - public IPooledEnumerable GetMobilesInRange(Point3D p, int range) where T : Mobile => - GetMobilesInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); - - public IPooledEnumerable GetMobilesInBounds(Rectangle2D bounds) => GetMobilesInBounds(bounds); - - public IPooledEnumerable GetMobilesInBounds(Rectangle2D bounds) where T : Mobile => - PooledEnumeration.GetMobiles(this, bounds); - public bool CanFit( Point3D p, int height, bool checkBlocksFit = false, bool checkMobiles = true, bool requireSurface = true @@ -981,10 +973,8 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa if (checkMobiles) { - for (var i = 0; i < mobs.Count; ++i) + foreach (var m in sector.Mobiles) { - var m = mobs[i]; - if (m.Location.m_X == x && m.Location.m_Y == y && (m.AccessLevel == AccessLevel.Player || !m.Hidden) && m.Z + 16 > z && z + height > m.Z) { @@ -1443,7 +1433,7 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa private bool m_Active; private List _clients; private ValueLinkList _items; - private List _mobiles; + private ValueLinkList _mobiles; private List _multis; private List _regions; @@ -1459,7 +1449,7 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa public List Multis => _multis ?? m_DefaultMultiList; - public List Mobiles => _mobiles ?? m_DefaultMobileList; + internal ref ValueLinkList Mobiles => ref _mobiles; internal ref readonly ValueLinkList Items => ref _items; @@ -1490,7 +1480,7 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa public void OnEnter(Mobile mob) { - Utility.Add(ref _mobiles, mob); + _mobiles.AddLast(mob); if (mob.NetState != null) { @@ -1502,7 +1492,7 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa public void OnLeave(Mobile mob) { - Utility.Remove(ref _mobiles, mob); + _mobiles.Remove(mob); if (mob.NetState != null) { @@ -1547,7 +1537,7 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa private void UpdateMobileRegions() { - if (_mobiles != null) + if (_mobiles.Count > 0) { using var queue = PooledRefQueue.Create(_mobiles.Count); foreach (var mob in _mobiles) @@ -1581,12 +1571,9 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa item.OnSectorActivate(); } - if (_mobiles != null) + foreach (var mob in _mobiles) { - foreach (var mob in _mobiles) - { - mob.OnSectorActivate(); - } + mob.OnSectorActivate(); } m_Active = true; @@ -1602,12 +1589,9 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa item.OnSectorDeactivate(); } - if (_mobiles != null) + foreach (var mob in _mobiles) { - foreach (var mob in _mobiles) - { - mob.OnSectorDeactivate(); - } + mob.OnSectorDeactivate(); } m_Active = false; diff --git a/Projects/Server/Maps/PooledEnumeration.cs b/Projects/Server/Maps/PooledEnumeration.cs index ef718e30b..a260d7dc4 100644 --- a/Projects/Server/Maps/PooledEnumeration.cs +++ b/Projects/Server/Maps/PooledEnumeration.cs @@ -35,7 +35,6 @@ public static class PooledEnumeration { ClientSelector = SelectClients; EntitySelector = SelectEntities; - MobileSelector = SelectMobiles; MultiSelector = SelectMultis; MultiTileSelector = SelectMultiTiles; } @@ -65,13 +64,9 @@ public static class PooledEnumeration public static IEnumerable SelectEntities(Map.Sector s, Rectangle2D bounds) { var entities = new List(s.Mobiles.Count + s.Items.Count); - for (int i = s.Mobiles.Count - 1; i >= 0; --i) + foreach (var mob in s.Mobiles) { - Mobile mob = s.Mobiles[i]; - if (mob is { Deleted: false } && bounds.Contains(mob.Location)) - { - entities.Add(mob); - } + entities.Add(mob); } foreach (var item in s.Items) @@ -82,19 +77,6 @@ public static class PooledEnumeration return entities; } - public static IEnumerable SelectMobiles(Map.Sector s, Rectangle2D bounds) where T : Mobile - { - var entities = new List(s.Mobiles.Count); - for (int i = s.Mobiles.Count - 1; i >= 0; --i) - { - if (s.Mobiles[i] is T { Deleted: false } mob && bounds.Contains(mob.Location)) - { - entities.Add(mob); - } - } - return entities; - } - public static IEnumerable SelectMultis(Map.Sector s, Rectangle2D bounds) { var entities = new List(s.Multis.Count); @@ -169,12 +151,6 @@ public static class PooledEnumeration public static PooledEnumerable GetEntities(Map map, Rectangle2D bounds) => PooledEnumerable.Instantiate(map, bounds, EntitySelector ?? SelectEntities); - public static PooledEnumerable GetMobiles(Map map, Rectangle2D bounds) => - GetMobiles(map, bounds); - - public static PooledEnumerable GetMobiles(Map map, Rectangle2D bounds) where T : Mobile => - PooledEnumerable.Instantiate(map, bounds, SelectMobiles); - public static PooledEnumerable GetMultis(Map map, Rectangle2D bounds) => PooledEnumerable.Instantiate(map, bounds, MultiSelector ?? SelectMultis); diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index 4b3ff0eab..cb9f11b3c 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -4154,10 +4154,8 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro if (oldSector != newSector) { using var queue = PooledRefQueue.Create(2048); - for (var i = 0; i < oldSector.Mobiles.Count; ++i) + foreach (var m in oldSector.Mobiles) { - var m = oldSector.Mobiles[i]; - if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z) { queue.Enqueue(m); @@ -4189,10 +4187,8 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro } } - for (var i = 0; i < newSector.Mobiles.Count; ++i) + foreach (var m in newSector.Mobiles) { - var m = newSector.Mobiles[i]; - if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z) { queue.Enqueue(m); @@ -4227,9 +4223,8 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro else { using var queue = PooledRefQueue<(IEntity, byte)>.Create(2048); - for (var i = 0; i < oldSector.Mobiles.Count; ++i) + foreach (var m in oldSector.Mobiles) { - var m = oldSector.Mobiles[i]; byte flag; if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z) { @@ -8126,14 +8121,25 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro public Map.ItemBoundsEnumerable GetItemsInRange(int range) where T : Item => m_Map == null ? Map.ItemBoundsEnumerable.Empty : m_Map.GetItemsInRange(m_Location, range); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public IPooledEnumerable GetObjectsInRange(int range) => m_Map?.GetObjectsInRange(m_Location, range) ?? PooledEnumeration.NullEnumerable.Instance; - public IPooledEnumerable GetMobilesInRange(int range) => GetMobilesInRange(range); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.MobileAtEnumerable GetMobilesInRange() => GetMobilesInRange(); - public IPooledEnumerable GetMobilesInRange(int range) where T : Mobile => - m_Map?.GetMobilesInRange(m_Location, range) ?? PooledEnumeration.NullEnumerable.Instance; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.MobileAtEnumerable GetMobilesInRange() where T : Mobile => + m_Map == null ? Map.MobileAtEnumerable.Empty : m_Map.GetMobilesAt(m_Location); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.MobileBoundsEnumerable GetMobilesInRange(int range) => GetMobilesInRange(range); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.MobileBoundsEnumerable GetMobilesInRange(int range) where T : Mobile => + m_Map == null ? Map.MobileBoundsEnumerable.Empty : m_Map.GetMobilesInRange(m_Location, range); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public IPooledEnumerable GetClientsInRange(int range) => m_Map?.GetClientsInRange(m_Location, range) ?? PooledEnumeration.NullEnumerable.Instance; diff --git a/Projects/UOContent/Commands/Object Creation/Decorate.cs b/Projects/UOContent/Commands/Object Creation/Decorate.cs index 67ac954da..426d1ed87 100644 --- a/Projects/UOContent/Commands/Object Creation/Decorate.cs +++ b/Projects/UOContent/Commands/Object Creation/Decorate.cs @@ -1175,10 +1175,8 @@ namespace Server.Commands if (item is BaseDoor door) { - var eable = maps[j].GetItemsInRange(loc, 1); var itemType = door.GetType(); - - foreach (var link in eable) + foreach (var link in maps[j].GetItemsInRange(loc, 1)) { if (link != item && link.Z == door.Z && link.GetType() == itemType) { diff --git a/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs b/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs index 29a803fde..dc849800e 100644 --- a/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs +++ b/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using ModernUO.Serialization; +using Server.Collections; using Server.Mobiles; using Server.Network; using Server.Spells; @@ -605,30 +606,35 @@ public partial class LeverPuzzleController : Item PlayerSendASCII(m_Player, 1); // A speeding rock ... PlaySounds(m_Player.Location, !m_Player.Female ? fs2 : ms2); + using var queue = PooledRefQueue.Create(); var j = Utility.Random(6, 10); for (var i = 0; i < j; i++) { - IEntity m_IEntity = new Entity(Serial.Zero, RandomPointIn(m_Player.Location, 10), m_Player.Map); + var entity = new Entity(Serial.Zero, RandomPointIn(m_Player.Location, 10), m_Player.Map); - var eable = m_IEntity.Map.GetMobilesInRange(m_IEntity.Location, 2); - var mobiles = new List(); - mobiles.AddRange(eable); - - for (var k = 0; k < mobiles.Count; k++) + // Queue the mobiles because we cannot modify while iterating through mobiles in range + foreach (var m in entity.Map.GetMobilesInRange(entity.Location, 2)) { - if (IsValidDamagable(mobiles[k]) && mobiles[k] != m_Player) + if (IsValidDamagable(m) && m != m_Player) { - PlayEffect(m_Player, mobiles[k], Rock(), 8, true); - DoDamage(mobiles[k], 25, 30, false); - - if (mobiles[k].Player) - { - POHMessage(mobiles[k], 2); // OUCH! - } + queue.Enqueue(m); } } - PlayEffect(m_Player, m_IEntity, Rock(), 8, false); + while (queue.Count > 0) + { + var m = queue.Dequeue(); + + PlayEffect(m_Player, m, Rock(), 8, true); + DoDamage(m, 25, 30, false); + + if (m.Player) + { + POHMessage(m, 2); // OUCH! + } + } + + PlayEffect(m_Player, entity, Rock(), 8, false); } } } diff --git a/Projects/UOContent/Engines/Factions/Items/Power Faction Items/StormsEye.cs b/Projects/UOContent/Engines/Factions/Items/Power Faction Items/StormsEye.cs index e4821e8bc..06319c84f 100644 --- a/Projects/UOContent/Engines/Factions/Items/Power Faction Items/StormsEye.cs +++ b/Projects/UOContent/Engines/Factions/Items/Power Faction Items/StormsEye.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using Server.Collections; using Server.Factions; using Server.Spells; using Server.Targeting; @@ -90,20 +90,21 @@ namespace Server private static void OnHit(Mobile from, Point3D origin, Map facet) { - var eable = facet.GetMobilesInRange(origin, 12); - var targets = new List(); - foreach (var m in eable) + using var queue = PooledRefQueue.Create(); + foreach (var m in facet.GetMobilesInRange(origin, 12)) { if (from.CanBeHarmful(m, false) && m.InLOS(new Point3D(origin.X, origin.Y, origin.Z + 1)) && Faction.Find(m) != null) { - targets.Add(from); + queue.Enqueue(from); } } - foreach (var mob in targets) + while (queue.Count > 0) { + var mob = queue.Dequeue(); + var damage = mob.Hits * 6 / 10; if (!mob.Player && damage < 10) diff --git a/Projects/UOContent/Items/Construction/Doors/BaseDoor.cs b/Projects/UOContent/Items/Construction/Doors/BaseDoor.cs index bae1dc5ef..c0a298f6e 100644 --- a/Projects/UOContent/Items/Construction/Doors/BaseDoor.cs +++ b/Projects/UOContent/Items/Construction/Doors/BaseDoor.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using ModernUO.Serialization; +using Server.Collections; using Server.Targeting; namespace Server.Items; @@ -343,28 +344,13 @@ public abstract partial class BaseDoor : Item, ILockable, ITelekinesisable } } - var mobs = map.GetSector(x, y).Mobiles; - - for (var i = 0; i < mobs.Count; ++i) + foreach (var m in map.GetMobilesAt(x, y)) { - var m = mobs[i]; - - if (m.Location.X == x && m.Location.Y == y) + // At the same location, not hidden, or is a player, alive, and within z-bounds - then cannot fit + if (m.Location.X == x && m.Location.Y == y && + (!m.Hidden || m.AccessLevel == AccessLevel.Player) && m.Alive && m.Z + 16 > z && z + height > m.Z) { - if (m.Hidden && m.AccessLevel > AccessLevel.Player) - { - continue; - } - - if (!m.Alive) - { - continue; - } - - if (m.Z + 16 > z && z + height > m.Z) - { - return false; - } + return false; } } diff --git a/Projects/UOContent/Items/Misc/Acid.cs b/Projects/UOContent/Items/Misc/Acid.cs index 5880f8b98..4f730610d 100644 --- a/Projects/UOContent/Items/Misc/Acid.cs +++ b/Projects/UOContent/Items/Misc/Acid.cs @@ -64,7 +64,7 @@ public partial class Acid : Item using var queue = PooledRefQueue.Create(); - foreach (var m in GetMobilesInRange(0)) + foreach (var m in GetMobilesAt()) { if (m.AccessLevel == AccessLevel.Player && m.Alive && !m.IsDeadBondedPet && (m is not BaseCreature bc || bc.Controlled || bc.Summoned)) diff --git a/Projects/UOContent/Items/Misc/Firebomb.cs b/Projects/UOContent/Items/Misc/Firebomb.cs index 9f1de58f6..2916ec80f 100644 --- a/Projects/UOContent/Items/Misc/Firebomb.cs +++ b/Projects/UOContent/Items/Misc/Firebomb.cs @@ -122,9 +122,8 @@ public partial class Firebomb : Item } else if (RootParent == null) { - var eable = Map.GetMobilesInRange(Location, 1); using var targets = PooledRefQueue.Create(); - foreach (var m in eable) + foreach (var m in Map.GetMobilesInRange(Location, 1)) { if (m_LitBy == null || SpellHelper.ValidIndirectTarget(m_LitBy, m) && m_LitBy.CanBeHarmful(m, false)) diff --git a/Projects/UOContent/Items/Misc/MorphItem.cs b/Projects/UOContent/Items/Misc/MorphItem.cs index ca5ecadc1..c5dd9c0de 100644 --- a/Projects/UOContent/Items/Misc/MorphItem.cs +++ b/Projects/UOContent/Items/Misc/MorphItem.cs @@ -78,8 +78,7 @@ public partial class MorphItem : Item public void Refresh() { var found = false; - var eable = GetMobilesInRange(CurrentRange); - foreach (var mob in eable) + foreach (var mob in GetMobilesInRange(CurrentRange)) { if (!mob.Hidden || mob.AccessLevel <= AccessLevel.Player) { diff --git a/Projects/UOContent/Items/Skill Items/Magical/Potions/Conflagration Potions/BaseConflagrationPotion.cs b/Projects/UOContent/Items/Skill Items/Magical/Potions/Conflagration Potions/BaseConflagrationPotion.cs index ceead34c0..fc0e501a5 100644 --- a/Projects/UOContent/Items/Skill Items/Magical/Potions/Conflagration Potions/BaseConflagrationPotion.cs +++ b/Projects/UOContent/Items/Skill Items/Magical/Potions/Conflagration Potions/BaseConflagrationPotion.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using ModernUO.Serialization; +using Server.Collections; using Server.Spells; using Server.Targeting; @@ -275,17 +276,24 @@ public abstract partial class BaseConflagrationPotion : BasePotion return; } - foreach (var m in _item.GetMobilesInRange(0)) + using var queue = PooledRefQueue.Create(); + foreach (var m in _item.GetMobilesAt()) { if (m.Z + 16 > _item.Z && _item.Z + 12 > m.Z && (!Core.AOS || m != from) && SpellHelper.ValidIndirectTarget(from, m) && from.CanBeHarmful(m, false)) { - from.DoHarmful(m); - - AOS.Damage(m, from, _item.GetDamage(), 0, 100, 0, 0, 0); - m.PlaySound(0x208); + queue.Enqueue(m); } } + + while (queue.Count > 0) + { + var m = queue.Dequeue(); + + from.DoHarmful(m); + AOS.Damage(m, from, _item.GetDamage(), 0, 100, 0, 0, 0); + m.PlaySound(0x208); + } } } } diff --git a/Projects/UOContent/Items/Skill Items/Magical/Potions/Confusion Blast Potions/BaseConfusionBlastPotion.cs b/Projects/UOContent/Items/Skill Items/Magical/Potions/Confusion Blast Potions/BaseConfusionBlastPotion.cs index 7b6934be4..fa8c91c79 100644 --- a/Projects/UOContent/Items/Skill Items/Magical/Potions/Confusion Blast Potions/BaseConfusionBlastPotion.cs +++ b/Projects/UOContent/Items/Skill Items/Magical/Potions/Confusion Blast Potions/BaseConfusionBlastPotion.cs @@ -82,13 +82,8 @@ public abstract partial class BaseConfusionBlastPotion : BasePotion foreach (var mobile in map.GetMobilesInRange(loc, Radius)) { - if (mobile is BaseCreature mon) + if (mobile is BaseCreature { Controlled: false, Summoned: false } mon) { - if (mon.Controlled || mon.Summoned) - { - continue; - } - mon.Pacify(from, Core.Now + TimeSpan.FromSeconds(5.0)); // TODO check } } diff --git a/Projects/UOContent/Items/Skill Items/Misc/FireHorn.cs b/Projects/UOContent/Items/Skill Items/Misc/FireHorn.cs index b53909a86..68d293405 100644 --- a/Projects/UOContent/Items/Skill Items/Misc/FireHorn.cs +++ b/Projects/UOContent/Items/Skill Items/Misc/FireHorn.cs @@ -98,10 +98,9 @@ public partial class FireHorn : Item ); var playerVsPlayer = false; - var eable = from.Map.GetMobilesInRange(loc, 2); using var targets = PooledRefQueue.Create(); - foreach (var m in eable) + foreach (var m in from.Map.GetMobilesInRange(loc, 2)) { if (from != m && SpellHelper.ValidIndirectTarget(from, m) && from.CanBeHarmful(m, false) && (!Core.AOS || from.InLOS(m))) diff --git a/Projects/UOContent/Items/Traps/FlameSpurtTrap.cs b/Projects/UOContent/Items/Traps/FlameSpurtTrap.cs index 5769db6a8..31bff1636 100644 --- a/Projects/UOContent/Items/Traps/FlameSpurtTrap.cs +++ b/Projects/UOContent/Items/Traps/FlameSpurtTrap.cs @@ -90,6 +90,8 @@ public partial class FlameSpurtTrap : BaseTrap if (_spurt?.Deleted != false) { _spurt = new Static(0x3709); + + // This MoveToWorld is generally not safe in a range loop, except we return after this on L100 _spurt.MoveToWorld(Location, Map); Effects.PlaySound(GetWorldLocation(), Map, 0x309); diff --git a/Projects/UOContent/Items/Traps/SpikeTrap.cs b/Projects/UOContent/Items/Traps/SpikeTrap.cs index dd036a583..aa0e16d96 100644 --- a/Projects/UOContent/Items/Traps/SpikeTrap.cs +++ b/Projects/UOContent/Items/Traps/SpikeTrap.cs @@ -1,5 +1,6 @@ using System; using ModernUO.Serialization; +using Server.Collections; using Server.Spells; namespace Server.Items; @@ -92,14 +93,21 @@ public partial class SpikeTrap : BaseTrap Effects.SendLocationEffect(Location, Map, GetBaseID(Type) + 1, 18, 3, GetEffectHue()); Effects.PlaySound(Location, Map, 0x22C); - foreach (var mob in GetMobilesInRange(0)) + var queue = PooledRefQueue.Create(); + foreach (var mob in GetMobilesAt()) { - if (mob.Alive && !mob.IsDeadBondedPet) + if (mob.Alive && !mob.IsDeadBondedPet && mob.AccessLevel == AccessLevel.Player) { - SpellHelper.Damage(TimeSpan.FromTicks(1), mob, mob, Utility.RandomMinMax(1, 6) * 6); + queue.Enqueue(mob); } } + while (queue.Count > 0) + { + var mob = queue.Dequeue(); + SpellHelper.Damage(TimeSpan.FromTicks(1), mob, mob, Utility.RandomMinMax(1, 6) * 6); + } + Timer.StartTimer(TimeSpan.FromSeconds(1.0), OnSpikeExtended); from.LocalOverheadMessage(MessageType.Regular, 0x22, 500852); // You stepped onto a spike trap! diff --git a/Projects/UOContent/Items/Traps/StoneFaceTrap.cs b/Projects/UOContent/Items/Traps/StoneFaceTrap.cs index 322cb192b..a8fb0457e 100644 --- a/Projects/UOContent/Items/Traps/StoneFaceTrap.cs +++ b/Projects/UOContent/Items/Traps/StoneFaceTrap.cs @@ -1,5 +1,6 @@ using System; using ModernUO.Serialization; +using Server.Collections; using Server.Spells; namespace Server.Items; @@ -94,13 +95,20 @@ public partial class StoneFaceTrap : BaseTrap public virtual void TriggerDamage() { + var queue = PooledRefQueue.Create(); foreach (var mob in GetMobilesInRange(1)) { if (mob.Alive && !mob.IsDeadBondedPet && mob.AccessLevel == AccessLevel.Player) { - SpellHelper.Damage(TimeSpan.FromTicks(1), mob, mob, Utility.Dice(3, 15, 0)); + queue.Enqueue(mob); } } + + while (queue.Count > 0) + { + var mob = queue.Dequeue(); + SpellHelper.Damage(TimeSpan.FromTicks(1), mob, mob, Utility.Dice(3, 15, 0)); + } } [AfterDeserialization] diff --git a/Projects/UOContent/Items/Weapons/Abilities/FrenziedWhirlwind.cs b/Projects/UOContent/Items/Weapons/Abilities/FrenziedWhirlwind.cs index 121265bc4..2e403eb4a 100644 --- a/Projects/UOContent/Items/Weapons/Abilities/FrenziedWhirlwind.cs +++ b/Projects/UOContent/Items/Weapons/Abilities/FrenziedWhirlwind.cs @@ -39,8 +39,7 @@ namespace Server.Items bool didEffect = false; - var eable = attacker.GetMobilesInRange(1); - foreach (var m in eable) + foreach (var m in attacker.GetMobilesInRange(1)) { if (m?.Deleted == false && m != defender && m != attacker && SpellHelper.ValidIndirectTarget(attacker, m) && diff --git a/Projects/UOContent/Items/Weapons/Abilities/WhirlwindAttack.cs b/Projects/UOContent/Items/Weapons/Abilities/WhirlwindAttack.cs index 3f135ceaf..b0e7f7a93 100644 --- a/Projects/UOContent/Items/Weapons/Abilities/WhirlwindAttack.cs +++ b/Projects/UOContent/Items/Weapons/Abilities/WhirlwindAttack.cs @@ -36,10 +36,9 @@ namespace Server.Items attacker.FixedEffect(0x3728, 10, 15); attacker.PlaySound(0x2A1); - var eable = attacker.GetMobilesInRange(1); using var queue = PooledRefQueue.Create(); - foreach (var m in eable) + foreach (var m in attacker.GetMobilesInRange(1)) { if (m?.Deleted == false && m != defender && m != attacker && m.Map == attacker.Map && m.Alive && diff --git a/Projects/UOContent/Items/Weapons/BaseWeapon.cs b/Projects/UOContent/Items/Weapons/BaseWeapon.cs index e9a17de95..26e300729 100644 --- a/Projects/UOContent/Items/Weapons/BaseWeapon.cs +++ b/Projects/UOContent/Items/Weapons/BaseWeapon.cs @@ -1718,9 +1718,8 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab return 0; } - var eable = defender.GetMobilesInRange(1); var inPack = 1; - foreach (var m in eable) + foreach (var m in defender.GetMobilesInRange(1)) { if (m != attacker && (m.PackInstinct & bc.PackInstinct) != 0 && (m.Controlled || m.Summoned) && master == (m.ControlMaster ?? m.SummonMaster) && m.Combatant == defender) @@ -1743,8 +1742,7 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab { if (MirrorImage.HasClone(defender) && defender.Skills.Ninjitsu.Value / 150.0 > Utility.RandomDouble()) { - var eable = defender.GetMobilesInRange(4); - foreach (var m in eable) + foreach (var m in defender.GetMobilesInRange(4)) { if (m?.Summoned == true && m.SummonMaster == defender) { @@ -3570,11 +3568,8 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab return; } - var range = Core.ML ? 5 : 10; - - var eable = from.GetMobilesInRange(range); using var queue = PooledRefQueue.Create(); - foreach (var m in eable) + foreach (var m in from.GetMobilesInRange(Core.ML ? 5 : 10)) { if (from != m && defender != m && SpellHelper.ValidIndirectTarget(from, m) && from.CanBeHarmful(m, false) && (!Core.ML || from.InLOS(m))) diff --git a/Projects/UOContent/Mobiles/AI/BaseAI.cs b/Projects/UOContent/Mobiles/AI/BaseAI.cs index 5a10ddac4..ffccd41e2 100644 --- a/Projects/UOContent/Mobiles/AI/BaseAI.cs +++ b/Projects/UOContent/Mobiles/AI/BaseAI.cs @@ -2604,9 +2604,7 @@ public abstract class BaseAI Mobile enemySummonMob = null; var enemySummonVal = double.MinValue; - var eable = map.GetMobilesInRange(m_Mobile.Location, iRange); - - foreach (var m in eable) + foreach (var m in map.GetMobilesInRange(m_Mobile.Location, iRange)) { if (m.Deleted || m.Blessed) { @@ -2808,9 +2806,7 @@ public abstract class BaseAI return; } - var eable = m_Mobile.GetMobilesInRange(m_Mobile.RangePerception); - - foreach (var trg in eable) + foreach (var trg in m_Mobile.GetMobilesInRange(m_Mobile.RangePerception)) { if (trg != m_Mobile && trg.Player && trg.Alive && trg.Hidden && trg.AccessLevel == AccessLevel.Player && m_Mobile.InLOS(trg)) diff --git a/Projects/UOContent/Mobiles/AI/HealerAI.cs b/Projects/UOContent/Mobiles/AI/HealerAI.cs index 6cb49ff31..9e228ba75 100644 --- a/Projects/UOContent/Mobiles/AI/HealerAI.cs +++ b/Projects/UOContent/Mobiles/AI/HealerAI.cs @@ -151,21 +151,21 @@ public class HealerAI : BaseAI foreach (var m in m_Mobile.GetMobilesInRange(m_Mobile.RangePerception)) { - if (!m_Mobile.CanSee(m) || !(m is BaseCreature) || ((BaseCreature)m).Team != m_Mobile.Team) + if (!m_Mobile.CanSee(m) || m is not BaseCreature bc || bc.Team != m_Mobile.Team) { continue; } for (var i = 0; i < funcs.Length; ++i) { - if (funcs[i](m)) + if (funcs[i](bc)) { - var val = -m_Mobile.GetDistanceToSqrt(m); + var val = -m_Mobile.GetDistanceToSqrt(bc); if (found == null || val > prio) { prio = val; - found = m; + found = bc; } break; diff --git a/Projects/UOContent/Mobiles/Abilities/AreaEffectMonsterAbility.cs b/Projects/UOContent/Mobiles/Abilities/AreaEffectMonsterAbility.cs index a86d6665d..444681627 100644 --- a/Projects/UOContent/Mobiles/Abilities/AreaEffectMonsterAbility.cs +++ b/Projects/UOContent/Mobiles/Abilities/AreaEffectMonsterAbility.cs @@ -8,9 +8,8 @@ public abstract class AreaEffectMonsterAbility : MonsterAbility public override void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target) { - var eable = source.GetMobilesInRange(AreaRange); using var queue = PooledRefQueue.Create(); - foreach (var m in eable) + foreach (var m in source.GetMobilesInRange(AreaRange)) { if (CanEffectTarget(source, m)) { diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index 4c13cfef3..1705d0df6 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -3800,8 +3800,7 @@ namespace Server.Mobiles } using var queue = PooledRefQueue.Create(); - var eable = master.GetMobilesInRange(3); - foreach (var m in eable) + foreach (var m in master.GetMobilesInRange(3)) { if (m is BaseCreature { Controlled: true, ControlOrder: OrderType.Guard or OrderType.Follow or OrderType.Come } pet && @@ -5346,9 +5345,8 @@ namespace Server.Mobiles return; } - var eable = GetMobilesInRange(AuraRange); using var queue = PooledRefQueue.Create(); - foreach (var m in eable) + foreach (var m in GetMobilesInRange(AuraRange)) { if (m != this && CanBeHarmful(m, false) && (Core.AOS || InLOS(m)) && (m is BaseCreature bc && (bc.Controlled || bc.Summoned || bc.Team != Team) || m.Player)) diff --git a/Projects/UOContent/Mobiles/Familiars/ShadowWisp.cs b/Projects/UOContent/Mobiles/Familiars/ShadowWisp.cs index 5a4809ea5..989ebf354 100644 --- a/Projects/UOContent/Mobiles/Familiars/ShadowWisp.cs +++ b/Projects/UOContent/Mobiles/Familiars/ShadowWisp.cs @@ -1,6 +1,5 @@ using System; using ModernUO.Serialization; -using Server.Collections; namespace Server.Mobiles; @@ -68,19 +67,13 @@ public partial class ShadowWispFamiliar : BaseFamiliar return; } - var eable = GetMobilesInRange(5); - using var queue = PooledRefQueue.Create(); - foreach (var m in eable) + foreach (var m in GetMobilesInRange(5)) { - if (m.Player && m.Alive && !m.IsDeadBondedPet && m.Karma <= 0 && m.AccessLevel < AccessLevel.Counselor) + if (!m.Player || !m.Alive || m.IsDeadBondedPet || m.Karma > 0 || m.AccessLevel >= AccessLevel.Counselor) { - queue.Enqueue(m); + continue; } - } - while (queue.Count > 0) - { - var m = queue.Dequeue(); var friendly = true; for (var j = 0; friendly && j < caster.Aggressors.Count; ++j) diff --git a/Projects/UOContent/Mobiles/Monsters/Humanoid/Magic/SavageShaman.cs b/Projects/UOContent/Mobiles/Monsters/Humanoid/Magic/SavageShaman.cs index 10c617afd..d1994f82d 100644 --- a/Projects/UOContent/Mobiles/Monsters/Humanoid/Magic/SavageShaman.cs +++ b/Projects/UOContent/Mobiles/Monsters/Humanoid/Magic/SavageShaman.cs @@ -1,6 +1,7 @@ using ModernUO.Serialization; using System; -using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Server.Collections; using Server.Items; using Server.Spells; @@ -131,13 +132,13 @@ namespace Server.Mobiles return; } - var list = new List(); + using var queue = PooledRefQueue.Create(); foreach (var m in GetMobilesInRange(8)) { if (m != this && m is SavageShaman ss) { - list.Add(ss); + queue.Enqueue(ss); } } @@ -148,22 +149,104 @@ namespace Server.Mobiles AIObject.NextMove = Core.TickCount + 1000; } - if (list.Count >= 3) + if (queue.Count < 3) { - for (var i = 0; i < list.Count; ++i) - { - var dancer = list[i]; - - dancer.Animate(111, 5, 1, true, false, 0); // Get down tonight... - - if (dancer.AIObject != null) - { - dancer.AIObject.NextMove = Core.TickCount + 1000; - } - } - - Timer.StartTimer(TimeSpan.FromSeconds(1.0), EndSavageDance); + return; } + + while (queue.Count > 0) + { + var dancer = queue.Dequeue(); + + dancer.Animate(111, 5, 1, true, false, 0); // Get down tonight... + + if (dancer.AIObject != null) + { + dancer.AIObject.NextMove = Core.TickCount + 1000; + } + } + + Timer.StartTimer(TimeSpan.FromSeconds(1.0), EndSavageDance); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool CanDoGreaterHeal(Mobile m, bool isFriendly) => + isFriendly && !m.Poisoned && !MortalStrike.IsWounded(m) && CanBeBeneficial(m); + + private void DoGreaterHeal(Mobile m) + { + DoBeneficial(m); + + // Algorithm: (40% of magery) + (1-10) + + var toHeal = (int)(Skills.Magery.Value * 0.4); + toHeal += Utility.Random(1, 10); + + m.Heal(toHeal, this); + + m.FixedParticles(0x376A, 9, 32, 5030, EffectLayer.Waist); + m.PlaySound(0x202); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool CanDoLightning(Mobile m, bool isFriendly) => + !isFriendly && CanBeHarmful(m) && (!m.Hidden || m.AccessLevel == AccessLevel.Player); + + private void DoLightning(Mobile m) + { + DoHarmful(m); + + double damage; + + if (Core.AOS) + { + var baseDamage = 6 + (int)(Skills.EvalInt.Value / 5.0); + + damage = Utility.RandomMinMax(baseDamage, baseDamage + 3); + } + else + { + damage = Utility.Random(12, 9); + } + + m.BoltEffect(0); + + SpellHelper.Damage(TimeSpan.FromSeconds(0.25), m, this, damage, 0, 0, 0, 0, 100); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool CanDoPoison(Mobile m, bool isFriendly) => + !isFriendly && CanBeHarmful(m) && (!m.Hidden || m.AccessLevel == AccessLevel.Player); + + private void DoPoison(Mobile m) + { + DoHarmful(m); + + m.Spell?.OnCasterHurt(); + + m.Paralyzed = false; + + var total = Skills.Magery.Value + Skills.Poisoning.Value; + + var dist = GetDistanceToSqrt(m); + + if (dist >= 3.0) + { + total -= (dist - 3.0) * 10.0; + } + + int level = total switch + { + >= 200.0 => Utility.Random(10) == 0 ? 3 : 2, + > 170.0 => 2, + > 130.0 => 1, + _ => 0 + }; + + m.ApplyPoison(this, Poison.GetPoison(level)); + + m.FixedParticles(0x374A, 10, 15, 5021, EffectLayer.Waist); + m.PlaySound(0x474); } public void EndSavageDance() @@ -173,137 +256,47 @@ namespace Server.Mobiles return; } - var eable = GetMobilesInRange(8); + var rnd = Utility.Random(3); - switch (Utility.Random(3)) + using var queue = PooledRefQueue.Create(); + foreach (var m in GetMobilesInRange(8)) { - case 0: /* greater heal */ - { - foreach (var m in eable) + var isFriendly = m is Savage or SavageRider or SavageShaman or SavageRidgeback; + var shouldApply = rnd switch + { + 0 => CanDoGreaterHeal(m, isFriendly), + 1 => CanDoLightning(m, isFriendly), + 2 => CanDoPoison(m, isFriendly), + }; + + if (shouldApply) + { + queue.Enqueue(m); + } + } + + while (queue.Count > 0) + { + var m = queue.Dequeue(); + + switch (rnd) + { + case 0: { - var isFriendly = m is Savage or SavageRider or SavageShaman or SavageRidgeback; - - if (!isFriendly) - { - continue; - } - - if (m.Poisoned || MortalStrike.IsWounded(m) || !CanBeBeneficial(m)) - { - continue; - } - - DoBeneficial(m); - - // Algorithm: (40% of magery) + (1-10) - - var toHeal = (int)(Skills.Magery.Value * 0.4); - toHeal += Utility.Random(1, 10); - - m.Heal(toHeal, this); - - m.FixedParticles(0x376A, 9, 32, 5030, EffectLayer.Waist); - m.PlaySound(0x202); + DoGreaterHeal(m); + break; } - - break; - } - case 1: /* lightning */ - { - foreach (var m in eable) + case 1: { - var isFriendly = m is Savage or SavageRider or SavageShaman or SavageRidgeback; - - if (isFriendly) - { - continue; - } - - if (!CanBeHarmful(m)) - { - continue; - } - - DoHarmful(m); - - double damage; - - if (Core.AOS) - { - var baseDamage = 6 + (int)(Skills.EvalInt.Value / 5.0); - - damage = Utility.RandomMinMax(baseDamage, baseDamage + 3); - } - else - { - damage = Utility.Random(12, 9); - } - - m.BoltEffect(0); - - SpellHelper.Damage(TimeSpan.FromSeconds(0.25), m, this, damage, 0, 0, 0, 0, 100); + DoLightning(m); + break; } - - break; - } - case 2: /* poison */ - { - foreach (var m in eable) + case 2: { - var isFriendly = m is Savage or SavageRider or SavageShaman or SavageRidgeback; - - if (isFriendly) - { - continue; - } - - if (!CanBeHarmful(m)) - { - continue; - } - - DoHarmful(m); - - m.Spell?.OnCasterHurt(); - - m.Paralyzed = false; - - var total = Skills.Magery.Value + Skills.Poisoning.Value; - - var dist = GetDistanceToSqrt(m); - - if (dist >= 3.0) - { - total -= (dist - 3.0) * 10.0; - } - - int level; - - if (total >= 200.0 && Utility.Random(1, 100) <= 10) - { - level = 3; - } - else if (total > 170.0) - { - level = 2; - } - else if (total > 130.0) - { - level = 1; - } - else - { - level = 0; - } - - m.ApplyPoison(this, Poison.GetPoison(level)); - - m.FixedParticles(0x374A, 10, 15, 5021, EffectLayer.Waist); - m.PlaySound(0x474); + DoPoison(m); + break; } - - break; - } + } } } } diff --git a/Projects/UOContent/Mobiles/Monsters/Humanoid/Melee/OrcBrute.cs b/Projects/UOContent/Mobiles/Monsters/Humanoid/Melee/OrcBrute.cs index 389e353bf..62387a71a 100644 --- a/Projects/UOContent/Mobiles/Monsters/Humanoid/Melee/OrcBrute.cs +++ b/Projects/UOContent/Mobiles/Monsters/Humanoid/Melee/OrcBrute.cs @@ -107,23 +107,19 @@ namespace Server.Mobiles return; } - var eable = GetMobilesInRange(10); var count = 0; - foreach (var m in eable) + foreach (var m in GetMobilesInRange(10)) { if (++count == 10) { - break; + BaseCreature orc = new SpawnedOrcishLord { Team = Team }; + + // This MoveToWorld is safe since we return after executing and do not keep looping. + orc.MoveToWorld(map.GetRandomNearbyLocation(target.Location), map); + orc.Combatant = target; + return; } } - - if (count < 10) - { - BaseCreature orc = new SpawnedOrcishLord { Team = Team }; - - orc.MoveToWorld(map.GetRandomNearbyLocation(target.Location), map); - orc.Combatant = target; - } } } } diff --git a/Projects/UOContent/Mobiles/Monsters/LBR/Meers/MeerCaptain.cs b/Projects/UOContent/Mobiles/Monsters/LBR/Meers/MeerCaptain.cs index bd0af9e21..3af690d48 100644 --- a/Projects/UOContent/Mobiles/Monsters/LBR/Meers/MeerCaptain.cs +++ b/Projects/UOContent/Mobiles/Monsters/LBR/Meers/MeerCaptain.cs @@ -118,9 +118,7 @@ namespace Server.Mobiles { m_NextAbilityTime = Core.Now + TimeSpan.FromSeconds(Utility.RandomMinMax(10, 15)); - var eable = GetMobilesInRange(8); - - foreach (var m in eable) + foreach (var m in GetMobilesInRange(8)) { if (m is not MeerWarrior || !IsFriend(m) || !CanBeBeneficial(m) || m.Hits >= m.HitsMax || m.Poisoned || MortalStrike.IsWounded(m)) diff --git a/Projects/UOContent/Mobiles/Monsters/LBR/Meers/MeerEternal.cs b/Projects/UOContent/Mobiles/Monsters/LBR/Meers/MeerEternal.cs index 2e1812078..89f7f3d8b 100644 --- a/Projects/UOContent/Mobiles/Monsters/LBR/Meers/MeerEternal.cs +++ b/Projects/UOContent/Mobiles/Monsters/LBR/Meers/MeerEternal.cs @@ -81,9 +81,8 @@ namespace Server.Mobiles private void DoAreaLeech_Finish() { - var eable = GetMobilesInRange(6); using var queue = PooledRefQueue.Create(); - foreach (var m in eable) + foreach (var m in GetMobilesInRange(6)) { if (CanBeHarmful(m) && IsEnemy(m)) { diff --git a/Projects/UOContent/Mobiles/Monsters/ML/Humanoid/Magic/MLDryad.cs b/Projects/UOContent/Mobiles/Monsters/ML/Humanoid/Magic/MLDryad.cs index 39188ef3c..05f7dcf1f 100644 --- a/Projects/UOContent/Mobiles/Monsters/ML/Humanoid/Magic/MLDryad.cs +++ b/Projects/UOContent/Mobiles/Monsters/ML/Humanoid/Magic/MLDryad.cs @@ -122,16 +122,15 @@ namespace Server.Mobiles UndressItem(m, Layer.Pants); UndressItem(m, Layer.Shirt); - m.SendLocalizedMessage( - 1072197 - ); // The dryad's beauty makes your blood race. Your clothing is too confining. + // The dryad's beauty makes your blood race. Your clothing is too confining. + m.SendLocalizedMessage(1072197); } } m_NextUndress = Core.Now + TimeSpan.FromMinutes(1); } - public void UndressItem(Mobile m, Layer layer) + public static void UndressItem(Mobile m, Layer layer) { var item = m.FindItemOnLayer(layer); diff --git a/Projects/UOContent/Mobiles/Monsters/ML/Special/Ilhenir.cs b/Projects/UOContent/Mobiles/Monsters/ML/Special/Ilhenir.cs index 1a241281d..79d68bbe1 100644 --- a/Projects/UOContent/Mobiles/Monsters/ML/Special/Ilhenir.cs +++ b/Projects/UOContent/Mobiles/Monsters/ML/Special/Ilhenir.cs @@ -1,6 +1,7 @@ using ModernUO.Serialization; using System; using System.Collections.Generic; +using Server.Collections; using Server.Engines.CannedEvil; using Server.Items; using Server.Network; @@ -311,31 +312,19 @@ namespace Server.Mobiles private void OnTick() { - var toDamage = new List(); - - foreach (var m in GetMobilesInRange(0)) + using var queue = PooledRefQueue.Create(); + foreach (var m in GetMobilesAt()) { - if (m is BaseCreature bc) + if ((m.Player || m is BaseCreature bc && (bc.Controlled || bc.Summoned)) + && m.Alive && !m.IsDeadBondedPet && m.CanBeDamaged()) { - if (!bc.Controlled && !bc.Summoned) - { - continue; - } - } - else if (!m.Player) - { - continue; - } - - if (m.Alive && !m.IsDeadBondedPet && m.CanBeDamaged()) - { - toDamage.Add(m); + queue.Enqueue(m); } } - for (var i = 0; i < toDamage.Count; ++i) + while (queue.Count > 0) { - Damage(toDamage[i]); + Damage(queue.Dequeue()); } ++m_Ticks; diff --git a/Projects/UOContent/Mobiles/Monsters/ML/Special/Meraktus.cs b/Projects/UOContent/Mobiles/Monsters/ML/Special/Meraktus.cs index 1b2438474..7fec256a1 100644 --- a/Projects/UOContent/Mobiles/Monsters/ML/Special/Meraktus.cs +++ b/Projects/UOContent/Mobiles/Monsters/ML/Special/Meraktus.cs @@ -2,6 +2,7 @@ using System; using Server.Engines.CannedEvil; using Server.Items; using ModernUO.Serialization; +using Server.Collections; namespace Server.Mobiles; @@ -214,30 +215,30 @@ public partial class Meraktus : BaseChampion public void Earthquake() { - var eable = GetMobilesInRange(8); - - foreach (var m in eable) + using var queue = PooledRefQueue.Create(); + foreach (var m in GetMobilesInRange(8)) { - if (m == this || !CanBeHarmful(m) || m.Deleted || !m.Player && - !(m is BaseCreature creature && (creature.Controlled || creature.Summoned || creature.Team != Team))) + if (m.Deleted || m == this || !CanBeHarmful(m)) { continue; } + if (m.Player || m is BaseCreature bc && (bc.Controlled || bc.Summoned || bc.Team != Team)) + { + queue.Enqueue(m); + } + } + + while (queue.Count > 0) + { + var m = queue.Dequeue(); + if (m is PlayerMobile pm && pm.Mounted) { pm.Mount.Rider = null; } - var damage = (int)(m.Hits * 0.6); - if (damage < 10) - { - damage = 10; - } - else if (damage > 75) - { - damage = 75; - } + var damage = Math.Clamp((int)(m.Hits * 0.6), 10, 75); DoHarmful(m); AOS.Damage(m, this, damage, 100, 0, 0, 0, 0); diff --git a/Projects/UOContent/Mobiles/Monsters/Misc/Melee/BladeSpirits.cs b/Projects/UOContent/Mobiles/Monsters/Misc/Melee/BladeSpirits.cs index f6e9bb085..03f4adf9f 100644 --- a/Projects/UOContent/Mobiles/Monsters/Misc/Melee/BladeSpirits.cs +++ b/Projects/UOContent/Mobiles/Monsters/Misc/Melee/BladeSpirits.cs @@ -1,6 +1,5 @@ using ModernUO.Serialization; using System; -using Server.Buffers; using Server.Collections; namespace Server.Mobiles @@ -69,28 +68,24 @@ namespace Server.Mobiles { if (Core.SE && Summoned) { - var eable = GetMobilesInRange(5); - using var queue = PooledRefQueue.Create(); - foreach (var m in eable) + using var list = PooledRefList.Create(); + foreach (var m in GetMobilesInRange(5)) { if (m is EnergyVortex or BladeSpirits && ((BaseCreature)m).Summoned) { - queue.Enqueue(m); + list.Add(m); } } - var amount = queue.Count - 6; + var amount = list.Count - 6; if (amount > 0) { - var mobs = queue.ToPooledArray(); - mobs.Shuffle(); + list.Shuffle(); while (amount > 0) { - Dispel(mobs[amount--]); + Dispel(list[amount--]); } - - STArrayPool.Shared.Return(mobs, true); } } diff --git a/Projects/UOContent/Mobiles/Monsters/Misc/Melee/EnergyVortex.cs b/Projects/UOContent/Mobiles/Monsters/Misc/Melee/EnergyVortex.cs index 52a77f288..a6ab88261 100644 --- a/Projects/UOContent/Mobiles/Monsters/Misc/Melee/EnergyVortex.cs +++ b/Projects/UOContent/Mobiles/Monsters/Misc/Melee/EnergyVortex.cs @@ -1,6 +1,5 @@ using ModernUO.Serialization; using System; -using Server.Buffers; using Server.Collections; namespace Server.Mobiles @@ -75,28 +74,24 @@ namespace Server.Mobiles { if (Core.SE && Summoned) { - var eable = GetMobilesInRange(5); - using var queue = PooledRefQueue.Create(); - foreach (var m in eable) + using var list = PooledRefList.Create(); + foreach (var m in GetMobilesInRange(5)) { if (m is EnergyVortex or BladeSpirits && ((BaseCreature)m).Summoned) { - queue.Enqueue(m); + list.Add(m); } } - var amount = queue.Count - 6; + var amount = list.Count - 6; if (amount > 0) { - var mobs = queue.ToPooledArray(); - mobs.Shuffle(); + list.Shuffle(); while (amount > 0) { - Dispel(mobs[amount--]); + Dispel(list[amount--]); } - - STArrayPool.Shared.Return(mobs, true); } } diff --git a/Projects/UOContent/Mobiles/Monsters/Plant/Melee/BogThing.cs b/Projects/UOContent/Mobiles/Monsters/Plant/Melee/BogThing.cs index 1c43b0d70..02e6589a4 100644 --- a/Projects/UOContent/Mobiles/Monsters/Plant/Melee/BogThing.cs +++ b/Projects/UOContent/Mobiles/Monsters/Plant/Melee/BogThing.cs @@ -1,4 +1,5 @@ using ModernUO.Serialization; +using Server.Collections; using Server.Engines.Plants; using Server.Items; @@ -81,24 +82,33 @@ namespace Server.Mobiles public void EatBoglings() { - var eable = GetMobilesInRange(2); - var sound = true; + if (Hits >= HitsMax) + { + return; + } - foreach (var bogling in eable) + using var queue = PooledRefQueue.Create(); + foreach (var bogling in GetMobilesInRange(2)) { if (Hits >= HitsMax) { break; } - if (sound) - { - PlaySound(Utility.Random(0x3B, 2)); // Eat sound - sound = false; - } - Hits += bogling.Hits / 2; - bogling.Delete(); + queue.Enqueue(bogling); + } + + if (queue.Count <= 0) + { + return; + } + + PlaySound(Utility.Random(0x3B, 2)); // Eat sound + + while (queue.Count > 0) + { + queue.Dequeue().Delete(); } } diff --git a/Projects/UOContent/Mobiles/Monsters/SE/Yamandon.cs b/Projects/UOContent/Mobiles/Monsters/SE/Yamandon.cs index 431268d40..a86d454c7 100644 --- a/Projects/UOContent/Mobiles/Monsters/SE/Yamandon.cs +++ b/Projects/UOContent/Mobiles/Monsters/SE/Yamandon.cs @@ -1,4 +1,5 @@ using ModernUO.Serialization; +using Server.Collections; using Server.Engines.Plants; using Server.Items; @@ -120,9 +121,8 @@ namespace Server.Mobiles Animate(10, 4, 1, true, false, 0); - var eable = target.GetMobilesInRange(8); - - foreach (var m in eable) + using var queue = PooledRefQueue.Create(); + foreach (var m in target.GetMobilesInRange(8)) { if (m == this || !(CanBeHarmful(m) || m.Player && m.Alive)) { @@ -134,6 +134,12 @@ namespace Server.Mobiles continue; } + queue.Enqueue(m); + } + + while (queue.Count > 0) + { + var m = queue.Dequeue(); DoHarmful(m); AOS.Damage(m, this, Utility.RandomMinMax(20, 25), true, 0, 0, 0, 100, 0); diff --git a/Projects/UOContent/Mobiles/PlayerMobile.cs b/Projects/UOContent/Mobiles/PlayerMobile.cs index bb318a120..75e638baa 100644 --- a/Projects/UOContent/Mobiles/PlayerMobile.cs +++ b/Projects/UOContent/Mobiles/PlayerMobile.cs @@ -856,9 +856,7 @@ namespace Server.Mobiles if (Core.AOS) { - var mobiles = Map.GetMobilesInRange(location, 0); - - foreach (Mobile m in mobiles) + foreach (Mobile m in Map.GetMobilesAt(location)) { if (m.Z >= location.Z && m.Z < location.Z + 16 && (!m.Hidden || m.AccessLevel == AccessLevel.Player)) { diff --git a/Projects/UOContent/Mobiles/Special/Barracoon.cs b/Projects/UOContent/Mobiles/Special/Barracoon.cs index 1e32adf74..50e941515 100644 --- a/Projects/UOContent/Mobiles/Special/Barracoon.cs +++ b/Projects/UOContent/Mobiles/Special/Barracoon.cs @@ -138,18 +138,13 @@ public partial class Barracoon : BaseChampion return; } - var eable = GetMobilesInRange(10); var rats = 0; - foreach (var m in eable) + foreach (var m in GetMobilesInRange(10)) { - if (m is Ratman or RatmanArcher or RatmanMage) + if (m is Ratman or RatmanArcher or RatmanMage && ++rats >= 16) { - rats++; - if (rats >= 16) - { - return; - } + return; } } diff --git a/Projects/UOContent/Mobiles/Special/Harrower.cs b/Projects/UOContent/Mobiles/Special/Harrower.cs index 64c7c82b9..9b69aa3c7 100644 --- a/Projects/UOContent/Mobiles/Special/Harrower.cs +++ b/Projects/UOContent/Mobiles/Special/Harrower.cs @@ -529,9 +529,8 @@ public partial class Harrower : BaseCreature return; } - var eable = m_Owner.GetMobilesInRange(16); Mobile toTeleport = null; - foreach (var m in eable) + foreach (var m in m_Owner.GetMobilesInRange(16)) { if (m != m_Owner && m.Player && m_Owner.CanBeHarmful(m) && m_Owner.CanSee(m)) { diff --git a/Projects/UOContent/Mobiles/Special/HarrowerTentacles.cs b/Projects/UOContent/Mobiles/Special/HarrowerTentacles.cs index 3e1ddc377..20877aa6a 100644 --- a/Projects/UOContent/Mobiles/Special/HarrowerTentacles.cs +++ b/Projects/UOContent/Mobiles/Special/HarrowerTentacles.cs @@ -1,5 +1,6 @@ using System; using ModernUO.Serialization; +using Server.Collections; namespace Server.Mobiles; @@ -119,9 +120,8 @@ public partial class HarrowerTentacles : BaseCreature return; } - var eable = m_Owner.GetMobilesInRange(9); - - foreach (var m in eable) + using var queue = PooledRefQueue.Create(); + foreach (var m in m_Owner.GetMobilesInRange(9)) { if (m == m_Owner || !m_Owner.CanBeHarmful(m)) { @@ -138,6 +138,12 @@ public partial class HarrowerTentacles : BaseCreature continue; } + queue.Enqueue(m); + } + + while (queue.Count > 0) + { + var m = queue.Dequeue(); m_Owner.DoHarmful(m); m.FixedParticles(0x374A, 10, 15, 5013, 0x455, 0, EffectLayer.Waist); diff --git a/Projects/UOContent/Mobiles/Special/Rikktor.cs b/Projects/UOContent/Mobiles/Special/Rikktor.cs index e671b9eac..dc5b225a7 100644 --- a/Projects/UOContent/Mobiles/Special/Rikktor.cs +++ b/Projects/UOContent/Mobiles/Special/Rikktor.cs @@ -1,5 +1,6 @@ using System; using ModernUO.Serialization; +using Server.Collections; using Server.Engines.CannedEvil; using Server.Items; @@ -98,9 +99,8 @@ public partial class Rikktor : BaseChampion PlaySound(0x2F3); - var eable = GetMobilesInRange(8); - - foreach (var m in eable) + using var queue = PooledRefQueue.Create(); + foreach (var m in GetMobilesInRange(8)) { if (m == this || !(CanBeHarmful(m) || m.Player && m.Alive)) { @@ -112,20 +112,18 @@ public partial class Rikktor : BaseChampion continue; } - var damage = m.Hits * 0.6; + queue.Enqueue(m); + } - if (damage < 10.0) - { - damage = 10.0; - } - else if (damage > 75.0) - { - damage = 75.0; - } + while (queue.Count > 0) + { + var m = queue.Dequeue(); + + var damage = Math.Clamp((int)(m.Hits * 0.6), 10, 75); DoHarmful(m); - AOS.Damage(m, this, (int)damage, 100, 0, 0, 0, 0); + AOS.Damage(m, this, damage, 100, 0, 0, 0, 0); if (m.Alive && m.Body.IsHuman && !m.Mounted) { diff --git a/Projects/UOContent/Mobiles/Special/Serado.cs b/Projects/UOContent/Mobiles/Special/Serado.cs index 4153ad55e..ec16790b3 100644 --- a/Projects/UOContent/Mobiles/Special/Serado.cs +++ b/Projects/UOContent/Mobiles/Special/Serado.cs @@ -1,5 +1,6 @@ using System; using ModernUO.Serialization; +using Server.Collections; using Server.Engines.CannedEvil; using Server.Engines.Plants; using Server.Items; @@ -152,9 +153,8 @@ public partial class Serado : BaseChampion Animate(10, 4, 1, true, false, 0); - var eable = target.GetMobilesInRange(8); - - foreach (var m in eable) + using var queue = PooledRefQueue.Create(); + foreach (var m in target.GetMobilesInRange(8)) { if (m == this || !(CanBeHarmful(m) || m.Player && m.Alive)) { @@ -166,6 +166,12 @@ public partial class Serado : BaseChampion continue; } + queue.Enqueue(m); + } + + while (queue.Count > 0) + { + var m = queue.Dequeue(); DoHarmful(m); AOS.Damage(m, this, Utility.RandomMinMax(20, 25), true, 0, 0, 0, 100, 0); diff --git a/Projects/UOContent/Mobiles/Vendors/PlayerVendor.cs b/Projects/UOContent/Mobiles/Vendors/PlayerVendor.cs index 0807b501e..cc3e707eb 100644 --- a/Projects/UOContent/Mobiles/Vendors/PlayerVendor.cs +++ b/Projects/UOContent/Mobiles/Vendors/PlayerVendor.cs @@ -1268,9 +1268,7 @@ namespace Server.Mobiles } else { - var mobiles = e.Mobile.GetMobilesInRange(2); - - foreach (var m in mobiles) + foreach (var m in e.Mobile.GetMobilesInRange(2)) { if (m.CanSee(e.Mobile) && m.InLOS(e.Mobile)) { diff --git a/Projects/UOContent/Multis/Houses/HousePlacement.cs b/Projects/UOContent/Multis/Houses/HousePlacement.cs index e21a1c046..b78af2a4f 100644 --- a/Projects/UOContent/Multis/Houses/HousePlacement.cs +++ b/Projects/UOContent/Multis/Houses/HousePlacement.cs @@ -151,12 +151,8 @@ namespace Server.Multis mobiles.Clear(); - var sector = map.GetSector(tileX, tileY); - - for (var i = 0; i < sector.Mobiles.Count; ++i) + foreach (var m in map.GetMobilesAt(tileX, tileY)) { - var m = sector.Mobiles[i]; - if (m.X == tileX && m.Y == tileY) { mobiles.Add(m); diff --git a/Projects/UOContent/Regions/GuardedRegion.cs b/Projects/UOContent/Regions/GuardedRegion.cs index 8d5a3685c..1f1af15c6 100644 --- a/Projects/UOContent/Regions/GuardedRegion.cs +++ b/Projects/UOContent/Regions/GuardedRegion.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text.Json.Serialization; using Server.Mobiles; using Server.Utilities; @@ -153,8 +152,15 @@ public class GuardedRegion : BaseRegion public override void MakeGuard(Mobile focus) { - var eable = focus.GetMobilesInRange(8); - var useGuard = eable.FirstOrDefault(m => m.Focus == null); + BaseGuard useGuard = null; + foreach (var m in focus.GetMobilesInRange(8)) + { + if (m.Focus == null) + { + useGuard = m; + break; + } + } if (useGuard == null) { @@ -286,19 +292,8 @@ public class GuardedRegion : BaseRegion if (fakeCall != null) { - fakeCall.Say( - Utility.RandomList( - 1007037, - 501603, - 1013037, - 1013038, - 1013039, - 1013041, - 1013042, - 1013043, - 1013052 - ) - ); + fakeCall.Say(Utility.Random(1013037, 16)); + MakeGuard(m); timer.Stop(); m_GuardCandidates.Remove(m); @@ -319,9 +314,7 @@ public class GuardedRegion : BaseRegion return; } - var eable = Map.GetMobilesInRange(p, 14); - - foreach (var m in eable) + foreach (var m in Map.GetMobilesInRange(p, 14)) { if (IsGuardCandidate(m) && (!AllowReds && m.Kills >= 5 && m.Region.IsPartOf(this) || m_GuardCandidates.ContainsKey(m))) diff --git a/Projects/UOContent/Skills/DetectHidden.cs b/Projects/UOContent/Skills/DetectHidden.cs index adf5f8e65..a0fcecafd 100644 --- a/Projects/UOContent/Skills/DetectHidden.cs +++ b/Projects/UOContent/Skills/DetectHidden.cs @@ -58,27 +58,29 @@ namespace Server.SkillHandlers if (range > 0) { - var inRange = src.Map.GetMobilesInRange(p, range); - - foreach (var trg in inRange) + foreach (var trg in src.Map.GetMobilesInRange(p, range)) { - if (trg.Hidden && src != trg) + if (!trg.Hidden || src == trg) { - var ss = srcSkill + Utility.Random(21) - 10; - var ts = trg.Skills.Hiding.Value + Utility.Random(21) - 10; - - if (src.AccessLevel >= trg.AccessLevel && (ss >= ts || inHouse && house.IsInside(trg))) - { - if (trg is ShadowKnight && (trg.X != p.X || trg.Y != p.Y)) - { - continue; - } - - trg.RevealingAction(); - trg.SendLocalizedMessage(500814); // You have been revealed! - foundAnyone = true; - } + continue; } + + var ss = srcSkill + Utility.Random(21) - 10; + var ts = trg.Skills.Hiding.Value + Utility.Random(21) - 10; + + if (src.AccessLevel < trg.AccessLevel || ss < ts && (!inHouse || !house.IsInside(trg))) + { + continue; + } + + if (trg is ShadowKnight && (trg.X != p.X || trg.Y != p.Y)) + { + continue; + } + + trg.RevealingAction(); + trg.SendLocalizedMessage(500814); // You have been revealed! + foundAnyone = true; } if (Faction.Find(src) != null) diff --git a/Projects/UOContent/Skills/Hiding.cs b/Projects/UOContent/Skills/Hiding.cs index 0ec124342..fae415dda 100644 --- a/Projects/UOContent/Skills/Hiding.cs +++ b/Projects/UOContent/Skills/Hiding.cs @@ -60,8 +60,7 @@ namespace Server.SkillHandlers { if (!CombatOverride) { - var eable = m.GetMobilesInRange(range); - foreach (var check in eable) + foreach (var check in m.GetMobilesInRange(range)) { if (check.InLOS(m) && check.Combatant == m) { diff --git a/Projects/UOContent/Skills/Peacemaking.cs b/Projects/UOContent/Skills/Peacemaking.cs index d6f479a1e..c963395d5 100644 --- a/Projects/UOContent/Skills/Peacemaking.cs +++ b/Projects/UOContent/Skills/Peacemaking.cs @@ -118,9 +118,8 @@ namespace Server.SkillHandlers calmed = true; - m.SendLocalizedMessage( - 500616 - ); // You hear lovely music, and forget to continue battling! + // You hear lovely music, and forget to continue battling! + m.SendLocalizedMessage(500616); m.Combatant = null; m.Warmode = false; @@ -132,9 +131,8 @@ namespace Server.SkillHandlers if (!calmed) { - from.SendLocalizedMessage( - 1049648 - ); // You play hypnotic music, but there is nothing in range for you to calm. + // You play hypnotic music, but there is nothing in range for you to calm. + from.SendLocalizedMessage(1049648); } else { @@ -216,9 +214,8 @@ namespace Server.SkillHandlers { from.SendLocalizedMessage(1049532); // You play hypnotic music, calming your target. - targ.SendLocalizedMessage( - 500616 - ); // You hear lovely music, and forget to continue battling! + // You hear lovely music, and forget to continue battling! + targ.SendLocalizedMessage(500616); } } } diff --git a/Projects/UOContent/Skills/Tracking/Tracking.cs b/Projects/UOContent/Skills/Tracking/Tracking.cs index e73226bb9..de2cc5709 100644 --- a/Projects/UOContent/Skills/Tracking/Tracking.cs +++ b/Projects/UOContent/Skills/Tracking/Tracking.cs @@ -210,20 +210,17 @@ namespace Server.SkillHandlers from.SendGump(new TrackWhoGump(from, mobs, range)); from.SendLocalizedMessage(1018093); // Select the one you would like to track. } + else if (type == 0) + { + from.SendLocalizedMessage(502991); // You see no evidence of animals in the area. + } + else if (type == 1) + { + from.SendLocalizedMessage(502993); // You see no evidence of creatures in the area. + } else { - if (type == 0) - { - from.SendLocalizedMessage(502991); // You see no evidence of animals in the area. - } - else if (type == 1) - { - from.SendLocalizedMessage(502993); // You see no evidence of creatures in the area. - } - else - { - from.SendLocalizedMessage(502995); // You see no evidence of people in the area. - } + from.SendLocalizedMessage(502995); // You see no evidence of people in the area. } } diff --git a/Projects/UOContent/Spells/Bushido/MomentumStrike.cs b/Projects/UOContent/Spells/Bushido/MomentumStrike.cs index 20a7ecf9c..9a0883d71 100644 --- a/Projects/UOContent/Spells/Bushido/MomentumStrike.cs +++ b/Projects/UOContent/Spells/Bushido/MomentumStrike.cs @@ -21,9 +21,8 @@ namespace Server.Spells.Bushido var weapon = attacker.Weapon; - var eable = attacker.GetMobilesInRange(weapon.MaxRange); using var queue = PooledRefQueue.Create(); - foreach (var m in eable) + foreach (var m in attacker.GetMobilesInRange(weapon.MaxRange)) { if (m != defender && m.Combatant == attacker) { diff --git a/Projects/UOContent/Spells/Chivalry/DispelEvil.cs b/Projects/UOContent/Spells/Chivalry/DispelEvil.cs index cbb15bee4..1f16ec2d9 100644 --- a/Projects/UOContent/Spells/Chivalry/DispelEvil.cs +++ b/Projects/UOContent/Spells/Chivalry/DispelEvil.cs @@ -46,9 +46,8 @@ namespace Server.Spells.Chivalry var chiv = Caster.Skills.Chivalry.Value; - var eable = Caster.GetMobilesInRange(8); using var queue = PooledRefQueue.Create(); - foreach (var m in eable) + foreach (var m in Caster.GetMobilesInRange(8)) { if (Caster != m && SpellHelper.ValidIndirectTarget(Caster, m) && Caster.CanBeHarmful(m, false)) { diff --git a/Projects/UOContent/Spells/Chivalry/HolyLight.cs b/Projects/UOContent/Spells/Chivalry/HolyLight.cs index ae0b79d25..06af40362 100644 --- a/Projects/UOContent/Spells/Chivalry/HolyLight.cs +++ b/Projects/UOContent/Spells/Chivalry/HolyLight.cs @@ -1,4 +1,5 @@ using System; +using Server.Collections; using Server.Items; namespace Server.Spells.Chivalry @@ -54,6 +55,7 @@ namespace Server.Spells.Chivalry 0 ); + using var queue = PooledRefQueue.Create(); foreach (var m in Caster.GetMobilesInRange(3)) { if (Caster == m || !SpellHelper.ValidIndirectTarget(Caster, m) || !Caster.CanBeHarmful(m, false) || @@ -62,6 +64,13 @@ namespace Server.Spells.Chivalry continue; } + queue.Enqueue(m); + } + + while (queue.Count > 0) + { + var m = queue.Dequeue(); + var damage = Math.Clamp(ComputePowerValue(10) + Utility.RandomMinMax(0, 2), 8, 24); Caster.DoHarmful(m); diff --git a/Projects/UOContent/Spells/Chivalry/NobleSacrifice.cs b/Projects/UOContent/Spells/Chivalry/NobleSacrifice.cs index d7ff23d05..ac8d1b6e1 100644 --- a/Projects/UOContent/Spells/Chivalry/NobleSacrifice.cs +++ b/Projects/UOContent/Spells/Chivalry/NobleSacrifice.cs @@ -34,10 +34,9 @@ namespace Server.Spells.Chivalry { if (CheckSequence()) { - var eable = Caster.GetMobilesInRange(3); using var pool = PooledRefQueue.Create(); - foreach (var m in eable) + foreach (var m in Caster.GetMobilesInRange(3)) { if (m is not BaseCreature { IsAnimatedDead: true } && Caster != m && m.InLOS(Caster) && Caster.CanBeBeneficial(m, false, true) && m is not Golem) diff --git a/Projects/UOContent/Spells/Eighth/Earthquake.cs b/Projects/UOContent/Spells/Eighth/Earthquake.cs index f821a49fc..7f3fa5f6f 100644 --- a/Projects/UOContent/Spells/Eighth/Earthquake.cs +++ b/Projects/UOContent/Spells/Eighth/Earthquake.cs @@ -37,9 +37,8 @@ namespace Server.Spells.Eighth return; } - var eable = Caster.GetMobilesInRange(1 + (int)(Caster.Skills.Magery.Value / 15.0)); using var queue = PooledRefQueue.Create(); - foreach (var m in eable) + foreach (var m in Caster.GetMobilesInRange(1 + (int)(Caster.Skills.Magery.Value / 15.0))) { if (Caster != m && SpellHelper.ValidIndirectTarget(Caster, m) && Caster.CanBeHarmful(m, false) && (!Core.AOS || Caster.InLOS(m))) diff --git a/Projects/UOContent/Spells/Fifth/PoisonField.cs b/Projects/UOContent/Spells/Fifth/PoisonField.cs index aeedc738f..a0bcd55a7 100644 --- a/Projects/UOContent/Spells/Fifth/PoisonField.cs +++ b/Projects/UOContent/Spells/Fifth/PoisonField.cs @@ -243,17 +243,15 @@ namespace Server.Spells.Fifth if (map != null && caster != null) { var eastToWest = m_Item.ItemID == 0x3915; - var eable = map.GetMobilesInBounds( - new Rectangle2D( - m_Item.X - (eastToWest ? 0 : 1), - m_Item.Y - (eastToWest ? 1 : 0), - eastToWest ? 1 : 2, - eastToWest ? 2 : 1 - ) + var bounds = new Rectangle2D( + m_Item.X - (eastToWest ? 0 : 1), + m_Item.Y - (eastToWest ? 1 : 0), + eastToWest ? 1 : 2, + eastToWest ? 2 : 1 ); using var queue = PooledRefQueue.Create(); - foreach (var m in eable) + foreach (var m in map.GetMobilesInBounds(bounds)) { if (m.Z + 16 > m_Item.Z && m_Item.Z + 12 > m.Z && (!Core.AOS || m != caster) && SpellHelper.ValidIndirectTarget(caster, m) && caster.CanBeHarmful(m, false)) diff --git a/Projects/UOContent/Spells/Fourth/ArchCure.cs b/Projects/UOContent/Spells/Fourth/ArchCure.cs index dbd628aad..1ae41eb84 100644 --- a/Projects/UOContent/Spells/Fourth/ArchCure.cs +++ b/Projects/UOContent/Spells/Fourth/ArchCure.cs @@ -48,8 +48,7 @@ namespace Server.Spells.Fourth pool.Enqueue(directTarget); } - var eable = map.GetMobilesInRange(loc, 2); - foreach (var m in eable) + foreach (var m in map.GetMobilesInRange(loc, 2)) { if (m != directTarget && AreaCanTarget(m, feluccaRules)) { diff --git a/Projects/UOContent/Spells/Fourth/ArchProtection.cs b/Projects/UOContent/Spells/Fourth/ArchProtection.cs index 57be05124..bcb7c293b 100644 --- a/Projects/UOContent/Spells/Fourth/ArchProtection.cs +++ b/Projects/UOContent/Spells/Fourth/ArchProtection.cs @@ -48,10 +48,8 @@ namespace Server.Spells.Fourth return; } - var eable = Caster.Map.GetMobilesInRange(loc, Core.AOS ? 2 : 3); using var targets = PooledRefQueue.Create(); - - foreach (var m in eable) + foreach (var m in Caster.Map.GetMobilesInRange(loc, Core.AOS ? 2 : 3)) { if (Caster.CanBeBeneficial(m, false)) { diff --git a/Projects/UOContent/Spells/Fourth/FireField.cs b/Projects/UOContent/Spells/Fourth/FireField.cs index f412369f6..0bf48268d 100644 --- a/Projects/UOContent/Spells/Fourth/FireField.cs +++ b/Projects/UOContent/Spells/Fourth/FireField.cs @@ -244,7 +244,7 @@ namespace Server.Spells.Fourth } using var queue = PooledRefQueue.Create(); - foreach (var m in m_Item.GetMobilesInRange(0)) + foreach (var m in m_Item.GetMobilesAt()) { if (m.Z + 16 > m_Item.Z && m_Item.Z + 12 > m.Z && (!Core.AOS || m != caster) && SpellHelper.ValidIndirectTarget(caster, m) && caster.CanBeHarmful(m, false)) @@ -256,10 +256,6 @@ namespace Server.Spells.Fourth while (queue.Count > 0) { var m = queue.Dequeue(); - if (m == null) - { - continue; - } if (SpellHelper.CanRevealCaster(m)) { diff --git a/Projects/UOContent/Spells/Mysticism/CleansingWindsSpell.cs b/Projects/UOContent/Spells/Mysticism/CleansingWindsSpell.cs index 6fa63bfa9..0d2074b8c 100644 --- a/Projects/UOContent/Spells/Mysticism/CleansingWindsSpell.cs +++ b/Projects/UOContent/Spells/Mysticism/CleansingWindsSpell.cs @@ -41,6 +41,10 @@ namespace Server.Spells.Mysticism Caster.PlaySound(0x64C); + var primarySkill = GetBaseSkill(Caster); + var secondarySkill = GetDamageSkill(Caster); + var cureChance = 10000 + (int)((primarySkill + secondarySkill) / 2 * 75); + using var pool = PooledRefQueue.Create(); pool.Enqueue(m); @@ -49,12 +53,7 @@ namespace Server.Spells.Mysticism { foreach (Mobile mob in Caster.Map.GetMobilesInRange(m.Location, 2)) { - if (mob == m) - { - continue; - } - - if (casterParty.Contains(mob) && Caster.CanBeBeneficial(mob, false)) + if (mob != m && casterParty.Contains(mob) && Caster.CanBeBeneficial(mob, false)) { pool.Enqueue(mob); if (pool.Count == 4) @@ -65,9 +64,6 @@ namespace Server.Spells.Mysticism } } - var primarySkill = GetBaseSkill(Caster); - var secondarySkill = GetDamageSkill(Caster); - var toHeal = ((int)((primarySkill + secondarySkill) / 4.0) + Utility.RandomMinMax(-3, 3)) / pool.Count; while (pool.Count > 0) @@ -102,9 +98,9 @@ namespace Server.Spells.Mysticism if (target.Poisoned) { var poisonLevel = target.Poison.Level + 1; - var chanceToCure = 10000 + (int)((primarySkill + secondarySkill) / 2 * 75) - poisonLevel * 1750; + var chanceToCure = cureChance - poisonLevel * 1750; - if (chanceToCure > Utility.Random(10000) && target.CurePoison(Caster)) + if (chanceToCure > 10000 || chanceToCure > Utility.Random(10000) && target.CurePoison(Caster)) { toHealMod -= (int)(toHeal * poisonLevel * 0.15); } diff --git a/Projects/UOContent/Spells/Necromancy/Exorcism.cs b/Projects/UOContent/Spells/Necromancy/Exorcism.cs index 4f1c8980a..351404647 100644 --- a/Projects/UOContent/Spells/Necromancy/Exorcism.cs +++ b/Projects/UOContent/Spells/Necromancy/Exorcism.cs @@ -1,4 +1,5 @@ using System; +using Server.Collections; using Server.Engines.CannedEvil; using Server.Engines.PartySystem; using Server.Factions; @@ -94,14 +95,23 @@ namespace Server.Spells.Necromancy if (map != null) { - // Surprisingly, no sparkle type effects + // Cannot move a mobile while iterating mobiles in range, so use a queue + using var queue = PooledRefQueue.Create(); foreach (var m in r.Spawn.GetMobilesInRange(Range)) { if (IsValidTarget(m)) { - m.Location = GetNearestShrine(m); + queue.Enqueue(m); } } + + while (queue.Count > 0) + { + var m = queue.Dequeue(); + + // Surprisingly, no sparkle type effects + m.Location = GetNearestShrine(m); + } } } diff --git a/Projects/UOContent/Spells/Necromancy/PoisonStrike.cs b/Projects/UOContent/Spells/Necromancy/PoisonStrike.cs index bc3eb7e3f..5061f535c 100644 --- a/Projects/UOContent/Spells/Necromancy/PoisonStrike.cs +++ b/Projects/UOContent/Spells/Necromancy/PoisonStrike.cs @@ -80,8 +80,7 @@ namespace Server.Spells.Necromancy var cbc = Caster as BaseCreature; var isMonster = cbc?.Controlled == false && (cbc.IsAnimatedDead || !cbc.Summoned); - var eable = m.GetMobilesInRange(2); - foreach (Mobile targ in eable) + foreach (Mobile targ in m.GetMobilesInRange(2)) { if (targ == Caster || m == targ || !SpellHelper.ValidIndirectTarget(Caster, targ) || !Caster.CanBeHarmful(targ, false)) diff --git a/Projects/UOContent/Spells/Necromancy/Wither.cs b/Projects/UOContent/Spells/Necromancy/Wither.cs index a3ca49087..cbed55f91 100644 --- a/Projects/UOContent/Spells/Necromancy/Wither.cs +++ b/Projects/UOContent/Spells/Necromancy/Wither.cs @@ -51,8 +51,7 @@ namespace Server.Spells.Necromancy var cbc = Caster as BaseCreature; var isMonster = cbc?.Controlled == false && (cbc.IsAnimatedDead || !cbc.Summoned); - var eable = Caster.GetMobilesInRange(Core.ML ? 4 : 5); - foreach (var targ in eable) + foreach (var targ in Caster.GetMobilesInRange(Core.ML ? 4 : 5)) { if (targ == Caster || !Caster.InLOS(targ) @@ -106,7 +105,7 @@ namespace Server.Spells.Necromancy double damage = Utility.RandomMinMax(30, 35); - damage *= 300 + m.Karma / 100 + GetDamageSkill(Caster) * 10; + damage *= 300 + m.Karma / 100.0 + GetDamageSkill(Caster) * 10; damage /= 1000; var sdiBonus = AosAttributes.GetValue(Caster, AosAttribute.SpellDamage); diff --git a/Projects/UOContent/Spells/Seventh/ChainLightning.cs b/Projects/UOContent/Spells/Seventh/ChainLightning.cs index 7f5de467f..49743de6e 100644 --- a/Projects/UOContent/Spells/Seventh/ChainLightning.cs +++ b/Projects/UOContent/Spells/Seventh/ChainLightning.cs @@ -35,11 +35,9 @@ namespace Server.Spells.Seventh if (map != null) { - using var pool = PooledRefQueue.Create(); var pvp = false; - - var eable = map.GetMobilesInRange(loc, 2); - foreach (var m in eable) + using var pool = PooledRefQueue.Create(); + foreach (var m in map.GetMobilesInRange(loc, 2)) { if (Core.AOS && (m == Caster || !Caster.InLOS(m)) || !SpellHelper.ValidIndirectTarget(Caster, m) || diff --git a/Projects/UOContent/Spells/Seventh/MassDispel.cs b/Projects/UOContent/Spells/Seventh/MassDispel.cs index 47d34d0ce..1032d3916 100644 --- a/Projects/UOContent/Spells/Seventh/MassDispel.cs +++ b/Projects/UOContent/Spells/Seventh/MassDispel.cs @@ -35,11 +35,8 @@ namespace Server.Spells.Seventh if (map != null) { - var eable = map.GetMobilesInRange(new Point3D(p), 8); - using var queue = PooledRefQueue.Create(); - - foreach (var bc in eable) + foreach (var bc in map.GetMobilesInRange(new Point3D(p), 8)) { if (!(bc.IsDispellable && Caster.CanBeHarmful(bc, false))) { diff --git a/Projects/UOContent/Spells/Seventh/MeteorSwarm.cs b/Projects/UOContent/Spells/Seventh/MeteorSwarm.cs index faa766112..723ddba95 100644 --- a/Projects/UOContent/Spells/Seventh/MeteorSwarm.cs +++ b/Projects/UOContent/Spells/Seventh/MeteorSwarm.cs @@ -42,9 +42,8 @@ namespace Server.Spells.Seventh if (map != null) { - var eable = map.GetMobilesInRange(loc, 2); using var queue = PooledRefQueue.Create(); - foreach (var m in eable) + foreach (var m in map.GetMobilesInRange(loc, 2)) { if (Caster == m || !SpellHelper.ValidIndirectTarget(Caster, m) || !Caster.CanBeHarmful(m, false) || Core.AOS && !Caster.InLOS(m)) diff --git a/Projects/UOContent/Spells/Sixth/MassCurse.cs b/Projects/UOContent/Spells/Sixth/MassCurse.cs index 78ad02d9f..634e13e1e 100644 --- a/Projects/UOContent/Spells/Sixth/MassCurse.cs +++ b/Projects/UOContent/Spells/Sixth/MassCurse.cs @@ -32,9 +32,7 @@ namespace Server.Spells.Sixth if (map != null) { - var eable = map.GetMobilesInRange(new Point3D(p), 2); - - foreach (var m in eable) + foreach (var m in map.GetMobilesInRange(new Point3D(p), 2)) { if (Core.AOS && (m == Caster || !SpellHelper.ValidIndirectTarget(Caster, m) || !Caster.CanSee(m) || !Caster.CanBeHarmful(m, false))) diff --git a/Projects/UOContent/Spells/Sixth/Reveal.cs b/Projects/UOContent/Spells/Sixth/Reveal.cs index 8560071fd..a7e8263c4 100644 --- a/Projects/UOContent/Spells/Sixth/Reveal.cs +++ b/Projects/UOContent/Spells/Sixth/Reveal.cs @@ -29,12 +29,8 @@ namespace Server.Spells.Sixth if (map != null) { - var eable = map.GetMobilesInRange( - new Point3D(p), - 1 + (int)(Caster.Skills.Magery.Value / 20.0) - ); - - foreach (var m in eable) + var range = 1 + (int)(Caster.Skills.Magery.Value / 20.0); + foreach (var m in map.GetMobilesInRange(new Point3D(p), range)) { if (m is ShadowKnight && (m.X != p.X || m.Y != p.Y)) { diff --git a/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs b/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs index e51e7f936..473087a32 100644 --- a/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs +++ b/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs @@ -114,8 +114,7 @@ namespace Server.Spells.Spellweaving private bool CheckArcanists() { var spellWeaving = Caster.Skills.Spellweaving.Value; - var eable = Caster.GetMobilesInRange(1); - foreach (var m in eable) + foreach (var m in Caster.GetMobilesInRange(1)) { if (m != Caster && m is PlayerMobile && Caster.CanBeBeneficial(m, false) && Math.Abs(spellWeaving - m.Skills.Spellweaving.Value) <= 20) @@ -132,9 +131,7 @@ namespace Server.Spells.Spellweaving // Everyone gets the Arcane Focus, power capped elsewhere var pool = PooledRefQueue.Create(); - var eable = Caster.GetMobilesInRange(1); - - foreach (var m in eable) + foreach (var m in Caster.GetMobilesInRange(1)) { if (m != Caster && m is PlayerMobile && Caster.CanBeBeneficial(m, false) && Math.Abs(spellWeaving - m.Skills.Spellweaving.Value) <= 20) diff --git a/Projects/UOContent/Spells/Spellweaving/EssenceOfWind.cs b/Projects/UOContent/Spells/Spellweaving/EssenceOfWind.cs index 838bdde14..75debc119 100644 --- a/Projects/UOContent/Spells/Spellweaving/EssenceOfWind.cs +++ b/Projects/UOContent/Spells/Spellweaving/EssenceOfWind.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Server.Collections; namespace Server.Spells.Spellweaving { @@ -24,7 +25,6 @@ namespace Server.Spells.Spellweaving { Caster.PlaySound(0x5C6); - var range = 5 + FocusLevel; var damage = 25 + FocusLevel; var skill = Caster.Skills.Spellweaving.Value; @@ -34,9 +34,8 @@ namespace Server.Spells.Spellweaving var fcMalus = FocusLevel + 1; var ssiMalus = 2 * (FocusLevel + 1); - var eable = Caster.GetMobilesInRange(range); - - foreach (var m in eable) + using var queue = PooledRefQueue.Create(); + foreach (var m in Caster.GetMobilesInRange(5 + FocusLevel)) { if (Caster == m || !Caster.InLOS(m) || !SpellHelper.ValidIndirectTarget(Caster, m) || !Caster.CanBeHarmful(m, false)) @@ -44,6 +43,12 @@ namespace Server.Spells.Spellweaving continue; } + queue.Enqueue(m); + } + + while (queue.Count > 0) + { + var m = queue.Dequeue(); Caster.DoHarmful(m); SpellHelper.Damage(this, m, damage, 0, 0, 100, 0, 0); diff --git a/Projects/UOContent/Spells/Spellweaving/Thunderstorm.cs b/Projects/UOContent/Spells/Spellweaving/Thunderstorm.cs index 714c78559..14de37d65 100644 --- a/Projects/UOContent/Spells/Spellweaving/Thunderstorm.cs +++ b/Projects/UOContent/Spells/Spellweaving/Thunderstorm.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Server.Collections; namespace Server.Spells.Spellweaving { @@ -38,12 +39,10 @@ namespace Server.Spells.Spellweaving var pvpDamage = damage * (100 + Math.Min(sdiBonus, 15)) / 100; - var range = 2 + FocusLevel; var duration = TimeSpan.FromSeconds(5 + FocusLevel); - var eable = Caster.GetMobilesInRange(range); - - foreach (var m in eable) + using var queue = PooledRefQueue.Create(); + foreach (var m in Caster.GetMobilesInRange(2 + FocusLevel)) { if (Caster == m || !SpellHelper.ValidIndirectTarget(Caster, m) || !Caster.CanBeHarmful(m, false) || !Caster.InLOS(m)) @@ -51,6 +50,12 @@ namespace Server.Spells.Spellweaving continue; } + queue.Enqueue(m); + } + + while (queue.Count > 0) + { + var m = queue.Dequeue(); Caster.DoHarmful(m); var oldSpell = m.Spell as Spell;