// Copyright (c) CypherCore All rights reserved. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. using Framework.Collections; using Framework.Constants; using Framework.Database; using Game.DataStorage; using Game.Entities; using System; using System.Collections.Generic; namespace Game { public class DisableManager : Singleton { DisableManager() { } public class DisableData { public ushort flags; public List param0 = new(); public List param1 = new(); } Dictionary> m_DisableMap = new(); public void LoadDisables() { uint oldMSTime = Time.GetMSTime(); // reload case m_DisableMap.Clear(); SQLResult result = DB.World.Query("SELECT sourceType, entry, flags, params_0, params_1 FROM disables"); if (result.IsEmpty()) { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 disables. DB table `disables` is empty!"); return; } uint total_count = 0; do { DisableType type = (DisableType)result.Read(0); if (type >= DisableType.Max) { Log.outError(LogFilter.Sql, "Invalid type {0} specified in `disables` table, skipped.", type); continue; } uint entry = result.Read(1); DisableFlags flags = (DisableFlags)result.Read(2); string params_0 = result.Read(3); string params_1 = result.Read(4); DisableData data = new(); data.flags = (ushort)flags; switch (type) { case DisableType.Spell: if (!(Global.SpellMgr.HasSpellInfo(entry, Difficulty.None) || flags.HasFlag(DisableFlags.SpellDeprecatedSpell))) { Log.outError(LogFilter.Sql, "Spell entry {0} from `disables` doesn't exist in dbc, skipped.", entry); continue; } if (flags == 0 || flags > DisableFlags.MaxSpell) { Log.outError(LogFilter.Sql, "Disable flags for spell {0} are invalid, skipped.", entry); continue; } if (flags.HasFlag(DisableFlags.SpellMap)) { var array = new StringArray(params_0, ','); for (byte i = 0; i < array.Length;) { if (uint.TryParse(array[i++], out uint id)) data.param0.Add(id); } } if (flags.HasFlag(DisableFlags.SpellArea)) { var array = new StringArray(params_1, ','); for (byte i = 0; i < array.Length;) { if (uint.TryParse(array[i++], out uint id)) data.param1.Add(id); } } break; // checked later case DisableType.Quest: break; case DisableType.Map: case DisableType.LFGMap: { MapRecord mapEntry = CliDB.MapStorage.LookupByKey(entry); if (mapEntry == null) { Log.outError(LogFilter.Sql, "Map entry {0} from `disables` doesn't exist in dbc, skipped.", entry); continue; } bool isFlagInvalid = false; switch (mapEntry.InstanceType) { case MapTypes.Common: if (flags != 0) isFlagInvalid = true; break; case MapTypes.Instance: case MapTypes.Raid: if (flags.HasFlag(DisableFlags.DungeonStatusHeroic) && Global.DB2Mgr.GetMapDifficultyData(entry, Difficulty.Heroic) == null) flags &= ~DisableFlags.DungeonStatusHeroic; if (flags.HasFlag(DisableFlags.DungeonStatusHeroic10Man) && Global.DB2Mgr.GetMapDifficultyData(entry, Difficulty.Raid10HC) == null) flags &= ~DisableFlags.DungeonStatusHeroic10Man; if (flags.HasFlag(DisableFlags.DungeonStatusHeroic25Man) && Global.DB2Mgr.GetMapDifficultyData(entry, Difficulty.Raid25HC) == null) flags &= ~DisableFlags.DungeonStatusHeroic25Man; if (flags == 0) isFlagInvalid = true; break; case MapTypes.Battleground: case MapTypes.Arena: Log.outError(LogFilter.Sql, "Battlegroundmap {0} specified to be disabled in map case, skipped.", entry); continue; } if (isFlagInvalid) { Log.outError(LogFilter.Sql, "Disable flags for map {0} are invalid, skipped.", entry); continue; } break; } case DisableType.Battleground: if (!CliDB.BattlemasterListStorage.ContainsKey(entry)) { Log.outError(LogFilter.Sql, "Battlegroundentry {0} from `disables` doesn't exist in dbc, skipped.", entry); continue; } if (flags != 0) Log.outError(LogFilter.Sql, "Disable flags specified for Battleground{0}, useless data.", entry); break; case DisableType.OutdoorPVP: if (entry > (int)OutdoorPvPTypes.Max) { Log.outError(LogFilter.Sql, "OutdoorPvPTypes value {0} from `disables` is invalid, skipped.", entry); continue; } if (flags != 0) Log.outError(LogFilter.Sql, "Disable flags specified for outdoor PvP {0}, useless data.", entry); break; case DisableType.Criteria: if (Global.CriteriaMgr.GetCriteria(entry) == null) { Log.outError(LogFilter.Sql, "Criteria entry {0} from `disables` doesn't exist in dbc, skipped.", entry); continue; } if (flags != 0) Log.outError(LogFilter.Sql, "Disable flags specified for Criteria {0}, useless data.", entry); break; case DisableType.VMAP: { MapRecord mapEntry = CliDB.MapStorage.LookupByKey(entry); if (mapEntry == null) { Log.outError(LogFilter.Sql, "Map entry {0} from `disables` doesn't exist in dbc, skipped.", entry); continue; } switch (mapEntry.InstanceType) { case MapTypes.Common: if (flags.HasFlag(DisableFlags.VmapAreaFlag)) Log.outInfo(LogFilter.Server, "Areaflag disabled for world map {0}.", entry); if (flags.HasFlag(DisableFlags.VmapLiquidStatus)) Log.outInfo(LogFilter.Server, "Liquid status disabled for world map {0}.", entry); break; case MapTypes.Instance: case MapTypes.Raid: if (flags.HasFlag(DisableFlags.VmapHeight)) Log.outInfo(LogFilter.Server, "Height disabled for instance map {0}.", entry); if (flags.HasFlag(DisableFlags.VmapLOS)) Log.outInfo(LogFilter.Server, "LoS disabled for instance map {0}.", entry); break; case MapTypes.Battleground: if (flags.HasFlag(DisableFlags.VmapHeight)) Log.outInfo(LogFilter.Server, "Height disabled for Battlegroundmap {0}.", entry); if (flags.HasFlag(DisableFlags.VmapLOS)) Log.outInfo(LogFilter.Server, "LoS disabled for Battlegroundmap {0}.", entry); break; case MapTypes.Arena: if (flags.HasFlag(DisableFlags.VmapHeight)) Log.outInfo(LogFilter.Server, "Height disabled for arena map {0}.", entry); if (flags.HasFlag(DisableFlags.VmapLOS)) Log.outInfo(LogFilter.Server, "LoS disabled for arena map {0}.", entry); break; default: break; } break; } case DisableType.MMAP: { MapRecord mapEntry = CliDB.MapStorage.LookupByKey(entry); if (mapEntry == null) { Log.outError(LogFilter.Sql, "Map entry {0} from `disables` doesn't exist in dbc, skipped.", entry); continue; } switch (mapEntry.InstanceType) { case MapTypes.Common: Log.outInfo(LogFilter.Server, "Pathfinding disabled for world map {0}.", entry); break; case MapTypes.Instance: case MapTypes.Raid: Log.outInfo(LogFilter.Server, "Pathfinding disabled for instance map {0}.", entry); break; case MapTypes.Battleground: Log.outInfo(LogFilter.Server, "Pathfinding disabled for Battlegroundmap {0}.", entry); break; case MapTypes.Arena: Log.outInfo(LogFilter.Server, "Pathfinding disabled for arena map {0}.", entry); break; default: break; } break; } default: break; } if (!m_DisableMap.ContainsKey(type)) m_DisableMap[type] = new Dictionary(); m_DisableMap[type].Add(entry, data); ++total_count; } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, "Loaded {0} disables in {1} ms", total_count, Time.GetMSTimeDiffToNow(oldMSTime)); } public void CheckQuestDisables() { if (!m_DisableMap.ContainsKey(DisableType.Quest) || m_DisableMap[DisableType.Quest].Count == 0) { Log.outInfo(LogFilter.ServerLoading, "Checked 0 quest disables."); return; } uint oldMSTime = Time.GetMSTime(); // check only quests, rest already done at startup foreach (var pair in m_DisableMap[DisableType.Quest]) { uint entry = pair.Key; if (Global.ObjectMgr.GetQuestTemplate(entry) == null) { Log.outError(LogFilter.Sql, "Quest entry {0} from `disables` doesn't exist, skipped.", entry); m_DisableMap[DisableType.Quest].Remove(entry); continue; } if (pair.Value.flags != 0) Log.outError(LogFilter.Sql, "Disable flags specified for quest {0}, useless data.", entry); } Log.outInfo(LogFilter.ServerLoading, "Checked {0} quest disables in {1} ms", m_DisableMap[DisableType.Quest].Count, Time.GetMSTimeDiffToNow(oldMSTime)); } public bool IsDisabledFor(DisableType type, uint entry, WorldObject refe, DisableFlags flags = 0) { Cypher.Assert(type < DisableType.Max); if (!m_DisableMap.ContainsKey(type) || m_DisableMap[type].Empty()) return false; var data = m_DisableMap[type].LookupByKey(entry); if (data == null) // not disabled return false; switch (type) { case DisableType.Spell: { DisableFlags spellFlags = (DisableFlags)data.flags; if (refe != null) { if ((refe.IsPlayer() && spellFlags.HasFlag(DisableFlags.SpellPlayer)) || (refe.IsCreature() && (spellFlags.HasFlag(DisableFlags.SpellCreature) || (refe.ToUnit().IsPet() && spellFlags.HasFlag(DisableFlags.SpellPet)))) || (refe.IsGameObject() && spellFlags.HasFlag(DisableFlags.SpellGameobject))) { if (spellFlags.HasAnyFlag(DisableFlags.SpellArenas | DisableFlags.SpellBattleGrounds)) { var map = refe.GetMap(); if (map != null) { if (spellFlags.HasFlag(DisableFlags.SpellArenas) && map.IsBattleArena()) return true; // Current map is Arena and this spell is disabled here if (spellFlags.HasFlag(DisableFlags.SpellBattleGrounds) && map.IsBattleground()) return true; // Current map is a Battleground and this spell is disabled here } } if (spellFlags.HasFlag(DisableFlags.SpellMap)) { List mapIds = data.param0; if (mapIds.Contains(refe.GetMapId())) return true; // Spell is disabled on current map if (!spellFlags.HasFlag(DisableFlags.SpellArea)) return false; // Spell is disabled on another map, but not this one, return false // Spell is disabled in an area, but not explicitly our current mapId. Continue processing. } if (spellFlags.HasFlag(DisableFlags.SpellArea)) { var areaIds = data.param1; if (areaIds.Contains(refe.GetAreaId())) return true; // Spell is disabled in this area return false; // Spell is disabled in another area, but not this one, return false } else return true; // Spell disabled for all maps } return false; } else if (spellFlags.HasFlag(DisableFlags.SpellDeprecatedSpell)) // call not from spellcast return true; else if (flags.HasAnyFlag(DisableFlags.SpellLOS)) return spellFlags.HasFlag(DisableFlags.SpellLOS); break; } case DisableType.Map: case DisableType.LFGMap: Player player = refe.ToPlayer(); if (player != null) { MapRecord mapEntry = CliDB.MapStorage.LookupByKey(entry); if (mapEntry.IsDungeon()) { DisableFlags disabledModes = (DisableFlags)data.flags; Difficulty targetDifficulty = player.GetDifficultyID(mapEntry); Global.DB2Mgr.GetDownscaledMapDifficultyData(entry, ref targetDifficulty); switch (targetDifficulty) { case Difficulty.Normal: return disabledModes.HasFlag(DisableFlags.DungeonStatusNormal); case Difficulty.Heroic: return disabledModes.HasFlag(DisableFlags.DungeonStatusHeroic); case Difficulty.Raid10HC: return disabledModes.HasFlag(DisableFlags.DungeonStatusHeroic10Man); case Difficulty.Raid25HC: return disabledModes.HasFlag(DisableFlags.DungeonStatusHeroic25Man); default: return false; } } else if (mapEntry.InstanceType == MapTypes.Common) return true; } return false; case DisableType.Quest: return true; case DisableType.Battleground: case DisableType.OutdoorPVP: case DisableType.Criteria: case DisableType.MMAP: return true; case DisableType.VMAP: return flags.HasAnyFlag((DisableFlags)data.flags); } return false; } public bool IsVMAPDisabledFor(uint entry, DisableFlags flags) { return IsDisabledFor(DisableType.VMAP, entry, null, flags); } public bool IsPathfindingEnabled(uint mapId) { return WorldConfig.GetBoolValue(WorldCfg.EnableMmaps) && !Global.DisableMgr.IsDisabledFor(DisableType.MMAP, mapId, null); } } public enum DisableType { Spell = 0, Quest = 1, Map = 2, Battleground = 3, Criteria = 4, OutdoorPVP = 5, VMAP = 6, MMAP = 7, LFGMap = 8, Max = 9 } [Flags] public enum DisableFlags { SpellPlayer = 0x01, SpellCreature = 0x02, SpellPet = 0x04, SpellDeprecatedSpell = 0x08, SpellMap = 0x10, SpellArea = 0x20, SpellLOS = 0x40, SpellGameobject = 0x80, SpellArenas = 0x100, SpellBattleGrounds = 0x200, MaxSpell = SpellPlayer | SpellCreature | SpellPet | SpellDeprecatedSpell | SpellMap | SpellArea | SpellLOS | SpellGameobject | SpellArenas | SpellBattleGrounds, VmapAreaFlag = 0x01, VmapHeight = 0x02, VmapLOS = 0x04, VmapLiquidStatus = 0x08, MMapPathFinding = 0x00, DungeonStatusNormal = 0x01, DungeonStatusHeroic = 0x02, DungeonStatusNormal10Man = 0x01, DungeonStatusNormal25Man = 0x02, DungeonStatusHeroic10Man = 0x04, DungeonStatusHeroic25Man = 0x08 } }