using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using PhoenixLib.Logging; using WingsAPI.Communication.ServerApi.Protocol; using WingsAPI.Data.GameData; using WingsEmu.Core.Extensions; using WingsEmu.Core.Generics; using WingsEmu.DTOs.Maps; using WingsEmu.DTOs.ServerDatas; using WingsEmu.Game.Configurations; using WingsEmu.Game.Entities; using WingsEmu.Game.Entities.Event; using WingsEmu.Game.Extensions; using WingsEmu.Game.Helpers.Damages; using WingsEmu.Game.Managers.ServerData; using WingsEmu.Game.Maps; using WingsEmu.Game.Networking; using WingsEmu.Game.Portals; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects.Maps; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.ImportObjects.Portals; using WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence; namespace WingsEmu.Plugins.BasicImplementations.ServerConfigs; public class MapManager : IMapManager { private readonly Dictionary _baseMapInstancesByMapId = new(); private readonly SerializableGameServer _gameServer; private readonly Dictionary _mapConfigByMapId = new(); private readonly IEnumerable _mapConfigurations; private readonly Dictionary _mapDataByMapVNum = new(); private readonly Dictionary> _mapFlags = new(); private readonly IMapInstanceFactory _mapInstanceFactory; private readonly ConcurrentDictionary> _mapInstancesByFlags = new(); private readonly ConcurrentDictionary _mapInstancesByGuid = new(); private readonly IResourceLoader _mapLoader; private readonly IMapMonsterManager _mapMonsterManager; private readonly IMapNpcManager _mapNpcManager; private readonly IMonsterEntityFactory _monsterEntityFactory; private readonly INpcEntityFactory _npcEntityFactory; private readonly IEnumerable _portalConfigurationFiles; private readonly Dictionary> _portalDataByMapId = new(); private readonly ITimeSpaceConfiguration _timeSpaceConfiguration; private readonly ITimeSpacePortalFactory _timeSpacePortalFactory; public MapManager(IEnumerable portalConfigurationFiles, IResourceLoader mapLoader, IEnumerable mapConfigurations, SerializableGameServer gameServer, IMapInstanceFactory mapInstanceFactory, IMonsterEntityFactory monsterEntityFactory, INpcEntityFactory npcEntityFactory, IMapMonsterManager mapMonsterManager, IMapNpcManager mapNpcManager, ITimeSpaceConfiguration timeSpaceConfiguration, ITimeSpacePortalFactory timeSpacePortalFactory) { _portalConfigurationFiles = portalConfigurationFiles; _mapLoader = mapLoader; _mapConfigurations = mapConfigurations; _gameServer = gameServer; _mapInstanceFactory = mapInstanceFactory; _monsterEntityFactory = monsterEntityFactory; _npcEntityFactory = npcEntityFactory; _mapMonsterManager = mapMonsterManager; _mapNpcManager = mapNpcManager; _timeSpaceConfiguration = timeSpaceConfiguration; _timeSpacePortalFactory = timeSpacePortalFactory; } public async Task Initialize() { int count = 0; IEnumerable maps = await _mapLoader.LoadAsync(); foreach (MapDataDTO map in maps) { _mapDataByMapVNum[map.Id] = map; count++; } Log.Info($"[MAP_MANAGER] Loaded {count.ToString()} MapClientData"); count = 0; IEnumerable portals = _portalConfigurationFiles.SelectMany(s => s.Portals.Select(p => p.ToDto())).ToList(); foreach (PortalDTO portal in portals) { if (!_portalDataByMapId.TryGetValue(portal.SourceMapId, out List portalDtos)) { portalDtos = _portalDataByMapId[portal.SourceMapId] = new List(); } portalDtos.Add(portal); count++; } Log.Info($"[MAP_MANAGER] Loaded {count.ToString()} MapPortals"); DateTime initTime = DateTime.UtcNow; count = 0; int countBaseMaps = 0; var configuredMaps = _mapConfigurations.SelectMany(s => s.Select(p => p.ToDto())).ToList(); ParserDataPostgresSync.SyncMapsAndPortals(configuredMaps, portals.ToList()); foreach (ServerMapDto configuredMap in configuredMaps) { // Load MapFlags at the beginning _mapFlags[configuredMap.Id] = configuredMap.Flags.ToHashSet(); if (_gameServer.ChannelType == GameChannelType.ACT_4 ? !configuredMap.Flags.Contains(MapFlags.ACT_4) && !configuredMap.Flags.Contains(MapFlags.IS_MINILAND_MAP) : configuredMap.Flags.Contains(MapFlags.ACT_4)) { continue; } _mapConfigByMapId[configuredMap.Id] = configuredMap; count++; if (!configuredMap.Flags.Contains(MapFlags.IS_BASE_MAP)) { continue; } IMapInstance mapInstance = GenerateMapInstanceByMapId(configuredMap.Id, MapInstanceType.BaseMapInstance); mapInstance.Initialize(initTime); _baseMapInstancesByMapId[configuredMap.Id] = mapInstance; countBaseMaps++; } Log.Info($"[MAP_MANAGER] Loaded {count.ToString()} MapConfigurations"); Log.Info($"[MAP_MANAGER] Instantiated {countBaseMaps.ToString()} BaseMaps"); } public MapDataDTO GetMapByMapId(int mapId) => _mapDataByMapVNum.GetOrDefault(mapId); public IMapInstance GenerateMapInstanceByMapId(int mapId, MapInstanceType type) { if (!_mapConfigByMapId.TryGetValue(mapId, out ServerMapDto mapConfiguration)) { Log.Warn($"[MAP_MANAGER] Couldn't find a ServerMapDto/MapConfiguration while trying to generate a MapInstance with MapId: '{mapId.ToString()}'"); return null; } if (!_mapDataByMapVNum.TryGetValue(mapConfiguration.MapVnum, out MapDataDTO map)) { Log.Warn($"[MAP_MANAGER] Couldn't find a MapDataDto while trying to generate a MapInstance with MapVNum: '{mapConfiguration.MapVnum.ToString()}'"); return null; } IMapInstance mapInstance = _mapInstanceFactory.CreateMap(new Map { Flags = mapConfiguration.Flags, Grid = map.Grid, Width = map.Width, Height = map.Height, MapId = mapConfiguration.Id, MapVnum = map.Id, MapNameId = mapConfiguration.NameId, Music = mapConfiguration.MusicId }, type); if (_portalDataByMapId.TryGetValue(mapId, out List portals)) { mapInstance.LoadPortals(portals); } IEnumerable timeSpacePortals = _timeSpaceConfiguration.GetTimeSpaceConfigurationsByMapId(mapId); if (timeSpacePortals != null) { foreach (TimeSpaceFileConfiguration timeSpacePortal in timeSpacePortals) { foreach (TimeSpacePlacement placement in timeSpacePortal.Placement.Where(x => x.MapId == mapId)) { ITimeSpacePortalEntity portal = _timeSpacePortalFactory.CreateTimeSpacePortal(timeSpacePortal, new Position(placement.MapX, placement.MapY)); mapInstance.TimeSpacePortals.Add(portal); } } } IEnumerable monsters = _mapMonsterManager.GetByMapId(mapId); if (monsters != null) { foreach (MapMonsterDTO monster in monsters) { try { IMonsterEntity mapMonster = _monsterEntityFactory.CreateMapMonster(monster, mapInstance); mapMonster.EmitEvent(new MapJoinMonsterEntityEvent(mapMonster)); } catch { Log.Warn("[MOB] Couldn't load monster: " + monster.MonsterVNum + $" on map: {mapId}"); } } } IEnumerable npcs = _mapNpcManager.GetByMapId(mapId); if (npcs != null) { foreach (MapNpcDTO npc in npcs) { try { INpcEntity npcEntity = _npcEntityFactory.CreateMapNpc(npc, mapInstance); npcEntity.EmitEvent(new MapJoinNpcEntityEvent(npcEntity)); } catch { Log.Warn("[NPC] Couldn't load NPC: " + npc.NpcVNum + $" on map: {mapId}"); } } } _mapInstancesByGuid.TryAdd(mapInstance.Id, mapInstance); foreach (MapFlags flag in mapConfiguration.Flags) { _mapInstancesByFlags.GetOrAdd(flag, new ThreadSafeList()).Add(mapInstance); } return mapInstance; } public IMapInstance GenerateMapInstanceByMapVNum(ServerMapDto serverMapDto, MapInstanceType type) { if (!_mapDataByMapVNum.TryGetValue(serverMapDto.MapVnum, out MapDataDTO map)) { Log.Warn($"[MAP_MANAGER] Couldn't find a MapDataDto while trying to generate a MapInstance with MapVNum: '{serverMapDto.MapVnum.ToString()}'"); return null; } IMapInstance mapInstance = _mapInstanceFactory.CreateMap(new Map { Flags = serverMapDto.Flags, Grid = map.Grid, Width = map.Width, Height = map.Height, MapId = serverMapDto.Id, MapVnum = map.Id, MapNameId = serverMapDto.NameId, Music = serverMapDto.MusicId }, type); _mapInstancesByGuid.TryAdd(mapInstance.Id, mapInstance); foreach (MapFlags flag in serverMapDto.Flags) { _mapInstancesByFlags.GetOrAdd(flag, new ThreadSafeList()).Add(mapInstance); } return mapInstance; } public async Task TeleportOnRandomPlaceInMapAsync(IClientSession session, IMapInstance mapInstance, bool isSameMap = false) { if (mapInstance == default) { return; } Position pos = mapInstance.GetRandomPosition(); switch (isSameMap) { case false: session.ChangeMap(mapInstance, pos.X, pos.Y); break; case true: session.PlayerEntity.TeleportOnMap(pos.X, pos.Y); break; } } public IEnumerable GetMapsWithFlag(MapFlags flags) { if (!_mapInstancesByFlags.TryGetValue(flags, out ThreadSafeList maps) || maps == null) { return Array.Empty(); } return maps; } public IMapInstance GetMapInstance(Guid id) => _mapInstancesByGuid.GetOrDefault(id); public IMapInstance GetBaseMapInstanceByMapId(int mapId) => _baseMapInstancesByMapId.GetOrDefault(mapId); public Guid GetBaseMapInstanceIdByMapId(int mapId) => _baseMapInstancesByMapId.TryGetValue(mapId, out IMapInstance map) ? map.Id : Guid.Empty; public bool HasMapFlagByMapId(int mapId, MapFlags mapFlag) { if (!_mapFlags.TryGetValue(mapId, out HashSet mapFlags)) { return false; } return mapFlags != null && mapFlags.Contains(mapFlag); } public IReadOnlyList GetMapFlagByMapId(int mapId) => _mapFlags.TryGetValue(mapId, out HashSet mapFlags) ? mapFlags.ToList() : Array.Empty(); public void RemoveMapInstance(Guid mapId) { if (!_mapInstancesByGuid.TryRemove(mapId, out IMapInstance map)) { return; } if (map.HasMapFlag(MapFlags.IS_BASE_MAP)) { _baseMapInstancesByMapId.Remove(map.MapId); } if (_mapConfigByMapId.TryGetValue(map.MapId, out ServerMapDto mapConfiguration)) { foreach (MapFlags flag in mapConfiguration.Flags) { if (_mapInstancesByFlags.TryGetValue(flag, out ThreadSafeList list) && list != null) { list.Remove(map); } } } map.Destroy(); } }