diff --git a/Source/Framework/Constants/AccountConst.cs b/Source/Framework/Constants/AccountConst.cs index 78b73f1ac..755d2805a 100644 --- a/Source/Framework/Constants/AccountConst.cs +++ b/Source/Framework/Constants/AccountConst.cs @@ -291,7 +291,7 @@ namespace Framework.Constants CommandInstanceListbinds = 413, CommandInstanceUnbind = 414, CommandInstanceStats = 415, - CommandInstanceSavedata = 416, + // 416 previously used, do not reuse CommandLearn = 417, // 418 previously used, do not reuse CommandLearnAllMy = 419, diff --git a/Source/Framework/Constants/Language.cs b/Source/Framework/Constants/Language.cs index 5f776e0a2..a533a3478 100644 --- a/Source/Framework/Constants/Language.cs +++ b/Source/Framework/Constants/Language.cs @@ -1139,14 +1139,14 @@ namespace Framework.Constants // Instance Commands CommandListBindInfo = 5045, CommandListBindPlayerBinds = 5046, - CommandListBindGroupBinds = 5047, + CommandInstUnbindFailed = 5047, CommandInstUnbindUnbinding = 5048, CommandInstUnbindUnbound = 5049, CommandInstStatLoadedInst = 5050, CommandInstStatPlayersIn = 5051, CommandInstStatSaves = 5052, CommandInstStatPlayersbound = 5053, - CommandInstStatGroupsbound = 5054, + // = 5054, // old LANG_COMMAND_INST_STAT_GROUPSBOUND NotDungeon = 5055, // Map Is Not A Dungeon. NoInstanceData = 5056, // Map Has No Instance Data. CommandInstSetBossState = 5057, diff --git a/Source/Framework/Database/Databases/CharacterDatabase.cs b/Source/Framework/Database/Databases/CharacterDatabase.cs index 451b59f00..f6491d9d9 100644 --- a/Source/Framework/Database/Databases/CharacterDatabase.cs +++ b/Source/Framework/Database/Databases/CharacterDatabase.cs @@ -750,6 +750,7 @@ namespace Framework.Database PrepareStatement(CharStatements.DEL_CHARACTER_INSTANCE_LOCK_BY_GUID, "DELETE FROM character_instance_lock WHERE guid = ?"); PrepareStatement(CharStatements.INS_CHARACTER_INSTANCE_LOCK, "INSERT INTO character_instance_lock (guid, mapId, lockId, instanceId, difficulty, data, completedEncountersMask, entranceWorldSafeLocId, expiryTime, extended) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); PrepareStatement(CharStatements.UPD_CHARACTER_INSTANCE_LOCK_EXTENSION, "UPDATE character_instance_lock SET extended = ? WHERE guid = ? AND mapId = ? AND lockId = ?"); + PrepareStatement(CharStatements.UPD_CHARACTER_INSTANCE_LOCK_FORCE_EXPIRE, "UPDATE character_instance_lock SET expiryTime = ?, extended = 0 WHERE guid = ? AND mapId = ? AND lockId = ?"); PrepareStatement(CharStatements.DEL_INSTANCE, "DELETE FROM instance WHERE instanceId = ?"); PrepareStatement(CharStatements.INS_INSTANCE, "INSERT INTO instance (instanceId, data, completedEncountersMask, entranceWorldSafeLocId) VALUES (?, ?, ?, ?)"); } @@ -1359,6 +1360,7 @@ namespace Framework.Database DEL_CHARACTER_INSTANCE_LOCK_BY_GUID, INS_CHARACTER_INSTANCE_LOCK, UPD_CHARACTER_INSTANCE_LOCK_EXTENSION, + UPD_CHARACTER_INSTANCE_LOCK_FORCE_EXPIRE, DEL_INSTANCE, INS_INSTANCE, diff --git a/Source/Game/Chat/Commands/InstanceCommands.cs b/Source/Game/Chat/Commands/InstanceCommands.cs index 86328c31a..a00bee3ee 100644 --- a/Source/Game/Chat/Commands/InstanceCommands.cs +++ b/Source/Game/Chat/Commands/InstanceCommands.cs @@ -16,12 +16,11 @@ */ using Framework.Constants; -using Framework.IO; using Game.DataStorage; using Game.Entities; -using Game.Groups; using Game.Maps; using System; +using System.Collections.Generic; namespace Game.Chat { @@ -74,66 +73,26 @@ namespace Game.Chat [Command("listbinds", RBACPermissions.CommandInstanceListbinds)] static bool HandleInstanceListBindsCommand(CommandHandler handler) { - /*Player player = handler.GetSelectedPlayer(); - if (!player) + Player player = handler.GetSelectedPlayer(); + if (player == null) player = handler.GetSession().GetPlayer(); - string format = "map: {0} inst: {1} perm: {2} diff: {3} canReset: {4} TTR: {5}"; - - uint counter = 0; - foreach (var difficulty in CliDB.DifficultyStorage.Values) + DateTime now = GameTime.GetDateAndTime(); + var instanceLocks = Global.InstanceLockMgr.GetInstanceLocksForPlayer(player.GetGUID()); + foreach (InstanceLock instanceLock in instanceLocks) { - var binds = player.GetBoundInstances((Difficulty)difficulty.Id); - foreach (var (mapId, bind) in binds) - { - InstanceSave save = bind.save; - string timeleft = Time.GetTimeString(save.GetResetTime() - GameTime.GetGameTime()); - handler.SendSysMessage(format, mapId, save.GetInstanceId(), bind.perm ? "yes" : "no", save.GetDifficultyID(), save.CanReset() ? "yes" : "no", timeleft); - counter++; - } - } - handler.SendSysMessage("player binds: {0}", counter); - - counter = 0; - Group group = player.GetGroup(); - if (group) - { - foreach (var difficulty in CliDB.DifficultyStorage.Values) - { - var binds = group.GetBoundInstances((Difficulty)difficulty.Id); - foreach (var (mapId, bind) in binds) - { - InstanceSave save = bind.save; - string timeleft = Time.GetTimeString(save.GetResetTime() - GameTime.GetGameTime()); - handler.SendSysMessage(format, mapId, save.GetInstanceId(), bind.perm ? "yes" : "no", save.GetDifficultyID(), save.CanReset() ? "yes" : "no", timeleft); - counter++; - } - } - } - handler.SendSysMessage("group binds: {0}", counter);*/ - - return true; - } - - [Command("savedata", RBACPermissions.CommandInstanceSavedata)] - static bool HandleInstanceSaveDataCommand(CommandHandler handler) - { - /*Player player = handler.GetSession().GetPlayer(); - InstanceMap map = player.GetMap().ToInstanceMap(); - if (map == null) - { - handler.SendSysMessage("Map is not a dungeon."); - return false; + MapDb2Entries entries = new(instanceLock.GetMapId(), instanceLock.GetDifficultyId()); + string timeleft = !instanceLock.IsExpired() ? Time.secsToTimeString((ulong)(instanceLock.GetEffectiveExpiryTime() - now).TotalSeconds) : "-"; + handler.SendSysMessage(CypherStrings.CommandListBindInfo, + entries.Map.Id, entries.Map.MapName[Global.WorldMgr.GetDefaultDbcLocale()], + entries.MapDifficulty.DifficultyID, CliDB.DifficultyStorage.LookupByKey(entries.MapDifficulty.DifficultyID).Name, + instanceLock.GetInstanceId(), + handler.GetCypherString(instanceLock.IsExpired() ? CypherStrings.Yes : CypherStrings.No), + handler.GetCypherString(instanceLock.IsExtended() ? CypherStrings.Yes : CypherStrings.No), + timeleft); } - if (map.GetInstanceScript() == null) - { - handler.SendSysMessage("Map has no instance data."); - return false; - } - - map.GetInstanceScript().SaveToDB();*/ - + handler.SendSysMessage(CypherStrings.CommandListBindPlayerBinds, instanceLocks.Count); return true; } @@ -186,44 +145,66 @@ 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()); + + InstanceLocksStatistics statistics = Global.InstanceLockMgr.GetStatistics(); + + handler.SendSysMessage(CypherStrings.CommandInstStatSaves, statistics.InstanceCount); + handler.SendSysMessage(CypherStrings.CommandInstStatPlayersbound, statistics.PlayerCount); return true; } [Command("unbind", RBACPermissions.CommandInstanceUnbind)] - static bool HandleInstanceUnbindCommand(CommandHandler handler, string mapArg, byte? difficultyArg) + static bool HandleInstanceUnbindCommand(CommandHandler handler, [VariantArg(typeof(uint), typeof(string))] object mapArg, uint? difficultyArg) { - /*Player player = handler.GetSelectedPlayer(); - if (!player) + Player player = handler.GetSelectedPlayer(); + if (player == null) player = handler.GetSession().GetPlayer(); - ushort counter = 0; - uint mapId = 0; + uint? mapId = null; + Difficulty? difficulty = null; - if (!mapArg.IsEmpty() && mapArg.IsNumber()) - if (!uint.TryParse(mapArg, out mapId) || mapId == 0) - return false; + if (mapArg is uint) + mapId = (uint)mapArg; - foreach (var difficulty in CliDB.DifficultyStorage.Values) + if (difficultyArg.HasValue && CliDB.DifficultyStorage.ContainsKey(difficultyArg.Value)) + difficulty = (Difficulty)difficultyArg; + + List locksReset = new(); + List locksNotReset = new(); + + Global.InstanceLockMgr.ResetInstanceLocksForPlayer(player.GetGUID(), mapId, difficulty, locksReset, locksNotReset); + + DateTime now = GameTime.GetDateAndTime(); + foreach (InstanceLock instanceLock in locksReset) { - var binds = player.GetBoundInstances((Difficulty)difficulty.Id); - foreach (var pair in binds) - { - InstanceSave save = pair.Value.save; - if (pair.Key != player.GetMapId() && (mapId == 0 || mapId == pair.Key) && (!difficultyArg.HasValue || difficultyArg.Value == (byte)save.GetDifficultyID())) - { - string timeleft = Time.GetTimeString(save.GetResetTime() - GameTime.GetGameTime()); - handler.SendSysMessage("unbinding map: {0} inst: {1} perm: {2} diff: {3} canReset: {4} TTR: {5}", pair.Key, save.GetInstanceId(), - pair.Value.perm ? "yes" : "no", save.GetDifficultyID(), save.CanReset() ? "yes" : "no", timeleft); - player.UnbindInstance(pair.Key, (Difficulty)difficulty.Id); - counter++; - } - } + MapDb2Entries entries = new(instanceLock.GetMapId(), instanceLock.GetDifficultyId()); + string timeleft = !instanceLock.IsExpired() ? Time.secsToTimeString((ulong)(instanceLock.GetEffectiveExpiryTime() - now).TotalSeconds) : "-"; + handler.SendSysMessage(CypherStrings.CommandInstUnbindUnbinding, + entries.Map.Id, entries.Map.MapName[Global.WorldMgr.GetDefaultDbcLocale()], + entries.MapDifficulty.DifficultyID, CliDB.DifficultyStorage.LookupByKey(entries.MapDifficulty.DifficultyID).Name, + instanceLock.GetInstanceId(), + handler.GetCypherString(instanceLock.IsExpired() ? CypherStrings.Yes : CypherStrings.No), + handler.GetCypherString(instanceLock.IsExtended() ? CypherStrings.Yes : CypherStrings.No), + timeleft); } - handler.SendSysMessage("instances unbound: {0}", counter);*/ + + handler.SendSysMessage(CypherStrings.CommandInstUnbindUnbound, locksReset.Count); + + foreach (InstanceLock instanceLock in locksNotReset) + { + MapDb2Entries entries = new(instanceLock.GetMapId(), instanceLock.GetDifficultyId()); + string timeleft = !instanceLock.IsExpired() ? Time.secsToTimeString((ulong)(instanceLock.GetEffectiveExpiryTime() - now).TotalSeconds) : "-"; + handler.SendSysMessage(CypherStrings.CommandInstUnbindFailed, + entries.Map.Id, entries.Map.MapName[Global.WorldMgr.GetDefaultDbcLocale()], + entries.MapDifficulty.DifficultyID, CliDB.DifficultyStorage.LookupByKey(entries.MapDifficulty.DifficultyID).Name, + instanceLock.GetInstanceId(), + handler.GetCypherString(instanceLock.IsExpired() ? CypherStrings.Yes : CypherStrings.No), + handler.GetCypherString(instanceLock.IsExtended() ? CypherStrings.Yes : CypherStrings.No), + timeleft); + } + + player.SendRaidInfo(); return true; } diff --git a/Source/Game/Maps/Instances/InstanceLockManager.cs b/Source/Game/Maps/Instances/InstanceLockManager.cs index 36f7927cc..6485f1739 100644 --- a/Source/Game/Maps/Instances/InstanceLockManager.cs +++ b/Source/Game/Maps/Instances/InstanceLockManager.cs @@ -368,6 +368,71 @@ namespace Game.Maps return Tuple.Create(DateTime.MinValue, DateTime.MinValue); } + /// + /// Resets instances that match given filter - for use in GM commands + /// + /// Guid of player whose locks will be removed + /// (Optional) Map id of instance locks to reset + /// (Optional) Difficulty of instance locks to reset + /// All locks that were reset + /// Locks that could not be reset because they are used by existing instance map + public void ResetInstanceLocksForPlayer(ObjectGuid playerGuid, uint? mapId, Difficulty? difficulty, List locksReset, List locksFailedToReset) + { + var playerLocks = _instanceLocksByPlayer.LookupByKey(playerGuid); + if (playerLocks == null) + return; + + foreach (var playerLockPair in playerLocks) + { + if (playerLockPair.Value.IsInUse()) + { + locksFailedToReset.Add(playerLockPair.Value); + continue; + } + + if (mapId.HasValue && mapId.Value != playerLockPair.Value.GetMapId()) + continue; + + if (difficulty.HasValue && difficulty.Value != playerLockPair.Value.GetDifficultyId()) + continue; + + locksReset.Add(playerLockPair.Value); + } + + if (!locksReset.Empty()) + { + SQLTransaction trans = new(); + foreach (InstanceLock instanceLock in locksReset) + { + MapDb2Entries entries = new(instanceLock.GetMapId(), instanceLock.GetDifficultyId()); + DateTime newExpiryTime = GetNextResetTime(entries) - TimeSpan.FromSeconds(entries.MapDifficulty.GetRaidDuration()); + // set reset time to last reset time + instanceLock.SetExpiryTime(newExpiryTime); + instanceLock.SetExtended(false); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_INSTANCE_LOCK_FORCE_EXPIRE); + stmt.AddValue(0, (ulong)Time.DateTimeToUnixTime(newExpiryTime)); + stmt.AddValue(1, playerGuid.GetCounter()); + stmt.AddValue(2, entries.MapDifficulty.MapID); + stmt.AddValue(3, entries.MapDifficulty.LockID); + trans.Append(stmt); + } + DB.Characters.CommitTransaction(trans); + } + } + + /// + /// Retrieves instance lock statistics - for use in GM commands + /// + /// Statistics info + public InstanceLocksStatistics GetStatistics() + { + InstanceLocksStatistics statistics; + statistics.InstanceCount = _instanceLockDataById.Count; + statistics.PlayerCount = _instanceLocksByPlayer.Count; + return statistics; + } + public DateTime GetNextResetTime(MapDb2Entries entries) { DateTime dateTime = GameTime.GetDateAndTime(); @@ -418,7 +483,8 @@ namespace Game.Maps uint _instanceId; DateTime _expiryTime; bool _extended; - InstanceLockData _data; + InstanceLockData _data = new(); + bool _isInUse; public InstanceLock(uint mapId, Difficulty difficultyId, DateTime expiryTime, uint instanceId) { @@ -468,6 +534,10 @@ namespace Game.Maps public InstanceLockData GetData() { return _data; } public virtual InstanceLockData GetInstanceInitializationData() { return _data; } + + public bool IsInUse() { return _isInUse; } + + public void SetInUse(bool inUse) { _isInUse = inUse; } } class SharedInstanceLockData : InstanceLockData @@ -553,4 +623,10 @@ namespace Game.Maps EntranceWorldSafeLocId = entranceWorldSafeLocId; } } + + public struct InstanceLocksStatistics + { + public int InstanceCount; // Number of existing ID-based locks + public int PlayerCount; // Number of players that have any lock + } } diff --git a/Source/Game/Maps/Instances/InstanceScriptData.cs b/Source/Game/Maps/Instances/InstanceScriptData.cs index aa6bfeea3..347852866 100644 --- a/Source/Game/Maps/Instances/InstanceScriptData.cs +++ b/Source/Game/Maps/Instances/InstanceScriptData.cs @@ -255,7 +255,7 @@ namespace Game.Maps { _doc = JsonNode.Parse(data).AsObject(); } - catch (JsonException ex) + catch (JsonException) { FillData(false); } diff --git a/Source/Game/Maps/Map.cs b/Source/Game/Maps/Map.cs index 03eedd7a1..a3fab9243 100644 --- a/Source/Game/Maps/Map.cs +++ b/Source/Game/Maps/Map.cs @@ -4768,6 +4768,15 @@ namespace Game.Maps Global.WorldStateMgr.SetValue(WorldStates.TeamInInstanceAlliance, instanceTeam == TeamId.Alliance ? 1 : 0, false, this); Global.WorldStateMgr.SetValue(WorldStates.TeamInInstanceHorde, instanceTeam == TeamId.Horde ? 1 : 0, false, this); + + if (i_instanceLock != null) + i_instanceLock.SetInUse(true); + } + + ~InstanceMap() + { + if (i_instanceLock != null) + i_instanceLock.SetInUse(false); } public override void InitVisibilityDistance() diff --git a/Source/Game/Maps/MapManager.cs b/Source/Game/Maps/MapManager.cs index 6f4abf312..a51d8981b 100644 --- a/Source/Game/Maps/MapManager.cs +++ b/Source/Game/Maps/MapManager.cs @@ -495,7 +495,7 @@ namespace Game.Entities IntervalTimer i_timer = new(); object _mapsLock = new(); uint i_gridCleanUpDelay; - BitSet _freeInstanceIds; + BitSet _freeInstanceIds = new(1); uint _nextInstanceId; MapUpdater m_updater; uint _scheduledScripts;