servuo/Server/SpawnArea.cs
VitaNex f840e50340 Updated .editorconfig file with advanced configuration settings that will help maintain user code style and best practices with most IDE's.
Complete core housekeeping operation using "Profile 1" with new .editorconfig settings.
2021-01-15 12:51:10 -08:00

419 lines
9.1 KiB
C#

#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<Point3D>
{
private static readonly TileFlag[] _EmptyFilters;
private static readonly TileFlag[] _AllFilters;
private static readonly Dictionary<int, SpawnArea> _Cache;
public const ushort PixelColor = 0xFC1F;
public const int Stride = 32;
static SpawnArea()
{
_EmptyFilters = new TileFlag[0];
_AllFilters = Enum.GetValues(typeof(TileFlag)).Cast<TileFlag>().Where(f => f != TileFlag.None).ToArray();
_Cache = new Dictionary<int, SpawnArea>();
}
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<Rectangle3D> 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<TileFlag> 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<Point3D> _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<Point3D>.IsReadOnly => true;
private SpawnArea(Map facet, string region, TileFlag[] filters, SpawnValidator validator)
{
_Points = new HashSet<Point3D>();
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<Rectangle3D> 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<Point3D> 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<Point3D> GetEnumerator()
{
return _Points.GetEnumerator();
}
void ICollection<Point3D>.Clear()
{
_Points.Clear();
}
void ICollection<Point3D>.Add(Point3D p)
{
_Points.Add(p);
}
bool ICollection<Point3D>.Remove(Point3D p)
{
return _Points.Remove(p);
}
bool ICollection<Point3D>.Contains(Point3D p)
{
return _Points.Contains(p);
}
void ICollection<Point3D>.CopyTo(Point3D[] array, int index)
{
_Points.CopyTo(array, index);
}
}
}