diff --git a/Intersect (Core)/Network/Packets/Server/MapPacket.cs b/Intersect (Core)/Network/Packets/Server/MapPacket.cs index 44252015a4..396af3c7d5 100644 --- a/Intersect (Core)/Network/Packets/Server/MapPacket.cs +++ b/Intersect (Core)/Network/Packets/Server/MapPacket.cs @@ -1,7 +1,10 @@ -using MessagePack; +using System.Buffers.Binary; +using System.Numerics; +using MessagePack; using System.Security.Cryptography; using Intersect.GameObjects.Maps; using Intersect.Models; +using Newtonsoft.Json.Converters; namespace Intersect.Network.Packets.Server; @@ -94,15 +97,18 @@ public string? CacheVersion return _version; } - _version = ComputeCacheVersion(MapId, Revision); + _version = ComputeCacheVersion(MapId, Revision, GridX, GridY, CameraHolds); return _version; } } - public static string ComputeCacheVersion(Guid id, int revision) + public static string ComputeCacheVersion(Guid id, int revision, int gridX, int gridY, bool[] cameraHolds) { var hashInputData = id.ToByteArray() .Concat(BitConverter.GetBytes(revision)) + .Concat(BitConverter.GetBytes(gridX)) + .Concat(BitConverter.GetBytes(gridY)) + .Concat(cameraHolds.SelectMany(BitConverter.GetBytes)) .ToArray(); var versionData = SHA256.HashData(hashInputData); var version = Convert.ToBase64String(versionData); diff --git a/Intersect.Client.Core/Core/Sounds/MapSound.cs b/Intersect.Client.Core/Core/Sounds/MapSound.cs index f1f8e5d97d..5b4f50dfc7 100644 --- a/Intersect.Client.Core/Core/Sounds/MapSound.cs +++ b/Intersect.Client.Core/Core/Sounds/MapSound.cs @@ -82,7 +82,7 @@ private void UpdateSoundVolume() } else { - if (mDistance > 0 && Globals.GridMaps.Contains(mMapId)) + if (mDistance > 0 && Globals.GridMaps.ContainsKey(mMapId)) { var volume = 100 - 100 / (mDistance + 1) * CalculateSoundDistance(); if (volume < 0) diff --git a/Intersect.Client.Core/Entities/Entity.cs b/Intersect.Client.Core/Entities/Entity.cs index b6f8d68a20..76a925744c 100644 --- a/Intersect.Client.Core/Entities/Entity.cs +++ b/Intersect.Client.Core/Entities/Entity.cs @@ -10,6 +10,7 @@ using Intersect.Client.General; using Intersect.Client.Items; using Intersect.Client.Localization; +using Intersect.Client.Maps; using Intersect.Client.Spells; using Intersect.Core; using Intersect.Enums; @@ -1054,8 +1055,8 @@ public virtual void Draw() } WorldPos.Reset(); - var map = Maps.MapInstance.Get(MapId); - if (map == null || !Globals.GridMaps.Contains(MapId)) + + if (!Globals.GridMaps.ContainsKey(MapId) || !Maps.MapInstance.TryGet(MapId, out var map)) { return; } @@ -1391,6 +1392,11 @@ public virtual void DrawEquipment(string filename, Color renderColor) protected virtual void CalculateOrigin() { + if (LatestMap?.IsDisposed ?? false) + { + LatestMap = Maps.MapInstance.TryGet(MapId, out MapInstance updatedInstance) ? updatedInstance : null; + } + if (LatestMap == default) { mOrigin = default; diff --git a/Intersect.Client.Core/Entities/Events/Event.cs b/Intersect.Client.Core/Entities/Events/Event.cs index 148575b4f7..edb6549e8f 100644 --- a/Intersect.Client.Core/Entities/Events/Event.cs +++ b/Intersect.Client.Core/Entities/Events/Event.cs @@ -110,7 +110,7 @@ protected bool TryEnsureTexture(out GameTexture? texture) public override void Draw() { WorldPos.Reset(); - if (MapInstance == default || !Globals.GridMaps.Contains(MapId) || !TryEnsureTexture(out var texture)) + if (MapInstance == default || !Globals.GridMaps.ContainsKey(MapId) || !TryEnsureTexture(out var texture)) { return; } diff --git a/Intersect.Client.Core/Entities/Projectiles/Projectile.cs b/Intersect.Client.Core/Entities/Projectiles/Projectile.cs index df0298dc17..fc92251d36 100644 --- a/Intersect.Client.Core/Entities/Projectiles/Projectile.cs +++ b/Intersect.Client.Core/Entities/Projectiles/Projectile.cs @@ -328,7 +328,7 @@ public override bool Update() for (var s = 0; s < _spawnedAmount; s++) { var spawn = _spawns[s]; - + if (spawn != null && Maps.MapInstance.Get(spawn.SpawnMapId) != null) { if (_targetId != Guid.Empty && _targetId != _owner && @@ -774,7 +774,7 @@ private bool Collided(int i) /// public override void Draw() { - if (Maps.MapInstance.Get(MapId) == null || !Globals.GridMaps.Contains(MapId)) + if (Maps.MapInstance.Get(MapId) == null || !Globals.GridMaps.ContainsKey(MapId)) { return; } diff --git a/Intersect.Client.Core/General/Globals.cs b/Intersect.Client.Core/General/Globals.cs index 5a13c26699..8941916261 100644 --- a/Intersect.Client.Core/General/Globals.cs +++ b/Intersect.Client.Core/General/Globals.cs @@ -146,7 +146,7 @@ public static GameStates GameState } } - public static List GridMaps = new List(); + public static Dictionary GridMaps = []; public static bool HasGameData = false; diff --git a/Intersect.Client.Core/Maps/MapInstance.cs b/Intersect.Client.Core/Maps/MapInstance.cs index 91622ea3b3..14ec02a6e4 100644 --- a/Intersect.Client.Core/Maps/MapInstance.cs +++ b/Intersect.Client.Core/Maps/MapInstance.cs @@ -38,7 +38,19 @@ public partial class MapInstance : MapBase, IGameObject, IMap //Map State Variables public static Dictionary MapRequests { get; set; } = new Dictionary(); - public static MapLoadedDelegate OnMapLoaded { get; set; } + public static event MapLoadedDelegate? MapLoaded; + + public void MarkLoadFinished() + { + ApplicationContext.CurrentContext.Logger.LogDebug( + "Done loading map {Id} ({Name}) @ ({GridX}, {GridY})", + Id, + Name, + GridX, + GridY + ); + MapLoaded?.Invoke(this); + } private static MapControllers sLookup; @@ -120,6 +132,8 @@ public MapInstance(Guid id) : base(id) { } + public bool IsDisposed { get; private set; } + public bool IsLoaded { get; private set; } //Camera Locking Variables @@ -177,8 +191,8 @@ public void Load(string json) IsLoaded = true; Autotiles = new MapAutotiles(this); - OnMapLoaded -= HandleMapLoaded; - OnMapLoaded += HandleMapLoaded; + MapLoaded -= HandleMapLoaded; + MapLoaded += HandleMapLoaded; MapRequests.Remove(Id); } @@ -328,7 +342,7 @@ private void HandleMapLoaded(MapInstance map) { //See if this new map is on the same grid as us var updatedBuffers = new HashSet(); - if (map != this && Globals.GridMaps.Contains(map.Id) && Globals.GridMaps.Contains(Id) && IsLoaded) + if (map != this && Globals.GridMaps.ContainsKey(map.Id) && Globals.GridMaps.ContainsKey(Id) && IsLoaded) { var surroundingMaps = GenerateAutotileGrid(); if (map.GridX == GridX - 1) @@ -546,7 +560,7 @@ private GameTileBuffer[] CheckAutotile(int x, int y, MapBase[,] surroundingMaps) public MapBase[,] GenerateAutotileGrid() { var mapBase = new MapBase[3, 3]; - if (Globals.MapGrid != null && Globals.GridMaps.Contains(Id)) + if (Globals.MapGrid != null && Globals.GridMaps.ContainsKey(Id)) { for (var x = -1; x <= 1; x++) { @@ -770,7 +784,7 @@ public void BuildVBOs() var endVbo = DateTime.UtcNow; var elapsedVbo = endVbo - startVbo; - ApplicationContext.Context.Value?.Logger.LogInformation($"Built VBO for map instance {Id} in {elapsedVbo.TotalMilliseconds}ms"); + ApplicationContext.Context.Value?.Logger.LogInformation($"Built VBO for map {Id} '{Name}' in {elapsedVbo.TotalMilliseconds}ms"); // lock (mTileBuffers) // { @@ -1571,8 +1585,18 @@ public override void Delete() //Dispose public void Dispose(bool prep = true, bool killentities = true) { + IsDisposed = true; + + ApplicationContext.CurrentContext.Logger.LogDebug( + "Disposing map {Id} ({Name}) @ ({GridX}, {GridY})", + Id, + Name, + GridX, + GridY + ); + 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 23b452bb4b..1ad0d56607 100644 --- a/Intersect.Client.Core/Networking/PacketHandler.cs +++ b/Intersect.Client.Core/Networking/PacketHandler.cs @@ -236,6 +236,16 @@ private void HandleMap(IPacketSender packetSender, MapPacket packet, bool skipSa if (!skipSave) { + ApplicationContext.CurrentContext.Logger.LogDebug( + "Saving map {Id} @ ({GridX}, {GridY}) revision {Revision} version {Version} holds {CameraHolds}", + packet.MapId, + packet.GridX, + packet.GridY, + packet.Revision, + packet.CacheVersion, + $"[{string.Join(", ", packet.CameraHolds)}]" + ); + ObjectCacheData cacheData = new() { Id = new Id(mapId), @@ -246,7 +256,7 @@ private void HandleMap(IPacketSender packetSender, MapPacket packet, bool skipSa if (!ObjectDataDiskCache.TrySave(cacheData)) { - ApplicationContext.Context.Value?.Logger.LogWarning($"Failed to save cache for {cacheKey}"); + ApplicationContext.CurrentContext.Logger.LogWarning("Failed to save cache for {CacheKey}", cacheKey); } } @@ -254,7 +264,7 @@ private void HandleMap(IPacketSender packetSender, MapPacket packet, bool skipSa if (MapInstance.TryGet(mapId, out var mapInstance)) { - if (packet.Revision == mapInstance.Revision) + if (skipSave && packet.Revision == mapInstance.Revision) { return; } @@ -270,28 +280,56 @@ private void HandleMap(IPacketSender packetSender, MapPacket packet, bool skipSa mapInstance.LoadTileData(packet.TileData); mapInstance.AttributeData = packet.AttributeData; mapInstance.CreateMapSounds(); - if (mapId == Globals.Me.MapId) + + if (mapId == Globals.Me?.MapId) { - Audio.PlayMusic(mapInstance.Music, ClientConfiguration.Instance.MusicFadeTimer, ClientConfiguration.Instance.MusicFadeTimer, true); + Audio.PlayMusic( + mapInstance.Music, + ClientConfiguration.Instance.MusicFadeTimer, + ClientConfiguration.Instance.MusicFadeTimer, + true + ); } - mapInstance.GridX = packet.GridX; - mapInstance.GridY = packet.GridY; + if (!Globals.GridMaps.TryGetValue(packet.MapId, out var gridPosition)) + { + ApplicationContext.CurrentContext.Logger.LogDebug( + "Falling back to packet position for map '{MapName}' ({MapId})", + mapInstance.Name, + mapInstance.Id + ); + gridPosition = new Point(packet.GridX, packet.GridY); + } + + mapInstance.GridX = gridPosition.X; + mapInstance.GridY = gridPosition.Y; mapInstance.CameraHolds = packet.CameraHolds; + + ApplicationContext.CurrentContext.Logger.LogDebug( + "Loading map {Id} ({Name}) @ ({GridX}, {GridY}) revision {Revision} version {Version} holds {CameraHolds}", + mapInstance.Id, + mapInstance.Name, + gridPosition.X, + gridPosition.Y, + mapInstance.Revision, + packet.CacheVersion, + $"[{string.Join(", ", mapInstance.CameraHolds)}]" + ); + mapInstance.Autotiles.InitAutotiles(mapInstance.GenerateAutotileGrid()); - if (Globals.PendingEvents.ContainsKey(mapId)) + if (Globals.PendingEvents.TryGetValue(mapId, out var pendingEventsForMap)) { - foreach (var evt in Globals.PendingEvents[mapId]) + foreach (var (eventId, eventEntityPacket) in pendingEventsForMap) { - mapInstance.AddEvent(evt.Key, evt.Value); + mapInstance.AddEvent(eventId, eventEntityPacket); } - Globals.PendingEvents[mapId].Clear(); + pendingEventsForMap.Clear(); } } - MapInstance.OnMapLoaded?.Invoke(mapInstance); + mapInstance.MarkLoadFinished(); } //MapPacket @@ -403,32 +441,35 @@ public void HandlePacket(IPacketSender packetSender, EventEntityPacket packet) } //MapEntitiesPacket - public void HandlePacket(IPacketSender packetSender, MapEntitiesPacket packet) + public void HandlePacket(IPacketSender packetSender, MapEntitiesPacket entitiesPacket) { - var mapEntities = new Dictionary>(); - foreach (var pkt in packet.MapEntities) + Dictionary> entitiesByMapId = []; + foreach (var entityPacket in entitiesPacket.MapEntities) { - HandlePacket(pkt); + HandlePacket(entityPacket); - if (!mapEntities.ContainsKey(pkt.MapId)) + if (!entitiesByMapId.TryGetValue(entityPacket.MapId, out var value)) { - mapEntities.Add(pkt.MapId, new List()); + value = []; + entitiesByMapId.Add(entityPacket.MapId, value); } - mapEntities[pkt.MapId].Add(pkt.EntityId); + value.Add(entityPacket.EntityId); } - //Remove any entities on the map that shouldn't be there anymore! - foreach (var entities in mapEntities) + // Remove any entities on the map that shouldn't be there anymore! + foreach (var (mapId, entitiesOnMap) in entitiesByMapId) { - foreach (var entity in Globals.Entities) + foreach (var (entityId, entity) in Globals.Entities) { - if (entity.Value.MapId == entities.Key && !entities.Value.Contains(entity.Key)) + if (entity.MapId != mapId || entitiesOnMap.Contains(entityId)) { - if (!Globals.EntitiesToDispose.Contains(entity.Key) && entity.Value != Globals.Me && !(entity.Value is Projectile)) - { - Globals.EntitiesToDispose.Add(entity.Key); - } + continue; + } + + if (!Globals.EntitiesToDispose.Contains(entityId) && entity != Globals.Me && entity is not Projectile) + { + Globals.EntitiesToDispose.Add(entityId); } } } @@ -1755,7 +1796,7 @@ public void HandlePacket(IPacketSender packetSender, MapGridPacket packet) Globals.MapGrid[x, y] = packet.Grid[x, y]; if (Globals.MapGrid[x, y] != Guid.Empty) { - Globals.GridMaps.Add(Globals.MapGrid[x, y]); + Globals.GridMaps[Globals.MapGrid[x, y]] = new Point(x, y); // MapInstance.UpdateMapRequestTime(Globals.MapGrid[x, y]); } } diff --git a/Intersect.Client.Framework/Maps/IMapInstance.cs b/Intersect.Client.Framework/Maps/IMapInstance.cs index b27dd53fdb..730eb75694 100644 --- a/Intersect.Client.Framework/Maps/IMapInstance.cs +++ b/Intersect.Client.Framework/Maps/IMapInstance.cs @@ -23,6 +23,7 @@ public interface IMapInstance int Y { get; } int GridX { get; set; } int GridY { get; set; } + bool IsDisposed { get; } bool IsLoaded { get; } void AddTileAnimation(Guid animId, int tileX, int tileY, Direction dir = Direction.None, IEntity? owner = null); diff --git a/Intersect.Server.Core/Maps/MapController.cs b/Intersect.Server.Core/Maps/MapController.cs index 1111de5838..38df4f3127 100644 --- a/Intersect.Server.Core/Maps/MapController.cs +++ b/Intersect.Server.Core/Maps/MapController.cs @@ -34,22 +34,98 @@ namespace Intersect.Server.Maps; /// public partial class MapController : MapBase { - private ConcurrentDictionary mInstances = new ConcurrentDictionary(); + private readonly ConcurrentDictionary mInstances = []; private static MapControllers sLookup; //Location of Map in the current grid - [JsonIgnore] [NotMapped] public int MapGrid; + [JsonIgnore] + [NotMapped] + public int MapGrid + { + get => _mapGridId; + set + { + if (value == _mapGridId) + { + return; + } + + _mapGridId = value; + CachedMapClientPacket = null; + } + } + + [JsonIgnore] + [NotMapped] + public int MapGridX + { + get => _mapGridX; + set + { + if (value == _mapGridX) + { + return; + } + + _mapGridX = value; + CachedMapClientPacket = null; + } + } - [JsonIgnore] [NotMapped] public int MapGridX = -1; + [JsonIgnore] + [NotMapped] + public int MapGridY + { + get => _mapGridY; + set + { + if (value == _mapGridY) + { + return; + } - [JsonIgnore] [NotMapped] public int MapGridY = -1; + _mapGridY = value; + CachedMapClientPacket = null; + } + } + + public bool[] GetCameraHolds() + { + switch (Options.Instance.Map.GameBorderStyle) + { + case 1: + return [true, true, true, true]; + + break; + + case 0: + var grid = DbInterface.GetGrid(MapGrid); + if (grid != null) + { + return + [ + 0 == MapGridY, + grid.YMax - 1 == MapGridY, + 0 == MapGridX, + grid.XMax - 1 == MapGridX, + ]; + } + + break; + } + + return [true, true, true, true]; + } //Temporary Values private Guid[] mSurroundingMapIds = new Guid[0]; private Guid[] mSurroundingMapsIdsWithSelf = new Guid[0]; private MapController[] mSurroundingMaps = new MapController[0]; private MapController[] mSurroundingMapsWithSelf = new MapController[0]; + private int _mapGridId; + private int _mapGridX = -1; + private int _mapGridY = -1; [NotMapped, JsonIgnore] public MapPacket? CachedMapClientPacket { get; set; } @@ -528,7 +604,7 @@ public void DestroyOrphanedLayers() } TileData = LZ4.PickleString(JsonConvert.SerializeObject(Layers, Formatting.None, mJsonSerializerSettings)); Layers = null; - + } } diff --git a/Intersect.Server.Core/Networking/PacketHandler.cs b/Intersect.Server.Core/Networking/PacketHandler.cs index 5e5a715bb0..f4142b92fc 100644 --- a/Intersect.Server.Core/Networking/PacketHandler.cs +++ b/Intersect.Server.Core/Networking/PacketHandler.cs @@ -763,7 +763,13 @@ public void HandlePacket(Client client, GetObjectData packet) continue; } - var version = MapPacket.ComputeCacheVersion(descriptor.Id, descriptor.Revision); + var version = MapPacket.ComputeCacheVersion( + descriptor.Id, + descriptor.Revision, + descriptor.MapGridX, + descriptor.MapGridY, + descriptor.GetCameraHolds() + ); var checksumToCompare = string.Equals(cacheKey.Version, version, StringComparison.Ordinal) ? cacheKey.Checksum diff --git a/Intersect.Server.Core/Networking/PacketSender.cs b/Intersect.Server.Core/Networking/PacketSender.cs index 64d8e17447..ab6d6c0a35 100644 --- a/Intersect.Server.Core/Networking/PacketSender.cs +++ b/Intersect.Server.Core/Networking/PacketSender.cs @@ -203,26 +203,7 @@ public static MapPacket GenerateMapPacket(Client client, Guid mapId) } else { - switch (Options.Instance.Map.GameBorderStyle) - { - case 1: - mapPacket.CameraHolds = new bool[4] { true, true, true, true }; - - break; - - case 0: - var grid = DbInterface.GetGrid(map.MapGrid); - if (grid != null) - { - mapPacket.CameraHolds = new bool[4] - { - 0 == map.MapGridY, grid.YMax - 1 == map.MapGridY, - 0 == map.MapGridX, grid.XMax - 1 == map.MapGridX - }; - } - - break; - } + mapPacket.CameraHolds = map.GetCameraHolds(); } if (client.IsEditor)