#region References using System; using System.Collections; using System.Collections.Generic; using System.Linq; #endregion namespace Server { public delegate bool SpawnValidator(Map map, int x, int y, int z); public sealed class SpawnArea : ICollection { private static readonly TileFlag[] _EmptyFilters; private static readonly TileFlag[] _AllFilters; private static readonly Dictionary _Cache; public const ushort PixelColor = 0xFC1F; public const int Stride = 32; static SpawnArea() { _EmptyFilters = new TileFlag[0]; _AllFilters = Enum.GetValues(typeof(TileFlag)).Cast().Where(f => f != TileFlag.None).ToArray(); _Cache = new Dictionary(); } public static SpawnArea Instantiate(Region region, TileFlag filter, SpawnValidator validator, bool cache) { var name = region.Name; if (region.IsDefault || String.IsNullOrWhiteSpace(name)) { name = "Default"; } var filters = GetFilters(filter); var hash = GetHashCode(region.Map, name, filters, validator); if (!_Cache.TryGetValue(hash, out var o) || o == null) { o = new SpawnArea(region.Map, name, filters, validator); if (cache) { _Cache[hash] = o; } o.Invalidate(); } return o; } private static IEnumerable Slice(Rectangle3D rect) { if (rect.Width <= Stride && rect.Height <= Stride) { yield return rect; yield break; } int x, y, z = Math.Min(rect.Start.Z, rect.End.Z); int ow, oh, od = rect.Depth; x = rect.Start.X; while (x < rect.End.X) { ow = Math.Min(Stride, rect.End.X - x); y = rect.Start.Y; while (y < rect.End.Y) { oh = Math.Min(Stride, rect.End.Y - y); yield return new Rectangle3D(x, y, z, ow, oh, od); y += oh; } x += ow; } } private static TileFlag[] GetFilters(TileFlag filter) { if (filter == TileFlag.None) { return _EmptyFilters; } return _AllFilters.Where(f => f != TileFlag.None && filter.HasFlag(f)).ToArray(); } private static int GetHashCode(Map facet, string region, IEnumerable filters, SpawnValidator validator) { unchecked { var hash = region.Length; hash = region.Aggregate(hash, (v, c) => unchecked((v * 397) ^ Convert.ToInt32(c))); hash = (hash * 397) ^ facet.MapID; hash = (hash * 397) ^ facet.MapIndex; var filter = TileFlag.None; foreach (var f in filters) { filter |= f; } if (filter != TileFlag.None) { hash = (hash * 397) ^ (int)(((long)filter >> 0) & 0x7FFFFFFF); hash = (hash * 397) ^ (int)(((long)filter >> 32) & 0x7FFFFFFF); } if (validator != null) { hash = (hash * 397) ^ validator.GetHashCode(); } return hash; } } private Rectangle3D _Bounds; private readonly HashSet _Points; public SpawnValidator Validator { get; private set; } public TileFlag[] Filters { get; private set; } public Map Facet { get; private set; } public string Region { get; private set; } public Point2D Center { get; private set; } public Rectangle3D Bounds => _Bounds; public int Count => _Points.Count; bool ICollection.IsReadOnly => true; private SpawnArea(Map facet, string region, TileFlag[] filters, SpawnValidator validator) { _Points = new HashSet(); Facet = facet; Region = region; Filters = filters; Validator = validator; } public bool Contains(int x, int y) { return Contains(x, y, Facet.Tiles.GetLandTile(x, y).Z); } public bool Contains(int x, int y, int z) { return Contains(new Point3D(x, y, z)); } public bool Contains(IPoint3D p) { return Contains(new Point3D(p)); } public bool Contains(Point3D p) { return _Points.Contains(p); } public Point3D GetRandom() { if (Facet == null || Facet == Map.Internal || Count == 0) { return Point3D.Zero; } var p = Point3D.Zero; if (Count <= 1024) { p = _Points.ElementAt(Utility.Random(Count)); } if (p == Point3D.Zero) { do { p.X = Utility.RandomMinMax(_Bounds.Start.X, _Bounds.End.X); p.Y = Utility.RandomMinMax(_Bounds.Start.Y, _Bounds.End.Y); p.Z = Facet.Tiles.GetLandTile(p.X, p.Y).Z; } while (!Contains(p)); } if (Validator == null || Validator(Facet, p.X, p.Y, p.Z)) { return p; } return GetRandom(); } public void Invalidate() { _Points.Clear(); if (Facet == null || Facet == Map.Internal) { return; } Region region; if (String.IsNullOrWhiteSpace(Region) || Region == "Default") { region = Facet.DefaultRegion; } else if (!Facet.Regions.TryGetValue(Region, out region)) { return; } if (region == null || (!region.IsDefault && (region.Area == null || region.Area.Length == 0))) { return; } IEnumerable bounds; if (region.IsDefault) { var fw = Facet.MapID <= 1 ? 5119 : Facet.Width; var fh = Facet.MapID <= 1 ? 4095 : Facet.Height; var fd = Server.Region.MaxZ - Server.Region.MinZ; _Bounds = new Rectangle3D(0, 0, Server.Region.MinZ, fw, fh, fd); bounds = new[] { _Bounds }; } else { int x1 = Int16.MaxValue, y1 = Int16.MaxValue, z1 = SByte.MaxValue; int x2 = Int16.MinValue, y2 = Int16.MinValue, z2 = SByte.MinValue; foreach (var o in region.Area) { x1 = Math.Min(x1, o.Start.X); y1 = Math.Min(y1, o.Start.Y); z1 = Math.Min(z1, o.Start.Z); x2 = Math.Max(x2, o.End.X); y2 = Math.Max(y2, o.End.Y); z2 = Math.Max(z2, o.End.Z); } _Bounds = new Rectangle3D(x1, y1, z1, x2 - x1, y2 - y1, z2 - z1); bounds = region.Area; } var pending = bounds.SelectMany(Slice).AsParallel().SelectMany(Compute); pending = pending.WithMergeOptions(ParallelMergeOptions.NotBuffered); pending = pending.WithExecutionMode(ParallelExecutionMode.ForceParallelism); foreach (var p in pending) { _Points.Add(p); } Center = new Point2D(_Bounds.Start.X + (_Bounds.Width / 2), _Bounds.Start.Y + (_Bounds.Height / 2)); } private IEnumerable Compute(Rectangle3D area) { // Check all corners to skip large bodies of water. if (Filters.Contains(TileFlag.Wet)) { var land1 = Facet.Tiles.GetLandTile(area.Start.X, area.Start.Y); // TL var land2 = Facet.Tiles.GetLandTile(area.End.X, area.Start.Y); // TR var land3 = Facet.Tiles.GetLandTile(area.Start.X, area.End.Y); // BL var land4 = Facet.Tiles.GetLandTile(area.End.X, area.End.Y); // BR var ignore1 = land1.Ignored || TileData.LandTable[land1.ID].Flags.HasFlag(TileFlag.Wet); var ignore2 = land2.Ignored || TileData.LandTable[land2.ID].Flags.HasFlag(TileFlag.Wet); var ignore3 = land3.Ignored || TileData.LandTable[land3.ID].Flags.HasFlag(TileFlag.Wet); var ignore4 = land4.Ignored || TileData.LandTable[land4.ID].Flags.HasFlag(TileFlag.Wet); if (ignore1 && ignore2 && ignore3 && ignore4) { yield break; } } var p = Point3D.Zero; for (p.X = area.Start.X; p.X <= area.End.X; p.X++) { for (p.Y = area.Start.Y; p.Y <= area.End.Y; p.Y++) { var land = Facet.Tiles.GetLandTile(p.X, p.Y); p.Z = land.Z; if (Contains(p)) { continue; } if (!CanSpawn(p.X, p.Y, p.Z)) { continue; } if (Filters.Length > 0) { if (land.Ignored) { continue; } var flags = TileData.LandTable[land.ID].Flags; if (Filters.Any(f => flags.HasFlag(f))) { continue; } var valid = true; foreach (var tile in Facet.Tiles.GetStaticTiles(p.X, p.Y)) { flags = TileData.ItemTable[tile.ID].Flags; if (Filters.Any(f => flags.HasFlag(f))) { valid = false; break; } } if (!valid) { continue; } } if (Validator != null && !Validator(Facet, p.X, p.Y, p.Z)) { continue; } yield return p; } } } private bool CanSpawn(int x, int y, int z) { return Facet.CanFit(x, y, z, Server.Region.MaxZ - z, true, false, true); } public override int GetHashCode() { return GetHashCode(Facet, Region, Filters, Validator); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public IEnumerator GetEnumerator() { return _Points.GetEnumerator(); } void ICollection.Clear() { _Points.Clear(); } void ICollection.Add(Point3D p) { _Points.Add(p); } bool ICollection.Remove(Point3D p) { return _Points.Remove(p); } bool ICollection.Contains(Point3D p) { return _Points.Contains(p); } void ICollection.CopyTo(Point3D[] array, int index) { _Points.CopyTo(array, index); } } }