From efd2f4cb491e5601a95144b4560ab760db36c3b1 Mon Sep 17 00:00:00 2001 From: hondacrx Date: Sun, 24 Jul 2022 17:50:51 -0400 Subject: [PATCH] Core/Maps: Removed MapInstanced - no longer neccessary for grid data reference counting (moved to TerrainInfo) Port From (https://github.com/TrinityCore/TrinityCore/commit/fbe0b8efebca3bc2662b477bdf45627f9783d6c2) --- Source/Game/Chat/Commands/DebugCommands.cs | 23 +- Source/Game/Chat/Commands/GoCommands.cs | 18 +- Source/Game/Chat/Commands/ListCommands.cs | 36 +- Source/Game/Entities/Player/Player.cs | 2 +- Source/Game/Garrisons/GarrisonMap.cs | 3 +- Source/Game/Handlers/MiscHandler.cs | 2 +- .../Maps/Instances/InstanceSaveManager.cs | 50 ++- Source/Game/Maps/Instances/MapInstance.cs | 293 ------------- Source/Game/Maps/Map.cs | 355 ++++++++------- Source/Game/Maps/MapManager.cs | 405 +++++++++--------- Source/Game/Maps/TerrainManager.cs | 38 +- .../Generators/FlightPathMovementGenerator.cs | 9 +- Source/Game/World/WorldManager.cs | 13 - 13 files changed, 513 insertions(+), 734 deletions(-) delete mode 100644 Source/Game/Maps/Instances/MapInstance.cs diff --git a/Source/Game/Chat/Commands/DebugCommands.cs b/Source/Game/Chat/Commands/DebugCommands.cs index dbcf31635..5bc2905a4 100644 --- a/Source/Game/Chat/Commands/DebugCommands.cs +++ b/Source/Game/Chat/Commands/DebugCommands.cs @@ -575,19 +575,24 @@ namespace Game.Chat [Command("loadcells", RBACPermissions.CommandDebug, true)] static bool HandleDebugLoadCellsCommand(CommandHandler handler, uint? mapId, uint? tileX, uint? tileY) { - Map map = null; if (mapId.HasValue) - map = Global.MapMgr.FindBaseNonInstanceMap(mapId.Value); - else { - Player player = handler.GetPlayer(); - if (player != null) - { - // Fallback to player's map if no map has been specified - map = player.GetMap(); - } + Global.MapMgr.DoForAllMapsWithMapId(mapId.Value, map => HandleDebugLoadCellsCommandHelper(handler, map, tileX, tileY)); + return true; } + Player player = handler.GetPlayer(); + if (player != null) + { + // Fallback to player's map if no map has been specified + return HandleDebugLoadCellsCommandHelper(handler, player.GetMap(), tileX, tileY); + } + + return false; + } + + static bool HandleDebugLoadCellsCommandHelper(CommandHandler handler, Map map, uint? tileX, uint? tileY) + { if (!map) return false; diff --git a/Source/Game/Chat/Commands/GoCommands.cs b/Source/Game/Chat/Commands/GoCommands.cs index 75b883e44..10e5f9702 100644 --- a/Source/Game/Chat/Commands/GoCommands.cs +++ b/Source/Game/Chat/Commands/GoCommands.cs @@ -205,8 +205,8 @@ namespace Game.Chat.Commands else player.SaveRecallPosition(); // save only in non-flight case - Map map = Global.MapMgr.CreateBaseMap(mapId); - float z = Math.Max(map.GetStaticHeight(PhasingHandler.EmptyPhaseShift, x, y, MapConst.MaxHeight), map.GetWaterLevel(PhasingHandler.EmptyPhaseShift, x, y)); + TerrainInfo terrain = Global.TerrainMgr.LoadTerrain(mapId); + float z = Math.Max(terrain.GetStaticHeight(PhasingHandler.EmptyPhaseShift, x, y, MapConst.MaxHeight), terrain.GetWaterLevel(PhasingHandler.EmptyPhaseShift, x, y)); player.TeleportTo(mapId, x, y, z, player.GetOrientation()); return true; @@ -352,8 +352,8 @@ namespace Game.Chat.Commands else player.SaveRecallPosition(); // save only in non-flight case - Map map = Global.MapMgr.CreateBaseMap(mapId); - z = Math.Max(map.GetStaticHeight(PhasingHandler.EmptyPhaseShift, x, y, MapConst.MaxHeight), map.GetWaterLevel(PhasingHandler.EmptyPhaseShift, x, y)); + TerrainInfo terrain = Global.TerrainMgr.LoadTerrain(mapId); + z = Math.Max(terrain.GetStaticHeight(PhasingHandler.EmptyPhaseShift, x, y, MapConst.MaxHeight), terrain.GetWaterLevel(PhasingHandler.EmptyPhaseShift, x, y)); player.TeleportTo(mapId, x, y, z, 0.0f); return true; @@ -398,8 +398,8 @@ namespace Game.Chat.Commands handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, mapId); return false; } - Map map = Global.MapMgr.CreateBaseMap(mapId); - z = Math.Max(map.GetStaticHeight(PhasingHandler.EmptyPhaseShift, x, y, MapConst.MaxHeight), map.GetWaterLevel(PhasingHandler.EmptyPhaseShift, x, y)); + TerrainInfo terrain = Global.TerrainMgr.LoadTerrain(mapId); + z = Math.Max(terrain.GetStaticHeight(PhasingHandler.EmptyPhaseShift, x, y, MapConst.MaxHeight), terrain.GetWaterLevel(PhasingHandler.EmptyPhaseShift, x, y)); } return DoTeleport(handler, new Position(x, y, z.Value, o.Value), mapId); @@ -427,10 +427,10 @@ namespace Game.Chat.Commands x /= 100.0f; y /= 100.0f; - Map map = Global.MapMgr.CreateBaseMap(zoneEntry.ContinentID); + TerrainInfo terrain = Global.TerrainMgr.LoadTerrain(zoneEntry.ContinentID); if (!Global.DB2Mgr.Zone2MapCoordinates(areaEntry.ParentAreaID != 0 ? areaEntry.ParentAreaID : areaId, ref x, ref y)) { - handler.SendSysMessage(CypherStrings.InvalidZoneMap, areaId, areaEntry.AreaName[handler.GetSessionDbcLocale()], map.GetId(), map.GetMapName()); + handler.SendSysMessage(CypherStrings.InvalidZoneMap, areaId, areaEntry.AreaName[handler.GetSessionDbcLocale()], terrain.GetId(), terrain.GetMapName()); return false; } @@ -446,7 +446,7 @@ namespace Game.Chat.Commands else player.SaveRecallPosition(); // save only in non-flight case - float z = Math.Max(map.GetStaticHeight(PhasingHandler.EmptyPhaseShift, x, y, MapConst.MaxHeight), map.GetWaterLevel(PhasingHandler.EmptyPhaseShift, x, y)); + float z = Math.Max(terrain.GetStaticHeight(PhasingHandler.EmptyPhaseShift, x, y, MapConst.MaxHeight), terrain.GetWaterLevel(PhasingHandler.EmptyPhaseShift, x, y)); player.TeleportTo(zoneEntry.ContinentID, x, y, z, player.GetOrientation()); return true; diff --git a/Source/Game/Chat/Commands/ListCommands.cs b/Source/Game/Chat/Commands/ListCommands.cs index 045728f4c..1aa935baa 100644 --- a/Source/Game/Chat/Commands/ListCommands.cs +++ b/Source/Game/Chat/Commands/ListCommands.cs @@ -72,27 +72,17 @@ namespace Game.Chat.Commands bool liveFound = false; // Get map (only support base map from console) - Map thisMap; + Map thisMap = null; if (handler.GetSession() != null) thisMap = handler.GetSession().GetPlayer().GetMap(); - else - thisMap = Global.MapMgr.FindBaseNonInstanceMap(mapId); // If map found, try to find active version of this creature if (thisMap) { var creBounds = thisMap.GetCreatureBySpawnIdStore().LookupByKey(guid); - if (!creBounds.Empty()) - { - foreach (var creature in creBounds) - { - if (handler.GetSession()) - handler.SendSysMessage(CypherStrings.CreatureListChat, guid, guid, cInfo.Name, x, y, z, mapId, creature.GetGUID().ToString(), creature.IsAlive() ? "*" : " "); - else - handler.SendSysMessage(CypherStrings.CreatureListConsole, guid, cInfo.Name, x, y, z, mapId, creature.GetGUID().ToString(), creature.IsAlive() ? "*" : " "); - } - liveFound = true; - } + foreach (var creature in creBounds) + handler.SendSysMessage(CypherStrings.CreatureListChat, guid, guid, cInfo.Name, x, y, z, mapId, creature.GetGUID().ToString(), creature.IsAlive() ? "*" : " "); + liveFound = !creBounds.Empty(); } if (!liveFound) @@ -422,27 +412,17 @@ namespace Game.Chat.Commands bool liveFound = false; // Get map (only support base map from console) - Map thisMap; + Map thisMap = null; if (handler.GetSession() != null) thisMap = handler.GetSession().GetPlayer().GetMap(); - else - thisMap = Global.MapMgr.FindBaseNonInstanceMap(mapId); // If map found, try to find active version of this object if (thisMap) { var goBounds = thisMap.GetGameObjectBySpawnIdStore().LookupByKey(guid); - if (!goBounds.Empty()) - { - foreach (var go in goBounds) - { - if (handler.GetSession()) - handler.SendSysMessage(CypherStrings.GoListChat, guid, entry, guid, gInfo.name, x, y, z, mapId, go.GetGUID(), go.IsSpawned() ? "*" : " "); - else - handler.SendSysMessage(CypherStrings.GoListConsole, guid, gInfo.name, x, y, z, mapId, go.GetGUID(), go.IsSpawned() ? "*" : " "); - } - liveFound = true; - } + foreach (var go in goBounds) + handler.SendSysMessage(CypherStrings.GoListChat, guid, entry, guid, gInfo.name, x, y, z, mapId, go.GetGUID().ToString(), go.IsSpawned() ? "*" : " "); + liveFound = !goBounds.Empty(); } if (!liveFound) diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index d99a03bf4..63a72206a 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -1776,7 +1776,7 @@ namespace Game.Entities // Check enter rights before map getting to avoid creating instance copy for player // this check not dependent from map instance copy and same for all instance copies of selected map - if (Global.MapMgr.PlayerCannotEnter(mapid, this, false) != 0) + if (Map.PlayerCannotEnter(mapid, this, false) != 0) return false; // Seamless teleport can happen only if cosmetic maps match diff --git a/Source/Game/Garrisons/GarrisonMap.cs b/Source/Game/Garrisons/GarrisonMap.cs index 720ef6199..a38c8f5b5 100644 --- a/Source/Game/Garrisons/GarrisonMap.cs +++ b/Source/Game/Garrisons/GarrisonMap.cs @@ -24,8 +24,7 @@ namespace Game.Garrisons { class GarrisonMap : Map { - public GarrisonMap(uint id, long expiry, uint instanceId, Map parent, ObjectGuid owner) - : base(id, expiry, instanceId, Difficulty.Normal, parent) + public GarrisonMap(uint id, long expiry, uint instanceId, ObjectGuid owner) : base(id, expiry, instanceId, Difficulty.Normal) { _owner = owner; InitVisibilityDistance(); diff --git a/Source/Game/Handlers/MiscHandler.cs b/Source/Game/Handlers/MiscHandler.cs index c8fd31d10..f407eb101 100644 --- a/Source/Game/Handlers/MiscHandler.cs +++ b/Source/Game/Handlers/MiscHandler.cs @@ -274,7 +274,7 @@ namespace Game bool teleported = false; if (player.GetMapId() != at.target_mapId) { - EnterState denyReason = Global.MapMgr.PlayerCannotEnter(at.target_mapId, player, false); + EnterState denyReason = Map.PlayerCannotEnter(at.target_mapId, player, false); if (denyReason != 0) { bool reviveAtTrigger = false; // should we revive the player if he is trying to enter the correct instance? diff --git a/Source/Game/Maps/Instances/InstanceSaveManager.cs b/Source/Game/Maps/Instances/InstanceSaveManager.cs index 6c9023603..14badc0d2 100644 --- a/Source/Game/Maps/Instances/InstanceSaveManager.cs +++ b/Source/Game/Maps/Instances/InstanceSaveManager.cs @@ -453,7 +453,7 @@ namespace Game.Maps void _ResetInstance(uint mapid, uint instanceId) { Log.outDebug(LogFilter.Maps, "InstanceSaveMgr._ResetInstance {0}, {1}", mapid, instanceId); - Map map = Global.MapMgr.CreateBaseMap(mapid); + var map = CliDB.MapStorage.LookupByKey(mapid); if (!map.IsDungeon()) return; @@ -463,7 +463,7 @@ namespace Game.Maps DeleteInstanceFromDB(instanceId); // even if save not loaded - Map iMap = ((MapInstanced)map).FindInstanceMap(instanceId); + Map iMap = Global.MapMgr.FindMap(mapid, instanceId); if (iMap != null) { ((InstanceMap)iMap).Reset(InstanceResetMethod.RespawnDelay); @@ -539,27 +539,23 @@ namespace Game.Maps } // note: this isn't fast but it's meant to be executed very rarely - Map map = Global.MapMgr.CreateBaseMap(mapid); // _not_ include difficulty - var instMaps = ((MapInstanced)map).GetInstancedMaps(); - uint timeLeft; - - foreach (var pair in instMaps) + if (mapEntry.IsDungeon()) { - Map map2 = pair.Value; - if (!map2.IsDungeon()) - continue; - - if (warn) + Global.MapMgr.DoForAllMapsWithMapId(mapid, map => { - if (now >= resetTime) - timeLeft = 0; - else - timeLeft = (uint)(resetTime - now); + if (warn) + { + uint timeLeft; + if (now >= resetTime) + timeLeft = 0; + else + timeLeft = (uint)(resetTime - now); - ((InstanceMap)map2).SendResetWarnings(timeLeft); - } - else - ((InstanceMap)map2).Reset(InstanceResetMethod.Global); + ((InstanceMap)map).SendResetWarnings(timeLeft); + } + else + ((InstanceMap)map).Reset(InstanceResetMethod.Global); + }); } // @todo delete creature/gameobject respawn times even if the maps are not loaded @@ -583,7 +579,6 @@ namespace Game.Maps return ret; } - public long GetResetTimeFor(uint mapid, Difficulty d) { return m_resetTimeByMapDifficulty.LookupByKey(MathFunctions.MakePair64(mapid, (uint)d)); @@ -767,4 +762,17 @@ namespace Game.Maps bool m_canReset; bool m_toDelete; } + + public class InstanceTemplate + { + public uint Parent; + public uint ScriptId; + } + + public class InstanceBind + { + public InstanceSave save; + public bool perm; + public BindExtensionState extendState; + } } diff --git a/Source/Game/Maps/Instances/MapInstance.cs b/Source/Game/Maps/Instances/MapInstance.cs deleted file mode 100644 index 05f80d87f..000000000 --- a/Source/Game/Maps/Instances/MapInstance.cs +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) 2012-2020 CypherCore - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -using Framework.Constants; -using Game.BattleGrounds; -using Game.DataStorage; -using Game.Entities; -using Game.Garrisons; -using Game.Groups; -using Game.Scenarios; -using System.Collections.Generic; -using System.Linq; - -namespace Game.Maps -{ - public class MapInstanced : Map - { - public MapInstanced(uint id, uint expiry) : base(id, expiry, 0, Difficulty.Normal) { } - - public override void InitVisibilityDistance() - { - if (m_InstancedMaps.Empty()) - return; - //initialize visibility distances for all instance copies - foreach (var i in m_InstancedMaps) - i.Value.InitVisibilityDistance(); - } - - public override void Update(uint diff) - { - base.Update(diff); - - foreach (var pair in m_InstancedMaps.ToList()) - { - if (pair.Value.CanUnload(diff)) - { - if (!DestroyInstance(pair)) - { - //m_unloadTimer - } - } - else - pair.Value.Update(diff); - } - } - - public override void DelayedUpdate(uint t_diff) - { - foreach (var i in m_InstancedMaps) - i.Value.DelayedUpdate(t_diff); - - base.DelayedUpdate(t_diff); - } - - public override void UnloadAll() - { - // Unload instanced maps - foreach (var i in m_InstancedMaps) - i.Value.UnloadAll(); - - m_InstancedMaps.Clear(); - - base.UnloadAll(); - } - - public Map CreateInstanceForPlayer(uint mapId, Player player, uint loginInstanceId = 0) - { - if (GetId() != mapId || player == null) - return null; - - Map map = null; - uint newInstanceId; // instanceId of the resulting map - - if (IsBattlegroundOrArena()) - { - // instantiate or find existing bg map for player - // the instance id is set in Battlegroundid - newInstanceId = player.GetBattlegroundId(); - if (newInstanceId == 0) - return null; - - map = Global.MapMgr.FindMap(mapId, newInstanceId); - if (map == null) - { - Battleground bg = player.GetBattleground(); - if (bg) - map = CreateBattleground(newInstanceId, bg); - else - { - player.TeleportToBGEntryPoint(); - return null; - } - } - } - else if(IsDungeon()) - { - InstanceBind pBind = player.GetBoundInstance(GetId(), player.GetDifficultyID(GetEntry())); - InstanceSave pSave = pBind != null ? pBind.save : null; - - // priority: - // 1. player's permanent bind - // 2. player's current instance id if this is at login - // 3. group's current bind - // 4. player's current bind - if (pBind == null || !pBind.perm) - { - if (loginInstanceId != 0) // if the player has a saved instance id on login, we either use this instance or relocate him out (return null) - { - map = FindInstanceMap(loginInstanceId); - if (map == null && pSave != null && pSave.GetInstanceId() == loginInstanceId) - map = CreateInstance(loginInstanceId, pSave, pSave.GetDifficultyID(), player.GetTeamId()); - return map; - } - - InstanceBind groupBind; - Group group = player.GetGroup(); - // use the player's difficulty setting (it may not be the same as the group's) - if (group) - { - groupBind = group.GetBoundInstance(this); - if (groupBind != null) - { - // solo saves should be reset when entering a group's instance - player.UnbindInstance(GetId(), player.GetDifficultyID(GetEntry())); - pSave = groupBind.save; - } - } - } - if (pSave != null) - { - // solo/perm/group - newInstanceId = pSave.GetInstanceId(); - map = FindInstanceMap(newInstanceId); - // it is possible that the save exists but the map doesn't - if (map == null) - map = CreateInstance(newInstanceId, pSave, pSave.GetDifficultyID(), player.GetTeamId()); - } - else - { - // if no instanceId via group members or instance saves is found - // the instance will be created for the first time - newInstanceId = Global.MapMgr.GenerateInstanceId(); - - Difficulty diff = player.GetGroup() != null ? player.GetGroup().GetDifficultyID(GetEntry()) : player.GetDifficultyID(GetEntry()); - //Seems it is now possible, but I do not know if it should be allowed - map = FindInstanceMap(newInstanceId); - if (map == null) - map = CreateInstance(newInstanceId, null, diff, player.GetTeamId()); - } - } - else if (IsGarrison()) - { - newInstanceId = (uint)player.GetGUID().GetCounter(); - map = FindInstanceMap(newInstanceId); - if (!map) - map = CreateGarrison(newInstanceId, player); - } - - return map; - } - - InstanceMap CreateInstance(uint InstanceId, InstanceSave save, Difficulty difficulty, int teamId) - { - lock (_mapLock) - { - // make sure we have a valid map id - MapRecord entry = CliDB.MapStorage.LookupByKey(GetId()); - if (entry == null) - { - Log.outError(LogFilter.Maps, "CreateInstance: no record for map {0}", GetId()); - Cypher.Assert(false); - } - - // some instances only have one difficulty - Global.DB2Mgr.GetDownscaledMapDifficultyData(GetId(), ref difficulty); - - Log.outDebug(LogFilter.Maps, "MapInstanced.CreateInstance: {0} map instance {1} for {2} created with difficulty {3}", save != null ? "" : "new ", InstanceId, GetId(), difficulty); - - InstanceMap map = new(GetId(), GetGridExpiry(), InstanceId, difficulty, this, teamId); - Cypher.Assert(map.IsDungeon()); - - map.LoadRespawnTimes(); - map.LoadCorpseData(); - - bool load_data = save != null; - map.CreateInstanceData(load_data); - InstanceScenario instanceScenario = Global.ScenarioMgr.CreateInstanceScenario(map, teamId); - if (instanceScenario != null) - map.SetInstanceScenario(instanceScenario); - - if (WorldConfig.GetBoolValue(WorldCfg.InstancemapLoadGrids)) - map.LoadAllCells(); - - m_InstancedMaps[InstanceId] = map; - return map; - } - } - - BattlegroundMap CreateBattleground(uint InstanceId, Battleground bg) - { - lock (_mapLock) - { - Log.outDebug(LogFilter.Maps, "MapInstanced.CreateBattleground: map bg {0} for {1} created.", InstanceId, GetId()); - - BattlegroundMap map = new(GetId(), (uint)GetGridExpiry(), InstanceId, this, Difficulty.None); - Cypher.Assert(map.IsBattlegroundOrArena()); - map.SetBG(bg); - bg.SetBgMap(map); - - m_InstancedMaps[InstanceId] = map; - return map; - } - } - - GarrisonMap CreateGarrison(uint instanceId, Player owner) - { - lock (_mapLock) - { - GarrisonMap map = new(GetId(), GetGridExpiry(), instanceId, this, owner.GetGUID()); - Cypher.Assert(map.IsGarrison()); - - m_InstancedMaps[instanceId] = map; - return map; - } - } - - bool DestroyInstance(KeyValuePair pair) - { - pair.Value.RemoveAllPlayers(); - if (pair.Value.HavePlayers()) - return false; - - pair.Value.UnloadAll(); - // should only unload VMaps if this is the last instance and grid unloading is enabled - if (m_InstancedMaps.Count <= 1 && WorldConfig.GetBoolValue(WorldCfg.GridUnload)) - { - Global.VMapMgr.UnloadMap(pair.Value.GetId()); - Global.MMapMgr.UnloadMap(pair.Value.GetId()); - // in that case, unload grids of the base map, too - // so in the next map creation, (EnsureGridCreated actually) VMaps will be reloaded - base.UnloadAll(); - } - - // Free up the instance id and allow it to be reused for bgs and arenas (other instances are handled in the InstanceSaveMgr) - if (pair.Value.IsBattlegroundOrArena()) - Global.MapMgr.FreeInstanceId(pair.Value.GetInstanceId()); - - // erase map - pair.Value.Dispose(); - m_InstancedMaps.Remove(pair.Key); - - return true; - } - - public override EnterState CannotEnter(Player player) { return EnterState.CanEnter; } - - public Map FindInstanceMap(uint instanceId) - { - return m_InstancedMaps.LookupByKey(instanceId); - } - - public Dictionary GetInstancedMaps() { return m_InstancedMaps; } - - Dictionary m_InstancedMaps = new(); - } - - public class InstanceTemplate - { - public uint Parent; - public uint ScriptId; - } - - public class InstanceBind - { - public InstanceSave save; - public bool perm; - public BindExtensionState extendState; - } -} diff --git a/Source/Game/Maps/Map.cs b/Source/Game/Maps/Map.cs index 57e627e13..58ca99b88 100644 --- a/Source/Game/Maps/Map.cs +++ b/Source/Game/Maps/Map.cs @@ -37,7 +37,7 @@ namespace Game.Maps { public class Map : IDisposable { - public Map(uint id, long expiry, uint instanceId, Difficulty spawnmode, Map parent = null) + public Map(uint id, long expiry, uint instanceId, Difficulty spawnmode) { i_mapRecord = CliDB.MapStorage.LookupByKey(id); i_spawnMode = spawnmode; @@ -227,14 +227,6 @@ namespace Game.Maps } void EnsureGridCreated(GridCoord p) - { - lock (_gridLock) - { - EnsureGridCreated_i(p); - } - } - - void EnsureGridCreated_i(GridCoord p) { if (GetGrid(p.X_coord, p.Y_coord) == null) { @@ -330,7 +322,7 @@ namespace Game.Maps { EnsureGridLoadedForActiveObject(new Cell(x, y), obj); } - + public virtual bool AddPlayerToMap(Player player, bool initPlayer = true) { CellCoord cellCoord = GridDefines.ComputeCellCoord(player.GetPositionX(), player.GetPositionY()); @@ -416,7 +408,7 @@ namespace Game.Maps player.SendPacket(updateWorldState); } } - + void InitializeObject(WorldObject obj) { if (!obj.IsTypeId(TypeId.Unit) || !obj.IsTypeId(TypeId.GameObject)) @@ -1529,7 +1521,7 @@ namespace Game.Maps _corpsesByCell.Clear(); _corpsesByPlayer.Clear(); _corpseBones.Clear(); - } + } public static bool IsInWMOInterior(uint mogpFlags) { @@ -1662,6 +1654,93 @@ namespace Game.Maps return result; } + public static EnterState PlayerCannotEnter(uint mapid, Player player, bool loginCheck) + { + var entry = CliDB.MapStorage.LookupByKey(mapid); + if (entry == null) + return EnterState.CannotEnterNoEntry; + + if (!entry.IsDungeon()) + return EnterState.CanEnter; + + Difficulty targetDifficulty, requestedDifficulty; + targetDifficulty = requestedDifficulty = player.GetDifficultyID(entry); + // Get the highest available difficulty if current setting is higher than the instance allows + var mapDiff = Global.DB2Mgr.GetDownscaledMapDifficultyData(mapid, ref targetDifficulty); + if (mapDiff == null) + return EnterState.CannotEnterDifficultyUnavailable; + + //Bypass checks for GMs + if (player.IsGameMaster()) + return EnterState.CanEnter; + + //Other requirements + if (!player.Satisfy(Global.ObjectMgr.GetAccessRequirement(mapid, targetDifficulty), mapid, true)) + return EnterState.CannotEnterUnspecifiedReason; + + string mapName = entry.MapName[Global.WorldMgr.GetDefaultDbcLocale()]; + + Group group = player.GetGroup(); + if (entry.IsRaid() && (int)entry.Expansion() >= WorldConfig.GetIntValue(WorldCfg.Expansion)) // can only enter in a raid group but raids from old expansion don't need a group + if ((!group || !group.IsRaidGroup()) && !WorldConfig.GetBoolValue(WorldCfg.InstanceIgnoreRaid)) + return EnterState.CannotEnterNotInRaid; + + if (!player.IsAlive()) + { + if (player.HasCorpse()) + { + // let enter in ghost mode in instance that connected to inner instance with corpse + uint corpseMap = player.GetCorpseLocation().GetMapId(); + do + { + if (corpseMap == mapid) + break; + + InstanceTemplate corpseInstance = Global.ObjectMgr.GetInstanceTemplate(corpseMap); + corpseMap = corpseInstance != null ? corpseInstance.Parent : 0; + } while (corpseMap != 0); + + if (corpseMap == 0) + return EnterState.CannotEnterCorpseInDifferentInstance; + + Log.outDebug(LogFilter.Maps, $"MAP: Player '{player.GetName()}' has corpse in instance '{mapName}' and can enter."); + } + else + Log.outDebug(LogFilter.Maps, $"Map::CanPlayerEnter - player '{player.GetName()}' is dead but does not have a corpse!"); + } + + //Get instance where player's group is bound & its map + if (!loginCheck && group) + { + InstanceBind boundInstance = group.GetBoundInstance(entry); + if (boundInstance != null && boundInstance.save != null) + { + Map boundMap = Global.MapMgr.FindMap(mapid, boundInstance.save.GetInstanceId()); + if (boundMap != null) + { + EnterState denyReason = boundMap.CannotEnter(player); + if (denyReason != 0) + return denyReason; + } + } + } + + // players are only allowed to enter 5 instances per hour + if (entry.IsDungeon() && (!player.GetGroup() || (player.GetGroup() && !player.GetGroup().IsLFGGroup()))) + { + uint instanceIdToCheck = 0; + InstanceSave save = player.GetInstanceSave(mapid); + if (save != null) + instanceIdToCheck = save.GetInstanceId(); + + // instanceId can never be 0 - will not be found + if (!player.CheckInstanceCount(instanceIdToCheck) && !player.IsDead()) + return EnterState.CannotEnterTooManyInstances; + } + + return EnterState.CanEnter; + } + public string GetMapName() { return i_mapRecord.MapName[Global.WorldMgr.GetDefaultDbcLocale()]; @@ -1960,7 +2039,7 @@ namespace Game.Maps if ((types & SpawnObjectTypeMask.GameObject) != 0) PushRespawnInfoFrom(respawnData, _gameObjectRespawnTimesBySpawnId); } - + public RespawnInfo GetRespawnInfo(SpawnObjectType type, ulong spawnId) { var map = GetRespawnMapForType(type); @@ -1987,7 +2066,7 @@ namespace Game.Maps default: Cypher.Assert(false); return null; - } + } } void UnloadAllRespawnInfos() // delete everything from memory @@ -2150,7 +2229,7 @@ namespace Game.Maps } public bool ShouldBeSpawnedOnGridLoad(ulong spawnId) { return ShouldBeSpawnedOnGridLoad(SpawnData.TypeFor(), spawnId); } - + bool ShouldBeSpawnedOnGridLoad(SpawnObjectType type, ulong spawnId) { Cypher.Assert(SpawnData.TypeHasData(type)); @@ -3100,7 +3179,7 @@ namespace Game.Maps { return $"Id: {GetId()} InstanceId: {GetInstanceId()} Difficulty: {GetDifficultyID()} HasPlayers: {HavePlayers()}"; } - + public MapRecord GetEntry() { return i_mapRecord; @@ -3152,7 +3231,7 @@ namespace Game.Maps } public TerrainInfo GetTerrain() { return m_terrain; } - + public uint GetInstanceId() { return i_InstanceId; @@ -3246,7 +3325,7 @@ namespace Game.Maps { return i_mapRecord != null && i_mapRecord.IsScenario(); } - + public bool IsGarrison() { return i_mapRecord != null && i_mapRecord.IsGarrison(); @@ -3309,7 +3388,7 @@ namespace Game.Maps { return m_activeNonPlayers.Count; } - + public Dictionary GetObjectsStore() { return _objectsStore; } public MultiMap GetCreatureBySpawnIdStore() { return _creatureBySpawnIdStore; } @@ -3328,7 +3407,6 @@ namespace Game.Maps return _corpsesByPlayer.LookupByKey(ownerGuid); } - public MapInstanced ToMapInstanced() { return Instanceable() ? (this as MapInstanced) : null; } public InstanceMap ToInstanceMap() { return IsDungeon() ? (this as InstanceMap) : null; } public BattlegroundMap ToBattlegroundMap() { return IsBattlegroundOrArena() ? (this as BattlegroundMap) : null; } @@ -3376,7 +3454,7 @@ namespace Game.Maps public long GetCreatureRespawnTime(ulong spawnId) { return GetRespawnTime(SpawnObjectType.Creature, spawnId); } public long GetGORespawnTime(ulong spawnId) { return GetRespawnTime(SpawnObjectType.GameObject, spawnId); } - + void SetTimer(uint t) { i_gridExpiry = t < MapConst.MinGridDelay ? MapConst.MinGridDelay : t; @@ -3412,7 +3490,7 @@ namespace Game.Maps { return _objectsStore.LookupByKey(guid) as SceneObject; } - + public Conversation GetConversation(ObjectGuid guid) { return (Conversation)_objectsStore.LookupByKey(guid); @@ -3858,7 +3936,7 @@ namespace Game.Maps } return gameobject; } - + private Unit _GetScriptUnit(WorldObject obj, bool isSource, ScriptInfo scriptInfo) { Unit unit = null; @@ -4601,12 +4679,10 @@ namespace Game.Maps Global.MapMgr.DecreaseScheduledScriptCount(); } } - #endregion #region Fields internal object _mapLock = new(); - object _gridLock = new(); bool _creatureToMoveLock; List creaturesToMove = new(); @@ -4677,8 +4753,7 @@ namespace Game.Maps public class InstanceMap : Map { - public InstanceMap(uint id, long expiry, uint InstanceId, Difficulty spawnMode, Map parent, int instanceTeam) - : base(id, expiry, InstanceId, spawnMode, parent) + public InstanceMap(uint id, long expiry, uint InstanceId, Difficulty spawnMode, int instanceTeam) : base(id, expiry, InstanceId, spawnMode) { scriptTeam = instanceTeam; @@ -4734,133 +4809,123 @@ namespace Game.Maps public override bool AddPlayerToMap(Player player, bool initPlayer = true) { - // @todo Not sure about checking player level: already done in HandleAreaTriggerOpcode - // GMs still can teleport player in instance. - // Is it needed? - lock (_mapLock) + Group group = player.GetGroup(); + + // increase current instances (hourly limit) + if (!group || !group.IsLFGGroup()) + player.AddInstanceEnterTime(GetInstanceId(), GameTime.GetGameTime()); + + // get or create an instance save for the map + InstanceSave mapSave = Global.InstanceSaveMgr.GetInstanceSave(GetInstanceId()); + if (mapSave == null) { - // Dungeon only code - if (IsDungeon()) + Log.outInfo(LogFilter.Maps, "InstanceMap.Add: creating instance save for map {0} spawnmode {1} with instance id {2}", GetId(), GetDifficultyID(), GetInstanceId()); + mapSave = Global.InstanceSaveMgr.AddInstanceSave(GetId(), GetInstanceId(), GetDifficultyID(), 0, 0, true); + } + + Cypher.Assert(mapSave != null); + + // check for existing instance binds + InstanceBind playerBind = player.GetBoundInstance(GetId(), GetDifficultyID()); + if (playerBind != null && playerBind.perm) + { + // cannot enter other instances if bound permanently + if (playerBind.save != mapSave) { - Group group = player.GetGroup(); - - // increase current instances (hourly limit) - if (!group || !group.IsLFGGroup()) - player.AddInstanceEnterTime(GetInstanceId(), GameTime.GetGameTime()); - - // get or create an instance save for the map - InstanceSave mapSave = Global.InstanceSaveMgr.GetInstanceSave(GetInstanceId()); - if (mapSave == null) + Log.outError(LogFilter.Maps, "InstanceMap.Add: player {0}({1}) is permanently bound to instance {2} {3}, {4}, {5}, {6}, {7}, {8} but he is being put into instance {9} {10}, {11}, {12}, {13}, {14}, {15}", + player.GetName(), player.GetGUID().ToString(), GetMapName(), playerBind.save.GetMapId(), + playerBind.save.GetInstanceId(), playerBind.save.GetDifficultyID(), + playerBind.save.GetPlayerCount(), playerBind.save.GetGroupCount(), + playerBind.save.CanReset(), GetMapName(), mapSave.GetMapId(), mapSave.GetInstanceId(), + mapSave.GetDifficultyID(), mapSave.GetPlayerCount(), mapSave.GetGroupCount(), + mapSave.CanReset()); + return false; + } + } + else + { + if (group) + { + // solo saves should have been reset when the map was loaded + InstanceBind groupBind = group.GetBoundInstance(this); + if (playerBind != null && playerBind.save != mapSave) { - Log.outInfo(LogFilter.Maps, "InstanceMap.Add: creating instance save for map {0} spawnmode {1} with instance id {2}", GetId(), GetDifficultyID(), GetInstanceId()); - mapSave = Global.InstanceSaveMgr.AddInstanceSave(GetId(), GetInstanceId(), GetDifficultyID(), 0, 0, true); - } - - Cypher.Assert(mapSave != null); - - // check for existing instance binds - InstanceBind playerBind = player.GetBoundInstance(GetId(), GetDifficultyID()); - if (playerBind != null && playerBind.perm) - { - // cannot enter other instances if bound permanently - if (playerBind.save != mapSave) - { - Log.outError(LogFilter.Maps, "InstanceMap.Add: player {0}({1}) is permanently bound to instance {2} {3}, {4}, {5}, {6}, {7}, {8} but he is being put into instance {9} {10}, {11}, {12}, {13}, {14}, {15}", - player.GetName(), player.GetGUID().ToString(), GetMapName(), playerBind.save.GetMapId(), - playerBind.save.GetInstanceId(), playerBind.save.GetDifficultyID(), - playerBind.save.GetPlayerCount(), playerBind.save.GetGroupCount(), - playerBind.save.CanReset(), GetMapName(), mapSave.GetMapId(), mapSave.GetInstanceId(), - mapSave.GetDifficultyID(), mapSave.GetPlayerCount(), mapSave.GetGroupCount(), - mapSave.CanReset()); - return false; - } + Log.outError(LogFilter.Maps, + "InstanceMapAdd: player {0}({1}) is being put into instance {2} {3}, {4}, {5}, {6}, {7}, {8} but he is in group {9} and is bound to instance {10}, {11}, {12}, {13}, {14}, {15}!", + player.GetName(), player.GetGUID().ToString(), GetMapName(), mapSave.GetMapId(), mapSave.GetInstanceId(), + mapSave.GetDifficultyID(), mapSave.GetPlayerCount(), mapSave.GetGroupCount(), + mapSave.CanReset(), group.GetLeaderGUID().ToString(), + playerBind.save.GetMapId(), playerBind.save.GetInstanceId(), + playerBind.save.GetDifficultyID(), playerBind.save.GetPlayerCount(), + playerBind.save.GetGroupCount(), playerBind.save.CanReset()); + if (groupBind != null) + Log.outError(LogFilter.Maps, + "InstanceMap.Add: the group is bound to the instance {0} {1}, {2}, {3}, {4}, {5}, {6}", + GetMapName(), groupBind.save.GetMapId(), groupBind.save.GetInstanceId(), + groupBind.save.GetDifficultyID(), groupBind.save.GetPlayerCount(), + groupBind.save.GetGroupCount(), groupBind.save.CanReset()); + Cypher.Assert(false); + return false; } + // bind to the group or keep using the group save + if (groupBind == null) + group.BindToInstance(mapSave, false); else { - if (group) + // cannot jump to a different instance without resetting it + if (groupBind.save != mapSave) { - // solo saves should have been reset when the map was loaded - InstanceBind groupBind = group.GetBoundInstance(this); - if (playerBind != null && playerBind.save != mapSave) - { - Log.outError(LogFilter.Maps, - "InstanceMapAdd: player {0}({1}) is being put into instance {2} {3}, {4}, {5}, {6}, {7}, {8} but he is in group {9} and is bound to instance {10}, {11}, {12}, {13}, {14}, {15}!", - player.GetName(), player.GetGUID().ToString(), GetMapName(), mapSave.GetMapId(), mapSave.GetInstanceId(), - mapSave.GetDifficultyID(), mapSave.GetPlayerCount(), mapSave.GetGroupCount(), - mapSave.CanReset(), group.GetLeaderGUID().ToString(), - playerBind.save.GetMapId(), playerBind.save.GetInstanceId(), - playerBind.save.GetDifficultyID(), playerBind.save.GetPlayerCount(), - playerBind.save.GetGroupCount(), playerBind.save.CanReset()); - if (groupBind != null) - Log.outError(LogFilter.Maps, - "InstanceMap.Add: the group is bound to the instance {0} {1}, {2}, {3}, {4}, {5}, {6}", - GetMapName(), groupBind.save.GetMapId(), groupBind.save.GetInstanceId(), - groupBind.save.GetDifficultyID(), groupBind.save.GetPlayerCount(), - groupBind.save.GetGroupCount(), groupBind.save.CanReset()); - Cypher.Assert(false); - return false; - } - // bind to the group or keep using the group save - if (groupBind == null) - group.BindToInstance(mapSave, false); + Log.outError(LogFilter.Maps, + "InstanceMap.Add: player {0}({1}) is being put into instance {2}, {3}, {4} but he is in group {5} which is bound to instance {6}, {7}, {8}!", + player.GetName(), player.GetGUID().ToString(), mapSave.GetMapId(), mapSave.GetInstanceId(), + mapSave.GetDifficultyID(), group.GetLeaderGUID().ToString(), + groupBind.save.GetMapId(), groupBind.save.GetInstanceId(), + groupBind.save.GetDifficultyID()); + Log.outError(LogFilter.Maps, "MapSave players: {0}, group count: {1}", + mapSave.GetPlayerCount(), mapSave.GetGroupCount()); + if (groupBind.save != null) + Log.outError(LogFilter.Maps, "GroupBind save players: {0}, group count: {1}", + groupBind.save.GetPlayerCount(), groupBind.save.GetGroupCount()); else - { - // cannot jump to a different instance without resetting it - if (groupBind.save != mapSave) - { - Log.outError(LogFilter.Maps, - "InstanceMap.Add: player {0}({1}) is being put into instance {2}, {3}, {4} but he is in group {5} which is bound to instance {6}, {7}, {8}!", - player.GetName(), player.GetGUID().ToString(), mapSave.GetMapId(), mapSave.GetInstanceId(), - mapSave.GetDifficultyID(), group.GetLeaderGUID().ToString(), - groupBind.save.GetMapId(), groupBind.save.GetInstanceId(), - groupBind.save.GetDifficultyID()); - Log.outError(LogFilter.Maps, "MapSave players: {0}, group count: {1}", - mapSave.GetPlayerCount(), mapSave.GetGroupCount()); - if (groupBind.save != null) - Log.outError(LogFilter.Maps, "GroupBind save players: {0}, group count: {1}", - groupBind.save.GetPlayerCount(), groupBind.save.GetGroupCount()); - else - Log.outError(LogFilter.Maps, "GroupBind save NULL"); - return false; - } - // if the group/leader is permanently bound to the instance - // players also become permanently bound when they enter - if (groupBind.perm) - { - PendingRaidLock pendingRaidLock = new(); - pendingRaidLock.TimeUntilLock = 60000; - pendingRaidLock.CompletedMask = i_data != null ? i_data.GetCompletedEncounterMask() : 0; - pendingRaidLock.Extending = false; - pendingRaidLock.WarningOnly = false; // events it throws: 1 : INSTANCE_LOCK_WARNING 0 : INSTANCE_LOCK_STOP / INSTANCE_LOCK_START - player.SendPacket(pendingRaidLock); - player.SetPendingBind(mapSave.GetInstanceId(), 60000); - } - } + Log.outError(LogFilter.Maps, "GroupBind save NULL"); + return false; } - else + // if the group/leader is permanently bound to the instance + // players also become permanently bound when they enter + if (groupBind.perm) { - // set up a solo bind or continue using it - if (playerBind == null) - player.BindToInstance(mapSave, false); - else - // cannot jump to a different instance without resetting it - Cypher.Assert(playerBind.save == mapSave); + PendingRaidLock pendingRaidLock = new(); + pendingRaidLock.TimeUntilLock = 60000; + pendingRaidLock.CompletedMask = i_data != null ? i_data.GetCompletedEncounterMask() : 0; + pendingRaidLock.Extending = false; + pendingRaidLock.WarningOnly = false; // events it throws: 1 : INSTANCE_LOCK_WARNING 0 : INSTANCE_LOCK_STOP / INSTANCE_LOCK_START + player.SendPacket(pendingRaidLock); + player.SetPendingBind(mapSave.GetInstanceId(), 60000); } } } - - // for normal instances cancel the reset schedule when the - // first player enters (no players yet) - SetResetSchedule(false); - - Log.outInfo(LogFilter.Maps, "MAP: Player '{0}' entered instance '{1}' of map '{2}'", player.GetName(), - GetInstanceId(), GetMapName()); - // initialize unload state - m_unloadTimer = 0; - m_resetAfterUnload = false; - m_unloadWhenEmpty = false; + else + { + // set up a solo bind or continue using it + if (playerBind == null) + player.BindToInstance(mapSave, false); + else + // cannot jump to a different instance without resetting it + Cypher.Assert(playerBind.save == mapSave); + } } + // for normal instances cancel the reset schedule when the + // first player enters (no players yet) + SetResetSchedule(false); + + Log.outInfo(LogFilter.Maps, "MAP: Player '{0}' entered instance '{1}' of map '{2}'", player.GetName(), + GetInstanceId(), GetMapName()); + // initialize unload state + m_unloadTimer = 0; + m_resetAfterUnload = false; + m_unloadWhenEmpty = false; + // this will acquire the same mutex so it cannot be in the previous block base.AddPlayerToMap(player, initPlayer); @@ -5117,7 +5182,7 @@ namespace Game.Maps public int GetTeamIdInInstance() { return scriptTeam; } public Team GetTeamInInstance() { return scriptTeam == TeamId.Alliance ? Team.Alliance : Team.Horde; } - + public uint GetScriptId() { return i_script_id; @@ -5127,7 +5192,7 @@ namespace Game.Maps { return $"{base.GetDebugInfo()}\nScriptId: {GetScriptId()} ScriptName: {GetScriptName()}"; } - + public InstanceScript GetInstanceScript() { return i_data; @@ -5146,8 +5211,8 @@ namespace Game.Maps public class BattlegroundMap : Map { - public BattlegroundMap(uint id, uint expiry, uint InstanceId, Map _parent, Difficulty spawnMode) - : base(id, expiry, InstanceId, spawnMode, _parent) + public BattlegroundMap(uint id, uint expiry, uint InstanceId, Difficulty spawnMode) + : base(id, expiry, InstanceId, spawnMode) { InitVisibilityDistance(); } @@ -5175,9 +5240,7 @@ namespace Game.Maps public override bool AddPlayerToMap(Player player, bool initPlayer = true) { - lock (_mapLock) - player.m_InstanceValid = true; - + player.m_InstanceValid = true; return base.AddPlayerToMap(player, initPlayer); } diff --git a/Source/Game/Maps/MapManager.cs b/Source/Game/Maps/MapManager.cs index b7443b04f..5ec0b8ec1 100644 --- a/Source/Game/Maps/MapManager.cs +++ b/Source/Game/Maps/MapManager.cs @@ -17,12 +17,15 @@ using Framework.Constants; using Framework.Database; +using Game.BattleGrounds; using Game.DataStorage; +using Game.Garrisons; using Game.Groups; using Game.Maps; using System; using System.Collections; using System.Collections.Generic; +using System.Linq; namespace Game.Entities { @@ -49,161 +52,202 @@ namespace Game.Entities pair.Value.InitVisibilityDistance(); } - public Map CreateBaseMap(uint id) + Map FindMap_i(uint mapId, uint instanceId) { - Map map = FindBaseMap(id); - if (map == null) - { - var entry = CliDB.MapStorage.LookupByKey(id); - Cypher.Assert(entry != null); - if (entry.ParentMapID != -1 || entry.CosmeticParentMapID != -1) - { - CreateBaseMap((uint)(entry.ParentMapID != -1 ? entry.ParentMapID : entry.CosmeticParentMapID)); - - // must have been created by parent map - map = FindBaseMap(id); - return map; - } - - lock(_mapsLock) - map = CreateBaseMap_i(entry); - } - Cypher.Assert(map != null); - return map; + return i_maps.LookupByKey((mapId, instanceId)); } - Map CreateBaseMap_i(MapRecord mapEntry) + Map CreateWorldMap(uint mapId, uint instanceId) { - Map map; - if (mapEntry.Instanceable()) - map = new MapInstanced(mapEntry.Id, i_gridCleanUpDelay); - else - map = new Map(mapEntry.Id, i_gridCleanUpDelay, 0, Difficulty.None); + Map map = new Map(mapId, i_gridCleanUpDelay, instanceId, Difficulty.None); + map.LoadRespawnTimes(); + map.LoadCorpseData(); - i_maps[mapEntry.Id] = map; - - if (!mapEntry.Instanceable()) - { - map.LoadRespawnTimes(); - map.LoadCorpseData(); - } + if (WorldConfig.GetBoolValue(WorldCfg.BasemapLoadGrids)) + map.LoadAllCells(); return map; } - public Map FindBaseNonInstanceMap(uint mapId) + InstanceMap CreateInstance(uint mapId, uint instanceId, InstanceSave save, Difficulty difficulty, int team) { - Map map = FindBaseMap(mapId); - if (map != null && map.Instanceable()) - return null; - return map; - } - - public Map CreateMap(uint id, Player player, uint loginInstanceId = 0) - { - Map m = CreateBaseMap(id); - - if (m != null && m.Instanceable()) - m = ((MapInstanced)m).CreateInstanceForPlayer(id, player, loginInstanceId); - - return m; - } - - public Map FindMap(uint mapid, uint instanceId) - { - Map map = FindBaseMap(mapid); - if (map == null) - return null; - - if (!map.Instanceable()) - return instanceId == 0 ? map : null; - - return ((MapInstanced)map).FindInstanceMap(instanceId); - } - - public EnterState PlayerCannotEnter(uint mapid, Player player, bool loginCheck = false) - { - MapRecord entry = CliDB.MapStorage.LookupByKey(mapid); + // make sure we have a valid map id + var entry = CliDB.MapStorage.LookupByKey(mapId); if (entry == null) - return EnterState.CannotEnterNoEntry; - - if (!entry.IsDungeon()) - return EnterState.CanEnter; - - Difficulty targetDifficulty = player.GetDifficultyID(entry); - // Get the highest available difficulty if current setting is higher than the instance allows - MapDifficultyRecord mapDiff = Global.DB2Mgr.GetDownscaledMapDifficultyData(entry.Id, ref targetDifficulty); - if (mapDiff == null) - return EnterState.CannotEnterDifficultyUnavailable; - - //Bypass checks for GMs - if (player.IsGameMaster()) - return EnterState.CanEnter; - - //Other requirements - if (!player.Satisfy(Global.ObjectMgr.GetAccessRequirement(mapid, targetDifficulty), mapid, true)) - return EnterState.CannotEnterUnspecifiedReason; - - string mapName = entry.MapName[Global.WorldMgr.GetDefaultDbcLocale()]; - - Group group = player.GetGroup(); - if (entry.IsRaid() && entry.Expansion() >= (Expansion)WorldConfig.GetIntValue(WorldCfg.Expansion)) // can only enter in a raid group but raids from old expansion don't need a group - if ((!group || !group.IsRaidGroup()) && WorldConfig.GetBoolValue(WorldCfg.InstanceIgnoreRaid)) - return EnterState.CannotEnterNotInRaid; - - if (!player.IsAlive()) { - if (player.HasCorpse()) - { - // let enter in ghost mode in instance that connected to inner instance with corpse - uint corpseMap = player.GetCorpseLocation().GetMapId(); - do - { - if (corpseMap == mapid) - break; - - InstanceTemplate corpseInstance = Global.ObjectMgr.GetInstanceTemplate(corpseMap); - corpseMap = corpseInstance != null ? corpseInstance.Parent : 0; - } while (corpseMap != 0); - - if (corpseMap == 0) - return EnterState.CannotEnterCorpseInDifferentInstance; - - Log.outDebug(LogFilter.Maps, "MAP: Player '{0}' has corpse in instance '{1}' and can enter.", player.GetName(), mapName); - } - else - Log.outDebug(LogFilter.Maps, "Map.CanPlayerEnter - player '{0}' is dead but does not have a corpse!", player.GetName()); + Log.outError(LogFilter.Maps, $"CreateInstance: no entry for map {mapId}"); + //ABORT(); + return null; } - //Get instance where player's group is bound & its map - if (!loginCheck && group) + // some instances only have one difficulty + Global.DB2Mgr.GetDownscaledMapDifficultyData(mapId, ref difficulty); + + Log.outDebug(LogFilter.Maps, $"MapInstanced::CreateInstance: {(save != null ? "" : "new ")} map instance {instanceId} for {mapId} created with difficulty {difficulty}"); + + InstanceMap map = new InstanceMap(mapId, i_gridCleanUpDelay, instanceId, difficulty, team); + Cypher.Assert(map.IsDungeon()); + + map.LoadRespawnTimes(); + map.LoadCorpseData(); + + bool load_data = save != null; + map.CreateInstanceData(load_data); + var instanceScenario = Global.ScenarioMgr.CreateInstanceScenario(map, team); + if (instanceScenario != null) + map.SetInstanceScenario(instanceScenario); + + if (WorldConfig.GetBoolValue(WorldCfg.InstancemapLoadGrids)) + map.LoadAllCells(); + + return map; + } + + BattlegroundMap CreateBattleground(uint mapId, uint instanceId, Battleground bg) + { + Log.outDebug(LogFilter.Maps, $"MapInstanced::CreateBattleground: map bg {instanceId} for {mapId} created."); + + BattlegroundMap map = new BattlegroundMap(mapId, i_gridCleanUpDelay, instanceId, Difficulty.None); + Cypher.Assert(map.IsBattlegroundOrArena()); + map.SetBG(bg); + bg.SetBgMap(map); + return map; + } + + GarrisonMap CreateGarrison(uint mapId, uint instanceId, Player owner) + { + GarrisonMap map = new GarrisonMap(mapId, i_gridCleanUpDelay, instanceId, owner.GetGUID()); + Cypher.Assert(map.IsGarrison()); + return map; + } + + /// + /// create the instance if it's not created already + /// the player is not actually added to the instance(only in InstanceMap::Add) + /// + /// + /// + /// + /// the right instance for the object, based on its InstanceId + public Map CreateMap(uint mapId, Player player, uint loginInstanceId = 0) + { + if (!player) + return null; + + var entry = CliDB.MapStorage.LookupByKey(mapId); + if (entry == null) + return null; + + lock (_mapsLock) { - InstanceBind boundInstance = group.GetBoundInstance(entry); - if (boundInstance != null && boundInstance.save != null) + Map map = null; + uint newInstanceId = 0; // instanceId of the resulting map + + if (entry.IsBattlegroundOrArena()) { - Map boundMap = FindMap(mapid, boundInstance.save.GetInstanceId()); - if (boundMap != null) + // instantiate or find existing bg map for player + // the instance id is set in battlegroundid + newInstanceId = player.GetBattlegroundId(); + if (newInstanceId == 0) + return null; + + map = FindMap(mapId, newInstanceId); + if (!map) { - EnterState denyReason = boundMap.CannotEnter(player); - if (denyReason != 0) - return denyReason; + Battleground bg = player.GetBattleground(); + if (bg != null) + map = CreateBattleground(mapId, newInstanceId, bg); + else + { + player.TeleportToBGEntryPoint(); + return null; + } } } - } - // players are only allowed to enter 10 instances per hour - if (entry.IsDungeon() && (player.GetGroup() == null || (player.GetGroup() != null && !player.GetGroup().IsLFGGroup()))) - { - uint instaceIdToCheck = 0; - InstanceSave save = player.GetInstanceSave(mapid); - if (save != null) - instaceIdToCheck = save.GetInstanceId(); + else if (entry.IsDungeon()) + { + InstanceBind pBind = player.GetBoundInstance(mapId, player.GetDifficultyID(entry)); + InstanceSave pSave = pBind != null ? pBind.save : null; - // instanceId can never be 0 - will not be found - if (!player.CheckInstanceCount(instaceIdToCheck) && !player.IsDead()) - return EnterState.CannotEnterTooManyInstances; - } + // priority: + // 1. player's permanent bind + // 2. player's current instance id if this is at login + // 3. group's current bind + // 4. player's current bind + if (pBind == null || !pBind.perm) + { + if (loginInstanceId != 0) // if the player has a saved instance id on login, we either use this instance or relocate him out (return null) + { + map = FindMap_i(mapId, loginInstanceId); + if (map == null && pSave != null && pSave.GetInstanceId() == loginInstanceId) + map = CreateInstance(mapId, loginInstanceId, pSave, pSave.GetDifficultyID(), player.GetTeamId()); + return map; + } - return EnterState.CanEnter; + InstanceBind groupBind = null; + Group group = player.GetGroup(); + // use the player's difficulty setting (it may not be the same as the group's) + if (group != null) + { + groupBind = group.GetBoundInstance(entry); + if (groupBind != null) + { + // solo saves should be reset when entering a group's instance + player.UnbindInstance(mapId, player.GetDifficultyID(entry)); + pSave = groupBind.save; + } + } + } + if (pSave != null) + { + // solo/perm/group + newInstanceId = pSave.GetInstanceId(); + map = FindMap_i(mapId, newInstanceId); + // it is possible that the save exists but the map doesn't + if (!map) + map = CreateInstance(mapId, newInstanceId, pSave, pSave.GetDifficultyID(), player.GetTeamId()); + } + else + { + Difficulty diff = player.GetGroup() ? player.GetGroup().GetDifficultyID(entry) : player.GetDifficultyID(entry); + + // if no instanceId via group members or instance saves is found + // the instance will be created for the first time + newInstanceId = GenerateInstanceId(); + + //Seems it is now possible, but I do not know if it should be allowed + //ASSERT(!FindInstanceMap(NewInstanceId)); + map = FindMap_i(mapId, newInstanceId); + if (!map) + map = CreateInstance(mapId, newInstanceId, null, diff, player.GetTeamId()); + } + } + else if (entry.IsGarrison()) + { + newInstanceId = (uint)player.GetGUID().GetCounter(); + map = FindMap_i(mapId, newInstanceId); + if (!map) + map = CreateGarrison(mapId, newInstanceId, player); + } + else + { + newInstanceId = 0; + map = FindMap_i(mapId, newInstanceId); + if (!map) + map = CreateWorldMap(mapId, newInstanceId); + } + + if (map) + i_maps[(map.GetId(), map.GetInstanceId())] = map; + + return map; + } + } + + public Map FindMap(uint mapId, uint instanceId) + { + lock (_mapsLock) + return FindMap_i(mapId, instanceId); } public void Update(uint diff) @@ -213,8 +257,16 @@ namespace Game.Entities return; var time = (uint)i_timer.GetCurrent(); - foreach (var map in i_maps.Values) + foreach (var (key, map) in i_maps) { + if (map.CanUnload(diff)) + { + if (DestroyMap(map)) + i_maps.Remove(key); + + continue; + } + if (m_updater != null) m_updater.ScheduleUpdate(map, (uint)i_timer.GetCurrent()); else @@ -230,6 +282,23 @@ namespace Game.Entities i_timer.SetCurrent(0); } + bool DestroyMap(Map map) + { + map.RemoveAllPlayers(); + if (map.HavePlayers()) + return false; + + map.UnloadAll(); + + // Free up the instance id and allow it to be reused for bgs and arenas (other instances are handled in the InstanceSaveMgr) + if (map.IsBattlegroundOrArena()) + FreeInstanceId(map.GetInstanceId()); + + // erase map + map.Dispose(); + return true; + } + public bool IsValidMAP(uint mapId) { return CliDB.MapStorage.ContainsKey(mapId); @@ -253,38 +322,13 @@ namespace Game.Entities public uint GetNumInstances() { lock (_mapsLock) - { - uint ret = 0; - foreach (var pair in i_maps) - { - Map map = pair.Value; - if (!map.IsDungeon()) - continue; - - var maps = ((MapInstanced)map).GetInstancedMaps(); - ret += (uint)maps.Count; - } - return ret; - } + return (uint)i_maps.Count(pair => pair.Value.IsDungeon()); } public uint GetNumPlayersInInstances() { lock (_mapsLock) - { - uint ret = 0; - foreach (var pair in i_maps) - { - Map map = pair.Value; - if (!map.IsDungeon()) - continue; - - var maps = ((MapInstanced)map).GetInstancedMaps(); - foreach (var imap in maps) - ret += (uint)imap.Value.GetPlayers().Count; - } - return ret; - } + return (uint)i_maps.Sum(pair => pair.Value.IsDungeon() ? pair.Value.GetPlayers().Count : 0); } public void InitInstanceIds() @@ -375,27 +419,12 @@ namespace Game.Entities public void SetNextInstanceId(uint nextInstanceId) { _nextInstanceId = nextInstanceId; } - Map FindBaseMap(uint mapId) - { - return i_maps.LookupByKey(mapId); - } - public void DoForAllMaps(Action worker) { lock (_mapsLock) { - foreach (var map in i_maps.Values) - { - MapInstanced mapInstanced = map.ToMapInstanced(); - if (mapInstanced) - { - var instances = mapInstanced.GetInstancedMaps(); - foreach (var instance in instances.Values) - worker(instance); - } - else - worker(map); - } + foreach (var (_, map) in i_maps) + worker(map); } } @@ -403,19 +432,9 @@ namespace Game.Entities { lock (_mapsLock) { - var map = i_maps.LookupByKey(mapId); - if (map != null) - { - MapInstanced mapInstanced = map.ToMapInstanced(); - if (mapInstanced) - { - var instances = mapInstanced.GetInstancedMaps(); - foreach (var instance in instances) - worker(instance.Value); - } - else - worker(map); - } + var list = i_maps.Where(pair => pair.Key.mapId == mapId && pair.Key.instanceId >= 0); + foreach (var (_, map) in list) + worker(map); } } @@ -424,7 +443,7 @@ namespace Game.Entities public void DecreaseScheduledScriptCount(uint count) { _scheduledScripts -= count; } public bool IsScriptScheduled() { return _scheduledScripts > 0; } - Dictionary i_maps = new(); + Dictionary<(uint mapId, uint instanceId), Map> i_maps = new(); IntervalTimer i_timer = new(); object _mapsLock= new(); uint i_gridCleanUpDelay; diff --git a/Source/Game/Maps/TerrainManager.cs b/Source/Game/Maps/TerrainManager.cs index d4fabb3fb..efb9076f5 100644 --- a/Source/Game/Maps/TerrainManager.cs +++ b/Source/Game/Maps/TerrainManager.cs @@ -146,10 +146,11 @@ namespace Game.Maps TerrainInfo _parentTerrain; List _childTerrain = new(); - object _loadMutex = new(); + object _loadLock = new(); GridMap[][] _gridMap = new GridMap[MapConst.MaxGrids][]; ushort[][] _referenceCountFromMap = new ushort[MapConst.MaxGrids][]; + BitSet _loadedGrids = new(MapConst.MaxGrids * MapConst.MaxGrids); BitSet _gridFileExists = new(MapConst.MaxGrids * MapConst.MaxGrids); // cache what grids are available for this map (not including parent/child maps) static TimeSpan CleanupInterval = TimeSpan.FromMinutes(1); @@ -187,9 +188,9 @@ namespace Game.Maps { var build = reader.ReadUInt32(); byte[] tilesData = reader.ReadArray(MapConst.MaxGrids * MapConst.MaxGrids); - for (uint gx = 0; gx < MapConst.MaxGrids; ++gx) - for (uint gy = 0; gy < MapConst.MaxGrids; ++gy) - _gridFileExists[(int)(gx * MapConst.MaxGrids + gy)] = tilesData[(int)(gx * MapConst.MaxGrids + gy)] == 49; // char of 1 + for (int gx = 0; gx < MapConst.MaxGrids; ++gx) + for (int gy = 0; gy < MapConst.MaxGrids; ++gy) + _gridFileExists[GetBitsetIndex(gx, gy)] = tilesData[GetBitsetIndex(gx, gy)] == 49; // char of 1 return; } @@ -197,7 +198,7 @@ namespace Game.Maps for (int gx = 0; gx < MapConst.MaxGrids; ++gx) for (int gy = 0; gy < MapConst.MaxGrids; ++gy) - _gridFileExists[gx * MapConst.MaxGrids + gy] = ExistMap(GetId(), gx, gy, false); + _gridFileExists[GetBitsetIndex(gx, gy)] = ExistMap(GetId(), gx, gy, false); } public static bool ExistMap(uint mapid, int gx, int gy, bool log = true) @@ -264,7 +265,7 @@ namespace Game.Maps public bool HasChildTerrainGridFile(uint mapId, int gx, int gy) { var childMap = _childTerrain.Find(childTerrain => childTerrain.GetId() == mapId); - return childMap != null && childMap._gridFileExists[gx * MapConst.MaxGrids + gy]; + return childMap != null && childMap._gridFileExists[GetBitsetIndex(gx, gy)]; } public void AddChildTerrain(TerrainInfo childTerrain) @@ -278,8 +279,8 @@ namespace Game.Maps if (++_referenceCountFromMap[gx][gy] != 1) // check if already loaded return; - //std::lock_guard < std::mutex > lock (_loadMutex) ; - LoadMapAndVMapImpl(gx, gy); + lock(_loadLock) + LoadMapAndVMapImpl(gx, gy); } public void LoadMapAndVMapImpl(int gx, int gy) @@ -290,6 +291,8 @@ namespace Game.Maps foreach (TerrainInfo childTerrain in _childTerrain) childTerrain.LoadMapAndVMapImpl(gx, gy); + + _loadedGrids[GetBitsetIndex(gx, gy)] = true; } public void LoadMap(int gx, int gy) @@ -297,7 +300,7 @@ namespace Game.Maps if (_gridMap[gx][gy] != null) return; - if (!_gridFileExists[gx * MapConst.MaxGrids + gy]) + if (!_gridFileExists[GetBitsetIndex(gx, gy)]) return; // map file name @@ -310,7 +313,7 @@ namespace Game.Maps if (gridMapLoadResult == LoadResult.Success) _gridMap[gx][gy] = gridMap; else - _gridFileExists[gx * MapConst.MaxGrids + gy] = false; + _gridFileExists[GetBitsetIndex(gx, gy)] = false; if (gridMapLoadResult == LoadResult.ReadFromFileFailed) Log.outError(LogFilter.Maps, $"Error loading map file: {fileName}"); @@ -328,7 +331,8 @@ namespace Game.Maps case LoadResult.Success: Log.outDebug(LogFilter.Maps, $"VMAP loaded name:{GetMapName()}, id:{GetId()}, x:{gx}, y:{gy} (vmap rep.: x:{gx}, y:{gy})"); break; - default: + case LoadResult.VersionMismatch: + case LoadResult.ReadFromFileFailed: Log.outError(LogFilter.Maps, $"Could not load VMAP name:{GetMapName()}, id:{GetId()}, x:{gx}, y:{gy} (vmap rep.: x:{gx}, y:{gy})"); break; case LoadResult.DisabledInConfig: @@ -363,6 +367,8 @@ namespace Game.Maps foreach (var childTerrain in _childTerrain) childTerrain.UnloadMapImpl(gx, gy); + + _loadedGrids[GetBitsetIndex(gx, gy)] = false; } public GridMap GetGrid(uint mapId, float x, float y, bool loadIfMissing = true) @@ -372,10 +378,10 @@ namespace Game.Maps int gy = (int)(MapConst.CenterGridId - y / MapConst.SizeofGrids); //grid y // ensure GridMap is loaded - if (_gridMap[gx][gy] == null && loadIfMissing) + if (_loadedGrids[GetBitsetIndex(gx, gy)] && loadIfMissing) { - //std::lock_guard < std::mutex > lock (_loadMutex) ; - LoadMapAndVMapImpl(gx, gy); + lock(_loadLock) + LoadMapAndVMapImpl(gx, gy); } GridMap grid = _gridMap[gx][gy]; @@ -398,7 +404,7 @@ namespace Game.Maps // delete those GridMap objects which have refcount = 0 for (int x = 0; x < MapConst.MaxGrids; ++x) for (int y = 0; y < MapConst.MaxGrids; ++y) - if (_gridMap[x][y] != null && _referenceCountFromMap[x][y] == 0) + if (_loadedGrids[GetBitsetIndex(x, y)] && _referenceCountFromMap[x][y] == 0) UnloadMapImpl(x, y); _cleanupTimer.Reset(CleanupInterval); @@ -889,5 +895,7 @@ namespace Game.Maps } public uint GetId() { return _mapId; } + + static int GetBitsetIndex(int gx, int gy) { return gx * MapConst.MaxGrids + gy; } } } diff --git a/Source/Game/Movement/Generators/FlightPathMovementGenerator.cs b/Source/Game/Movement/Generators/FlightPathMovementGenerator.cs index 26980f832..ebba78df5 100644 --- a/Source/Game/Movement/Generators/FlightPathMovementGenerator.cs +++ b/Source/Game/Movement/Generators/FlightPathMovementGenerator.cs @@ -109,7 +109,7 @@ namespace Game.Movement break; if (_currentNode == _preloadTargetNode) - PreloadEndGrid(); + PreloadEndGrid(owner); _currentNode += (departureEvent ? 1 : 0); departureEvent = !departureEvent; @@ -262,14 +262,17 @@ namespace Game.Movement else _preloadTargetNode = (uint)nodeCount - 3; + while (_path[(int)_preloadTargetNode].ContinentID != _endMapId) + ++_preloadTargetNode; + _endGridX = _path[nodeCount - 1].Loc.X; _endGridY = _path[nodeCount - 1].Loc.Y; } - void PreloadEndGrid() + void PreloadEndGrid(Player owner) { // Used to preload the final grid where the flightmaster is - Map endMap = Global.MapMgr.FindBaseNonInstanceMap(_endMapId); + Map endMap = owner.GetMap(); // Load the grid if (endMap != null) diff --git a/Source/Game/World/WorldManager.cs b/Source/Game/World/WorldManager.cs index 9cd3f1680..ece5f38a4 100644 --- a/Source/Game/World/WorldManager.cs +++ b/Source/Game/World/WorldManager.cs @@ -1116,19 +1116,6 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loading phase names..."); Global.ObjectMgr.LoadPhaseNames(); - - // Preload all cells, if required for the base maps - if (WorldConfig.GetBoolValue(WorldCfg.BasemapLoadGrids)) - { - Global.MapMgr.DoForAllMaps(map => - { - if (!map.Instanceable()) - { - Log.outInfo(LogFilter.ServerLoading, "Pre-loading base map data for map {0}", map.GetId()); - map.LoadAllCells(); - } - }); - } } public void LoadConfigSettings(bool reload = false)