From 66c7047a295fee1abc96f3d3f02c59af89f8e349 Mon Sep 17 00:00:00 2001 From: hondacrx Date: Tue, 4 Oct 2022 10:57:04 -0400 Subject: [PATCH] Core/Instances: Delete InstanceSaveMgr and replace most of its uses with new InstanceLockMgr Port From (https://github.com/TrinityCore/TrinityCore/commit/9b924522d0549dd67b10e2cbdfc20297dd21e182) --- Source/Framework/Constants/PlayerConst.cs | 8 - Source/Game/Chat/Commands/DebugCommands.cs | 22 - Source/Game/Chat/Commands/InstanceCommands.cs | 14 +- Source/Game/DungeonFinding/LFGManager.cs | 18 +- Source/Game/Entities/Player/Player.DB.cs | 84 +- Source/Game/Entities/Player/Player.Fields.cs | 1 - Source/Game/Entities/Player/Player.Map.cs | 222 +---- Source/Game/Entities/Player/Player.cs | 5 - Source/Game/Globals/Global.cs | 1 - Source/Game/Globals/ObjectManager.cs | 6 + Source/Game/Groups/Group.cs | 211 ----- Source/Game/Groups/GroupManager.cs | 43 - Source/Game/Handlers/CalendarHandler.cs | 56 +- Source/Game/Handlers/CharacterHandler.cs | 5 - Source/Game/Handlers/MiscHandler.cs | 31 +- Source/Game/Handlers/MovementHandler.cs | 28 +- .../Maps/Instances/InstanceLockManager.cs | 6 +- .../Maps/Instances/InstanceSaveManager.cs | 778 ------------------ Source/Game/Maps/Instances/InstanceScript.cs | 15 +- Source/Game/Maps/Map.cs | 92 +-- Source/Game/Maps/MapManager.cs | 44 + .../Networking/Packets/CalendarPackets.cs | 4 +- Source/Game/Scenarios/InstanceScenario.cs | 4 +- Source/Game/Scripting/CoreScripts.cs | 2 +- Source/Game/Scripting/ScriptManager.cs | 2 +- Source/Game/Spells/Spell.cs | 24 +- Source/Game/World/WorldManager.cs | 5 +- 27 files changed, 186 insertions(+), 1545 deletions(-) delete mode 100644 Source/Game/Maps/Instances/InstanceSaveManager.cs diff --git a/Source/Framework/Constants/PlayerConst.cs b/Source/Framework/Constants/PlayerConst.cs index 3e87cb6cb..9f3e1537a 100644 --- a/Source/Framework/Constants/PlayerConst.cs +++ b/Source/Framework/Constants/PlayerConst.cs @@ -740,14 +740,6 @@ namespace Framework.Constants Error = 1 } - public enum BindExtensionState - { - Expired = 0, - Normal = 1, - Extended = 2, - Keep = 255 // special state: keep current save type - } - public enum TalentLearnResult { LearnOk = 0, diff --git a/Source/Game/Chat/Commands/DebugCommands.cs b/Source/Game/Chat/Commands/DebugCommands.cs index 8b8651718..8a7f18fe2 100644 --- a/Source/Game/Chat/Commands/DebugCommands.cs +++ b/Source/Game/Chat/Commands/DebugCommands.cs @@ -879,28 +879,6 @@ namespace Game.Chat return true; } - if (difficulty == 0) - { - handler.SendSysMessage($"Resetting all difficulties for '{mEntry.MapName[handler.GetSessionDbcLocale()]}'."); - foreach (var diff in CliDB.DifficultyStorage.Values) - { - if (Global.DB2Mgr.GetMapDifficultyData(mapId, (Difficulty)diff.Id) != null) - { - handler.SendSysMessage($"Resetting difficulty {diff.Id} for '{mEntry.MapName[handler.GetSessionDbcLocale()]}'."); - Global.InstanceSaveMgr.ForceGlobalReset(mapId, (Difficulty)diff.Id); - } - } - } - else if (mEntry.IsNonRaidDungeon() && difficulty == (int)Difficulty.Normal) - { - handler.SendSysMessage($"'{mEntry.MapName[handler.GetSessionDbcLocale()]}' does not have any permanent saves for difficulty {(Difficulty)difficulty}."); - } - else - { - handler.SendSysMessage($"Resetting difficulty {(Difficulty)difficulty} for '{mEntry.MapName[handler.GetSessionDbcLocale()]}'."); - Global.InstanceSaveMgr.ForceGlobalReset(mapId, (Difficulty)difficulty); - } - return true; } diff --git a/Source/Game/Chat/Commands/InstanceCommands.cs b/Source/Game/Chat/Commands/InstanceCommands.cs index c4f49001e..52f7fa450 100644 --- a/Source/Game/Chat/Commands/InstanceCommands.cs +++ b/Source/Game/Chat/Commands/InstanceCommands.cs @@ -74,7 +74,7 @@ namespace Game.Chat [Command("listbinds", RBACPermissions.CommandInstanceListbinds)] static bool HandleInstanceListBindsCommand(CommandHandler handler) { - Player player = handler.GetSelectedPlayer(); + /*Player player = handler.GetSelectedPlayer(); if (!player) player = handler.GetSession().GetPlayer(); @@ -110,7 +110,7 @@ namespace Game.Chat } } } - handler.SendSysMessage("group binds: {0}", counter); + handler.SendSysMessage("group binds: {0}", counter);*/ return true; } @@ -186,9 +186,9 @@ namespace Game.Chat { handler.SendSysMessage("instances loaded: {0}", Global.MapMgr.GetNumInstances()); handler.SendSysMessage("players in instances: {0}", Global.MapMgr.GetNumPlayersInInstances()); - handler.SendSysMessage("instance saves: {0}", Global.InstanceSaveMgr.GetNumInstanceSaves()); - handler.SendSysMessage("players bound: {0}", Global.InstanceSaveMgr.GetNumBoundPlayersTotal()); - handler.SendSysMessage("groups bound: {0}", Global.InstanceSaveMgr.GetNumBoundGroupsTotal()); + //handler.SendSysMessage("instance saves: {0}", Global.InstanceSaveMgr.GetNumInstanceSaves()); + //handler.SendSysMessage("players bound: {0}", Global.InstanceSaveMgr.GetNumBoundPlayersTotal()); + //handler.SendSysMessage("groups bound: {0}", Global.InstanceSaveMgr.GetNumBoundGroupsTotal()); return true; } @@ -196,7 +196,7 @@ namespace Game.Chat [Command("unbind", RBACPermissions.CommandInstanceUnbind)] static bool HandleInstanceUnbindCommand(CommandHandler handler, string mapArg, byte? difficultyArg) { - Player player = handler.GetSelectedPlayer(); + /*Player player = handler.GetSelectedPlayer(); if (!player) player = handler.GetSession().GetPlayer(); @@ -223,7 +223,7 @@ namespace Game.Chat } } } - handler.SendSysMessage("instances unbound: {0}", counter); + handler.SendSysMessage("instances unbound: {0}", counter);*/ return true; } diff --git a/Source/Game/DungeonFinding/LFGManager.cs b/Source/Game/DungeonFinding/LFGManager.cs index ee822827c..5dccd33ce 100644 --- a/Source/Game/DungeonFinding/LFGManager.cs +++ b/Source/Game/DungeonFinding/LFGManager.cs @@ -795,19 +795,15 @@ namespace Game.DungeonFinding LFGDungeonData dungeon = GetLFGDungeon(dungeonId); Cypher.Assert(dungeon != null); Cypher.Assert(player); - InstanceBind playerBind = player.GetBoundInstance(dungeon.map, dungeon.difficulty); + MapDb2Entries entries = new(dungeon.map, dungeon.difficulty); + InstanceLock playerBind = Global.InstanceLockMgr.FindActiveInstanceLock(guid, entries); if (playerBind != null) { - InstanceSave playerSave = playerBind.save; - if (playerSave != null) - { - uint dungeonInstanceId = playerSave.GetInstanceId(); - var itLockedDungeon = lockedDungeons.LookupByKey(dungeonId); - if (itLockedDungeon == 0 || itLockedDungeon == dungeonInstanceId) - eraseDungeon = false; + uint dungeonInstanceId = playerBind.GetInstanceId(); + if (!lockedDungeons.TryGetValue(dungeonId, out uint lockedDungeon) || lockedDungeon == dungeonInstanceId) + eraseDungeon = false; - lockedDungeons[dungeonId] = dungeonInstanceId; - } + lockedDungeons[dungeonId] = dungeonInstanceId; } } @@ -1625,7 +1621,7 @@ namespace Game.DungeonFinding lockStatus = LfgLockStatusType.NotInSeason; else if (Global.DisableMgr.IsDisabledFor(DisableType.LFGMap, dungeon.map, player)) lockStatus = LfgLockStatusType.RaidLocked; - else if (dungeon.difficulty > Difficulty.Normal && player.GetBoundInstance(dungeon.map, dungeon.difficulty) != null) + else if (dungeon.difficulty > Difficulty.Normal && Global.InstanceLockMgr.FindActiveInstanceLock(guid, new MapDb2Entries(dungeon.map, dungeon.difficulty)) != null) lockStatus = LfgLockStatusType.RaidLocked; else if (dungeon.seasonal && !IsSeasonActive(dungeon.id)) lockStatus = LfgLockStatusType.NotInSeason; diff --git a/Source/Game/Entities/Player/Player.DB.cs b/Source/Game/Entities/Player/Player.DB.cs index ae81b2042..9551600ae 100644 --- a/Source/Game/Entities/Player/Player.DB.cs +++ b/Source/Game/Entities/Player/Player.DB.cs @@ -1056,78 +1056,6 @@ namespace Game.Entities RemoveAtLoginFlag(AtLoginFlags.Resurrect); } - void _LoadBoundInstances(SQLResult result) - { - m_boundInstances.Clear(); - - Group group = GetGroup(); - - if (!result.IsEmpty()) - { - do - { - bool perm = result.Read(1); - uint mapId = result.Read(2); - uint instanceId = result.Read(0); - byte difficulty = result.Read(3); - BindExtensionState extendState = (BindExtensionState)result.Read(4); - - long resetTime = result.Read(5); - // the resettime for normal instances is only saved when the InstanceSave is unloaded - // so the value read from the DB may be wrong here but only if the InstanceSave is loaded - // and in that case it is not used - - uint entranceId = result.Read(6); - - bool deleteInstance = false; - - MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapId); - string mapname = mapEntry != null ? mapEntry.MapName[Global.WorldMgr.GetDefaultDbcLocale()] : "Unknown"; - - if (mapEntry == null || !mapEntry.IsDungeon()) - { - Log.outError(LogFilter.Player, "_LoadBoundInstances: player {0}({1}) has bind to not existed or not dungeon map {2} ({3})", GetName(), GetGUID().ToString(), mapId, mapname); - deleteInstance = true; - } - else if (CliDB.DifficultyStorage.HasRecord(difficulty)) - { - Log.outError(LogFilter.Player, "_LoadBoundInstances: player {0}({1}) has bind to not existed difficulty {2} instance for map {3} ({4})", GetName(), GetGUID().ToString(), difficulty, mapId, mapname); - deleteInstance = true; - } - else - { - MapDifficultyRecord mapDiff = Global.DB2Mgr.GetMapDifficultyData(mapId, (Difficulty)difficulty); - if (mapDiff == null) - { - Log.outError(LogFilter.Player, "_LoadBoundInstances: player {0}({1}) has bind to not existed difficulty {2} instance for map {3} ({4})", GetName(), GetGUID().ToString(), difficulty, mapId, mapname); - deleteInstance = true; - } - else if (!perm && group) - { - Log.outError(LogFilter.Player, "_LoadBoundInstances: player {0}({1}) is in group {2} but has a non-permanent character bind to map {3} ({4}), {5}, {6}", - GetName(), GetGUID().ToString(), group.GetGUID().ToString(), mapId, mapname, instanceId, difficulty); - deleteInstance = true; - } - } - - if (deleteInstance) - { - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE_GUID); - stmt.AddValue(0, GetGUID().GetCounter()); - stmt.AddValue(1, instanceId); - DB.Characters.Execute(stmt); - - continue; - } - - // since non permanent binds are always solo bind, they can always be reset - InstanceSave save = Global.InstanceSaveMgr.AddInstanceSave(mapId, instanceId, (Difficulty)difficulty, resetTime, entranceId, !perm, true); - if (save != null) - BindToInstance(save, perm, extendState, true); - } - while (result.NextRow()); - } - } void _LoadVoidStorage(SQLResult result) { if (result.IsEmpty()) @@ -2793,7 +2721,6 @@ namespace Game.Entities SetUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.TodayHonorableKills), todayKills); SetUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.YesterdayHonorableKills), yesterdayKills); - _LoadBoundInstances(holder.GetResult(PlayerLoginQueryLoad.BoundInstances)); _LoadInstanceTimeRestrictions(holder.GetResult(PlayerLoginQueryLoad.InstanceLockTimes)); _LoadBGData(holder.GetResult(PlayerLoginQueryLoad.BgData)); @@ -2976,15 +2903,6 @@ namespace Game.Entities Log.outDebug(LogFilter.Player, "Player {0} using client without required expansion tried login at non accessible map {1}", GetName(), mapId); RelocateToHomebind(); } - - // fix crash (because of if (Map* map = _FindMap(instanceId)) in MapInstanced.CreateInstance) - if (instance_id != 0) - { - InstanceSave save = GetInstanceSave(mapId); - if (save != null) - if (save.GetInstanceId() != instance_id) - instance_id = 0; - } } // NOW player must have valid map @@ -3028,7 +2946,7 @@ namespace Game.Entities areaTrigger = Global.ObjectMgr.GetGoBackTrigger(mapId); check = true; } - else if (instance_id != 0 && Global.InstanceSaveMgr.GetInstanceSave(instance_id) == null) // ... and instance is reseted then look for entrance. + else if (instance_id != 0 && Global.InstanceLockMgr.FindActiveInstanceLock(guid, new MapDb2Entries(mapId, map.GetDifficultyID())) != null) // ... and instance is reseted then look for entrance. { areaTrigger = Global.ObjectMgr.GetMapEntranceTrigger(mapId); check = true; diff --git a/Source/Game/Entities/Player/Player.Fields.cs b/Source/Game/Entities/Player/Player.Fields.cs index c2f51038a..6605dd521 100644 --- a/Source/Game/Entities/Player/Player.Fields.cs +++ b/Source/Game/Entities/Player/Player.Fields.cs @@ -78,7 +78,6 @@ namespace Game.Entities bool m_bPassOnGroupLoot; GroupUpdateCounter[] m_groupUpdateSequences = new GroupUpdateCounter[2]; - public Dictionary> m_boundInstances = new(); Dictionary m_recentInstances = new(); Dictionary _instanceResetTimes = new(); uint _pendingBindId; diff --git a/Source/Game/Entities/Player/Player.Map.cs b/Source/Game/Entities/Player/Player.Map.cs index c90240438..062322ac2 100644 --- a/Source/Game/Entities/Player/Player.Map.cs +++ b/Source/Game/Entities/Player/Player.Map.cs @@ -290,152 +290,6 @@ namespace Game.Entities public ZonePVPTypeOverride GetOverrideZonePVPType() { return (ZonePVPTypeOverride)(uint)m_activePlayerData.OverrideZonePVPType; } public void SetOverrideZonePVPType(ZonePVPTypeOverride type) { SetUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.OverrideZonePVPType), (uint)type); } - public InstanceBind GetBoundInstance(uint mapid, Difficulty difficulty, bool withExpired = false) - { - // some instances only have one difficulty - MapDifficultyRecord mapDiff = Global.DB2Mgr.GetDownscaledMapDifficultyData(mapid, ref difficulty); - if (mapDiff == null) - return null; - - var difficultyDic = m_boundInstances.LookupByKey(difficulty); - if (difficultyDic == null) - return null; - - var instanceBind = difficultyDic.LookupByKey(mapid); - if (instanceBind != null) - if (instanceBind.extendState != 0 || withExpired) - return instanceBind; - - return null; - } - public Dictionary GetBoundInstances(Difficulty difficulty) { return m_boundInstances.LookupByKey(difficulty); } - - public InstanceSave GetInstanceSave(uint mapid) - { - MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapid); - InstanceBind pBind = GetBoundInstance(mapid, GetDifficultyID(mapEntry)); - InstanceSave pSave = pBind?.save; - if (pBind == null || !pBind.perm) - { - Group group = GetGroup(); - if (group) - { - InstanceBind groupBind = group.GetBoundInstance(GetDifficultyID(mapEntry), mapid); - if (groupBind != null) - pSave = groupBind.save; - } - } - - return pSave; - } - - public void UnbindInstance(uint mapid, Difficulty difficulty, bool unload = false) - { - var difficultyDic = m_boundInstances.LookupByKey(difficulty); - if (difficultyDic != null) - { - var pair = difficultyDic.Find(mapid); - if (pair.Value != null) - UnbindInstance(pair, difficultyDic, unload); - } - } - - public void UnbindInstance(KeyValuePair pair, Dictionary difficultyDic, bool unload) - { - if (pair.Value != null) - { - if (!unload) - { - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE_GUID); - - stmt.AddValue(0, GetGUID().GetCounter()); - stmt.AddValue(1, pair.Value.save.GetInstanceId()); - - DB.Characters.Execute(stmt); - } - - if (pair.Value.perm) - GetSession().SendCalendarRaidLockoutRemoved(pair.Value.save); - - pair.Value.save.RemovePlayer(this); // save can become invalid - difficultyDic.Remove(pair.Key); - } - } - - public InstanceBind BindToInstance(InstanceSave save, bool permanent, BindExtensionState extendState = BindExtensionState.Normal, bool load = false) - { - if (save != null) - { - InstanceBind bind = new(); - if (m_boundInstances.ContainsKey(save.GetDifficultyID()) && m_boundInstances[save.GetDifficultyID()].ContainsKey(save.GetMapId())) - bind = m_boundInstances[save.GetDifficultyID()][save.GetMapId()]; - - if (extendState == BindExtensionState.Keep) // special flag, keep the player's current extend state when updating for new boss down - { - if (save == bind.save) - extendState = bind.extendState; - else - extendState = BindExtensionState.Normal; - } - - if (!load) - { - PreparedStatement stmt; - if (bind.save != null) - { - // update the save when the group kills a boss - if (permanent != bind.perm || save != bind.save || extendState != bind.extendState) - { - stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_INSTANCE); - - stmt.AddValue(0, save.GetInstanceId()); - stmt.AddValue(1, permanent); - stmt.AddValue(2, (byte)extendState); - stmt.AddValue(3, GetGUID().GetCounter()); - stmt.AddValue(4, bind.save.GetInstanceId()); - - DB.Characters.Execute(stmt); - } - } - else - { - stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_INSTANCE); - stmt.AddValue(0, GetGUID().GetCounter()); - stmt.AddValue(1, save.GetInstanceId()); - stmt.AddValue(2, permanent); - stmt.AddValue(3, (byte)extendState); - DB.Characters.Execute(stmt); - } - } - - if (bind.save != save) - { - if (bind.save != null) - bind.save.RemovePlayer(this); - save.AddPlayer(this); - } - - if (permanent) - save.SetCanReset(false); - - bind.save = save; - bind.perm = permanent; - bind.extendState = extendState; - if (!load) - Log.outDebug(LogFilter.Maps, "Player.BindToInstance: Player '{0}' ({1}) is now bound to map (ID: {2}, Instance {3}, Difficulty {4})", GetName(), GetGUID().ToString(), save.GetMapId(), save.GetInstanceId(), save.GetDifficultyID()); - - Global.ScriptMgr.OnPlayerBindToInstance(this, save.GetDifficultyID(), save.GetMapId(), permanent, extendState); - - if (!m_boundInstances.ContainsKey(save.GetDifficultyID())) - m_boundInstances[save.GetDifficultyID()] = new Dictionary(); - - m_boundInstances[save.GetDifficultyID()][save.GetMapId()] = bind; - return bind; - } - - return null; - } - public void ConfirmPendingBind() { InstanceMap map = GetMap().ToInstanceMap(); @@ -466,7 +320,7 @@ namespace Game.Entities lockInfos.InstanceID = instanceLock.GetInstanceId(); lockInfos.MapID = instanceLock.GetMapId(); lockInfos.DifficultyID = (uint)instanceLock.GetDifficultyId(); - lockInfos.TimeRemaining = (int)(instanceLock.GetEffectiveExpiryTime() - now).TotalSeconds; + lockInfos.TimeRemaining = (int)Math.Max((instanceLock.GetEffectiveExpiryTime() - now).TotalSeconds, 0); lockInfos.CompletedMask = instanceLock.GetData().CompletedEncountersMask; lockInfos.Locked = !instanceLock.IsExpired(); @@ -646,52 +500,7 @@ namespace Game.Entities // Reset all solo instances and optionally send a message on success for each public void ResetInstances(InstanceResetMethod method, bool isRaid, bool isLegacy) { - // method can be INSTANCE_RESET_ALL, INSTANCE_RESET_CHANGE_DIFFICULTY, INSTANCE_RESET_GROUP_JOIN - // we assume that when the difficulty changes, all instances that can be reset will be - Difficulty difficulty = GetDungeonDifficultyID(); - if (isRaid) - { - if (!isLegacy) - difficulty = GetRaidDifficultyID(); - else - difficulty = GetLegacyRaidDifficultyID(); - } - - var difficultyDic = m_boundInstances.LookupByKey(difficulty); - if (difficultyDic == null) - return; - - foreach (var pair in difficultyDic) - { - InstanceSave p = pair.Value.save; - MapRecord entry = CliDB.MapStorage.LookupByKey(difficulty); - if (entry == null || entry.IsRaid() != isRaid || !p.CanReset()) - continue; - - if (method == InstanceResetMethod.All) - { - // the "reset all instances" method can only reset normal maps - if (entry.InstanceType == MapTypes.Raid || difficulty == Difficulty.Heroic) - continue; - } - - // if the map is loaded, reset it - Map map = Global.MapMgr.FindMap(p.GetMapId(), p.GetInstanceId()); - if (map != null && map.IsDungeon()) - if (!map.ToInstanceMap().Reset(method)) - continue; - - // since this is a solo instance there should not be any players inside - if (method == InstanceResetMethod.All || method == InstanceResetMethod.ChangeDifficulty) - SendResetInstanceSuccess(p.GetMapId()); - - p.DeleteFromDB(); - difficultyDic.Remove(pair.Key); - - // the following should remove the instance save from the manager and delete it as well - p.RemovePlayer(this); - } } public void SendResetInstanceSuccess(uint MapId) @@ -719,35 +528,6 @@ namespace Game.Entities SendPacket(transferAborted); } - public void SendInstanceResetWarning(uint mapid, Difficulty difficulty, uint time, bool welcome) - { - // type of warning, based on the time remaining until reset - InstanceResetWarningType type; - if (welcome) - type = InstanceResetWarningType.Welcome; - else if (time > 21600) - type = InstanceResetWarningType.Welcome; - else if (time > 3600) - type = InstanceResetWarningType.WarningHours; - else if (time > 300) - type = InstanceResetWarningType.WarningMin; - else - type = InstanceResetWarningType.WarningMinSoon; - - RaidInstanceMessage raidInstanceMessage = new(); - raidInstanceMessage.Type = type; - raidInstanceMessage.MapID = mapid; - raidInstanceMessage.DifficultyID = difficulty; - - InstanceBind bind = GetBoundInstance(mapid, difficulty); - if (bind != null) - raidInstanceMessage.Locked = bind.perm; - else - raidInstanceMessage.Locked = false; - raidInstanceMessage.Extended = false; - SendPacket(raidInstanceMessage); - } - public override void ProcessTerrainStatusUpdate(ZLiquidStatus oldLiquidStatus, LiquidData newLiquidData) { // process liquid auras using generic unit code diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index efa132724..3de77cfdf 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -727,11 +727,6 @@ namespace Game.Entities if (GetTransport() != null) GetTransport().RemovePassenger(this); - - // clean up player-instance binds, may unload some instance saves - foreach (var difficultyDic in m_boundInstances.Values) - foreach (var instanceBind in difficultyDic.Values) - instanceBind.save.RemovePlayer(this); } public override void AddToWorld() diff --git a/Source/Game/Globals/Global.cs b/Source/Game/Globals/Global.cs index aac38c06c..4f327ac29 100644 --- a/Source/Game/Globals/Global.cs +++ b/Source/Game/Globals/Global.cs @@ -73,7 +73,6 @@ public static class Global public static VMapManager VMapMgr { get { return VMapManager.Instance; } } public static WaypointManager WaypointMgr { get { return WaypointManager.Instance; } } public static TransportManager TransportMgr { get { return TransportManager.Instance; } } - public static InstanceSaveManager InstanceSaveMgr { get { return InstanceSaveManager.Instance; } } public static InstanceLockManager InstanceLockMgr { get { return InstanceLockManager.Instance; } } public static ScenarioManager ScenarioMgr { get { return ScenarioManager.Instance; } } diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index 73f9b6336..cbcfd6949 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -12061,4 +12061,10 @@ namespace Game } } } + + public class InstanceTemplate + { + public uint Parent; + public uint ScriptId; + } } diff --git a/Source/Game/Groups/Group.cs b/Source/Game/Groups/Group.cs index 333a00bf8..9bead0668 100644 --- a/Source/Game/Groups/Group.cs +++ b/Source/Game/Groups/Group.cs @@ -673,26 +673,6 @@ namespace Game.Groups PreparedStatement stmt; SQLTransaction trans = new(); - // Remove the groups permanent instance bindings - foreach (var difficultyDic in m_boundInstances.Values) - { - foreach (var pair in difficultyDic.ToList()) - { - // Do not unbind saves of instances that already had map created (a newLeader entered) - // forcing a new instance with another leader requires group disbanding (confirmed on retail) - if (pair.Value.perm && Global.MapMgr.FindMap(pair.Key, pair.Value.save.GetInstanceId()) == null) - { - stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_INSTANCE_PERM_BINDING); - stmt.AddValue(0, m_dbStoreId); - stmt.AddValue(1, pair.Value.save.GetInstanceId()); - trans.Append(stmt); - - pair.Value.save.RemoveGroup(this); - difficultyDic.Remove(pair.Key); - } - } - } - // Update the group leader stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GROUP_LEADER); @@ -1381,166 +1361,7 @@ namespace Game.Groups public void ResetInstances(InstanceResetMethod method, bool isRaid, bool isLegacy, Player SendMsgTo) { - if (IsBGGroup() || IsBFGroup()) - return; - // method can be INSTANCE_RESET_ALL, INSTANCE_RESET_CHANGE_DIFFICULTY, INSTANCE_RESET_GROUP_DISBAND - - // we assume that when the difficulty changes, all instances that can be reset will be - Difficulty diff = GetDungeonDifficultyID(); - if (isRaid) - { - if (!isLegacy) - diff = GetRaidDifficultyID(); - else - diff = GetLegacyRaidDifficultyID(); - } - - var difficultyDic = m_boundInstances.LookupByKey(diff); - if (difficultyDic == null) - return; - - foreach (var pair in difficultyDic.ToList()) - { - InstanceSave instanceSave = pair.Value.save; - MapRecord entry = CliDB.MapStorage.LookupByKey(pair.Key); - if (entry == null || entry.IsRaid() != isRaid || (!instanceSave.CanReset() && method != InstanceResetMethod.GroupDisband)) - continue; - - if (method == InstanceResetMethod.All) - { - // the "reset all instances" method can only reset normal maps - if (entry.InstanceType == MapTypes.Raid || diff == Difficulty.Heroic) - continue; - } - - bool isEmpty = true; - // if the map is loaded, reset it - Map map = Global.MapMgr.FindMap(instanceSave.GetMapId(), instanceSave.GetInstanceId()); - if (map && map.IsDungeon() && !(method == InstanceResetMethod.GroupDisband && !instanceSave.CanReset())) - { - if (instanceSave.CanReset()) - isEmpty = ((InstanceMap)map).Reset(method); - else - isEmpty = !map.HavePlayers(); - } - - if (SendMsgTo) - { - if (!isEmpty) - SendMsgTo.SendResetInstanceFailed(ResetFailedReason.Failed, instanceSave.GetMapId()); - else if (WorldConfig.GetBoolValue(WorldCfg.InstancesResetAnnounce)) - { - Group group = SendMsgTo.GetGroup(); - if (group) - { - for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.Next()) - { - Player player = refe.GetSource(); - if (player) - player.SendResetInstanceSuccess(instanceSave.GetMapId()); - } - } - else - SendMsgTo.SendResetInstanceSuccess(instanceSave.GetMapId()); - } - else - SendMsgTo.SendResetInstanceSuccess(instanceSave.GetMapId()); - } - - if (isEmpty || method == InstanceResetMethod.GroupDisband || method == InstanceResetMethod.ChangeDifficulty) - { - // do not reset the instance, just unbind if others are permanently bound to it - if (instanceSave.CanReset()) - { - if (map != null && map.IsDungeon() && SendMsgTo) - { - AreaTriggerStruct instanceEntrance = Global.ObjectMgr.GetGoBackTrigger(map.GetId()); - - if (instanceEntrance == null) - Log.outDebug(LogFilter.Misc, $"Instance entrance not found for map {map.GetId()}"); - else - { - WorldSafeLocsEntry graveyardLocation = Global.ObjectMgr.GetClosestGraveYard( - new WorldLocation(instanceEntrance.target_mapId, instanceEntrance.target_X, instanceEntrance.target_Y, instanceEntrance.target_Z), - SendMsgTo.GetTeam(), null); - uint zoneId = Global.TerrainMgr.GetZoneId(PhasingHandler.EmptyPhaseShift, graveyardLocation.Loc.GetMapId(), - graveyardLocation.Loc.GetPositionX(), graveyardLocation.Loc.GetPositionY(), graveyardLocation.Loc.GetPositionZ()); - - foreach (MemberSlot member in GetMemberSlots()) - { - if (!Global.ObjAccessor.FindConnectedPlayer(member.guid)) - { - var stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_POSITION_BY_MAPID); - - stmt.AddValue(0, graveyardLocation.Loc.GetPositionX()); - stmt.AddValue(1, graveyardLocation.Loc.GetPositionY()); - stmt.AddValue(2, graveyardLocation.Loc.GetPositionZ()); - stmt.AddValue(3, instanceEntrance.target_Orientation); - stmt.AddValue(4, graveyardLocation.Loc.GetMapId()); - stmt.AddValue(5, zoneId); - stmt.AddValue(6, member.guid.GetCounter()); - stmt.AddValue(7, map.GetId()); - - DB.Characters.Execute(stmt); - } - } - } - } - instanceSave.DeleteFromDB(); - } - else - { - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_INSTANCE_BY_INSTANCE); - stmt.AddValue(0, instanceSave.GetInstanceId()); - - DB.Characters.Execute(stmt); - } - - - difficultyDic.Remove(pair.Key); - // this unloads the instance save unless online players are bound to it - // (eg. permanent binds or GM solo binds) - instanceSave.RemoveGroup(this); - } - } - } - - public InstanceBind GetBoundInstance(Player player) - { - uint mapid = player.GetMapId(); - MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapid); - return GetBoundInstance(mapEntry); - } - - public InstanceBind GetBoundInstance(Map aMap) - { - return GetBoundInstance(aMap.GetEntry()); - } - - public InstanceBind GetBoundInstance(MapRecord mapEntry) - { - if (mapEntry == null || !mapEntry.IsDungeon()) - return null; - - Difficulty difficulty = GetDifficultyID(mapEntry); - return GetBoundInstance(difficulty, mapEntry.Id); - } - - public InstanceBind GetBoundInstance(Difficulty difficulty, uint mapId) - { - // some instances only have one difficulty - Global.DB2Mgr.GetDownscaledMapDifficultyData(mapId, ref difficulty); - - var difficultyDic = m_boundInstances.LookupByKey(difficulty); - if (difficultyDic == null) - return null; - - var instanceBind = difficultyDic.LookupByKey(mapId); - if (instanceBind != null) - return instanceBind; - - return null; } public void LinkOwnedInstance(GroupInstanceReference refe) @@ -1548,30 +1369,6 @@ namespace Game.Groups m_ownedInstancesMgr.InsertLast(refe); } - public void UnbindInstance(uint mapid, Difficulty difficulty, bool unload = false) - { - var difficultyDic = m_boundInstances.LookupByKey(difficulty); - if (difficultyDic == null) - return; - - var instanceBind = difficultyDic.LookupByKey(mapid); - if (instanceBind != null) - { - if (!unload) - { - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_INSTANCE_BY_GUID); - - stmt.AddValue(0, m_dbStoreId); - stmt.AddValue(1, instanceBind.save.GetInstanceId()); - - DB.Characters.Execute(stmt); - } - - instanceBind.save.RemoveGroup(this); // save can become invalid - difficultyDic.Remove(mapid); - } - } - void _homebindIfInstance(Player player) { if (player && !player.IsGameMaster() && CliDB.MapStorage.LookupByKey(player.GetMapId()).IsDungeon()) @@ -1986,11 +1783,6 @@ namespace Game.Groups } } - public Dictionary GetBoundInstances(Difficulty difficulty) - { - return m_boundInstances.LookupByKey(difficulty); - } - void _initRaidSubGroupsCounter() { // Sub group counters initialization @@ -2077,8 +1869,6 @@ namespace Game.Groups worker(refe.GetSource()); } - uint GetInstanceId(MapRecord mapEntry) { return 0; } - public ObjectGuid GetRecentInstanceOwner(uint mapId) { @@ -2119,7 +1909,6 @@ namespace Game.Groups ItemQuality m_lootThreshold; ObjectGuid m_looterGuid; ObjectGuid m_masterLooterGuid; - Dictionary> m_boundInstances = new(); Dictionary> m_recentInstances = new(); GroupInstanceRefManager m_ownedInstancesMgr = new(); byte[] m_subGroupsCounts; diff --git a/Source/Game/Groups/GroupManager.cs b/Source/Game/Groups/GroupManager.cs index 92b8e6f28..47d64bdb3 100644 --- a/Source/Game/Groups/GroupManager.cs +++ b/Source/Game/Groups/GroupManager.cs @@ -182,49 +182,6 @@ namespace Game.Groups Log.outInfo(LogFilter.ServerLoading, "Loaded {0} group members in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } - - Log.outInfo(LogFilter.ServerLoading, "Loading Group instance saves..."); - { - uint oldMSTime = Time.GetMSTime(); - - // 0 1 2 3 4 5 6 - SQLResult result = DB.Characters.Query("SELECT gi.guid, i.map, gi.instance, gi.permanent, i.difficulty, i.resettime, i.entranceId, " + - // 7 - "(SELECT COUNT(1) FROM character_instance ci LEFT JOIN `groups` g ON ci.guid = g.leaderGuid WHERE ci.instance = gi.instance AND ci.permanent = 1 LIMIT 1) " + - "FROM group_instance gi LEFT JOIN instance i ON gi.instance = i.id ORDER BY guid"); - - if (result.IsEmpty()) - { - Log.outInfo(LogFilter.ServerLoading, "Loaded 0 group-instance saves. DB table `group_instance` is empty!"); - return; - } - - uint count = 0; - do - { - Group group = GetGroupByDbStoreId(result.Read(0)); - // group will never be NULL (we have run consistency sql's before loading) - - MapRecord mapEntry = CliDB.MapStorage.LookupByKey(result.Read(1)); - if (mapEntry == null || !mapEntry.IsDungeon()) - { - Log.outError(LogFilter.Sql, "Incorrect entry in group_instance table : no dungeon map {0}", result.Read(1)); - continue; - } - - uint diff = result.Read(4); - DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(diff); - if (difficultyEntry == null || difficultyEntry.InstanceType != mapEntry.InstanceType) - continue; - - InstanceSave save = Global.InstanceSaveMgr.AddInstanceSave(mapEntry.Id, result.Read(2), (Difficulty)diff, result.Read(5), result.Read(6), result.Read(7) == 0, true); - group.BindToInstance(save, result.Read(3), true); - ++count; - } - while (result.NextRow()); - - Log.outInfo(LogFilter.ServerLoading, "Loaded {0} group-instance saves in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); - } } Dictionary GroupStore = new(); diff --git a/Source/Game/Handlers/CalendarHandler.cs b/Source/Game/Handlers/CalendarHandler.cs index 05a3820ab..25d7e9762 100644 --- a/Source/Game/Handlers/CalendarHandler.cs +++ b/Source/Game/Handlers/CalendarHandler.cs @@ -78,7 +78,7 @@ namespace Game lockoutInfo.MapID = (int)instanceLock.GetMapId(); lockoutInfo.DifficultyID = (uint)instanceLock.GetDifficultyId(); - lockoutInfo.ExpireTime = (int)(instanceLock.GetEffectiveExpiryTime() - GameTime.GetSystemTime()).TotalSeconds; + lockoutInfo.ExpireTime = (int)Math.Max((instanceLock.GetEffectiveExpiryTime() - GameTime.GetSystemTime()).TotalSeconds, 0); lockoutInfo.InstanceID = instanceLock.GetInstanceId(); packet.RaidLockouts.Add(lockoutInfo); @@ -538,21 +538,22 @@ namespace Game [WorldPacketHandler(ClientOpcodes.SetSavedInstanceExtend)] void HandleSetSavedInstanceExtend(SetSavedInstanceExtend setSavedInstanceExtend) { - Player player = GetPlayer(); - if (player) - { - InstanceBind instanceBind = player.GetBoundInstance((uint)setSavedInstanceExtend.MapID, (Difficulty)setSavedInstanceExtend.DifficultyID, setSavedInstanceExtend.Extend); // include expired instances if we are toggling extend on - if (instanceBind == null || instanceBind.save == null || !instanceBind.perm) - return; + // cannot modify locks currently in use + if (_player.GetMapId() == setSavedInstanceExtend.MapID) + return; - BindExtensionState newState; - if (!setSavedInstanceExtend.Extend || instanceBind.extendState == BindExtensionState.Expired) - newState = BindExtensionState.Normal; - else - newState = BindExtensionState.Extended; + var expiryTimes = Global.InstanceLockMgr.UpdateInstanceLockExtensionForPlayer(_player.GetGUID(), new MapDb2Entries((uint)setSavedInstanceExtend.MapID, (Difficulty)setSavedInstanceExtend.DifficultyID), setSavedInstanceExtend.Extend); - player.BindToInstance(instanceBind.save, true, newState, false); - } + if (expiryTimes.Item1 == DateTime.MinValue) + return; + + CalendarRaidLockoutUpdated calendarRaidLockoutUpdated = new(); + calendarRaidLockoutUpdated.ServerTime = GameTime.GetGameTime(); + calendarRaidLockoutUpdated.MapID = setSavedInstanceExtend.MapID; + calendarRaidLockoutUpdated.DifficultyID = setSavedInstanceExtend.DifficultyID; + calendarRaidLockoutUpdated.OldTimeRemaining = (int)Math.Max((expiryTimes.Item1 - GameTime.GetSystemTime()).TotalSeconds, 0); + calendarRaidLockoutUpdated.NewTimeRemaining = (int)Math.Max((expiryTimes.Item2 - GameTime.GetSystemTime()).TotalSeconds, 0); + SendPacket(calendarRaidLockoutUpdated); } public void SendCalendarRaidLockoutAdded(InstanceLock instanceLock) @@ -562,35 +563,10 @@ namespace Game calendarRaidLockoutAdded.ServerTime = (uint)GameTime.GetGameTime(); calendarRaidLockoutAdded.MapID = (int)instanceLock.GetMapId(); calendarRaidLockoutAdded.DifficultyID = instanceLock.GetDifficultyId(); - calendarRaidLockoutAdded.TimeRemaining = (int)(instanceLock.GetExpiryTime() - GameTime.GetSystemTime()).TotalSeconds; + calendarRaidLockoutAdded.TimeRemaining = (int)(instanceLock.GetEffectiveExpiryTime() - GameTime.GetSystemTime()).TotalSeconds; SendPacket(calendarRaidLockoutAdded); } - public void SendCalendarRaidLockoutUpdated(InstanceSave save) - { - if (save == null) - return; - - long currTime = GameTime.GetGameTime(); - - CalendarRaidLockoutUpdated packet = new(); - packet.DifficultyID = (uint)save.GetDifficultyID(); - packet.MapID = (int)save.GetMapId(); - packet.NewTimeRemaining = 0; // FIXME - packet.OldTimeRemaining = (int)(save.GetResetTime() - currTime); - - SendPacket(packet); - } - - public void SendCalendarRaidLockoutRemoved(InstanceSave save) - { - CalendarRaidLockoutRemoved calendarRaidLockoutRemoved = new(); - calendarRaidLockoutRemoved.InstanceID = save.GetInstanceId(); - calendarRaidLockoutRemoved.MapID = (int)save.GetMapId(); - calendarRaidLockoutRemoved.DifficultyID = save.GetDifficultyID(); - SendPacket(calendarRaidLockoutRemoved); - } - void SendCalendarRaidLockoutRemoved(InstanceLock instanceLock) { CalendarRaidLockoutRemoved calendarRaidLockoutRemoved = new(); diff --git a/Source/Game/Handlers/CharacterHandler.cs b/Source/Game/Handlers/CharacterHandler.cs index f9c6a0de1..95838121b 100644 --- a/Source/Game/Handlers/CharacterHandler.cs +++ b/Source/Game/Handlers/CharacterHandler.cs @@ -2583,10 +2583,6 @@ namespace Game stmt.AddValue(0, lowGuid); SetQuery(PlayerLoginQueryLoad.Group, stmt); - stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_INSTANCE); - stmt.AddValue(0, lowGuid); - SetQuery(PlayerLoginQueryLoad.BoundInstances, stmt); - stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_AURAS); stmt.AddValue(0, lowGuid); SetQuery(PlayerLoginQueryLoad.Auras, stmt); @@ -2866,7 +2862,6 @@ namespace Game From, Customizations, Group, - BoundInstances, Auras, AuraEffects, AuraStoredLocations, diff --git a/Source/Game/Handlers/MiscHandler.cs b/Source/Game/Handlers/MiscHandler.cs index 252090399..26bfe1c19 100644 --- a/Source/Game/Handlers/MiscHandler.cs +++ b/Source/Game/Handlers/MiscHandler.cs @@ -354,25 +354,36 @@ namespace Game if (!teleported) { WorldSafeLocsEntry entranceLocation = null; - InstanceSave instanceSave = player.GetInstanceSave(at.target_mapId); - if (instanceSave != null) + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(at.target_mapId); + if (mapEntry.Instanceable()) { // Check if we can contact the instancescript of the instance for an updated entrance location - Map map = Global.MapMgr.FindMap(at.target_mapId, player.GetInstanceSave(at.target_mapId).GetInstanceId()); - if (map) + uint targetInstanceId = Global.MapMgr.FindInstanceIdForPlayer(at.target_mapId, _player); + if (targetInstanceId != 0) { - InstanceMap instanceMap = map.ToInstanceMap(); - if (instanceMap != null) + Map map = Global.MapMgr.FindMap(at.target_mapId, targetInstanceId); + if (map != null) { - InstanceScript instanceScript = instanceMap.GetInstanceScript(); - if (instanceScript != null) - entranceLocation = Global.ObjectMgr.GetWorldSafeLoc(instanceScript.GetEntranceLocation()); + InstanceMap instanceMap = map.ToInstanceMap(); + if (instanceMap) + { + InstanceScript instanceScript = instanceMap.GetInstanceScript(); + if (instanceScript != null) + entranceLocation = Global.ObjectMgr.GetWorldSafeLoc(instanceScript.GetEntranceLocation()); + } } } // Finally check with the instancesave for an entrance location if we did not get a valid one from the instancescript if (entranceLocation == null) - entranceLocation = Global.ObjectMgr.GetWorldSafeLoc(instanceSave.GetEntranceLocation()); + { + Group group = player.GetGroup(); + Difficulty difficulty = group ? group.GetDifficultyID(mapEntry) : player.GetDifficultyID(mapEntry); + ObjectGuid instanceOwnerGuid = group ? group.GetRecentInstanceOwner(at.target_mapId) : player.GetGUID(); + InstanceLock instanceLock = Global.InstanceLockMgr.FindActiveInstanceLock(instanceOwnerGuid, new MapDb2Entries(mapEntry, Global.DB2Mgr.GetDownscaledMapDifficultyData(at.target_mapId, ref difficulty))); + if (instanceLock != null) + entranceLocation = Global.ObjectMgr.GetWorldSafeLoc(instanceLock.GetData().EntranceWorldSafeLocId); + } } if (entranceLocation != null) diff --git a/Source/Game/Handlers/MovementHandler.cs b/Source/Game/Handlers/MovementHandler.cs index 40e4135b5..01e64d83d 100644 --- a/Source/Game/Handlers/MovementHandler.cs +++ b/Source/Game/Handlers/MovementHandler.cs @@ -377,19 +377,27 @@ namespace Game if (mapEntry.IsDungeon()) { // check if this instance has a reset time and send it to player if so - Difficulty diff = newMap.GetDifficultyID(); - MapDifficultyRecord mapDiff = Global.DB2Mgr.GetMapDifficultyData(mapEntry.Id, diff); - if (mapDiff != null) + MapDb2Entries entries = new(mapEntry.Id, newMap.GetDifficultyID()); + if (entries.MapDifficulty.HasResetSchedule()) { - if (mapDiff.GetRaidDuration() != 0) + RaidInstanceMessage raidInstanceMessage = new(); + raidInstanceMessage.Type = InstanceResetWarningType.Welcome; + raidInstanceMessage.MapID = mapEntry.Id; + raidInstanceMessage.DifficultyID = newMap.GetDifficultyID(); + + InstanceLock playerLock = Global.InstanceLockMgr.FindActiveInstanceLock(GetPlayer().GetGUID(), entries); + if (playerLock != null) { - long timeReset = Global.InstanceSaveMgr.GetResetTimeFor(mapEntry.Id, diff); - if (timeReset != 0) - { - uint timeleft = (uint)(timeReset - GameTime.GetGameTime()); - player.SendInstanceResetWarning(mapEntry.Id, diff, timeleft, true); - } + raidInstanceMessage.Locked = !playerLock.IsExpired(); + raidInstanceMessage.Extended = playerLock.IsExtended(); } + else + { + raidInstanceMessage.Locked = false; + raidInstanceMessage.Extended = false; + } + + SendPacket(raidInstanceMessage); } // check if instance is valid diff --git a/Source/Game/Maps/Instances/InstanceLockManager.cs b/Source/Game/Maps/Instances/InstanceLockManager.cs index 4cf076c8e..13ff7ce5c 100644 --- a/Source/Game/Maps/Instances/InstanceLockManager.cs +++ b/Source/Game/Maps/Instances/InstanceLockManager.cs @@ -315,16 +315,20 @@ namespace Game.Maps Log.outDebug(LogFilter.Instance, $"Deleting instance {instanceId} as it is no longer referenced by any player"); } - public void UpdateInstanceLockExtensionForPlayer(ObjectGuid playerGuid, MapDb2Entries entries, bool extended) + public Tuple UpdateInstanceLockExtensionForPlayer(ObjectGuid playerGuid, MapDb2Entries entries, bool extended) { InstanceLock instanceLock = FindActiveInstanceLock(playerGuid, entries, true, false); if (instanceLock != null) { + DateTime oldExpiryTime = instanceLock.GetEffectiveExpiryTime(); instanceLock.SetExtended(extended); DB.Characters.Execute($"UPDATE character_instance_lock SET extended = {(extended ? 1 : 0)} WHERE guid = {playerGuid.GetCounter()} AND mapId = {entries.MapDifficulty.MapID} AND lockId = {entries.MapDifficulty.LockID}"); Log.outDebug(LogFilter.Instance, $"[{entries.Map.Id}-{entries.Map.MapName[Global.WorldMgr.GetDefaultDbcLocale()]} | " + $"{entries.MapDifficulty.DifficultyID}-{CliDB.DifficultyStorage.LookupByKey(entries.MapDifficulty.DifficultyID).Name}] Instance lock for {playerGuid} is {(extended ? "now" : "no longer")} extended"); + return Tuple.Create(oldExpiryTime, instanceLock.GetEffectiveExpiryTime()); } + + return Tuple.Create(DateTime.MinValue, DateTime.MinValue); } public DateTime GetNextResetTime(MapDb2Entries entries) diff --git a/Source/Game/Maps/Instances/InstanceSaveManager.cs b/Source/Game/Maps/Instances/InstanceSaveManager.cs deleted file mode 100644 index 14badc0d2..000000000 --- a/Source/Game/Maps/Instances/InstanceSaveManager.cs +++ /dev/null @@ -1,778 +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 Framework.Database; -using Game.DataStorage; -using Game.Entities; -using Game.Groups; -using Game.Scenarios; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Game.Maps -{ - public class InstanceSaveManager : Singleton - { - InstanceSaveManager() { } - - public InstanceSave AddInstanceSave(uint mapId, uint instanceId, Difficulty difficulty, long resetTime, uint entranceId, bool canReset, bool load = false) - { - InstanceSave old_save = GetInstanceSave(instanceId); - if (old_save != null) - return old_save; - - MapRecord entry = CliDB.MapStorage.LookupByKey(mapId); - if (entry == null) - { - Log.outError(LogFilter.Server, "InstanceSaveManager.AddInstanceSave: wrong mapid = {0}, instanceid = {1}!", mapId, instanceId); - return null; - } - - if (instanceId == 0) - { - Log.outError(LogFilter.Server, "InstanceSaveManager.AddInstanceSave: mapid = {0}, wrong instanceid = {1}!", mapId, instanceId); - return null; - } - - DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty); - if (difficultyEntry == null || difficultyEntry.InstanceType != entry.InstanceType) - { - Log.outError(LogFilter.Server, "InstanceSaveManager.AddInstanceSave: mapid = {0}, instanceid = {1}, wrong dificalty {2}!", mapId, instanceId, difficulty); - return null; - } - - if (entranceId != 0 && Global.ObjectMgr.GetWorldSafeLoc(entranceId) == null) - { - Log.outWarn(LogFilter.Misc, "InstanceSaveManager.AddInstanceSave: invalid entranceId = {0} defined for instance save with mapid = {1}, instanceid = {2}!", entranceId, mapId, instanceId); - entranceId = 0; - } - - if (resetTime == 0) - { - // initialize reset time - // for normal instances if no creatures are killed the instance will reset in two hours - if (entry.InstanceType == MapTypes.Raid || difficulty > Difficulty.Normal) - resetTime = GetResetTimeFor(mapId, difficulty); - else - { - resetTime = GameTime.GetGameTime() + 2 * Time.Hour; - // normally this will be removed soon after in InstanceMap.Add, prevent error - ScheduleReset(true, resetTime, new InstResetEvent(0, mapId, difficulty, instanceId)); - } - } - - Log.outDebug(LogFilter.Maps, "InstanceSaveManager.AddInstanceSave: mapid = {0}, instanceid = {1}", mapId, instanceId); - - InstanceSave save = new(mapId, instanceId, difficulty, entranceId, resetTime, canReset); - if (!load) - save.SaveToDB(); - - m_instanceSaveById[instanceId] = save; - return save; - } - - public InstanceSave GetInstanceSave(uint InstanceId) - { - return m_instanceSaveById.LookupByKey(InstanceId); - } - - public void DeleteInstanceFromDB(uint instanceid) - { - SQLTransaction trans = new(); - - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_INSTANCE_BY_INSTANCE); - stmt.AddValue(0, instanceid); - trans.Append(stmt); - - stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE); - stmt.AddValue(0, instanceid); - trans.Append(stmt); - - stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_INSTANCE_BY_INSTANCE); - stmt.AddValue(0, instanceid); - trans.Append(stmt); - - stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_SCENARIO_INSTANCE_CRITERIA_FOR_INSTANCE); - stmt.AddValue(0, instanceid); - trans.Append(stmt); - - DB.Characters.CommitTransaction(trans); - // Respawn times should be deleted only when the map gets unloaded - } - - public void RemoveInstanceSave(uint InstanceId) - { - var instanceSave = m_instanceSaveById.LookupByKey(InstanceId); - if (instanceSave != null) - { - // save the resettime for normal instances only when they get unloaded - long resettime = instanceSave.GetResetTimeForDB(); - if (resettime != 0) - { - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_INSTANCE_RESETTIME); - stmt.AddValue(0, resettime); - stmt.AddValue(1, InstanceId); - DB.Characters.Execute(stmt); - } - - instanceSave.SetToDelete(true); - m_instanceSaveById.Remove(InstanceId); - } - } - - public void UnloadInstanceSave(uint InstanceId) - { - InstanceSave save = GetInstanceSave(InstanceId); - if (save != null) - save.UnloadIfEmpty(); - } - - public void LoadInstances() - { - uint oldMSTime = Time.GetMSTime(); - - // Delete expired instances (Instance related spawns are removed in the following cleanup queries) - DB.Characters.DirectExecute("DELETE i FROM instance i LEFT JOIN instance_reset ir ON mapid = map AND i.difficulty = ir.difficulty " + - "WHERE (i.resettime > 0 AND i.resettime < UNIX_TIMESTAMP()) OR (ir.resettime IS NOT NULL AND ir.resettime < UNIX_TIMESTAMP())"); - - // Delete invalid character_instance and group_instance references - DB.Characters.DirectExecute("DELETE ci.* FROM character_instance AS ci LEFT JOIN characters AS c ON ci.guid = c.guid WHERE c.guid IS NULL"); - DB.Characters.DirectExecute("DELETE gi.* FROM group_instance AS gi LEFT JOIN `groups` AS g ON gi.guid = g.guid WHERE g.guid IS NULL"); - - // Delete invalid instance references - DB.Characters.DirectExecute("DELETE i.* FROM instance AS i LEFT JOIN character_instance AS ci ON i.id = ci.instance LEFT JOIN group_instance AS gi ON i.id = gi.instance WHERE ci.guid IS NULL AND gi.guid IS NULL"); - - // Delete invalid references to instance - DB.Characters.DirectExecute("DELETE FROM respawn WHERE instanceId > 0 AND instanceId NOT IN (SELECT id FROM instance)"); - DB.Characters.DirectExecute("DELETE tmp.* FROM character_instance AS tmp LEFT JOIN instance ON tmp.instance = instance.id WHERE tmp.instance > 0 AND instance.id IS NULL"); - DB.Characters.DirectExecute("DELETE tmp.* FROM group_instance AS tmp LEFT JOIN instance ON tmp.instance = instance.id WHERE tmp.instance > 0 AND instance.id IS NULL"); - - // Clean invalid references to instance - DB.Characters.DirectExecute("UPDATE corpse SET instanceId = 0 WHERE instanceId > 0 AND instanceId NOT IN (SELECT id FROM instance)"); - DB.Characters.DirectExecute("UPDATE characters AS tmp LEFT JOIN instance ON tmp.instance_id = instance.id SET tmp.instance_id = 0 WHERE tmp.instance_id > 0 AND instance.id IS NULL"); - - // Initialize instance id storage (Needs to be done after the trash has been clean out) - Global.MapMgr.InitInstanceIds(); - - // Load reset times and clean expired instances - LoadResetTimes(); - - Log.outInfo(LogFilter.ServerLoading, "Loaded instances in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime)); - } - - void LoadResetTimes() - { - long now = GameTime.GetGameTime(); - long today = (now / Time.Day) * Time.Day; - - // NOTE: Use DirectPExecute for tables that will be queried later - - // get the current reset times for normal instances (these may need to be updated) - // these are only kept in memory for InstanceSaves that are loaded later - // resettime = 0 in the DB for raid/heroic instances so those are skipped - Dictionary> instResetTime = new(); - - // index instance ids by map/difficulty pairs for fast reset warning send - MultiMap mapDiffResetInstances = new(); - - SQLResult result = DB.Characters.Query("SELECT id, map, difficulty, resettime FROM instance ORDER BY id ASC"); - if (!result.IsEmpty()) - { - do - { - uint instanceId = result.Read(0); - - // Mark instance id as being used - Global.MapMgr.RegisterInstanceId(instanceId); - long resettime = result.Read(3); - if (resettime != 0) - { - uint mapid = result.Read(1); - uint difficulty = result.Read(2); - - instResetTime[instanceId] = Tuple.Create(MathFunctions.MakePair32(mapid, difficulty), resettime); - mapDiffResetInstances.Add(MathFunctions.MakePair32(mapid, difficulty), instanceId); - } - } - while (result.NextRow()); - - // schedule the reset times - foreach (var pair in instResetTime) - if (pair.Value.Item2 > now) - ScheduleReset(true, pair.Value.Item2, new InstResetEvent(0, MathFunctions.Pair32_LoPart(pair.Value.Item1), (Difficulty)MathFunctions.Pair32_HiPart(pair.Value.Item1), pair.Key)); - } - - // load the global respawn times for raid/heroic instances - uint resetHour = WorldConfig.GetUIntValue(WorldCfg.InstanceResetTimeHour); - result = DB.Characters.Query("SELECT mapid, difficulty, resettime FROM instance_reset"); - if (!result.IsEmpty()) - { - do - { - uint mapid = result.Read(0); - Difficulty difficulty = (Difficulty)result.Read(1); - long oldresettime = result.Read(2); - - MapDifficultyRecord mapDiff = Global.DB2Mgr.GetMapDifficultyData(mapid, difficulty); - if (mapDiff == null) - { - Log.outError(LogFilter.Server, "InstanceSaveManager.LoadResetTimes: invalid mapid({0})/difficulty({1}) pair in instance_reset!", mapid, difficulty); - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GLOBAL_INSTANCE_RESETTIME); - stmt.AddValue(0, mapid); - stmt.AddValue(1, (byte)difficulty); - DB.Characters.DirectExecute(stmt); - continue; - } - - // update the reset time if the hour in the configs changes - long newresettime = Time.GetLocalHourTimestamp(oldresettime, resetHour, false); - if (oldresettime != newresettime) - { - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GLOBAL_INSTANCE_RESETTIME); - stmt.AddValue(0, newresettime); - stmt.AddValue(1, mapid); - stmt.AddValue(2, (byte)difficulty); - DB.Characters.DirectExecute(stmt); - } - - InitializeResetTimeFor(mapid, difficulty, newresettime); - } while (result.NextRow()); - } - - // calculate new global reset times for expired instances and those that have never been reset yet - // add the global reset times to the priority queue - foreach (var mapDifficultyPair in Global.DB2Mgr.GetMapDifficulties()) - { - uint mapid = mapDifficultyPair.Key; - - foreach (var difficultyPair in mapDifficultyPair.Value) - { - Difficulty difficulty = (Difficulty)difficultyPair.Key; - MapDifficultyRecord mapDiff = difficultyPair.Value; - if (mapDiff.GetRaidDuration() == 0) - continue; - - // the reset_delay must be at least one day - uint period = (uint)(((mapDiff.GetRaidDuration() * WorldConfig.GetFloatValue(WorldCfg.RateInstanceResetTime)) / Time.Day) * Time.Day); - if (period < Time.Day) - period = Time.Day; - - long t = GetResetTimeFor(mapid, difficulty); - if (t == 0) - { - // initialize the reset time - t = Time.GetLocalHourTimestamp(today + period, resetHour); - - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GLOBAL_INSTANCE_RESETTIME); - stmt.AddValue(0, mapid); - stmt.AddValue(1, (byte)difficulty); - stmt.AddValue(2, t); - DB.Characters.DirectExecute(stmt); - } - - if (t < now) - { - // assume that expired instances have already been cleaned - // calculate the next reset time - long day = (t / Time.Day) * Time.Day; - t = Time.GetLocalHourTimestamp(day + ((today - day) / period + 1) * period, resetHour); - - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GLOBAL_INSTANCE_RESETTIME); - stmt.AddValue(0, t); - stmt.AddValue(1, mapid); - stmt.AddValue(2, (byte)difficulty); - DB.Characters.DirectExecute(stmt); - } - - InitializeResetTimeFor(mapid, difficulty, t); - - // schedule the global reset/warning - byte type; - for (type = 1; type < 4; ++type) - if (t - ResetTimeDelay[type - 1] > now) - break; - - ScheduleReset(true, t - ResetTimeDelay[type - 1], new InstResetEvent(type, mapid, difficulty, 0)); - - var range = mapDiffResetInstances.LookupByKey(MathFunctions.MakePair32(mapid, (uint)difficulty)); - foreach (var id in range) - ScheduleReset(true, t - ResetTimeDelay[type - 1], new InstResetEvent(type, mapid, difficulty, id)); - - } - } - } - - public long GetSubsequentResetTime(uint mapid, Difficulty difficulty, long resetTime) - { - MapDifficultyRecord mapDiff = Global.DB2Mgr.GetMapDifficultyData(mapid, difficulty); - if (mapDiff == null || mapDiff.GetRaidDuration() == 0) - { - Log.outError(LogFilter.Misc, "InstanceSaveManager.GetSubsequentResetTime: not valid difficulty or no reset delay for map {0}", mapid); - return 0; - } - - long resetHour = WorldConfig.GetIntValue(WorldCfg.InstanceResetTimeHour); - long period = (uint)(((mapDiff.GetRaidDuration() * WorldConfig.GetFloatValue(WorldCfg.RateInstanceResetTime)) / Time.Day) * Time.Day); - if (period < Time.Day) - period = Time.Day; - - return Time.GetLocalHourTimestamp(((resetTime + Time.Minute) / Time.Day * Time.Day) + period, (uint)resetHour); - } - - public void ScheduleReset(bool add, long time, InstResetEvent Event) - { - if (!add) - { - // find the event in the queue and remove it - var range = m_resetTimeQueue.LookupByKey(time); - foreach (var instResetEvent in range) - { - if (instResetEvent == Event) - { - m_resetTimeQueue.Remove(time, instResetEvent); - return; - } - } - - // in case the reset time changed (should happen very rarely), we search the whole queue - foreach (var pair in m_resetTimeQueue) - { - if (pair.Value == Event) - { - m_resetTimeQueue.Remove(pair); - return; - } - } - - Log.outError(LogFilter.Server, "InstanceSaveManager.ScheduleReset: cannot cancel the reset, the event({0}, {1}, {2}) was not found!", Event.type, Event.mapid, Event.instanceId); - } - else - m_resetTimeQueue.Add(time, Event); - } - - public void ForceGlobalReset(uint mapId, Difficulty difficulty) - { - if (Global.DB2Mgr.GetDownscaledMapDifficultyData(mapId, ref difficulty) == null) - return; - // remove currently scheduled reset times - ScheduleReset(false, 0, new InstResetEvent(1, mapId, difficulty, 0)); - ScheduleReset(false, 0, new InstResetEvent(4, mapId, difficulty, 0)); - // force global reset on the instance - _ResetOrWarnAll(mapId, difficulty, false, GameTime.GetGameTime()); - } - - public void Update() - { - long now = GameTime.GetGameTime(); - - while (!m_resetTimeQueue.Empty()) - { - var pair = m_resetTimeQueue.First(); - long time = pair.Key; - if (time >= now) - break; - - InstResetEvent Event = pair.Value; - if (Event.type == 0) - { - // for individual normal instances, max creature respawn + X hours - _ResetInstance(Event.mapid, Event.instanceId); - m_resetTimeQueue.Remove(pair); - } - else - { - // global reset/warning for a certain map - long resetTime = GetResetTimeFor(Event.mapid, Event.difficulty); - _ResetOrWarnAll(Event.mapid, Event.difficulty, Event.type != 4, resetTime); - if (Event.type != 4) - { - // schedule the next warning/reset - ++Event.type; - ScheduleReset(true, resetTime - ResetTimeDelay[Event.type - 1], Event); - } - m_resetTimeQueue.Remove(pair); - } - } - } - - void _ResetSave(KeyValuePair pair) - { - // unbind all players bound to the instance - // do not allow UnbindInstance to automatically unload the InstanceSaves - lock_instLists = true; - - bool shouldDelete = true; - var pList = pair.Value.m_playerList; - List temp = new(); // list of expired binds that should be unbound - foreach (var player in pList) - { - InstanceBind bind = player.GetBoundInstance(pair.Value.GetMapId(), pair.Value.GetDifficultyID()); - if (bind != null) - { - Cypher.Assert(bind.save == pair.Value); - if (bind.perm && bind.extendState != 0) // permanent and not already expired - { - // actual promotion in DB already happened in caller - bind.extendState = bind.extendState == BindExtensionState.Extended ? BindExtensionState.Normal : BindExtensionState.Expired; - shouldDelete = false; - continue; - } - } - temp.Add(player); - } - - var gList = pair.Value.m_groupList; - while (!gList.Empty()) - { - Group group = gList.First(); - group.UnbindInstance(pair.Value.GetMapId(), pair.Value.GetDifficultyID(), true); - } - - if (shouldDelete) - m_instanceSaveById.Remove(pair.Key); - - lock_instLists = false; - } - - void _ResetInstance(uint mapid, uint instanceId) - { - Log.outDebug(LogFilter.Maps, "InstanceSaveMgr._ResetInstance {0}, {1}", mapid, instanceId); - var map = CliDB.MapStorage.LookupByKey(mapid); - if (!map.IsDungeon()) - return; - - var pair = m_instanceSaveById.Find(instanceId); - if (pair.Value != null) - _ResetSave(pair); - - DeleteInstanceFromDB(instanceId); // even if save not loaded - - Map iMap = Global.MapMgr.FindMap(mapid, instanceId); - if (iMap != null) - { - ((InstanceMap)iMap).Reset(InstanceResetMethod.RespawnDelay); - iMap.DeleteRespawnTimes(); - iMap.DeleteCorpseData(); - } - else - Map.DeleteRespawnTimesInDB(mapid, instanceId); - - // Free up the instance id and allow it to be reused - Global.MapMgr.FreeInstanceId(instanceId); - } - - void _ResetOrWarnAll(uint mapid, Difficulty difficulty, bool warn, long resetTime) - { - // global reset for all instances of the given map - MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapid); - if (!mapEntry.Instanceable()) - return; - - Log.outDebug(LogFilter.Misc, "InstanceSaveManager.ResetOrWarnAll: Processing map {0} ({1}) on difficulty {2} (warn? {3})", mapEntry.MapName[Global.WorldMgr.GetDefaultDbcLocale()], mapid, difficulty, warn); - long now = GameTime.GetGameTime(); - - if (!warn) - { - // calculate the next reset time - long next_reset = GetSubsequentResetTime(mapid, difficulty, resetTime); - if (next_reset == 0) - return; - - // delete them from the DB, even if not loaded - SQLTransaction trans = new(); - - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF); - stmt.AddValue(0, mapid); - stmt.AddValue(1, (byte)difficulty); - trans.Append(stmt); - - stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_INSTANCE_BY_MAP_DIFF); - stmt.AddValue(0, mapid); - stmt.AddValue(1, (byte)difficulty); - trans.Append(stmt); - - stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_EXPIRED_INSTANCE_BY_MAP_DIFF); - stmt.AddValue(0, mapid); - stmt.AddValue(1, (byte)difficulty); - trans.Append(stmt); - - stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_EXPIRE_CHAR_INSTANCE_BY_MAP_DIFF); - stmt.AddValue(0, mapid); - stmt.AddValue(1, (byte)difficulty); - trans.Append(stmt); - - DB.Characters.CommitTransaction(trans); - - // promote loaded binds to instances of the given map - foreach (var pair in m_instanceSaveById.ToList()) - { - if (pair.Value.GetMapId() == mapid && pair.Value.GetDifficultyID() == difficulty) - _ResetSave(pair); - } - - SetResetTimeFor(mapid, difficulty, next_reset); - ScheduleReset(true, next_reset - 3600, new InstResetEvent(1, mapid, difficulty, 0)); - - // Update it in the DB - stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GLOBAL_INSTANCE_RESETTIME); - stmt.AddValue(0, (uint)next_reset); - stmt.AddValue(1, (ushort)mapid); - stmt.AddValue(2, (byte)difficulty); - - DB.Characters.Execute(stmt); - } - - // note: this isn't fast but it's meant to be executed very rarely - if (mapEntry.IsDungeon()) - { - Global.MapMgr.DoForAllMapsWithMapId(mapid, map => - { - if (warn) - { - uint timeLeft; - if (now >= resetTime) - timeLeft = 0; - else - timeLeft = (uint)(resetTime - now); - - ((InstanceMap)map).SendResetWarnings(timeLeft); - } - else - ((InstanceMap)map).Reset(InstanceResetMethod.Global); - }); - } - - // @todo delete creature/gameobject respawn times even if the maps are not loaded - } - - public uint GetNumBoundPlayersTotal() - { - uint ret = 0; - foreach (var pair in m_instanceSaveById) - ret += pair.Value.GetPlayerCount(); - - return ret; - } - - public uint GetNumBoundGroupsTotal() - { - uint ret = 0; - foreach (var pair in m_instanceSaveById) - ret += pair.Value.GetGroupCount(); - - return ret; - } - - public long GetResetTimeFor(uint mapid, Difficulty d) - { - return m_resetTimeByMapDifficulty.LookupByKey(MathFunctions.MakePair64(mapid, (uint)d)); - } - - // Use this on startup when initializing reset times - void InitializeResetTimeFor(uint mapid, Difficulty d, long t) - { - m_resetTimeByMapDifficulty[MathFunctions.MakePair64(mapid, (uint)d)] = t; - } - // Use this only when updating existing reset times - void SetResetTimeFor(uint mapid, Difficulty d, long t) - { - var key = MathFunctions.MakePair64(mapid, (uint)d); - Cypher.Assert(m_resetTimeByMapDifficulty.ContainsKey(key)); - m_resetTimeByMapDifficulty[key] = t; - } - - public Dictionary GetResetTimeMap() - { - return m_resetTimeByMapDifficulty; - } - - public int GetNumInstanceSaves() { return m_instanceSaveById.Count; } - - public class InstResetEvent - { - public InstResetEvent(byte t = 0, uint _mapid = 0, Difficulty d = Difficulty.Normal, uint _instanceid = 0) - { - type = t; - difficulty = d; - mapid = _mapid; - instanceId = _instanceid; - } - - public byte type; - public Difficulty difficulty; - public uint mapid; - public uint instanceId; - } - - static ushort[] ResetTimeDelay = { 3600, 900, 300, 60 }; - - // used during global instance resets - public bool lock_instLists; - // fast lookup by instance id - Dictionary m_instanceSaveById = new(); - // fast lookup for reset times (always use existed functions for access/set) - Dictionary m_resetTimeByMapDifficulty = new(); - MultiMap m_resetTimeQueue = new(); - } - - public class InstanceSave - { - public InstanceSave(uint MapId, uint InstanceId, Difficulty difficulty, uint entranceId, long resetTime, bool canReset) - { - m_resetTime = resetTime; - m_instanceid = InstanceId; - m_mapid = MapId; - m_difficulty = difficulty; - m_entranceId = entranceId; - m_canReset = canReset; - m_toDelete = false; - } - - public void SaveToDB() - { - // save instance data too - string data = ""; - uint completedEncounters = 0; - - Map map = Global.MapMgr.FindMap(GetMapId(), m_instanceid); - if (map != null) - { - Cypher.Assert(map.IsDungeon()); - InstanceScript instanceScript = ((InstanceMap)map).GetInstanceScript(); - if (instanceScript != null) - { - data = instanceScript.GetSaveData(); - completedEncounters = instanceScript.GetCompletedEncounterMask(); - m_entranceId = instanceScript.GetEntranceLocation(); - } - - InstanceScenario scenario = map.ToInstanceMap().GetInstanceScenario(); - if (scenario != null) - scenario.SaveToDB(); - } - - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_INSTANCE_SAVE); - stmt.AddValue(0, m_instanceid); - stmt.AddValue(1, GetMapId()); - stmt.AddValue(2, GetResetTimeForDB()); - stmt.AddValue(3, (uint)GetDifficultyID()); - stmt.AddValue(4, completedEncounters); - stmt.AddValue(5, data); - stmt.AddValue(6, m_entranceId); - DB.Characters.Execute(stmt); - } - - public long GetResetTimeForDB() - { - // only save the reset time for normal instances - MapRecord entry = CliDB.MapStorage.LookupByKey(GetMapId()); - if (entry == null || entry.InstanceType == MapTypes.Raid || GetDifficultyID() == Difficulty.Heroic) - return 0; - else - return GetResetTime(); - } - - MapRecord GetMapEntry() - { - return CliDB.MapStorage.LookupByKey(m_mapid); - } - - public void DeleteFromDB() - { - Global.InstanceSaveMgr.DeleteInstanceFromDB(GetInstanceId()); - } - - public bool UnloadIfEmpty() - { - if (m_playerList.Empty() && m_groupList.Empty()) - { - if (!Global.InstanceSaveMgr.lock_instLists) - Global.InstanceSaveMgr.RemoveInstanceSave(GetInstanceId()); - - return false; - } - else - return true; - } - - public uint GetPlayerCount() { return (uint)m_playerList.Count; } - public uint GetGroupCount() { return (uint)m_groupList.Count; } - - public uint GetInstanceId() { return m_instanceid; } - public uint GetMapId() { return m_mapid; } - - public long GetResetTime() { return m_resetTime; } - public void SetResetTime(long resetTime) { m_resetTime = resetTime; } - - public uint GetEntranceLocation() { return m_entranceId; } - void SetEntranceLocation(uint entranceId) { m_entranceId = entranceId; } - - public void AddPlayer(Player player) - { - m_playerList.Add(player); - } - public bool RemovePlayer(Player player) - { - m_playerList.Remove(player); - - return UnloadIfEmpty(); - } - - public void AddGroup(Group group) { m_groupList.Add(group); } - public bool RemoveGroup(Group group) - { - m_groupList.Remove(group); - - return UnloadIfEmpty(); - } - - public bool CanReset() { return m_canReset; } - public void SetCanReset(bool canReset) { m_canReset = canReset; } - - public Difficulty GetDifficultyID() { return m_difficulty; } - - public void SetToDelete(bool toDelete) - { - m_toDelete = toDelete; - } - - public List m_playerList = new(); - public List m_groupList = new(); - long m_resetTime; - uint m_instanceid; - uint m_mapid; - Difficulty m_difficulty; - uint m_entranceId; - 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/InstanceScript.cs b/Source/Game/Maps/Instances/InstanceScript.cs index a94300bc6..6342cd55e 100644 --- a/Source/Game/Maps/Instances/InstanceScript.cs +++ b/Source/Game/Maps/Instances/InstanceScript.cs @@ -430,7 +430,7 @@ namespace Game.Maps bossInfo.state = state; SaveToDB(); - if (state == EncounterState.Done) + if (state == EncounterState.Done && dungeonEncounter != null) instance.UpdateInstanceLock(dungeonEncounter, new(id, state)); } @@ -754,11 +754,20 @@ namespace Game.Maps return false; } + bool IsEncounterCompleted(uint dungeonEncounterId) + { + for (uint i = 0; i < bosses.Count; ++i) + for (var j = 0; j < bosses[i].DungeonEncounters.Length; ++j) + if (bosses[i].DungeonEncounters[j] != null && bosses[i].DungeonEncounters[j].Id == dungeonEncounterId) + return bosses[i].state == EncounterState.Done; + + return false; + } + public void SetEntranceLocation(uint worldSafeLocationId) { _entranceId = worldSafeLocationId; - if (_temporaryEntranceId != 0) - _temporaryEntranceId = 0; + _temporaryEntranceId = 0; } public void SendEncounterUnit(EncounterFrameType type, Unit unit = null, byte priority = 0) diff --git a/Source/Game/Maps/Map.cs b/Source/Game/Maps/Map.cs index 7851c956c..ecccd360a 100644 --- a/Source/Game/Maps/Map.cs +++ b/Source/Game/Maps/Map.cs @@ -1713,20 +1713,17 @@ namespace Game.Maps { //Get instance where player's group is bound & its map uint instanceIdToCheck = Global.MapMgr.FindInstanceIdForPlayer(mapid, player); - if (instanceIdToCheck != 0) + Map boundMap = Global.MapMgr.FindMap(mapid, instanceIdToCheck); + if (boundMap != null) { - Map boundMap = Global.MapMgr.FindMap(mapid, instanceIdToCheck); - if (boundMap != null) - { - EnterState denyReason = boundMap.CannotEnter(player); - if (denyReason != 0) - return denyReason; - } - - // players are only allowed to enter 10 instances per hour - if (entry.IsDungeon() && !player.CheckInstanceCount(instanceIdToCheck) && !player.IsDead()) - return EnterState.CannotEnterTooManyInstances; + EnterState denyReason = boundMap.CannotEnter(player); + if (denyReason != 0) + return denyReason; } + + // players are only allowed to enter 10 instances per hour + if (entry.IsDungeon() && !player.CheckInstanceCount(instanceIdToCheck) && !player.IsDead()) + return EnterState.CannotEnterTooManyInstances; } return EnterState.CanEnter; @@ -2090,6 +2087,9 @@ namespace Game.Maps void DeleteRespawnInfoFromDB(SpawnObjectType type, ulong spawnId, SQLTransaction dbTrans = null) { + if (Instanceable()) + return; + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_RESPAWN); stmt.AddValue(0, (ushort)type); stmt.AddValue(1, spawnId); @@ -2727,6 +2727,9 @@ namespace Game.Maps public void SaveRespawnInfoDB(RespawnInfo info, SQLTransaction dbTrans = null) { + if (Instanceable()) + return; + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_RESPAWN); stmt.AddValue(0, (ushort)info.type); stmt.AddValue(1, info.spawnId); @@ -2738,6 +2741,9 @@ namespace Game.Maps public void LoadRespawnTimes() { + if (Instanceable()) + return; + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_RESPAWNS); stmt.AddValue(0, GetId()); stmt.AddValue(1, GetInstanceId()); @@ -2768,14 +2774,17 @@ namespace Game.Maps public void DeleteRespawnTimes() { UnloadAllRespawnInfos(); - DeleteRespawnTimesInDB(GetId(), GetInstanceId()); + DeleteRespawnTimesInDB(); } - public static void DeleteRespawnTimesInDB(uint mapId, uint instanceId) + public void DeleteRespawnTimesInDB() { + if (Instanceable()) + return; + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ALL_RESPAWNS); - stmt.AddValue(0, mapId); - stmt.AddValue(1, instanceId); + stmt.AddValue(0, GetId()); + stmt.AddValue(1, GetInstanceId()); DB.Characters.Execute(stmt); } @@ -3284,11 +3293,6 @@ namespace Game.Maps return i_mapRecord != null && i_mapRecord.IsRaid(); } - public bool IsRaidOrHeroicDungeon() - { - return IsRaid() || IsHeroic(); - } - public bool IsHeroic() { DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(i_spawnMode); @@ -4893,7 +4897,6 @@ namespace Game.Maps // for normal instances schedule the reset after all players have left SetResetSchedule(true); - Global.InstanceSaveMgr.UnloadInstanceSave(GetInstanceId()); } public void CreateInstanceData() @@ -4942,10 +4945,11 @@ namespace Game.Maps public bool Reset(InstanceResetMethod method) { // note: since the map may not be loaded when the instance needs to be reset - // the instance must be deleted from the DB by InstanceSaveManager + // the instance must be deleted from the DB if (HavePlayers()) { + // on manual reset, fail if (method == InstanceResetMethod.All || method == InstanceResetMethod.ChangeDifficulty) { // notify the players to leave the instance so it can be reset @@ -4954,24 +4958,15 @@ namespace Game.Maps } else { - bool doUnload = true; + // on lock expiration boot players (do we also care about extension state?) if (method == InstanceResetMethod.Global) { // set the homebind timer for players inside (1 minute) foreach (Player player in GetPlayers()) - { - InstanceBind bind = player.GetBoundInstance(GetId(), GetDifficultyID()); - if (bind != null && bind.extendState != 0 && bind.save.GetInstanceId() == GetInstanceId()) - doUnload = false; - else - player.m_InstanceValid = false; - } - - if (doUnload && HasPermBoundPlayers()) // check if any unloaded players have a nonexpired save to this - doUnload = false; + player.m_InstanceValid = false; } - if (doUnload) + if (!HasPermBoundPlayers()) { // the unload timer is not started // instead the map will unload immediately after the players have left @@ -5075,28 +5070,9 @@ namespace Game.Maps base.UnloadAll(); } - public void SendResetWarnings(uint timeLeft) - { - foreach (Player player in GetPlayers()) - player.SendInstanceResetWarning(GetId(), player.GetDifficultyID(GetEntry()), timeLeft, true); - } - public void SetResetSchedule(bool on) { - // only for normal instances - // the reset time is only scheduled when there are no payers inside - // it is assumed that the reset time will rarely (if ever) change while the reset is scheduled - if (IsDungeon() && !HavePlayers() && !IsRaidOrHeroicDungeon()) - { - InstanceSave save = Global.InstanceSaveMgr.GetInstanceSave(GetInstanceId()); - if (save != null) - Global.InstanceSaveMgr.ScheduleReset(on, save.GetResetTime(), - new InstanceSaveManager.InstResetEvent(0, GetId(), GetDifficultyID(), GetInstanceId())); - else - Log.outError(LogFilter.Maps, - "InstanceMap.SetResetSchedule: cannot turn schedule {0}, there is no save information for instance (map [id: {1}, name: {2}], instance id: {3}, difficulty: {4})", - on ? "on" : "off", GetId(), GetMapName(), GetInstanceId(), GetDifficultyID()); - } + } bool HasPermBoundPlayers() @@ -5115,12 +5091,6 @@ namespace Game.Maps return GetEntry().MaxPlayers; } - public uint GetMaxResetDelay() - { - MapDifficultyRecord mapDiff = GetMapDifficulty(); - return mapDiff != null ? mapDiff.GetRaidDuration() : 0; - } - public int GetTeamIdInInstance() { if (Global.WorldStateMgr.GetValue(WorldStates.TeamInInstanceAlliance, this) != 0) diff --git a/Source/Game/Maps/MapManager.cs b/Source/Game/Maps/MapManager.cs index a887b7b29..1bea95ec1 100644 --- a/Source/Game/Maps/MapManager.cs +++ b/Source/Game/Maps/MapManager.cs @@ -242,6 +242,50 @@ namespace Game.Entities return FindMap_i(mapId, instanceId); } + public uint FindInstanceIdForPlayer(uint mapId, Player player) + { + MapRecord entry = CliDB.MapStorage.LookupByKey(mapId); + if (entry == null) + return 0; + + if (entry.IsBattlegroundOrArena()) + return player.GetBattlegroundId(); + else if (entry.IsDungeon()) + { + Group group = player.GetGroup(); + Difficulty difficulty = group != null ? group.GetDifficultyID(entry) : player.GetDifficultyID(entry); + MapDb2Entries entries = new(entry, Global.DB2Mgr.GetDownscaledMapDifficultyData(mapId, ref difficulty)); + + ObjectGuid instanceOwnerGuid = group ? group.GetRecentInstanceOwner(mapId) : player.GetGUID(); + InstanceLock instanceLock = Global.InstanceLockMgr.FindActiveInstanceLock(instanceOwnerGuid, entries); + uint newInstanceId = 0; + if (instanceLock != null) + newInstanceId = instanceLock.GetInstanceId(); + else if (!entries.MapDifficulty.HasResetSchedule()) // Try finding instance id for normal dungeon + newInstanceId = group ? group.GetRecentInstanceId(mapId) : player.GetRecentInstanceId(mapId); + + if (newInstanceId == 0) + return 0; + + Map map = FindMap(mapId, newInstanceId); + + // is is possible that instance id is already in use by another group for boss-based locks + if (!entries.IsInstanceIdBound() && instanceLock != null && map != null && map.ToInstanceMap().GetInstanceLock() != instanceLock) + return 0; + + return newInstanceId; + } + else if (entry.IsGarrison()) + return (uint)player.GetGUID().GetCounter(); + else + { + if (entry.IsSplitByFaction()) + return (uint)player.GetTeamId(); + + return 0; + } + } + public void Update(uint diff) { i_timer.Update(diff); diff --git a/Source/Game/Networking/Packets/CalendarPackets.cs b/Source/Game/Networking/Packets/CalendarPackets.cs index 96297e7ca..57cc268e6 100644 --- a/Source/Game/Networking/Packets/CalendarPackets.cs +++ b/Source/Game/Networking/Packets/CalendarPackets.cs @@ -632,11 +632,11 @@ namespace Game.Networking.Packets _worldPacket.WriteInt32(NewTimeRemaining); } - public int MapID; - public int OldTimeRemaining; public long ServerTime; + public int MapID; public uint DifficultyID; public int NewTimeRemaining; + public int OldTimeRemaining; } class CalendarCommunityInvite : ServerPacket diff --git a/Source/Game/Scenarios/InstanceScenario.cs b/Source/Game/Scenarios/InstanceScenario.cs index 119f8cfc2..b2176cb6a 100644 --- a/Source/Game/Scenarios/InstanceScenario.cs +++ b/Source/Game/Scenarios/InstanceScenario.cs @@ -65,8 +65,9 @@ namespace Game.Scenarios Criteria criteria = Global.CriteriaMgr.GetCriteria(iter.Key); switch (criteria.Entry.Type) { - // Blizzard only appears to store creature kills + // Blizzard only appears to store creature kills and dungeon encounters case CriteriaType.KillCreature: + case CriteriaType.DefeatDungeonEncounter: break; default: continue; @@ -131,6 +132,7 @@ namespace Game.Scenarios { // Blizzard appears to only stores creatures killed progress for unknown reasons. Either technical shortcoming or intentional case CriteriaType.KillCreature: + case CriteriaType.DefeatDungeonEncounter: break; default: continue; diff --git a/Source/Game/Scripting/CoreScripts.cs b/Source/Game/Scripting/CoreScripts.cs index f6997bdaf..07666eb3a 100644 --- a/Source/Game/Scripting/CoreScripts.cs +++ b/Source/Game/Scripting/CoreScripts.cs @@ -681,7 +681,7 @@ namespace Game.Scripting public virtual void OnSave(Player player) { } // Called when a player is bound to an instance - public virtual void OnBindToInstance(Player player, Difficulty difficulty, uint mapId, bool permanent, BindExtensionState extendState) { } + public virtual void OnBindToInstance(Player player, Difficulty difficulty, uint mapId, bool permanent, byte extendState) { } // Called when a player switches to a new zone public virtual void OnUpdateZone(Player player, uint newZone, uint newArea) { } diff --git a/Source/Game/Scripting/ScriptManager.cs b/Source/Game/Scripting/ScriptManager.cs index 0521d3f73..d62874708 100644 --- a/Source/Game/Scripting/ScriptManager.cs +++ b/Source/Game/Scripting/ScriptManager.cs @@ -963,7 +963,7 @@ namespace Game.Scripting { ForEach(p => p.OnSave(player)); } - public void OnPlayerBindToInstance(Player player, Difficulty difficulty, uint mapid, bool permanent, BindExtensionState extendState) + public void OnPlayerBindToInstance(Player player, Difficulty difficulty, uint mapid, bool permanent, byte extendState) { ForEach(p => p.OnBindToInstance(player, difficulty, mapid, permanent, extendState)); } diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index 8cda38451..6a8bdce2a 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -5411,22 +5411,16 @@ namespace Game.Spells return SpellCastResult.SummonPending; // check if our map is dungeon - MapRecord map = CliDB.MapStorage.LookupByKey(m_caster.GetMapId()); - if (map.IsDungeon()) + InstanceMap map = m_caster.GetMap().ToInstanceMap(); + if (map != null) { - uint mapId = m_caster.GetMap().GetId(); - Difficulty difficulty = m_caster.GetMap().GetDifficultyID(); - if (map.IsRaid()) - { - InstanceBind targetBind = target.GetBoundInstance(mapId, difficulty); - if (targetBind != null) - { - InstanceBind casterBind = m_caster.ToPlayer().GetBoundInstance(mapId, difficulty); - if (casterBind != null) - if (targetBind.perm && targetBind.save != casterBind.save) - return SpellCastResult.TargetLockedToRaidInstance; - } - } + uint mapId = map.GetId(); + Difficulty difficulty = map.GetDifficultyID(); + InstanceLock mapLock = map.GetInstanceLock(); + if (mapLock != null) + if (Global.InstanceLockMgr.CanJoinInstanceLock(target.GetGUID(), new MapDb2Entries(mapId, difficulty), mapLock) != TransferAbortReason.None) + return SpellCastResult.TargetLockedToRaidInstance; + if (!target.Satisfy(Global.ObjectMgr.GetAccessRequirement(mapId, difficulty), mapId)) return SpellCastResult.BadTargets; } diff --git a/Source/Game/World/WorldManager.cs b/Source/Game/World/WorldManager.cs index e406d3217..9c154ce27 100644 --- a/Source/Game/World/WorldManager.cs +++ b/Source/Game/World/WorldManager.cs @@ -514,8 +514,8 @@ namespace Game // Must be called before `respawn` data Log.outInfo(LogFilter.ServerLoading, "Loading instances..."); - Global.InstanceSaveMgr.LoadInstances(); + Global.MapMgr.InitInstanceIds(); Global.InstanceLockMgr.Load(); Log.outInfo(LogFilter.ServerLoading, "Loading Localization strings..."); @@ -1493,9 +1493,6 @@ namespace Game Global.GuildMgr.SaveGuilds(); } - // update the instance reset times - Global.InstanceSaveMgr.Update(); - // Check for shutdown warning if (_guidWarn && !_guidAlert) {