#region Map Updates /* * Name: Map Updates * Date: 20th August, 2015 * Author: Vorspire * Testing: Punkte * * Test Methods: Stealth; 250+ clients connected, all moving randomly. * Test Results: 35ms average latency (ping) under load. * * Notes: * Until these updates become main-stream, they will remain differentiated * by their preprocessor directives. When they are deemed stable enough, * old code can be removed, along with the directives. */ /* * Map_AllUpdates * * When defined, enables all updates listed below regardless of whether their * preprocessor directives are defined. * * This can be used to compile your server with all of the updates enabled by * adding a single preprocessor directive definition to your build solution. */ //#define Map_AllUpdates /* * Map_NewEnumerables * * When defined, enables a major update to the IPooledEnumerables factory. * * This update removes the need for enumerator instantiation and replaces * them with simple, yet powerful Linq queries. * * In addition, the PooledEnumerable class is replaced with a compatible, * single generic class template and takes advantage of the nature of * nested static context to ensure that a buffer pool is available for * each type of PooledEnumerable result, where result is T. * This update generally increases performance and reduces overall player * connection latency. */ #define Map_NewEnumerables /* * UseMaxRange * * When defined, enables a minor update that forces Get*InRange methods to * use Core.GlobalMaxUpdateRange, when no range is specified. * * By default, a constant range of 18 is used however, Core.GlobalMaxUpdateRange * is usually greater than that with a default value of 24. * * This update will allow things such as Effects to be displayed to more players, * as well as increasing the range of player sight. * * The benefits of this update appeal to players who choose to increase the * dimensions of their game window beyond the client's limits. * (This can also be beneficial for shards that mainly target the Enhanced client) */ #define Map_UseMaxRange /* * Map_PoolFixColumn * * When defined, enables aminor update that attempts to improve the performance * of Item stack fixing. * * Item stack fixing is a feature that corrects the Z level of items that * are stacked on a single tile. * * This update also uses linq to increase performance. */ #define Map_PoolFixColumn /* * Map_InternalProtection * * When defined, enables a minor update that protects the Internal Map from * potential name changes and ensures that Maps can be correctly parsed by * ID or Name without conflicts. * * In some cases where the AllMaps list is modified after all Maps have been * defined, the Map names may be cached and that cache will become stale. * This update removes the caching and uses Linq to improve performance. * * If you have issues with Map parsing where the Map returns null or an * unexpected Map instance, try enabling this update. * * If your shard implements any kind of feature that modifies (adds or removes) * the AllMaps list, then you should enable this update. * If this update is not enabled in the case of the above context, issues can * be verified by having your system generate a new Map instance and modifying * the AllMaps list, then using [Props on an Item or Mobile and selecting the * Map property from the Gump; The list of names may not be what you expect, * in which case, enabling this update will fix it. */ #define Map_InternalProtection #endregion #region References #region References using System; using System.Collections; using System.Collections.Generic; using Server.Items; using Server.Network; using Server.Targeting; #endregion #if Map_NewEnumerables || Map_PoolFixColumn || Map_InternalProtection || Map_AllUpdates using System.Linq; #endif #endregion namespace Server { [Flags] public enum MapRules { None = 0x0000, Internal = 0x0001, // Internal map (used for dragging, commodity deeds, etc) FreeMovement = 0x0002, // Anyone can move over anyone else without taking stamina loss BeneficialRestrictions = 0x0004, // Disallow performing beneficial actions on criminals/murderers HarmfulRestrictions = 0x0008, // Disallow performing harmful actions on innocents TrammelRules = FreeMovement | BeneficialRestrictions | HarmfulRestrictions, FeluccaRules = None } public interface IPooledEnumerable : IEnumerable { void Free(); } public interface IPooledEnumerable : IPooledEnumerable, IEnumerable { } public interface IPooledEnumerator : IEnumerator { void Free(); } #if Map_NewEnumerables || Map_AllUpdates public static class PooledEnumeration { public delegate IEnumerable Selector(Sector sector, Rectangle2D bounds); public static Selector ClientSelector { get; set; } public static Selector EntitySelector { get; set; } public static Selector MobileSelector { get; set; } public static Selector ItemSelector { get; set; } public static Selector MultiSelector { get; set; } public static Selector MultiTileSelector { get; set; } static PooledEnumeration() { ClientSelector = SelectClients; EntitySelector = SelectEntities; MobileSelector = SelectMobiles; ItemSelector = SelectItems; MultiSelector = SelectMultis; MultiTileSelector = SelectMultiTiles; } public static IEnumerable SelectClients(Sector s, Rectangle2D bounds) { return s.Clients.Where(o => o != null && o.Mobile != null && !o.Mobile.Deleted && bounds.Contains(o.Mobile)); } public static IEnumerable SelectEntities(Sector s, Rectangle2D bounds) { return Enumerable.Empty() .Union(s.Mobiles.Where(o => o != null && !o.Deleted)) .Union(s.Items.Where(o => o != null && !o.Deleted && o.Parent == null)) .Where(bounds.Contains); } public static IEnumerable SelectMobiles(Sector s, Rectangle2D bounds) { return s.Mobiles.Where(o => o != null && !o.Deleted && bounds.Contains(o)); } public static IEnumerable SelectItems(Sector s, Rectangle2D bounds) { return s.Items.Where(o => o != null && !o.Deleted && o.Parent == null && bounds.Contains(o)); } public static IEnumerable SelectMultis(Sector s, Rectangle2D bounds) { return s.Multis.Where(o => o != null && !o.Deleted && bounds.Contains(o.Location)); } public static IEnumerable SelectMultiTiles(Sector s, Rectangle2D bounds) { foreach (var o in s.Multis.Where(o => o != null && !o.Deleted)) { var c = o.Components; int x, y, xo, yo; StaticTile[] t, r; for (x = bounds.Start.X; x < bounds.End.X; x++) { xo = x - (o.X + c.Min.X); if (xo < 0 || xo >= c.Width) { continue; } for (y = bounds.Start.Y; y < bounds.End.Y; y++) { yo = y - (o.Y + c.Min.Y); if (yo < 0 || yo >= c.Height) { continue; } t = c.Tiles[xo][yo]; if (t.Length <= 0) { continue; } r = new StaticTile[t.Length]; for (var i = 0; i < t.Length; i++) { r[i] = t[i]; r[i].Z += o.Z; } yield return r; } } } } public static Map.PooledEnumerable GetClients(Map map, Rectangle2D bounds) { return Map.PooledEnumerable.Instantiate(map, bounds, ClientSelector ?? SelectClients); } public static Map.PooledEnumerable GetEntities(Map map, Rectangle2D bounds) { return Map.PooledEnumerable.Instantiate(map, bounds, EntitySelector ?? SelectEntities); } public static Map.PooledEnumerable GetMobiles(Map map, Rectangle2D bounds) { return Map.PooledEnumerable.Instantiate(map, bounds, MobileSelector ?? SelectMobiles); } public static Map.PooledEnumerable GetItems(Map map, Rectangle2D bounds) { return Map.PooledEnumerable.Instantiate(map, bounds, ItemSelector ?? SelectItems); } public static Map.PooledEnumerable GetMultis(Map map, Rectangle2D bounds) { return Map.PooledEnumerable.Instantiate(map, bounds, MultiSelector ?? SelectMultis); } public static Map.PooledEnumerable GetMultiTiles(Map map, Rectangle2D bounds) { return Map.PooledEnumerable.Instantiate(map, bounds, MultiTileSelector ?? SelectMultiTiles); } public static IEnumerable EnumerateSectors(Map map, Rectangle2D bounds) { if (map == null || map == Map.Internal) { yield break; } int x1 = bounds.Start.X, y1 = bounds.Start.Y, x2 = bounds.End.X, y2 = bounds.End.Y; if (!Bound(map, ref x1, ref y1, ref x2, ref y2, out var xSector, out var ySector)) { yield break; } var index = 0; while (NextSector(map, x1, y1, x2, y2, ref index, ref xSector, ref ySector, out var s)) { yield return s; } } public static bool Bound(Map map, ref int x1, ref int y1, ref int x2, ref int y2, out int xSector, out int ySector) { if (map == null || map == Map.Internal) { xSector = ySector = 0; return false; } map.Bound(x1, y1, out x1, out y1); map.Bound(x2 - 1, y2 - 1, out x2, out y2); x1 >>= Map.SectorShift; y1 >>= Map.SectorShift; x2 >>= Map.SectorShift; y2 >>= Map.SectorShift; xSector = x1; ySector = y1; return true; } private static bool NextSector( Map map, int x1, int y1, int x2, int y2, ref int index, ref int xSector, ref int ySector, out Sector s) { if (map == null) { s = null; xSector = ySector = 0; return false; } if (map == Map.Internal) { s = map.InvalidSector; xSector = ySector = 0; return false; } if (index++ > 0) { if (++ySector > y2) { ySector = y1; if (++xSector > x2) { xSector = x1; s = map.InvalidSector; return false; } } } s = map.GetRealSector(xSector, ySector); return true; } } #endif [Parsable] //[CustomEnum( new string[]{ "Felucca", "Trammel", "Ilshenar", "Malas", "Internal" } )] public class Map : IComparable, IComparable { #region Compile-Time -> Run-Time Support #if Map_NewEnumerables || Map_AllUpdates public static readonly bool NewEnumerables = true; #else public static readonly bool NewEnumerables = false; #endif #if Map_UseMaxRange || Map_AllUpdates public static readonly bool UseMaxRange = true; #else public static readonly bool UseMaxRange = false; #endif #if Map_PoolFixColumn || Map_AllUpdates public static readonly bool PoolFixColumn = true; #else public static readonly bool PoolFixColumn = false; #endif #if Map_InternalProtection || Map_AllUpdates public static readonly bool InternalProtection = true; #else public static readonly bool InternalProtection = false; #endif #endregion public const int SectorSize = 16; public const int SectorShift = 4; public static int SectorActiveRange = 2; private static readonly Map[] m_Maps = new Map[0x100]; public static Map[] Maps => m_Maps; public static Map Felucca => m_Maps[0]; public static Map Trammel => m_Maps[1]; public static Map Ilshenar => m_Maps[2]; public static Map Malas => m_Maps[3]; public static Map Tokuno => m_Maps[4]; public static Map TerMur => m_Maps[5]; public static Map Internal => m_Maps[0x7F]; private static readonly List m_AllMaps = new List(); public static List AllMaps => m_AllMaps; private readonly int m_MapID; private readonly int m_MapIndex; private readonly int m_FileIndex; private readonly int m_Width; private readonly int m_Height; private readonly int m_SectorsWidth; private readonly int m_SectorsHeight; private readonly Dictionary m_Regions; private Region m_DefaultRegion; public int Season { get; set; } private readonly Sector[][] m_Sectors; private readonly Sector m_InvalidSector; private TileMatrix m_Tiles; #if Map_InternalProtection || Map_AllUpdates public static string[] GetMapNames() { return m_Maps.Where(m => m != null).Select(m => m.Name).ToArray(); } public static Map[] GetMapValues() { return m_Maps.Where(m => m != null).ToArray(); } public static Map Parse(string value) { if (String.IsNullOrWhiteSpace(value)) { return null; } if (Insensitive.Equals(value, "Internal")) { return Internal; } if (!Int32.TryParse(value, out var index)) { return m_Maps.FirstOrDefault(m => m != null && Insensitive.Equals(m.Name, value)); } if (index == 127) { return Internal; } return m_Maps.FirstOrDefault(m => m != null && m.MapIndex == index); } public override string ToString() { return Name; } #else private static string[] m_MapNames; private static Map[] m_MapValues; public static string[] GetMapNames() { CheckNamesAndValues(); return m_MapNames; } public static Map[] GetMapValues() { CheckNamesAndValues(); return m_MapValues; } public static Map Parse(string value) { CheckNamesAndValues(); for (int i = 0; i < m_MapNames.Length; ++i) { if (Insensitive.Equals(m_MapNames[i], value)) { return m_MapValues[i]; } } int index; if (int.TryParse(value, out index)) { if (index >= 0 && index < m_Maps.Length && m_Maps[index] != null) { return m_Maps[index]; } } throw new ArgumentException("Invalid map name"); } private static void CheckNamesAndValues() { if (m_MapNames != null && m_MapNames.Length == m_AllMaps.Count) { return; } m_MapNames = new string[m_AllMaps.Count]; m_MapValues = new Map[m_AllMaps.Count]; for (int i = 0; i < m_AllMaps.Count; ++i) { Map map = m_AllMaps[i]; m_MapNames[i] = map.Name; m_MapValues[i] = map; } } public override string ToString() { return Name; } #endif public int GetAverageZ(int x, int y) { int z = 0, avg = 0, top = 0; GetAverageZ(x, y, ref z, ref avg, ref top); return avg; } public void GetAverageZ(int x, int y, ref int z, ref int avg, ref int top) { var zTop = Tiles.GetLandTile(x, y).Z; var zLeft = Tiles.GetLandTile(x, y + 1).Z; var zRight = Tiles.GetLandTile(x + 1, y).Z; var zBottom = Tiles.GetLandTile(x + 1, y + 1).Z; z = zTop; if (zLeft < z) { z = zLeft; } if (zRight < z) { z = zRight; } if (zBottom < z) { z = zBottom; } top = zTop; if (zLeft > top) { top = zLeft; } if (zRight > top) { top = zRight; } if (zBottom > top) { top = zBottom; } if (Math.Abs(zTop - zBottom) > Math.Abs(zLeft - zRight)) { avg = FloorAverage(zLeft, zRight); } else { avg = FloorAverage(zTop, zBottom); } } private static int FloorAverage(int a, int b) { var v = a + b; if (v < 0) { --v; } return v / 2; } #if Map_NewEnumerables || Map_AllUpdates #region Get*InRange/Bounds public IPooledEnumerable GetObjectsInRange(Point3D p) { #if Map_UseMaxRange || Map_AllUpdates return GetObjectsInRange(p, Core.GlobalMaxUpdateRange); #else return GetObjectsInRange(p, 18); #endif } public IPooledEnumerable GetObjectsInRange(Point3D p, int range) { return GetObjectsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); } public IPooledEnumerable GetObjectsInBounds(Rectangle2D bounds) { return PooledEnumeration.GetEntities(this, bounds); } public IPooledEnumerable GetClientsInRange(Point3D p) { #if Map_UseMaxRange || Map_AllUpdates return GetClientsInRange(p, Core.GlobalMaxUpdateRange); #else return GetClientsInRange(p, 18); #endif } public IPooledEnumerable GetClientsInRange(Point3D p, int range) { return GetClientsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); } public IPooledEnumerable GetClientsInBounds(Rectangle2D bounds) { return PooledEnumeration.GetClients(this, bounds); } public IPooledEnumerable GetItemsInRange(Point3D p) { #if Map_UseMaxRange || Map_AllUpdates return GetItemsInRange(p, Core.GlobalMaxUpdateRange); #else return GetItemsInRange(p, 18); #endif } public IPooledEnumerable GetItemsInRange(Point3D p, int range) { return GetItemsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); } public IPooledEnumerable GetItemsInBounds(Rectangle2D bounds) { return PooledEnumeration.GetItems(this, bounds); } public IPooledEnumerable GetMobilesInRange(Point3D p) { #if Map_UseMaxRange || Map_AllUpdates return GetMobilesInRange(p, Core.GlobalMaxUpdateRange); #else return GetMobilesInRange(p, 18); #endif } public IPooledEnumerable GetMobilesInRange(Point3D p, int range) { return GetMobilesInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); } public IPooledEnumerable GetMobilesInBounds(Rectangle2D bounds) { return PooledEnumeration.GetMobiles(this, bounds); } #endregion public IPooledEnumerable GetMultiTilesAt(int x, int y) { return PooledEnumeration.GetMultiTiles(this, new Rectangle2D(x, y, 1, 1)); } #else #region Get*InRange/Bounds public IPooledEnumerable GetObjectsInRange(Point3D p) { #if Map_UseMaxRange || Map_AllUpdates return GetObjectsInRange(p, Core.GlobalMaxUpdateRange); #else return GetObjectsInRange(p, 18); #endif } public IPooledEnumerable GetObjectsInRange(Point3D p, int range) { if (this == Internal) { return NullEnumerable.Instance; } return PooledEnumerable.Instantiate( EntityEnumerator.Instantiate(this, new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1))); } public IPooledEnumerable GetObjectsInBounds(Rectangle2D bounds) { if (this == Internal) { return NullEnumerable.Instance; } return PooledEnumerable.Instantiate(EntityEnumerator.Instantiate(this, bounds)); } public IPooledEnumerable GetClientsInRange(Point3D p) { #if Map_UseMaxRange || Map_AllUpdates return GetClientsInRange(p, Core.GlobalMaxUpdateRange); #else return GetClientsInRange(p, 18); #endif } public IPooledEnumerable GetClientsInRange(Point3D p, int range) { if (this == Internal) { return NullEnumerable.Instance; } return PooledEnumerable.Instantiate( ClientEnumerator.Instantiate(this, new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1))); } public IPooledEnumerable GetClientsInBounds(Rectangle2D bounds) { if (this == Internal) { return NullEnumerable.Instance; } return PooledEnumerable.Instantiate(ClientEnumerator.Instantiate(this, bounds)); } public IPooledEnumerable GetItemsInRange(Point3D p) { #if Map_UseMaxRange || Map_AllUpdates return GetItemsInRange(p, Core.GlobalMaxUpdateRange); #else return GetItemsInRange(p, 18); #endif } public IPooledEnumerable GetItemsInRange(Point3D p, int range) { if (this == Internal) { return NullEnumerable.Instance; } return PooledEnumerable.Instantiate( ItemEnumerator.Instantiate(this, new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1))); } public IPooledEnumerable GetItemsInBounds(Rectangle2D bounds) { if (this == Internal) { return NullEnumerable.Instance; } return PooledEnumerable.Instantiate(ItemEnumerator.Instantiate(this, bounds)); } public IPooledEnumerable GetMobilesInRange(Point3D p) { #if Map_UseMaxRange || Map_AllUpdates return GetMobilesInRange(p, Core.GlobalMaxUpdateRange); #else return GetMobilesInRange(p, 18); #endif } public IPooledEnumerable GetMobilesInRange(Point3D p, int range) { if (this == Internal) { return NullEnumerable.Instance; } return PooledEnumerable.Instantiate( MobileEnumerator.Instantiate(this, new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1))); } public IPooledEnumerable GetMobilesInBounds(Rectangle2D bounds) { if (this == Internal) { return NullEnumerable.Instance; } return PooledEnumerable.Instantiate(MobileEnumerator.Instantiate(this, bounds)); } #endregion public IPooledEnumerable GetMultiTilesAt(int x, int y) { if (this == Internal) { return NullEnumerable.Instance; } Sector sector = GetSector(x, y); if (sector.Multis.Count == 0) { return NullEnumerable.Instance; } return PooledEnumerable.Instantiate(MultiTileEnumerator.Instantiate(sector, new Point2D(x, y))); } #endif #region CanFit public bool CanFit(Point3D p, int height, bool checkBlocksFit) { return CanFit(p.m_X, p.m_Y, p.m_Z, height, checkBlocksFit, true, true); } public bool CanFit(Point3D p, int height, bool checkBlocksFit, bool checkMobiles) { return CanFit(p.m_X, p.m_Y, p.m_Z, height, checkBlocksFit, checkMobiles, true, false); } public bool CanFit(Point2D p, int z, int height, bool checkBlocksFit) { return CanFit(p.m_X, p.m_Y, z, height, checkBlocksFit, true, true, false); } public bool CanFit(Point3D p, int height) { return CanFit(p.m_X, p.m_Y, p.m_Z, height, false, true, true, false); } public bool CanFit(Point2D p, int z, int height) { return CanFit(p.m_X, p.m_Y, z, height, false, true, true, false); } public bool CanFit(int x, int y, int z, int height) { return CanFit(x, y, z, height, false, true, true, false); } public bool CanFit(int x, int y, int z, int height, bool checksBlocksFit) { return CanFit(x, y, z, height, checksBlocksFit, true, true, false); } public bool CanFit(int x, int y, int z, int height, bool checkBlocksFit, bool checkMobiles) { return CanFit(x, y, z, height, checkBlocksFit, checkMobiles, true, false); } public bool CanFit(int x, int y, int z, int height, bool checkBlocksFit, bool checkMobiles, bool requireSurface) { return CanFit(x, y, z, height, checkBlocksFit, checkMobiles, requireSurface, false); } public bool CanFit(int x, int y, int z, int height, bool checkBlocksFit, bool checkMobiles, bool requireSurface, bool ignoreRoof) { if (this == Internal) { return false; } if (x < 0 || y < 0 || x >= m_Width || y >= m_Height) { return false; } int lowZ = 0, avgZ = 0, topZ = 0; GetAverageZ(x, y, ref lowZ, ref avgZ, ref topZ); var lt = Tiles.GetLandTile(x, y); var landFlags = TileData.LandTable[lt.ID & TileData.MaxLandValue].Flags; if ((landFlags & TileFlag.Impassable) != 0 && avgZ > z && (z + height) > lowZ) { return false; } var hasSurface = (landFlags & TileFlag.Impassable) == 0 && z == avgZ && !lt.Ignored; var staticTiles = Tiles.GetStaticTiles(x, y, true); bool surface, impassable, roof; foreach (var t in staticTiles) { var id = TileData.ItemTable[t.ID & TileData.MaxItemValue]; surface = id.Surface; impassable = id.Impassable; roof = (id.Flags & TileFlag.Roof) != 0; if ((surface || impassable) && (!ignoreRoof || !roof) && (t.Z + id.CalcHeight) > z && (z + height) > t.Z) { return false; } if (surface && !impassable && z == (t.Z + id.CalcHeight)) { hasSurface = true; } } var sector = GetSector(x, y); var items = sector.Items; var mobs = sector.Mobiles; foreach (var item in items) { if (!(item is BaseMulti) && item.ItemID <= TileData.MaxItemValue && item.AtWorldPoint(x, y)) { var id = item.ItemData; surface = id.Surface; impassable = id.Impassable; if ((surface || impassable || (checkBlocksFit && item.BlocksFit)) && (item.Z + id.CalcHeight) > z && (z + height) > item.Z) { return false; } if (surface && !impassable && !item.Movable && z == (item.Z + id.CalcHeight)) { hasSurface = true; } } } if (checkMobiles) { foreach (var m in mobs) { if (m.Location.m_X == x && m.Location.m_Y == y && (m.AccessLevel == AccessLevel.Player || !m.Hidden)) { if ((m.Z + 16) > z && (z + height) > m.Z) { return false; } } } } return !requireSurface || hasSurface; } public bool CanFit(int x, int y, int z, int height, bool checkBlocksFit, bool checkMobiles, bool requireSurface, Mobile mob) { if (this == Internal) return false; if (x < 0 || y < 0 || x >= Width || y >= Height) return false; var hasSurface = false; var checkmob = false; var canswim = false; var cantwalk = false; if (mob != null) { checkmob = true; canswim = mob.CanSwim; cantwalk = mob.CantWalk; } var lt = Tiles.GetLandTile(x, y); int lowZ = 0, avgZ = 0, topZ = 0; bool surface, impassable; var wet = false; GetAverageZ(x, y, ref lowZ, ref avgZ, ref topZ); var landFlags = TileData.LandTable[lt.ID & TileData.MaxLandValue].Flags; impassable = (landFlags & TileFlag.Impassable) != 0; if (checkmob) { wet = (landFlags & TileFlag.Wet) != 0; // dont allow wateronly creatures on land if (cantwalk && !wet) impassable = true; // allow water creatures on water if (canswim && wet) { impassable = false; } } if (impassable && avgZ > z && (z + height) > lowZ) return false; else if (!impassable && z == avgZ && !lt.Ignored) hasSurface = true; var staticTiles = Tiles.GetStaticTiles(x, y, true); for (var i = 0; i < staticTiles.Length; ++i) { var id = TileData.ItemTable[staticTiles[i].ID & TileData.MaxItemValue]; surface = id.Surface; impassable = id.Impassable; if (checkmob) { wet = (id.Flags & TileFlag.Wet) != 0; // dont allow wateronly creatures on land if (cantwalk && !wet) impassable = true; // allow water creatures on water if (canswim && wet) { surface = true; impassable = false; } } if ((surface || impassable) && (staticTiles[i].Z + id.CalcHeight) > z && (z + height) > staticTiles[i].Z) return false; else if (surface && !impassable && z == (staticTiles[i].Z + id.CalcHeight)) hasSurface = true; } var sector = GetSector(x, y); var items = sector.Items; var mobs = sector.Mobiles; for (var i = 0; i < items.Count; ++i) { var item = items[i]; if (item.ItemID < 0x4000 && item.AtWorldPoint(x, y)) { var id = item.ItemData; surface = id.Surface; impassable = id.Impassable; if (checkmob) { wet = (id.Flags & TileFlag.Wet) != 0; // dont allow wateronly creatures on land if (cantwalk && !wet) impassable = true; // allow water creatures on water if (canswim && wet) { surface = true; impassable = false; } } if ((surface || impassable || (checkBlocksFit && item.BlocksFit)) && (item.Z + id.CalcHeight) > z && (z + height) > item.Z) return false; else if (surface && !impassable && !item.Movable && z == (item.Z + id.CalcHeight)) hasSurface = true; } } if (checkMobiles) { for (var i = 0; i < mobs.Count; ++i) { var m = mobs[i]; if (m.Location.X == x && m.Location.Y == y && (m.AccessLevel == AccessLevel.Player || !m.Hidden)) { if ((m.Z + 16) > z && (z + height) > m.Z) return false; } } } return !requireSurface || hasSurface; } #endregion #region CanSpawnMobile public bool CanSpawnMobile(Point3D p) { return CanSpawnMobile(p.m_X, p.m_Y, p.m_Z); } public bool CanSpawnMobile(Point2D p, int z) { return CanSpawnMobile(p.m_X, p.m_Y, z); } public bool CanSpawnMobile(int x, int y, int z) { if (!Region.Find(new Point3D(x, y, z), this).AllowSpawn()) { return false; } return CanFit(x, y, z, 16); } #endregion #region Find Item/Mobile public TItem FindItem(Point3D p, int range = 0) where TItem : Item { var eable = GetItemsInRange(p, range); foreach (var item in eable) { if (item.GetType() == typeof(TItem)) { eable.Free(); return item as TItem; } } eable.Free(); return null; } public IEnumerable FindItems(Point3D p, int range = 0) where TItem : Item { var eable = GetItemsInRange(p, range); foreach (var item in eable) { if (item.GetType() == typeof(TItem)) { yield return item as TItem; } } eable.Free(); } public TMob FindMobile(Point3D p, int range = 0) where TMob : Mobile { var eable = GetMobilesInRange(p, range); foreach (var m in eable) { if (m.GetType() == typeof(TMob)) { eable.Free(); return m as TMob; } } eable.Free(); return null; } public IEnumerable FindMobiles(Point3D p, int range = 0) where TMob : Mobile { var eable = GetMobilesInRange(p, range); foreach (var m in eable) { if (m.GetType() == typeof(TMob)) { yield return m as TMob; } } eable.Free(); } #endregion #region Spawn Position public Point3D GetSpawnPosition(Point3D center, int range) { for (var i = 0; i < 10; i++) { var x = center.X + (Utility.Random((range * 2) + 1) - range); var y = center.Y + (Utility.Random((range * 2) + 1) - range); var z = GetAverageZ(x, y); if (CanSpawnMobile(new Point2D(x, y), center.Z)) return new Point3D(x, y, center.Z); if (CanSpawnMobile(new Point2D(x, y), z)) return new Point3D(x, y, z); } return center; } public Point3D GetRandomSpawnPoint(Rectangle2D rec) { if (this == Internal) return Point3D.Zero; var x = Utility.RandomMinMax(rec.X, rec.X + rec.Width); var y = Utility.RandomMinMax(rec.Y, rec.Y + rec.Height); var z = GetAverageZ(x, y); return new Point3D(x, y, z); } #endregion private class ZComparer : IComparer { public static readonly ZComparer Default = new ZComparer(); public int Compare(Item x, Item y) { if (x == null || y == null) return 0; return x.Z.CompareTo(y.Z); } } #if Map_PoolFixColumn || Map_AllUpdates private static readonly Queue> _FixPool = new Queue>(128); private static readonly List _EmptyFixItems = new List(); private static List AcquireFixItems(Map map, int x, int y) { if (map == null || map == Internal || x < 0 || x > map.Width || y < 0 || y > map.Height) { return _EmptyFixItems; } List pool = null; lock (_FixPool) { if (_FixPool.Count > 0) { pool = _FixPool.Dequeue(); } } if (pool == null) { pool = new List(128); // Arbitrary limit } var eable = map.GetItemsInRange(new Point3D(x, y, 0), 0); pool.AddRange( eable.Where(item => item.ItemID <= TileData.MaxItemValue && !(item is BaseMulti)) .OrderBy(item => item.Z) .Take(pool.Capacity)); eable.Free(); return pool; } private static void FreeFixItems(List pool) { if (pool == _EmptyFixItems) { return; } pool.Clear(); lock (_FixPool) { if (_FixPool.Count < 128) { _FixPool.Enqueue(pool); } } } public void FixColumn(int x, int y) { var landTile = Tiles.GetLandTile(x, y); var tiles = Tiles.GetStaticTiles(x, y, true); int landZ = 0, landAvg = 0, landTop = 0; GetAverageZ(x, y, ref landZ, ref landAvg, ref landTop); var items = AcquireFixItems(this, x, y); for (var i = 0; i < items.Count; i++) { var toFix = items[i]; if (!toFix.Movable) { continue; } var z = Int32.MinValue; var currentZ = toFix.Z; if (!landTile.Ignored && landAvg <= currentZ) { z = landAvg; } foreach (var tile in tiles) { var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; var checkZ = tile.Z; var checkTop = checkZ + id.CalcHeight; if (checkTop == checkZ && !id.Surface) { ++checkTop; } if (checkTop > z && checkTop <= currentZ) { z = checkTop; } } for (var j = 0; j < items.Count; ++j) { if (j == i) { continue; } var item = items[j]; var id = item.ItemData; var checkZ = item.Z; var checkTop = checkZ + id.CalcHeight; if (checkTop == checkZ && !id.Surface) { ++checkTop; } if (checkTop > z && checkTop <= currentZ) { z = checkTop; } } if (z != Int32.MinValue) { toFix.Location = new Point3D(toFix.X, toFix.Y, z); } } FreeFixItems(items); } #else public void FixColumn(int x, int y) { LandTile landTile = Tiles.GetLandTile(x, y); int landZ = 0, landAvg = 0, landTop = 0; GetAverageZ(x, y, ref landZ, ref landAvg, ref landTop); StaticTile[] tiles = Tiles.GetStaticTiles(x, y, true); List items = new List(); IPooledEnumerable eable = GetItemsInRange(new Point3D(x, y, 0), 0); foreach (Item item in eable) { if (!(item is BaseMulti) && item.ItemID <= TileData.MaxItemValue) { items.Add(item); if (items.Count > 100) { break; } } } eable.Free(); if (items.Count > 100) { return; } items.Sort(ZComparer.Default); for (int i = 0; i < items.Count; ++i) { Item toFix = items[i]; if (!toFix.Movable) { continue; } int z = int.MinValue; int currentZ = toFix.Z; if (!landTile.Ignored && landAvg <= currentZ) { z = landAvg; } for (int j = 0; j < tiles.Length; ++j) { StaticTile tile = tiles[j]; ItemData id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; int checkZ = tile.Z; int checkTop = checkZ + id.CalcHeight; if (checkTop == checkZ && !id.Surface) { ++checkTop; } if (checkTop > z && checkTop <= currentZ) { z = checkTop; } } for (int j = 0; j < items.Count; ++j) { if (j == i) { continue; } Item item = items[j]; ItemData id = item.ItemData; int checkZ = item.Z; int checkTop = checkZ + id.CalcHeight; if (checkTop == checkZ && !id.Surface) { ++checkTop; } if (checkTop > z && checkTop <= currentZ) { z = checkTop; } } if (z != int.MinValue) { toFix.Location = new Point3D(toFix.X, toFix.Y, z); } } } #endif /// /// Gets the highest surface that is lower than . /// /// The reference point. /// A surface IEntity or Item. public object GetTopSurface(Point3D p) { if (this == Internal) { return null; } object surface = null; var surfaceZ = Int32.MinValue; var lt = Tiles.GetLandTile(p.X, p.Y); if (!lt.Ignored) { var avgZ = GetAverageZ(p.X, p.Y); if (avgZ <= p.Z) { surface = lt; surfaceZ = avgZ; if (surfaceZ == p.Z) { return surface; } } } var staticTiles = Tiles.GetStaticTiles(p.X, p.Y, true); foreach (var tile in staticTiles) { var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; if (id.Surface || (id.Flags & TileFlag.Wet) != 0) { var tileZ = tile.Z + id.CalcHeight; if (tileZ > surfaceZ && tileZ <= p.Z) { surface = tile; surfaceZ = tileZ; if (surfaceZ == p.Z) { return surface; } } } } var sector = GetSector(p.X, p.Y); foreach (var item in sector.Items) { if (!(item is BaseMulti) && item.ItemID <= TileData.MaxItemValue && item.AtWorldPoint(p.X, p.Y) && !item.Movable) { var id = item.ItemData; if (id.Surface || (id.Flags & TileFlag.Wet) != 0) { var itemZ = item.Z + id.CalcHeight; if (itemZ > surfaceZ && itemZ <= p.Z) { surface = item; surfaceZ = itemZ; if (surfaceZ == p.Z) { return surface; } } } } } return surface; } public void Bound(int x, int y, out int newX, out int newY) { if (x < 0) { newX = 0; } else if (x >= m_Width) { newX = m_Width - 1; } else { newX = x; } if (y < 0) { newY = 0; } else if (y >= m_Height) { newY = m_Height - 1; } else { newY = y; } } public Point2D Bound(Point2D p) { int x = p.m_X, y = p.m_Y; if (x < 0) { x = 0; } else if (x >= m_Width) { x = m_Width - 1; } if (y < 0) { y = 0; } else if (y >= m_Height) { y = m_Height - 1; } return new Point2D(x, y); } public Map(int mapID, int mapIndex, int fileIndex, int width, int height, int season, string name, MapRules rules) { m_MapID = mapID; m_MapIndex = mapIndex; m_FileIndex = fileIndex; m_Width = width; m_Height = height; Season = season; Name = name; Rules = rules; m_Regions = new Dictionary(StringComparer.OrdinalIgnoreCase); m_InvalidSector = new Sector(0, 0, this); m_SectorsWidth = width >> SectorShift; m_SectorsHeight = height >> SectorShift; m_Sectors = new Sector[m_SectorsWidth][]; } #region GetSector public Sector GetSector(Point3D p) { return InternalGetSector(p.m_X >> SectorShift, p.m_Y >> SectorShift); } public Sector GetSector(Point2D p) { return InternalGetSector(p.m_X >> SectorShift, p.m_Y >> SectorShift); } public Sector GetSector(IPoint2D p) { return InternalGetSector(p.X >> SectorShift, p.Y >> SectorShift); } public Sector GetSector(int x, int y) { return InternalGetSector(x >> SectorShift, y >> SectorShift); } public Sector GetRealSector(int x, int y) { return InternalGetSector(x, y); } private Sector InternalGetSector(int x, int y) { if (x >= 0 && x < m_SectorsWidth && y >= 0 && y < m_SectorsHeight) { var xSectors = m_Sectors[x]; if (xSectors == null) { m_Sectors[x] = xSectors = new Sector[m_SectorsHeight]; } var sec = xSectors[y]; if (sec == null) { xSectors[y] = sec = new Sector(x, y, this); } return sec; } else { return m_InvalidSector; } } #endregion public void ActivateSectors(int cx, int cy) { for (var x = cx - SectorActiveRange; x <= cx + SectorActiveRange; ++x) { for (var y = cy - SectorActiveRange; y <= cy + SectorActiveRange; ++y) { var sect = GetRealSector(x, y); if (sect != m_InvalidSector) { sect.Activate(); } } } } public void DeactivateSectors(int cx, int cy) { for (var x = cx - SectorActiveRange; x <= cx + SectorActiveRange; ++x) { for (var y = cy - SectorActiveRange; y <= cy + SectorActiveRange; ++y) { var sect = GetRealSector(x, y); if (sect != m_InvalidSector && !PlayersInRange(sect, SectorActiveRange)) { sect.Deactivate(); } } } } private bool PlayersInRange(Sector sect, int range) { for (var x = sect.X - range; x <= sect.X + range; ++x) { for (var y = sect.Y - range; y <= sect.Y + range; ++y) { var check = GetRealSector(x, y); if (check != m_InvalidSector && check.Players.Count > 0) { return true; } } } return false; } public void OnClientChange(NetState oldState, NetState newState, Mobile m) { if (this != Internal) { GetSector(m).OnClientChange(oldState, newState); } } public virtual void OnEnter(Mobile m) { if (this != Internal) { GetSector(m).OnEnter(m); } } public virtual void OnEnter(Item item) { if (this == Internal) { return; } GetSector(item).OnEnter(item); if (item is BaseMulti) { var m = (BaseMulti)item; var mcl = m.Components; var start = GetMultiMinSector(item.Location, mcl); var end = GetMultiMaxSector(item.Location, mcl); AddMulti(m, start, end); } } public virtual void OnLeave(Mobile m) { if (this != Internal) { GetSector(m).OnLeave(m); } } public virtual void OnLeave(Item item) { if (this == Internal) { return; } GetSector(item).OnLeave(item); if (item is BaseMulti) { var m = (BaseMulti)item; var mcl = m.Components; var start = GetMultiMinSector(item.Location, mcl); var end = GetMultiMaxSector(item.Location, mcl); RemoveMulti(m, start, end); } } public void RemoveMulti(BaseMulti m, Sector start, Sector end) { if (this == Internal) { return; } for (var x = start.X; x <= end.X; ++x) { for (var y = start.Y; y <= end.Y; ++y) { InternalGetSector(x, y).OnMultiLeave(m); } } } public void AddMulti(BaseMulti m, Sector start, Sector end) { if (this == Internal) { return; } for (var x = start.X; x <= end.X; ++x) { for (var y = start.Y; y <= end.Y; ++y) { InternalGetSector(x, y).OnMultiEnter(m); } } } public Sector GetMultiMinSector(Point3D loc, MultiComponentList mcl) { return GetSector(Bound(new Point2D(loc.m_X + mcl.Min.m_X, loc.m_Y + mcl.Min.m_Y))); } public Sector GetMultiMaxSector(Point3D loc, MultiComponentList mcl) { return GetSector(Bound(new Point2D(loc.m_X + mcl.Max.m_X, loc.m_Y + mcl.Max.m_Y))); } public virtual void OnMove(Point3D oldLocation, Mobile m) { if (this == Internal) { return; } var oldSector = GetSector(oldLocation); var newSector = GetSector(m.Location); if (oldSector != newSector) { oldSector.OnLeave(m); newSector.OnEnter(m); } } public virtual void OnMove(Point3D oldLocation, Item item) { if (this == Internal) { return; } var oldSector = GetSector(oldLocation); var newSector = GetSector(item.Location); if (oldSector != newSector) { oldSector.OnLeave(item); newSector.OnEnter(item); } if (item is BaseMulti) { var m = (BaseMulti)item; var mcl = m.Components; var start = GetMultiMinSector(item.Location, mcl); var end = GetMultiMaxSector(item.Location, mcl); var oldStart = GetMultiMinSector(oldLocation, mcl); var oldEnd = GetMultiMaxSector(oldLocation, mcl); if (oldStart != start || oldEnd != end) { RemoveMulti(m, oldStart, oldEnd); AddMulti(m, start, end); } } } private readonly object tileLock = new object(); public TileMatrix Tiles { get { if (m_Tiles != null) { return m_Tiles; } lock (tileLock) return m_Tiles ?? (m_Tiles = new TileMatrix(this, m_FileIndex, m_MapID, m_Width, m_Height)); } } public int MapID => m_MapID; public int MapIndex => m_MapIndex; public int Width => m_Width; public int Height => m_Height; public Dictionary Regions => m_Regions; public void RegisterRegion(Region reg) { var regName = reg.Name; if (regName != null) { if (m_Regions.ContainsKey(regName)) { Console.WriteLine("Warning: Duplicate region name '{0}' for map '{1}'", regName, Name); } else { m_Regions[regName] = reg; } } } public void UnregisterRegion(Region reg) { var regName = reg.Name; if (regName != null) { m_Regions.Remove(regName); } } public Region DefaultRegion { get => m_DefaultRegion ?? (m_DefaultRegion = new Region(null, this, 0, new Rectangle3D[0])); set => m_DefaultRegion = value; } public MapRules Rules { get; set; } public Sector InvalidSector => m_InvalidSector; public string Name { get; set; } #if Map_NewEnumerables || Map_AllUpdates public class NullEnumerable : IPooledEnumerable { public static readonly NullEnumerable Instance = new NullEnumerable(); private readonly IEnumerable _Empty; private NullEnumerable() { _Empty = Enumerable.Empty(); } IEnumerator IEnumerable.GetEnumerator() { return _Empty.GetEnumerator(); } public IEnumerator GetEnumerator() { return _Empty.GetEnumerator(); } public void Free() { } } public sealed class PooledEnumerable : IPooledEnumerable, IDisposable { private static readonly Queue> _Buffer = new Queue>(0x400); public static PooledEnumerable Instantiate(Map map, Rectangle2D bounds, PooledEnumeration.Selector selector) { PooledEnumerable e = null; lock (((ICollection)_Buffer).SyncRoot) { if (_Buffer.Count > 0) { e = _Buffer.Dequeue(); } } var pool = PooledEnumeration.EnumerateSectors(map, bounds).SelectMany(s => selector(s, bounds)); if (e != null) { e._Pool.AddRange(pool); } else { e = new PooledEnumerable(pool); } return e; } private bool _IsDisposed; private List _Pool = new List(0x40); private IEnumerable InternalPool { get { var i = _Pool.Count; while (--i >= 0) { if (i < _Pool.Count) { yield return _Pool[i]; } } } } public PooledEnumerable(IEnumerable pool) { _Pool.AddRange(pool); } IEnumerator IEnumerable.GetEnumerator() { return InternalPool.GetEnumerator(); } public IEnumerator GetEnumerator() { return InternalPool.GetEnumerator(); } public void Free() { if (_IsDisposed) { return; } _Pool.Clear(); if (_Pool.Capacity > 0x100) { _Pool.Capacity = 0x100; } lock (((ICollection)_Buffer).SyncRoot) { _Buffer.Enqueue(this); } } public void Dispose() { _IsDisposed = true; _Pool.Clear(); _Pool.TrimExcess(); _Pool = null; } } #else #region Enumerables public class NullEnumerable : IPooledEnumerable { private readonly InternalEnumerator m_Enumerator; public static readonly NullEnumerable Instance = new NullEnumerable(); private NullEnumerable() { m_Enumerator = new InternalEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return m_Enumerator; } public IEnumerator GetEnumerator() { return m_Enumerator; } public void Free() { } private class InternalEnumerator : IEnumerator { public void Reset() { } object IEnumerator.Current => null; public K Current => default(K); public bool MoveNext() { return false; } void IDisposable.Dispose() { } } } private class PooledEnumerable : IPooledEnumerable, IDisposable { private IPooledEnumerator m_Enumerator; private static readonly Queue> m_InstancePool = new Queue>(); public static PooledEnumerable Instantiate(IPooledEnumerator etor) { PooledEnumerable e = null; lock (m_InstancePool) { if (m_InstancePool.Count > 0) { e = m_InstancePool.Dequeue(); e.m_Enumerator = etor; } } if (e == null) { e = new PooledEnumerable(etor); } return e; } private PooledEnumerable(IPooledEnumerator etor) { m_Enumerator = etor; } IEnumerator IEnumerable.GetEnumerator() { if (m_Enumerator == null) { throw new ObjectDisposedException("PooledEnumerable", "GetEnumerator() called after Free()"); } return m_Enumerator; } public IEnumerator GetEnumerator() { if (m_Enumerator == null) { throw new ObjectDisposedException("PooledEnumerable", "GetEnumerator() called after Free()"); } return m_Enumerator; } public void Free() { if (m_Enumerator != null) { m_Enumerator.Free(); m_Enumerator = null; } lock (m_InstancePool) { if (m_InstancePool.Count < 200) // Arbitrary { m_InstancePool.Enqueue(this); } } } public void Dispose() { // Don't return disposed objects to the instance pool //Free(); if (m_Enumerator != null) { m_Enumerator.Free(); m_Enumerator = null; } } } #endregion #region Enumerators private class ClientEnumerator : IPooledEnumerator { private Map m_Map; private Rectangle2D m_Bounds; private int m_xSector, m_ySector; private int m_xSectorStart, m_ySectorStart; private int m_xSectorEnd, m_ySectorEnd; private List m_CurrentList; private int m_CurrentIndex; private static readonly Queue m_InstancePool = new Queue(); public static ClientEnumerator Instantiate(Map map, Rectangle2D bounds) { ClientEnumerator e = null; lock (m_InstancePool) { if (m_InstancePool.Count > 0) { e = m_InstancePool.Dequeue(); e.m_Map = map; e.m_Bounds = bounds; } } if (e == null) { e = new ClientEnumerator(map, bounds); } e.Reset(); return e; } public void Free() { if (m_Map == null) { return; } m_Map = null; lock (m_InstancePool) { if (m_InstancePool.Count < 200) // Arbitrary { m_InstancePool.Enqueue(this); } } } private ClientEnumerator(Map map, Rectangle2D bounds) { m_Map = map; m_Bounds = bounds; } public NetState Current => m_CurrentList[m_CurrentIndex]; object IEnumerator.Current => m_CurrentList[m_CurrentIndex]; void IDisposable.Dispose() { } public bool MoveNext() { while (true) { ++m_CurrentIndex; if (m_CurrentIndex == m_CurrentList.Count) { ++m_ySector; if (m_ySector > m_ySectorEnd) { m_ySector = m_ySectorStart; ++m_xSector; if (m_xSector > m_xSectorEnd) { m_CurrentIndex = -1; return false; } } m_CurrentIndex = -1; m_CurrentList = m_Map.InternalGetSector(m_xSector, m_ySector).Clients; } else { Mobile m = m_CurrentList[m_CurrentIndex].Mobile; if (m != null && !m.Deleted && m_Bounds.Contains(m.Location)) { return true; } } } } public void Reset() { m_Map.Bound(m_Bounds.Start.m_X, m_Bounds.Start.m_Y, out m_xSectorStart, out m_ySectorStart); m_Map.Bound(m_Bounds.End.m_X - 1, m_Bounds.End.m_Y - 1, out m_xSectorEnd, out m_ySectorEnd); m_xSector = m_xSectorStart >>= SectorShift; m_ySector = m_ySectorStart >>= SectorShift; m_xSectorEnd >>= SectorShift; m_ySectorEnd >>= SectorShift; m_CurrentIndex = -1; m_CurrentList = m_Map.InternalGetSector(m_xSector, m_ySector).Clients; } } private class EntityEnumerator : IPooledEnumerator { private Map m_Map; private Rectangle2D m_Bounds; private int m_xSector, m_ySector; private int m_xSectorStart, m_ySectorStart; private int m_xSectorEnd, m_ySectorEnd; private int m_Stage; private IList m_CurrentList; private int m_CurrentIndex; private static readonly Queue m_InstancePool = new Queue(); public static EntityEnumerator Instantiate(Map map, Rectangle2D bounds) { EntityEnumerator e = null; lock (m_InstancePool) { if (m_InstancePool.Count > 0) { e = m_InstancePool.Dequeue(); e.m_Map = map; e.m_Bounds = bounds; } } if (e == null) { e = new EntityEnumerator(map, bounds); } e.Reset(); return e; } public void Free() { if (m_Map == null) { return; } m_Map = null; lock (m_InstancePool) { if (m_InstancePool.Count < 200) // Arbitrary { m_InstancePool.Enqueue(this); } } } private EntityEnumerator(Map map, Rectangle2D bounds) { m_Map = map; m_Bounds = bounds; } public IEntity Current => (IEntity)m_CurrentList[m_CurrentIndex]; object IEnumerator.Current => m_CurrentList[m_CurrentIndex]; void IDisposable.Dispose() { } public bool MoveNext() { while (true) { ++m_CurrentIndex; if (m_CurrentIndex < 0 || m_CurrentIndex > m_CurrentList.Count) { // Sanity Console.WriteLine("EntityEnumerator OOB: {0}", m_CurrentIndex); return false; } if (m_CurrentIndex == m_CurrentList.Count) { ++m_ySector; if (m_ySector > m_ySectorEnd) { m_ySector = m_ySectorStart; ++m_xSector; if (m_xSector > m_xSectorEnd) { if (m_Stage > 0) { m_CurrentIndex = -1; return false; } ++m_Stage; m_xSector = m_xSectorStart >>= SectorShift; m_ySector = m_ySectorStart >>= SectorShift; } } m_CurrentIndex = -1; if (m_Stage == 0) { m_CurrentList = m_Map.InternalGetSector(m_xSector, m_ySector).Items; } else { m_CurrentList = m_Map.InternalGetSector(m_xSector, m_ySector).Mobiles; } } else { IEntity e = (IEntity)m_CurrentList[m_CurrentIndex]; if (e.Deleted) { continue; } if (e is Item) { Item item = (Item)e; if (item.Parent != null) { continue; } } if (m_Bounds.Contains(e.Location)) { return true; } } } } public void Reset() { m_Map.Bound(m_Bounds.Start.m_X, m_Bounds.Start.m_Y, out m_xSectorStart, out m_ySectorStart); m_Map.Bound(m_Bounds.End.m_X - 1, m_Bounds.End.m_Y - 1, out m_xSectorEnd, out m_ySectorEnd); m_xSector = m_xSectorStart >>= SectorShift; m_ySector = m_ySectorStart >>= SectorShift; m_xSectorEnd >>= SectorShift; m_ySectorEnd >>= SectorShift; m_CurrentIndex = -1; m_Stage = 0; m_CurrentList = m_Map.InternalGetSector(m_xSector, m_ySector).Items; } } private class ItemEnumerator : IPooledEnumerator { private Map m_Map; private Rectangle2D m_Bounds; private int m_xSector, m_ySector; private int m_xSectorStart, m_ySectorStart; private int m_xSectorEnd, m_ySectorEnd; private List m_CurrentList; private int m_CurrentIndex; private static readonly Queue m_InstancePool = new Queue(); public static ItemEnumerator Instantiate(Map map, Rectangle2D bounds) { ItemEnumerator e = null; lock (m_InstancePool) { if (m_InstancePool.Count > 0) { e = m_InstancePool.Dequeue(); e.m_Map = map; e.m_Bounds = bounds; } } if (e == null) { e = new ItemEnumerator(map, bounds); } e.Reset(); return e; } public void Free() { if (m_Map == null) { return; } m_Map = null; lock (m_InstancePool) { if (m_InstancePool.Count < 200) // Arbitrary { m_InstancePool.Enqueue(this); } } } private ItemEnumerator(Map map, Rectangle2D bounds) { m_Map = map; m_Bounds = bounds; } public Item Current => m_CurrentList[m_CurrentIndex]; object IEnumerator.Current => m_CurrentList[m_CurrentIndex]; void IDisposable.Dispose() { } public bool MoveNext() { while (true) { ++m_CurrentIndex; if (m_CurrentIndex == m_CurrentList.Count) { ++m_ySector; if (m_ySector > m_ySectorEnd) { m_ySector = m_ySectorStart; ++m_xSector; if (m_xSector > m_xSectorEnd) { m_CurrentIndex = -1; return false; } } m_CurrentIndex = -1; m_CurrentList = m_Map.InternalGetSector(m_xSector, m_ySector).Items; } else { Item item = m_CurrentList[m_CurrentIndex]; if (!item.Deleted && item.Parent == null && m_Bounds.Contains(item.Location)) { return true; } } } } public void Reset() { m_Map.Bound(m_Bounds.Start.m_X, m_Bounds.Start.m_Y, out m_xSectorStart, out m_ySectorStart); m_Map.Bound(m_Bounds.End.m_X - 1, m_Bounds.End.m_Y - 1, out m_xSectorEnd, out m_ySectorEnd); m_xSector = m_xSectorStart >>= SectorShift; m_ySector = m_ySectorStart >>= SectorShift; m_xSectorEnd >>= SectorShift; m_ySectorEnd >>= SectorShift; m_CurrentIndex = -1; m_CurrentList = m_Map.InternalGetSector(m_xSector, m_ySector).Items; } } private class MobileEnumerator : IPooledEnumerator { private Map m_Map; private Rectangle2D m_Bounds; private int m_xSector, m_ySector; private int m_xSectorStart, m_ySectorStart; private int m_xSectorEnd, m_ySectorEnd; private List m_CurrentList; private int m_CurrentIndex; private static readonly Queue m_InstancePool = new Queue(); public static MobileEnumerator Instantiate(Map map, Rectangle2D bounds) { MobileEnumerator e = null; lock (m_InstancePool) { if (m_InstancePool.Count > 0) { e = m_InstancePool.Dequeue(); e.m_Map = map; e.m_Bounds = bounds; } } if (e == null) { e = new MobileEnumerator(map, bounds); } e.Reset(); return e; } public void Free() { if (m_Map == null) { return; } m_Map = null; lock (m_InstancePool) { if (m_InstancePool.Count < 200) // Arbitrary { m_InstancePool.Enqueue(this); } } } private MobileEnumerator(Map map, Rectangle2D bounds) { m_Map = map; m_Bounds = bounds; } public Mobile Current => m_CurrentList[m_CurrentIndex]; object IEnumerator.Current => m_CurrentList[m_CurrentIndex]; void IDisposable.Dispose() { } public bool MoveNext() { while (true) { ++m_CurrentIndex; if (m_CurrentIndex == m_CurrentList.Count) { ++m_ySector; if (m_ySector > m_ySectorEnd) { m_ySector = m_ySectorStart; ++m_xSector; if (m_xSector > m_xSectorEnd) { m_CurrentIndex = -1; return false; } } m_CurrentIndex = -1; m_CurrentList = m_Map.InternalGetSector(m_xSector, m_ySector).Mobiles; } else { Mobile m = m_CurrentList[m_CurrentIndex]; if (!m.Deleted && m_Bounds.Contains(m.Location)) { return true; } } } } public void Reset() { m_Map.Bound(m_Bounds.Start.m_X, m_Bounds.Start.m_Y, out m_xSectorStart, out m_ySectorStart); m_Map.Bound(m_Bounds.End.m_X - 1, m_Bounds.End.m_Y - 1, out m_xSectorEnd, out m_ySectorEnd); m_xSector = m_xSectorStart >>= SectorShift; m_ySector = m_ySectorStart >>= SectorShift; m_xSectorEnd >>= SectorShift; m_ySectorEnd >>= SectorShift; m_CurrentIndex = -1; m_CurrentList = m_Map.InternalGetSector(m_xSector, m_ySector).Mobiles; } } private class MultiTileEnumerator : IPooledEnumerator { private List m_List; private Point2D m_Location; private StaticTile[] m_Current; private int m_Index; private static readonly Queue m_InstancePool = new Queue(); public static MultiTileEnumerator Instantiate(Sector sector, Point2D loc) { MultiTileEnumerator e = null; lock (m_InstancePool) { if (m_InstancePool.Count > 0) { e = m_InstancePool.Dequeue(); e.m_List = sector.Multis; e.m_Location = loc; } } if (e == null) { e = new MultiTileEnumerator(sector, loc); } e.Reset(); return e; } private MultiTileEnumerator(Sector sector, Point2D loc) { m_List = sector.Multis; m_Location = loc; } public StaticTile[] Current => m_Current; object IEnumerator.Current => m_Current; void IDisposable.Dispose() { } public bool MoveNext() { while (++m_Index < m_List.Count) { BaseMulti m = m_List[m_Index]; if (m != null && !m.Deleted) { MultiComponentList list = m.Components; int xOffset = m_Location.m_X - (m.Location.m_X + list.Min.m_X); int yOffset = m_Location.m_Y - (m.Location.m_Y + list.Min.m_Y); if (xOffset >= 0 && xOffset < list.Width && yOffset >= 0 && yOffset < list.Height) { StaticTile[] tiles = list.Tiles[xOffset][yOffset]; if (tiles.Length > 0) { StaticTile[] copy = new StaticTile[tiles.Length]; for (int i = 0; i < copy.Length; ++i) { copy[i] = tiles[i]; copy[i].Z += m.Z; } m_Current = copy; return true; } } } } return false; } public void Free() { if (m_List == null) { return; } lock (m_InstancePool) { if (m_InstancePool.Count < 200) // Arbitrary { m_InstancePool.Enqueue(this); } m_List = null; } } public void Reset() { m_Current = null; m_Index = -1; } } #endregion #endif public Point3D GetPoint(object o, bool eye) { Point3D p; if (o is Mobile) { p = ((Mobile)o).Location; p.Z += 14; //eye ? 15 : 10; } else if (o is Item) { p = ((Item)o).GetWorldLocation(); p.Z += (((Item)o).ItemData.Height / 2) + 1; } else if (o is Point3D) { p = (Point3D)o; } else if (o is LandTarget) { p = ((LandTarget)o).Location; int low = 0, avg = 0, top = 0; GetAverageZ(p.X, p.Y, ref low, ref avg, ref top); p.Z = top + 1; } else if (o is StaticTarget) { var st = (StaticTarget)o; var id = TileData.ItemTable[st.ItemID & TileData.MaxItemValue]; p = new Point3D(st.X, st.Y, st.Z - id.CalcHeight + (id.Height / 2)); } else if (o is IPoint3D) { p = new Point3D((IPoint3D)o); } else { Console.WriteLine("Warning: Invalid object ({0}) in line of sight", o); p = Point3D.Zero; } return p; } #region Line Of Sight private static int m_MaxLOSDistance = Core.GlobalMaxUpdateRange + 1; public static int MaxLOSDistance { get => m_MaxLOSDistance; set => m_MaxLOSDistance = value; } public bool LineOfSight(Point3D org, Point3D dest) { if (this == Internal) { return false; } if (!Utility.InRange(org, dest, m_MaxLOSDistance)) { return false; } var end = dest; if (org.X > dest.X || (org.X == dest.X && org.Y > dest.Y) || (org.X == dest.X && org.Y == dest.Y && org.Z > dest.Z)) { var swap = org; org = dest; dest = swap; } if (org == dest) { return true; } var xd = dest.m_X - org.m_X; var yd = dest.m_Y - org.m_Y; var zd = dest.m_Z - org.m_Z; var zslp = Math.Sqrt(xd * xd + yd * yd); double sq3d; if (zd != 0) { sq3d = Math.Sqrt(zslp * zslp + zd * zd); } else { sq3d = zslp; } var rise = yd / sq3d; var run = xd / sq3d; zslp = zd / sq3d; double x = org.m_X; double y = org.m_Y; double z = org.m_Z; var path = new Point3DList(); int ix, iy, iz; Point3D p; while (Utility.NumberBetween(x, dest.m_X, org.m_X, 0.5) && Utility.NumberBetween(y, dest.m_Y, org.m_Y, 0.5) && Utility.NumberBetween(z, dest.m_Z, org.m_Z, 0.5)) { ix = (int)Math.Round(x); iy = (int)Math.Round(y); iz = (int)Math.Round(z); if (path.Count > 0) { p = path.Last; if (p.m_X != ix || p.m_Y != iy || p.m_Z != iz) { path.Add(ix, iy, iz); } } else { path.Add(ix, iy, iz); } x += run; y += rise; z += zslp; } if (path.Count == 0) { return true; } p = path.Last; if (p != dest) { path.Add(dest); } Point3D pTop = org, pBottom = dest; Utility.FixPoints(ref pTop, ref pBottom); var pathCount = path.Count; var endTop = end.m_Z + 1; int height, landZ, landAvg, landTop, pointTop, ltID; bool contains; Point3D point; LandTile landTile; ItemData id; TileFlag flags; StaticTile[] statics; IPooledEnumerable eable; for (var i = 0; i < pathCount; ++i) { point = path[i]; pointTop = point.m_Z; landTile = Tiles.GetLandTile(point.X, point.Y); landZ = landAvg = landTop = 0; GetAverageZ(point.m_X, point.m_Y, ref landZ, ref landAvg, ref landTop); if (landZ <= pointTop && landTop >= point.m_Z && (point.m_X != end.m_X || point.m_Y != end.m_Y || landZ > endTop || landTop < end.m_Z) && !landTile.Ignored) { return false; } statics = Tiles.GetStaticTiles(point.m_X, point.m_Y, true); contains = false; ltID = landTile.ID; for (var j = 0; !contains && j < m_InvalidLandTiles.Length; ++j) { contains = ltID == m_InvalidLandTiles[j]; } if (contains && statics.Length == 0) { eable = GetItemsInRange(point, 0); foreach (var item in eable) { if (item.Visible) { contains = false; } if (!contains) { break; } } eable.Free(); if (contains) { return false; } } foreach (var t in statics) { id = TileData.ItemTable[t.ID & TileData.MaxItemValue]; flags = id.Flags; height = id.CalcHeight; if (t.Z <= pointTop && t.Z + height >= point.Z && (flags & (TileFlag.Window | TileFlag.NoShoot)) != 0) { if (point.m_X != end.m_X || point.m_Y != end.m_Y || t.Z > endTop || t.Z + height < end.m_Z) { return false; } } } } var rect = new Rectangle2D(pTop.m_X, pTop.m_Y, pBottom.m_X - pTop.m_X + 1, pBottom.m_Y - pTop.m_Y + 1); var area = GetItemsInBounds(rect); try { int count; bool found; Point3D loc; foreach (var i in area) { if (!i.Visible) { continue; } if (i is BaseMulti || i.ItemID > TileData.MaxItemValue) { continue; } id = i.ItemData; flags = id.Flags; if ((flags & (TileFlag.Window | TileFlag.NoShoot)) == 0) { continue; } height = id.CalcHeight; found = false; count = path.Count; for (var j = 0; j < count; ++j) { point = path[j]; pointTop = point.m_Z + 1; loc = i.Location; if (loc.m_X == point.m_X && loc.m_Y == point.m_Y && loc.m_Z <= pointTop && loc.m_Z + height >= point.m_Z) { if (loc.m_X == end.m_X && loc.m_Y == end.m_Y && loc.m_Z <= endTop && loc.m_Z + height >= end.m_Z) { continue; } found = true; break; } } if (found) { return false; } } } finally { area.Free(); } return true; } public bool LineOfSight(object from, object dest) { if (from == dest || (from is Mobile && ((Mobile)from).AccessLevel > AccessLevel.Player)) { return true; } if (dest is Item && from is Mobile && ((Item)dest).RootParent == from) { return true; } return LineOfSight(GetPoint(from, true), GetPoint(dest, false)); } public bool LineOfSight(Mobile from, Point3D target) { if (from.AccessLevel > AccessLevel.Player) { return true; } var eye = from.Location; eye.Z += 14; return LineOfSight(eye, target); } public bool LineOfSight(Mobile from, Mobile to) { if (from == to || from.AccessLevel > AccessLevel.Player) { return true; } var eye = from.Location; var target = to.Location; eye.Z += 14; target.Z += 14; return LineOfSight(eye, target); } #endregion private static int[] m_InvalidLandTiles = { 0x244 }; public static int[] InvalidLandTiles { get => m_InvalidLandTiles; set => m_InvalidLandTiles = value; } public int CompareTo(Map other) { if (other == null) { return -1; } return m_MapID.CompareTo(other.m_MapID); } public int CompareTo(object other) { return CompareTo(other as Map); } } }