diff --git a/.editorconfig b/.editorconfig index 28dc3d60c5..c2ed880e28 100644 --- a/.editorconfig +++ b/.editorconfig @@ -136,9 +136,12 @@ csharp_preserve_single_line_blocks = true csharp_style_namespace_declarations = file_scoped:silent # ReSharper properties +resharper_csharp_wrap_after_declaration_lpar = true resharper_csharp_wrap_after_invocation_lpar = true resharper_csharp_wrap_arguments_style = chop_if_long +resharper_csharp_wrap_before_declaration_rpar = true resharper_csharp_wrap_before_invocation_rpar = true +resharper_csharp_wrap_parameters_style = chop_if_long resharper_keep_existing_invocation_parens_arrangement = false resharper_keep_existing_property_patterns_arrangement = false resharper_max_invocation_arguments_on_line = 3 diff --git a/Framework/Intersect.Framework.Core/GameObjects/Maps/MapAutotiles.cs b/Framework/Intersect.Framework.Core/GameObjects/Maps/MapAutotiles.cs index da76cd0cba..1b252ac6a3 100644 --- a/Framework/Intersect.Framework.Core/GameObjects/Maps/MapAutotiles.cs +++ b/Framework/Intersect.Framework.Core/GameObjects/Maps/MapAutotiles.cs @@ -1,4 +1,6 @@ -using Intersect.Logging; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Intersect.Logging; namespace Intersect.GameObjects.Maps; @@ -96,7 +98,13 @@ public partial class MapAutotiles private readonly MapBase mMyMap; - public Dictionary Layers; + private Dictionary Layers { get; set; } + + private readonly int _mapWidth = Options.MapWidth; + private readonly int _mapHeight = Options.MapHeight; + private readonly int _tileWidth = Options.TileWidth; + private readonly int _tileHeight = Options.TileHeight; + private readonly List _layersAll = Options.Instance.MapOpts.Layers.All; public MapAutotiles(MapBase map) { @@ -108,339 +116,458 @@ public MapAutotiles(MapBase map) } } + public bool TryGetAutoTilesForLayer(string layerName, [NotNullWhen(true)] out QuarterTileCls[,]? quarterTiles) + { + if (Layers != default) + { + return Layers.TryGetValue(layerName, out quarterTiles); + } + + LegacyLogging.Logger?.Error($"[{mMyMap?.Id.ToString() ?? "No map?"}][AutoTiles] Layers not initialized!"); + quarterTiles = null; + return quarterTiles != null; + } + + public bool TryGetAutoTileForLayer(string layerName, int x, int y, [NotNullWhen(true)] out QuarterTileCls? quarterTile) + { + if (TryGetAutoTilesForLayer(layerName, out var quarterTiles)) + { + return TryGetAutoTile( + quarterTiles, + x, + y, + out quarterTile + ); + } + + quarterTile = null; + return false; + + } + + private bool TryGetAutoTile( + QuarterTileCls[,] quarterTiles, + int x, + int y, + [NotNullWhen(true)] out QuarterTileCls? quarterTile + ) + { + if (x < 0 || _mapWidth <= x || y < 0 || _mapHeight <= y) + { + quarterTile = null; + return false; + } + + quarterTile = quarterTiles[x, y]; + return quarterTile != null; + } + private void InitVxAutotileTemplate() { // Inner tiles (Top right subtile region) // NW - a - AutoInner[1].X = (Int16) Options.TileWidth; + AutoInner[1].X = (Int16) _tileWidth; AutoInner[1].Y = 0; // NE - b - AutoInner[2].X = (Int16) (2 * Options.TileWidth - Options.TileWidth / 2); + AutoInner[2].X = (Int16) (2 * _tileWidth - _tileWidth / 2); AutoInner[2].Y = 0; // SW - c - AutoInner[3].X = (Int16) Options.TileWidth; - AutoInner[3].Y = (Int16) (Options.TileHeight / 2); + AutoInner[3].X = (Int16) _tileWidth; + AutoInner[3].Y = (Int16) (_tileHeight / 2); // SE - d - AutoInner[4].X = (Int16) (2 * Options.TileWidth - Options.TileWidth / 2); - AutoInner[4].Y = (Int16) (Options.TileHeight / 2); + AutoInner[4].X = (Int16) (2 * _tileWidth - _tileWidth / 2); + AutoInner[4].Y = (Int16) (_tileHeight / 2); // Outer Tiles - NW (bottom subtile region) // NW - e AutoNw[1].X = 0; - AutoNw[1].Y = (Int16) Options.TileHeight; + AutoNw[1].Y = (Int16) _tileHeight; // NE - f - AutoNw[2].X = (Int16) (Options.TileWidth / 2); - AutoNw[2].Y = (Int16) Options.TileHeight; + AutoNw[2].X = (Int16) (_tileWidth / 2); + AutoNw[2].Y = (Int16) _tileHeight; // SW - g AutoNw[3].X = 0; - AutoNw[3].Y = (Int16) (2 * Options.TileHeight - Options.TileHeight / 2); + AutoNw[3].Y = (Int16) (2 * _tileHeight - _tileHeight / 2); // SE - h - AutoNw[4].X = (Int16) (Options.TileWidth / 2); - AutoNw[4].Y = (Int16) (2 * Options.TileHeight - Options.TileHeight / 2); + AutoNw[4].X = (Int16) (_tileWidth / 2); + AutoNw[4].Y = (Int16) (2 * _tileHeight - _tileHeight / 2); // Outer Tiles - NE (bottom subtile region) // NW - i - AutoNe[1].X = (Int16) Options.TileWidth; - AutoNe[1].Y = (Int16) Options.TileHeight; + AutoNe[1].X = (Int16) _tileWidth; + AutoNe[1].Y = (Int16) _tileHeight; // NE - g - AutoNe[2].X = (Int16) (2 * Options.TileWidth - Options.TileWidth / 2); - AutoNe[2].Y = (Int16) Options.TileHeight; + AutoNe[2].X = (Int16) (2 * _tileWidth - _tileWidth / 2); + AutoNe[2].Y = (Int16) _tileHeight; // SW - k - AutoNe[3].X = (Int16) Options.TileWidth; - AutoNe[3].Y = (Int16) (2 * Options.TileHeight - Options.TileHeight / 2); + AutoNe[3].X = (Int16) _tileWidth; + AutoNe[3].Y = (Int16) (2 * _tileHeight - _tileHeight / 2); // SE - l - AutoNe[4].X = (Int16) (2 * Options.TileWidth - Options.TileWidth / 2); - AutoNe[4].Y = (Int16) (2 * Options.TileHeight - Options.TileHeight / 2); + AutoNe[4].X = (Int16) (2 * _tileWidth - _tileWidth / 2); + AutoNe[4].Y = (Int16) (2 * _tileHeight - _tileHeight / 2); // Outer Tiles - SW (bottom subtile region) // NW - m AutoSw[1].X = 0; - AutoSw[1].Y = (Int16) (2 * Options.TileHeight); + AutoSw[1].Y = (Int16) (2 * _tileHeight); // NE - n - AutoSw[2].X = (Int16) (Options.TileWidth / 2); - AutoSw[2].Y = (Int16) (2 * Options.TileHeight); + AutoSw[2].X = (Int16) (_tileWidth / 2); + AutoSw[2].Y = (Int16) (2 * _tileHeight); // SW - o AutoSw[3].X = 0; - AutoSw[3].Y = (Int16) (2 * Options.TileHeight + Options.TileHeight / 2); + AutoSw[3].Y = (Int16) (2 * _tileHeight + _tileHeight / 2); // SE - p - AutoSw[4].X = (Int16) (Options.TileWidth / 2); - AutoSw[4].Y = (Int16) (2 * Options.TileHeight + Options.TileHeight / 2); + AutoSw[4].X = (Int16) (_tileWidth / 2); + AutoSw[4].Y = (Int16) (2 * _tileHeight + _tileHeight / 2); // Outer Tiles - SE (bottom subtile region) // NW - q - AutoSe[1].X = (Int16) Options.TileWidth; - AutoSe[1].Y = (Int16) (2 * Options.TileHeight); + AutoSe[1].X = (Int16) _tileWidth; + AutoSe[1].Y = (Int16) (2 * _tileHeight); // NE - r - AutoSe[2].X = (Int16) (2 * Options.TileWidth - Options.TileWidth / 2); - AutoSe[2].Y = (Int16) (2 * Options.TileHeight); + AutoSe[2].X = (Int16) (2 * _tileWidth - _tileWidth / 2); + AutoSe[2].Y = (Int16) (2 * _tileHeight); // SW - s - AutoSe[3].X = (Int16) Options.TileWidth; - AutoSe[3].Y = (Int16) (2 * Options.TileHeight + Options.TileHeight / 2); + AutoSe[3].X = (Int16) _tileWidth; + AutoSe[3].Y = (Int16) (2 * _tileHeight + _tileHeight / 2); // SE - t - AutoSe[4].X = (Int16) (2 * Options.TileWidth - Options.TileWidth / 2); - AutoSe[4].Y = (Int16) (2 * Options.TileHeight + Options.TileHeight / 2); + AutoSe[4].X = (Int16) (2 * _tileWidth - _tileWidth / 2); + AutoSe[4].Y = (Int16) (2 * _tileHeight + _tileHeight / 2); } private void InitXpAutotileTemplate() { // Inner tiles (Top right subtile region) // NW - a - AutoInnerXp[1].X = (Int16) (Options.TileWidth * 2); + AutoInnerXp[1].X = (Int16) (_tileWidth * 2); AutoInnerXp[1].Y = 0; // NE - b - AutoInnerXp[2].X = (Int16) (2 * Options.TileWidth + Options.TileWidth / 2); + AutoInnerXp[2].X = (Int16) (2 * _tileWidth + _tileWidth / 2); AutoInnerXp[2].Y = 0; // SW - c - AutoInnerXp[3].X = (Int16) (Options.TileWidth * 2); - AutoInnerXp[3].Y = (Int16) (Options.TileHeight / 2); + AutoInnerXp[3].X = (Int16) (_tileWidth * 2); + AutoInnerXp[3].Y = (Int16) (_tileHeight / 2); // SE - d - AutoInnerXp[4].X = (Int16) (2 * Options.TileWidth + Options.TileWidth / 2); - AutoInnerXp[4].Y = (Int16) (Options.TileHeight / 2); + AutoInnerXp[4].X = (Int16) (2 * _tileWidth + _tileWidth / 2); + AutoInnerXp[4].Y = (Int16) (_tileHeight / 2); // Outer Tiles - NW (bottom subtile region) // NW - e AutoNwXp[1].X = 0; - AutoNwXp[1].Y = (Int16) Options.TileHeight; + AutoNwXp[1].Y = (Int16) _tileHeight; // NE - f - AutoNwXp[2].X = (Int16) (Options.TileWidth / 2); - AutoNwXp[2].Y = (Int16) Options.TileHeight; + AutoNwXp[2].X = (Int16) (_tileWidth / 2); + AutoNwXp[2].Y = (Int16) _tileHeight; // SW - g AutoNwXp[3].X = 0; - AutoNwXp[3].Y = (Int16) (Options.TileHeight + Options.TileHeight / 2); + AutoNwXp[3].Y = (Int16) (_tileHeight + _tileHeight / 2); // SE - h - AutoNwXp[4].X = (Int16) (Options.TileWidth / 2); - AutoNwXp[4].Y = (Int16) (Options.TileHeight + Options.TileHeight / 2); + AutoNwXp[4].X = (Int16) (_tileWidth / 2); + AutoNwXp[4].Y = (Int16) (_tileHeight + _tileHeight / 2); // Outer Tiles - NE (bottom subtile region) // NW - i - AutoNeXp[1].X = (Int16) (Options.TileWidth * 2); - AutoNeXp[1].Y = (Int16) Options.TileHeight; + AutoNeXp[1].X = (Int16) (_tileWidth * 2); + AutoNeXp[1].Y = (Int16) _tileHeight; // NE - g - AutoNeXp[2].X = (Int16) (2 * Options.TileWidth + Options.TileWidth / 2); - AutoNeXp[2].Y = (Int16) Options.TileHeight; + AutoNeXp[2].X = (Int16) (2 * _tileWidth + _tileWidth / 2); + AutoNeXp[2].Y = (Int16) _tileHeight; // SW - k - AutoNeXp[3].X = (Int16) (Options.TileWidth * 2); - AutoNeXp[3].Y = (Int16) (Options.TileHeight + Options.TileHeight / 2); + AutoNeXp[3].X = (Int16) (_tileWidth * 2); + AutoNeXp[3].Y = (Int16) (_tileHeight + _tileHeight / 2); // SE - l - AutoNeXp[4].X = (Int16) (2 * Options.TileWidth + Options.TileWidth / 2); - AutoNeXp[4].Y = (Int16) (Options.TileHeight + Options.TileHeight / 2); + AutoNeXp[4].X = (Int16) (2 * _tileWidth + _tileWidth / 2); + AutoNeXp[4].Y = (Int16) (_tileHeight + _tileHeight / 2); // Outer Tiles - SW (bottom subtile region) // NW - m AutoSwXp[1].X = 0; - AutoSwXp[1].Y = (Int16) (3 * Options.TileHeight); + AutoSwXp[1].Y = (Int16) (3 * _tileHeight); // NE - n - AutoSwXp[2].X = (Int16) (Options.TileWidth / 2); - AutoSwXp[2].Y = (Int16) (3 * Options.TileHeight); + AutoSwXp[2].X = (Int16) (_tileWidth / 2); + AutoSwXp[2].Y = (Int16) (3 * _tileHeight); // SW - o AutoSwXp[3].X = 0; - AutoSwXp[3].Y = (Int16) (3 * Options.TileHeight + Options.TileHeight / 2); + AutoSwXp[3].Y = (Int16) (3 * _tileHeight + _tileHeight / 2); // SE - p - AutoSwXp[4].X = (Int16) (Options.TileWidth / 2); - AutoSwXp[4].Y = (Int16) (3 * Options.TileHeight + Options.TileHeight / 2); + AutoSwXp[4].X = (Int16) (_tileWidth / 2); + AutoSwXp[4].Y = (Int16) (3 * _tileHeight + _tileHeight / 2); // Outer Tiles - SE (bottom subtile region) // NW - q - AutoSeXp[1].X = (Int16) (Options.TileWidth * 2); - AutoSeXp[1].Y = (Int16) (3 * Options.TileHeight); + AutoSeXp[1].X = (Int16) (_tileWidth * 2); + AutoSeXp[1].Y = (Int16) (3 * _tileHeight); // NE - r - AutoSeXp[2].X = (Int16) (2 * Options.TileWidth + Options.TileWidth / 2); - AutoSeXp[2].Y = (Int16) (3 * Options.TileHeight); + AutoSeXp[2].X = (Int16) (2 * _tileWidth + _tileWidth / 2); + AutoSeXp[2].Y = (Int16) (3 * _tileHeight); // SW - s - AutoSeXp[3].X = (Int16) (Options.TileWidth * 2); - AutoSeXp[3].Y = (Int16) (3 * Options.TileHeight + Options.TileHeight / 2); + AutoSeXp[3].X = (Int16) (_tileWidth * 2); + AutoSeXp[3].Y = (Int16) (3 * _tileHeight + _tileHeight / 2); // SE - t - AutoSeXp[4].X = (Int16) (2 * Options.TileWidth + Options.TileWidth / 2); - AutoSeXp[4].Y = (Int16) (3 * Options.TileHeight + Options.TileHeight / 2); + AutoSeXp[4].X = (Int16) (2 * _tileWidth + _tileWidth / 2); + AutoSeXp[4].Y = (Int16) (3 * _tileHeight + _tileHeight / 2); // Center Tiles - C // NW - A - AutoCxp[1].X = (Int16) Options.TileWidth; - AutoCxp[1].Y = (Int16) (Options.TileHeight * 2); + AutoCxp[1].X = (Int16) _tileWidth; + AutoCxp[1].Y = (Int16) (_tileHeight * 2); // NE - B - AutoCxp[2].X = (Int16) (Options.TileWidth + Options.TileWidth / 2); - AutoCxp[2].Y = (Int16) (Options.TileHeight * 2); + AutoCxp[2].X = (Int16) (_tileWidth + _tileWidth / 2); + AutoCxp[2].Y = (Int16) (_tileHeight * 2); // SW - C - AutoCxp[3].X = (Int16) Options.TileWidth; - AutoCxp[3].Y = (Int16) (Options.TileHeight * 2 + Options.TileHeight / 2); + AutoCxp[3].X = (Int16) _tileWidth; + AutoCxp[3].Y = (Int16) (_tileHeight * 2 + _tileHeight / 2); // SE - D - AutoCxp[4].X = (Int16) (Options.TileWidth + Options.TileWidth / 2); - AutoCxp[4].Y = (Int16) (Options.TileHeight * 2 + Options.TileHeight / 2); + AutoCxp[4].X = (Int16) (_tileWidth + _tileWidth / 2); + AutoCxp[4].Y = (Int16) (_tileHeight * 2 + _tileHeight / 2); // Outer Tiles - N (North Horizontal region) // NW - E - AutoNxp[1].X = (Int16) Options.TileWidth; - AutoNxp[1].Y = (Int16) Options.TileHeight; + AutoNxp[1].X = (Int16) _tileWidth; + AutoNxp[1].Y = (Int16) _tileHeight; // NE - F - AutoNxp[2].X = (Int16) (Options.TileWidth + Options.TileWidth / 2); - AutoNxp[2].Y = (Int16) Options.TileHeight; + AutoNxp[2].X = (Int16) (_tileWidth + _tileWidth / 2); + AutoNxp[2].Y = (Int16) _tileHeight; // SW - G - AutoNxp[3].X = (Int16) Options.TileWidth; - AutoNxp[3].Y = (Int16) (Options.TileHeight + Options.TileHeight / 2); + AutoNxp[3].X = (Int16) _tileWidth; + AutoNxp[3].Y = (Int16) (_tileHeight + _tileHeight / 2); // SE - H - AutoNxp[4].X = (Int16) (Options.TileWidth + Options.TileWidth / 2); - AutoNxp[4].Y = (Int16) (Options.TileHeight + Options.TileHeight / 2); + AutoNxp[4].X = (Int16) (_tileWidth + _tileWidth / 2); + AutoNxp[4].Y = (Int16) (_tileHeight + _tileHeight / 2); // Outer Tiles - E (East Vertical region) // NW - I - AutoExp[1].X = (Int16) (Options.TileWidth * 2); - AutoExp[1].Y = (Int16) (Options.TileHeight * 2); + AutoExp[1].X = (Int16) (_tileWidth * 2); + AutoExp[1].Y = (Int16) (_tileHeight * 2); // NE - J - AutoExp[2].X = (Int16) (Options.TileWidth * 2 + Options.TileWidth / 2); - AutoExp[2].Y = (Int16) (Options.TileHeight * 2); + AutoExp[2].X = (Int16) (_tileWidth * 2 + _tileWidth / 2); + AutoExp[2].Y = (Int16) (_tileHeight * 2); // SW - K - AutoExp[3].X = (Int16) (Options.TileWidth * 2); - AutoExp[3].Y = (Int16) (Options.TileHeight * 2 + Options.TileHeight / 2); + AutoExp[3].X = (Int16) (_tileWidth * 2); + AutoExp[3].Y = (Int16) (_tileHeight * 2 + _tileHeight / 2); // SE - L - AutoExp[4].X = (Int16) (Options.TileWidth * 2 + Options.TileWidth / 2); - AutoExp[4].Y = (Int16) (Options.TileHeight * 2 + Options.TileHeight / 2); + AutoExp[4].X = (Int16) (_tileWidth * 2 + _tileWidth / 2); + AutoExp[4].Y = (Int16) (_tileHeight * 2 + _tileHeight / 2); // Outer Tiles - W (West Vertical region) // NW - M AutoWxp[1].X = 0; - AutoWxp[1].Y = (Int16) (Options.TileHeight * 2); + AutoWxp[1].Y = (Int16) (_tileHeight * 2); // NE - N - AutoWxp[2].X = (Int16) (Options.TileWidth / 2); - AutoWxp[2].Y = (Int16) (Options.TileHeight * 2); + AutoWxp[2].X = (Int16) (_tileWidth / 2); + AutoWxp[2].Y = (Int16) (_tileHeight * 2); // SW - O AutoWxp[3].X = 0; - AutoWxp[3].Y = (Int16) (Options.TileHeight * 2 + Options.TileHeight / 2); + AutoWxp[3].Y = (Int16) (_tileHeight * 2 + _tileHeight / 2); // SE - P - AutoWxp[4].X = (Int16) (Options.TileWidth / 2); - AutoWxp[4].Y = (Int16) (Options.TileHeight * 2 + Options.TileHeight / 2); + AutoWxp[4].X = (Int16) (_tileWidth / 2); + AutoWxp[4].Y = (Int16) (_tileHeight * 2 + _tileHeight / 2); // Outer Tiles - S (South Horizontal region) // NW - Q - AutoSxp[1].X = (Int16) Options.TileWidth; - AutoSxp[1].Y = (Int16) (Options.TileHeight * 3); + AutoSxp[1].X = (Int16) _tileWidth; + AutoSxp[1].Y = (Int16) (_tileHeight * 3); // NE - R - AutoSxp[2].X = (Int16) (Options.TileWidth + Options.TileWidth / 2); - AutoSxp[2].Y = (Int16) (Options.TileHeight * 3); + AutoSxp[2].X = (Int16) (_tileWidth + _tileWidth / 2); + AutoSxp[2].Y = (Int16) (_tileHeight * 3); // SW - S - AutoSxp[3].X = (Int16) Options.TileWidth; - AutoSxp[3].Y = (Int16) (Options.TileHeight * 3 + Options.TileHeight / 2); + AutoSxp[3].X = (Int16) _tileWidth; + AutoSxp[3].Y = (Int16) (_tileHeight * 3 + _tileHeight / 2); // SE - T - AutoSxp[4].X = (Int16) (Options.TileWidth + Options.TileWidth / 2); - AutoSxp[4].Y = (Int16) (Options.TileHeight * 3 + Options.TileHeight / 2); + AutoSxp[4].X = (Int16) (_tileWidth + _tileWidth / 2); + AutoSxp[4].Y = (Int16) (_tileHeight * 3 + _tileHeight / 2); } - private void CreateFields() + private Dictionary EnsureLayers() { - Layers = new Dictionary(); - foreach (var layerName in Options.Instance.MapOpts.Layers.All) + if (Layers is {} layers) + { + return layers; + } + + layers = []; + foreach (var layerName in _layersAll) { - var layer = new QuarterTileCls[Options.MapWidth, Options.MapHeight]; - for (var x = 0; x < Options.MapWidth; x++) + var layerQuarterTiles = new QuarterTileCls[_mapWidth, _mapHeight]; + for (var x = 0; x < _mapWidth; x++) { - for (var y = 0; y < Options.MapHeight; y++) + for (var y = 0; y < _mapHeight; y++) { - layer[x, y] = new QuarterTileCls() { QuarterTile = new PointStruct[5] }; + layerQuarterTiles[x, y] = new QuarterTileCls { QuarterTile = new PointStruct[5] }; } } - Layers.Add(layerName, layer); + layers.Add(layerName, layerQuarterTiles); } + + Layers = layers; + return layers; } public void InitAutotiles(MapBase[,] surroundingMaps) { - lock (mMyMap.MapLock) + var startInitAutoTiles = DateTime.UtcNow; + + if (!mMyMap.Lock.TryAcquireLock($"{nameof(MapAutotiles)}.{nameof(InitAutotiles)}({nameof(MapBase)}[,])", out var lockRef)) { - if (Layers == null) - { - CreateFields(); - } + throw new InvalidOperationException("Failed to acquire map instance lock from InitAutotiles()"); + } + + var endLock = DateTime.UtcNow; + DateTime endCreateFields; + TimeSpan elapsedCalculateAutotile = default; + TimeSpan elapsedCacheRenderState = default; + + using (lockRef) + { + EnsureLayers(); + endCreateFields = DateTime.UtcNow; - foreach (var layerName in Options.Instance.MapOpts.Layers.All) + foreach (var layerName in _layersAll) { - for (var x = 0; x < Options.MapWidth; x++) + if (!mMyMap.Layers.TryGetValue(layerName, out var layerTiles)) { - for (var y = 0; y < Options.MapHeight; y++) + continue; + } + + if (!TryGetAutoTilesForLayer(layerName, out var layerAutoTiles)) + { + continue; + } + + for (var x = 0; x < _mapWidth; x++) + { + for (var y = 0; y < _mapHeight; y++) { + if (!TryGetAutoTile( + layerAutoTiles, + x, + y, + out var autoTile + )) + { + continue; + } + + DateTime startCalculateAutotiles = DateTime.UtcNow; // calculate the subtile positions and place them - CalculateAutotile(x, y, layerName, surroundingMaps); + CalculateAutotile(x, y, layerName, surroundingMaps, layerTiles, autoTile); + elapsedCalculateAutotile += DateTime.UtcNow - startCalculateAutotiles; + DateTime startCacheRenderState = DateTime.UtcNow; // cache the rendering state of the tiles and set them - CacheRenderState(x, y, layerName); + CacheRenderState(x, y, layerName, autoTile); + elapsedCacheRenderState += DateTime.UtcNow - startCacheRenderState; } } } } + + var endInitAutotiles = DateTime.UtcNow; + + LegacyLogging.Logger?.Debug($""" + [{mMyMap.Id}][{mMyMap.Name}] Took {(endInitAutotiles - startInitAutoTiles).TotalMilliseconds}ms to run InitAutotiles() + - Lock took {(endLock - startInitAutoTiles).TotalMilliseconds}ms to acquire + - CreateFields took {(endCreateFields - endLock).TotalMilliseconds}ms to run + - All CalculateAutotile calls took {elapsedCalculateAutotile.TotalMilliseconds}ms + - All CacheRenderState calls took {elapsedCacheRenderState.TotalMilliseconds}ms + """); } public bool UpdateAutoTiles(int x, int y, MapBase[,] surroundingMaps) { var changed = false; - lock (mMyMap.MapLock) + + if (!mMyMap.Lock.TryAcquireLock($"{nameof(MapAutotiles)}.{nameof(UpdateAutoTiles)}(int, int, {nameof(MapBase)}[,])", out var lockRef)) { - foreach (var layer in Options.Instance.MapOpts.Layers.All) + throw new InvalidOperationException("Failed to acquire map instance lock from UpdateAutoTiles(int, int, MapBase[,])"); + } + + using (lockRef) + { + foreach (var layer in _layersAll) { + if (!mMyMap.Layers.TryGetValue(layer, out var layerTiles)) + { + continue; + } + + if (!TryGetAutoTilesForLayer(layer, out var layerAutoTiles)) + { + continue; + } + for (var x1 = x - 1; x1 < x + 2; x1++) { - if (x1 < 0 || x1 >= Options.MapWidth) + if (x1 < 0 || x1 >= _mapWidth) { continue; } for (var y1 = y - 1; y1 < y + 2; y1++) { - if (y1 < 0 || y1 >= Options.MapHeight) + if (!TryGetAutoTile( + layerAutoTiles, + x, + y, + out var autoTile + )) { continue; } - var oldautotile = Layers[layer][x1, y1].Copy(); + var oldAutoTile = autoTile.Copy(); // calculate the subtile positions and place them - CalculateAutotile(x1, y1, layer, surroundingMaps); + CalculateAutotile(x1, y1, layer, surroundingMaps, layerTiles, autoTile); // cache the rendering state of the tiles and set them - CacheRenderState(x1, y1, layer); + CacheRenderState(x1, y1, layer, autoTile); - if (!Layers[layer][x1, y1].Equals(oldautotile)) + if (!autoTile.Equals(oldAutoTile)) { changed = true; } @@ -454,27 +581,52 @@ public bool UpdateAutoTiles(int x, int y, MapBase[,] surroundingMaps) public void UpdateAutoTiles(int x, int y, string layerName, MapBase[,] surroundingMaps) { - lock (mMyMap.MapLock) + if (!mMyMap.Lock.TryAcquireLock($"{nameof(MapAutotiles)}.{nameof(UpdateAutoTiles)}(int, int, string, {nameof(MapBase)}[,])", out var lockRef)) { + throw new InvalidOperationException("Failed to acquire map instance lock from UpdateAutoTiles(int, int, string, MapBase[,])"); + } + + using (lockRef) + { + if (!mMyMap.Layers.TryGetValue(layerName, out var layerTiles)) + { + return; + } + + if (!TryGetAutoTilesForLayer(layerName, out var layerAutoTiles)) + { + return; + } + for (var x1 = x - 1; x1 < x + 2; x1++) { - if (x1 < 0 || x1 >= Options.MapWidth) + if (x1 < 0 || x1 >= _mapWidth) { continue; } for (var y1 = y - 1; y1 < y + 2; y1++) { - if (y1 < 0 || y1 >= Options.MapHeight) + if (y1 < 0 || y1 >= _mapHeight) + { + continue; + } + + if (!TryGetAutoTile( + layerAutoTiles, + x, + y, + out var autoTile + )) { continue; } // calculate the subtile positions and place them - CalculateAutotile(x1, y1, layerName, surroundingMaps); + CalculateAutotile(x1, y1, layerName, surroundingMaps, layerTiles, autoTile); // cache the rendering state of the tiles and set them - CacheRenderState(x1, y1, layerName); + CacheRenderState(x1, y1, layerName, autoTile); } } } @@ -482,54 +634,89 @@ public void UpdateAutoTiles(int x, int y, string layerName, MapBase[,] surroundi public void UpdateCliffAutotiles(MapBase curMap, string layerName) { - if (!curMap.Layers.ContainsKey(layerName)) + if (!curMap.Layers.TryGetValue(layerName, out var layerTiles)) + { + return; + } + + if (!TryGetAutoTilesForLayer(layerName, out var layerAutoTiles)) { return; } foreach (var map in curMap.GenerateAutotileGrid()) { - if (map != null) + if (map == null) + { + continue; + } + + for (var x = 0; x < _mapWidth; x++) { - for (var x1 = 0; x1 < Options.MapWidth; x1++) + for (var y = 0; y < _mapHeight; y++) { - for (var y1 = 0; y1 < Options.MapHeight; y1++) + if (map.Layers[layerName][x, y].Autotile != AUTOTILE_CLIFF) { - if (map.Layers[layerName][x1, y1].Autotile == AUTOTILE_CLIFF) - { - map.Autotiles.CalculateAutotile(x1, y1, layerName, map.GenerateAutotileGrid()); - map.Autotiles.CacheRenderState(x1, y1, layerName); - } + continue; } + + if (!TryGetAutoTile( + layerAutoTiles, + x, + y, + out var autoTile + )) + { + continue; + } + + map.Autotiles.CalculateAutotile(x, y, layerName, map.GenerateAutotileGrid(), layerTiles, autoTile); + map.Autotiles.CacheRenderState(x, y, layerName, autoTile); } } } } - public bool UpdateAutoTile(int x, int y, string layerName, MapBase[,] surroundingMaps) + public bool UpdateAutoTile(int x, int y, string layerName, MapBase[,] surroundingMaps, Tile[,] layerTiles) { - if (x < 0 || x >= Options.MapWidth || y < 0 || y >= Options.MapHeight) + if (x < 0 || x >= _mapWidth || y < 0 || y >= _mapHeight) { return false; } - var oldautotile = Layers[layerName][x, y].Copy(); - lock (mMyMap.MapLock) + if (!TryGetAutoTileForLayer( + layerName, + x, + y, + out var autoTile + )) + { + return false; + } + + var oldAutoTile = autoTile.Copy(); + + if (!mMyMap.Lock.TryAcquireLock($"{nameof(MapAutotiles)}.{nameof(UpdateAutoTile)}(int, int, string, {nameof(MapBase)}[,])", out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock from UpdateAutoTile(int, int, string, MapBase[,])"); + } + + using (lockRef) { // calculate the subtile positions and place them - CalculateAutotile(x, y, layerName, surroundingMaps); + CalculateAutotile(x, y, layerName, surroundingMaps, layerTiles, autoTile); // cache the rendering state of the tiles and set them - CacheRenderState(x, y, layerName); + CacheRenderState(x, y, layerName, autoTile); } - return !Layers[layerName][x, y].Equals(oldautotile); + return !autoTile.Equals(oldAutoTile); } - public void CacheRenderState(int x, int y, string layerName) + public void CacheRenderState(int x, int y, string layerName, QuarterTileCls? autoTile = null) { // exit out early - if (x < 0 || x > Options.MapWidth || y < 0 || y > Options.MapHeight) + if (x < 0 || x > _mapWidth || y < 0 || y > _mapHeight) { return; } @@ -548,46 +735,46 @@ public void CacheRenderState(int x, int y, string layerName) return; } - if (!mMyMap.Layers.ContainsKey(layerName)) + if (!mMyMap.Layers.TryGetValue(layerName, out var layerTiles)) { return; } - var layer = mMyMap.Layers[layerName]; - if (mMyMap.Layers[layerName] == null) + if (autoTile == null && !TryGetAutoTileForLayer( + layerName, + x, + y, + out autoTile + )) { - LegacyLogging.Logger?.Error($"{nameof(layer)}=null"); - return; } - var tile = layer[x, y]; - var autotile = Layers[layerName][x, y]; + var tile = layerTiles[x, y]; // check if it needs to be rendered as an autotile - if (tile.Autotile == AUTOTILE_NONE || tile.Autotile == AUTOTILE_FAKE) + if (tile.Autotile is AUTOTILE_NONE or AUTOTILE_FAKE) { // default to... default - autotile.RenderState = RENDER_STATE_NORMAL; + autoTile.RenderState = RENDER_STATE_NORMAL; //Autotile[layerName][x, y].QuarterTile = null; } else { - autotile.RenderState = RENDER_STATE_AUTOTILE; + autoTile.RenderState = RENDER_STATE_AUTOTILE; // cache tileset positioning - int quarterNum; - for (quarterNum = 1; quarterNum < 5; quarterNum++) + for (var quarterNum = 1; quarterNum < 5; quarterNum++) { - autotile.QuarterTile[quarterNum].X = (short) (tile.X * Options.TileWidth + autotile.QuarterTile[quarterNum].X); - - autotile.QuarterTile[quarterNum].Y = (short) (tile.Y * Options.TileHeight + autotile.QuarterTile[quarterNum].Y); + var quarterTile = autoTile.QuarterTile[quarterNum]; + autoTile.QuarterTile[quarterNum].X = (short) (tile.X * _tileWidth + quarterTile.X); + autoTile.QuarterTile[quarterNum].Y = (short) (tile.Y * _tileHeight + quarterTile.Y); } } } - public void CalculateAutotile(int x, int y, string layerName, MapBase[,] surroundingMaps) + public void CalculateAutotile(int x, int y, string layerName, MapBase[,] surroundingMaps, Tile[,] layerTiles, QuarterTileCls? autoTile) { // Right, so we//ve split the tile block in to an easy to remember // collection of letters. We now need to do the calculations to find @@ -598,35 +785,7 @@ public void CalculateAutotile(int x, int y, string layerName, MapBase[,] surroun // Then we calculate exactly which situation has arisen. // The situations are "inner", "outer", "horizontal", "vertical" and "fill". - // Exit out if we don//t have an auatotile - if (mMyMap == null) - { - LegacyLogging.Logger?.Error($"{nameof(mMyMap)}=null"); - - return; - } - - if (mMyMap.Layers == null) - { - LegacyLogging.Logger?.Error($"{nameof(mMyMap.Layers)}=null"); - - return; - } - - if (!mMyMap.Layers.ContainsKey(layerName)) - { - return; - } - - var layer = mMyMap.Layers[layerName]; - if (mMyMap.Layers[layerName] == null) - { - LegacyLogging.Logger?.Error($"{nameof(layer)}=null"); - - return; - } - - var tile = layer[x, y]; + var tile = layerTiles[x, y]; if (tile.Autotile == 0) { return; @@ -640,16 +799,16 @@ public void CalculateAutotile(int x, int y, string layerName, MapBase[,] surroun case AUTOTILE_NORMAL: case AUTOTILE_ANIM: // North West Quarter - CalculateNW_Normal(layerName, x, y, surroundingMaps); + CalculateNW_Normal(layerName, x, y, surroundingMaps, layerTiles, autoTile); // North East Quarter - CalculateNE_Normal(layerName, x, y, surroundingMaps); + CalculateNE_Normal(layerName, x, y, surroundingMaps, layerTiles, autoTile); // South West Quarter - CalculateSW_Normal(layerName, x, y, surroundingMaps); + CalculateSW_Normal(layerName, x, y, surroundingMaps, layerTiles, autoTile); // South East Quarter - CalculateSE_Normal(layerName, x, y, surroundingMaps); + CalculateSE_Normal(layerName, x, y, surroundingMaps, layerTiles, autoTile); break; @@ -657,38 +816,43 @@ public void CalculateAutotile(int x, int y, string layerName, MapBase[,] surroun case AUTOTILE_CLIFF: var cliffStart = 0; - var cliffHeight = CalculateCliffHeight(layerName, x, y, surroundingMaps, out cliffStart); + var cliffHeight = CalculateCliffHeight(layerName, x, y, surroundingMaps, layerTiles, out cliffStart); //Calculate cliffStart and cliffHeight of immediately adjacent cliffs var leftCliffStart = 0; var leftCliffHeight = 0; - if (CheckTileMatch(layerName, x, y, x - 1, y, surroundingMaps)) + if (CheckTileMatch(layerName, x, y, x - 1, y, surroundingMaps, layerTiles)) { - leftCliffHeight = CalculateCliffHeight(layerName, x - 1, y, surroundingMaps, out leftCliffStart); + leftCliffHeight = CalculateCliffHeight(layerName, x - 1, y, surroundingMaps, layerTiles, out leftCliffStart); } var rightCliffStart = 0; var rightCliffHeight = 0; - if (CheckTileMatch(layerName, x, y, x + 1, y, surroundingMaps)) + if (CheckTileMatch(layerName, x, y, x + 1, y, surroundingMaps, layerTiles)) { rightCliffHeight = CalculateCliffHeight( - layerName, x + 1, y, surroundingMaps, out rightCliffStart + layerName, + x + 1, + y, + surroundingMaps, + layerTiles, + out rightCliffStart ); } - var assumeInteriorEast = CheckTileMatch(layerName, x, y, x + 1, cliffStart, surroundingMaps) && - !CheckTileMatch(layerName, x, y, x + 1, cliffStart - 1, surroundingMaps); + var assumeInteriorEast = CheckTileMatch(layerName, x, y, x + 1, cliffStart, surroundingMaps, layerTiles) && + !CheckTileMatch(layerName, x, y, x + 1, cliffStart - 1, surroundingMaps, layerTiles); - var assumeInteriorWest = CheckTileMatch(layerName, x, y, x - 1, cliffStart, surroundingMaps) && - !CheckTileMatch(layerName, x, y, x - 1, cliffStart - 1, surroundingMaps); + var assumeInteriorWest = CheckTileMatch(layerName, x, y, x - 1, cliffStart, surroundingMaps, layerTiles) && + !CheckTileMatch(layerName, x, y, x - 1, cliffStart - 1, surroundingMaps, layerTiles); var rangeHeight = cliffHeight; var x1 = x - 1; - while (x1 > -Options.MapWidth && CheckTileMatch(layerName, x, y, x1, cliffStart, surroundingMaps)) + while (x1 > -_mapWidth && CheckTileMatch(layerName, x, y, x1, cliffStart, surroundingMaps, layerTiles)) { var adjStart = 0; - var height = CalculateCliffHeight(layerName, x1, cliffStart, surroundingMaps, out adjStart); + var height = CalculateCliffHeight(layerName, x1, cliffStart, surroundingMaps, layerTiles, out adjStart); if (adjStart == cliffStart) { if (height > rangeHeight) @@ -705,10 +869,10 @@ public void CalculateAutotile(int x, int y, string layerName, MapBase[,] surroun } x1 = x + 1; - while (x1 < Options.MapWidth * 2 && CheckTileMatch(layerName, x, y, x1, cliffStart, surroundingMaps)) + while (x1 < _mapWidth * 2 && CheckTileMatch(layerName, x, y, x1, cliffStart, surroundingMaps, layerTiles)) { var adjStart = 0; - var height = CalculateCliffHeight(layerName, x1, cliffStart, surroundingMaps, out adjStart); + var height = CalculateCliffHeight(layerName, x1, cliffStart, surroundingMaps, layerTiles, out adjStart); if (adjStart == cliffStart) { if (height > rangeHeight) @@ -730,7 +894,7 @@ public void CalculateAutotile(int x, int y, string layerName, MapBase[,] surroun //if (assumeInteriorEast || assumeInteriorWest) //{ // var x1 = x - 1; - // while (x1 > -Options.MapWidth && CheckTileMatch(layerName, x, y, x1, cliffStart, surroundingMaps)) + // while (x1 > -_mapWidth && CheckTileMatch(layerName, x, y, x1, cliffStart, surroundingMaps)) // { // var adjStart = 0; // var height = CalculateCliffHeight(layerName, x1, cliffStart, surroundingMaps, out adjStart); @@ -742,7 +906,7 @@ public void CalculateAutotile(int x, int y, string layerName, MapBase[,] surroun // if (lowestCliffBottom <= cliffStart + cliffHeight) // { // x1 = x + 1; - // while (x1 < Options.MapWidth * 2 && CheckTileMatch(layerName, x, y, x1, cliffStart, surroundingMaps)) + // while (x1 < _mapWidth * 2 && CheckTileMatch(layerName, x, y, x1, cliffStart, surroundingMaps)) // { // var adjStart = 0; // var height = CalculateCliffHeight(layerName, x1, cliffStart, surroundingMaps, out adjStart); @@ -768,26 +932,64 @@ public void CalculateAutotile(int x, int y, string layerName, MapBase[,] surroun // North West Quarter CalculateNW_Cliff( - layerName, x, y, surroundingMaps, cliffStart, cliffHeight, leftCliffStart, leftCliffHeight, - assumeInteriorWest + layerName, + x, + y, + surroundingMaps, + layerTiles, + cliffStart, + cliffHeight, + leftCliffStart, + leftCliffHeight, + assumeInteriorWest, + autoTile ); // North East Quarter CalculateNE_Cliff( - layerName, x, y, surroundingMaps, cliffStart, cliffHeight, rightCliffStart, rightCliffHeight, - assumeInteriorEast + layerName, + x, + y, + surroundingMaps, + layerTiles, + cliffStart, + cliffHeight, + rightCliffStart, + rightCliffHeight, + assumeInteriorEast, + autoTile ); // South West Quarter CalculateSW_Cliff( - layerName, x, y, surroundingMaps, cliffStart, cliffHeight, leftCliffStart, leftCliffHeight, - assumeInteriorWest, drawBottom + layerName, + x, + y, + surroundingMaps, + layerTiles, + cliffStart, + cliffHeight, + leftCliffStart, + leftCliffHeight, + assumeInteriorWest, + drawBottom, + autoTile ); // South East Quarter CalculateSE_Cliff( - layerName, x, y, surroundingMaps, cliffStart, cliffHeight, rightCliffStart, rightCliffHeight, - assumeInteriorEast, drawBottom + layerName, + x, + y, + surroundingMaps, + layerTiles, + cliffStart, + cliffHeight, + rightCliffStart, + rightCliffHeight, + assumeInteriorEast, + drawBottom, + autoTile ); break; @@ -795,16 +997,16 @@ public void CalculateAutotile(int x, int y, string layerName, MapBase[,] surroun // Waterfalls case AUTOTILE_WATERFALL: // North West Quarter - CalculateNW_Waterfall(layerName, x, y, surroundingMaps); + CalculateNW_Waterfall(layerName, x, y, surroundingMaps, layerTiles, autoTile); // North East Quarter - CalculateNE_Waterfall(layerName, x, y, surroundingMaps); + CalculateNE_Waterfall(layerName, x, y, surroundingMaps, layerTiles, autoTile); // South West Quarter - CalculateSW_Waterfall(layerName, x, y, surroundingMaps); + CalculateSW_Waterfall(layerName, x, y, surroundingMaps, layerTiles, autoTile); // South East Quarter - CalculateSE_Waterfall(layerName, x, y, surroundingMaps); + CalculateSE_Waterfall(layerName, x, y, surroundingMaps, layerTiles, autoTile); break; @@ -812,41 +1014,72 @@ public void CalculateAutotile(int x, int y, string layerName, MapBase[,] surroun case AUTOTILE_XP: case AUTOTILE_ANIM_XP: // North West Quarter - CalculateNW_XP(layerName, x, y, surroundingMaps); + CalculateNW_XP(layerName, x, y, surroundingMaps, layerTiles, autoTile); // North East Quarter - CalculateNE_XP(layerName, x, y, surroundingMaps); + CalculateNE_XP(layerName, x, y, surroundingMaps, layerTiles, autoTile); // South West Quarter - CalculateSW_XP(layerName, x, y, surroundingMaps); + CalculateSW_XP(layerName, x, y, surroundingMaps, layerTiles, autoTile); // South East Quarter - CalculateSE_XP(layerName, x, y, surroundingMaps); + CalculateSE_XP(layerName, x, y, surroundingMaps, layerTiles, autoTile); break; } } // Normal autotiling - public void CalculateNW_Normal(string layerName, int x, int y, MapBase[,] surroundingMaps) + public void CalculateNW_Normal( + string layerName, + int x, + int y, + MapBase[,] surroundingMaps, + Tile[,] layerTiles, + QuarterTileCls? autoTile + ) { var tmpTile = new bool[4]; byte situation = 1; // North West - if (CheckTileMatch(layerName, x, y, x - 1, y - 1, surroundingMaps)) + if (CheckTileMatch( + layerName, + x, + y, + x - 1, + y - 1, + surroundingMaps, + layerTiles + )) { tmpTile[1] = true; } // North - if (CheckTileMatch(layerName, x, y, x, y - 1, surroundingMaps)) + if (CheckTileMatch( + layerName, + x, + y, + x, + y - 1, + surroundingMaps, + layerTiles + )) { tmpTile[2] = true; } // West - if (CheckTileMatch(layerName, x, y, x - 1, y, surroundingMaps)) + if (CheckTileMatch( + layerName, + x, + y, + x - 1, + y, + surroundingMaps, + layerTiles + )) { tmpTile[3] = true; } @@ -885,47 +1118,113 @@ public void CalculateNW_Normal(string layerName, int x, int y, MapBase[,] surrou switch (situation) { case AUTO_TILE_INNER: - PlaceAutotile(layerName, x, y, 1, "e"); + PlaceAutotile( + layerName, + x, + y, + 1, + 'e', + autoTile + ); break; case AUTO_TILE_OUTER: - PlaceAutotile(layerName, x, y, 1, "a"); + PlaceAutotile( + layerName, + x, + y, + 1, + 'a', + autoTile + ); break; case AUTO_TILE_HORIZONTAL: - PlaceAutotile(layerName, x, y, 1, "i"); + PlaceAutotile( + layerName, + x, + y, + 1, + 'i', + autoTile + ); break; case AUTO_TILE_VERTICAL: - PlaceAutotile(layerName, x, y, 1, "m"); + PlaceAutotile( + layerName, + x, + y, + 1, + 'm', + autoTile + ); break; case AUTO_TILE_FILL: - PlaceAutotile(layerName, x, y, 1, "q"); + PlaceAutotile( + layerName, + x, + y, + 1, + 'q', + autoTile + ); break; } } - public void CalculateNE_Normal(string layerName, int x, int y, MapBase[,] surroundingMaps) + public void CalculateNE_Normal( + string layerName, + int x, + int y, + MapBase[,] surroundingMaps, + Tile[,] layerTiles, + QuarterTileCls? autoTile + ) { var tmpTile = new bool[4]; byte situation = 1; // North - if (CheckTileMatch(layerName, x, y, x, y - 1, surroundingMaps)) + if (CheckTileMatch( + layerName, + x, + y, + x, + y - 1, + surroundingMaps, + layerTiles + )) { tmpTile[1] = true; } // North East - if (CheckTileMatch(layerName, x, y, x + 1, y - 1, surroundingMaps)) + if (CheckTileMatch( + layerName, + x, + y, + x + 1, + y - 1, + surroundingMaps, + layerTiles + )) { tmpTile[2] = true; } // East - if (CheckTileMatch(layerName, x, y, x + 1, y, surroundingMaps)) + if (CheckTileMatch( + layerName, + x, + y, + x + 1, + y, + surroundingMaps, + layerTiles + )) { tmpTile[3] = true; } @@ -964,47 +1263,113 @@ public void CalculateNE_Normal(string layerName, int x, int y, MapBase[,] surrou switch (situation) { case AUTO_TILE_INNER: - PlaceAutotile(layerName, x, y, 2, "j"); + PlaceAutotile( + layerName, + x, + y, + 2, + 'j', + autoTile + ); break; case AUTO_TILE_OUTER: - PlaceAutotile(layerName, x, y, 2, "b"); + PlaceAutotile( + layerName, + x, + y, + 2, + 'b', + autoTile + ); break; case AUTO_TILE_HORIZONTAL: - PlaceAutotile(layerName, x, y, 2, "f"); + PlaceAutotile( + layerName, + x, + y, + 2, + 'f', + autoTile + ); break; case AUTO_TILE_VERTICAL: - PlaceAutotile(layerName, x, y, 2, "r"); + PlaceAutotile( + layerName, + x, + y, + 2, + 'r', + autoTile + ); break; case AUTO_TILE_FILL: - PlaceAutotile(layerName, x, y, 2, "n"); + PlaceAutotile( + layerName, + x, + y, + 2, + 'n', + autoTile + ); break; } } - public void CalculateSW_Normal(string layerName, int x, int y, MapBase[,] surroundingMaps) + public void CalculateSW_Normal( + string layerName, + int x, + int y, + MapBase[,] surroundingMaps, + Tile[,] layerTiles, + QuarterTileCls? autoTile + ) { var tmpTile = new bool[4]; byte situation = 1; // West - if (CheckTileMatch(layerName, x, y, x - 1, y, surroundingMaps)) + if (CheckTileMatch( + layerName, + x, + y, + x - 1, + y, + surroundingMaps, + layerTiles + )) { tmpTile[1] = true; } // South West - if (CheckTileMatch(layerName, x, y, x - 1, y + 1, surroundingMaps)) + if (CheckTileMatch( + layerName, + x, + y, + x - 1, + y + 1, + surroundingMaps, + layerTiles + )) { tmpTile[2] = true; } // South - if (CheckTileMatch(layerName, x, y, x, y + 1, surroundingMaps)) + if (CheckTileMatch( + layerName, + x, + y, + x, + y + 1, + surroundingMaps, + layerTiles + )) { tmpTile[3] = true; } @@ -1043,47 +1408,113 @@ public void CalculateSW_Normal(string layerName, int x, int y, MapBase[,] surrou switch (situation) { case AUTO_TILE_INNER: - PlaceAutotile(layerName, x, y, 3, "o"); + PlaceAutotile( + layerName, + x, + y, + 3, + 'o', + autoTile + ); break; case AUTO_TILE_OUTER: - PlaceAutotile(layerName, x, y, 3, "c"); + PlaceAutotile( + layerName, + x, + y, + 3, + 'c', + autoTile + ); break; case AUTO_TILE_HORIZONTAL: - PlaceAutotile(layerName, x, y, 3, "s"); + PlaceAutotile( + layerName, + x, + y, + 3, + 's', + autoTile + ); break; case AUTO_TILE_VERTICAL: - PlaceAutotile(layerName, x, y, 3, "g"); + PlaceAutotile( + layerName, + x, + y, + 3, + 'g', + autoTile + ); break; case AUTO_TILE_FILL: - PlaceAutotile(layerName, x, y, 3, "k"); + PlaceAutotile( + layerName, + x, + y, + 3, + 'k', + autoTile + ); break; } } - public void CalculateSE_Normal(string layerName, int x, int y, MapBase[,] surroundingMaps) + public void CalculateSE_Normal( + string layerName, + int x, + int y, + MapBase[,] surroundingMaps, + Tile[,] layerTiles, + QuarterTileCls? autoTile + ) { var tmpTile = new bool[4]; byte situation = 1; // South - if (CheckTileMatch(layerName, x, y, x, y + 1, surroundingMaps)) + if (CheckTileMatch( + layerName, + x, + y, + x, + y + 1, + surroundingMaps, + layerTiles + )) { tmpTile[1] = true; } // South East - if (CheckTileMatch(layerName, x, y, x + 1, y + 1, surroundingMaps)) + if (CheckTileMatch( + layerName, + x, + y, + x + 1, + y + 1, + surroundingMaps, + layerTiles + )) { tmpTile[2] = true; } // East - if (CheckTileMatch(layerName, x, y, x + 1, y, surroundingMaps)) + if (CheckTileMatch( + layerName, + x, + y, + x + 1, + y, + surroundingMaps, + layerTiles + )) { tmpTile[3] = true; } @@ -1122,94 +1553,245 @@ public void CalculateSE_Normal(string layerName, int x, int y, MapBase[,] surrou switch (situation) { case AUTO_TILE_INNER: - PlaceAutotile(layerName, x, y, 4, "t"); + PlaceAutotile( + layerName, + x, + y, + 4, + 't', + autoTile + ); break; case AUTO_TILE_OUTER: - PlaceAutotile(layerName, x, y, 4, "d"); + PlaceAutotile( + layerName, + x, + y, + 4, + 'd', + autoTile + ); break; case AUTO_TILE_HORIZONTAL: - PlaceAutotile(layerName, x, y, 4, "p"); + PlaceAutotile( + layerName, + x, + y, + 4, + 'p', + autoTile + ); break; case AUTO_TILE_VERTICAL: - PlaceAutotile(layerName, x, y, 4, "l"); + PlaceAutotile( + layerName, + x, + y, + 4, + 'l', + autoTile + ); break; case AUTO_TILE_FILL: - PlaceAutotile(layerName, x, y, 4, "h"); + PlaceAutotile( + layerName, + x, + y, + 4, + 'h', + autoTile + ); break; } } // Waterfall autotiling - public void CalculateNW_Waterfall(string layerName, int x, int y, MapBase[,] surroundingMaps) + public void CalculateNW_Waterfall( + string layerName, + int x, + int y, + MapBase[,] surroundingMaps, + Tile[,] layerTiles, + QuarterTileCls? autoTile + ) { - var tmpTile = CheckTileMatch(layerName, x, y, x - 1, y, surroundingMaps); + var tmpTile = CheckTileMatch( + layerName, + x, + y, + x - 1, + y, + surroundingMaps, + layerTiles + ); // Actually place the subtile if (tmpTile) { // Extended - PlaceAutotile(layerName, x, y, 1, "i"); + PlaceAutotile( + layerName, + x, + y, + 1, + 'i', + autoTile + ); } else { // Edge - PlaceAutotile(layerName, x, y, 1, "e"); + PlaceAutotile( + layerName, + x, + y, + 1, + 'e', + autoTile + ); } } - public void CalculateNE_Waterfall(string layerName, int x, int y, MapBase[,] surroundingMaps) + public void CalculateNE_Waterfall( + string layerName, + int x, + int y, + MapBase[,] surroundingMaps, + Tile[,] layerTiles, + QuarterTileCls? autoTile + ) { - var tmpTile = CheckTileMatch(layerName, x, y, x + 1, y, surroundingMaps); + var tmpTile = CheckTileMatch( + layerName, + x, + y, + x + 1, + y, + surroundingMaps, + layerTiles + ); // Actually place the subtile if (tmpTile) { // Extended - PlaceAutotile(layerName, x, y, 2, "f"); + PlaceAutotile( + layerName, + x, + y, + 2, + 'f', + autoTile + ); } else { // Edge - PlaceAutotile(layerName, x, y, 2, "j"); + PlaceAutotile( + layerName, + x, + y, + 2, + 'j', + autoTile + ); } } - public void CalculateSW_Waterfall(string layerName, int x, int y, MapBase[,] surroundingMaps) + public void CalculateSW_Waterfall( + string layerName, + int x, + int y, + MapBase[,] surroundingMaps, + Tile[,] layerTiles, + QuarterTileCls? autoTile + ) { - var tmpTile = CheckTileMatch(layerName, x, y, x - 1, y, surroundingMaps); + var tmpTile = CheckTileMatch( + layerName, + x, + y, + x - 1, + y, + surroundingMaps, + layerTiles + ); // Actually place the subtile if (tmpTile) { // Extended - PlaceAutotile(layerName, x, y, 3, "k"); + PlaceAutotile( + layerName, + x, + y, + 3, + 'k', + autoTile + ); } else { // Edge - PlaceAutotile(layerName, x, y, 3, "g"); + PlaceAutotile( + layerName, + x, + y, + 3, + 'g', + autoTile + ); } } - public void CalculateSE_Waterfall(string layerName, int x, int y, MapBase[,] surroundingMaps) + public void CalculateSE_Waterfall( + string layerName, + int x, + int y, + MapBase[,] surroundingMaps, + Tile[,] layerTiles, + QuarterTileCls? autoTile + ) { - var tmpTile = CheckTileMatch(layerName, x, y, x + 1, y, surroundingMaps); + var tmpTile = CheckTileMatch( + layerName, + x, + y, + x + 1, + y, + surroundingMaps, + layerTiles + ); // Actually place the subtile if (tmpTile) { // Extended - PlaceAutotile(layerName, x, y, 4, "h"); + PlaceAutotile( + layerName, + x, + y, + 4, + 'h', + autoTile + ); } else { // Edge - PlaceAutotile(layerName, x, y, 4, "l"); + PlaceAutotile( + layerName, + x, + y, + 4, + 'l', + autoTile + ); } } @@ -1219,24 +1801,26 @@ public void CalculateNW_Cliff( int x, int y, MapBase[,] surroundingMaps, + Tile[,] layerTiles, int cliffStart, int cliffHeight, int adjacentStart, int adjacentHeight, - bool assumeInterior + bool assumeInterior, + QuarterTileCls? autoTile ) { var tmpTile = new bool[5]; byte situation = 1; // North West - if (CheckTileMatch(layerName, x, y, x - 1, y - 1, surroundingMaps)) + if (CheckTileMatch(layerName, x, y, x - 1, y - 1, surroundingMaps, layerTiles)) { tmpTile[1] = true; } // North - if (CheckTileMatch(layerName, x, y, x, y - 1, surroundingMaps)) + if (CheckTileMatch(layerName, x, y, x, y - 1, surroundingMaps, layerTiles)) { tmpTile[2] = true; } @@ -1295,19 +1879,19 @@ bool assumeInterior switch (situation) { case AUTO_TILE_INNER: - PlaceAutotile(layerName, x, y, 1, "e"); + PlaceAutotile(layerName, x, y, 1, 'e', autoTile); break; case AUTO_TILE_HORIZONTAL: - PlaceAutotile(layerName, x, y, 1, "i"); + PlaceAutotile(layerName, x, y, 1, 'i', autoTile); break; case AUTO_TILE_VERTICAL: - PlaceAutotile(layerName, x, y, 1, "m"); + PlaceAutotile(layerName, x, y, 1, 'm', autoTile); break; case AUTO_TILE_FILL: - PlaceAutotile(layerName, x, y, 1, "q"); + PlaceAutotile(layerName, x, y, 1, 'q', autoTile); break; } @@ -1318,24 +1902,26 @@ public void CalculateNE_Cliff( int x, int y, MapBase[,] surroundingMaps, + Tile[,] layerTiles, int cliffStart, int cliffHeight, int adjacentStart, int adjacentHeight, - bool assumeInterior + bool assumeInterior, + QuarterTileCls? autoTile ) { var tmpTile = new bool[5]; byte situation = 1; // North - if (CheckTileMatch(layerName, x, y, x, y - 1, surroundingMaps)) + if (CheckTileMatch(layerName, x, y, x, y - 1, surroundingMaps, layerTiles)) { tmpTile[1] = true; } // North East - if (CheckTileMatch(layerName, x, y, x + 1, y + 1, surroundingMaps)) + if (CheckTileMatch(layerName, x, y, x + 1, y + 1, surroundingMaps, layerTiles)) { tmpTile[2] = true; } @@ -1393,19 +1979,19 @@ bool assumeInterior switch (situation) { case AUTO_TILE_INNER: - PlaceAutotile(layerName, x, y, 2, "j"); + PlaceAutotile(layerName, x, y, 2, 'j', autoTile); break; case AUTO_TILE_HORIZONTAL: - PlaceAutotile(layerName, x, y, 2, "f"); + PlaceAutotile(layerName, x, y, 2, 'f', autoTile); break; case AUTO_TILE_VERTICAL: - PlaceAutotile(layerName, x, y, 2, "r"); + PlaceAutotile(layerName, x, y, 2, 'r', autoTile); break; case AUTO_TILE_FILL: - PlaceAutotile(layerName, x, y, 2, "n"); + PlaceAutotile(layerName, x, y, 2, 'n', autoTile); break; } @@ -1416,12 +2002,14 @@ public void CalculateSW_Cliff( int x, int y, MapBase[,] surroundingMaps, + Tile[,] layerTiles, int cliffStart, int cliffHeight, int adjacentStart, int adjacentHeight, bool assumeInterior, - bool drawBottom + bool drawBottom, + QuarterTileCls? autoTile ) { var tmpTile = new bool[5]; @@ -1442,20 +2030,20 @@ bool drawBottom } // South West - if (CheckTileMatch(layerName, x, y, x - 1, y + 1, surroundingMaps)) + if (CheckTileMatch(layerName, x, y, x - 1, y + 1, surroundingMaps, layerTiles)) { tmpTile[2] = true; } // South - if (CheckTileMatch(layerName, x, y, x, y + 1, surroundingMaps) || !drawBottom) + if (CheckTileMatch(layerName, x, y, x, y + 1, surroundingMaps, layerTiles) || !drawBottom) { tmpTile[3] = true; } //Center - if (CheckTileMatch(layerName, x, y, x - 1, y, surroundingMaps) && - !CheckTileMatch(layerName, x, y, x, y - 1, surroundingMaps)) + if (CheckTileMatch(layerName, x, y, x - 1, y, surroundingMaps, layerTiles) && + !CheckTileMatch(layerName, x, y, x, y - 1, surroundingMaps, layerTiles)) { tmpTile[4] = true; } @@ -1498,19 +2086,19 @@ bool drawBottom switch (situation) { case AUTO_TILE_INNER: - PlaceAutotile(layerName, x, y, 3, "o"); + PlaceAutotile(layerName, x, y, 3, 'o', autoTile); break; case AUTO_TILE_HORIZONTAL: - PlaceAutotile(layerName, x, y, 3, "s"); + PlaceAutotile(layerName, x, y, 3, 's', autoTile); break; case AUTO_TILE_VERTICAL: - PlaceAutotile(layerName, x, y, 3, "g"); + PlaceAutotile(layerName, x, y, 3, 'g', autoTile); break; case AUTO_TILE_FILL: - PlaceAutotile(layerName, x, y, 3, "k"); + PlaceAutotile(layerName, x, y, 3, 'k', autoTile); break; } @@ -1521,25 +2109,27 @@ public void CalculateSE_Cliff( int x, int y, MapBase[,] surroundingMaps, + Tile[,] layerTiles, int cliffStart, int cliffHeight, int adjacentStart, int adjacentHeight, bool assumeInterior, - bool drawBottom + bool drawBottom, + QuarterTileCls? autoTile ) { var tmpTile = new bool[5]; byte situation = 1; // South - if (CheckTileMatch(layerName, x, y, x, y + 1, surroundingMaps) || !drawBottom) + if (CheckTileMatch(layerName, x, y, x, y + 1, surroundingMaps, layerTiles) || !drawBottom) { tmpTile[1] = true; } // South East - if (CheckTileMatch(layerName, x, y, x + 1, y + 1, surroundingMaps)) + if (CheckTileMatch(layerName, x, y, x + 1, y + 1, surroundingMaps, layerTiles)) { tmpTile[2] = true; } @@ -1559,8 +2149,8 @@ bool drawBottom } //Center - if (CheckTileMatch(layerName, x, y, x + 1, y, surroundingMaps) && - !CheckTileMatch(layerName, x, y, x, y - 1, surroundingMaps)) + if (CheckTileMatch(layerName, x, y, x + 1, y, surroundingMaps, layerTiles) && + !CheckTileMatch(layerName, x, y, x, y - 1, surroundingMaps, layerTiles)) { tmpTile[4] = true; } @@ -1603,26 +2193,33 @@ bool drawBottom switch (situation) { case AUTO_TILE_INNER: - PlaceAutotile(layerName, x, y, 4, "t"); + PlaceAutotile(layerName, x, y, 4, 't', autoTile); break; case AUTO_TILE_HORIZONTAL: - PlaceAutotile(layerName, x, y, 4, "p"); + PlaceAutotile(layerName, x, y, 4, 'p', autoTile); break; case AUTO_TILE_VERTICAL: - PlaceAutotile(layerName, x, y, 4, "l"); + PlaceAutotile(layerName, x, y, 4, 'l', autoTile); break; case AUTO_TILE_FILL: - PlaceAutotile(layerName, x, y, 4, "h"); + PlaceAutotile(layerName, x, y, 4, 'h', autoTile); break; } } // Normal autotiling - public void CalculateNW_XP(string layerName, int x, int y, MapBase[,] surroundingMaps) + public void CalculateNW_XP( + string layerName, + int x, + int y, + MapBase[,] surroundingMaps, + Tile[,] layerTiles, + QuarterTileCls? autoTile + ) { var tmpTile = new bool[4, 4]; byte situation = 1; @@ -1632,7 +2229,15 @@ public void CalculateNW_XP(string layerName, int x, int y, MapBase[,] surroundin { for (var i = -1; i < 2; i++) { - if (CheckTileMatch(layerName, x, y, x + i, y + j, surroundingMaps)) + if (CheckTileMatch( + layerName, + x, + y, + x + i, + y + j, + surroundingMaps, + layerTiles + )) { tmpTile[i + 2, j + 2] = true; } @@ -1702,49 +2307,126 @@ public void CalculateNW_XP(string layerName, int x, int y, MapBase[,] surroundin switch (situation) { case XP_INNER: - PlaceAutotileXp(layerName, x, y, 1, "a"); + PlaceAutotileXp( + layerName, + x, + y, + 1, + 'a', + autoTile + ); break; case XP_FILL: - PlaceAutotileXp(layerName, x, y, 1, "A"); + PlaceAutotileXp( + layerName, + x, + y, + 1, + 'A', + autoTile + ); break; case XP_NW: - PlaceAutotileXp(layerName, x, y, 1, "e"); + PlaceAutotileXp( + layerName, + x, + y, + 1, + 'e', + autoTile + ); break; case XPN: - PlaceAutotileXp(layerName, x, y, 1, "E"); + PlaceAutotileXp( + layerName, + x, + y, + 1, + 'E', + autoTile + ); break; case XP_NE: - PlaceAutotileXp(layerName, x, y, 1, "i"); + PlaceAutotileXp( + layerName, + x, + y, + 1, + 'i', + autoTile + ); break; case XPE: - PlaceAutotileXp(layerName, x, y, 1, "I"); + PlaceAutotileXp( + layerName, + x, + y, + 1, + 'I', + autoTile + ); break; case XP_SE: - PlaceAutotileXp(layerName, x, y, 1, "q"); + PlaceAutotileXp( + layerName, + x, + y, + 1, + 'q', + autoTile + ); break; case XPS: - PlaceAutotileXp(layerName, x, y, 1, "Q"); + PlaceAutotileXp( + layerName, + x, + y, + 1, + 'Q', + autoTile + ); break; case XP_SW: - PlaceAutotileXp(layerName, x, y, 1, "m"); + PlaceAutotileXp( + layerName, + x, + y, + 1, + 'm', + autoTile + ); break; case XPW: - PlaceAutotileXp(layerName, x, y, 1, "M"); + PlaceAutotileXp( + layerName, + x, + y, + 1, + 'M', + autoTile + ); break; } } - public void CalculateNE_XP(string layerName, int x, int y, MapBase[,] surroundingMaps) + public void CalculateNE_XP( + string layerName, + int x, + int y, + MapBase[,] surroundingMaps, + Tile[,] layerTiles, + QuarterTileCls? autoTile + ) { var tmpTile = new bool[4, 4]; byte situation = 1; @@ -1754,7 +2436,15 @@ public void CalculateNE_XP(string layerName, int x, int y, MapBase[,] surroundin { for (var i = -1; i < 2; i++) { - if (CheckTileMatch(layerName, x, y, x + i, y + j, surroundingMaps)) + if (CheckTileMatch( + layerName, + x, + y, + x + i, + y + j, + surroundingMaps, + layerTiles + )) { tmpTile[i + 2, j + 2] = true; } @@ -1830,49 +2520,126 @@ public void CalculateNE_XP(string layerName, int x, int y, MapBase[,] surroundin switch (situation) { case XP_INNER: - PlaceAutotileXp(layerName, x, y, 2, "b"); + PlaceAutotileXp( + layerName, + x, + y, + 2, + 'b', + autoTile + ); break; case XP_FILL: - PlaceAutotileXp(layerName, x, y, 2, "B"); + PlaceAutotileXp( + layerName, + x, + y, + 2, + 'B', + autoTile + ); break; case XP_NW: - PlaceAutotileXp(layerName, x, y, 2, "f"); + PlaceAutotileXp( + layerName, + x, + y, + 2, + 'f', + autoTile + ); break; case XPN: - PlaceAutotileXp(layerName, x, y, 2, "F"); + PlaceAutotileXp( + layerName, + x, + y, + 2, + 'F', + autoTile + ); break; case XP_NE: - PlaceAutotileXp(layerName, x, y, 2, "j"); + PlaceAutotileXp( + layerName, + x, + y, + 2, + 'j', + autoTile + ); break; case XPE: - PlaceAutotileXp(layerName, x, y, 2, "J"); + PlaceAutotileXp( + layerName, + x, + y, + 2, + 'J', + autoTile + ); break; case XP_SE: - PlaceAutotileXp(layerName, x, y, 2, "r"); + PlaceAutotileXp( + layerName, + x, + y, + 2, + 'r', + autoTile + ); break; case XPS: - PlaceAutotileXp(layerName, x, y, 2, "R"); + PlaceAutotileXp( + layerName, + x, + y, + 2, + 'R', + autoTile + ); break; case XP_SW: - PlaceAutotileXp(layerName, x, y, 2, "n"); + PlaceAutotileXp( + layerName, + x, + y, + 2, + 'n', + autoTile + ); break; case XPW: - PlaceAutotileXp(layerName, x, y, 2, "N"); + PlaceAutotileXp( + layerName, + x, + y, + 2, + 'N', + autoTile + ); break; } } - public void CalculateSW_XP(string layerName, int x, int y, MapBase[,] surroundingMaps) + public void CalculateSW_XP( + string layerName, + int x, + int y, + MapBase[,] surroundingMaps, + Tile[,] layerTiles, + QuarterTileCls? autoTile + ) { var tmpTile = new bool[4, 4]; byte situation = 1; @@ -1882,7 +2649,15 @@ public void CalculateSW_XP(string layerName, int x, int y, MapBase[,] surroundin { for (var i = -1; i < 2; i++) { - if (CheckTileMatch(layerName, x, y, x + i, y + j, surroundingMaps)) + if (CheckTileMatch( + layerName, + x, + y, + x + i, + y + j, + surroundingMaps, + layerTiles + )) { tmpTile[i + 2, j + 2] = true; } @@ -1958,49 +2733,126 @@ public void CalculateSW_XP(string layerName, int x, int y, MapBase[,] surroundin switch (situation) { case XP_INNER: - PlaceAutotileXp(layerName, x, y, 3, "c"); + PlaceAutotileXp( + layerName, + x, + y, + 3, + 'c', + autoTile + ); break; case XP_FILL: - PlaceAutotileXp(layerName, x, y, 3, "C"); + PlaceAutotileXp( + layerName, + x, + y, + 3, + 'C', + autoTile + ); break; case XP_NW: - PlaceAutotileXp(layerName, x, y, 3, "g"); + PlaceAutotileXp( + layerName, + x, + y, + 3, + 'g', + autoTile + ); break; case XPN: - PlaceAutotileXp(layerName, x, y, 3, "G"); + PlaceAutotileXp( + layerName, + x, + y, + 3, + 'G', + autoTile + ); break; case XP_NE: - PlaceAutotileXp(layerName, x, y, 3, "k"); + PlaceAutotileXp( + layerName, + x, + y, + 3, + 'k', + autoTile + ); break; case XPE: - PlaceAutotileXp(layerName, x, y, 3, "K"); + PlaceAutotileXp( + layerName, + x, + y, + 3, + 'K', + autoTile + ); break; case XP_SE: - PlaceAutotileXp(layerName, x, y, 3, "s"); + PlaceAutotileXp( + layerName, + x, + y, + 3, + 's', + autoTile + ); break; case XPS: - PlaceAutotileXp(layerName, x, y, 3, "S"); + PlaceAutotileXp( + layerName, + x, + y, + 3, + 'S', + autoTile + ); break; case XP_SW: - PlaceAutotileXp(layerName, x, y, 3, "o"); + PlaceAutotileXp( + layerName, + x, + y, + 3, + 'o', + autoTile + ); break; case XPW: - PlaceAutotileXp(layerName, x, y, 3, "O"); + PlaceAutotileXp( + layerName, + x, + y, + 3, + 'O', + autoTile + ); break; } } - public void CalculateSE_XP(string layerName, int x, int y, MapBase[,] surroundingMaps) + public void CalculateSE_XP( + string layerName, + int x, + int y, + MapBase[,] surroundingMaps, + Tile[,] layerTiles, + QuarterTileCls? autoTile + ) { var tmpTile = new bool[4, 4]; byte situation = 1; @@ -2010,7 +2862,15 @@ public void CalculateSE_XP(string layerName, int x, int y, MapBase[,] surroundin { for (var i = -1; i < 2; i++) { - if (CheckTileMatch(layerName, x, y, x + i, y + j, surroundingMaps)) + if (CheckTileMatch( + layerName, + x, + y, + x + i, + y + j, + surroundingMaps, + layerTiles + )) { tmpTile[i + 2, j + 2] = true; } @@ -2098,137 +2958,195 @@ public void CalculateSE_XP(string layerName, int x, int y, MapBase[,] surroundin switch (situation) { case XP_INNER: - PlaceAutotileXp(layerName, x, y, 4, "d"); + PlaceAutotileXp( + layerName, + x, + y, + 4, + 'd', + autoTile + ); break; case XP_FILL: - PlaceAutotileXp(layerName, x, y, 4, "D"); + PlaceAutotileXp( + layerName, + x, + y, + 4, + 'D', + autoTile + ); break; case XP_NW: - PlaceAutotileXp(layerName, x, y, 4, "h"); + PlaceAutotileXp( + layerName, + x, + y, + 4, + 'h', + autoTile + ); break; case XPN: - PlaceAutotileXp(layerName, x, y, 4, "H"); + PlaceAutotileXp( + layerName, + x, + y, + 4, + 'H', + autoTile + ); break; case XP_NE: - PlaceAutotileXp(layerName, x, y, 4, "l"); + PlaceAutotileXp( + layerName, + x, + y, + 4, + 'l', + autoTile + ); break; case XPE: - PlaceAutotileXp(layerName, x, y, 4, "L"); + PlaceAutotileXp( + layerName, + x, + y, + 4, + 'L', + autoTile + ); break; case XP_SE: - PlaceAutotileXp(layerName, x, y, 4, "t"); + PlaceAutotileXp( + layerName, + x, + y, + 4, + 't', + autoTile + ); break; case XPS: - PlaceAutotileXp(layerName, x, y, 4, "T"); + PlaceAutotileXp( + layerName, + x, + y, + 4, + 'T', + autoTile + ); break; case XP_SW: - PlaceAutotileXp(layerName, x, y, 4, "p"); + PlaceAutotileXp( + layerName, + x, + y, + 4, + 'p', + autoTile + ); break; case XPW: - PlaceAutotileXp(layerName, x, y, 4, "P"); + PlaceAutotileXp( + layerName, + x, + y, + 4, + 'P', + autoTile + ); break; } } - public bool CheckTileMatch(string layerName, int x1, int y1, int x2, int y2, MapBase[,] surroundingMaps) + public bool CheckTileMatch(string layerName, int x1, int y1, int x2, int y2, MapBase[,] surroundingMaps, Tile[,] layerTiles) { - Tile targetTile; - targetTile.TilesetId = Guid.Empty; - targetTile.X = -1; - targetTile.Y = -1; - targetTile.Autotile = 0; - var gridX = 0; var gridY = 0; if (x2 < 0) { gridX = -1; - x2 += Options.MapWidth; + x2 += _mapWidth; } - if (y2 < 0) + if (x2 >= _mapWidth) { - gridY = -1; - y2 += Options.MapHeight; + gridX = 1; + x2 -= _mapWidth; } - if (x2 >= Options.MapWidth) + if (y2 < 0) { - gridX = 1; - x2 -= Options.MapWidth; + gridY = -1; + y2 += _mapHeight; } - if (y2 >= Options.MapHeight) + if (y2 >= _mapHeight) { gridY = 1; - y2 -= Options.MapHeight; + y2 -= _mapHeight; } - if (surroundingMaps[gridX + 1, gridY + 1] != null) + Tile targetTile = new() + { + TilesetId = default, + X = -1, + Y = -1, + Autotile = 0, + }; + + var surroundingMap = surroundingMaps[gridX + 1, gridY + 1]; + if (surroundingMap != null) { - var layers = surroundingMaps[gridX + 1, gridY + 1].Layers; - if (!layers.ContainsKey(layerName)) + var layers = surroundingMap.Layers; + if (!layers.TryGetValue(layerName, out var surroundingMapLayerTiles)) { return true; } - var tiles = layers[layerName]; - targetTile = tiles[x2, y2]; - } - if (!mMyMap.Layers.ContainsKey(layerName)) - { - return true; + targetTile = surroundingMapLayerTiles[x2, y2]; } - var sourceTile = mMyMap.Layers[layerName][x1, y1]; if (targetTile.X == -1) { return true; } - // fakes ALWAYS return true - if (targetTile.Autotile == AUTOTILE_FAKE) - { - return true; - } - - // check neighbour is an autotile - if (targetTile.Autotile == 0) + switch (targetTile.Autotile) { - return false; + // fakes ALWAYS return true + case AUTOTILE_FAKE: + return true; + // check neighbour is an autotile + case 0: + return false; } - // check we//re a matching - if (sourceTile.TilesetId != targetTile.TilesetId) - { - return false; - } + var sourceTile = layerTiles[x1, y1]; // check tiles match - if (sourceTile.X != targetTile.X) + // The int check should be faster than guid comparison + if (sourceTile.X != targetTile.X || sourceTile.Y != targetTile.Y) { return false; } - if (sourceTile.Y != targetTile.Y) - { - return false; - } - - return true; + // check we//re a matching + return sourceTile.TilesetId == targetTile.TilesetId; } - private int CalculateCliffHeight(string layerName, int x, int y, MapBase[,] surroundingMaps, out int cliffStart) + private int CalculateCliffHeight(string layerName, int x, int y, MapBase[,] surroundingMaps, Tile[,] layerTiles, out int cliffStart) { Tile sourceTile; sourceTile.TilesetId = Guid.Empty; @@ -2243,392 +3161,448 @@ private int CalculateCliffHeight(string layerName, int x, int y, MapBase[,] surr if (x < 0) { gridX = -1; - x += Options.MapWidth; + x += _mapWidth; } if (y < 0) { gridY = -1; - y += Options.MapHeight; + y += _mapHeight; } - if (x >= Options.MapWidth) + if (x >= _mapWidth) { gridX = 1; - x -= Options.MapWidth; + x -= _mapWidth; } - if (y >= Options.MapHeight) + if (y >= _mapHeight) { gridY = 1; - y -= Options.MapHeight; + y -= _mapHeight; } - if (surroundingMaps[gridX + 1, gridY + 1] != null) + var otherMap = surroundingMaps[gridX + 1, gridY + 1]; + if (otherMap != null) { - var layers = surroundingMaps[gridX + 1, gridY + 1].Layers; - var tiles = layers[layerName]; - sourceTile = tiles[x, y]; + if (otherMap.Layers is not { } otherMapLayers) + { + return 0; + } + + if (!otherMapLayers.TryGetValue(layerName, out var otherMapTiles)) + { + return 0; + } + + sourceTile = otherMapTiles[x, y]; } - if (sourceTile.Autotile == (int) AUTOTILE_CLIFF) + if (sourceTile.Autotile != AUTOTILE_CLIFF) { - var height = 1; - var i = y - 1; - while (i > -Options.MapHeight) - { - if (CheckTileMatch(layerName, x, y, x, i, surroundingMaps)) - { - height++; - cliffStart--; - } - else - { - break; - } + return 0; + } - i--; + var height = 1; + var i = y - 1; + while (i > -_mapHeight) + { + if (!CheckTileMatch( + layerName, + x, + y, + x, + i, + surroundingMaps, + layerTiles + )) + { + break; } - i = y + 1; - while (i < Options.MapHeight * 2) - { - if (CheckTileMatch(layerName, x, y, x, i, surroundingMaps)) - { - height++; - } - else - { - break; - } + height++; + cliffStart--; + i--; + } - i++; + i = y + 1; + while (i < _mapHeight * 2) + { + if (!CheckTileMatch( + layerName, + x, + y, + x, + i, + surroundingMaps, + layerTiles + )) + { + break; } - return height; + height++; + i++; } - return 0; + return height; + } - public void PlaceAutotile(string layerName, int x, int y, byte tileQuarter, string autoTileLetter) + public void PlaceAutotile( + string layerName, + int x, + int y, + byte tileQuarter, + char autoTileLetter, + QuarterTileCls? autoTile + ) { var quarterTile = new PointStruct(); switch (autoTileLetter) { - case "a": + case 'a': quarterTile.X = AutoInner[1].X; quarterTile.Y = AutoInner[1].Y; break; - case "b": + case 'b': quarterTile.X = AutoInner[2].X; quarterTile.Y = AutoInner[2].Y; break; - case "c": + case 'c': quarterTile.X = AutoInner[3].X; quarterTile.Y = AutoInner[3].Y; break; - case "d": + case 'd': quarterTile.X = AutoInner[4].X; quarterTile.Y = AutoInner[4].Y; break; - case "e": + case 'e': quarterTile.X = AutoNw[1].X; quarterTile.Y = AutoNw[1].Y; break; - case "f": + case 'f': quarterTile.X = AutoNw[2].X; quarterTile.Y = AutoNw[2].Y; break; - case "g": + case 'g': quarterTile.X = AutoNw[3].X; quarterTile.Y = AutoNw[3].Y; break; - case "h": + case 'h': quarterTile.X = AutoNw[4].X; quarterTile.Y = AutoNw[4].Y; break; - case "i": + case 'i': quarterTile.X = AutoNe[1].X; quarterTile.Y = AutoNe[1].Y; break; - case "j": + case 'j': quarterTile.X = AutoNe[2].X; quarterTile.Y = AutoNe[2].Y; break; - case "k": + case 'k': quarterTile.X = AutoNe[3].X; quarterTile.Y = AutoNe[3].Y; break; - case "l": + case 'l': quarterTile.X = AutoNe[4].X; quarterTile.Y = AutoNe[4].Y; break; - case "m": + case 'm': quarterTile.X = AutoSw[1].X; quarterTile.Y = AutoSw[1].Y; break; - case "n": + case 'n': quarterTile.X = AutoSw[2].X; quarterTile.Y = AutoSw[2].Y; break; - case "o": + case 'o': quarterTile.X = AutoSw[3].X; quarterTile.Y = AutoSw[3].Y; break; - case "p": + case 'p': quarterTile.X = AutoSw[4].X; quarterTile.Y = AutoSw[4].Y; break; - case "q": + case 'q': quarterTile.X = AutoSe[1].X; quarterTile.Y = AutoSe[1].Y; break; - case "r": + case 'r': quarterTile.X = AutoSe[2].X; quarterTile.Y = AutoSe[2].Y; break; - case "s": + case 's': quarterTile.X = AutoSe[3].X; quarterTile.Y = AutoSe[3].Y; break; - case "t": + case 't': quarterTile.X = AutoSe[4].X; quarterTile.Y = AutoSe[4].Y; break; } - Layers[layerName][x, y].QuarterTile[tileQuarter] = quarterTile; + + if (autoTile == null && !TryGetAutoTileForLayer( + layerName, + x, + y, + out autoTile + )) + { + return; + } + + autoTile.QuarterTile[tileQuarter] = quarterTile; } - public void PlaceAutotileXp(string layerName, int x, int y, byte tileQuarter, string autoTileLetter) + public void PlaceAutotileXp( + string layerName, + int x, + int y, + byte tileQuarter, + char autoTileLetter, + QuarterTileCls? autoTile + ) { var quarterTile = new PointStruct(); switch (autoTileLetter) { - case "a": + case 'a': quarterTile.X = AutoInnerXp[1].X; quarterTile.Y = AutoInnerXp[1].Y; break; - case "b": + case 'b': quarterTile.X = AutoInnerXp[2].X; quarterTile.Y = AutoInnerXp[2].Y; break; - case "c": + case 'c': quarterTile.X = AutoInnerXp[3].X; quarterTile.Y = AutoInnerXp[3].Y; break; - case "d": + case 'd': quarterTile.X = AutoInnerXp[4].X; quarterTile.Y = AutoInnerXp[4].Y; break; - case "e": + case 'e': quarterTile.X = AutoNwXp[1].X; quarterTile.Y = AutoNwXp[1].Y; break; - case "f": + case 'f': quarterTile.X = AutoNwXp[2].X; quarterTile.Y = AutoNwXp[2].Y; break; - case "g": + case 'g': quarterTile.X = AutoNwXp[3].X; quarterTile.Y = AutoNwXp[3].Y; break; - case "h": + case 'h': quarterTile.X = AutoNwXp[4].X; quarterTile.Y = AutoNwXp[4].Y; break; - case "i": + case 'i': quarterTile.X = AutoNeXp[1].X; quarterTile.Y = AutoNeXp[1].Y; break; - case "j": + case 'j': quarterTile.X = AutoNeXp[2].X; quarterTile.Y = AutoNeXp[2].Y; break; - case "k": + case 'k': quarterTile.X = AutoNeXp[3].X; quarterTile.Y = AutoNeXp[3].Y; break; - case "l": + case 'l': quarterTile.X = AutoNeXp[4].X; quarterTile.Y = AutoNeXp[4].Y; break; - case "m": + case 'm': quarterTile.X = AutoSwXp[1].X; quarterTile.Y = AutoSwXp[1].Y; break; - case "n": + case 'n': quarterTile.X = AutoSwXp[2].X; quarterTile.Y = AutoSwXp[2].Y; break; - case "o": + case 'o': quarterTile.X = AutoSwXp[3].X; quarterTile.Y = AutoSwXp[3].Y; break; - case "p": + case 'p': quarterTile.X = AutoSwXp[4].X; quarterTile.Y = AutoSwXp[4].Y; break; - case "q": + case 'q': quarterTile.X = AutoSeXp[1].X; quarterTile.Y = AutoSeXp[1].Y; break; - case "r": + case 'r': quarterTile.X = AutoSeXp[2].X; quarterTile.Y = AutoSeXp[2].Y; break; - case "s": + case 's': quarterTile.X = AutoSeXp[3].X; quarterTile.Y = AutoSeXp[3].Y; break; - case "t": + case 't': quarterTile.X = AutoSeXp[4].X; quarterTile.Y = AutoSeXp[4].Y; break; //XP Additional Templates - case "A": + case 'A': quarterTile.X = AutoCxp[1].X; quarterTile.Y = AutoCxp[1].Y; break; - case "B": + case 'B': quarterTile.X = AutoCxp[2].X; quarterTile.Y = AutoCxp[2].Y; break; - case "C": + case 'C': quarterTile.X = AutoCxp[3].X; quarterTile.Y = AutoCxp[3].Y; break; - case "D": + case 'D': quarterTile.X = AutoCxp[4].X; quarterTile.Y = AutoCxp[4].Y; break; - case "E": + case 'E': quarterTile.X = AutoNxp[1].X; quarterTile.Y = AutoNxp[1].Y; break; - case "F": + case 'F': quarterTile.X = AutoNxp[2].X; quarterTile.Y = AutoNxp[2].Y; break; - case "G": + case 'G': quarterTile.X = AutoNxp[3].X; quarterTile.Y = AutoNxp[3].Y; break; - case "H": + case 'H': quarterTile.X = AutoNxp[4].X; quarterTile.Y = AutoNxp[4].Y; break; - case "I": + case 'I': quarterTile.X = AutoExp[1].X; quarterTile.Y = AutoExp[1].Y; break; - case "J": + case 'J': quarterTile.X = AutoExp[2].X; quarterTile.Y = AutoExp[2].Y; break; - case "K": + case 'K': quarterTile.X = AutoExp[3].X; quarterTile.Y = AutoExp[3].Y; break; - case "L": + case 'L': quarterTile.X = AutoExp[4].X; quarterTile.Y = AutoExp[4].Y; break; - case "M": + case 'M': quarterTile.X = AutoWxp[1].X; quarterTile.Y = AutoWxp[1].Y; break; - case "N": + case 'N': quarterTile.X = AutoWxp[2].X; quarterTile.Y = AutoWxp[2].Y; break; - case "O": + case 'O': quarterTile.X = AutoWxp[3].X; quarterTile.Y = AutoWxp[3].Y; break; - case "P": + case 'P': quarterTile.X = AutoWxp[4].X; quarterTile.Y = AutoWxp[4].Y; break; - case "Q": + case 'Q': quarterTile.X = AutoSxp[1].X; quarterTile.Y = AutoSxp[1].Y; break; - case "R": + case 'R': quarterTile.X = AutoSxp[2].X; quarterTile.Y = AutoSxp[2].Y; break; - case "S": + case 'S': quarterTile.X = AutoSxp[3].X; quarterTile.Y = AutoSxp[3].Y; break; - case "T": + case 'T': quarterTile.X = AutoSxp[4].X; quarterTile.Y = AutoSxp[4].Y; break; } - Layers[layerName][x, y].QuarterTile[tileQuarter] = quarterTile; + + if (autoTile == null && !TryGetAutoTileForLayer( + layerName, + x, + y, + out autoTile + )) + { + return; + } + + autoTile.QuarterTile[tileQuarter] = quarterTile; } } diff --git a/Framework/Intersect.Framework.Core/GameObjects/Maps/MapBase.cs b/Framework/Intersect.Framework.Core/GameObjects/Maps/MapBase.cs index 54668be3bc..5f08dc741e 100644 --- a/Framework/Intersect.Framework.Core/GameObjects/Maps/MapBase.cs +++ b/Framework/Intersect.Framework.Core/GameObjects/Maps/MapBase.cs @@ -1,11 +1,12 @@ using System.ComponentModel.DataAnnotations.Schema; +using System.Runtime.CompilerServices; using Intersect.Collections; using Intersect.Compression; using Intersect.Enums; using Intersect.Framework.Core.Serialization; using Intersect.GameObjects.Events; using Intersect.Models; - +using Intersect.Threading; using Newtonsoft.Json; namespace Intersect.GameObjects.Maps; @@ -29,7 +30,7 @@ public partial class MapBase : DatabaseObject //Client/Editor Only [JsonIgnore] [NotMapped] - public MapAutotiles Autotiles; + public MapAutotiles Autotiles { get; set; } [NotMapped] public List EventIds { get; set; } = []; @@ -46,15 +47,30 @@ public partial class MapBase : DatabaseObject private byte[] mCachedAttributeData = null; //SyncLock - [JsonIgnore] - [NotMapped] - protected object mMapLock = new(); + // [JsonIgnore] + // [NotMapped] + // protected object mMapLock = new(); + + // [NotMapped] + // [JsonIgnore] + // public object MapLock => mMapLock; + + private readonly LockHelper _lock; + + [JsonIgnore, NotMapped] + public LockHelper Lock + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _lock; + } [JsonConstructor] public MapBase(Guid id) : base(id) { Name = "New Map"; + _lock = new LockHelper($"{Name} / {Id}"); + //Create empty tile array and then compress it down if (Layers == null) { @@ -74,6 +90,7 @@ public MapBase(Guid id) : base(id) public MapBase() { Name = "New Map"; + _lock = new LockHelper($"{Name} / {Id}"); } public MapBase(MapBase mapBase) : base(mapBase?.Id ?? Guid.Empty) @@ -83,77 +100,97 @@ public MapBase(MapBase mapBase) : base(mapBase?.Id ?? Guid.Empty) return; } - lock (MapLock ?? throw new ArgumentNullException(nameof(MapLock), @"this")) + _lock = new LockHelper($"{mapBase.Name} / {Id}"); + + try { - lock (mapBase.MapLock ?? throw new ArgumentNullException(nameof(mapBase.MapLock), nameof(mapBase))) + if (!Lock.TryAcquireLock("MapBase(MapBase) constructor (self)", out var selfLock)) + { + throw new InvalidOperationException("Failed to acquire lock on self"); + } + + using (selfLock) { - Name = mapBase.Name; - Brightness = mapBase.Brightness; - IsIndoors = mapBase.IsIndoors; - if (Layers != null && mapBase.Layers != null) + if (!mapBase.Lock.TryAcquireLock("MapBase(MapBase) constructor (other)", out var otherLock)) { - Layers.Clear(); + throw new InvalidOperationException("Failed to acquire lock on other"); + } - foreach (var layer in mapBase.Layers) + using (otherLock) + { + Name = mapBase.Name; + Brightness = mapBase.Brightness; + IsIndoors = mapBase.IsIndoors; + if (Layers != null && mapBase.Layers != null) { - var tiles = new Tile[Options.MapWidth, Options.MapHeight]; - for (var x = 0; x < Options.MapWidth; x++) + Layers.Clear(); + + foreach (var layer in mapBase.Layers) { - for (var y = 0; y < Options.MapHeight; y++) + var tiles = new Tile[Options.MapWidth, Options.MapHeight]; + for (var x = 0; x < Options.MapWidth; x++) { - tiles[x, y] = new Tile + for (var y = 0; y < Options.MapHeight; y++) { - TilesetId = layer.Value[x, y].TilesetId, - X = layer.Value[x, y].X, - Y = layer.Value[x, y].Y, - Autotile = layer.Value[x, y].Autotile - }; + tiles[x, y] = new Tile + { + TilesetId = layer.Value[x, y].TilesetId, + X = layer.Value[x, y].X, + Y = layer.Value[x, y].Y, + Autotile = layer.Value[x, y].Autotile + }; + } } + + Layers.Add(layer.Key, tiles); } - Layers.Add(layer.Key, tiles); } - } - for (var x = 0; x < Options.MapWidth; x++) - { - for (var y = 0; y < Options.MapHeight; y++) + for (var x = 0; x < Options.MapWidth; x++) { - if (Attributes == null) + for (var y = 0; y < Options.MapHeight; y++) { - continue; - } + if (Attributes == null) + { + continue; + } - if (mapBase.Attributes?[x, y] == null) - { - Attributes[x, y] = null; - } - else - { - Attributes[x, y] = mapBase.Attributes[x, y].Clone(); + if (mapBase.Attributes?[x, y] == null) + { + Attributes[x, y] = null; + } + else + { + Attributes[x, y] = mapBase.Attributes[x, y].Clone(); + } } } - } - for (var i = 0; i < mapBase.Spawns?.Count; i++) - { - Spawns.Add(new NpcSpawn(mapBase.Spawns[i])); - } + for (var i = 0; i < mapBase.Spawns?.Count; i++) + { + Spawns.Add(new NpcSpawn(mapBase.Spawns[i])); + } - for (var i = 0; i < mapBase.Lights?.Count; i++) - { - Lights.Add(new LightBase(mapBase.Lights[i])); - } + for (var i = 0; i < mapBase.Lights?.Count; i++) + { + Lights.Add(new LightBase(mapBase.Lights[i])); + } - foreach (var record in mapBase.LocalEvents) - { - var evt = new EventBase(record.Key, record.Value?.JsonData); - LocalEvents?.Add(record.Key, evt); - } + foreach (var record in mapBase.LocalEvents) + { + var evt = new EventBase(record.Key, record.Value?.JsonData); + LocalEvents?.Add(record.Key, evt); + } - EventIds?.Clear(); - EventIds?.AddRange(mapBase.EventIds?.ToArray() ?? []); + EventIds?.Clear(); + EventIds?.AddRange(mapBase.EventIds?.ToArray() ?? []); + } } } + finally + { + // Nothing to do + } } //For server only @@ -348,10 +385,6 @@ public AnimationBase WeatherAnimation public bool HideEquipment { get; set; } - [NotMapped] - [JsonIgnore] - public object MapLock => mMapLock; - public virtual MapBase[,] GenerateAutotileGrid() { return null; diff --git a/Framework/Intersect.Framework.Core/GameObjects/Maps/MapTile.cs b/Framework/Intersect.Framework.Core/GameObjects/Maps/MapTile.cs index b349c885ea..0e8806c7ec 100644 --- a/Framework/Intersect.Framework.Core/GameObjects/Maps/MapTile.cs +++ b/Framework/Intersect.Framework.Core/GameObjects/Maps/MapTile.cs @@ -4,12 +4,16 @@ namespace Intersect.GameObjects.Maps; public partial struct Tile { + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public Guid TilesetId; + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public int X; + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public int Y; + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public byte Autotile; [JsonIgnore] diff --git a/Framework/Intersect.Framework.Core/Models/ObjectDataDiskCache.cs b/Framework/Intersect.Framework.Core/Models/ObjectDataDiskCache.cs index b149866df9..8a0eed4c94 100644 --- a/Framework/Intersect.Framework.Core/Models/ObjectDataDiskCache.cs +++ b/Framework/Intersect.Framework.Core/Models/ObjectDataDiskCache.cs @@ -11,7 +11,10 @@ namespace Intersect.Models; public static class ObjectDataDiskCache -{ private static string RootCacheDirectory => Path.Combine( +{ + public static bool Enabled { get; set; } = false; + + private static string RootCacheDirectory => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ".intersect", (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()).GetName().Name ?? @@ -47,6 +50,12 @@ private static SymmetricAlgorithm CreateCryptoServiceProvider(string identifier, public static bool TryLoad(Guid id, [NotNullWhen(true)] out ObjectCacheData? data) { + if (!Enabled) + { + data = null; + return false; + } + try { #if DEBUG @@ -135,6 +144,11 @@ public static bool TryLoad(Guid id, [NotNullWhen(true)] out ObjectCacheData objectCacheData) { + if (!Enabled) + { + return false; + } + try { #if DEBUG diff --git a/Framework/Intersect.Framework.Core/Threading/LockHelper.cs b/Framework/Intersect.Framework.Core/Threading/LockHelper.cs new file mode 100644 index 0000000000..2e0ad189c7 --- /dev/null +++ b/Framework/Intersect.Framework.Core/Threading/LockHelper.cs @@ -0,0 +1,169 @@ +#if DIAGNOSTIC +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Intersect.Logging; +#endif + +namespace Intersect.Threading; + +public sealed class LockHelper(string? name = default) +{ + private readonly object _lock = new(); + + private bool _lockTaken; + private DateTime _takenAt; + + public void Lock(Action work, string? debugInfo = default) + { + if (_lockTaken) + { + return; + } + +#if DIAGNOSTIC + DateTime takenAt = default; +#endif + + try + { +#if DIAGNOSTIC + var waitStartedAt = DateTime.UtcNow; +#endif + + Monitor.Enter(_lock, ref _lockTaken); + +#if DIAGNOSTIC + var waitElapsed = DateTime.UtcNow - waitStartedAt; + + DebugPrint($"[{name}] Waited for {waitElapsed.TotalMilliseconds}ms to acquire the lock [{debugInfo}]"); +#endif + + if (!_lockTaken) + { + return; + } + +#if DIAGNOSTIC + _takenAt = takenAt = DateTime.UtcNow; +#endif + + work(); + } + finally + { + if (_lockTaken) + { + Monitor.Exit(_lock); + _lockTaken = false; + +#if DIAGNOSTIC + var elapsedTaken = DateTime.UtcNow - takenAt; + + DebugPrint($"[{name}] Held lock for {elapsedTaken.TotalMilliseconds}ms [{debugInfo}]"); +#endif + } + else + { +#pragma warning disable CA2219 + throw new InvalidOperationException( + $"[LockHelper][{name ?? "?"}] Failed to take lock {debugInfo ?? string.Empty}" + ); +#pragma warning restore CA2219 + } + } + } + + public bool TryAcquireLock(out Reference lockHelperReference) => + TryAcquireLock(null, out lockHelperReference); + + public bool TryAcquireLock(string? debugInfo, out Reference lockHelperReference) + { + bool lockTaken = false; + +#if DIAGNOSTIC + DebugPrint($"TryAcquireLock from: {debugInfo}"); + + if (_lockTaken) + { + DebugPrint($"We will be waiting on a lock from: {debugInfo}"); + } + + var waitStartedAt = DateTime.UtcNow; +#endif + + Monitor.Enter(_lock, ref lockTaken); + +#if DIAGNOSTIC + var waitElapsed = DateTime.UtcNow - waitStartedAt; + + DebugPrint($"[{name}] Waited for {waitElapsed.TotalMilliseconds}ms to acquire the lock [{debugInfo}]"); + + _takenAt = DateTime.UtcNow; +#endif + + if (lockTaken) + { + _lockTaken = lockTaken; + lockHelperReference = new Reference(name, _lock, _takenAt, ref _lockTaken); + return true; + } + + lockHelperReference = default; + return false; + } + + public bool ReleaseLock(string? debugInfo = default) + { + if (!_lockTaken) + { + return false; + } + + Monitor.Exit(_lock); + _lockTaken = false; + +#if DIAGNOSTIC + var elapsedTaken = DateTime.UtcNow - _takenAt; + + DebugPrint($"[{name}] Held lock for {elapsedTaken.TotalMilliseconds}ms [{debugInfo}]"); +#endif + + return true; + } + +#if DIAGNOSTIC + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void DebugPrint(string message) + { + // if (LegacyLogging.Logger is { } logger) + // { + // logger.Debug(message); + // } + // else + // { + // Console.WriteLine(message); + // } + } +#endif + + public ref struct Reference(string name, object lockObject, DateTime takenAt, ref bool lockTaken) : IDisposable + { + private readonly ref bool _lockTaken = ref lockTaken; + + public void Dispose() + { + if (!_lockTaken) + { + return; + } + + Monitor.Exit(lockObject); + +#if DIAGNOSTIC + var elapsedTaken = DateTime.UtcNow - takenAt; + + DebugPrint($"[{name}] Held lock for {elapsedTaken.TotalMilliseconds}ms [from ref]"); +#endif + } + } +} \ No newline at end of file diff --git a/Intersect.Client.Core/Core/Graphics.cs b/Intersect.Client.Core/Core/Graphics.cs index f243011d53..f0f62c59cf 100644 --- a/Intersect.Client.Core/Core/Graphics.cs +++ b/Intersect.Client.Core/Core/Graphics.cs @@ -302,7 +302,7 @@ public static void DrawInGame(TimeSpan deltaTime) continue; } - DrawMapPanorama(Globals.MapGrid[x, y]); + DrawMapPanorama(mapId); } } @@ -619,10 +619,24 @@ private static void DrawMap(Guid mapId, int layer) return; } - if (!new FloatRect( - map.X, map.Y, Options.TileWidth * Options.MapWidth, Options.TileHeight * Options.MapHeight - ).IntersectsWith(WorldViewport)) + var mapBounds = new FloatRect( + map.X, + map.Y, + Options.TileWidth * Options.MapWidth, + Options.TileHeight * Options.MapHeight + ); + if (!mapBounds.IntersectsWith(WorldViewport)) { + var scaledMapBounds = new FloatRect( + map.GridX, + map.GridY, + 1, + 1 + ); + var scaledWorldViewport = WorldViewport / new Point( + Options.TileWidth * Options.MapWidth, + Options.TileHeight * Options.MapHeight + ); return; } diff --git a/Intersect.Client.Core/Maps/MapInstance.cs b/Intersect.Client.Core/Maps/MapInstance.cs index 64a31839fa..a33bdc15a0 100644 --- a/Intersect.Client.Core/Maps/MapInstance.cs +++ b/Intersect.Client.Core/Maps/MapInstance.cs @@ -1,6 +1,5 @@ using System.Diagnostics; using System.Collections.Concurrent; -using Intersect.Client.Classes.MonoGame.Graphics; using Intersect.Client.Core; using Intersect.Client.Entities; using Intersect.Client.Entities.Events; @@ -37,9 +36,11 @@ public partial class MapInstance : MapBase, IGameObject, IMap public delegate void MapLoadedDelegate(MapInstance map); //Map State Variables - public static Dictionary MapRequests { get; set; } = new Dictionary(); + public static ConcurrentDictionary MapRequests { get; set; } = []; - public static MapLoadedDelegate OnMapLoaded { get; set; } + public static event MapLoadedDelegate? MapLoaded; + + public static void DoMapLoaded(MapInstance mapInstance) => MapLoaded?.Invoke(mapInstance); private static MapControllers sLookup; @@ -116,6 +117,16 @@ public partial class MapInstance : MapBase, IGameObject, IMap private readonly Dictionary _tileBuffersPerLayer = []; // [Layer][Autotile Frame][Buffer Index] + private readonly int _mapWidth = Options.Instance.MapOpts.MapWidth; + private readonly int _mapHeight = Options.Instance.MapOpts.MapHeight; + private readonly int _tileWidth = Options.TileWidth; + private readonly int _tileHeight = Options.TileHeight; + private readonly int _tileHalfWidth = Options.Instance.MapOpts.TileWidth / 2; + private readonly int _tileHalfHeight = Options.Instance.MapOpts.TileHeight / 2; + private readonly List _layersAll = Options.Instance.MapOpts.Layers.All; + private int _gridY; + private int _gridX; + //Initialization public MapInstance(Guid id) : base(id) { @@ -138,7 +149,7 @@ public int GridX } _gridX = value; - X = _gridX * _width * _tileWidth; + X = _gridX * _mapWidth * _tileWidth; } } @@ -153,7 +164,7 @@ public int GridY } _gridY = value; - Y = _gridY * _height * _tileHeight; + Y = _gridY * _mapHeight * _tileHeight; } } @@ -162,8 +173,10 @@ public int GridY public new static MapControllers Lookup => sLookup ?? (sLookup = new MapControllers(MapBase.Lookup)); + public void MarkLoaded() => IsLoaded = true; + //Load - public void Load(string json) + public void Load(string json, bool markLoaded = true) { LocalEntitiesToDispose.AddRange(LocalEntities.Keys.ToArray()); JsonConvert.PopulateObject( @@ -176,23 +189,45 @@ public void Load(string json) } ); - IsLoaded = true; + IsLoaded = markLoaded; Autotiles = new MapAutotiles(this); - OnMapLoaded -= HandleMapLoaded; - OnMapLoaded += HandleMapLoaded; - MapRequests.Remove(Id); + MapLoaded -= HandleMapLoaded; + MapLoaded += HandleMapLoaded; + MapRequests.Remove(Id, out _); } public void LoadTileData(byte[] packet) { - Layers = JsonConvert.DeserializeObject>(LZ4.UnPickleString(packet), mJsonSerializerSettings); - foreach (var layer in Options.Instance.MapOpts.Layers.All) + var startLoadTileData = DateTime.UtcNow; + + var json = LZ4.UnPickleString(packet); + + var endDecompression = DateTime.UtcNow; + + Layers = JsonConvert.DeserializeObject>(json, mJsonSerializerSettings); + + var endDeserialization = DateTime.UtcNow; + + if (Layers is { } layers) { - if (!Layers.ContainsKey(layer)) + foreach (var layer in _layersAll) { - Layers.Add(layer, new Tile[_width, _height]); + if (!layers.ContainsKey(layer)) + { + layers.Add(layer, new Tile[_mapWidth, _mapHeight]); + } } } + + var endLayerAdditions = DateTime.UtcNow; + + Log.Debug($""" + [HandleMap] LoadTileData() {Id} ({Name}) + - Full method took {(endLayerAdditions - startLoadTileData).TotalMilliseconds}ms + - Decompressing took {(endDecompression - startLoadTileData).TotalMilliseconds}ms + - Deserialization took {(endDeserialization - endDecompression).TotalMilliseconds}ms + - Adding layer buffers took {(endLayerAdditions - endDeserialization).TotalMilliseconds}ms + """); } private void CacheTextures() @@ -202,16 +237,16 @@ private void CacheTextures() return; } - foreach (var layer in Options.Instance.MapOpts.Layers.All) + foreach (var layer in _layersAll) { if (!Layers.TryGetValue(layer, out var layerTiles)) { continue; } - for (var x = 0; x < _width; x++) + for (var x = 0; x < _mapWidth; x++) { - for (var y = 0; y < _height; y++) + for (var y = 0; y < _mapHeight; y++) { var layerTile = layerTiles[x, y]; if (layerTile.TilesetId == default) @@ -342,7 +377,7 @@ private void HandleMapLoaded(MapInstance map) else if (map.GridY == GridY) { //Check West - for (var y = 0; y < _height; y++) + for (var y = 0; y < _mapHeight; y++) { updatedBuffers.UnionWith(CheckAutotile(0, y, surroundingMaps)); } @@ -350,7 +385,7 @@ private void HandleMapLoaded(MapInstance map) else if (map.GridY == GridY + 1) { //Check Southwest - updatedBuffers.UnionWith(CheckAutotile(0, _height - 1, surroundingMaps)); + updatedBuffers.UnionWith(CheckAutotile(0, _mapHeight - 1, surroundingMaps)); } } else if (map.GridX == GridX) @@ -358,7 +393,7 @@ private void HandleMapLoaded(MapInstance map) if (map.GridY == GridY - 1) { //Check North - for (var x = 0; x < _width; x++) + for (var x = 0; x < _mapWidth; x++) { updatedBuffers.UnionWith(CheckAutotile(x, 0, surroundingMaps)); } @@ -366,9 +401,9 @@ private void HandleMapLoaded(MapInstance map) else if (map.GridY == GridY + 1) { //Check South - for (var x = 0; x < _width; x++) + for (var x = 0; x < _mapWidth; x++) { - updatedBuffers.UnionWith(CheckAutotile(x, _height - 1, surroundingMaps)); + updatedBuffers.UnionWith(CheckAutotile(x, _mapHeight - 1, surroundingMaps)); } } } @@ -378,34 +413,39 @@ private void HandleMapLoaded(MapInstance map) { //Check Northeast updatedBuffers.UnionWith( - CheckAutotile(_width - 1, _height, surroundingMaps) + CheckAutotile(_mapWidth - 1, _mapHeight, surroundingMaps) ); } else if (map.GridY == GridY) { //Check East - for (var y = 0; y < _height; y++) + for (var y = 0; y < _mapHeight; y++) { - updatedBuffers.UnionWith(CheckAutotile(_width - 1, y, surroundingMaps)); + updatedBuffers.UnionWith(CheckAutotile(_mapWidth - 1, y, surroundingMaps)); } } else if (map.GridY == GridY + 1) { //Check Southeast updatedBuffers.UnionWith( - CheckAutotile(_width - 1, _height - 1, surroundingMaps) + CheckAutotile(_mapWidth - 1, _mapHeight - 1, surroundingMaps) ); } } //Along with edges we need to recalculate ALL cliffs :( - foreach (var layer in Options.Instance.MapOpts.Layers.All) + foreach (var layer in _layersAll) { - for (var x = 0; x < _width; x++) + if (!Layers.TryGetValue(layer, out var layerTiles)) { - for (var y = 0; y < _height; y++) + continue; + } + + for (var x = 0; x < _mapWidth; x++) + { + for (var y = 0; y < _mapHeight; y++) { - if (Layers[layer][x, y].Autotile == MapAutotiles.AUTOTILE_CLIFF) + if (layerTiles[x, y].Autotile == MapAutotiles.AUTOTILE_CLIFF) { updatedBuffers.UnionWith(CheckAutotile(x, y, surroundingMaps)); } @@ -414,10 +454,16 @@ private void HandleMapLoaded(MapInstance map) } } + var startSetData = DateTime.UtcNow; + foreach (var buffer in updatedBuffers) { buffer.SetData(); } + + var elapsedSetData = DateTime.UtcNow - startSetData; + + Log.Debug($"Took {elapsedSetData.TotalMilliseconds}ms to update {updatedBuffers.Count} buffers"); } private GameTileBuffer[] CheckAutotile(int x, int y, MapBase[,] surroundingMaps) @@ -427,13 +473,19 @@ private GameTileBuffer[] CheckAutotile(int x, int y, MapBase[,] surroundingMaps) var updated = new List(); // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator - foreach (var layer in Options.Instance.MapOpts.Layers.All) + foreach (var layer in _layersAll) { + if (!Layers.TryGetValue(layer, out var layerTiles)) + { + continue; + } + if (!Autotiles.UpdateAutoTile( x, y, layer, - surroundingMaps + surroundingMaps, + layerTiles )) { continue; @@ -445,18 +497,12 @@ private GameTileBuffer[] CheckAutotile(int x, int y, MapBase[,] surroundingMaps) continue; } - if (!Layers.TryGetValue(layer, out var layerTiles)) - { - continue; - } - var tile = layerTiles[x, y]; - if (tile.TilesetTexture == default) + if (tile.TilesetTexture is not GameTexture tilesetTexture) { continue; } - var tilesetTexture = (GameTexture)tile.TilesetTexture; if (tile.X < 0 || tile.Y < 0) { continue; @@ -469,6 +515,11 @@ private GameTileBuffer[] CheckAutotile(int x, int y, MapBase[,] surroundingMaps) } var tilesetPlatformTexture = tilesetTexture.GetTexture(); + if (tilesetPlatformTexture == null) + { + continue; + } + if (!tileBuffer.TryGetValue(tilesetPlatformTexture, out var tileBuffersForTexture)) { continue; @@ -546,35 +597,49 @@ private GameTileBuffer[] CheckAutotile(int x, int y, MapBase[,] surroundingMaps) //Helper Functions public MapBase[,] GenerateAutotileGrid() { - var mapBase = new MapBase[3, 3]; - if (Globals.MapGrid != null && Globals.GridMaps.Contains(Id)) + var generatedGrid = new MapBase[3, 3]; + if (Globals.MapGrid is {} mapGrid && Globals.GridMaps.Contains(Id)) { - for (var x = -1; x <= 1; x++) + var gridX = GridX; + var gridY = GridY; + + var lowX = Math.Max(0, GridX - 1); + var lowY = Math.Max(0, GridY - 1); + var highX = Math.Min(Globals.MapGridWidth - 1, gridX + 1); + var highY = Math.Min(Globals.MapGridHeight - 1, gridY + 1); + for (var x = lowX; x <= highX; x++) { - for (var y = -1; y <= 1; y++) + for (var y = lowY; y <= highY; y++) { - var x1 = GridX + x; - var y1 = GridY + y; - if (x1 >= 0 && y1 >= 0 && x1 < Globals.MapGridWidth && y1 < Globals.MapGridHeight) + MapBase targetMap; + if (x == gridX && y == gridY) { - if (x == 0 && y == 0) - { - mapBase[x + 1, y + 1] = this; - } - else - { - mapBase[x + 1, y + 1] = Lookup.Get(Globals.MapGrid[x1, y1]); - } + targetMap = this; + } + else + { + targetMap = Lookup.Get(mapGrid[x, y]); + } + + var targetX = x + 1 - gridX; + var targetY = y + 1 - gridY; + try + { + generatedGrid[targetX, targetY] = targetMap; + } + catch + { + targetX.ToString(); } } } } else { - Debug.WriteLine("Returning null mapgrid for map " + Name); + Debug.WriteLine($"Returning null mapgrid for map {Name}"); } - return mapBase; + return generatedGrid; } /// @@ -596,9 +661,9 @@ private void UpdateMapAttributes() return; } - for (var y = 0; y < _height; y++) + for (var y = 0; y < _mapHeight; y++) { - for (var x = 0; x < _width; x++) + for (var x = 0; x < _mapWidth; x++) { var mapAttribute = mapAttributes[x, y]; if (mapAttribute == default) @@ -676,9 +741,9 @@ private void ClearMapAttributes() public void CreateMapSounds() { ClearAttributeSounds(); - for (var x = 0; x < _width; ++x) + for (var x = 0; x < _mapWidth; ++x) { - for (var y = 0; y < _height; ++y) + for (var y = 0; y < _mapHeight; ++y) { var attribute = Attributes?[x, y]; if (attribute?.Type != MapAttributeType.Sound) @@ -749,8 +814,9 @@ public void BuildVBOs() // () => // { var startVbo = DateTime.UtcNow; + var bufferCount = 0; Dictionary buffers = []; - foreach (var layer in Options.Instance.MapOpts.Layers.All) + foreach (var layer in _layersAll) { var layerBuffers = DrawMapLayer(layer, X, Y); if (layerBuffers == default) @@ -758,6 +824,7 @@ public void BuildVBOs() continue; } + bufferCount += layerBuffers.Aggregate(0, (sum, lb) => sum + lb.Length); buffers[layer] = layerBuffers; for (var animationFrameIndex = 0; animationFrameIndex < MapAnimationFrames; animationFrameIndex++) { @@ -771,7 +838,7 @@ public void BuildVBOs() var endVbo = DateTime.UtcNow; var elapsedVbo = endVbo - startVbo; - Log.Info($"Built VBO for map instance {Id} in {elapsedVbo.TotalMilliseconds}ms"); + Log.Info($"Built VBO for map instance {Id} in {elapsedVbo.TotalMilliseconds}ms ({Name}) ({bufferCount} buffers)"); // lock (mTileBuffers) // { @@ -789,7 +856,7 @@ public void BuildVBOs() public void DestroyVBOs() { - foreach (var layer in Options.Instance.MapOpts.Layers.All) + foreach (var layer in _layersAll) { if (_tileBuffersPerTexturePerLayer.Remove(layer, out var tileBuffersPerTextureForLayer)) { @@ -870,8 +937,8 @@ public void DrawItemsAndLights() foreach (var (key, tileItems) in MapItems) { // Calculate tile coordinates. - var tileX = key % _width; - var tileY = (int)Math.Floor(key / (float)_width); + var tileX = key % _mapWidth; + var tileY = (int)Math.Floor(key / (float)_mapWidth); // Loop through this in reverse to match client/server display and pick-up order. for (var index = tileItems.Count - 1; index >= 0; index--) @@ -934,7 +1001,7 @@ public void DrawItemNames() { // Apparently it is! Do we have any items to render here? var tileItems = new List(); - if (MapItems.TryGetValue(y * _width + x, out tileItems)) + if (MapItems.TryGetValue(y * _mapWidth + x, out tileItems)) { var baseOffset = 0; // Loop through this in reverse to match client/server display and pick-up order. @@ -975,15 +1042,6 @@ public void DrawItemNames() } } - private readonly int _width = Options.Instance.MapOpts.MapWidth; - private readonly int _height = Options.Instance.MapOpts.MapHeight; - private readonly int _tileWidth = Options.Instance.MapOpts.TileWidth; - private readonly int _tileHeight = Options.Instance.MapOpts.TileHeight; - private readonly int _tileHalfWidth = Options.Instance.MapOpts.TileWidth / 2; - private readonly int _tileHalfHeight = Options.Instance.MapOpts.TileHeight / 2; - private int _gridY; - private int _gridX; - private void DrawAutoTile( string layerName, int destX, @@ -1009,14 +1067,9 @@ private void DrawAutoTile( layerTile = layerTiles[x, y]; } - if (layerAutoTile == null) + if (layerAutoTile == null && !Autotiles.TryGetAutoTileForLayer(layerName, x, y, out layerAutoTile)) { - if (!Autotiles.Layers.TryGetValue(layerName, out var layerAutoTiles)) - { - return; - } - - layerAutoTile = layerAutoTiles[x, y]; + return; } var quarterTile = layerAutoTile.QuarterTile[quarterNum]; @@ -1080,19 +1133,27 @@ private void DrawAutoTile( { if (!Layers.TryGetValue(layerName, out var layerTiles)) { + Log.Error($"[{Id}][{Name}] No tiles in layer '{layerName}'"); + return null; + } + + if (Autotiles == default) + { + Log.Error($"[{Id}] {nameof(Autotiles)} is null {nameof(IsLoaded)}={IsLoaded}"); return null; } - if (!Autotiles.Layers.TryGetValue(layerName, out var layerAutoTiles)) + if (!Autotiles.TryGetAutoTilesForLayer(layerName, out var layerAutoTiles)) { + Log.Error($"[{Id}][{Name}] No auto tiles in layer '{layerName}'"); return null; } var tileBuffersPerTexture = new Dictionary(); - for (var x = 0; x < _width; x++) + for (var x = 0; x < _mapWidth; x++) { - for (var y = 0; y < _height; y++) + for (var y = 0; y < _mapHeight; y++) { var layerTile = layerTiles[x, y]; if (layerTile.TilesetTexture is not GameTexture tilesetTexture) @@ -1282,8 +1343,8 @@ public void DrawFog() : Math.Max(0, mCurFogIntensity - elapsedTime / 2000f); // Calculate the number of times the fog texture needs to be drawn to cover the map area. - var xCount = _width * _tileWidth * 3 / fogTex.Width; - var yCount = _height * _tileHeight * 3 / fogTex.Height; + var xCount = _mapWidth * _tileWidth * 3 / fogTex.Width; + var yCount = _mapHeight * _tileHeight * 3 / fogTex.Height; // Update the fog texture's position based on its speed and elapsed time. mFogCurrentX += elapsedTime / 1000f * FogXSpeed * 2; @@ -1304,8 +1365,8 @@ public void DrawFog() Graphics.DrawGameTexture( fogTex, new FloatRect(0, 0, fogTex.Width, fogTex.Height), new FloatRect( - X - _width * _tileWidth * 1f + x * fogTex.Width + drawX, - Y - _height * _tileHeight * 1f + y * fogTex.Height + drawY, + X - _mapWidth * _tileWidth * 1f + x * fogTex.Width + drawX, + Y - _mapHeight * _tileHeight * 1f + y * fogTex.Height + drawY, fogTex.Width, fogTex.Height ), new Color((byte)(FogTransparency * mCurFogIntensity), 255, 255, 255) ); @@ -1472,8 +1533,8 @@ public void CompareEffects(IMapInstance oldMap) float dy = Y - oldMap.Y; // Update fog position based on displacement. - mFogCurrentX += (_tileWidth * _width % fogTex.Width) * -Math.Sign(dx); - mFogCurrentY += (_tileHeight * _height % fogTex.Height) * -Math.Sign(dy); + mFogCurrentX += (_tileWidth * _mapWidth % fogTex.Width) * -Math.Sign(dx); + mFogCurrentY += (_tileHeight * _mapHeight % fogTex.Height) * -Math.Sign(dy); // Reset fog intensity of old map. tempMap.mCurFogIntensity = 0; @@ -1573,7 +1634,7 @@ public override void Delete() public void Dispose(bool prep = true, bool killentities = true) { IsLoaded = false; - OnMapLoaded -= HandleMapLoaded; + MapLoaded -= HandleMapLoaded; foreach (var evt in mEvents) { diff --git a/Intersect.Client.Core/Networking/PacketHandler.cs b/Intersect.Client.Core/Networking/PacketHandler.cs index 952ba7c5c5..b797447e96 100644 --- a/Intersect.Client.Core/Networking/PacketHandler.cs +++ b/Intersect.Client.Core/Networking/PacketHandler.cs @@ -190,7 +190,7 @@ public void HandlePacket(IPacketSender packetSender, MapAreaPacket packet) { foreach (var map in packet.Maps) { - HandleMap(packetSender, map); + HandleMap(map); } } @@ -227,12 +227,14 @@ public void HandlePacket(IPacketSender packetSender, MapAreaIdsPacket packet) foreach (var cachedMap in loadedCachedMaps) { - HandleMap(packetSender, cachedMap, skipSave: true); + HandleMap(cachedMap, skipSave: true); } } - private void HandleMap(IPacketSender packetSender, MapPacket packet, bool skipSave = false) + internal static void HandleMap(MapPacket packet, bool skipSave = false) { + var startHandleMap = DateTime.UtcNow; + var mapId = packet.MapId; if (!skipSave) @@ -251,55 +253,150 @@ private void HandleMap(IPacketSender packetSender, MapPacket packet, bool skipSa } } + var endMapCache = DateTime.UtcNow; + MapInstance.UpdateMapRequestTime(packet.MapId); - if (MapInstance.TryGet(mapId, out var mapInstance)) + var endUpdateMapRequestTime = DateTime.UtcNow; + + if (MapInstance.TryGet(mapId, out var map)) { - if (packet.Revision == mapInstance.Revision) + if (packet.Revision == map.Revision) { return; } - mapInstance.Dispose(false, false); + map.Dispose(false, false); + } + + var endMapTryGet = DateTime.UtcNow; + + map = new MapInstance(mapId); + MapInstance.Lookup.Set(mapId, map); + + var endMapLookupSet = DateTime.UtcNow; + + DateTime endMapLoad; + DateTime endMapLoadTileData; + DateTime endSetAttributeData; + DateTime endCreateMapSounds; + DateTime endPlayMusic; + DateTime endGridXYCameraHolds; + DateTime endInitAutotiles; + DateTime endPendingEvents; + + if (!map.Lock.TryAcquireLock("HandleMap packet handler", out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock from HandleMap packet handler"); } - mapInstance = new MapInstance(mapId); - MapInstance.Lookup.Set(mapId, mapInstance); - lock (mapInstance.MapLock) + using (lockRef) { - mapInstance.Load(packet.Data); - mapInstance.LoadTileData(packet.TileData); - mapInstance.AttributeData = packet.AttributeData; - mapInstance.CreateMapSounds(); + map.Load(packet.Data, markLoaded: false); + endMapLoad = DateTime.UtcNow; + + map.LoadTileData(packet.TileData); + endMapLoadTileData = DateTime.UtcNow; + + map.AttributeData = packet.AttributeData; + endSetAttributeData = DateTime.UtcNow; + + map.CreateMapSounds(); + endCreateMapSounds = DateTime.UtcNow; + if (mapId == Globals.Me.MapId) { - Audio.PlayMusic(mapInstance.Music, ClientConfiguration.Instance.MusicFadeTimer, ClientConfiguration.Instance.MusicFadeTimer, true); + Audio.PlayMusic(map.Music, ClientConfiguration.Instance.MusicFadeTimer, ClientConfiguration.Instance.MusicFadeTimer, true); } + endPlayMusic = DateTime.UtcNow; + + map.GridX = packet.GridX; + map.GridY = packet.GridY; + map.CameraHolds = packet.CameraHolds; + endGridXYCameraHolds = DateTime.UtcNow; - mapInstance.GridX = packet.GridX; - mapInstance.GridY = packet.GridY; - mapInstance.CameraHolds = packet.CameraHolds; - mapInstance.Autotiles.InitAutotiles(mapInstance.GenerateAutotileGrid()); + map.Autotiles.InitAutotiles(map.GenerateAutotileGrid()); - if (Globals.PendingEvents.ContainsKey(mapId)) + endInitAutotiles = DateTime.UtcNow; + + map.MarkLoaded(); + + if (Globals.PendingEvents.TryGetValue(mapId, out var pendingEventsForMap)) { - foreach (var evt in Globals.PendingEvents[mapId]) + Log.Debug($"Received map {map.Name} ({mapId}) has {pendingEventsForMap.Count} pending events"); + + foreach (var (eventId, pendingEvent) in pendingEventsForMap) { - mapInstance.AddEvent(evt.Key, evt.Value); + map.AddEvent(eventId, pendingEvent); } - Globals.PendingEvents[mapId].Clear(); - } - } - - MapInstance.OnMapLoaded?.Invoke(mapInstance); + pendingEventsForMap.Clear(); + } + endPendingEvents = DateTime.UtcNow; + } + + var endMapLock = DateTime.UtcNow; + + MapInstance.DoMapLoaded(map); + + var endDoMapLoadedInvocation = DateTime.UtcNow; + + var elapsedHandleMap = endDoMapLoadedInvocation - startHandleMap; + var elapsedMapCache = endMapCache - startHandleMap; + var elapsedUpdateMapRequestTime = endUpdateMapRequestTime - endMapCache; + var elapsedMapTryGet = endMapTryGet - endUpdateMapRequestTime; + var elapsedMapLookupSet = endMapLookupSet - endMapTryGet; + var elapsedMapLock = endMapLock - endMapLookupSet; + var elapsedDoMapLoadedInvocation = endDoMapLoadedInvocation - endMapLock; + var elapsedMapLoad = endMapLoad - endMapLookupSet; + var elapsedMapLoadTileData = endMapLoadTileData - endMapLoad; + var elapsedSetAttributeData = endSetAttributeData - endMapLoadTileData; + var elapsedCreateMapSounds = endCreateMapSounds - endSetAttributeData; + var elapsedPlayMusic = endPlayMusic - endCreateMapSounds; + var elapsedGridXYCameraHolds = endGridXYCameraHolds - endPlayMusic; + var elapsedInitAutotiles = endInitAutotiles - endGridXYCameraHolds; + var elapsedPendingEvents = endPendingEvents - endInitAutotiles; + + Log.Debug($""" + [HandleMap] Received map {packet.MapId} ({map.Name}) + - Full method took {elapsedHandleMap.TotalMilliseconds}ms + - Map caching took {elapsedMapCache.TotalMilliseconds}ms + - Update Map Request Time took {elapsedUpdateMapRequestTime.TotalMilliseconds}ms + - Map Try Get took {elapsedMapTryGet.TotalMilliseconds}ms + - Map Lookup Set took {elapsedMapLookupSet.TotalMilliseconds}ms + - Map lock took {elapsedMapLock.TotalMilliseconds}ms + - Map load took {elapsedMapLoad.TotalMilliseconds}ms + - Loading tile data took {elapsedMapLoadTileData.TotalMilliseconds}ms + - Setting attribute data took {elapsedSetAttributeData.TotalMilliseconds}ms + - Creating map sounds took {elapsedCreateMapSounds.TotalMilliseconds}ms + - Starting map music (if it's the current map) took {elapsedPlayMusic.TotalMilliseconds}ms + - Settings GridX, GridY, and CameraHolds took {elapsedGridXYCameraHolds.TotalMilliseconds}ms + - Init autotiles took {elapsedInitAutotiles.TotalMilliseconds}ms + - Pending events took {elapsedPendingEvents.TotalMilliseconds}ms + - MapLoaded event invocation took {elapsedDoMapLoadedInvocation.TotalMilliseconds}ms + """); } //MapPacket public void HandlePacket(IPacketSender packetSender, MapPacket packet) { - HandleMap(packetSender, packet); - Player.FetchNewMaps(); + if (Globals.Me?.MapInstance is {} currentMap) + { + if (currentMap.Id != packet.MapId) + { + Task.Run(DoHandleMap); + return; + } + } + + DoHandleMap(); + return; + + void DoHandleMap() + { + HandleMap(packet); + Player.FetchNewMaps(); + } } //PlayerEntityPacket @@ -376,30 +473,21 @@ public void HandlePacket(IPacketSender packetSender, ProjectileEntityPacket pack //EventEntityPacket public void HandlePacket(IPacketSender packetSender, EventEntityPacket packet) { - var map = MapInstance.Get(packet.MapId); - if (map != null) + if (MapInstance.TryGet(packet.MapId, out var map)) { - map?.AddEvent(packet.EntityId, packet); + Log.Debug($"Received event entity '{packet.Name}' ({packet.EntityId}) for map {map.Name} ({map.Id})"); + map.AddEvent(packet.EntityId, packet); } else { - var dict = Globals.PendingEvents.ContainsKey(packet.MapId) - ? Globals.PendingEvents[packet.MapId] - : new Dictionary(); - - if (dict.ContainsKey(packet.EntityId)) + Log.Debug($"Received event entity '{packet.Name}' ({packet.EntityId}) for not-yet-loaded map {packet.MapId}"); + if (!Globals.PendingEvents.TryGetValue(packet.MapId, out var pendingEventsForMap)) { - dict[packet.EntityId] = packet; - } - else - { - dict.Add(packet.EntityId, packet); + pendingEventsForMap = []; + Globals.PendingEvents[packet.MapId] = pendingEventsForMap; } - if (!Globals.PendingEvents.ContainsKey(packet.MapId)) - { - Globals.PendingEvents.Add(packet.MapId, dict); - } + pendingEventsForMap[packet.EntityId] = packet; } } diff --git a/Intersect.Client.Core/Networking/PacketSender.cs b/Intersect.Client.Core/Networking/PacketSender.cs index 4483e2c8c2..9d0f370840 100644 --- a/Intersect.Client.Core/Networking/PacketSender.cs +++ b/Intersect.Client.Core/Networking/PacketSender.cs @@ -6,9 +6,16 @@ using Intersect.Enums; using Intersect.Framework; using Intersect.GameObjects.Maps; +using Intersect.Logging; using Intersect.Models; +using Intersect.Network; using Intersect.Network.Packets.Client; +using Intersect.Network.Packets.Server; using AdminAction = Intersect.Admin.Actions.AdminAction; +using ChatMsgPacket = Intersect.Network.Packets.Client.ChatMsgPacket; +using PartyInvitePacket = Intersect.Network.Packets.Client.PartyInvitePacket; +using PingPacket = Intersect.Network.Packets.Client.PingPacket; +using TradeRequestPacket = Intersect.Network.Packets.Client.TradeRequestPacket; namespace Intersect.Client.Networking; @@ -46,6 +53,7 @@ public static void SendNeedMap(params ObjectCacheKey[] cacheKeys) return; } + Log.Debug($"Requesting maps via cache keys:\n{string.Join("\n", validMapCacheKeys.Select(k => $"\t{k}"))}"); Network.SendPacket(new GetObjectData(validMapCacheKeys)); MapInstance.UpdateMapRequestTime(validMapCacheKeys.Select(cacheKey => cacheKey.Id.Guid).ToArray()); } @@ -61,21 +69,79 @@ public static void SendNeedMap(params Guid[] mapIds) return; } - Network.SendPacket( - new GetObjectData( - validMapIds.Select(id => new ObjectCacheKey(new Id(id))).ToArray() - ) + // TODO: Background all of this? + List> cacheKeys = new(validMapIds.Length); + List loadedCachedMaps = new(validMapIds.Length); + foreach (var mapId in validMapIds) + { + if (ObjectDataDiskCache.TryLoad(mapId, out var cacheData)) + { + ObjectCacheKey cacheKey = new(cacheData.Id); + var deserializedCachedPacket = MessagePacker.Instance.Deserialize(cacheData.Data, silent: true); + if (deserializedCachedPacket != default) + { + cacheKey = new ObjectCacheKey( + cacheData.Id, + cacheData.Checksum, + cacheData.Version + ); + cacheKeys.Add(cacheKey); + loadedCachedMaps.Add(deserializedCachedPacket); + continue; + } + + Log.Warn($"Failed to deserialized cached data for {cacheKey}, will fetch again"); + } + + cacheKeys.Add(new ObjectCacheKey(new Id(mapId))); + } + + SendNeedMap(cacheKeys.ToArray()); + + Task.Run( + () => + { + foreach (var cachedMap in loadedCachedMaps) + { + PacketHandler.HandleMap(cachedMap, skipSave: true); + } + } ); - MapInstance.UpdateMapRequestTime(validMapIds); } + // public static void SendNeedMap(params Guid[] mapIds) + // { + // var validMapIds = mapIds.Where( + // mapId => mapId != default && !MapInstance.TryGet(mapId, out _) && MapInstance.MapNotRequested(mapId) + // ) + // .ToArray(); + // if (validMapIds.Length < 1) + // { + // return; + // } + // + // Log.Debug($"Requesting maps via IDs:\n{string.Join("\n", mapIds.Select(k => $"\t{k}"))}"); + // Network.SendPacket( + // new GetObjectData( + // validMapIds.Select(id => new ObjectCacheKey(new Id(id))).ToArray() + // ) + // ); + // MapInstance.UpdateMapRequestTime(validMapIds); + // } + public static void SendNeedMapForGrid(MapInstance? mapInstance = default) { - if (mapInstance == default && !MapInstance.TryGet(Globals.Me.MapId, out mapInstance)) + if (Globals.Me is not { } player) { return; } + if (mapInstance == default && !MapInstance.TryGet(player.MapId, out mapInstance)) + { + SendNeedMap(player.MapId); + return; + } + var gridX = mapInstance.GridX; var gridY = mapInstance.GridY; var minX = Math.Max(0, gridX - 1); @@ -497,7 +563,7 @@ public static void SendTransferGuild(Guid id) { Network.SendPacket(new UpdateGuildMemberPacket(id, null, Enums.GuildMemberUpdateAction.Transfer)); } - + public static void SendClosePicture(Guid eventId) { if (eventId != Guid.Empty) diff --git a/Intersect.Client.Framework/GenericClasses/FloatRect.cs b/Intersect.Client.Framework/GenericClasses/FloatRect.cs index 11c0aefd01..8865f48ce7 100644 --- a/Intersect.Client.Framework/GenericClasses/FloatRect.cs +++ b/Intersect.Client.Framework/GenericClasses/FloatRect.cs @@ -179,6 +179,17 @@ public bool Contains(Point pt) ); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FloatRect operator /(FloatRect lhs, Point rhs) + { + return new FloatRect( + lhs.X / rhs.X, + lhs.Y / rhs.Y, + lhs.Width / rhs.X, + lhs.Height / rhs.Y + ); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static FloatRect operator /(float lhs, FloatRect rhs) => rhs / lhs; diff --git a/Intersect.Client.Framework/Maps/IMapInstance.cs b/Intersect.Client.Framework/Maps/IMapInstance.cs index fac59388b2..1f94cf5f04 100644 --- a/Intersect.Client.Framework/Maps/IMapInstance.cs +++ b/Intersect.Client.Framework/Maps/IMapInstance.cs @@ -29,7 +29,7 @@ public interface IMapInstance void AddTileAnimation(Guid animId, int tileX, int tileY, Direction dir = Direction.None, IEntity? owner = null); void CompareEffects(IMapInstance oldMap); bool InView(); - void Load(string json); + void Load(string json, bool markLoaded = true); void LoadTileData(byte[] packet); void Update(bool isLocal); } diff --git a/Intersect.Editor/Core/Graphics.cs b/Intersect.Editor/Core/Graphics.cs index c56c57c45f..0450421df8 100644 --- a/Intersect.Editor/Core/Graphics.cs +++ b/Intersect.Editor/Core/Graphics.cs @@ -226,7 +226,12 @@ public static void Render() if (map != null) { - lock (map.MapLock) + if (!map.Lock.TryAcquireLock(out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock"); + } + + using (lockRef) { //Draw this map DrawMap( @@ -265,7 +270,12 @@ public static void Render() continue; } - lock (map.MapLock) + if (!map.Lock.TryAcquireLock(out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock"); + } + + using (lockRef) { DrawMapAttributes( map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, @@ -294,15 +304,22 @@ public static void Render() if (x >= 0 && x < Globals.MapGrid.GridWidth && y >= 0 && y < Globals.MapGrid.GridHeight) { var map = MapInstance.Get(Globals.MapGrid.Grid[x, y].MapId); - if (map != null) + if (map == null) { - lock (map.MapLock) - { - DrawMapEvents( - map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, - false, null - ); - } + continue; + } + + if (!map.Lock.TryAcquireLock(out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock"); + } + + using (lockRef) + { + DrawMapEvents( + map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, + false, null + ); } } } @@ -316,16 +333,23 @@ public static void Render() if (x >= 0 && x < Globals.MapGrid.GridWidth && y >= 0 && y < Globals.MapGrid.GridHeight) { var map = MapInstance.Get(Globals.MapGrid.Grid[x, y].MapId); - if (map != null) + if (map == null) { - lock (map.MapLock) - { - //Draw this map - DrawMap( - map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, - false, 1, null - ); - } + continue; + } + + if (!map.Lock.TryAcquireLock(out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock"); + } + + using (lockRef) + { + //Draw this map + DrawMap( + map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, + false, 1, null + ); } } } @@ -339,15 +363,22 @@ public static void Render() if (x >= 0 && x < Globals.MapGrid.GridWidth && y >= 0 && y < Globals.MapGrid.GridHeight) { var map = MapInstance.Get(Globals.MapGrid.Grid[x, y].MapId); - if (map != null) + if (map == null) { - lock (map.MapLock) - { - DrawMapAttributes( - map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, - false, null, true, false - ); - } + continue; + } + + if (!map.Lock.TryAcquireLock(out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock"); + } + + using (lockRef) + { + DrawMapAttributes( + map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, + false, null, true, false + ); } } } @@ -474,11 +505,25 @@ RenderTarget2D target break; } + if (!map.Autotiles.TryGetAutoTileForLayer( + layerName, + x, + y, + out var autoTile + )) + { + return; + } + DrawTexture( - texture, destX, destY, - (int) map.Autotiles.Layers[layerName][x, y].QuarterTile[quarterNum].X + xOffset, - (int) map.Autotiles.Layers[layerName][x, y].QuarterTile[quarterNum].Y + yOffset, - Options.TileWidth / 2, Options.TileHeight / 2, target + texture, + destX, + destY, + autoTile.QuarterTile[quarterNum].X + xOffset, + autoTile.QuarterTile[quarterNum].Y + yOffset, + Options.TileWidth / 2, + Options.TileHeight / 2, + target ); } @@ -574,9 +619,14 @@ RenderTarget2D renderTarget2D tmpMap = TilePreviewStruct; if (TilePreviewUpdated || TilePreviewStruct == null) { - if (Globals.CurrentMap != null) + if (Globals.CurrentMap is {} currentMap) { - lock (Globals.CurrentMap.MapLock) + if (!currentMap.Lock.TryAcquireLock(out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock"); + } + + using (lockRef) { TilePreviewStruct = new MapInstance(Globals.CurrentMap); } @@ -769,15 +819,25 @@ RenderTarget2D renderTarget2D GameContentManager.TextureType.Tileset, tilesetObj.Name ); - if (tilesetTex == null || tmpMap.Autotiles == null || tmpMap.Autotiles.Layers == null) + if (tilesetTex == null || tmpMap.Autotiles == null) { continue; } - if (tmpMap.Autotiles.Layers[drawLayer][x, y].RenderState != + if (!tmpMap.Autotiles.TryGetAutoTileForLayer( + drawLayer, + x, + y, + out var autoTile + )) + { + continue; + } + + if (autoTile.RenderState != MapAutotiles.RENDER_STATE_NORMAL) { - if (tmpMap.Autotiles.Layers[drawLayer][x, y].RenderState != + if (autoTile.RenderState != MapAutotiles.RENDER_STATE_AUTOTILE) { continue; @@ -1651,7 +1711,12 @@ public static Bitmap ScreenShotMap() continue; } - lock (map.MapLock) + if (!map.Lock.TryAcquireLock(out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock"); + } + + using (lockRef) { //Draw this map DrawMap( @@ -1670,25 +1735,32 @@ public static Bitmap ScreenShotMap() if (x >= 0 && x < Globals.MapGrid.GridWidth && y >= 0 && y < Globals.MapGrid.GridHeight) { var map = MapInstance.Get(Globals.MapGrid.Grid[x, y].MapId); - if (map != null) + if (map == null) { - lock (map.MapLock) - { - DrawMapAttributes( - map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, true, - sScreenShotRenderTexture, false, false - ); + continue; + } - DrawMapAttributes( - map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, true, - sScreenShotRenderTexture, false, true - ); + if (!map.Lock.TryAcquireLock(out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock"); + } - DrawMapAttributes( - map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, true, - sScreenShotRenderTexture, true, true - ); - } + using (lockRef) + { + DrawMapAttributes( + map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, true, + sScreenShotRenderTexture, false, false + ); + + DrawMapAttributes( + map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, true, + sScreenShotRenderTexture, false, true + ); + + DrawMapAttributes( + map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, true, + sScreenShotRenderTexture, true, true + ); } } } @@ -1702,15 +1774,22 @@ public static Bitmap ScreenShotMap() if (x >= 0 && x < Globals.MapGrid.GridWidth && y >= 0 && y < Globals.MapGrid.GridHeight) { var map = MapInstance.Get(Globals.MapGrid.Grid[x, y].MapId); - if (map != null) + if (map == null) { - lock (map.MapLock) - { - DrawMapEvents( - map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, - true, sScreenShotRenderTexture - ); - } + continue; + } + + if (!map.Lock.TryAcquireLock(out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock"); + } + + using (lockRef) + { + DrawMapEvents( + map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, + true, sScreenShotRenderTexture + ); } } } @@ -1724,16 +1803,23 @@ public static Bitmap ScreenShotMap() if (x >= 0 && x < Globals.MapGrid.GridWidth && y >= 0 && y < Globals.MapGrid.GridHeight) { var map = MapInstance.Get(Globals.MapGrid.Grid[x, y].MapId); - if (map != null) + if (map == null) { - lock (map.MapLock) - { - //Draw this map - DrawMap( - map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, true, 1, - sScreenShotRenderTexture - ); - } + continue; + } + + if (!map.Lock.TryAcquireLock(out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock"); + } + + using (lockRef) + { + //Draw this map + DrawMap( + map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, true, 1, + sScreenShotRenderTexture + ); } } } @@ -1747,15 +1833,22 @@ public static Bitmap ScreenShotMap() if (x >= 0 && x < Globals.MapGrid.GridWidth && y >= 0 && y < Globals.MapGrid.GridHeight) { var map = MapInstance.Get(Globals.MapGrid.Grid?[x, y]?.MapId ?? Guid.Empty); - if (map != null) + if (map == null) { - lock (map.MapLock) - { - DrawMapAttributes( - map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, true, - sScreenShotRenderTexture, true, false - ); - } + continue; + } + + if (!map.Lock.TryAcquireLock(out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock"); + } + + using (lockRef) + { + DrawMapAttributes( + map, x - Globals.CurrentMap.MapGridX, y - Globals.CurrentMap.MapGridY, true, + sScreenShotRenderTexture, true, false + ); } } } @@ -1763,7 +1856,12 @@ public static Bitmap ScreenShotMap() } else { - lock (Globals.CurrentMap.MapLock) + if (!Globals.CurrentMap.Lock.TryAcquireLock(out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock"); + } + + using (lockRef) { //Draw this map DrawMap(Globals.CurrentMap, 0, 0, true, 0, sScreenShotRenderTexture); diff --git a/Intersect.Editor/Maps/MapInstance.cs b/Intersect.Editor/Maps/MapInstance.cs index 945c0b4561..bb5f9b62db 100644 --- a/Intersect.Editor/Maps/MapInstance.cs +++ b/Intersect.Editor/Maps/MapInstance.cs @@ -26,7 +26,12 @@ public partial class MapInstance : MapBase, IGameObject public MapInstance(Guid id) : base(id) { - lock (MapLock) + if (!Lock.TryAcquireLock(out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock"); + } + + using (lockRef) { Autotiles = new MapAutotiles(this); } @@ -34,7 +39,12 @@ public MapInstance(Guid id) : base(id) public MapInstance(MapBase mapStruct) : base(mapStruct) { - lock (MapLock) + if (!Lock.TryAcquireLock(out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock"); + } + + using (lockRef) { Autotiles = new MapAutotiles(this); if (typeof(MapInstance) == mapStruct.GetType()) @@ -56,7 +66,12 @@ public MapInstance(MapBase mapStruct) : base(mapStruct) public void Load(string mapJson, bool import = false, bool clearEvents = true) { - lock (MapLock) + if (!Lock.TryAcquireLock(out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock"); + } + + using (lockRef) { var up = Up; var down = Down; @@ -88,7 +103,12 @@ public void Load(string mapJson, bool import = false, bool clearEvents = true) public void LoadTileData(byte[] packet) { - lock (MapLock) + if (!Lock.TryAcquireLock(out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock"); + } + + using (lockRef) { Layers = JsonConvert.DeserializeObject>(LZ4.UnPickleString(packet), mJsonSerializerSettings); foreach (var layer in Options.Instance.MapOpts.Layers.All) @@ -271,7 +291,12 @@ public void Update() public void InitAutotiles() { - lock (MapLock) + if (!Lock.TryAcquireLock(out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock"); + } + + using (lockRef) { Autotiles.InitAutotiles(GenerateAutotileGrid()); } diff --git a/Intersect.Editor/Networking/Network.cs b/Intersect.Editor/Networking/Network.cs index c81416d3d0..de38bd7ac9 100644 --- a/Intersect.Editor/Networking/Network.cs +++ b/Intersect.Editor/Networking/Network.cs @@ -136,7 +136,7 @@ public static void InitNetwork() /// Interval between status pings in ms (e.g. full, bad version, etc.) /// #if DEBUG - private const long ServerStatusPingInterval = 15_000; + private const long ServerStatusPingInterval = 1_000; #else private const long ServerStatusPingInterval = 15_000; #endif diff --git a/Intersect.Editor/Networking/PacketHandler.cs b/Intersect.Editor/Networking/PacketHandler.cs index cf20a7676d..ea50782f6a 100644 --- a/Intersect.Editor/Networking/PacketHandler.cs +++ b/Intersect.Editor/Networking/PacketHandler.cs @@ -181,11 +181,19 @@ public void HandlePacket(IPacketSender packetSender, MapPacket packet) map.SaveStateAsUnchanged(); map.InitAutotiles(); map.UpdateAdjacentAutotiles(); - if (MapInstance.Get(packet.MapId) != null) + if (MapInstance.TryGet(packet.MapId, out var existingMap)) { - lock (MapInstance.Get(packet.MapId).MapLock) + if (!existingMap.Lock.TryAcquireLock( + "Editor HandlePacket(IPacketSender, MapPacket)", + out var lockRef + )) + { + throw new InvalidOperationException("Failed to acquire map instance lock from Editor HandlePacket(IPacketSender, MapPacket)"); + } + + using (lockRef) { - if (Globals.CurrentMap == MapInstance.Get(packet.MapId)) + if (Globals.CurrentMap == existingMap) { Globals.CurrentMap = map; } diff --git a/Intersect.Server.Core/Database/DbInterface.cs b/Intersect.Server.Core/Database/DbInterface.cs index d0079606da..48cb94814b 100644 --- a/Intersect.Server.Core/Database/DbInterface.cs +++ b/Intersect.Server.Core/Database/DbInterface.cs @@ -1608,7 +1608,12 @@ public static void GenerateMapGrids() foreach (MapController map in MapController.Lookup.Values) { - lock (map.GetMapLock()) + if (!map.Lock.TryAcquireLock($"{nameof(DbInterface)}.{nameof(GenerateMapGrids)}()", out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock from DbInterface.GenerateMapGrids()"); + } + + using (lockRef) { var gridIndex = map.MapGrid; var grid = mapGrids[gridIndex]; diff --git a/Intersect.Server.Core/Entities/Events/EventPageInstance.cs b/Intersect.Server.Core/Entities/Events/EventPageInstance.cs index 163e27804b..bf8591850e 100644 --- a/Intersect.Server.Core/Entities/Events/EventPageInstance.cs +++ b/Intersect.Server.Core/Entities/Events/EventPageInstance.cs @@ -1,6 +1,7 @@ using Intersect.Enums; using Intersect.GameObjects; using Intersect.GameObjects.Events; +using Intersect.Logging; using Intersect.Network.Packets.Server; using Intersect.Server.Entities.Pathfinding; using Intersect.Server.Framework; @@ -237,9 +238,10 @@ public EventMovementSpeed MovementSpeed public void SendToPlayer() { - if (Player != null) + if (Player is {} player) { - PacketSender.SendEntityDataTo(Player, this); + Log.Debug($"Sending EventPageInstance '{Name}' to '{player.Name}' ({Id} to {player.Id})"); + PacketSender.SendEntityDataTo(player, this); } else { @@ -894,7 +896,7 @@ public bool ShouldDespawn(MapController map) return false; } - + protected override EntityItemSource? AsItemSource() { return null; diff --git a/Intersect.Server.Core/Entities/Player.cs b/Intersect.Server.Core/Entities/Player.cs index 851a46f37d..7c5419a0dd 100644 --- a/Intersect.Server.Core/Entities/Player.cs +++ b/Intersect.Server.Core/Entities/Player.cs @@ -6862,12 +6862,9 @@ public Event FindGlobalEventInstance(EventPageInstance en) public void SendEvents() { - foreach (var evt in EventLookup) + foreach (var (_, eventInstance) in EventLookup) { - if (evt.Value.PageInstance != null) - { - evt.Value.PageInstance.SendToPlayer(); - } + eventInstance.PageInstance?.SendToPlayer(); } } diff --git a/Intersect.Server.Core/Maps/MapController.cs b/Intersect.Server.Core/Maps/MapController.cs index 9a68d2de47..c788612f2f 100644 --- a/Intersect.Server.Core/Maps/MapController.cs +++ b/Intersect.Server.Core/Maps/MapController.cs @@ -64,7 +64,12 @@ public Guid[] SurroundingMapIds set { - lock (GetMapLock()) + if (!Lock.TryAcquireLock($"{nameof(MapController)}.set_{nameof(SurroundingMapIds)}()", out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock from MapController.set_SurroundingMapIds()"); + } + + using (lockRef) { mSurroundingMapIds = value; var surroundingMapsIdsWithSelf = new List(value); @@ -85,7 +90,12 @@ public MapController[] SurroundingMaps set { - lock (GetMapLock()) + if (!Lock.TryAcquireLock($"{nameof(MapController)}.set_{nameof(SurroundingMaps)}()", out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock from MapController.set_SurroundingMaps()"); + } + + using (lockRef) { mSurroundingMaps = value; var surroundingMapsWithSelf = new List(value); @@ -195,10 +205,10 @@ public static List GetSurroundingMapInstances(Guid mapControllerId, //GameObject Functions - public object GetMapLock() - { - return mMapLock; - } + // public object GetMapLock() + // { + // return mMapLock; + // } public override void Load(string json, bool keepCreationTime = true) { @@ -210,7 +220,12 @@ public override void Load(string json, bool keepCreationTime = true) /// public void Initialize() { - lock (mMapLock) + if (!Lock.TryAcquireLock($"{nameof(MapController)}.{nameof(Initialize)}()", out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock from MapController.Initialize()"); + } + + using (lockRef) { DespawnAllInstances(); RespawnAllInstances(); @@ -224,7 +239,12 @@ public void Initialize() /// The revision number public void Load(string json, int keepRevision = -1) { - lock (mMapLock) + if (!Lock.TryAcquireLock($"{nameof(MapController)}.{nameof(Load)}()", out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock from MapController.Load()"); + } + + using (lockRef) { CachedMapClientPacket = default; DespawnAllInstances(); @@ -419,7 +439,13 @@ public void RespawnAllInstances() public bool TryCreateInstance(Guid instanceId, out MapInstance newLayer, Player creator) { newLayer = null; - lock (GetMapLock()) + + if (!Lock.TryAcquireLock($"{nameof(MapController)}.{nameof(TryCreateInstance)}()", out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock from MapController.TryCreateInstance()"); + } + + using (lockRef) { if (!mInstances.ContainsKey(instanceId)) { @@ -465,7 +491,12 @@ public void RemoveEntityFromAllSurroundingMapsInInstance(Entity entity, Guid map /// The instance ID to dispose of public void DisposeInstanceWithId(Guid mapInstanceId) { - lock (GetMapLock()) + if (!Lock.TryAcquireLock($"{nameof(MapController)}.{nameof(DisposeInstanceWithId)}()", out var lockRef)) + { + throw new InvalidOperationException("Failed to acquire map instance lock from MapController.DisposeInstanceWithId()"); + } + + using (lockRef) { if (mInstances[mapInstanceId] != null) { @@ -528,7 +559,7 @@ public void DestroyOrphanedLayers() } TileData = LZ4.PickleString(JsonConvert.SerializeObject(Layers, Formatting.None, mJsonSerializerSettings)); Layers = null; - + } } diff --git a/Intersect.Server.Core/Maps/MapInstance.cs b/Intersect.Server.Core/Maps/MapInstance.cs index 9de9b65aa1..8d28889000 100644 --- a/Intersect.Server.Core/Maps/MapInstance.cs +++ b/Intersect.Server.Core/Maps/MapInstance.cs @@ -19,7 +19,7 @@ namespace Intersect.Server.Maps; /// -/// A exists to process map updates, but on a "layered" system, where a single map can be processing +/// A exists to process map updates, but on a "layered" system, where a single map can be processing /// differently for each instance that exists upon it. /// /// @@ -37,7 +37,7 @@ namespace Intersect.Server.Maps; /// /// /// Warps to a new map, or their has otherwise changed. -/// +/// /// /// Walks across a map boundary and fetches new maps from . /// @@ -85,7 +85,7 @@ public partial class MapInstance : IMapInstance /// /// An ID referring to which instance this processer belongs to. /// - /// Entities/Items/Etc with that share an with + /// Entities/Items/Etc with that share an with /// will be processed and fed packets by that processer. /// /// @@ -143,7 +143,7 @@ public partial class MapInstance : IMapInstance // Animations & Text private MapActionMessages mActionMessages = new MapActionMessages(); private MapAnimations mMapAnimations = new MapAnimations(); - + public MapInstance(MapController map, Guid mapInstanceId, Player creator) { mMapController = map; @@ -193,15 +193,6 @@ public object GetLock() return mMapProcessLock; } - /// - /// Gets our _parent's_ map lock. - /// - /// The parent map instance's map lock - public object GetControllerLock() - { - return mMapController.GetMapLock(); - } - public virtual void Dispose() { if (!IsDisposed) @@ -714,7 +705,7 @@ private void AddItem(IItemSource? source, MapItem item) } TileItems[item.TileIndex]?.TryAdd(item.UniqueId, item); - + MapHelper.Instance.InvokeItemAdded(source, item); } @@ -933,10 +924,10 @@ private void SpawnAttributeItem(int x, int y) var mapItemSource = new MapItemSource { Id = MapInstanceId, - MapInstanceReference = new WeakReference(this), + MapInstanceReference = new WeakReference(this), DescriptorId = mMapController.Id, }; - + AddItem(mapItemSource, mapItem); PacketSender.SendMapItemUpdate(mMapController.Id, MapInstanceId, mapItem, false); } @@ -1453,7 +1444,7 @@ private void SendBatchedPacketsToPlayers() var nearbyPlayers = new HashSet(); // Get all players in surrounding and current maps - foreach (var mapInstance in MapController.GetSurroundingMapInstances(mMapController.Id, MapInstanceId, true)) + foreach (var mapInstance in MapController.GetSurroundingMapInstances(mMapController.Id, MapInstanceId, true)) { foreach (var plyr in mapInstance.GetPlayers()) { diff --git a/Intersect.Server.Core/Maps/TileHelper.cs b/Intersect.Server.Core/Maps/TileHelper.cs index f764715d7a..b47221f440 100644 --- a/Intersect.Server.Core/Maps/TileHelper.cs +++ b/Intersect.Server.Core/Maps/TileHelper.cs @@ -38,14 +38,14 @@ public bool Translate(int xOffset, int yOffset) mTileX += xOffset; mTileY += yOffset; - return TryFix(); + return TryFix(xOffset != 0 && yOffset != 0); } - public bool TryFix() + public bool TryFix(bool diagonal = false) { var oldTileX = mTileX; var oldTileY = mTileY; - if (Fix()) + if (Fix(diagonal)) { return true; } @@ -129,47 +129,76 @@ private bool TransitionMaps(int direction) } } - private bool Fix() + private bool Fix(bool diagonal = false) { if (!MapController.Lookup.Keys.Contains(mMapId)) { return false; } - var curMap = MapController.Get(mMapId); + var transitionFailure = false; + while (mTileX < 0) { - if (!TransitionMaps((int) Direction.Left)) + if (TransitionMaps((int)Direction.Left)) + { + continue; + } + + if (!diagonal) { return false; } + + transitionFailure = true; } while (mTileY < 0) { - if (!TransitionMaps((int) Direction.Up)) + if (TransitionMaps((int)Direction.Up)) + { + continue; + } + + if (!diagonal) { return false; } + + transitionFailure = true; } while (mTileX >= Options.MapWidth) { - if (!TransitionMaps((int) Direction.Right)) + if (TransitionMaps((int)Direction.Right)) + { + continue; + } + + if (!diagonal) { return false; } + + transitionFailure = true; } while (mTileY >= Options.MapHeight) { - if (!TransitionMaps((int) Direction.Down)) + if (TransitionMaps((int)Direction.Down)) + { + continue; + } + + if (!diagonal) { return false; } + + transitionFailure = true; } - return true; + return !transitionFailure || Fix(false); } public Guid GetMapId() diff --git a/Intersect.Server.Core/Networking/PacketSender.cs b/Intersect.Server.Core/Networking/PacketSender.cs index d89c2656d2..021a035670 100644 --- a/Intersect.Server.Core/Networking/PacketSender.cs +++ b/Intersect.Server.Core/Networking/PacketSender.cs @@ -313,7 +313,7 @@ public static void SendMap(Client client, Guid mapId, bool allEditors = false, s if (entity != null) { //TODO: INCLUDE EVENTS IN MAP PACKET - if (mapId == entity.MapId) + if (mapId == entity.MapId || entity.Map.SurroundingMapIds.Contains(mapId)) { entity.SendEvents(); }