From 15ae7a7c66208c3d763260afd988c5fbbbc61a23 Mon Sep 17 00:00:00 2001 From: hondacrx Date: Sun, 23 Aug 2020 21:52:32 -0400 Subject: [PATCH] Dynamic Creature/Go spawning Port From (https://github.com/TrinityCore/TrinityCore/commit/03b125e6d1947258316c931499746696a95aded2) --- Source/Framework/Constants/Language.cs | 23 +- Source/Framework/Constants/MapConst.cs | 31 + Source/Framework/Constants/SharedConst.cs | 11 + Source/Framework/Constants/SmartAIConst.cs | 16 +- .../Database/Databases/WorldDatabase.cs | 2 + Source/Game/AI/CoreAI/CreatureAI.cs | 9 + Source/Game/AI/ScriptedAI/ScriptedAI.cs | 12 +- Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs | 46 +- Source/Game/AI/SmartScripts/SmartAIManager.cs | 30 +- Source/Game/AI/SmartScripts/SmartScript.cs | 85 + .../Game/Chat/Commands/GameObjectCommands.cs | 38 +- Source/Game/Chat/Commands/GoCommands.cs | 21 +- Source/Game/Chat/Commands/ListCommands.cs | 143 +- Source/Game/Chat/Commands/MiscCommands.cs | 2508 +++++++++-------- Source/Game/Chat/Commands/NPCCommands.cs | 201 +- Source/Game/Chat/Commands/WPCommands.cs | 8 +- Source/Game/Conditions/ConditionManager.cs | 6 +- .../Game/Entities/Creature/Creature.Fields.cs | 1 + Source/Game/Entities/Creature/Creature.cs | 405 +-- Source/Game/Entities/Creature/CreatureData.cs | 27 +- Source/Game/Entities/GameObject/GameObject.cs | 274 +- .../Entities/GameObject/GameObjectData.cs | 21 +- Source/Game/Entities/Object/ObjectGuid.cs | 12 + Source/Game/Entities/Object/Position.cs | 6 + Source/Game/Entities/Object/WorldObject.cs | 4 +- Source/Game/Entities/Player/Player.Map.cs | 2 + Source/Game/Entities/Transport.cs | 18 +- Source/Game/Events/GameEventManager.cs | 28 +- Source/Game/Garrisons/Garrisons.cs | 2 +- Source/Game/Globals/ObjectManager.cs | 584 +++- Source/Game/Maps/GridNotifiers.cs | 25 + Source/Game/Maps/Map.cs | 615 +++- Source/Game/Maps/MapManager.cs | 10 + Source/Game/Maps/ObjectGridLoader.cs | 42 +- Source/Game/Maps/SpawnData.cs | 55 + Source/Game/Maps/ZoneScript.cs | 2 +- Source/Game/OutdoorPVP/OutdoorPvP.cs | 10 +- Source/Game/Pools/PoolManager.cs | 47 +- Source/Game/Scripting/ScriptManager.cs | 31 +- Source/Game/Server/WorldConfig.cs | 43 + Source/Game/Server/WorldManager.cs | 90 +- .../IcecrownCitadel/IcecrownCitadel.cs | 4 +- .../InstanceIcecrownCitadel.cs | 10 +- Source/WorldServer/WorldServer.conf.dist | 116 + ...020_08_22_00_world_2017_07_31_00_world.sql | 214 ++ 45 files changed, 3925 insertions(+), 1963 deletions(-) create mode 100644 Source/Game/Maps/SpawnData.cs create mode 100644 sql/updates/world/master/2020_08_22_00_world_2017_07_31_00_world.sql diff --git a/Source/Framework/Constants/Language.cs b/Source/Framework/Constants/Language.cs index f9228d46e..96168ad36 100644 --- a/Source/Framework/Constants/Language.cs +++ b/Source/Framework/Constants/Language.cs @@ -1079,11 +1079,26 @@ namespace Framework.Constants DebugSceneObjectList = 5068, DebugSceneObjectDetail = 5069, - NpcinfoUnitFieldFlags2 = 5070, - NpcinfoUnitFieldFlags3 = 5071, - NpcinfoNpcFlags = 5072, + // Strings Added For DynamicSpawning + SpawninfoGroupId = 5070, + SpawninfoCompatibilityMode = 5071, + SpawninfoGuidinfo = 5072, + SpawninfoSpawnidLocation = 5073, + SpawninfoDistancefromplayer = 5074, + SpawngroupBadgroup = 5075, + SpawngroupSpawncount = 5076, + ListRespawnsRange = 5077, + ListRespawnsZone = 5078, + ListRespawnsListheader = 5079, + ListRespawnsOverdue = 5080, + ListRespawnsCreatures = 5081, + ListRespawnsGameobjects = 5082, - // Room For More Trinity Strings 5073-9999 + NpcinfoUnitFieldFlags2 = 5084, + NpcinfoUnitFieldFlags3 = 5085, + NpcinfoNpcFlags = 5086, + + // Room For More Trinity Strings 5087-6603 // Level Requirement Notifications SayReq = 6604, diff --git a/Source/Framework/Constants/MapConst.cs b/Source/Framework/Constants/MapConst.cs index 81f217e2b..3e571bc12 100644 --- a/Source/Framework/Constants/MapConst.cs +++ b/Source/Framework/Constants/MapConst.cs @@ -20,6 +20,8 @@ namespace Framework.Constants { public class MapConst { + public const uint InvalidZone = 0xFFFFFFFF; + //Grids public const int MaxGrids = 64; public const float SizeofGrids = 533.33333f; @@ -200,4 +202,33 @@ namespace Framework.Constants All = Vmap | Gobject } + + public enum SpawnObjectType + { + Creature = 0, + GameObject = 1, + + Max + } + + public enum SpawnObjectTypeMask + { + Creature = (1 << SpawnObjectType.Creature), + GameObject = (1 << SpawnObjectType.GameObject), + + All = (1 << SpawnObjectType.Max) - 1 + } + + [Flags] + public enum SpawnGroupFlags + { + None = 0x00, + System = 0x01, + CompatibilityMode = 0x02, + ManualSpawn = 0x04, + DynamicSpawnRate = 0x08, + EscortQuestNpc = 0x10, + + All = (System | CompatibilityMode | ManualSpawn | DynamicSpawnRate | EscortQuestNpc) + } } diff --git a/Source/Framework/Constants/SharedConst.cs b/Source/Framework/Constants/SharedConst.cs index b7aa804be..605b3205a 100644 --- a/Source/Framework/Constants/SharedConst.cs +++ b/Source/Framework/Constants/SharedConst.cs @@ -1316,6 +1316,17 @@ namespace Framework.Constants RealmZone, ResetDuelCooldowns, ResetDuelHealthMana, + RespawnDynamicEscortNpc, + RespawnDynamicMinimumCreature, + RespawnDynamicMinimumGameObject, + RespawnDynamicMode, + RespawnDynamicRateCreature, + RespawnDynamicRateGameobject, + RespawnGuidAlertLevel, + RespawnGuidWarnLevel, + RespawnGuidWarningFrequency, + RespawnMinCheckIntervalMs, + RespawnRestartQuietTime, RestrictedLfgChannel, SaveRespawnTimeImmediately, SessionAddDelay, diff --git a/Source/Framework/Constants/SmartAIConst.cs b/Source/Framework/Constants/SmartAIConst.cs index c240bb7fa..9c9c05c95 100644 --- a/Source/Framework/Constants/SmartAIConst.cs +++ b/Source/Framework/Constants/SmartAIConst.cs @@ -13,7 +13,9 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - */ + */ + +using System; namespace Framework.Constants { @@ -114,6 +116,15 @@ namespace Framework.Constants End = 3 } + [Flags] + public enum SmartAiSpawnFlags + { + None = 0x00, + IgnoreRespawn = 0x01, + ForceSpawn = 0x02, + NosaveRespawn = 0x04, + } + public enum SmartAITemplate { Basic = 0, //nothing is preset @@ -125,6 +136,7 @@ namespace Framework.Constants End = 6 } + [Flags] public enum SmartCastFlags { InterruptPrevious = 0x01, //Interrupt any spell casting @@ -361,6 +373,8 @@ namespace Framework.Constants PlayAnimkit = 128, ScenePlay = 129, // sceneId SceneCancel = 130, // sceneId + SpawnSpawngroup = 131, // Group ID, min secs, max secs, spawnflags + DespawnSpawngroup = 132, // Group ID, min secs, max secs, spawnflags // 131 - 134 : 3.3.5 reserved PlayCinematic = 135, // reserved for future uses SetMovementSpeed = 136, // movementType, speedInteger, speedFraction diff --git a/Source/Framework/Database/Databases/WorldDatabase.cs b/Source/Framework/Database/Databases/WorldDatabase.cs index 668d93f9f..c41fa12ae 100644 --- a/Source/Framework/Database/Databases/WorldDatabase.cs +++ b/Source/Framework/Database/Databases/WorldDatabase.cs @@ -89,6 +89,7 @@ namespace Framework.Database PrepareStatement(WorldStatements.DEL_DISABLES, "DELETE FROM disables WHERE entry = ? AND sourceType = ?"); PrepareStatement(WorldStatements.UPD_CREATURE_ZONE_AREA_DATA, "UPDATE creature SET zoneId = ?, areaId = ? WHERE guid = ?"); PrepareStatement(WorldStatements.UPD_GAMEOBJECT_ZONE_AREA_DATA, "UPDATE gameobject SET zoneId = ?, areaId = ? WHERE guid = ?"); + PrepareStatement(WorldStatements.DEL_SPAWNGROUP_MEMBER, "DELETE FROM spawn_group WHERE spawnType = ? AND spawnId = ?"); PrepareStatement(WorldStatements.SEL_GUILD_REWARDS_REQ_ACHIEVEMENTS, "SELECT AchievementRequired FROM guild_rewards_req_achievements WHERE ItemID = ?"); } } @@ -164,6 +165,7 @@ namespace Framework.Database DEL_DISABLES, UPD_CREATURE_ZONE_AREA_DATA, UPD_GAMEOBJECT_ZONE_AREA_DATA, + DEL_SPAWNGROUP_MEMBER, SEL_GUILD_REWARDS_REQ_ACHIEVEMENTS, MAX_WORLDDATABASE_STATEMENTS diff --git a/Source/Game/AI/CoreAI/CreatureAI.cs b/Source/Game/AI/CoreAI/CreatureAI.cs index a9e6dd4f4..255fe7e5e 100644 --- a/Source/Game/AI/CoreAI/CreatureAI.cs +++ b/Source/Game/AI/CoreAI/CreatureAI.cs @@ -474,6 +474,15 @@ namespace Game.AI // Object destruction is handled by Unit::RemoveCharmedBy public virtual PlayerAI GetAIForCharmedPlayer(Player who) { return null; } + + /// + /// Should return true if the NPC is target of an escort quest + /// If onlyIfActive is set, should return true only if the escort quest is currently active + /// + /// + /// + public virtual bool IsEscortNPC(bool onlyIfActive) { return false; } + List GetBoundary() { return _boundary; } bool MoveInLineOfSight_locked; diff --git a/Source/Game/AI/ScriptedAI/ScriptedAI.cs b/Source/Game/AI/ScriptedAI/ScriptedAI.cs index 557e5f681..a3a8bc46e 100644 --- a/Source/Game/AI/ScriptedAI/ScriptedAI.cs +++ b/Source/Game/AI/ScriptedAI/ScriptedAI.cs @@ -495,7 +495,7 @@ namespace Game.AI _events.Reset(); summons.DespawnAll(); _scheduler.CancelAll(); - if (instance != null) + if (instance != null && instance.GetBossState(_bossId) != EncounterState.Done) instance.SetBossState(_bossId, EncounterState.NotStarted); } @@ -576,12 +576,14 @@ namespace Game.AI DoMeleeAttackIfReady(); } - public void _DespawnAtEvade(uint delayToRespawn = 30, Creature who = null) + public void _DespawnAtEvade(uint delayToRespawn = 30, Creature who = null) { _DespawnAtEvade(TimeSpan.FromSeconds(delayToRespawn), who); } + + public void _DespawnAtEvade(TimeSpan delayToRespawn, Creature who = null) { - if (delayToRespawn < 2) + if (delayToRespawn < TimeSpan.FromSeconds(2)) { Log.outError(LogFilter.Scripts, "_DespawnAtEvade called with delay of {0} seconds, defaulting to 2.", delayToRespawn); - delayToRespawn = 2; + delayToRespawn = TimeSpan.FromSeconds(2); } if (!who) @@ -595,7 +597,7 @@ namespace Game.AI return; } - who.DespawnOrUnsummon(0, TimeSpan.FromSeconds(delayToRespawn)); + who.DespawnOrUnsummon(0, delayToRespawn); if (instance != null && who == me) instance.SetBossState(_bossId, EncounterState.Fail); diff --git a/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs b/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs index 2efd91687..7da034a09 100644 --- a/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs +++ b/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs @@ -18,6 +18,7 @@ using Framework.Constants; using Game.Entities; using Game.Groups; +using Game.Maps; using System; using System.Collections.Generic; using System.Linq; @@ -234,13 +235,17 @@ namespace Game.AI return; } - if (m_bCanInstantRespawn) + if (m_bCanInstantRespawn && !WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc)) { me.SetDeathState(DeathState.JustDied); me.Respawn(); } else + { + if (WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc)) + me.GetMap().RemoveRespawnTime(SpawnObjectType.Creature, me.GetSpawnId(), true); me.DespawnOrUnsummon(); + } return; } @@ -274,11 +279,18 @@ namespace Game.AI { Log.outDebug(LogFilter.Scripts, "EscortAI failed because player/group was to far away or not found"); - if (m_bCanInstantRespawn) + bool isEscort = false; + CreatureData cdata = me.GetCreatureData(); + if (cdata != null) + isEscort = (WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc) && cdata.spawnGroupData.flags.HasAnyFlag(SpawnGroupFlags.EscortQuestNpc)); + + if (m_bCanInstantRespawn && !isEscort) { me.SetDeathState(DeathState.JustDied); me.Respawn(); } + else if (m_bCanInstantRespawn && isEscort) + me.GetMap().RemoveRespawnTime(SpawnObjectType.Creature, me.GetSpawnId(), true); else me.DespawnOrUnsummon(); @@ -391,6 +403,25 @@ namespace Game.AI /// todo get rid of this many variables passed in function. public void Start(bool isActiveAttacker = true, bool run = false, ObjectGuid playerGUID = default, Quest quest = null, bool instantRespawn = false, bool canLoopPath = false, bool resetWaypoints = true) { + // Queue respawn from the point it starts + Map map = me.GetMap(); + if (map != null) + { + CreatureData cdata = me.GetCreatureData(); + if (cdata != null) + { + SpawnGroupTemplateData groupdata = cdata.spawnGroupData; + if (groupdata != null) + { + if (WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc) && groupdata.flags.HasAnyFlag(SpawnGroupFlags.EscortQuestNpc) && map.GetCreatureRespawnTime(me.GetSpawnId()) == 0) + { + me.SetRespawnTime(me.GetRespawnDelay()); + me.SaveRespawnTime(); + } + } + } + } + if (me.GetVictim()) { Log.outError(LogFilter.Server, "TSCR ERROR: EscortAI (script: {0}, creature entry: {1}) attempts to Start while in combat", me.GetScriptName(), me.GetEntry()); @@ -536,6 +567,17 @@ namespace Game.AI return false; } + public override bool IsEscortNPC(bool onlyIfActive) + { + if (!onlyIfActive) + return true; + + if (!GetEventStarterGUID().IsEmpty()) + return true; + + return false; + } + public virtual void WaypointReached(uint pointId) { } public virtual void WaypointStart(uint pointId) { } diff --git a/Source/Game/AI/SmartScripts/SmartAIManager.cs b/Source/Game/AI/SmartScripts/SmartAIManager.cs index 1c4f4235e..99aba6ac5 100644 --- a/Source/Game/AI/SmartScripts/SmartAIManager.cs +++ b/Source/Game/AI/SmartScripts/SmartAIManager.cs @@ -129,39 +129,39 @@ namespace Game.AI continue; } - CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(creature.id); + CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(creature.Id); if (creatureInfo == null) { - Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: Creature entry ({creature.id}) guid ({-temp.entryOrGuid}) does not exist, skipped loading."); + Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: Creature entry ({creature.Id}) guid ({-temp.entryOrGuid}) does not exist, skipped loading."); continue; } if (creatureInfo.AIName != "SmartAI") { - Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: Creature entry ({creature.id}) guid ({-temp.entryOrGuid}) is not using SmartAI, skipped loading."); + Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: Creature entry ({creature.Id}) guid ({-temp.entryOrGuid}) is not using SmartAI, skipped loading."); continue; } break; } case SmartScriptType.GameObject: { - GameObjectData gameObject = Global.ObjectMgr.GetGOData((ulong)-temp.entryOrGuid); + GameObjectData gameObject = Global.ObjectMgr.GetGameObjectData((ulong)-temp.entryOrGuid); if (gameObject == null) { Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: GameObject guid ({-temp.entryOrGuid}) does not exist, skipped loading."); continue; } - GameObjectTemplate gameObjectInfo = Global.ObjectMgr.GetGameObjectTemplate(gameObject.id); + GameObjectTemplate gameObjectInfo = Global.ObjectMgr.GetGameObjectTemplate(gameObject.Id); if (gameObjectInfo == null) { - Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: GameObject entry ({gameObject.id}) guid ({-temp.entryOrGuid}) does not exist, skipped loading."); + Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: GameObject entry ({gameObject.Id}) guid ({-temp.entryOrGuid}) does not exist, skipped loading."); continue; } if (gameObjectInfo.AIName != "SmartGameObjectAI") { - Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: GameObject entry ({gameObject.id}) guid ({-temp.entryOrGuid}) is not using SmartGameObjectAI, skipped loading."); + Log.outError(LogFilter.Sql, $"SmartAIMgr.LoadSmartAIFromDB: GameObject entry ({gameObject.Id}) guid ({-temp.entryOrGuid}) is not using SmartGameObjectAI, skipped loading."); continue; } break; @@ -758,7 +758,7 @@ namespace Game.AI return false; } - if (e.Event.distance.guid != 0 && Global.ObjectMgr.GetGOData(e.Event.distance.guid) == null) + if (e.Event.distance.guid != 0 && Global.ObjectMgr.GetGameObjectData(e.Event.distance.guid) == null) { Log.outError(LogFilter.Sql, "SmartAIMgr: Event SMART_EVENT_DISTANCE_GAMEOBJECT using invalid gameobject guid {0}, skipped.", e.Event.distance.guid); return false; @@ -1376,6 +1376,8 @@ namespace Game.AI case SmartActions.TriggerRandomTimedEvent: case SmartActions.SetCounter: case SmartActions.RemoveAllGameobjects: + case SmartActions.SpawnSpawngroup: + case SmartActions.DespawnSpawngroup: break; default: Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Not handled action_type({0}), event_type({1}), Entry {2} SourceType {3} Event {4}, skipped.", e.GetActionType(), e.GetEventType(), e.entryOrGuid, e.GetScriptType(), e.event_id); @@ -1423,7 +1425,7 @@ namespace Game.AI return false; } else - entry = data.id; + entry = data.Id; } else entry = (uint)e.entryOrGuid; @@ -2368,6 +2370,9 @@ namespace Game.AI [FieldOffset(4)] public DisableEvade disableEvade; + [FieldOffset(4)] + public GroupSpawn groupSpawn; + [FieldOffset(4)] public AuraType auraType; @@ -2854,6 +2859,13 @@ namespace Game.AI { public uint disable; } + public struct GroupSpawn + { + public uint groupId; + public uint minDelay; + public uint maxDelay; + public uint spawnflags; + } public struct AuraType { public uint type; diff --git a/Source/Game/AI/SmartScripts/SmartScript.cs b/Source/Game/AI/SmartScripts/SmartScript.cs index dffc900ac..1e58628db 100644 --- a/Source/Game/AI/SmartScripts/SmartScript.cs +++ b/Source/Game/AI/SmartScripts/SmartScript.cs @@ -2051,6 +2051,91 @@ namespace Game.AI target.ToCreature().SetCorpseDelay(e.Action.corpseDelay.timer); break; } + case SmartActions.SpawnSpawngroup: + { + if (e.Action.groupSpawn.minDelay == 0 && e.Action.groupSpawn.maxDelay == 0) + { + bool ignoreRespawn = ((e.Action.groupSpawn.spawnflags & (uint)SmartAiSpawnFlags.IgnoreRespawn) != 0); + bool force = ((e.Action.groupSpawn.spawnflags & (uint)SmartAiSpawnFlags.ForceSpawn) != 0); + + // Instant spawn + Global.ObjectMgr.SpawnGroupSpawn(e.Action.groupSpawn.groupId, GetBaseObject().GetMap(), ignoreRespawn, force); + } + else + { + // Delayed spawn (use values from parameter to schedule event to call us back + SmartEvent ne = new SmartEvent(); + ne.type = SmartEvents.Update; + ne.event_chance = 100; + + ne.minMaxRepeat.min = e.Action.groupSpawn.minDelay; + ne.minMaxRepeat.max = e.Action.groupSpawn.maxDelay; + ne.minMaxRepeat.repeatMin = 0; + ne.minMaxRepeat.repeatMax = 0; + + ne.event_flags = 0; + ne.event_flags |= SmartEventFlags.NotRepeatable; + + SmartAction ac = new SmartAction(); + ac.type = SmartActions.SpawnSpawngroup; + ac.groupSpawn.groupId = e.Action.groupSpawn.groupId; + ac.groupSpawn.minDelay = 0; + ac.groupSpawn.maxDelay = 0; + ac.groupSpawn.spawnflags = e.Action.groupSpawn.spawnflags; + ac.timeEvent.id = e.Action.timeEvent.id; + + SmartScriptHolder ev = new SmartScriptHolder(); + ev.Event = ne; + ev.event_id = e.event_id; + ev.Target = e.Target; + ev.Action = ac; + InitTimer(ev); + mStoredEvents.Add(ev); + } + break; + } + case SmartActions.DespawnSpawngroup: + { + if (e.Action.groupSpawn.minDelay == 0 && e.Action.groupSpawn.maxDelay == 0) + { + bool deleteRespawnTimes = ((e.Action.groupSpawn.spawnflags & (uint)SmartAiSpawnFlags.NosaveRespawn) != 0); + + // Instant spawn + Global.ObjectMgr.SpawnGroupDespawn(e.Action.groupSpawn.groupId, GetBaseObject().GetMap(), deleteRespawnTimes); + } + else + { + // Delayed spawn (use values from parameter to schedule event to call us back + SmartEvent ne = new SmartEvent(); + ne.type = SmartEvents.Update; + ne.event_chance = 100; + + ne.minMaxRepeat.min = e.Action.groupSpawn.minDelay; + ne.minMaxRepeat.max = e.Action.groupSpawn.maxDelay; + ne.minMaxRepeat.repeatMin = 0; + ne.minMaxRepeat.repeatMax = 0; + + ne.event_flags = 0; + ne.event_flags |= SmartEventFlags.NotRepeatable; + + SmartAction ac = new SmartAction(); + ac.type = SmartActions.DespawnSpawngroup; + ac.groupSpawn.groupId = e.Action.groupSpawn.groupId; + ac.groupSpawn.minDelay = 0; + ac.groupSpawn.maxDelay = 0; + ac.groupSpawn.spawnflags = e.Action.groupSpawn.spawnflags; + ac.timeEvent.id = e.Action.timeEvent.id; + + SmartScriptHolder ev = new SmartScriptHolder(); + ev.Event = ne; + ev.event_id = e.event_id; + ev.Target = e.Target; + ev.Action = ac; + InitTimer(ev); + mStoredEvents.Add(ev); + } + break; + } case SmartActions.DisableEvade: { if (!IsSmart()) diff --git a/Source/Game/Chat/Commands/GameObjectCommands.cs b/Source/Game/Chat/Commands/GameObjectCommands.cs index 46cb04771..84a414bfc 100644 --- a/Source/Game/Chat/Commands/GameObjectCommands.cs +++ b/Source/Game/Chat/Commands/GameObjectCommands.cs @@ -201,7 +201,7 @@ namespace Game.Chat if (gameObjectInfo == null) continue; - handler.SendSysMessage(CypherStrings.GoListChat, guid, entry, guid, gameObjectInfo.name, x, y, z, mapId); + handler.SendSysMessage(CypherStrings.GoListChat, guid, entry, guid, gameObjectInfo.name, x, y, z, mapId, "", ""); ++count; } while (result.NextRow()); @@ -412,10 +412,10 @@ namespace Game.Chat if (!ulong.TryParse(cValue, out ulong guidLow)) return false; - GameObjectData data = Global.ObjectMgr.GetGOData(guidLow); + GameObjectData data = Global.ObjectMgr.GetGameObjectData(guidLow); if (data == null) return false; - entry = data.id; + entry = data.Id; } else { @@ -427,15 +427,45 @@ namespace Game.Chat if (gameObjectInfo == null) return false; + GameObject thisGO = null; + if (handler.GetSession().GetPlayer()) + thisGO = handler.GetSession().GetPlayer().FindNearestGameObject(entry, 30); + else if (handler.GetSelectedObject() != null && handler.GetSelectedObject().IsTypeId(TypeId.GameObject)) + thisGO = handler.GetSelectedObject().ToGameObject(); + GameObjectTypes type = gameObjectInfo.type; uint displayId = gameObjectInfo.displayId; string name = gameObjectInfo.name; uint lootId = gameObjectInfo.GetLootId(); + // If we have a real object, send some info about it + if (thisGO != null) + { + handler.SendSysMessage(CypherStrings.SpawninfoGuidinfo, thisGO.GetGUID().ToString()); + handler.SendSysMessage(CypherStrings.SpawninfoSpawnidLocation, thisGO.GetSpawnId(), thisGO.GetPositionX(), thisGO.GetPositionY(), thisGO.GetPositionZ()); + Player player = handler.GetSession().GetPlayer(); + if (player != null) + { + Position playerPos = player.GetPosition(); + float dist = thisGO.GetExactDist(playerPos); + handler.SendSysMessage(CypherStrings.SpawninfoDistancefromplayer, dist); + } + } handler.SendSysMessage(CypherStrings.GoinfoEntry, entry); handler.SendSysMessage(CypherStrings.GoinfoType, type); handler.SendSysMessage(CypherStrings.GoinfoLootid, lootId); handler.SendSysMessage(CypherStrings.GoinfoDisplayid, displayId); + WorldObject obj = handler.GetSelectedObject(); + if (obj != null) + { + if (obj.IsGameObject() && obj.ToGameObject().GetGameObjectData() != null && obj.ToGameObject().GetGameObjectData().spawnGroupData.groupId != 0) + { + SpawnGroupTemplateData groupData = obj.ToGameObject().GetGameObjectData().spawnGroupData; + handler.SendSysMessage(CypherStrings.SpawninfoGroupId, groupData.name, groupData.groupId, groupData.flags, groupData.isActive); + } + if (obj.IsGameObject()) + handler.SendSysMessage(CypherStrings.SpawninfoCompatibilityMode, obj.ToGameObject().GetRespawnCompatibilityMode()); + } handler.SendSysMessage(CypherStrings.GoinfoName, name); handler.SendSysMessage(CypherStrings.GoinfoSize, gameObjectInfo.size); @@ -508,7 +538,7 @@ namespace Game.Chat return false; // TODO: is it really necessary to add both the real and DB table guid here ? - Global.ObjectMgr.AddGameObjectToGrid(spawnId, Global.ObjectMgr.GetGOData(spawnId)); + Global.ObjectMgr.AddGameObjectToGrid(spawnId, Global.ObjectMgr.GetGameObjectData(spawnId)); handler.SendSysMessage(CypherStrings.GameobjectAdd, objectId, objectInfo.name, spawnId, player.GetPositionX(), player.GetPositionY(), player.GetPositionZ()); return true; } diff --git a/Source/Game/Chat/Commands/GoCommands.cs b/Source/Game/Chat/Commands/GoCommands.cs index bdf379044..f6d0bda70 100644 --- a/Source/Game/Chat/Commands/GoCommands.cs +++ b/Source/Game/Chat/Commands/GoCommands.cs @@ -202,28 +202,17 @@ namespace Game.Chat.Commands if (!ulong.TryParse(id, out ulong guidLow) || guidLow == 0) return false; - float x, y, z, o; - uint mapId; - // by DB guid - GameObjectData goData = Global.ObjectMgr.GetGOData(guidLow); - if (goData != null) - { - x = goData.posX; - y = goData.posY; - z = goData.posZ; - o = goData.orientation; - mapId = goData.mapid; - } - else + GameObjectData goData = Global.ObjectMgr.GetGameObjectData(guidLow); + if (goData == null) { handler.SendSysMessage(CypherStrings.CommandGoobjnotfound); return false; } - if (!GridDefines.IsValidMapCoord(mapId, x, y, z, o) || Global.ObjectMgr.IsTransportMap(mapId)) + if (!GridDefines.IsValidMapCoord(goData.spawnPoint) || Global.ObjectMgr.IsTransportMap(goData.spawnPoint.GetMapId())) { - handler.SendSysMessage(CypherStrings.InvalidTargetCoord, x, y, mapId); + handler.SendSysMessage(CypherStrings.InvalidTargetCoord, goData.spawnPoint.GetPositionX(), goData.spawnPoint.GetPositionY(), goData.spawnPoint.GetMapId()); return false; } @@ -237,7 +226,7 @@ namespace Game.Chat.Commands else player.SaveRecallPosition(); - player.TeleportTo(mapId, x, y, z, o); + player.TeleportTo(goData.spawnPoint); return true; } diff --git a/Source/Game/Chat/Commands/ListCommands.cs b/Source/Game/Chat/Commands/ListCommands.cs index 62aeb087f..d5f4ef161 100644 --- a/Source/Game/Chat/Commands/ListCommands.cs +++ b/Source/Game/Chat/Commands/ListCommands.cs @@ -18,7 +18,9 @@ using Framework.Constants; using Framework.Database; using Framework.IO; +using Game.DataStorage; using Game.Entities; +using Game.Maps; using Game.Spells; using System.Collections.Generic; @@ -128,11 +130,39 @@ namespace Game.Chat.Commands float y = result.Read(2); float z = result.Read(3); ushort mapId = result.Read(4); + bool liveFound = false; + // Get map (only support base map from console) + Map thisMap; if (handler.GetSession() != null) - handler.SendSysMessage(CypherStrings.CreatureListChat, guid, guid, cInfo.Name, x, y, z, mapId); + thisMap = handler.GetSession().GetPlayer().GetMap(); else - handler.SendSysMessage(CypherStrings.CreatureListConsole, guid, cInfo.Name, x, y, z, mapId); + thisMap = Global.MapMgr.FindBaseNonInstanceMap(mapId); + + // If map found, try to find active version of this creature + if (thisMap) + { + var creBounds = thisMap.GetCreatureBySpawnIdStore().LookupByKey(guid); + if (!creBounds.Empty()) + { + foreach (var creature in creBounds) + { + if (handler.GetSession()) + handler.SendSysMessage(CypherStrings.CreatureListChat, guid, guid, cInfo.Name, x, y, z, mapId, creature.GetGUID().ToString(), creature.IsAlive() ? "*" : " "); + else + handler.SendSysMessage(CypherStrings.CreatureListConsole, guid, cInfo.Name, x, y, z, mapId, creature.GetGUID().ToString(), creature.IsAlive() ? "*" : " "); + } + liveFound = true; + } + } + + if (!liveFound) + { + if (handler.GetSession()) + handler.SendSysMessage(CypherStrings.CreatureListChat, guid, guid, cInfo.Name, x, y, z, mapId, "", ""); + else + handler.SendSysMessage(CypherStrings.CreatureListConsole, guid, cInfo.Name, x, y, z, mapId, "", ""); + } } while (result.NextRow()); } @@ -495,11 +525,39 @@ namespace Game.Chat.Commands float z = result.Read(3); ushort mapId = result.Read(4); uint entry = result.Read(5); + bool liveFound = false; + // Get map (only support base map from console) + Map thisMap; if (handler.GetSession() != null) - handler.SendSysMessage(CypherStrings.GoListChat, guid, entry, guid, gInfo.name, x, y, z, mapId); + thisMap = handler.GetSession().GetPlayer().GetMap(); else - handler.SendSysMessage(CypherStrings.GoListConsole, guid, gInfo.name, x, y, z, mapId); + thisMap = Global.MapMgr.FindBaseNonInstanceMap(mapId); + + // If map found, try to find active version of this object + if (thisMap) + { + var goBounds = thisMap.GetGameObjectBySpawnIdStore().LookupByKey(guid); + if (!goBounds.Empty()) + { + foreach (var go in goBounds) + { + if (handler.GetSession()) + handler.SendSysMessage(CypherStrings.GoListChat, guid, entry, guid, gInfo.name, x, y, z, mapId, go.GetGUID(), go.IsSpawned() ? "*" : " "); + else + handler.SendSysMessage(CypherStrings.GoListConsole, guid, gInfo.name, x, y, z, mapId, go.GetGUID(), go.IsSpawned() ? "*" : " "); + } + liveFound = true; + } + } + + if (!liveFound) + { + if (handler.GetSession()) + handler.SendSysMessage(CypherStrings.GoListChat, guid, entry, guid, gInfo.name, x, y, z, mapId, "", ""); + else + handler.SendSysMessage(CypherStrings.GoListConsole, guid, gInfo.name, x, y, z, mapId, "", ""); + } } while (result.NextRow()); } @@ -509,6 +567,77 @@ namespace Game.Chat.Commands return true; } + [Command("respawns", RBACPermissions.CommandListRespawns)] + static bool HandleListRespawnsCommand(StringArguments args, CommandHandler handler) + { + // We need a player + Player player = handler.GetSession().GetPlayer(); + if (player == null) + return false; + + // And we need a map + Map map = player.GetMap(); + if (map == null) + return false; + + uint range = 0; + if (!args.Empty()) + range = args.NextUInt32(); + + List respawns = new List(); + Locale locale = handler.GetSession().GetSessionDbcLocale(); + string stringOverdue = Global.ObjectMgr.GetCypherString(CypherStrings.ListRespawnsOverdue, locale); + string stringCreature = Global.ObjectMgr.GetCypherString(CypherStrings.ListRespawnsCreatures, locale); + string stringGameobject = Global.ObjectMgr.GetCypherString(CypherStrings.ListRespawnsGameobjects, locale); + + uint zoneId = player.GetZoneId(); + if (range != 0) + handler.SendSysMessage(CypherStrings.ListRespawnsRange, stringCreature, range); + else + handler.SendSysMessage(CypherStrings.ListRespawnsZone, stringCreature, GetZoneName(zoneId, handler.GetSessionDbcLocale()), zoneId); + handler.SendSysMessage(CypherStrings.ListRespawnsListheader); + map.GetRespawnInfo(respawns, SpawnObjectTypeMask.Creature, range != 0 ? 0 : zoneId); + foreach (RespawnInfo ri in respawns) + { + CreatureData data = Global.ObjectMgr.GetCreatureData(ri.spawnId); + if (data == null) + continue; + + if (range != 0 && !player.IsInDist(data.spawnPoint, range)) + continue; + + uint gridY = ri.gridId / MapConst.MaxGrids; + uint gridX = ri.gridId % MapConst.MaxGrids; + + string respawnTime = ri.respawnTime > Time.UnixTime ? Time.secsToTimeString((ulong)(ri.respawnTime - Time.UnixTime), true) : stringOverdue; + handler.SendSysMessage($"{ri.spawnId} | {ri.entry} | [{gridX},{gridY}] | {GetZoneName(ri.zoneId, handler.GetSessionDbcLocale())} ({ri.zoneId}) | {(data.spawnGroupData.isActive ? respawnTime : "inactive")}"); + } + + respawns.Clear(); + if (range != 0) + handler.SendSysMessage(CypherStrings.ListRespawnsRange, stringGameobject, range); + else + handler.SendSysMessage(CypherStrings.ListRespawnsZone, stringGameobject, GetZoneName(zoneId, handler.GetSessionDbcLocale()), zoneId); + handler.SendSysMessage(CypherStrings.ListRespawnsListheader); + map.GetRespawnInfo(respawns, SpawnObjectTypeMask.GameObject, range != 0 ? 0 : zoneId); + foreach (RespawnInfo ri in respawns) + { + GameObjectData data = Global.ObjectMgr.GetGameObjectData(ri.spawnId); + if (data == null) + continue; + + if (range != 0 && !player.IsInDist(data.spawnPoint, range)) + continue; + + uint gridY = ri.gridId / MapConst.MaxGrids; + uint gridX = ri.gridId % MapConst.MaxGrids; + + string respawnTime = ri.respawnTime > Time.UnixTime ? Time.secsToTimeString((ulong)(ri.respawnTime - Time.UnixTime), true) : stringOverdue; + handler.SendSysMessage($"{ri.spawnId} | {ri.entry} | [{gridX},{gridY}] | {GetZoneName(ri.zoneId, handler.GetSessionDbcLocale())} ({ri.zoneId}) | {(data.spawnGroupData.isActive ? respawnTime : "inactive")}"); + } + return true; + } + [Command("scenes", RBACPermissions.CommandListScenes)] static bool HandleListScenesCommand(StringArguments args, CommandHandler handler) { @@ -531,5 +660,11 @@ namespace Game.Chat.Commands return true; } + + static string GetZoneName(uint zoneId, Locale locale) + { + AreaTableRecord zoneEntry = CliDB.AreaTableStorage.LookupByKey(zoneId); + return zoneEntry != null ? zoneEntry.AreaName[locale] : ""; + } } } diff --git a/Source/Game/Chat/Commands/MiscCommands.cs b/Source/Game/Chat/Commands/MiscCommands.cs index b2e894457..a47ea389b 100644 --- a/Source/Game/Chat/Commands/MiscCommands.cs +++ b/Source/Game/Chat/Commands/MiscCommands.cs @@ -34,221 +34,8 @@ namespace Game.Chat { class MiscCommands { - [CommandNonGroup("commands", RBACPermissions.CommandCommands, true)] - static bool Commands(StringArguments args, CommandHandler handler) - { - string list = ""; - foreach (var command in CommandManager.GetCommands()) - { - if (handler.IsAvailable(command)) - { - if (handler.GetSession() != null) - list += "\n "; - else - list += "\n\r "; - - list += command.Name; - - if (!command.ChildCommands.Empty()) - list += " ..."; - } - } - - if (list.IsEmpty()) - return false; - - handler.SendSysMessage(CypherStrings.AvailableCmd); - handler.SendSysMessage(list); - return true; - } - - [CommandNonGroup("help", RBACPermissions.CommandHelp, true)] - static bool Help(StringArguments args, CommandHandler handler) - { - string cmd = args.NextString(""); - if (cmd.IsEmpty()) - { - handler.ShowHelpForCommand(CommandManager.GetCommands(), "help"); - handler.ShowHelpForCommand(CommandManager.GetCommands(), ""); - } - else - { - if (!handler.ShowHelpForCommand(CommandManager.GetCommands(), cmd)) - handler.SendSysMessage(CypherStrings.NoHelpCmd); - } - - return true; - } - - [CommandNonGroup("revive", RBACPermissions.CommandRevive, true)] - static bool Revive(StringArguments args, CommandHandler handler) - { - Player target; - ObjectGuid targetGuid; - if (!handler.ExtractPlayerTarget(args, out target, out targetGuid)) - return false; - - if (target != null) - { - target.ResurrectPlayer(0.5f); - target.SpawnCorpseBones(); - target.SaveToDB(); - } - else - Player.OfflineResurrect(targetGuid, null); - - return true; - } - - [CommandNonGroup("die", RBACPermissions.CommandDie)] - static bool Die(StringArguments args, CommandHandler handler) - { - Unit target = handler.GetSelectedUnit(); - - if (!target && handler.GetPlayer().GetTarget().IsEmpty()) - { - handler.SendSysMessage(CypherStrings.SelectCharOrCreature); - return false; - } - - Player player = target.ToPlayer(); - if (player) - if (handler.HasLowerSecurity(player, ObjectGuid.Empty, false)) - return false; - - if (target.IsAlive()) - handler.GetSession().GetPlayer().Kill(target); - - return true; - } - - [CommandNonGroup("gps", RBACPermissions.CommandGps)] - static bool HandleGPSCommand(StringArguments args, CommandHandler handler) - { - WorldObject obj; - if (!args.Empty()) - { - HighGuid guidHigh = 0; - ulong guidLow = handler.ExtractLowGuidFromLink(args, ref guidHigh); - if (guidLow == 0) - return false; - switch (guidHigh) - { - case HighGuid.Player: - { - obj = Global.ObjAccessor.FindPlayer(ObjectGuid.Create(HighGuid.Player, guidLow)); - if (!obj) - { - handler.SendSysMessage(CypherStrings.PlayerNotFound); - } - break; - } - case HighGuid.Creature: - { - obj = handler.GetCreatureFromPlayerMapByDbGuid(guidLow); - if (!obj) - { - handler.SendSysMessage(CypherStrings.CommandNocreaturefound); - } - break; - } - case HighGuid.GameObject: - { - obj = handler.GetObjectFromPlayerMapByDbGuid(guidLow); - if (!obj) - { - handler.SendSysMessage(CypherStrings.CommandNogameobjectfound); - } - break; - } - default: - return false; - } - if (!obj) - return false; - } - else - { - obj = handler.GetSelectedUnit(); - - if (!obj) - { - handler.SendSysMessage(CypherStrings.SelectCharOrCreature); - return false; - } - } - - CellCoord cellCoord = GridDefines.ComputeCellCoord(obj.GetPositionX(), obj.GetPositionY()); - Cell cell = new Cell(cellCoord); - - uint zoneId, areaId; - obj.GetZoneAndAreaId(out zoneId, out areaId); - uint mapId = obj.GetMapId(); - - MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapId); - AreaTableRecord zoneEntry = CliDB.AreaTableStorage.LookupByKey(zoneId); - AreaTableRecord areaEntry = CliDB.AreaTableStorage.LookupByKey(areaId); - - float zoneX = obj.GetPositionX(); - float zoneY = obj.GetPositionY(); - - Global.DB2Mgr.Map2ZoneCoordinates((int)zoneId, ref zoneX, ref zoneY); - - Map map = obj.GetMap(); - float groundZ = map.GetHeight(obj.GetPhaseShift(), obj.GetPositionX(), obj.GetPositionY(), MapConst.MaxHeight); - float floorZ = map.GetHeight(obj.GetPhaseShift(), obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ()); - - GridCoord gridCoord = GridDefines.ComputeGridCoord(obj.GetPositionX(), obj.GetPositionY()); - - // 63? WHY? - uint gridX = (MapConst.MaxGrids - 1) - gridCoord.X_coord; - uint gridY = (MapConst.MaxGrids - 1) - gridCoord.Y_coord; - - bool haveMap = Map.ExistMap(mapId, gridX, gridY); - bool haveVMap = Map.ExistVMap(mapId, gridX, gridY); - bool haveMMap = (Global.DisableMgr.IsPathfindingEnabled(mapId) && Global.MMapMgr.GetNavMesh(handler.GetSession().GetPlayer().GetMapId()) != null); - - if (haveVMap) - { - if (map.IsOutdoors(obj.GetPhaseShift(), obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ())) - handler.SendSysMessage(CypherStrings.GpsPositionOutdoors); - else - handler.SendSysMessage(CypherStrings.GpsPositionIndoors); - } - else - handler.SendSysMessage(CypherStrings.GpsNoVmap); - - string unknown = handler.GetCypherString(CypherStrings.Unknown); - - handler.SendSysMessage(CypherStrings.MapPosition, - mapId, (mapEntry != null ? mapEntry.MapName[handler.GetSessionDbcLocale()] : unknown), - zoneId, (zoneEntry != null ? zoneEntry.AreaName[handler.GetSessionDbcLocale()] : unknown), - areaId, (areaEntry != null ? areaEntry.AreaName[handler.GetSessionDbcLocale()] : unknown), - obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ(), obj.GetOrientation()); - - Transport transport = obj.GetTransport(); - if (transport) - { - handler.SendSysMessage(CypherStrings.TransportPosition, transport.GetGoInfo().MoTransport.SpawnMap, obj.GetTransOffsetX(), obj.GetTransOffsetY(), obj.GetTransOffsetZ(), obj.GetTransOffsetO(), - transport.GetEntry(), transport.GetName()); - } - - handler.SendSysMessage(CypherStrings.GridPosition, cell.GetGridX(), cell.GetGridY(), cell.GetCellX(), cell.GetCellY(), obj.GetInstanceId(), - zoneX, zoneY, groundZ, floorZ, haveMap, haveVMap, haveMMap); - - LiquidData liquidStatus; - ZLiquidStatus status = map.GetLiquidStatus(obj.GetPhaseShift(), obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ(), MapConst.MapAllLiquidTypes, out liquidStatus); - - if (liquidStatus != null) - handler.SendSysMessage(CypherStrings.LiquidStatus, liquidStatus.level, liquidStatus.depth_level, liquidStatus.entry, liquidStatus.type_flags, status); - - PhasingHandler.PrintToChat(handler, obj.GetPhaseShift()); - - return true; - } - [CommandNonGroup("additem", RBACPermissions.CommandAdditem)] - static bool AddItem(StringArguments args, CommandHandler handler) + static bool HandleAddItemCommand(StringArguments args, CommandHandler handler) { if (args.Empty()) return false; @@ -372,7 +159,7 @@ namespace Game.Chat } [CommandNonGroup("additemset", RBACPermissions.CommandAdditemset)] - static bool AddItemset(StringArguments args, CommandHandler handler) + static bool HandleAddItemSetCommand(StringArguments args, CommandHandler handler) { if (args.Empty()) return false; @@ -446,41 +233,9 @@ namespace Game.Chat return true; } - [CommandNonGroup("dev", RBACPermissions.CommandDev)] - static bool HandleDevCommand(StringArguments args, CommandHandler handler) - { - if (args.Empty()) - { - if (handler.GetSession().GetPlayer().HasPlayerFlag(PlayerFlags.Developer)) - handler.GetSession().SendNotification(CypherStrings.DevOn); - else - handler.GetSession().SendNotification(CypherStrings.DevOff); - return true; - } - - string argstr = args.NextString(); - - if (argstr == "on") - { - handler.GetSession().GetPlayer().HasPlayerFlag(PlayerFlags.Developer); - handler.GetSession().SendNotification(CypherStrings.DevOn); - return true; - } - - if (argstr == "off") - { - handler.GetSession().GetPlayer().RemovePlayerFlag(PlayerFlags.Developer); - handler.GetSession().SendNotification(CypherStrings.DevOff); - return true; - } - - handler.SendSysMessage(CypherStrings.UseBol); - return false; - } - // Teleport to Player [CommandNonGroup("appear", RBACPermissions.CommandAppear)] - static bool Appear(StringArguments args, CommandHandler handler) + static bool HandleAppearCommand(StringArguments args, CommandHandler handler) { Player target; ObjectGuid targetGuid; @@ -624,125 +379,282 @@ namespace Game.Chat return true; } - // Summon Player - [CommandNonGroup("summon", RBACPermissions.CommandSummon)] - static bool Summon(StringArguments args, CommandHandler handler) + [CommandNonGroup("bank", RBACPermissions.CommandBank)] + static bool HandleBankCommand(StringArguments args, CommandHandler handler) { - Player target; - ObjectGuid targetGuid; - string targetName; - if (!handler.ExtractPlayerTarget(args, out target, out targetGuid, out targetName)) + handler.GetSession().SendShowBank(handler.GetSession().GetPlayer().GetGUID()); + return true; + } + + [CommandNonGroup("bindsight", RBACPermissions.CommandBindsight)] + static bool HandleBindSightCommand(StringArguments args, CommandHandler handler) + { + Unit unit = handler.GetSelectedUnit(); + if (!unit) return false; - Player _player = handler.GetSession().GetPlayer(); - if (target == _player || targetGuid == _player.GetGUID()) - { - handler.SendSysMessage(CypherStrings.CantTeleportSelf); + handler.GetSession().GetPlayer().CastSpell(unit, 6277, true); + return true; + } + [CommandNonGroup("combatstop", RBACPermissions.CommandCombatstop, true)] + static bool HandleCombatStopCommand(StringArguments args, CommandHandler handler) + { + Player target = null; + + if (!args.Empty()) + { + target = Global.ObjAccessor.FindPlayerByName(args.NextString()); + if (!target) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + return false; + } + } + + if (!target) + { + if (!handler.ExtractPlayerTarget(args, out target)) + return false; + } + + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + target.CombatStop(); + target.GetHostileRefManager().DeleteReferences(); + return true; + } + + [CommandNonGroup("cometome", RBACPermissions.CommandCometome)] + static bool HandleComeToMeCommand(StringArguments args, CommandHandler handler) + { + Creature caster = handler.GetSelectedCreature(); + if (!caster) + { + handler.SendSysMessage(CypherStrings.SelectCreature); return false; } - if (target) + Player player = handler.GetSession().GetPlayer(); + caster.GetMotionMaster().MovePoint(0, player.GetPositionX(), player.GetPositionY(), player.GetPositionZ()); + + return true; + } + + [CommandNonGroup("commands", RBACPermissions.CommandCommands, true)] + static bool HandleCommandsCommand(StringArguments args, CommandHandler handler) + { + string list = ""; + foreach (var command in CommandManager.GetCommands()) { - string nameLink = handler.PlayerLink(targetName); - // check online security - if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) - return false; - - if (target.IsBeingTeleported()) + if (handler.IsAvailable(command)) { - handler.SendSysMessage(CypherStrings.IsTeleported, nameLink); + if (handler.GetSession() != null) + list += "\n "; + else + list += "\n\r "; + list += command.Name; + + if (!command.ChildCommands.Empty()) + list += " ..."; + } + } + + if (list.IsEmpty()) + return false; + + handler.SendSysMessage(CypherStrings.AvailableCmd); + handler.SendSysMessage(list); + return true; + } + + [CommandNonGroup("damage", RBACPermissions.CommandDamage)] + static bool HandleDamageCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string str = args.NextString(); + + if (str == "go") + { + ulong guidLow = args.NextUInt64(); + if (guidLow == 0) + { + handler.SendSysMessage(CypherStrings.BadValue); return false; } - Map map = _player.GetMap(); - if (map.IsBattlegroundOrArena()) + int damage = args.NextInt32(); + if (damage == 0) { - // only allow if gm mode is on - if (!_player.IsGameMaster()) + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + Player player = handler.GetSession().GetPlayer(); + if (player) + { + GameObject go = handler.GetObjectFromPlayerMapByDbGuid(guidLow); + if (!go) { - handler.SendSysMessage(CypherStrings.CannotGoToBgGm, nameLink); + handler.SendSysMessage(CypherStrings.CommandObjnotfound, guidLow); return false; } - // if both players are in different bgs - else if (target.GetBattlegroundId() != 0 && _player.GetBattlegroundId() != target.GetBattlegroundId()) - target.LeaveBattleground(false); // Note: should be changed so target gets no Deserter debuff - // all's well, set bg id - // when porting out from the bg, it will be reset to 0 - target.SetBattlegroundId(_player.GetBattlegroundId(), _player.GetBattlegroundTypeId()); - // remember current position as entry point for return at bg end teleportation - if (!target.GetMap().IsBattlegroundOrArena()) - target.SetBattlegroundEntryPoint(); - } - else if (map.Instanceable()) - { - Map targetMap = target.GetMap(); - - Player targetGroupLeader = null; - Group targetGroup = target.GetGroup(); - if (targetGroup != null) - targetGroupLeader = Global.ObjAccessor.GetPlayer(map, targetGroup.GetLeaderGUID()); - - // check if far teleport is allowed - if (targetGroupLeader == null || (targetGroupLeader.GetMapId() != map.GetId()) || (targetGroupLeader.GetInstanceId() != map.GetInstanceId())) + if (!go.IsDestructibleBuilding()) { - if ((targetMap.GetId() != map.GetId()) || (targetMap.GetInstanceId() != map.GetInstanceId())) - { - handler.SendSysMessage(CypherStrings.CannotSummonToInst); - return false; - } - } - - // check if we're already in a different instance of the same map - if ((targetMap.GetId() == map.GetId()) && (targetMap.GetInstanceId() != map.GetInstanceId())) - { - handler.SendSysMessage(CypherStrings.CannotSummonInstInst, nameLink); + handler.SendSysMessage(CypherStrings.InvalidGameobjectType); return false; } + + go.ModifyHealth(-damage, player); + handler.SendSysMessage(CypherStrings.GameobjectDamaged, go.GetName(), guidLow, -damage, go.GetGoValue().Building.Health); } - handler.SendSysMessage(CypherStrings.Summoning, nameLink, ""); - if (handler.NeedReportToTarget(target)) - target.SendSysMessage(CypherStrings.SummonedBy, handler.PlayerLink(_player.GetName())); + return true; + } - // stop flight if need - if (target.IsInFlight()) - { - target.GetMotionMaster().MovementExpired(); - target.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + Unit target = handler.GetSelectedUnit(); + if (!target || handler.GetSession().GetPlayer().GetTarget().IsEmpty()) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + Player player_ = target.ToPlayer(); + if (player_) + if (handler.HasLowerSecurity(player_, ObjectGuid.Empty, false)) + return false; + + if (!target.IsAlive()) + return true; + + if (!int.TryParse(str, out int damage_int)) + return false; + + if (damage_int <= 0) + return true; + + uint damage_ = (uint)damage_int; + + string schoolStr = args.NextString(); + + Player attacker = handler.GetSession().GetPlayer(); + + // flat melee damage without resistence/etc reduction + if (string.IsNullOrEmpty(schoolStr)) + { + attacker.DealDamage(target, damage_, null, DamageEffectType.Direct, SpellSchoolMask.Normal, null, false); + if (target != attacker) + attacker.SendAttackStateUpdate(HitInfo.AffectsVictim, target, SpellSchoolMask.Normal, damage_, 0, 0, VictimState.Hit, 0); + return true; + } + + if (!int.TryParse(schoolStr, out int school) || school >= (int)SpellSchools.Max) + return false; + + SpellSchoolMask schoolmask = (SpellSchoolMask)(1 << school); + + if (attacker.IsDamageReducedByArmor(schoolmask)) + damage_ = attacker.CalcArmorReducedDamage(handler.GetPlayer(), target, damage_, null, WeaponAttackType.BaseAttack); + + string spellStr = args.NextString(); + + // melee damage by specific school + if (string.IsNullOrEmpty(spellStr)) + { + DamageInfo dmgInfo = new DamageInfo(attacker, target, damage_, null, schoolmask, DamageEffectType.SpellDirect, WeaponAttackType.BaseAttack); + attacker.CalcAbsorbResist(dmgInfo); + + if (dmgInfo.GetDamage() == 0) + return true; + + damage_ = dmgInfo.GetDamage(); + + uint absorb = dmgInfo.GetAbsorb(); + uint resist = dmgInfo.GetResist(); + attacker.DealDamageMods(target, ref damage_, ref absorb); + attacker.DealDamage(target, damage_, null, DamageEffectType.Direct, schoolmask, null, false); + attacker.SendAttackStateUpdate(HitInfo.AffectsVictim, target, schoolmask, damage_, absorb, resist, VictimState.Hit, 0); + return true; + } + + // non-melee damage + // number or [name] Shift-click form |color|Hspell:spell_id|h[name]|h|r or Htalent form + uint spellid = handler.ExtractSpellIdFromLink(args); + if (spellid == 0) + return false; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellid, attacker.GetMap().GetDifficultyID()); + if (spellInfo == null) + return false; + + SpellNonMeleeDamage damageInfo = new SpellNonMeleeDamage(attacker, target, spellInfo, spellInfo.GetSpellXSpellVisualId(attacker), spellInfo.SchoolMask); + damageInfo.damage = damage_; + attacker.DealDamageMods(damageInfo.target, ref damageInfo.damage, ref damageInfo.absorb); + target.DealSpellDamage(damageInfo, true); + target.SendSpellNonMeleeDamageLog(damageInfo); + return true; + } + + [CommandNonGroup("dev", RBACPermissions.CommandDev)] + static bool HandleDevCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + { + if (handler.GetSession().GetPlayer().HasPlayerFlag(PlayerFlags.Developer)) + handler.GetSession().SendNotification(CypherStrings.DevOn); else - target.SaveRecallPosition(); - - // before GM - float x, y, z; - _player.GetClosePoint(out x, out y, out z, target.GetCombatReach()); - target.TeleportTo(_player.GetMapId(), x, y, z, target.GetOrientation()); - PhasingHandler.InheritPhaseShift(target, _player); - target.UpdateObjectVisibility(); + handler.GetSession().SendNotification(CypherStrings.DevOff); + return true; } - else + + string argstr = args.NextString(); + + if (argstr == "on") { - // check offline security - if (handler.HasLowerSecurity(null, targetGuid)) + handler.GetSession().GetPlayer().HasPlayerFlag(PlayerFlags.Developer); + handler.GetSession().SendNotification(CypherStrings.DevOn); + return true; + } + + if (argstr == "off") + { + handler.GetSession().GetPlayer().RemovePlayerFlag(PlayerFlags.Developer); + handler.GetSession().SendNotification(CypherStrings.DevOff); + return true; + } + + handler.SendSysMessage(CypherStrings.UseBol); + return false; + } + + [CommandNonGroup("die", RBACPermissions.CommandDie)] + static bool HandleDieCommand(StringArguments args, CommandHandler handler) + { + Unit target = handler.GetSelectedUnit(); + + if (!target && handler.GetPlayer().GetTarget().IsEmpty()) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + + Player player = target.ToPlayer(); + if (player) + if (handler.HasLowerSecurity(player, ObjectGuid.Empty, false)) return false; - string nameLink = handler.PlayerLink(targetName); - - handler.SendSysMessage(CypherStrings.Summoning, nameLink, handler.GetCypherString(CypherStrings.Offline)); - - // in point where GM stay - Player.SavePositionInDB(new WorldLocation(_player.GetMapId(), _player.GetPositionX(), _player.GetPositionY(), _player.GetPositionZ(), _player.GetOrientation()), _player.GetZoneId(), targetGuid); - } + if (target.IsAlive()) + handler.GetSession().GetPlayer().Kill(target); return true; } [CommandNonGroup("dismount", RBACPermissions.CommandDismount)] - static bool Dismount(StringArguments args, CommandHandler handler) + static bool HandleDismountCommand(StringArguments args, CommandHandler handler) { Player player = handler.GetSession().GetPlayer(); @@ -764,48 +676,6 @@ namespace Game.Chat return true; } - [CommandNonGroup("guid", RBACPermissions.CommandGuid)] - static bool Guid(StringArguments args, CommandHandler handler) - { - ObjectGuid guid = handler.GetSession().GetPlayer().GetTarget(); - - if (guid.IsEmpty()) - { - handler.SendSysMessage(CypherStrings.NoSelection); - return false; - } - - handler.SendSysMessage(CypherStrings.ObjectGuid, guid.ToString(), guid.GetHigh()); - return true; - } - - // move item to other slot - [CommandNonGroup("itemmove", RBACPermissions.CommandItemmove)] - static bool ItemMove(StringArguments args, CommandHandler handler) - { - if (args.Empty()) - return false; - - byte srcSlot = args.NextByte(); - byte dstSlot = args.NextByte(); - - if (srcSlot == dstSlot) - return true; - - if (handler.GetSession().GetPlayer().IsValidPos(InventorySlots.Bag0, srcSlot, true)) - return false; - - if (handler.GetSession().GetPlayer().IsValidPos(InventorySlots.Bag0, dstSlot, false)) - return false; - - ushort src = (ushort)((InventorySlots.Bag0 << 8) | srcSlot); - ushort dst = (ushort)((InventorySlots.Bag0 << 8) | dstSlot); - - handler.GetSession().GetPlayer().SwapItem(src, dst); - - return true; - } - [CommandNonGroup("distance", RBACPermissions.CommandDistance)] static bool HandleGetDistanceCommand(StringArguments args, CommandHandler handler) { @@ -867,73 +737,326 @@ namespace Game.Chat return true; } - // Teleport player to last position - [CommandNonGroup("recall", RBACPermissions.CommandRecall)] - static bool Recall(StringArguments args, CommandHandler handler) + [CommandNonGroup("freeze", RBACPermissions.CommandFreeze)] + static bool HandleFreezeCommand(StringArguments args, CommandHandler handler) { - Player target; - if (!handler.ExtractPlayerTarget(args, out target)) - return false; + Player player = handler.GetSelectedPlayer(); // Selected player, if any. Might be null. + int freezeDuration = 0; // Freeze Duration (in seconds) + bool canApplyFreeze = false; // Determines if every possible argument is set so Freeze can be applied + bool getDurationFromConfig = false; // If there's no given duration, we'll retrieve the world cfg value later - // check online security - if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) - return false; - - if (target.IsBeingTeleported()) + if (args.Empty()) { - handler.SendSysMessage(CypherStrings.IsTeleported, handler.GetNameLink(target)); + // Might have a selected player. We'll check it later + // Get the duration from world cfg + getDurationFromConfig = true; + } + else + { + // Get the args that we might have (up to 2) + string arg1 = args.NextString(); + string arg2 = args.NextString(); - return false; + // Analyze them to see if we got either a playerName or duration or both + if (!arg1.IsEmpty()) + { + if (arg1.IsNumber()) + { + // case 2: .freeze duration + // We have a selected player. We'll check him later + if (!int.TryParse(arg1, out freezeDuration)) + return false; + canApplyFreeze = true; + } + else + { + // case 3 or 4: .freeze player duration | .freeze player + // find the player + string name = arg1; + ObjectManager.NormalizePlayerName(ref name); + player = Global.ObjAccessor.FindPlayerByName(name); + // Check if we have duration set + if (!arg2.IsEmpty() && arg2.IsNumber()) + { + if (!int.TryParse(arg2, out freezeDuration)) + return false; + canApplyFreeze = true; + } + else + getDurationFromConfig = true; + } + } } - // stop flight if need - if (target.IsInFlight()) + // Check if duration needs to be retrieved from config + if (getDurationFromConfig) { - target.GetMotionMaster().MovementExpired(); - target.CleanupAfterTaxiFlight(); + freezeDuration = WorldConfig.GetIntValue(WorldCfg.GmFreezeDuration); + canApplyFreeze = true; } - target.Recall(); - return true; + // Player and duration retrieval is over + if (canApplyFreeze) + { + if (!player) // can be null if some previous selection failed + { + handler.SendSysMessage(CypherStrings.CommandFreezeWrong); + return true; + } + else if (player == handler.GetSession().GetPlayer()) + { + // Can't freeze himself + handler.SendSysMessage(CypherStrings.CommandFreezeError); + return true; + } + else // Apply the effect + { + // Add the freeze aura and set the proper duration + // Player combat status and flags are now handled + // in Freeze Spell AuraScript (OnApply) + Aura freeze = player.AddAura(9454, player); + if (freeze != null) + { + if (freezeDuration != 0) + freeze.SetDuration(freezeDuration * Time.InMilliseconds); + handler.SendSysMessage(CypherStrings.CommandFreeze, player.GetName()); + // save player + player.SaveToDB(); + return true; + } + } + } + return false; } - [CommandNonGroup("save", RBACPermissions.CommandSave)] - static bool Save(StringArguments args, CommandHandler handler) + [CommandNonGroup("gps", RBACPermissions.CommandGps)] + static bool HandleGPSCommand(StringArguments args, CommandHandler handler) { - Player player = handler.GetSession().GetPlayer(); - - // save GM account without delay and output message - if (handler.GetSession().HasPermission(RBACPermissions.CommandsSaveWithoutDelay)) + WorldObject obj; + if (!args.Empty()) { - Player target = handler.GetSelectedPlayer(); - if (target) - target.SaveToDB(); + HighGuid guidHigh = 0; + ulong guidLow = handler.ExtractLowGuidFromLink(args, ref guidHigh); + if (guidLow == 0) + return false; + switch (guidHigh) + { + case HighGuid.Player: + { + obj = Global.ObjAccessor.FindPlayer(ObjectGuid.Create(HighGuid.Player, guidLow)); + if (!obj) + { + handler.SendSysMessage(CypherStrings.PlayerNotFound); + } + break; + } + case HighGuid.Creature: + { + obj = handler.GetCreatureFromPlayerMapByDbGuid(guidLow); + if (!obj) + { + handler.SendSysMessage(CypherStrings.CommandNocreaturefound); + } + break; + } + case HighGuid.GameObject: + { + obj = handler.GetObjectFromPlayerMapByDbGuid(guidLow); + if (!obj) + { + handler.SendSysMessage(CypherStrings.CommandNogameobjectfound); + } + break; + } + default: + return false; + } + if (!obj) + return false; + } + else + { + obj = handler.GetSelectedUnit(); + + if (!obj) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + return false; + } + } + + CellCoord cellCoord = GridDefines.ComputeCellCoord(obj.GetPositionX(), obj.GetPositionY()); + Cell cell = new Cell(cellCoord); + + uint zoneId, areaId; + obj.GetZoneAndAreaId(out zoneId, out areaId); + uint mapId = obj.GetMapId(); + + MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapId); + AreaTableRecord zoneEntry = CliDB.AreaTableStorage.LookupByKey(zoneId); + AreaTableRecord areaEntry = CliDB.AreaTableStorage.LookupByKey(areaId); + + float zoneX = obj.GetPositionX(); + float zoneY = obj.GetPositionY(); + + Global.DB2Mgr.Map2ZoneCoordinates((int)zoneId, ref zoneX, ref zoneY); + + Map map = obj.GetMap(); + float groundZ = map.GetHeight(obj.GetPhaseShift(), obj.GetPositionX(), obj.GetPositionY(), MapConst.MaxHeight); + float floorZ = map.GetHeight(obj.GetPhaseShift(), obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ()); + + GridCoord gridCoord = GridDefines.ComputeGridCoord(obj.GetPositionX(), obj.GetPositionY()); + + // 63? WHY? + uint gridX = (MapConst.MaxGrids - 1) - gridCoord.X_coord; + uint gridY = (MapConst.MaxGrids - 1) - gridCoord.Y_coord; + + bool haveMap = Map.ExistMap(mapId, gridX, gridY); + bool haveVMap = Map.ExistVMap(mapId, gridX, gridY); + bool haveMMap = (Global.DisableMgr.IsPathfindingEnabled(mapId) && Global.MMapMgr.GetNavMesh(handler.GetSession().GetPlayer().GetMapId()) != null); + + if (haveVMap) + { + if (map.IsOutdoors(obj.GetPhaseShift(), obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ())) + handler.SendSysMessage(CypherStrings.GpsPositionOutdoors); else - player.SaveToDB(); - handler.SendSysMessage(CypherStrings.PlayerSaved); - return true; + handler.SendSysMessage(CypherStrings.GpsPositionIndoors); + } + else + handler.SendSysMessage(CypherStrings.GpsNoVmap); + + string unknown = handler.GetCypherString(CypherStrings.Unknown); + + handler.SendSysMessage(CypherStrings.MapPosition, + mapId, (mapEntry != null ? mapEntry.MapName[handler.GetSessionDbcLocale()] : unknown), + zoneId, (zoneEntry != null ? zoneEntry.AreaName[handler.GetSessionDbcLocale()] : unknown), + areaId, (areaEntry != null ? areaEntry.AreaName[handler.GetSessionDbcLocale()] : unknown), + obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ(), obj.GetOrientation()); + + Transport transport = obj.GetTransport(); + if (transport) + { + handler.SendSysMessage(CypherStrings.TransportPosition, transport.GetGoInfo().MoTransport.SpawnMap, obj.GetTransOffsetX(), obj.GetTransOffsetY(), obj.GetTransOffsetZ(), obj.GetTransOffsetO(), + transport.GetEntry(), transport.GetName()); } - // save if the player has last been saved over 20 seconds ago - uint saveInterval = WorldConfig.GetUIntValue(WorldCfg.IntervalSave); - if (saveInterval == 0 || (saveInterval > 20 * Time.InMilliseconds && player.GetSaveTimer() <= saveInterval - 20 * Time.InMilliseconds)) - player.SaveToDB(); + handler.SendSysMessage(CypherStrings.GridPosition, cell.GetGridX(), cell.GetGridY(), cell.GetCellX(), cell.GetCellY(), obj.GetInstanceId(), + zoneX, zoneY, groundZ, floorZ, haveMap, haveVMap, haveMMap); + + LiquidData liquidStatus; + ZLiquidStatus status = map.GetLiquidStatus(obj.GetPhaseShift(), obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ(), MapConst.MapAllLiquidTypes, out liquidStatus); + + if (liquidStatus != null) + handler.SendSysMessage(CypherStrings.LiquidStatus, liquidStatus.level, liquidStatus.depth_level, liquidStatus.entry, liquidStatus.type_flags, status); + + PhasingHandler.PrintToChat(handler, obj.GetPhaseShift()); return true; } - // Save all players in the world - [CommandNonGroup("saveall", RBACPermissions.CommandSaveall, true)] - static bool SaveAll(StringArguments args, CommandHandler handler) + [CommandNonGroup("guid", RBACPermissions.CommandGuid)] + static bool HandleGUIDCommand(StringArguments args, CommandHandler handler) { - Global.ObjAccessor.SaveAllPlayers(); - handler.SendSysMessage(CypherStrings.PlayersSaved); + ObjectGuid guid = handler.GetSession().GetPlayer().GetTarget(); + + if (guid.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.NoSelection); + return false; + } + + handler.SendSysMessage(CypherStrings.ObjectGuid, guid.ToString(), guid.GetHigh()); + return true; + } + + [CommandNonGroup("help", RBACPermissions.CommandHelp, true)] + static bool HandleHelpCommand(StringArguments args, CommandHandler handler) + { + string cmd = args.NextString(""); + if (cmd.IsEmpty()) + { + handler.ShowHelpForCommand(CommandManager.GetCommands(), "help"); + handler.ShowHelpForCommand(CommandManager.GetCommands(), ""); + } + else + { + if (!handler.ShowHelpForCommand(CommandManager.GetCommands(), cmd)) + handler.SendSysMessage(CypherStrings.NoHelpCmd); + } + + return true; + } + + [CommandNonGroup("hidearea", RBACPermissions.CommandHidearea)] + static bool HandleHideAreaCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player playerTarget = handler.GetSelectedPlayer(); + if (!playerTarget) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(args.NextUInt32()); + if (area == null) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + if (area.AreaBit < 0) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + uint offset = (uint)area.AreaBit / 64; + if (offset >= PlayerConst.ExploredZonesSize) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + uint val = (1u << (area.AreaBit % 64)); + playerTarget.RemoveExploredZones(offset, val); + + handler.SendSysMessage(CypherStrings.UnexploreArea); + return true; + } + + // move item to other slot + [CommandNonGroup("itemmove", RBACPermissions.CommandItemmove)] + static bool HandleItemMoveCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + byte srcSlot = args.NextByte(); + byte dstSlot = args.NextByte(); + + if (srcSlot == dstSlot) + return true; + + if (handler.GetSession().GetPlayer().IsValidPos(InventorySlots.Bag0, srcSlot, true)) + return false; + + if (handler.GetSession().GetPlayer().IsValidPos(InventorySlots.Bag0, dstSlot, false)) + return false; + + ushort src = (ushort)((InventorySlots.Bag0 << 8) | srcSlot); + ushort dst = (ushort)((InventorySlots.Bag0 << 8) | dstSlot); + + handler.GetSession().GetPlayer().SwapItem(src, dst); + return true; } // kick player [CommandNonGroup("kick", RBACPermissions.CommandKick, true)] - static bool Kick(StringArguments args, CommandHandler handler) + static bool HandleKickPlayerCommand(StringArguments args, CommandHandler handler) { Player target; string playerName; @@ -965,90 +1088,8 @@ namespace Game.Chat return true; } - [CommandNonGroup("unstuck", RBACPermissions.CommandUnstuck, true)] - static bool HandleUnstuckCommand(StringArguments args, CommandHandler handler) - { - uint SPELL_UNSTUCK_ID = 7355; - uint SPELL_UNSTUCK_VISUAL = 2683; - - // No args required for players - if (handler.GetSession() != null && handler.GetSession().HasPermission(RBACPermissions.CommandsUseUnstuckWithArgs)) - { - // 7355: "Stuck" - var player1 = handler.GetSession().GetPlayer(); - if (player1) - player1.CastSpell(player1, SPELL_UNSTUCK_ID, false); - return true; - } - - if (args.Empty()) - return false; - - string location_str = "inn"; - string loc = args.NextString(); - if (string.IsNullOrEmpty(loc)) - location_str = loc; - - Player player; - ObjectGuid targetGUID; - if (!handler.ExtractPlayerTarget(args, out player, out targetGUID)) - return false; - - if (!player) - { - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_HOMEBIND); - stmt.AddValue(0, targetGUID.GetCounter()); - SQLResult result = DB.Characters.Query(stmt); - if (!result.IsEmpty()) - { - Player.SavePositionInDB(new WorldLocation(result.Read(0), result.Read(2), result.Read(3), result.Read(4), 0.0f), result.Read(1), targetGUID); - return true; - } - - return false; - } - - if (player.IsInFlight() || player.IsInCombat()) - { - SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(SPELL_UNSTUCK_ID, Difficulty.None); - if (spellInfo == null) - return false; - - Player caster = handler.GetSession().GetPlayer(); - if (caster) - { - ObjectGuid castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, player.GetMapId(), SPELL_UNSTUCK_ID, player.GetMap().GenerateLowGuid(HighGuid.Cast)); - Spell.SendCastResult(caster, spellInfo, SPELL_UNSTUCK_VISUAL, castId, SpellCastResult.CantDoThatRightNow); - } - - return false; - } - - if (location_str == "inn") - { - var home = player.GetHomebind(); - player.TeleportTo(home.GetMapId(), home.GetPositionX(), home.GetPositionY(), home.GetPositionZ(), player.GetOrientation()); - return true; - } - - if (location_str == "graveyard") - { - player.RepopAtGraveyard(); - return true; - } - - if (location_str == "startzone") - { - player.TeleportTo(player.GetStartPosition()); - return true; - } - - //Not a supported argument - return false; - } - [CommandNonGroup("linkgrave", RBACPermissions.CommandLinkgrave)] - static bool LinkGrave(StringArguments args, CommandHandler handler) + static bool HandleLinkGraveCommand(StringArguments args, CommandHandler handler) { if (args.Empty()) return false; @@ -1094,8 +1135,285 @@ namespace Game.Chat return true; } + [CommandNonGroup("listfreeze", RBACPermissions.CommandListfreeze)] + static bool HandleListFreezeCommand(StringArguments args, CommandHandler handler) + { + // Get names from DB + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_AURA_FROZEN); + SQLResult result = DB.Characters.Query(stmt); + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.CommandNoFrozenPlayers); + return true; + } + + // Header of the names + handler.SendSysMessage(CypherStrings.CommandListFreeze); + + // Output of the results + do + { + string player = result.Read(0); + int remaintime = result.Read(1); + // Save the frozen player to update remaining time in case of future .listfreeze uses + // before the frozen state expires + Player frozen = Global.ObjAccessor.FindPlayerByName(player); + if (frozen) + frozen.SaveToDB(); + // Notify the freeze duration + if (remaintime == -1) // Permanent duration + handler.SendSysMessage(CypherStrings.CommandPermaFrozenPlayer, player); + else + // show time left (seconds) + handler.SendSysMessage(CypherStrings.CommandTempFrozenPlayer, player, remaintime / Time.InMilliseconds); + } + while (result.NextRow()); + + return true; + } + + [CommandNonGroup("mailbox", RBACPermissions.CommandMailbox)] + static bool HandleMailBoxCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + + handler.GetSession().SendShowMailBox(player.GetGUID()); + return true; + } + + [CommandNonGroup("movegens", RBACPermissions.CommandMovegens)] + static bool HandleMovegensCommand(StringArguments args, CommandHandler handler) + { + Unit unit = handler.GetSelectedUnit(); + if (!unit) + { + handler.SendSysMessage(CypherStrings.SelectCharOrCreature); + + return false; + } + + handler.SendSysMessage(CypherStrings.MovegensList, (unit.IsTypeId(TypeId.Player) ? "Player" : "Creature"), unit.GetGUID().ToString()); + + MotionMaster motionMaster = unit.GetMotionMaster(); + float x, y, z; + motionMaster.GetDestination(out x, out y, out z); + + for (byte i = 0; i < (int)MovementSlot.Max; ++i) + { + IMovementGenerator movementGenerator = motionMaster.GetMotionSlot(i); + if (movementGenerator == null) + { + handler.SendSysMessage("Empty"); + continue; + } + + switch (movementGenerator.GetMovementGeneratorType()) + { + case MovementGeneratorType.Idle: + handler.SendSysMessage(CypherStrings.MovegensIdle); + break; + case MovementGeneratorType.Random: + handler.SendSysMessage(CypherStrings.MovegensRandom); + break; + case MovementGeneratorType.Waypoint: + handler.SendSysMessage(CypherStrings.MovegensWaypoint); + break; + case MovementGeneratorType.Confused: + handler.SendSysMessage(CypherStrings.MovegensConfused); + break; + case MovementGeneratorType.Chase: + { + Unit target; + if (unit.IsTypeId(TypeId.Player)) + target = ((ChaseMovementGenerator)movementGenerator).GetTarget(); + else + target = ((ChaseMovementGenerator)movementGenerator).GetTarget(); + + if (!target) + handler.SendSysMessage(CypherStrings.MovegensChaseNull); + else if (target.IsTypeId(TypeId.Player)) + handler.SendSysMessage(CypherStrings.MovegensChasePlayer, target.GetName(), target.GetGUID().ToString()); + else + handler.SendSysMessage(CypherStrings.MovegensChaseCreature, target.GetName(), target.GetGUID().ToString()); + break; + } + case MovementGeneratorType.Follow: + { + Unit target; + if (unit.IsTypeId(TypeId.Player)) + target = ((FollowMovementGenerator)movementGenerator).GetTarget(); + else + target = ((FollowMovementGenerator)movementGenerator).GetTarget(); + + if (!target) + handler.SendSysMessage(CypherStrings.MovegensFollowNull); + else if (target.IsTypeId(TypeId.Player)) + handler.SendSysMessage(CypherStrings.MovegensFollowPlayer, target.GetName(), target.GetGUID().ToString()); + else + handler.SendSysMessage(CypherStrings.MovegensFollowCreature, target.GetName(), target.GetGUID().ToString()); + break; + } + case MovementGeneratorType.Home: + { + if (unit.IsTypeId(TypeId.Unit)) + handler.SendSysMessage(CypherStrings.MovegensHomeCreature, x, y, z); + else + handler.SendSysMessage(CypherStrings.MovegensHomePlayer); + break; + } + case MovementGeneratorType.Flight: + handler.SendSysMessage(CypherStrings.MovegensFlight); + break; + case MovementGeneratorType.Point: + { + handler.SendSysMessage(CypherStrings.MovegensPoint, x, y, z); + break; + } + case MovementGeneratorType.Fleeing: + handler.SendSysMessage(CypherStrings.MovegensFear); + break; + case MovementGeneratorType.Distract: + handler.SendSysMessage(CypherStrings.MovegensDistract); + break; + case MovementGeneratorType.Effect: + handler.SendSysMessage(CypherStrings.MovegensEffect); + break; + default: + handler.SendSysMessage(CypherStrings.MovegensUnknown, movementGenerator.GetMovementGeneratorType()); + break; + } + } + return true; + } + + // mute player for some times + [CommandNonGroup("mute", RBACPermissions.CommandMute, true)] + static bool HandleMuteCommand(StringArguments args, CommandHandler handler) + { + string nameStr; + string delayStr; + handler.ExtractOptFirstArg(args, out nameStr, out delayStr); + if (string.IsNullOrEmpty(delayStr)) + return false; + + string muteReason = args.NextString(); + string muteReasonStr = "No reason"; + if (muteReason != null) + muteReasonStr = muteReason; + + Player target; + ObjectGuid targetGuid; + string targetName; + if (!handler.ExtractPlayerTarget(new StringArguments(nameStr), out target, out targetGuid, out targetName)) + return false; + + uint accountId = target ? target.GetSession().GetAccountId() : Global.CharacterCacheStorage.GetCharacterAccountIdByGuid(targetGuid); + + // find only player from same account if any + if (!target) + { + WorldSession session = Global.WorldMgr.FindSession(accountId); + if (session != null) + target = session.GetPlayer(); + } + + if (!uint.TryParse(delayStr, out uint notSpeakTime)) + return false; + + // must have strong lesser security level + if (handler.HasLowerSecurity(target, targetGuid, true)) + return false; + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_MUTE_TIME); + string muteBy; + if (handler.GetSession() != null) + muteBy = handler.GetSession().GetPlayerName(); + else + muteBy = "Console"; + + if (target) + { + // Target is online, mute will be in effect right away. + long muteTime = Time.UnixTime + notSpeakTime * Time.Minute; + target.GetSession().m_muteTime = muteTime; + stmt.AddValue(0, muteTime); + string nameLink = handler.PlayerLink(targetName); + + if (WorldConfig.GetBoolValue(WorldCfg.ShowMuteInWorld)) + { + Global.WorldMgr.SendWorldText(CypherStrings.CommandMutemessageWorld, (handler.GetSession() != null ? handler.GetSession().GetPlayerName() : "Server"), nameLink, notSpeakTime, muteReasonStr); + target.SendSysMessage(CypherStrings.YourChatDisabled, notSpeakTime, muteBy, muteReasonStr); + } + else + { + target.SendSysMessage(CypherStrings.YourChatDisabled, notSpeakTime, muteBy, muteReasonStr); + } + } + else + { + // Target is offline, mute will be in effect starting from the next login. + int muteTime = -(int)(notSpeakTime * Time.Minute); + stmt.AddValue(0, muteTime); + } + + stmt.AddValue(1, muteReasonStr); + stmt.AddValue(2, muteBy); + stmt.AddValue(3, accountId); + DB.Login.Execute(stmt); + string nameLink_ = handler.PlayerLink(targetName); + + if (WorldConfig.GetBoolValue(WorldCfg.ShowMuteInWorld) && !target) + Global.WorldMgr.SendWorldText(CypherStrings.CommandMutemessageWorld, handler.GetSession().GetPlayerName(), nameLink_, notSpeakTime, muteReasonStr); + else + handler.SendSysMessage(target ? CypherStrings.YouDisableChat : CypherStrings.CommandDisableChatDelayed, nameLink_, notSpeakTime, muteReasonStr); + return true; + } + + // mutehistory command + [CommandNonGroup("mutehistory", RBACPermissions.CommandMutehistory, true)] + static bool HandleMuteInfoCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + string accountName = args.NextString(""); + if (accountName.IsEmpty()) + return false; + + uint accountId = Global.AccountMgr.GetId(accountName); + if (accountId == 0) + { + handler.SendSysMessage(CypherStrings.AccountNotExist, accountName); + return false; + } + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_MUTE_INFO); + stmt.AddValue(0, accountId); + + SQLResult result = DB.Login.Query(stmt); + if (result.IsEmpty()) + { + handler.SendSysMessage(CypherStrings.CommandMutehistoryEmpty, accountName); + return true; + } + + handler.SendSysMessage(CypherStrings.CommandMutehistory, accountName); + do + { + // we have to manually set the string for mutedate + long sqlTime = result.Read(0); + + // set it to string + string buffer = Time.UnixTimeToDateTime(sqlTime).ToShortTimeString(); + + handler.SendSysMessage(CypherStrings.CommandMutehistoryOutput, buffer, result.Read(1), result.Read(2), result.Read(3)); + } while (result.NextRow()); + + return true; + } + [CommandNonGroup("neargrave", RBACPermissions.CommandNeargrave)] - static bool NearGrave(StringArguments args, CommandHandler handler) + static bool HandleNearGraveCommand(StringArguments args, CommandHandler handler) { string px2 = args.NextString(); Team team; @@ -1154,129 +1472,6 @@ namespace Game.Chat return true; } - [CommandNonGroup("showarea", RBACPermissions.CommandShowarea)] - static bool ShowArea(StringArguments args, CommandHandler handler) - { - if (args.Empty()) - return false; - - Player playerTarget = handler.GetSelectedPlayer(); - if (!playerTarget) - { - handler.SendSysMessage(CypherStrings.NoCharSelected); - return false; - } - - AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(args.NextUInt32()); - if (area == null) - { - handler.SendSysMessage(CypherStrings.BadValue); - return false; - } - - if (area.AreaBit < 0) - { - handler.SendSysMessage(CypherStrings.BadValue); - return false; - } - - uint offset = (uint)area.AreaBit / 64; - if (offset >= PlayerConst.ExploredZonesSize) - { - handler.SendSysMessage(CypherStrings.BadValue); - return false; - } - - ulong val = 1ul << (area.AreaBit % 64); - playerTarget.AddExploredZones(offset, val); - - handler.SendSysMessage(CypherStrings.ExploreArea); - return true; - } - - [CommandNonGroup("hidearea", RBACPermissions.CommandHidearea)] - static bool HideArea(StringArguments args, CommandHandler handler) - { - if (args.Empty()) - return false; - - Player playerTarget = handler.GetSelectedPlayer(); - if (!playerTarget) - { - handler.SendSysMessage(CypherStrings.NoCharSelected); - return false; - } - - AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(args.NextUInt32()); - if (area == null) - { - handler.SendSysMessage(CypherStrings.BadValue); - return false; - } - - if (area.AreaBit < 0) - { - handler.SendSysMessage(CypherStrings.BadValue); - return false; - } - - uint offset = (uint)area.AreaBit / 64; - if (offset >= PlayerConst.ExploredZonesSize) - { - handler.SendSysMessage(CypherStrings.BadValue); - return false; - } - - uint val = (1u << (area.AreaBit % 64)); - playerTarget.RemoveExploredZones(offset, val); - - handler.SendSysMessage(CypherStrings.UnexploreArea); - return true; - } - - [CommandNonGroup("bank", RBACPermissions.CommandBank)] - static bool Bank(StringArguments args, CommandHandler handler) - { - handler.GetSession().SendShowBank(handler.GetSession().GetPlayer().GetGUID()); - return true; - } - - [CommandNonGroup("wchange", RBACPermissions.CommandWchange)] - static bool WChange(StringArguments args, CommandHandler handler) - { - if (args.Empty()) - return false; - - // Weather is OFF - if (!WorldConfig.GetBoolValue(WorldCfg.Weather)) - { - handler.SendSysMessage(CypherStrings.WeatherDisabled); - return false; - } - - // *Change the weather of a cell - //0 to 3, 0: fine, 1: rain, 2: snow, 3: sand - if (!uint.TryParse(args.NextString(), out uint type)) - return false; - - //0 to 1, sending -1 is instand good weather - if (!float.TryParse(args.NextString(), out float grade)) - return false; - - Player player = handler.GetSession().GetPlayer(); - uint zoneid = player.GetZoneId(); - - Weather weather = player.GetMap().GetOrGenerateZoneDefaultWeather(zoneid); - if (weather == null) - { - handler.SendSysMessage(CypherStrings.NoWeather); - return false; - } - - weather.SetWeather((WeatherType)type, grade); - return true; - } - [CommandNonGroup("pinfo", RBACPermissions.CommandPinfo, true)] static bool HandlePInfoCommand(StringArguments args, CommandHandler handler) { @@ -1625,491 +1820,97 @@ namespace Game.Chat return true; } - [CommandNonGroup("respawn", RBACPermissions.CommandRespawn)] - static bool Respawn(StringArguments args, CommandHandler handler) - { - Player player = handler.GetSession().GetPlayer(); - - // accept only explicitly selected target (not implicitly self targeting case) - Creature target = !player.GetTarget().IsEmpty() ? handler.GetSelectedCreature() : null; - if (target) - { - if (target.IsPet()) - { - handler.SendSysMessage(CypherStrings.SelectCreature); - return false; - } - - if (target.IsDead()) - target.Respawn(); - return true; - } - - var worker = new WorldObjectWorker(player, new RespawnDo()); - Cell.VisitGridObjects(player, worker, player.GetGridActivationRange()); - - return true; - } - - // mute player for some times - [CommandNonGroup("mute", RBACPermissions.CommandMute, true)] - static bool Mute(StringArguments args, CommandHandler handler) - { - string nameStr; - string delayStr; - handler.ExtractOptFirstArg(args, out nameStr, out delayStr); - if (string.IsNullOrEmpty(delayStr)) - return false; - - string muteReason = args.NextString(); - string muteReasonStr = "No reason"; - if (muteReason != null) - muteReasonStr = muteReason; - - Player target; - ObjectGuid targetGuid; - string targetName; - if (!handler.ExtractPlayerTarget(new StringArguments(nameStr), out target, out targetGuid, out targetName)) - return false; - - uint accountId = target ? target.GetSession().GetAccountId() : Global.CharacterCacheStorage.GetCharacterAccountIdByGuid(targetGuid); - - // find only player from same account if any - if (!target) - { - WorldSession session = Global.WorldMgr.FindSession(accountId); - if (session != null) - target = session.GetPlayer(); - } - - if (!uint.TryParse(delayStr, out uint notSpeakTime)) - return false; - - // must have strong lesser security level - if (handler.HasLowerSecurity(target, targetGuid, true)) - return false; - - PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_MUTE_TIME); - string muteBy; - if (handler.GetSession() != null) - muteBy = handler.GetSession().GetPlayerName(); - else - muteBy = "Console"; - - if (target) - { - // Target is online, mute will be in effect right away. - long muteTime = Time.UnixTime + notSpeakTime * Time.Minute; - target.GetSession().m_muteTime = muteTime; - stmt.AddValue(0, muteTime); - string nameLink = handler.PlayerLink(targetName); - - if (WorldConfig.GetBoolValue(WorldCfg.ShowMuteInWorld)) - { - Global.WorldMgr.SendWorldText(CypherStrings.CommandMutemessageWorld, (handler.GetSession() != null ? handler.GetSession().GetPlayerName() : "Server"), nameLink, notSpeakTime, muteReasonStr); - target.SendSysMessage(CypherStrings.YourChatDisabled, notSpeakTime, muteBy, muteReasonStr); - } - else - { - target.SendSysMessage(CypherStrings.YourChatDisabled, notSpeakTime, muteBy, muteReasonStr); - } - } - else - { - // Target is offline, mute will be in effect starting from the next login. - int muteTime = -(int)(notSpeakTime * Time.Minute); - stmt.AddValue(0, muteTime); - } - - stmt.AddValue(1, muteReasonStr); - stmt.AddValue(2, muteBy); - stmt.AddValue(3, accountId); - DB.Login.Execute(stmt); - string nameLink_ = handler.PlayerLink(targetName); - - if (WorldConfig.GetBoolValue(WorldCfg.ShowMuteInWorld) && !target) - Global.WorldMgr.SendWorldText(CypherStrings.CommandMutemessageWorld, handler.GetSession().GetPlayerName(), nameLink_, notSpeakTime, muteReasonStr); - else - handler.SendSysMessage(target ? CypherStrings.YouDisableChat : CypherStrings.CommandDisableChatDelayed, nameLink_, notSpeakTime, muteReasonStr); - return true; - } - - // unmute player - [CommandNonGroup("unmute", RBACPermissions.CommandUnmute, true)] - static bool UnMute(StringArguments args, CommandHandler handler) - { - Player target; - ObjectGuid targetGuid; - string targetName; - if (!handler.ExtractPlayerTarget(args, out target, out targetGuid, out targetName)) - return false; - - uint accountId = target ? target.GetSession().GetAccountId() : Global.CharacterCacheStorage.GetCharacterAccountIdByGuid(targetGuid); - - // find only player from same account if any - if (!target) - { - WorldSession session = Global.WorldMgr.FindSession(accountId); - if (session != null) - target = session.GetPlayer(); - } - - // must have strong lesser security level - if (handler.HasLowerSecurity(target, targetGuid, true)) - return false; - - if (target) - { - if (target.GetSession().CanSpeak()) - { - handler.SendSysMessage(CypherStrings.ChatAlreadyEnabled); - return false; - } - - target.GetSession().m_muteTime = 0; - } - - PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_MUTE_TIME); - stmt.AddValue(0, 0); - stmt.AddValue(1, ""); - stmt.AddValue(2, ""); - stmt.AddValue(3, accountId); - DB.Login.Execute(stmt); - - if (target) - target.SendSysMessage(CypherStrings.YourChatEnabled); - - string nameLink = handler.PlayerLink(targetName); - - handler.SendSysMessage(CypherStrings.YouEnableChat, nameLink); - - return true; - } - - // mutehistory command - [CommandNonGroup("mutehistory", RBACPermissions.CommandMutehistory, true)] - static bool HandleMuteInfoCommand(StringArguments args, CommandHandler handler) + [CommandNonGroup("playall", RBACPermissions.CommandPlayall)] + static bool HandlePlayAllCommand(StringArguments args, CommandHandler handler) { if (args.Empty()) return false; - string accountName = args.NextString(""); - if (accountName.IsEmpty()) - return false; + uint soundId = args.NextUInt32(); - uint accountId = Global.AccountMgr.GetId(accountName); - if (accountId == 0) + if (!CliDB.SoundKitStorage.ContainsKey(soundId)) { - handler.SendSysMessage(CypherStrings.AccountNotExist, accountName); + handler.SendSysMessage(CypherStrings.SoundNotExist, soundId); return false; } - PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_MUTE_INFO); - stmt.AddValue(0, accountId); - - SQLResult result = DB.Login.Query(stmt); - if (result.IsEmpty()) - { - handler.SendSysMessage(CypherStrings.CommandMutehistoryEmpty, accountName); - return true; - } - - handler.SendSysMessage(CypherStrings.CommandMutehistory, accountName); - do - { - // we have to manually set the string for mutedate - long sqlTime = result.Read(0); - - // set it to string - string buffer = Time.UnixTimeToDateTime(sqlTime).ToShortTimeString(); - - handler.SendSysMessage(CypherStrings.CommandMutehistoryOutput, buffer, result.Read(1), result.Read(2), result.Read(3)); - } while (result.NextRow()); + Global.WorldMgr.SendGlobalMessage(new PlaySound(handler.GetSession().GetPlayer().GetGUID(), soundId)); + handler.SendSysMessage(CypherStrings.CommandPlayedToAll, soundId); return true; } - [CommandNonGroup("movegens", RBACPermissions.CommandMovegens)] - static bool MoveGens(StringArguments args, CommandHandler handler) + [CommandNonGroup("possess", RBACPermissions.CommandPossess)] + static bool HandlePossessCommand(StringArguments args, CommandHandler handler) { Unit unit = handler.GetSelectedUnit(); if (!unit) - { - handler.SendSysMessage(CypherStrings.SelectCharOrCreature); - return false; - } - handler.SendSysMessage(CypherStrings.MovegensList, (unit.IsTypeId(TypeId.Player) ? "Player" : "Creature"), unit.GetGUID().ToString()); - - MotionMaster motionMaster = unit.GetMotionMaster(); - float x, y, z; - motionMaster.GetDestination(out x, out y, out z); - - for (byte i = 0; i < (int)MovementSlot.Max; ++i) - { - IMovementGenerator movementGenerator = motionMaster.GetMotionSlot(i); - if (movementGenerator == null) - { - handler.SendSysMessage("Empty"); - continue; - } - - switch (movementGenerator.GetMovementGeneratorType()) - { - case MovementGeneratorType.Idle: - handler.SendSysMessage(CypherStrings.MovegensIdle); - break; - case MovementGeneratorType.Random: - handler.SendSysMessage(CypherStrings.MovegensRandom); - break; - case MovementGeneratorType.Waypoint: - handler.SendSysMessage(CypherStrings.MovegensWaypoint); - break; - case MovementGeneratorType.Confused: - handler.SendSysMessage(CypherStrings.MovegensConfused); - break; - case MovementGeneratorType.Chase: - { - Unit target; - if (unit.IsTypeId(TypeId.Player)) - target = ((ChaseMovementGenerator)movementGenerator).GetTarget(); - else - target = ((ChaseMovementGenerator)movementGenerator).GetTarget(); - - if (!target) - handler.SendSysMessage(CypherStrings.MovegensChaseNull); - else if (target.IsTypeId(TypeId.Player)) - handler.SendSysMessage(CypherStrings.MovegensChasePlayer, target.GetName(), target.GetGUID().ToString()); - else - handler.SendSysMessage(CypherStrings.MovegensChaseCreature, target.GetName(), target.GetGUID().ToString()); - break; - } - case MovementGeneratorType.Follow: - { - Unit target; - if (unit.IsTypeId(TypeId.Player)) - target = ((FollowMovementGenerator)movementGenerator).GetTarget(); - else - target = ((FollowMovementGenerator)movementGenerator).GetTarget(); - - if (!target) - handler.SendSysMessage(CypherStrings.MovegensFollowNull); - else if (target.IsTypeId(TypeId.Player)) - handler.SendSysMessage(CypherStrings.MovegensFollowPlayer, target.GetName(), target.GetGUID().ToString()); - else - handler.SendSysMessage(CypherStrings.MovegensFollowCreature, target.GetName(), target.GetGUID().ToString()); - break; - } - case MovementGeneratorType.Home: - { - if (unit.IsTypeId(TypeId.Unit)) - handler.SendSysMessage(CypherStrings.MovegensHomeCreature, x, y, z); - else - handler.SendSysMessage(CypherStrings.MovegensHomePlayer); - break; - } - case MovementGeneratorType.Flight: - handler.SendSysMessage(CypherStrings.MovegensFlight); - break; - case MovementGeneratorType.Point: - { - handler.SendSysMessage(CypherStrings.MovegensPoint, x, y, z); - break; - } - case MovementGeneratorType.Fleeing: - handler.SendSysMessage(CypherStrings.MovegensFear); - break; - case MovementGeneratorType.Distract: - handler.SendSysMessage(CypherStrings.MovegensDistract); - break; - case MovementGeneratorType.Effect: - handler.SendSysMessage(CypherStrings.MovegensEffect); - break; - default: - handler.SendSysMessage(CypherStrings.MovegensUnknown, movementGenerator.GetMovementGeneratorType()); - break; - } - } + handler.GetSession().GetPlayer().CastSpell(unit, 530, true); return true; } - [CommandNonGroup("cometome", RBACPermissions.CommandCometome)] - static bool HandleComeToMeCommand(StringArguments args, CommandHandler handler) + [CommandNonGroup("pvpstats", RBACPermissions.CommandPvpstats, true)] + static bool HandlePvPstatsCommand(StringArguments args, CommandHandler handler) { - Creature caster = handler.GetSelectedCreature(); - if (!caster) + if (WorldConfig.GetBoolValue(WorldCfg.BattlegroundStoreStatisticsEnable)) { - handler.SendSysMessage(CypherStrings.SelectCreature); - return false; - } + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PVPSTATS_FACTIONS_OVERALL); + SQLResult result = DB.Characters.Query(stmt); - Player player = handler.GetSession().GetPlayer(); - caster.GetMotionMaster().MovePoint(0, player.GetPositionX(), player.GetPositionY(), player.GetPositionZ()); - - return true; - } - - [CommandNonGroup("damage", RBACPermissions.CommandDamage)] - static bool Damage(StringArguments args, CommandHandler handler) - { - if (args.Empty()) - return false; - - string str = args.NextString(); - - if (str == "go") - { - ulong guidLow = args.NextUInt64(); - if (guidLow == 0) + if (!result.IsEmpty()) { - handler.SendSysMessage(CypherStrings.BadValue); - return false; - } + uint horde_victories = result.Read(1); - int damage = args.NextInt32(); - if (damage == 0) - { - handler.SendSysMessage(CypherStrings.BadValue); - return false; - } - Player player = handler.GetSession().GetPlayer(); - if (player) - { - GameObject go = handler.GetObjectFromPlayerMapByDbGuid(guidLow); - if (!go) - { - handler.SendSysMessage(CypherStrings.CommandObjnotfound, guidLow); + if (!(result.NextRow())) return false; - } - if (!go.IsDestructibleBuilding()) - { - handler.SendSysMessage(CypherStrings.InvalidGameobjectType); - return false; - } + uint alliance_victories = result.Read(1); - go.ModifyHealth(-damage, player); - handler.SendSysMessage(CypherStrings.GameobjectDamaged, go.GetName(), guidLow, -damage, go.GetGoValue().Building.Health); + handler.SendSysMessage(CypherStrings.Pvpstats, alliance_victories, horde_victories); } - - return true; - } - - Unit target = handler.GetSelectedUnit(); - if (!target || handler.GetSession().GetPlayer().GetTarget().IsEmpty()) - { - handler.SendSysMessage(CypherStrings.SelectCharOrCreature); - return false; - } - Player player_ = target.ToPlayer(); - if (player_) - if (handler.HasLowerSecurity(player_, ObjectGuid.Empty, false)) + else return false; - - if (!target.IsAlive()) - return true; - - if (!int.TryParse(str, out int damage_int)) - return false; - - if (damage_int <= 0) - return true; - - uint damage_ = (uint)damage_int; - - string schoolStr = args.NextString(); - - Player attacker = handler.GetSession().GetPlayer(); - - // flat melee damage without resistence/etc reduction - if (string.IsNullOrEmpty(schoolStr)) - { - attacker.DealDamage(target, damage_, null, DamageEffectType.Direct, SpellSchoolMask.Normal, null, false); - if (target != attacker) - attacker.SendAttackStateUpdate(HitInfo.AffectsVictim, target, SpellSchoolMask.Normal, damage_, 0, 0, VictimState.Hit, 0); - return true; } + else + handler.SendSysMessage(CypherStrings.PvpstatsDisabled); - if (!int.TryParse(schoolStr, out int school) || school >= (int)SpellSchools.Max) - return false; - - SpellSchoolMask schoolmask = (SpellSchoolMask)(1 << school); - - if (attacker.IsDamageReducedByArmor(schoolmask)) - damage_ = attacker.CalcArmorReducedDamage(handler.GetPlayer(), target, damage_, null, WeaponAttackType.BaseAttack); - - string spellStr = args.NextString(); - - // melee damage by specific school - if (string.IsNullOrEmpty(spellStr)) - { - DamageInfo dmgInfo = new DamageInfo(attacker, target, damage_, null, schoolmask, DamageEffectType.SpellDirect, WeaponAttackType.BaseAttack); - attacker.CalcAbsorbResist(dmgInfo); - - if (dmgInfo.GetDamage() == 0) - return true; - - damage_ = dmgInfo.GetDamage(); - - uint absorb = dmgInfo.GetAbsorb(); - uint resist = dmgInfo.GetResist(); - attacker.DealDamageMods(target, ref damage_, ref absorb); - attacker.DealDamage(target, damage_, null, DamageEffectType.Direct, schoolmask, null, false); - attacker.SendAttackStateUpdate(HitInfo.AffectsVictim, target, schoolmask, damage_, absorb, resist, VictimState.Hit, 0); - return true; - } - - // non-melee damage - // number or [name] Shift-click form |color|Hspell:spell_id|h[name]|h|r or Htalent form - uint spellid = handler.ExtractSpellIdFromLink(args); - if (spellid == 0) - return false; - - SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellid, attacker.GetMap().GetDifficultyID()); - if (spellInfo == null) - return false; - - SpellNonMeleeDamage damageInfo = new SpellNonMeleeDamage(attacker, target, spellInfo, spellInfo.GetSpellXSpellVisualId(attacker), spellInfo.SchoolMask); - damageInfo.damage = damage_; - attacker.DealDamageMods(damageInfo.target, ref damageInfo.damage, ref damageInfo.absorb); - target.DealSpellDamage(damageInfo, true); - target.SendSpellNonMeleeDamageLog(damageInfo); return true; } - [CommandNonGroup("combatstop", RBACPermissions.CommandCombatstop, true)] - static bool CombatStop(StringArguments args, CommandHandler handler) + // Teleport player to last position + [CommandNonGroup("recall", RBACPermissions.CommandRecall)] + static bool HandleRecallCommand(StringArguments args, CommandHandler handler) { - Player target = null; - - if (!args.Empty()) - { - target = Global.ObjAccessor.FindPlayerByName(args.NextString()); - if (!target) - { - handler.SendSysMessage(CypherStrings.PlayerNotFound); - return false; - } - } - - if (!target) - { - if (!handler.ExtractPlayerTarget(args, out target)) - return false; - } + Player target; + if (!handler.ExtractPlayerTarget(args, out target)) + return false; // check online security if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) return false; - target.CombatStop(); - target.GetHostileRefManager().DeleteReferences(); + if (target.IsBeingTeleported()) + { + handler.SendSysMessage(CypherStrings.IsTeleported, handler.GetNameLink(target)); + + return false; + } + + // stop flight if need + if (target.IsInFlight()) + { + target.GetMotionMaster().MovementExpired(); + target.CleanupAfterTaxiFlight(); + } + + target.Recall(); return true; } [CommandNonGroup("repairitems", RBACPermissions.CommandRepairitems, true)] - static bool RepairItems(StringArguments args, CommandHandler handler) + static bool HandleRepairitemsCommand(StringArguments args, CommandHandler handler) { Player target; if (!handler.ExtractPlayerTarget(args, out target)) @@ -2129,96 +1930,265 @@ namespace Game.Chat return true; } - [CommandNonGroup("freeze", RBACPermissions.CommandFreeze)] - static bool HandleFreezeCommand(StringArguments args, CommandHandler handler) + [CommandNonGroup("respawn", RBACPermissions.CommandRespawn)] + static bool HandleRespawnCommand(StringArguments args, CommandHandler handler) { - Player player = handler.GetSelectedPlayer(); // Selected player, if any. Might be null. - int freezeDuration = 0; // Freeze Duration (in seconds) - bool canApplyFreeze = false; // Determines if every possible argument is set so Freeze can be applied - bool getDurationFromConfig = false; // If there's no given duration, we'll retrieve the world cfg value later + Player player = handler.GetSession().GetPlayer(); - if (args.Empty()) + // accept only explicitly selected target (not implicitly self targeting case) + Creature target = !player.GetTarget().IsEmpty() ? handler.GetSelectedCreature() : null; + if (target) { - // Might have a selected player. We'll check it later - // Get the duration from world cfg - getDurationFromConfig = true; + if (target.IsPet()) + { + handler.SendSysMessage(CypherStrings.SelectCreature); + return false; + } + + if (target.IsDead()) + target.Respawn(); + return true; + } + + // First handle any creatures that still have a corpse around + var worker = new WorldObjectWorker(player, new RespawnDo()); + Cell.VisitGridObjects(player, worker, player.GetGridActivationRange()); + + // Now handle any that had despawned, but had respawn time logged. + List data = new List(); + player.GetMap().GetRespawnInfo(data, SpawnObjectTypeMask.All, 0); + if (!data.Empty()) + { + uint gridId = GridDefines.ComputeGridCoord(player.GetPositionX(), player.GetPositionY()).GetId(); + foreach (RespawnInfo info in data) + if (info.gridId == gridId) + player.GetMap().RemoveRespawnTime(info, true); + } + + return true; + } + + [CommandNonGroup("revive", RBACPermissions.CommandRevive, true)] + static bool HandleReviveCommand(StringArguments args, CommandHandler handler) + { + Player target; + ObjectGuid targetGuid; + if (!handler.ExtractPlayerTarget(args, out target, out targetGuid)) + return false; + + if (target != null) + { + target.ResurrectPlayer(0.5f); + target.SpawnCorpseBones(); + target.SaveToDB(); + } + else + Player.OfflineResurrect(targetGuid, null); + + return true; + } + + // Save all players in the world + [CommandNonGroup("saveall", RBACPermissions.CommandSaveall, true)] + static bool HandleSaveAllCommand(StringArguments args, CommandHandler handler) + { + Global.ObjAccessor.SaveAllPlayers(); + handler.SendSysMessage(CypherStrings.PlayersSaved); + return true; + } + + [CommandNonGroup("save", RBACPermissions.CommandSave)] + static bool HandleSaveCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + + // save GM account without delay and output message + if (handler.GetSession().HasPermission(RBACPermissions.CommandsSaveWithoutDelay)) + { + Player target = handler.GetSelectedPlayer(); + if (target) + target.SaveToDB(); + else + player.SaveToDB(); + handler.SendSysMessage(CypherStrings.PlayerSaved); + return true; + } + + // save if the player has last been saved over 20 seconds ago + uint saveInterval = WorldConfig.GetUIntValue(WorldCfg.IntervalSave); + if (saveInterval == 0 || (saveInterval > 20 * Time.InMilliseconds && player.GetSaveTimer() <= saveInterval - 20 * Time.InMilliseconds)) + player.SaveToDB(); + + return true; + } + + [CommandNonGroup("showarea", RBACPermissions.CommandShowarea)] + static bool HandleShowAreaCommand(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + Player playerTarget = handler.GetSelectedPlayer(); + if (!playerTarget) + { + handler.SendSysMessage(CypherStrings.NoCharSelected); + return false; + } + + AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(args.NextUInt32()); + if (area == null) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + if (area.AreaBit < 0) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + uint offset = (uint)area.AreaBit / 64; + if (offset >= PlayerConst.ExploredZonesSize) + { + handler.SendSysMessage(CypherStrings.BadValue); + return false; + } + + ulong val = 1ul << (area.AreaBit % 64); + playerTarget.AddExploredZones(offset, val); + + handler.SendSysMessage(CypherStrings.ExploreArea); + return true; + } + + // Summon Player + [CommandNonGroup("summon", RBACPermissions.CommandSummon)] + static bool HandleSummonCommand(StringArguments args, CommandHandler handler) + { + Player target; + ObjectGuid targetGuid; + string targetName; + if (!handler.ExtractPlayerTarget(args, out target, out targetGuid, out targetName)) + return false; + + Player _player = handler.GetSession().GetPlayer(); + if (target == _player || targetGuid == _player.GetGUID()) + { + handler.SendSysMessage(CypherStrings.CantTeleportSelf); + + return false; + } + + if (target) + { + string nameLink = handler.PlayerLink(targetName); + // check online security + if (handler.HasLowerSecurity(target, ObjectGuid.Empty)) + return false; + + if (target.IsBeingTeleported()) + { + handler.SendSysMessage(CypherStrings.IsTeleported, nameLink); + + return false; + } + + Map map = _player.GetMap(); + if (map.IsBattlegroundOrArena()) + { + // only allow if gm mode is on + if (!_player.IsGameMaster()) + { + handler.SendSysMessage(CypherStrings.CannotGoToBgGm, nameLink); + return false; + } + // if both players are in different bgs + else if (target.GetBattlegroundId() != 0 && _player.GetBattlegroundId() != target.GetBattlegroundId()) + target.LeaveBattleground(false); // Note: should be changed so target gets no Deserter debuff + + // all's well, set bg id + // when porting out from the bg, it will be reset to 0 + target.SetBattlegroundId(_player.GetBattlegroundId(), _player.GetBattlegroundTypeId()); + // remember current position as entry point for return at bg end teleportation + if (!target.GetMap().IsBattlegroundOrArena()) + target.SetBattlegroundEntryPoint(); + } + else if (map.Instanceable()) + { + Map targetMap = target.GetMap(); + + Player targetGroupLeader = null; + Group targetGroup = target.GetGroup(); + if (targetGroup != null) + targetGroupLeader = Global.ObjAccessor.GetPlayer(map, targetGroup.GetLeaderGUID()); + + // check if far teleport is allowed + if (targetGroupLeader == null || (targetGroupLeader.GetMapId() != map.GetId()) || (targetGroupLeader.GetInstanceId() != map.GetInstanceId())) + { + if ((targetMap.GetId() != map.GetId()) || (targetMap.GetInstanceId() != map.GetInstanceId())) + { + handler.SendSysMessage(CypherStrings.CannotSummonToInst); + return false; + } + } + + // check if we're already in a different instance of the same map + if ((targetMap.GetId() == map.GetId()) && (targetMap.GetInstanceId() != map.GetInstanceId())) + { + handler.SendSysMessage(CypherStrings.CannotSummonInstInst, nameLink); + return false; + } + } + + handler.SendSysMessage(CypherStrings.Summoning, nameLink, ""); + if (handler.NeedReportToTarget(target)) + target.SendSysMessage(CypherStrings.SummonedBy, handler.PlayerLink(_player.GetName())); + + // stop flight if need + if (target.IsInFlight()) + { + target.GetMotionMaster().MovementExpired(); + target.CleanupAfterTaxiFlight(); + } + // save only in non-flight case + else + target.SaveRecallPosition(); + + // before GM + float x, y, z; + _player.GetClosePoint(out x, out y, out z, target.GetCombatReach()); + target.TeleportTo(_player.GetMapId(), x, y, z, target.GetOrientation()); + PhasingHandler.InheritPhaseShift(target, _player); + target.UpdateObjectVisibility(); } else { - // Get the args that we might have (up to 2) - string arg1 = args.NextString(); - string arg2 = args.NextString(); + // check offline security + if (handler.HasLowerSecurity(null, targetGuid)) + return false; - // Analyze them to see if we got either a playerName or duration or both - if (!arg1.IsEmpty()) - { - if (arg1.IsNumber()) - { - // case 2: .freeze duration - // We have a selected player. We'll check him later - if (!int.TryParse(arg1, out freezeDuration)) - return false; - canApplyFreeze = true; - } - else - { - // case 3 or 4: .freeze player duration | .freeze player - // find the player - string name = arg1; - ObjectManager.NormalizePlayerName(ref name); - player = Global.ObjAccessor.FindPlayerByName(name); - // Check if we have duration set - if (!arg2.IsEmpty() && arg2.IsNumber()) - { - if (!int.TryParse(arg2, out freezeDuration)) - return false; - canApplyFreeze = true; - } - else - getDurationFromConfig = true; - } - } + string nameLink = handler.PlayerLink(targetName); + + handler.SendSysMessage(CypherStrings.Summoning, nameLink, handler.GetCypherString(CypherStrings.Offline)); + + // in point where GM stay + Player.SavePositionInDB(new WorldLocation(_player.GetMapId(), _player.GetPositionX(), _player.GetPositionY(), _player.GetPositionZ(), _player.GetOrientation()), _player.GetZoneId(), targetGuid); } - // Check if duration needs to be retrieved from config - if (getDurationFromConfig) - { - freezeDuration = WorldConfig.GetIntValue(WorldCfg.GmFreezeDuration); - canApplyFreeze = true; - } + return true; + } - // Player and duration retrieval is over - if (canApplyFreeze) - { - if (!player) // can be null if some previous selection failed - { - handler.SendSysMessage(CypherStrings.CommandFreezeWrong); - return true; - } - else if (player == handler.GetSession().GetPlayer()) - { - // Can't freeze himself - handler.SendSysMessage(CypherStrings.CommandFreezeError); - return true; - } - else // Apply the effect - { - // Add the freeze aura and set the proper duration - // Player combat status and flags are now handled - // in Freeze Spell AuraScript (OnApply) - Aura freeze = player.AddAura(9454, player); - if (freeze != null) - { - if (freezeDuration != 0) - freeze.SetDuration(freezeDuration * Time.InMilliseconds); - handler.SendSysMessage(CypherStrings.CommandFreeze, player.GetName()); - // save player - player.SaveToDB(); - return true; - } - } - } - return false; + [CommandNonGroup("unbindsight", RBACPermissions.CommandUnbindsight)] + static bool HandleUnbindSightCommand(StringArguments args, CommandHandler handler) + { + Player player = handler.GetSession().GetPlayer(); + + if (player.IsPossessing()) + return false; + + player.StopCastingBindSight(); + return true; } [CommandNonGroup("unfreeze", RBACPermissions.CommandUnfreeze)] @@ -2280,76 +2250,60 @@ namespace Game.Chat return true; } - [CommandNonGroup("listfreeze", RBACPermissions.CommandListfreeze)] - static bool ListFreeze(StringArguments args, CommandHandler handler) + // unmute player + [CommandNonGroup("unmute", RBACPermissions.CommandUnmute, true)] + static bool HandleUnmuteCommand(StringArguments args, CommandHandler handler) { - // Get names from DB - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_AURA_FROZEN); - SQLResult result = DB.Characters.Query(stmt); - if (result.IsEmpty()) - { - handler.SendSysMessage(CypherStrings.CommandNoFrozenPlayers); - return true; - } - - // Header of the names - handler.SendSysMessage(CypherStrings.CommandListFreeze); - - // Output of the results - do - { - string player = result.Read(0); - int remaintime = result.Read(1); - // Save the frozen player to update remaining time in case of future .listfreeze uses - // before the frozen state expires - Player frozen = Global.ObjAccessor.FindPlayerByName(player); - if (frozen) - frozen.SaveToDB(); - // Notify the freeze duration - if (remaintime == -1) // Permanent duration - handler.SendSysMessage(CypherStrings.CommandPermaFrozenPlayer, player); - else - // show time left (seconds) - handler.SendSysMessage(CypherStrings.CommandTempFrozenPlayer, player, remaintime / Time.InMilliseconds); - } - while (result.NextRow()); - - return true; - } - - [CommandNonGroup("playall", RBACPermissions.CommandPlayall)] - static bool PlayAll(StringArguments args, CommandHandler handler) - { - if (args.Empty()) + Player target; + ObjectGuid targetGuid; + string targetName; + if (!handler.ExtractPlayerTarget(args, out target, out targetGuid, out targetName)) return false; - uint soundId = args.NextUInt32(); + uint accountId = target ? target.GetSession().GetAccountId() : Global.CharacterCacheStorage.GetCharacterAccountIdByGuid(targetGuid); - if (!CliDB.SoundKitStorage.ContainsKey(soundId)) + // find only player from same account if any + if (!target) { - handler.SendSysMessage(CypherStrings.SoundNotExist, soundId); - return false; + WorldSession session = Global.WorldMgr.FindSession(accountId); + if (session != null) + target = session.GetPlayer(); } - Global.WorldMgr.SendGlobalMessage(new PlaySound(handler.GetSession().GetPlayer().GetGUID(), soundId)); - - handler.SendSysMessage(CypherStrings.CommandPlayedToAll, soundId); - return true; - } - - [CommandNonGroup("possess", RBACPermissions.CommandPossess)] - static bool Possess(StringArguments args, CommandHandler handler) - { - Unit unit = handler.GetSelectedUnit(); - if (!unit) + // must have strong lesser security level + if (handler.HasLowerSecurity(target, targetGuid, true)) return false; - handler.GetSession().GetPlayer().CastSpell(unit, 530, true); + if (target) + { + if (target.GetSession().CanSpeak()) + { + handler.SendSysMessage(CypherStrings.ChatAlreadyEnabled); + return false; + } + + target.GetSession().m_muteTime = 0; + } + + PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_MUTE_TIME); + stmt.AddValue(0, 0); + stmt.AddValue(1, ""); + stmt.AddValue(2, ""); + stmt.AddValue(3, accountId); + DB.Login.Execute(stmt); + + if (target) + target.SendSysMessage(CypherStrings.YourChatEnabled); + + string nameLink = handler.PlayerLink(targetName); + + handler.SendSysMessage(CypherStrings.YouEnableChat, nameLink); + return true; } [CommandNonGroup("unpossess", RBACPermissions.CommandUnpossess)] - static bool UnPossess(StringArguments args, CommandHandler handler) + static bool HandleUnPossessCommand(StringArguments args, CommandHandler handler) { Unit unit = handler.GetSelectedUnit(); if (!unit) @@ -2360,63 +2314,121 @@ namespace Game.Chat return true; } - [CommandNonGroup("bindsight", RBACPermissions.CommandBindsight)] - static bool BindSight(StringArguments args, CommandHandler handler) + [CommandNonGroup("unstuck", RBACPermissions.CommandUnstuck, true)] + static bool HandleUnstuckCommand(StringArguments args, CommandHandler handler) { - Unit unit = handler.GetSelectedUnit(); - if (!unit) - return false; + uint SPELL_UNSTUCK_ID = 7355; + uint SPELL_UNSTUCK_VISUAL = 2683; - handler.GetSession().GetPlayer().CastSpell(unit, 6277, true); - return true; - } - - [CommandNonGroup("unbindsight", RBACPermissions.CommandUnbindsight)] - static bool UnbindSight(StringArguments args, CommandHandler handler) - { - Player player = handler.GetSession().GetPlayer(); - - if (player.IsPossessing()) - return false; - - player.StopCastingBindSight(); - return true; - } - - [CommandNonGroup("mailbox", RBACPermissions.CommandMailbox)] - static bool MailBox(StringArguments args, CommandHandler handler) - { - Player player = handler.GetSession().GetPlayer(); - - handler.GetSession().SendShowMailBox(player.GetGUID()); - return true; - } - - [CommandNonGroup("pvpstats", RBACPermissions.CommandPvpstats, true)] - static bool HandlePvPstatsCommand(StringArguments args, CommandHandler handler) - { - if (WorldConfig.GetBoolValue(WorldCfg.BattlegroundStoreStatisticsEnable)) + // No args required for players + if (handler.GetSession() != null && handler.GetSession().HasPermission(RBACPermissions.CommandsUseUnstuckWithArgs)) { - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PVPSTATS_FACTIONS_OVERALL); - SQLResult result = DB.Characters.Query(stmt); + // 7355: "Stuck" + var player1 = handler.GetSession().GetPlayer(); + if (player1) + player1.CastSpell(player1, SPELL_UNSTUCK_ID, false); + return true; + } + if (args.Empty()) + return false; + + string location_str = "inn"; + string loc = args.NextString(); + if (string.IsNullOrEmpty(loc)) + location_str = loc; + + Player player; + ObjectGuid targetGUID; + if (!handler.ExtractPlayerTarget(args, out player, out targetGUID)) + return false; + + if (!player) + { + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_HOMEBIND); + stmt.AddValue(0, targetGUID.GetCounter()); + SQLResult result = DB.Characters.Query(stmt); if (!result.IsEmpty()) { - uint horde_victories = result.Read(1); - - if (!(result.NextRow())) - return false; - - uint alliance_victories = result.Read(1); - - handler.SendSysMessage(CypherStrings.Pvpstats, alliance_victories, horde_victories); + Player.SavePositionInDB(new WorldLocation(result.Read(0), result.Read(2), result.Read(3), result.Read(4), 0.0f), result.Read(1), targetGUID); + return true; } - else - return false; - } - else - handler.SendSysMessage(CypherStrings.PvpstatsDisabled); + return false; + } + + if (player.IsInFlight() || player.IsInCombat()) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(SPELL_UNSTUCK_ID, Difficulty.None); + if (spellInfo == null) + return false; + + Player caster = handler.GetSession().GetPlayer(); + if (caster) + { + ObjectGuid castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, player.GetMapId(), SPELL_UNSTUCK_ID, player.GetMap().GenerateLowGuid(HighGuid.Cast)); + Spell.SendCastResult(caster, spellInfo, SPELL_UNSTUCK_VISUAL, castId, SpellCastResult.CantDoThatRightNow); + } + + return false; + } + + if (location_str == "inn") + { + var home = player.GetHomebind(); + player.TeleportTo(home.GetMapId(), home.GetPositionX(), home.GetPositionY(), home.GetPositionZ(), player.GetOrientation()); + return true; + } + + if (location_str == "graveyard") + { + player.RepopAtGraveyard(); + return true; + } + + if (location_str == "startzone") + { + player.TeleportTo(player.GetStartPosition()); + return true; + } + + //Not a supported argument + return false; + } + + [CommandNonGroup("wchange", RBACPermissions.CommandWchange)] + static bool HandleChangeWeather(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + // Weather is OFF + if (!WorldConfig.GetBoolValue(WorldCfg.Weather)) + { + handler.SendSysMessage(CypherStrings.WeatherDisabled); + return false; + } + + // *Change the weather of a cell + //0 to 3, 0: fine, 1: rain, 2: snow, 3: sand + if (!uint.TryParse(args.NextString(), out uint type)) + return false; + + //0 to 1, sending -1 is instand good weather + if (!float.TryParse(args.NextString(), out float grade)) + return false; + + Player player = handler.GetSession().GetPlayer(); + uint zoneid = player.GetZoneId(); + + Weather weather = player.GetMap().GetOrGenerateZoneDefaultWeather(zoneid); + if (weather == null) + { + handler.SendSysMessage(CypherStrings.NoWeather); + return false; + } + + weather.SetWeather((WeatherType)type, grade); return true; } } diff --git a/Source/Game/Chat/Commands/NPCCommands.cs b/Source/Game/Chat/Commands/NPCCommands.cs index ece9f0cae..01731cc38 100644 --- a/Source/Game/Chat/Commands/NPCCommands.cs +++ b/Source/Game/Chat/Commands/NPCCommands.cs @@ -33,6 +33,41 @@ namespace Game.Chat [CommandGroup("npc", RBACPermissions.CommandNpc)] class NPCCommands { + [Command("despawngroup", RBACPermissions.CommandNpcDespawngroup)] + static bool HandleNpcDespawnGroup(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + bool deleteRespawnTimes = false; + uint groupId = 0; + + // Decode arguments + string arg = args.NextString(); + while (!arg.IsEmpty()) + { + string thisArg = arg.ToLower(); + if (thisArg == "removerespawntime") + deleteRespawnTimes = true; + else if (thisArg.IsEmpty() || !thisArg.IsNumber()) + return false; + else + groupId = uint.Parse(thisArg); + + arg = args.NextString(); + } + + Player player = handler.GetSession().GetPlayer(); + + if (!Global.ObjectMgr.SpawnGroupDespawn(groupId, player.GetMap(), deleteRespawnTimes)) + { + handler.SendSysMessage(CypherStrings.SpawngroupBadgroup, groupId); + return false; + } + + return true; + } + [Command("evade", RBACPermissions.CommandNpcEvade)] static bool HandleNpcEvadeCommand(StringArguments args, CommandHandler handler) { @@ -96,13 +131,21 @@ namespace Game.Chat uint nativeid = target.GetNativeDisplayId(); uint Entry = target.GetEntry(); - long curRespawnDelay = target.GetRespawnTimeEx() - Time.UnixTime; + long curRespawnDelay = target.GetRespawnCompatibilityMode() ? target.GetRespawnTimeEx() - Time.UnixTime : target.GetMap().GetCreatureRespawnTime(target.GetSpawnId()) - Time.UnixTime; if (curRespawnDelay < 0) curRespawnDelay = 0; + string curRespawnDelayStr = Time.secsToTimeString((ulong)curRespawnDelay, true); string defRespawnDelayStr = Time.secsToTimeString(target.GetRespawnDelay(), true); handler.SendSysMessage(CypherStrings.NpcinfoChar, target.GetSpawnId(), target.GetGUID().ToString(), faction, npcflags, Entry, displayid, nativeid); + if (target.GetCreatureData() != null && target.GetCreatureData().spawnGroupData.groupId != 0) + { + SpawnGroupTemplateData groupData = target.GetCreatureData().spawnGroupData; + if (groupData != null) + handler.SendSysMessage(CypherStrings.SpawninfoGroupId, groupData.name, groupData.groupId, groupData.flags, groupData.isActive); + } + handler.SendSysMessage(CypherStrings.SpawninfoCompatibilityMode, target.GetRespawnCompatibilityMode()); handler.SendSysMessage(CypherStrings.NpcinfoLevel, target.GetLevel()); handler.SendSysMessage(CypherStrings.NpcinfoEquipment, target.GetCurrentEquipmentId(), target.GetOriginalEquipmentId()); handler.SendSysMessage(CypherStrings.NpcinfoHealth, target.GetCreateHealth(), target.GetMaxHealth(), target.GetHealth()); @@ -161,71 +204,60 @@ namespace Game.Chat ulong lowguid; Creature creature = handler.GetSelectedCreature(); - if (!creature) + Player player = handler.GetSession().GetPlayer(); + if (player == null) + return false; + + if (creature != null) + lowguid = creature.GetSpawnId(); + else { // number or [name] Shift-click form |color|Hcreature:creature_guid|h[name]|h|r string cId = handler.ExtractKeyFromLink(args, "Hcreature"); - if (string.IsNullOrEmpty(cId)) + if (cId.IsEmpty()) return false; if (!ulong.TryParse(cId, out lowguid)) return false; + } + // Attempting creature load from DB data CreatureData data = Global.ObjectMgr.GetCreatureData(lowguid); - if (data == null) - { - handler.SendSysMessage(CypherStrings.CommandCreatguidnotfound, lowguid); - return false; - } - - uint map_id = data.mapid; - if (handler.GetSession().GetPlayer().GetMapId() != map_id) - { - handler.SendSysMessage(CypherStrings.CommandCreatureatsamemap, lowguid); - return false; - } - - } - else + if (data == null) { - lowguid = creature.GetSpawnId(); + handler.SendSysMessage(CypherStrings.CommandCreatguidnotfound, lowguid); + return false; } - float x = handler.GetPlayer().GetPositionX(); - float y = handler.GetPlayer().GetPositionY(); - float z = handler.GetPlayer().GetPositionZ(); - float o = handler.GetPlayer().GetOrientation(); - - if (creature) + if (player.GetMapId() != data.spawnPoint.GetMapId()) { - CreatureData data = Global.ObjectMgr.GetCreatureData(creature.GetSpawnId()); - if (data != null) - { - data.posX = x; - data.posY = y; - data.posZ = z; - data.orientation = o; - } - creature.UpdatePosition(x, y, z, o); - creature.GetMotionMaster().Initialize(); - if (creature.IsAlive()) // dead creature will reset movement generator at respawn - { - creature.SetDeathState(DeathState.JustDied); - creature.Respawn(); - } + handler.SendSysMessage(CypherStrings.CommandCreatureatsamemap, lowguid); + return false; } + data.spawnPoint.Relocate(player); + + // update position in DB PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_CREATURE_POSITION); - - stmt.AddValue(0, x); - stmt.AddValue(1, y); - stmt.AddValue(2, z); - stmt.AddValue(3, o); + stmt.AddValue(0, player.GetPositionX()); + stmt.AddValue(1, player.GetPositionY()); + stmt.AddValue(2, player.GetPositionZ()); + stmt.AddValue(3, player.GetOrientation()); stmt.AddValue(4, lowguid); DB.World.Execute(stmt); + // respawn selected creature at the new location + if (creature) + { + if (creature.IsAlive()) + creature.SetDeathState(DeathState.JustDied); + creature.Respawn(true); + if (!creature.GetRespawnCompatibilityMode()) + creature.AddObjectToRemoveList(); + } + handler.SendSysMessage(CypherStrings.CommandCreaturemoved); return true; } @@ -264,7 +296,7 @@ namespace Game.Chat if (creatureTemplate == null) continue; - handler.SendSysMessage(CypherStrings.CreatureListChat, guid, guid, creatureTemplate.Name, x, y, z, mapId); + handler.SendSysMessage(CypherStrings.CreatureListChat, guid, guid, creatureTemplate.Name, x, y, z, mapId, "", ""); ++count; } @@ -386,6 +418,49 @@ namespace Game.Chat return true; } + [Command("spawngroup", RBACPermissions.CommandNpcSpawngroup)] + static bool HandleNpcSpawnGroup(StringArguments args, CommandHandler handler) + { + if (args.Empty()) + return false; + + bool ignoreRespawn = false; + bool force = false; + uint groupId = 0; + + // Decode arguments + string arg = args.NextString(); + while (!arg.IsEmpty()) + { + string thisArg = arg.ToLower(); + if (thisArg == "ignorerespawn") + ignoreRespawn = true; + else if (thisArg == "force") + force = true; + else if (thisArg.IsEmpty() || !thisArg.IsNumber()) + return false; + else + groupId = uint.Parse(thisArg); + + arg = args.NextString(); + } + + Player player = handler.GetSession().GetPlayer(); + + List creatureList = new List(); + if (!Global.ObjectMgr.SpawnGroupSpawn(groupId, player.GetMap(), ignoreRespawn, force, creatureList)) + { + handler.SendSysMessage(CypherStrings.SpawngroupBadgroup, groupId); + return false; + } + + handler.SendSysMessage(CypherStrings.SpawngroupSpawncount, creatureList.Count); + foreach (WorldObject obj in creatureList) + handler.SendSysMessage($"{obj.GetName()} ({obj.GetGUID()})"); + + return true; + } + [Command("tame", RBACPermissions.CommandNpcTame)] static bool HandleNpcTameCommand(StringArguments args, CommandHandler handler) { @@ -581,13 +656,11 @@ namespace Game.Chat { ulong guid = map.GenerateLowGuid(HighGuid.Creature); CreatureData data = Global.ObjectMgr.NewOrExistCreatureData(guid); - data.id = id; - data.posX = chr.GetTransOffsetX(); - data.posY = chr.GetTransOffsetY(); - data.posZ = chr.GetTransOffsetZ(); - data.orientation = chr.GetTransOffsetO(); + data.spawnId = guid; + data.Id = id; + data.spawnPoint.Relocate(chr.GetTransOffsetX(), chr.GetTransOffsetY(), chr.GetTransOffsetZ(), chr.GetTransOffsetO()); // @todo: add phases - + Creature _creature = trans.CreateNPCPassenger(guid, data); _creature.SaveToDB((uint)trans.GetGoInfo().MoTransport.SpawnMap, new List() { map.GetDifficultyID() }); @@ -607,7 +680,7 @@ namespace Game.Chat // To call _LoadGoods(); _LoadQuests(); CreateTrainerSpells() // current "creature" variable is deleted and created fresh new, otherwise old values might trigger asserts or cause undefined behavior creature.CleanupsBeforeDelete(); - creature = Creature.CreateCreatureFromDB(db_guid, map); + creature = Creature.CreateCreatureFromDB(db_guid, map, true, true); if (!creature) return false; @@ -794,7 +867,7 @@ namespace Game.Chat [Command("delete", RBACPermissions.CommandNpcDelete)] static bool HandleNpcDeleteCommand(StringArguments args, CommandHandler handler) { - Creature unit = null; + Creature creature; if (!args.Empty()) { @@ -806,21 +879,27 @@ namespace Game.Chat if (!ulong.TryParse(cId, out ulong guidLow) || guidLow == 0) return false; - unit = handler.GetCreatureFromPlayerMapByDbGuid(guidLow); + creature = handler.GetCreatureFromPlayerMapByDbGuid(guidLow); } else - unit = handler.GetSelectedCreature(); + creature = handler.GetSelectedCreature(); - if (!unit || unit.IsPet() || unit.IsTotem()) + if (!creature || creature.IsPet() || creature.IsTotem()) { handler.SendSysMessage(CypherStrings.SelectCreature); return false; } - // Delete the creature - unit.CombatStop(); - unit.DeleteFromDB(); - unit.AddObjectToRemoveList(); + TempSummon summon = creature.ToTempSummon(); + if (summon != null) + summon.UnSummon(); + else + { + // Delete the creature + creature.CombatStop(); + creature.DeleteFromDB(); + creature.AddObjectToRemoveList(); + } handler.SendSysMessage(CypherStrings.CommandDelcreatmessage); diff --git a/Source/Game/Chat/Commands/WPCommands.cs b/Source/Game/Chat/Commands/WPCommands.cs index 76e82956b..182b71f88 100644 --- a/Source/Game/Chat/Commands/WPCommands.cs +++ b/Source/Game/Chat/Commands/WPCommands.cs @@ -559,7 +559,7 @@ namespace Game.Chat.Commands creature.Dispose(); // To call _LoadGoods(); _LoadQuests(); CreateTrainerSpells(); - creature = Creature.CreateCreatureFromDB(dbGuid, map); + creature = Creature.CreateCreatureFromDB(dbGuid, map, true, true); if (!creature) { handler.SendSysMessage(CypherStrings.WaypointVpNotcreated, 1); @@ -779,7 +779,7 @@ namespace Game.Chat.Commands creature.Dispose(); // To call _LoadGoods(); _LoadQuests(); CreateTrainerSpells(); - creature = Creature.CreateCreatureFromDB(dbGuid, map); + creature = Creature.CreateCreatureFromDB(dbGuid, map, true, true); if (!creature) { handler.SendSysMessage(CypherStrings.WaypointVpNotcreated, id); @@ -844,7 +844,7 @@ namespace Game.Chat.Commands creature.CleanupsBeforeDelete(); creature.Dispose(); - creature = Creature.CreateCreatureFromDB(dbGuid, map); + creature = Creature.CreateCreatureFromDB(dbGuid, map, true, true); if (!creature) { handler.SendSysMessage(CypherStrings.WaypointVpNotcreated, 1); @@ -899,7 +899,7 @@ namespace Game.Chat.Commands creature.CleanupsBeforeDelete(); creature.Dispose(); - creature = Creature.CreateCreatureFromDB(dbGuid, map); + creature = Creature.CreateCreatureFromDB(dbGuid, map, true, true); if (!creature) { handler.SendSysMessage(CypherStrings.WaypointNotcreated, 1); diff --git a/Source/Game/Conditions/ConditionManager.cs b/Source/Game/Conditions/ConditionManager.cs index faa6a7e3c..6fa742fbe 100644 --- a/Source/Game/Conditions/ConditionManager.cs +++ b/Source/Game/Conditions/ConditionManager.cs @@ -1328,7 +1328,7 @@ namespace Game CreatureData creatureData = Global.ObjectMgr.GetCreatureData(cond.ConditionValue3); if (creatureData != null) { - if (cond.ConditionValue2 != 0 && creatureData.id != cond.ConditionValue2) + if (cond.ConditionValue2 != 0 && creatureData.Id != cond.ConditionValue2) { Log.outError(LogFilter.Sql, "{0} has guid {1} set but does not match creature entry ({1}), skipped.", cond.ToString(true), cond.ConditionValue3, cond.ConditionValue2); return false; @@ -1349,10 +1349,10 @@ namespace Game } if (cond.ConditionValue3 != 0) { - GameObjectData goData = Global.ObjectMgr.GetGOData(cond.ConditionValue3); + GameObjectData goData = Global.ObjectMgr.GetGameObjectData(cond.ConditionValue3); if (goData != null) { - if (cond.ConditionValue2 != 0 && goData.id != cond.ConditionValue2) + if (cond.ConditionValue2 != 0 && goData.Id != cond.ConditionValue2) { Log.outError(LogFilter.Sql, "{0} has guid {1} set but does not match gameobject entry ({1}), skipped.", cond.ToString(true), cond.ConditionValue3, cond.ConditionValue2); return false; diff --git a/Source/Game/Entities/Creature/Creature.Fields.cs b/Source/Game/Entities/Creature/Creature.Fields.cs index 71319037c..949ff1a70 100644 --- a/Source/Game/Entities/Creature/Creature.Fields.cs +++ b/Source/Game/Entities/Creature/Creature.Fields.cs @@ -71,6 +71,7 @@ namespace Game.Entities //Formation var CreatureGroup m_formation; bool TriggerJustRespawned; + bool m_respawnCompatibilityMode; public uint[] m_spells = new uint[SharedConst.MaxCreatureSpells]; diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index ff9e4bf63..7b8db4187 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -123,45 +123,62 @@ namespace Game.Entities if (GetDeathState() != DeathState.Corpse) return; - m_corpseRemoveTime = Time.UnixTime; - SetDeathState(DeathState.Dead); - RemoveAllAuras(); - DestroyForNearbyPlayers(); // old UpdateObjectVisibility() - loot.Clear(); - uint respawnDelay = m_respawnDelay; - if (IsAIEnabled) - GetAI().CorpseRemoved(respawnDelay); - - if (destroyForNearbyPlayers) - DestroyForNearbyPlayers(); - - // Should get removed later, just keep "compatibility" with scripts - if (setSpawnTime) - m_respawnTime = Math.Max(Time.UnixTime + respawnDelay, m_respawnTime); - - // if corpse was removed during falling, the falling will continue and override relocation to respawn position - if (IsFalling()) - StopMoving(); - - float x, y, z, o; - GetRespawnPosition(out x, out y, out z, out o); - - // We were spawned on transport, calculate real position - if (IsSpawnedOnTransport()) + if (m_respawnCompatibilityMode) { - Position pos = m_movementInfo.transport.pos; - pos.posX = x; - pos.posY = y; - pos.posZ = z; - pos.SetOrientation(o); + m_corpseRemoveTime = Time.UnixTime; + SetDeathState(DeathState.Dead); + RemoveAllAuras(); + //DestroyForNearbyPlayers(); // old UpdateObjectVisibility() + loot.Clear(); + uint respawnDelay = m_respawnDelay; + if (IsAIEnabled) + GetAI().CorpseRemoved(respawnDelay); - ITransport transport = GetDirectTransport(); - if (transport != null) - transport.CalculatePassengerPosition(ref x, ref y, ref z, ref o); + if (destroyForNearbyPlayers) + DestroyForNearbyPlayers(); + + // Should get removed later, just keep "compatibility" with scripts + if (setSpawnTime) + m_respawnTime = Math.Max(Time.UnixTime + respawnDelay, m_respawnTime); + + // if corpse was removed during falling, the falling will continue and override relocation to respawn position + if (IsFalling()) + StopMoving(); + + float x, y, z, o; + GetRespawnPosition(out x, out y, out z, out o); + + // We were spawned on transport, calculate real position + if (IsSpawnedOnTransport()) + { + Position pos = m_movementInfo.transport.pos; + pos.posX = x; + pos.posY = y; + pos.posZ = z; + pos.SetOrientation(o); + + ITransport transport = GetDirectTransport(); + if (transport != null) + transport.CalculatePassengerPosition(ref x, ref y, ref z, ref o); + } + + SetHomePosition(x, y, z, o); + GetMap().CreatureRelocation(this, x, y, z, o); } + else + { + // In case this is called directly and normal respawn timer not set + // Since this timer will be longer than the already present time it + // will be ignored if the correct place added a respawn timer + if (setSpawnTime) + { + uint respawnDelay = m_respawnDelay; + m_respawnTime = Math.Max(Time.UnixTime + respawnDelay, m_respawnTime); - SetHomePosition(x, y, z, o); - GetMap().CreatureRelocation(this, x, y, z, o); + SaveRespawnTime(0, false); + } + AddObjectToRemoveList(); + } } public bool InitEntry(uint entry, CreatureData data = null) @@ -375,7 +392,7 @@ namespace Game.Entities { case DeathState.JustRespawned: case DeathState.JustDied: - Log.outError(LogFilter.Unit, "Creature ({0}) in wrong state: {2}", GetGUID().ToString(), m_deathState); + Log.outError(LogFilter.Unit, "Creature ({0}) in wrong state: {1}", GetGUID().ToString(), m_deathState); break; case DeathState.Dead: { @@ -383,22 +400,25 @@ namespace Game.Entities if (m_respawnTime <= now) { // First check if there are any scripts that object to us respawning - if (!Global.ScriptMgr.CanSpawn(GetSpawnId(), GetEntry(), GetCreatureTemplate(), GetCreatureData(), GetMap())) - break; // Will be rechecked on next Update call + if (!Global.ScriptMgr.CanSpawn(GetSpawnId(), GetEntry(), GetCreatureData(), GetMap())) + { + m_respawnTime = now + RandomHelper.URand(4, 7); + break; // Will be rechecked on next Update call after delay expires + } ObjectGuid dbtableHighGuid = ObjectGuid.Create(HighGuid.Creature, GetMapId(), GetEntry(), m_spawnId); - long linkedRespawntime = GetMap().GetLinkedRespawnTime(dbtableHighGuid); - if (linkedRespawntime == 0) // Can respawn + long linkedRespawnTime = GetMap().GetLinkedRespawnTime(dbtableHighGuid); + if (linkedRespawnTime == 0) // Can respawn Respawn(); else // the master is dead { ObjectGuid targetGuid = Global.ObjectMgr.GetLinkedRespawnGuid(dbtableHighGuid); if (targetGuid == dbtableHighGuid) // if linking self, never respawn (check delayed to next day) - SetRespawnTime(Time.Day); + SetRespawnTime(Time.Week); else { // else copy time from master and add a little - long baseRespawnTime = Math.Max(linkedRespawntime, now); + long baseRespawnTime = Math.Max(linkedRespawnTime, now); long offset = RandomHelper.URand(5, Time.Minute); // linked guid can be a boss, uses std::numeric_limits::max to never respawn in that instance @@ -731,7 +751,7 @@ namespace Game.Entities lowGuid = map.GenerateLowGuid(HighGuid.Creature); Creature creature = new Creature(); - if (!creature.Create(lowGuid, map, entry, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), null, vehId)) + if (!creature.Create(lowGuid, map, entry, pos, null, vehId)) return null; return creature; @@ -740,13 +760,13 @@ namespace Game.Entities public static Creature CreateCreatureFromDB(ulong spawnId, Map map, bool addToMap = true, bool allowDuplicate = false) { Creature creature = new Creature(); - if (!creature.LoadCreatureFromDB(spawnId, map, addToMap, allowDuplicate)) + if (!creature.LoadFromDB(spawnId, map, addToMap, allowDuplicate)) return null; return creature; } - public bool Create(ulong guidlow, Map map, uint entry, float x, float y, float z, float ang, CreatureData data, uint vehId) + public bool Create(ulong guidlow, Map map, uint entry, Position pos, CreatureData data = null, uint vehId = 0, bool dynamic = false) { SetMap(map); @@ -756,6 +776,10 @@ namespace Game.Entities PhasingHandler.InitDbVisibleMapId(GetPhaseShift(), data.terrainSwapMap); } + // Set if this creature can handle dynamic spawns + if (!dynamic) + SetRespawnCompatibilityMode(); + CreatureTemplate cinfo = Global.ObjectMgr.GetCreatureTemplate(entry); if (cinfo == null) { @@ -765,13 +789,13 @@ namespace Game.Entities //! Relocate before CreateFromProto, to initialize coords and allow //! returning correct zone id for selecting OutdoorPvP/Battlefield script - Relocate(x, y, z, ang); + Relocate(pos); // Check if the position is valid before calling CreateFromProto(), otherwise we might add Auras to Creatures at // invalid position, triggering a crash about Auras not removed in the destructor if (!IsPositionValid()) { - Log.outError(LogFilter.Unit, "Creature.Create: given coordinates for creature (guidlow {0}, entry {1}) are not valid (X: {2}, Y: {3}, Z: {4}, O: {5})", guidlow, entry, x, y, z, ang); + Log.outError(LogFilter.Unit, $"Creature.Create: given coordinates for creature (guidlow {guidlow}, entry {entry}) are not valid ({pos})"); return false; } UpdatePositionData(); @@ -810,10 +834,8 @@ namespace Game.Entities //! Need to be called after LoadCreaturesAddon - MOVEMENTFLAG_HOVER is set there if (HasUnitMovementFlag(MovementFlag.Hover)) { - z += m_unitData.HoverHeight; - //! Relocate again with updated Z coord - Relocate(x, y, z, ang); + posZ += m_unitData.HoverHeight; } LastUsedScriptID = GetScriptId(); @@ -924,6 +946,14 @@ namespace Game.Entities && !GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoXpAtKill); } + public bool IsEscortNPC(bool onlyIfActive = true) + { + if (!IsAIEnabled) + return false; + + return GetAI().IsEscortNPC(onlyIfActive); + } + public override bool IsMovementPreventedByCasting() { // first check if currently a movement allowed channel is active and we're not casting @@ -1068,16 +1098,20 @@ namespace Game.Entities dynamicflags = 0; } - // data.guid = guid must not be updated at save - data.id = GetEntry(); - data.mapid = (ushort)mapid; + if (data.spawnId == 0) + data.spawnId = m_spawnId; + Cypher.Assert(data.spawnId == m_spawnId); + + data.Id = GetEntry(); data.displayid = displayId; - data.equipmentId = GetCurrentEquipmentId(); - data.posX = GetPositionX(); - data.posY = GetPositionY(); - data.posZ = GetPositionZMinusOffset(); - data.orientation = GetOrientation(); - data.spawntimesecs = m_respawnDelay; + data.equipmentId = (sbyte)GetCurrentEquipmentId(); + + if (GetTransport() == null) + data.spawnPoint.WorldRelocate(this); + else + data.spawnPoint.WorldRelocate(mapid, GetTransOffsetX(), GetTransOffsetY(), GetTransOffsetZ(), GetTransOffsetO()); + + data.spawntimesecs = (int)m_respawnDelay; // prevent add data integrity problems data.spawndist = GetDefaultMovementType() == MovementGeneratorType.Idle ? 0.0f : m_respawnradius; data.currentwaypoint = 0; @@ -1092,6 +1126,8 @@ namespace Game.Entities data.unit_flags2 = unitFlags2; data.unit_flags3 = unitFlags3; data.dynamicflags = (uint)dynamicflags; + if (data.spawnGroupData == null) + data.spawnGroupData = Global.ObjectMgr.GetDefaultSpawnGroup(); data.phaseId = GetDBPhase() > 0 ? (uint)GetDBPhase() : data.phaseId; data.phaseGroup = GetDBPhase() < 0 ? (uint)-GetDBPhase() : data.phaseGroup; @@ -1414,7 +1450,7 @@ namespace Game.Entities return; } - GetMap().RemoveCreatureRespawnTime(m_spawnId); + GetMap().RemoveRespawnTime(SpawnObjectType.Creature, m_spawnId); Global.ObjectMgr.DeleteCreatureData(m_spawnId); SQLTransaction trans = new SQLTransaction(); @@ -1423,6 +1459,11 @@ namespace Game.Entities stmt.AddValue(0, m_spawnId); trans.Append(stmt); + stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_SPAWNGROUP_MEMBER); + stmt.AddValue(0, (byte)SpawnObjectType.Creature); + stmt.AddValue(1, m_spawnId); + trans.Append(stmt); + stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_CREATURE_ADDON); stmt.AddValue(0, m_spawnId); trans.Append(stmt); @@ -1549,14 +1590,31 @@ namespace Game.Entities if (s == DeathState.JustDied) { m_corpseRemoveTime = Time.UnixTime + m_corpseDelay; - if (IsDungeonBoss() && m_respawnDelay == 0) - m_respawnTime = long.MaxValue; // never respawn in this instance + uint respawnDelay = m_respawnDelay; + uint scalingMode = WorldConfig.GetUIntValue(WorldCfg.RespawnDynamicMode); + if (scalingMode != 0) + GetMap().ApplyDynamicModeRespawnScaling(this, m_spawnId, ref respawnDelay, scalingMode); + // @todo remove the boss respawn time hack in a dynspawn follow-up once we have creature groups in instances + if (m_respawnCompatibilityMode) + { + if (IsDungeonBoss() && m_respawnDelay == 0) + m_respawnTime = long.MaxValue; // never respawn in this instance + else + m_respawnTime = Time.UnixTime + respawnDelay + m_corpseDelay; + } else - m_respawnTime = Time.UnixTime + m_respawnDelay + m_corpseDelay; + { + if (IsDungeonBoss() && m_respawnDelay == 0) + m_respawnTime = long.MaxValue; // never respawn in this instance + else + m_respawnTime = Time.UnixTime + respawnDelay; + } // always save boss respawn time at death to prevent crash cheating if (WorldConfig.GetBoolValue(WorldCfg.SaveRespawnTimeImmediately) || IsWorldBoss()) SaveRespawnTime(); + else if (!m_respawnCompatibilityMode) + SaveRespawnTime(0, false); ReleaseFocus(null, false); // remove spellcast focus DoNotReacquireTarget(); // cancel delayed re-target @@ -1632,8 +1690,6 @@ namespace Game.Entities public void Respawn(bool force = false) { - DestroyForNearbyPlayers(); - if (force) { if (IsAlive()) @@ -1642,49 +1698,60 @@ namespace Game.Entities SetDeathState(DeathState.Corpse); } - RemoveCorpse(false, false); - - if (GetDeathState() == DeathState.Dead) + if (m_respawnCompatibilityMode) { - if (m_spawnId != 0) - GetMap().RemoveCreatureRespawnTime(m_spawnId); + DestroyForNearbyPlayers(); + RemoveCorpse(false, false); - Log.outDebug(LogFilter.Unit, "Respawning creature {0} ({1})", GetName(), GetGUID().ToString()); - m_respawnTime = 0; - ResetPickPocketRefillTimer(); - loot.Clear(); + if (GetDeathState() == DeathState.Dead) + { + if (m_spawnId != 0) + GetMap().RemoveRespawnTime(SpawnObjectType.Creature, m_spawnId); + + Log.outDebug(LogFilter.Unit, "Respawning creature {0} ({1})", GetName(), GetGUID().ToString()); + m_respawnTime = 0; + ResetPickPocketRefillTimer(); + loot.Clear(); + + if (m_originalEntry != GetEntry()) + UpdateEntry(m_originalEntry); - if (m_originalEntry != GetEntry()) - UpdateEntry(m_originalEntry); - else SelectLevel(); - SetDeathState(DeathState.JustRespawned); + SetDeathState(DeathState.JustRespawned); - CreatureModel display = new CreatureModel(GetNativeDisplayId(), GetNativeDisplayScale(), 1.0f); - if (Global.ObjectMgr.GetCreatureModelRandomGender(ref display, GetCreatureTemplate()) != null) - { - SetDisplayId(display.CreatureDisplayID, display.DisplayScale); - SetNativeDisplayId(display.CreatureDisplayID, display.DisplayScale); + CreatureModel display = new CreatureModel(GetNativeDisplayId(), GetNativeDisplayScale(), 1.0f); + if (Global.ObjectMgr.GetCreatureModelRandomGender(ref display, GetCreatureTemplate()) != null) + { + SetDisplayId(display.CreatureDisplayID, display.DisplayScale); + SetNativeDisplayId(display.CreatureDisplayID, display.DisplayScale); + } + + GetMotionMaster().InitDefault(); + //Re-initialize reactstate that could be altered by movementgenerators + InitializeReactState(); + + //Call AI respawn virtual function//Call AI respawn virtual function + if (IsAIEnabled) + { + //reset the AI to be sure no dirty or uninitialized values will be used till next tick + GetAI().Reset(); + TriggerJustRespawned = true;//delay event to next tick so all creatures are created on the map before processing + } + + uint poolid = GetSpawnId() != 0 ? Global.PoolMgr.IsPartOfAPool(GetSpawnId()) : 0; + if (poolid != 0) + Global.PoolMgr.UpdatePool(poolid, GetSpawnId()); } - - GetMotionMaster().InitDefault(); - //Re-initialize reactstate that could be altered by movementgenerators - InitializeReactState(); - - //Call AI respawn virtual function - if (IsAIEnabled) - { - GetAI().Reset(); - TriggerJustRespawned = true;//delay event to next tick so all creatures are created on the map before processing - } - - uint poolid = GetSpawnId() != 0 ? Global.PoolMgr.IsPartOfAPool(GetSpawnId()) : 0; - if (poolid != 0) - Global.PoolMgr.UpdatePool(poolid, GetSpawnId()); + UpdateObjectVisibility(); + } + else + { + if (m_spawnId != 0) + GetMap().RemoveRespawnTime(SpawnObjectType.Creature, m_spawnId, true); } - UpdateObjectVisibility(); + Log.outDebug(LogFilter.Unit, $"Respawning creature {GetName()} ({GetGUID()})"); } public void ForcedDespawn(uint timeMSToDespawn = 0, TimeSpan forceRespawnTimer = default) @@ -1695,30 +1762,49 @@ namespace Game.Entities return; } - uint corpseDelay = GetCorpseDelay(); - uint respawnDelay = GetRespawnDelay(); - - // do it before killing creature - DestroyForNearbyPlayers(); - - bool overrideRespawnTime = false; - if (IsAlive()) + if (m_respawnCompatibilityMode) { - if (forceRespawnTimer > TimeSpan.Zero) + uint corpseDelay = GetCorpseDelay(); + uint respawnDelay = GetRespawnDelay(); + + // do it before killing creature + DestroyForNearbyPlayers(); + + bool overrideRespawnTime = false; + if (IsAlive()) { - SetCorpseDelay(0); - SetRespawnDelay((uint)forceRespawnTimer.TotalSeconds); - overrideRespawnTime = false; + if (forceRespawnTimer > TimeSpan.Zero) + { + SetCorpseDelay(0); + SetRespawnDelay((uint)forceRespawnTimer.TotalSeconds); + overrideRespawnTime = false; + } + + SetDeathState(DeathState.JustDied); } - SetDeathState(DeathState.JustDied); + // Skip corpse decay time + RemoveCorpse(overrideRespawnTime, false); + + SetCorpseDelay(corpseDelay); + SetRespawnDelay(respawnDelay); } + else + { + if (forceRespawnTimer > TimeSpan.Zero) + SaveRespawnTime((uint)forceRespawnTimer.TotalSeconds); + else + { + uint respawnDelay = m_respawnDelay; + uint scalingMode = WorldConfig.GetUIntValue(WorldCfg.RespawnDynamicMode); + if (scalingMode != 0) + GetMap().ApplyDynamicModeRespawnScaling(this, m_spawnId, ref respawnDelay, scalingMode); + m_respawnTime = Time.UnixTime + respawnDelay; + SaveRespawnTime(); + } - // Skip corpse decay time - RemoveCorpse(overrideRespawnTime, false); - - SetCorpseDelay(corpseDelay); - SetRespawnDelay(respawnDelay); + AddObjectToRemoveList(); + } } public void DespawnOrUnsummon(TimeSpan time, TimeSpan forceRespawnTimer = default) { DespawnOrUnsummon((uint)time.TotalMilliseconds, forceRespawnTimer); } @@ -2074,12 +2160,19 @@ namespace Game.Entities return false; } - public override void SaveRespawnTime() + public override void SaveRespawnTime(uint forceDelay = 0, bool saveToDb = true) { if (IsSummon() || m_spawnId == 0 || (m_creatureData != null && !m_creatureData.dbData)) return; - GetMap().SaveCreatureRespawnTime(m_spawnId, m_respawnTime); + if (m_respawnCompatibilityMode) + { + GetMap().SaveRespawnTimeDB(SpawnObjectType.Creature, m_spawnId, m_respawnTime); + return; + } + + uint thisRespawnTime = (uint)(forceDelay != 0 ? Time.UnixTime + forceDelay : m_respawnTime); + GetMap().SaveRespawnTime(SpawnObjectType.Creature, m_spawnId, GetEntry(), thisRespawnTime, GetMap().GetZoneId(GetPhaseShift(), GetHomePosition()), GridDefines.ComputeGridCoord(GetHomePosition().GetPositionX(), GetHomePosition().GetPositionY()).GetId(), m_creatureData.dbData && saveToDb); } public bool CanCreatureAttack(Unit victim, bool force = true) @@ -2293,32 +2386,20 @@ namespace Game.Entities } public void GetRespawnPosition(out float x, out float y, out float z, out float ori, out float dist) { - // for npcs on transport, this will return transport offset - if (m_spawnId != 0) + if (m_creatureData != null) { - CreatureData data = Global.ObjectMgr.GetCreatureData(GetSpawnId()); - if (data != null) - { - x = data.posX; - y = data.posY; - z = data.posZ; - ori = data.orientation; - dist = data.spawndist; - - return; - } + m_creatureData.spawnPoint.GetPosition(out x, out y, out z, out ori); + dist = m_creatureData.spawndist; + } + else + { + Position homePos = GetHomePosition(); + homePos.GetPosition(out x, out y, out z, out ori); + dist = 0; } - - // changed this from current position to home position, fixes world summons with infinite duration (wg npcs for example) - Position homePos = GetHomePosition(); - x = homePos.GetPositionX(); - y = homePos.GetPositionY(); - z = homePos.GetPositionZ(); - ori = homePos.GetOrientation(); - dist = 0; } - bool IsSpawnedOnTransport() { return m_creatureData != null && m_creatureData.mapid != GetMapId(); } + bool IsSpawnedOnTransport() { return m_creatureData != null && m_creatureData.spawnPoint.GetMapId() != GetMapId(); } public void AllLootRemovedFromCorpse() { @@ -2934,12 +3015,7 @@ namespace Game.Entities public CreatureTemplate GetCreatureTemplate() { return m_creatureInfo; } public CreatureData GetCreatureData() { return m_creatureData; } - public override bool LoadFromDB(ulong spawnId, Map map) - { - return LoadCreatureFromDB(spawnId, map, false, false); - } - - public bool LoadCreatureFromDB(ulong spawnId, Map map, bool addToMap, bool allowDuplicate) + public override bool LoadFromDB(ulong spawnId, Map map, bool addToMap, bool allowDuplicate) { if (!allowDuplicate) { @@ -2976,39 +3052,48 @@ namespace Game.Entities } m_spawnId = spawnId; + m_respawnCompatibilityMode = data.spawnGroupData.flags.HasAnyFlag(SpawnGroupFlags.CompatibilityMode); m_creatureData = data; m_respawnradius = data.spawndist; - m_respawnDelay = data.spawntimesecs; - if (!Create(map.GenerateLowGuid(HighGuid.Creature), map, data.id, data.posX, data.posY, data.posZ, data.orientation, data, 0)) + m_respawnDelay = (uint)data.spawntimesecs; + // Is the creature script objecting to us spawning? If yes, delay by a little bit (then re-check in ::Update) + if (!m_respawnCompatibilityMode && m_respawnTime == 0 && !Global.ScriptMgr.CanSpawn(spawnId, data.Id, data, map)) + { + SaveRespawnTime(RandomHelper.URand(4, 7)); + return false; + } + + if (!Create(map.GenerateLowGuid(HighGuid.Creature), map, data.Id, data.spawnPoint, data, 0, !m_respawnCompatibilityMode)) return false; //We should set first home position, because then AI calls home movement - SetHomePosition(data.posX, data.posY, data.posZ, data.orientation); + SetHomePosition(data.spawnPoint); m_deathState = DeathState.Alive; m_respawnTime = GetMap().GetCreatureRespawnTime(m_spawnId); - // Is the creature script objecting to us spawning? If yes, delay by one second (then re-check in ::Update) - if (m_respawnTime == 0 && !Global.ScriptMgr.CanSpawn(spawnId, GetEntry(), GetCreatureTemplate(), GetCreatureData(), map)) - m_respawnTime = Time.UnixTime + 1; + // Is the creature script objecting to us spawning? If yes, delay by a little bit (then re-check in ::Update) + if (m_respawnCompatibilityMode && m_respawnTime == 0 && !Global.ScriptMgr.CanSpawn(spawnId, GetEntry(), GetCreatureData(), map)) + m_respawnTime = Time.UnixTime + RandomHelper.URand(4, 7); - if (m_respawnTime != 0) // respawn on Update + if (m_respawnTime != 0) // respawn on UpdateLoadCreatureFromDB { m_deathState = DeathState.Dead; if (CanFly()) { - float tz = map.GetHeight(GetPhaseShift(), data.posX, data.posY, data.posZ, true, MapConst.MaxFallDistance); - if (data.posZ - tz > 0.1f && GridDefines.IsValidMapCoord(tz)) - Relocate(data.posX, data.posY, tz); + float tz = map.GetHeight(GetPhaseShift(), data.spawnPoint, true, MapConst.MaxFallDistance); + if (data.spawnPoint.GetPositionZ() - tz > 0.1f && GridDefines.IsValidMapCoord(tz)) + Relocate(data.spawnPoint.GetPositionX(), data.spawnPoint.GetPositionY(), tz); } } SetSpawnHealth(); + // checked at creature_template loading DefaultMovementType = (MovementGeneratorType)data.movementType; - loot.SetGUID(ObjectGuid.Create(HighGuid.LootObject, data.mapid, data.id, GetMap().GenerateLowGuid(HighGuid.LootObject))); + loot.SetGUID(ObjectGuid.Create(HighGuid.LootObject, GetMapId(), data.Id, GetMap().GenerateLowGuid(HighGuid.LootObject))); if (addToMap && !GetMap().AddToMap(this)) return false; @@ -3102,6 +3187,10 @@ namespace Game.Entities m_originalEntry = entry; } + // There's many places not ready for dynamic spawns. This allows them to live on for now. + void SetRespawnCompatibilityMode(bool mode = true) { m_respawnCompatibilityMode = mode; } + public bool GetRespawnCompatibilityMode() { return m_respawnCompatibilityMode; } + public Unit SelectVictim() { // function provides main threat functionality diff --git a/Source/Game/Entities/Creature/CreatureData.cs b/Source/Game/Entities/Creature/CreatureData.cs index c3018285b..d2b501bf4 100644 --- a/Source/Game/Entities/Creature/CreatureData.cs +++ b/Source/Game/Entities/Creature/CreatureData.cs @@ -21,6 +21,7 @@ using System; using System.Collections.Generic; using Framework.Dynamic; using Game.Networking.Packets; +using Game.Maps; namespace Game.Entities { @@ -327,34 +328,22 @@ namespace Game.Entities public EquipmentItem[] Items = new EquipmentItem[SharedConst.MaxEquipmentItems]; } - public class CreatureData + public class CreatureData : SpawnData { - public uint id; // entry in creature_template - public ushort mapid; public uint displayid; - public int equipmentId; - public float posX; - public float posY; - public float posZ; - public float orientation; - public uint spawntimesecs; + public sbyte equipmentId; public float spawndist; public uint currentwaypoint; public uint curhealth; public uint curmana; public byte movementType; - public List spawnDifficulties = new List(); public ulong npcflag; - public uint unit_flags; // enum UnitFlags mask values - public uint unit_flags2; // enum UnitFlags2 mask values - public uint unit_flags3; // enum UnitFlags3 mask values + public uint unit_flags; // enum UnitFlags mask values + public uint unit_flags2; // enum UnitFlags2 mask values + public uint unit_flags3; // enum UnitFlags3 mask values public uint dynamicflags; - public PhaseUseFlagsValues phaseUseFlags; - public uint phaseId; - public uint phaseGroup; - public int terrainSwapMap; - public uint ScriptId; - public bool dbData; + + public CreatureData() : base(SpawnObjectType.Creature) { } } public class CreatureModelInfo diff --git a/Source/Game/Entities/GameObject/GameObject.cs b/Source/Game/Entities/GameObject/GameObject.cs index 0e892bb24..3761a8374 100644 --- a/Source/Game/Entities/GameObject/GameObject.cs +++ b/Source/Game/Entities/GameObject/GameObject.cs @@ -166,7 +166,7 @@ namespace Game.Entities return null; GameObject go = new GameObject(); - if (!go.Create(entry, map, pos, rotation, animProgress, goState, artKit)) + if (!go.Create(entry, map, pos, rotation, animProgress, goState, artKit, false, 0)) return null; return go; @@ -175,13 +175,13 @@ namespace Game.Entities public static GameObject CreateGameObjectFromDB(ulong spawnId, Map map, bool addToMap = true) { GameObject go = new GameObject(); - if (!go.LoadGameObjectFromDB(spawnId, map, addToMap)) + if (!go.LoadFromDB(spawnId, map, addToMap)) return null; return go; } - bool Create(uint entry, Map map, Position pos, Quaternion rotation, uint animProgress, GameObjectState goState, uint artKit) + bool Create(uint entry, Map map, Position pos, Quaternion rotation, uint animProgress, GameObjectState goState, uint artKit, bool dynamic, ulong spawnid) { Cypher.Assert(map); SetMap(map); @@ -194,6 +194,10 @@ namespace Game.Entities return false; } + // Set if this object can handle dynamic spawns + if (!dynamic) + SetRespawnCompatibilityMode(); + UpdatePositionData(); SetZoneScript(); @@ -373,6 +377,9 @@ namespace Game.Entities if (map.Is25ManRaid()) loot.maxDuplicates = 3; + if (spawnid != 0) + m_spawnId = spawnid; + uint linkedEntry = GetGoInfo().GetLinkedGameObjectEntry(); if (linkedEntry != 0) { @@ -507,81 +514,88 @@ namespace Game.Entities goto case LootState.Ready; case LootState.Ready: { - if (m_respawnTime > 0) // timer on + if (m_respawnCompatibilityMode) { - long now = Time.UnixTime; - if (m_respawnTime <= now) // timer expired + if (m_respawnTime > 0) // timer on { - ObjectGuid dbtableHighGuid = ObjectGuid.Create(HighGuid.GameObject, GetMapId(), GetEntry(), m_spawnId); - long linkedRespawntime = GetMap().GetLinkedRespawnTime(dbtableHighGuid); - if (linkedRespawntime != 0) // Can't respawn, the master is dead + long now = Time.UnixTime; + if (m_respawnTime <= now) // timer expired { - ObjectGuid targetGuid = Global.ObjectMgr.GetLinkedRespawnGuid(dbtableHighGuid); - if (targetGuid == dbtableHighGuid) // if linking self, never respawn (check delayed to next day) - SetRespawnTime(Time.Day); - else - m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime) + RandomHelper.IRand(5, Time.Minute); // else copy time from master and add a little - SaveRespawnTime(); // also save to DB immediately - return; - } + ObjectGuid dbtableHighGuid = ObjectGuid.Create(HighGuid.GameObject, GetMapId(), GetEntry(), m_spawnId); + long linkedRespawntime = GetMap().GetLinkedRespawnTime(dbtableHighGuid); + if (linkedRespawntime != 0) // Can't respawn, the master is dead + { + ObjectGuid targetGuid = Global.ObjectMgr.GetLinkedRespawnGuid(dbtableHighGuid); + if (targetGuid == dbtableHighGuid) // if linking self, never respawn (check delayed to next day) + SetRespawnTime(Time.Week); + else + m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime) + RandomHelper.IRand(5, Time.Minute); // else copy time from master and add a little + SaveRespawnTime(); // also save to DB immediately + return; + } - m_respawnTime = 0; - m_SkillupList.Clear(); - m_usetimes = 0; + m_respawnTime = 0; + m_SkillupList.Clear(); + m_usetimes = 0; - // If nearby linked trap exists, respawn it - GameObject linkedTrap = GetLinkedTrap(); - if (linkedTrap) - linkedTrap.SetLootState(LootState.Ready); + // If nearby linked trap exists, respawn it + GameObject linkedTrap = GetLinkedTrap(); + if (linkedTrap) + linkedTrap.SetLootState(LootState.Ready); - switch (GetGoType()) - { - case GameObjectTypes.FishingNode: // can't fish now - { - Unit caster = GetOwner(); - if (caster != null && caster.IsTypeId(TypeId.Player)) + switch (GetGoType()) + { + case GameObjectTypes.FishingNode: // can't fish now { - caster.ToPlayer().RemoveGameObject(this, false); - caster.ToPlayer().SendPacket(new FishEscaped()); + Unit caster = GetOwner(); + if (caster != null && caster.IsTypeId(TypeId.Player)) + { + caster.ToPlayer().RemoveGameObject(this, false); + caster.ToPlayer().SendPacket(new FishEscaped()); + } + // can be delete + m_lootState = LootState.JustDeactivated; + return; } - // can be delete - m_lootState = LootState.JustDeactivated; - return; - } - case GameObjectTypes.Door: - case GameObjectTypes.Button: - //we need to open doors if they are closed (add there another condition if this code breaks some usage, but it need to be here for Battlegrounds) - if (GetGoState() != GameObjectState.Ready) - ResetDoorOrButton(); - break; - case GameObjectTypes.FishingHole: - // Initialize a new max fish count on respawn - m_goValue.FishingHole.MaxOpens = RandomHelper.URand(GetGoInfo().FishingHole.minRestock, GetGoInfo().FishingHole.maxRestock); - break; - default: - break; + case GameObjectTypes.Door: + case GameObjectTypes.Button: + //we need to open doors if they are closed (add there another condition if this code breaks some usage, but it need to be here for Battlegrounds) + if (GetGoState() != GameObjectState.Ready) + ResetDoorOrButton(); + break; + case GameObjectTypes.FishingHole: + // Initialize a new max fish count on respawn + m_goValue.FishingHole.MaxOpens = RandomHelper.URand(GetGoInfo().FishingHole.minRestock, GetGoInfo().FishingHole.maxRestock); + break; + default: + break; + } + + if (!m_spawnedByDefault) // despawn timer + { + // can be despawned or destroyed + SetLootState(LootState.JustDeactivated); + return; + } + + // Call AI Reset (required for example in SmartAI to clear one time events) + if (GetAI() != null) + GetAI().Reset(); + + // respawn timer + uint poolid = GetSpawnId() != 0 ? Global.PoolMgr.IsPartOfAPool(GetSpawnId()) : 0; + if (poolid != 0) + Global.PoolMgr.UpdatePool(poolid, GetSpawnId()); + else + GetMap().AddToMap(this); } - - if (!m_spawnedByDefault) // despawn timer - { - // can be despawned or destroyed - SetLootState(LootState.JustDeactivated); - return; - } - - // Call AI Reset (required for example in SmartAI to clear one time events) - if (GetAI() != null) - GetAI().Reset(); - - // respawn timer - uint poolid = GetSpawnId() != 0 ? Global.PoolMgr.IsPartOfAPool(GetSpawnId()) : 0; - if (poolid != 0) - Global.PoolMgr.UpdatePool(poolid, GetSpawnId()); - else - GetMap().AddToMap(this); } } + // Set respawn timer + if (!m_respawnCompatibilityMode && m_respawnTime > 0) + SaveRespawnTime(0, false); + if (IsSpawned()) { GameObjectTemplate goInfo = GetGoInfo(); @@ -785,6 +799,7 @@ namespace Game.Entities if (m_respawnDelayTime == 0) return; + // ToDo: Decide if we should properly despawn these. Maybe they expect to be able to manually respawn from script? if (!m_spawnedByDefault) { m_respawnTime = 0; @@ -792,12 +807,29 @@ namespace Game.Entities return; } - m_respawnTime = Time.UnixTime + m_respawnDelayTime; + uint respawnDelay = m_respawnDelayTime; + uint scalingMode = WorldConfig.GetUIntValue(WorldCfg.RespawnDynamicMode); + if (scalingMode != 0) + GetMap().ApplyDynamicModeRespawnScaling(this, m_spawnId, ref respawnDelay, scalingMode); + m_respawnTime = Time.UnixTime + respawnDelay; // if option not set then object will be saved at grid unload + // Otherwise just save respawn time to map object memory if (WorldConfig.GetBoolValue(WorldCfg.SaveRespawnTimeImmediately)) SaveRespawnTime(); + if (!m_respawnCompatibilityMode) + { + // Respawn time was just saved if set to save to DB + // If not, we save only to map memory + if (!WorldConfig.GetBoolValue(WorldCfg.SaveRespawnTimeImmediately)) + SaveRespawnTime(0, false); + + // Then despawn + AddObjectToRemoveList(); + return; + } + DestroyForNearbyPlayers(); // old UpdateObjectVisibility() break; } @@ -890,7 +922,7 @@ namespace Game.Entities { // this should only be used when the gameobject has already been loaded // preferably after adding to map, because mapid may not be valid otherwise - GameObjectData data = Global.ObjectMgr.GetGOData(m_spawnId); + GameObjectData data = Global.ObjectMgr.GetGameObjectData(m_spawnId); if (data == null) { Log.outError(LogFilter.Maps, "GameObject.SaveToDB failed, cannot get gameobject data!"); @@ -911,22 +943,22 @@ namespace Game.Entities m_spawnId = Global.ObjectMgr.GenerateGameObjectSpawnId(); // update in loaded data (changing data only in this place) - GameObjectData data = new GameObjectData(); + GameObjectData data = Global.ObjectMgr.NewOrExistGameObjectData(m_spawnId); - // guid = guid must not be updated at save - data.id = GetEntry(); - data.mapid = (ushort)mapid; - data.posX = GetPositionX(); - data.posY = GetPositionY(); - data.posZ = GetPositionZ(); - data.orientation = GetOrientation(); + if (data.spawnId == 0) + data.spawnId = m_spawnId; + Cypher.Assert(data.spawnId == m_spawnId); + + data.Id = GetEntry(); + data.spawnPoint.WorldRelocate(this); data.rotation = m_worldRotation; data.spawntimesecs = (int)(m_spawnedByDefault ? m_respawnDelayTime : -m_respawnDelayTime); data.animprogress = GetGoAnimProgress(); - data.go_state = GetGoState(); + data.goState = GetGoState(); data.spawnDifficulties = spawnDifficulties; data.artKit = (byte)GetGoArtKit(); - Global.ObjectMgr.NewGOData(m_spawnId, data); + if (data.spawnGroupData == null) + data.spawnGroupData = Global.ObjectMgr.GetDefaultSpawnGroup(); data.phaseId = GetDBPhase() > 0 ? (uint)GetDBPhase() : data.phaseId; data.phaseGroup = GetDBPhase() < 0 ? (uint)-GetDBPhase() : data.phaseGroup; @@ -958,24 +990,24 @@ namespace Game.Entities DB.World.Execute(stmt); } - bool LoadGameObjectFromDB(ulong spawnId, Map map, bool addToMap) + public override bool LoadFromDB(ulong spawnId, Map map, bool addToMap, bool unused = true) { - GameObjectData data = Global.ObjectMgr.GetGOData(spawnId); + GameObjectData data = Global.ObjectMgr.GetGameObjectData(spawnId); if (data == null) { Log.outError(LogFilter.Maps, "Gameobject (SpawnId: {0}) not found in table `gameobject`, can't load. ", spawnId); return false; } - uint entry = data.id; - Position pos = new Position(data.posX, data.posY, data.posZ, data.orientation); + uint entry = data.Id; uint animprogress = data.animprogress; - GameObjectState go_state = data.go_state; + GameObjectState go_state = data.goState; uint artKit = data.artKit; m_spawnId = spawnId; - if (!Create(entry, map, pos, data.rotation, animprogress, go_state, artKit)) + m_respawnCompatibilityMode = ((data.spawnGroupData.flags & SpawnGroupFlags.CompatibilityMode) != 0); + if (!Create(entry, map, data.spawnPoint, data.rotation, animprogress, go_state, artKit, !m_respawnCompatibilityMode, spawnId)) return false; PhasingHandler.InitDbPhaseShift(GetPhaseShift(), data.phaseUseFlags, data.phaseId, data.phaseGroup); @@ -1000,7 +1032,7 @@ namespace Game.Entities if (m_respawnTime != 0 && m_respawnTime <= Time.UnixTime) { m_respawnTime = 0; - GetMap().RemoveGORespawnTime(m_spawnId); + GetMap().RemoveRespawnTime(SpawnObjectType.GameObject, m_spawnId); } } } @@ -1021,20 +1053,20 @@ namespace Game.Entities public void DeleteFromDB() { - GetMap().RemoveGORespawnTime(m_spawnId); - Global.ObjectMgr.DeleteGOData(m_spawnId); + GetMap().RemoveRespawnTime(SpawnObjectType.GameObject, m_spawnId); + Global.ObjectMgr.DeleteGameObjectData(m_spawnId); + + SQLTransaction trans = new SQLTransaction(); PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_GAMEOBJECT); - stmt.AddValue(0, m_spawnId); - - DB.World.Execute(stmt); + trans.Append(stmt); stmt = DB.World.GetPreparedStatement(WorldStatements.DEL_EVENT_GAMEOBJECT); - stmt.AddValue(0, m_spawnId); + trans.Append(stmt); - DB.World.Execute(stmt); + DB.World.CommitTransaction(trans); } public override bool HasQuest(uint quest_id) @@ -1097,10 +1129,19 @@ namespace Game.Entities return Global.ObjAccessor.GetUnit(this, GetOwnerGUID()); } - public override void SaveRespawnTime() + public override void SaveRespawnTime(uint forceDelay = 0, bool savetodb = true) { - if (m_goData != null && m_goData.dbData && m_respawnTime > Time.UnixTime && m_spawnedByDefault) - GetMap().SaveGORespawnTime(m_spawnId, m_respawnTime); + if (m_goData != null && m_respawnTime > Time.UnixTime && m_spawnedByDefault) + { + if (m_respawnCompatibilityMode) + { + GetMap().SaveRespawnTimeDB(SpawnObjectType.GameObject, m_spawnId, m_respawnTime); + return; + } + + uint thisRespawnTime = (uint)(forceDelay != 0 ? Time.UnixTime + forceDelay : m_respawnTime); + GetMap().SaveRespawnTime(SpawnObjectType.GameObject, m_spawnId, GetEntry(), thisRespawnTime, GetZoneId(), GridDefines.ComputeGridCoord(GetPositionX(), GetPositionY()).GetId(), m_goData.dbData ? savetodb : false); + } } public override bool IsNeverVisibleFor(WorldObject seer) @@ -1160,7 +1201,7 @@ namespace Game.Entities if (m_spawnedByDefault && m_respawnTime > 0) { m_respawnTime = Time.UnixTime; - GetMap().RemoveGORespawnTime(m_spawnId); + GetMap().RemoveRespawnTime(SpawnObjectType.GameObject, m_spawnId, true); } } @@ -1262,7 +1303,7 @@ namespace Game.Entities public void SetGoArtKit(byte kit) { SetUpdateFieldValue(m_values.ModifyValue(m_gameObjectData).ModifyValue(m_gameObjectData.ArtKit), kit); - GameObjectData data = Global.ObjectMgr.GetGOData(m_spawnId); + GameObjectData data = Global.ObjectMgr.GetGameObjectData(m_spawnId); if (data != null) data.artKit = kit; } @@ -1273,10 +1314,10 @@ namespace Game.Entities if (go != null) { go.SetGoArtKit(artkit); - data = go.GetGoData(); + data = go.GetGameObjectData(); } else if (lowguid != 0) - data = Global.ObjectMgr.GetGOData(lowguid); + data = Global.ObjectMgr.GetGameObjectData(lowguid); if (data != null) data.artKit = artkit; @@ -2075,7 +2116,7 @@ namespace Game.Entities public uint GetScriptId() { - GameObjectData gameObjectData = GetGoData(); + GameObjectData gameObjectData = GetGameObjectData(); if (gameObjectData != null) { uint scriptId = gameObjectData.ScriptId; @@ -2548,23 +2589,10 @@ namespace Game.Entities public void GetRespawnPosition(out float x, out float y, out float z, out float ori) { - if (m_spawnId != 0) - { - GameObjectData data = Global.ObjectMgr.GetGOData(GetSpawnId()); - if (data != null) - { - x = posX; - y = posY; - z = posZ; - ori = data.orientation; - return; - } - } - - x = GetPositionX(); - y = GetPositionY(); - z = GetPositionZ(); - ori = GetOrientation(); + if (m_goData != null) + m_goData.spawnPoint.GetPosition(out x, out y, out z, out ori); + else + GetPosition(out x, out y, out z, out ori); } public float GetInteractionDistance() @@ -2624,18 +2652,13 @@ namespace Game.Entities public GameObjectTemplate GetGoInfo() { return m_goInfo; } public GameObjectTemplateAddon GetTemplateAddon() { return m_goTemplateAddon; } - public GameObjectData GetGoData() { return m_goData; } + public GameObjectData GetGameObjectData() { return m_goData; } public GameObjectValue GetGoValue() { return m_goValue; } public ulong GetSpawnId() { return m_spawnId; } public long GetPackedWorldRotation() { return m_packedRotation; } - public override bool LoadFromDB(ulong spawnId, Map map) - { - return LoadGameObjectFromDB(spawnId, map, false); - } - public void SetOwnerGUID(ObjectGuid owner) { // Owner already found and different than expected owner - remove object from old owner @@ -2768,6 +2791,10 @@ namespace Game.Entities AddFlag(GameObjectFlags.MapObject); } + // There's many places not ready for dynamic spawns. This allows them to live on for now. + void SetRespawnCompatibilityMode(bool mode = true) { m_respawnCompatibilityMode = mode; } + public bool GetRespawnCompatibilityMode() { return m_respawnCompatibilityMode; } + #region Fields protected GameObjectFieldData m_gameObjectData; protected GameObjectValue m_goValue; @@ -2799,6 +2826,7 @@ namespace Game.Entities public Position StationaryPosition { get; set; } GameObjectAI m_AI; + bool m_respawnCompatibilityMode; ushort _animKitId; uint _worldEffectID; diff --git a/Source/Game/Entities/GameObject/GameObjectData.cs b/Source/Game/Entities/GameObject/GameObjectData.cs index 41d3bb380..1b91e00d3 100644 --- a/Source/Game/Entities/GameObject/GameObjectData.cs +++ b/Source/Game/Entities/GameObject/GameObjectData.cs @@ -21,6 +21,7 @@ using Framework.GameMath; using System.Collections.Generic; using System.Runtime.InteropServices; using Game.Networking.Packets; +using Game.Maps; namespace Game.Entities { @@ -1274,25 +1275,13 @@ namespace Game.Entities public uint WorldEffectID; } - public class GameObjectData + public class GameObjectData : SpawnData { - public uint id; // entry in gamobject_template - public ushort mapid; - public float posX; - public float posY; - public float posZ; - public float orientation; public Quaternion rotation; - public int spawntimesecs; public uint animprogress; - public GameObjectState go_state; - public List spawnDifficulties = new List(); + public GameObjectState goState; public byte artKit; - public PhaseUseFlagsValues phaseUseFlags; - public uint phaseId; - public uint phaseGroup; - public int terrainSwapMap; - public uint ScriptId; - public bool dbData = true; + + public GameObjectData() : base(SpawnObjectType.GameObject) { } } } diff --git a/Source/Game/Entities/Object/ObjectGuid.cs b/Source/Game/Entities/Object/ObjectGuid.cs index ff7c66f29..0bb1022f7 100644 --- a/Source/Game/Entities/Object/ObjectGuid.cs +++ b/Source/Game/Entities/Object/ObjectGuid.cs @@ -345,6 +345,10 @@ namespace Game.Entities { if (_nextGuid >= ObjectGuid.GetMaxCounter(_highGuid) - 1) HandleCounterOverflow(); + + if (_highGuid == HighGuid.Creature || _highGuid == HighGuid.Vehicle || _highGuid == HighGuid.GameObject || _highGuid == HighGuid.Transport) + CheckGuidTrigger(_nextGuid); + return _nextGuid++; } @@ -355,5 +359,13 @@ namespace Game.Entities Log.outFatal(LogFilter.Server, "{0} guid overflow!! Can't continue, shutting down server. ", _highGuid); Global.WorldMgr.StopNow(); } + + void CheckGuidTrigger(ulong guidlow) + { + if (!Global.WorldMgr.IsGuidAlert() && guidlow > WorldConfig.GetUInt64Value(WorldCfg.RespawnGuidAlertLevel)) + Global.WorldMgr.TriggerGuidAlert(); + else if (!Global.WorldMgr.IsGuidWarning() && guidlow > WorldConfig.GetUInt64Value(WorldCfg.RespawnGuidWarnLevel)) + Global.WorldMgr.TriggerGuidWarning(); + } } } diff --git a/Source/Game/Entities/Object/Position.cs b/Source/Game/Entities/Object/Position.cs index 88b2969f9..5cf5b5b94 100644 --- a/Source/Game/Entities/Object/Position.cs +++ b/Source/Game/Entities/Object/Position.cs @@ -364,6 +364,12 @@ namespace Game.Entities Relocate(pos); } + public void WorldRelocate(uint mapId, Position pos) + { + _mapId = mapId; + Relocate(pos); + } + public void WorldRelocate(WorldLocation loc) { _mapId = loc._mapId; diff --git a/Source/Game/Entities/Object/WorldObject.cs b/Source/Game/Entities/Object/WorldObject.cs index 26e817f67..53a9dfcc9 100644 --- a/Source/Game/Entities/Object/WorldObject.cs +++ b/Source/Game/Entities/Object/WorldObject.cs @@ -1731,7 +1731,7 @@ namespace Game.Entities public virtual uint GetLevelForTarget(WorldObject target) { return 1; } - public virtual void SaveRespawnTime() { } + public virtual void SaveRespawnTime(uint forceDelay = 0, bool saveToDB = true) { } public ZoneScript GetZoneScript() { return m_zoneScript; } @@ -1770,7 +1770,7 @@ namespace Game.Entities public virtual bool IsInvisibleDueToDespawn() { return false; } public virtual bool IsAlwaysDetectableFor(WorldObject seer) { return false; } - public virtual bool LoadFromDB(ulong guid, Map map) { return true; } + public virtual bool LoadFromDB(ulong spawnId, Map map, bool addToMap, bool allowDuplicate) { return true; } //Position diff --git a/Source/Game/Entities/Player/Player.Map.cs b/Source/Game/Entities/Player/Player.Map.cs index 1f2e13538..f0518800e 100644 --- a/Source/Game/Entities/Player/Player.Map.cs +++ b/Source/Game/Entities/Player/Player.Map.cs @@ -160,6 +160,8 @@ namespace Game.Entities guild.UpdateMemberData(this, GuildMemberData.ZoneId, newZone); } + GetMap().UpdatePlayerZoneStats(m_zoneUpdateId, newZone); + // group update if (GetGroup()) { diff --git a/Source/Game/Entities/Transport.cs b/Source/Game/Entities/Transport.cs index 821708e49..1b450a330 100644 --- a/Source/Game/Entities/Transport.cs +++ b/Source/Game/Entities/Transport.cs @@ -318,14 +318,12 @@ namespace Game.Entities { Map map = GetMap(); - Creature creature = Creature.CreateCreatureFromDB(guid, map, false); + Creature creature = Creature.CreateCreatureFromDB(guid, map, false, true); if (!creature) return null; - float x = data.posX; - float y = data.posY; - float z = data.posZ; - float o = data.orientation; + float x, y, z, o; + data.spawnPoint.GetPosition(out x, out y, out z, out o); creature.SetTransport(this); creature.m_movementInfo.transport.guid = GetGUID(); @@ -365,10 +363,8 @@ namespace Game.Entities if (!go) return null; - float x = data.posX; - float y = data.posY; - float z = data.posZ; - float o = data.orientation; + float x, y, z, o; + data.spawnPoint.GetPosition(out x, out y, out z, out o); go.SetTransport(this); go.m_movementInfo.transport.guid = GetGUID(); @@ -472,7 +468,7 @@ namespace Game.Entities pos.GetPosition(out x, out y, out z, out o); CalculatePassengerPosition(ref x, ref y, ref z, ref o); - if (!summon.Create(map.GenerateLowGuid(HighGuid.Creature), map, entry, x, y, z, o, null, vehId)) + if (!summon.Create(map.GenerateLowGuid(HighGuid.Creature), map, entry, new Position(x, y, z, o), null, vehId)) return null; PhasingHandler.InheritPhaseShift(summon, summoner ? (WorldObject)summoner : this); @@ -553,7 +549,7 @@ namespace Game.Entities // GameObjects on transport foreach (var go in cell.Value.gameobjects) - CreateGOPassenger(go, Global.ObjectMgr.GetGOData(go)); + CreateGOPassenger(go, Global.ObjectMgr.GetGameObjectData(go)); } } diff --git a/Source/Game/Events/GameEventManager.cs b/Source/Game/Events/GameEventManager.cs index b1ec2f436..7e14105df 100644 --- a/Source/Game/Events/GameEventManager.cs +++ b/Source/Game/Events/GameEventManager.cs @@ -412,7 +412,7 @@ namespace Game int internal_event_id = mGameEvent.Length + event_id - 1; - GameObjectData data = Global.ObjectMgr.GetGOData(guid); + GameObjectData data = Global.ObjectMgr.GetGameObjectData(guid); if (data == null) { Log.outError(LogFilter.Sql, "`game_event_gameobject` contains gameobject (GUID: {0}) not found in `gameobject` table.", guid); @@ -773,7 +773,7 @@ namespace Game uint entry = 0; CreatureData data = Global.ObjectMgr.GetCreatureData(guid); if (data != null) - entry = data.id; + entry = data.Id; VendorItem vItem = new VendorItem(); vItem.item = result.Read(2); @@ -1103,7 +1103,7 @@ namespace Game // get the creature data from the low guid to get the entry, to be able to find out the whole guid CreatureData data = Global.ObjectMgr.GetCreatureData(guid); if (data != null) - creaturesByMap.Add(data.mapid, guid); + creaturesByMap.Add(data.spawnPoint.GetMapId(), guid); } foreach (var key in creaturesByMap.Keys) @@ -1170,9 +1170,9 @@ namespace Game Global.ObjectMgr.AddCreatureToGrid(guid, data); // Spawn if necessary (loaded grids only) - Map map = Global.MapMgr.FindMap(data.mapid, 0); + Map map = Global.MapMgr.FindMap(data.spawnPoint.GetMapId(), 0); // We use spawn coords to spawn - if (map != null && !map.Instanceable() && map.IsGridLoaded(data.posX, data.posY)) + if (map != null && !map.Instanceable() && map.IsGridLoaded(data.spawnPoint)) Creature.CreateCreatureFromDB(guid, map); } } @@ -1187,15 +1187,15 @@ namespace Game foreach (var guid in mGameEventGameobjectGuids[internal_event_id]) { // Add to correct cell - GameObjectData data = Global.ObjectMgr.GetGOData(guid); + GameObjectData data = Global.ObjectMgr.GetGameObjectData(guid); if (data != null) { Global.ObjectMgr.AddGameObjectToGrid(guid, data); // Spawn if necessary (loaded grids only) // this base map checked as non-instanced and then only existed - Map map = Global.MapMgr.FindMap(data.mapid, 0); + Map map = Global.MapMgr.FindMap(data.spawnPoint.GetMapId(), 0); // We use current coords to unspawn, not spawn coords since creature can have changed grid - if (map != null && !map.Instanceable() && map.IsGridLoaded(data.posX, data.posY)) + if (map != null && !map.Instanceable() && map.IsGridLoaded(data.spawnPoint)) { GameObject pGameobject = GameObject.CreateGameObjectFromDB(guid, map, false); // @todo find out when it is add to map @@ -1243,7 +1243,7 @@ namespace Game { Global.ObjectMgr.RemoveCreatureFromGrid(guid, data); - Global.MapMgr.DoForAllMapsWithMapId(data.mapid, map => + Global.MapMgr.DoForAllMapsWithMapId(data.spawnPoint.GetMapId(), map => { var creatureBounds = map.GetCreatureBySpawnIdStore().LookupByKey(guid); foreach (var creature in creatureBounds) @@ -1265,12 +1265,12 @@ namespace Game if (event_id > 0 && HasGameObjectActiveEventExcept(guid, (ushort)event_id)) continue; // Remove the gameobject from grid - GameObjectData data = Global.ObjectMgr.GetGOData(guid); + GameObjectData data = Global.ObjectMgr.GetGameObjectData(guid); if (data != null) { Global.ObjectMgr.RemoveGameObjectFromGrid(guid, data); - Global.MapMgr.DoForAllMapsWithMapId(data.mapid, map => + Global.MapMgr.DoForAllMapsWithMapId(data.spawnPoint.GetMapId(), map => { var gameobjectBounds = map.GetGameObjectBySpawnIdStore().LookupByKey(guid); foreach (var go in gameobjectBounds) @@ -1300,7 +1300,7 @@ namespace Game continue; // Update if spawned - Global.MapMgr.DoForAllMapsWithMapId(data.mapid, map => + Global.MapMgr.DoForAllMapsWithMapId(data.spawnPoint.GetMapId(), map => { var creatureBounds = map.GetCreatureBySpawnIdStore().LookupByKey(tuple.Item1); foreach (var creature in creatureBounds) @@ -1337,12 +1337,12 @@ namespace Game tuple.Item2.modelid_prev = data2.displayid; tuple.Item2.equipement_id_prev = (byte)data2.equipmentId; data2.displayid = tuple.Item2.modelid; - data2.equipmentId = tuple.Item2.equipment_id; + data2.equipmentId = (sbyte)tuple.Item2.equipment_id; } else { data2.displayid = tuple.Item2.modelid_prev; - data2.equipmentId = tuple.Item2.equipement_id_prev; + data2.equipmentId = (sbyte)tuple.Item2.equipement_id_prev; } } } diff --git a/Source/Game/Garrisons/Garrisons.cs b/Source/Game/Garrisons/Garrisons.cs index d5b3c97e6..c5ada77fd 100644 --- a/Source/Game/Garrisons/Garrisons.cs +++ b/Source/Game/Garrisons/Garrisons.cs @@ -831,7 +831,7 @@ namespace Game.Garrisons T BuildingSpawnHelper(GameObject building, ulong spawnId, Map map) where T : WorldObject, new() { T spawn = new T(); - if (!spawn.LoadFromDB(spawnId, map)) + if (!spawn.LoadFromDB(spawnId, map, false, false)) return null; float x = spawn.GetPositionX(); diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index 89edcfaa1..0f4c03f71 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -1310,7 +1310,7 @@ namespace Game case ScriptCommands.RespawnGameobject: { - GameObjectData data = GetGOData(tmp.RespawnGameObject.GOGuid); + GameObjectData data = GetGameObjectData(tmp.RespawnGameObject.GOGuid); if (data == null) { Log.outError(LogFilter.Sql, "Table `{0}` has invalid gameobject (GUID: {1}) in SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id {2}", @@ -1318,11 +1318,11 @@ namespace Game continue; } - GameObjectTemplate info = GetGameObjectTemplate(data.id); + GameObjectTemplate info = GetGameObjectTemplate(data.Id); if (info == null) { Log.outError(LogFilter.Sql, "Table `{0}` has gameobject with invalid entry (GUID: {1} Entry: {2}) in SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id {3}", - tableName, tmp.RespawnGameObject.GOGuid, data.id, tmp.id); + tableName, tmp.RespawnGameObject.GOGuid, data.Id, tmp.id); continue; } @@ -1357,7 +1357,7 @@ namespace Game case ScriptCommands.OpenDoor: case ScriptCommands.CloseDoor: { - GameObjectData data = GetGOData(tmp.ToggleDoor.GOGuid); + GameObjectData data = GetGameObjectData(tmp.ToggleDoor.GOGuid); if (data == null) { Log.outError(LogFilter.Sql, "Table `{0}` has invalid gameobject (GUID: {1}) in {2} for script id {3}", @@ -1365,11 +1365,11 @@ namespace Game continue; } - GameObjectTemplate info = GetGameObjectTemplate(data.id); + GameObjectTemplate info = GetGameObjectTemplate(data.Id); if (info == null) { Log.outError(LogFilter.Sql, "Table `{0}` has gameobject with invalid entry (GUID: {1} Entry: {2}) in {3} for script id {4}", - tableName, tmp.ToggleDoor.GOGuid, data.id, tmp.command, tmp.id); + tableName, tmp.ToggleDoor.GOGuid, data.Id, tmp.command, tmp.id); continue; } @@ -2724,7 +2724,6 @@ namespace Game linkedRespawnStorage.Clear(); // 0 1 2 SQLResult result = DB.World.Query("SELECT guid, linkedGuid, linkType FROM linked_respawn ORDER BY guid ASC"); - if (result.IsEmpty()) { Log.outError(LogFilter.ServerLoading, "Loaded 0 linked respawns. DB table `linked_respawn` is empty."); @@ -2760,8 +2759,8 @@ namespace Game break; } - MapRecord map = CliDB.MapStorage.LookupByKey(master.mapid); - if (map == null || !map.Instanceable() || (master.mapid != slave.mapid)) + MapRecord map = CliDB.MapStorage.LookupByKey(master.spawnPoint.GetMapId()); + if (map == null || !map.Instanceable() || (master.spawnPoint.GetMapId() != slave.spawnPoint.GetMapId())) { Log.outError(LogFilter.Sql, "Creature '{0}' linking to '{1}' on an unpermitted map.", guidLow, linkedGuidLow); error = true; @@ -2776,8 +2775,8 @@ namespace Game break; } - guid = ObjectGuid.Create(HighGuid.Creature, slave.mapid, slave.id, guidLow); - linkedGuid = ObjectGuid.Create(HighGuid.Creature, master.mapid, master.id, linkedGuidLow); + guid = ObjectGuid.Create(HighGuid.Creature, slave.spawnPoint.GetMapId(), slave.Id, guidLow); + linkedGuid = ObjectGuid.Create(HighGuid.Creature, master.spawnPoint.GetMapId(), master.Id, linkedGuidLow); break; } case CreatureLinkedRespawnType.CreatureToGO: @@ -2790,7 +2789,7 @@ namespace Game break; } - GameObjectData master = GetGOData(linkedGuidLow); + GameObjectData master = GetGameObjectData(linkedGuidLow); if (master == null) { Log.outError(LogFilter.Sql, "Couldn't get gameobject data for GUIDLow {0}", linkedGuidLow); @@ -2798,8 +2797,8 @@ namespace Game break; } - MapRecord map = CliDB.MapStorage.LookupByKey(master.mapid); - if (map == null || !map.Instanceable() || (master.mapid != slave.mapid)) + MapRecord map = CliDB.MapStorage.LookupByKey(master.spawnPoint.GetMapId()); + if (map == null || !map.Instanceable() || (master.spawnPoint.GetMapId() != slave.spawnPoint.GetMapId())) { Log.outError(LogFilter.Sql, "Creature '{0}' linking to '{1}' on an unpermitted map.", guidLow, linkedGuidLow); error = true; @@ -2814,13 +2813,13 @@ namespace Game break; } - guid = ObjectGuid.Create(HighGuid.Creature, slave.mapid, slave.id, guidLow); - linkedGuid = ObjectGuid.Create(HighGuid.GameObject, master.mapid, master.id, linkedGuidLow); + guid = ObjectGuid.Create(HighGuid.Creature, slave.spawnPoint.GetMapId(), slave.Id, guidLow); + linkedGuid = ObjectGuid.Create(HighGuid.GameObject, master.spawnPoint.GetMapId(), master.Id, linkedGuidLow); break; } case CreatureLinkedRespawnType.GOToGO: { - GameObjectData slave = GetGOData(guidLow); + GameObjectData slave = GetGameObjectData(guidLow); if (slave == null) { Log.outError(LogFilter.Sql, "Couldn't get gameobject data for GUIDLow {0}", guidLow); @@ -2828,7 +2827,7 @@ namespace Game break; } - GameObjectData master = GetGOData(linkedGuidLow); + GameObjectData master = GetGameObjectData(linkedGuidLow); if (master == null) { Log.outError(LogFilter.Sql, "Couldn't get gameobject data for GUIDLow {0}", linkedGuidLow); @@ -2836,8 +2835,8 @@ namespace Game break; } - MapRecord map = CliDB.MapStorage.LookupByKey(master.mapid); - if (map == null || !map.Instanceable() || (master.mapid != slave.mapid)) + MapRecord map = CliDB.MapStorage.LookupByKey(master.spawnPoint.GetMapId()); + if (map == null || !map.Instanceable() || (master.spawnPoint.GetMapId() != slave.spawnPoint.GetMapId())) { Log.outError(LogFilter.Sql, "Creature '{0}' linking to '{1}' on an unpermitted map.", guidLow, linkedGuidLow); error = true; @@ -2852,13 +2851,13 @@ namespace Game break; } - guid = ObjectGuid.Create(HighGuid.GameObject, slave.mapid, slave.id, guidLow); - linkedGuid = ObjectGuid.Create(HighGuid.GameObject, master.mapid, master.id, linkedGuidLow); + guid = ObjectGuid.Create(HighGuid.GameObject, slave.spawnPoint.GetMapId(), slave.Id, guidLow); + linkedGuid = ObjectGuid.Create(HighGuid.GameObject, master.spawnPoint.GetMapId(), master.Id, linkedGuidLow); break; } case CreatureLinkedRespawnType.GOToCreature: { - GameObjectData slave = GetGOData(guidLow); + GameObjectData slave = GetGameObjectData(guidLow); if (slave == null) { Log.outError(LogFilter.Sql, "Couldn't get gameobject data for GUIDLow {0}", guidLow); @@ -2874,8 +2873,8 @@ namespace Game break; } - MapRecord map = CliDB.MapStorage.LookupByKey(master.mapid); - if (map == null || !map.Instanceable() || (master.mapid != slave.mapid)) + MapRecord map = CliDB.MapStorage.LookupByKey(master.spawnPoint.GetMapId()); + if (map == null || !map.Instanceable() || (master.spawnPoint.GetMapId() != slave.spawnPoint.GetMapId())) { Log.outError(LogFilter.Sql, "Creature '{0}' linking to '{1}' on an unpermitted map.", guidLow, linkedGuidLow); error = true; @@ -2890,8 +2889,8 @@ namespace Game break; } - guid = ObjectGuid.Create(HighGuid.GameObject, slave.mapid, slave.id, guidLow); - linkedGuid = ObjectGuid.Create(HighGuid.Creature, master.mapid, master.id, linkedGuidLow); + guid = ObjectGuid.Create(HighGuid.GameObject, slave.spawnPoint.GetMapId(), slave.Id, guidLow); + linkedGuid = ObjectGuid.Create(HighGuid.Creature, master.spawnPoint.GetMapId(), master.Id, linkedGuidLow); break; } @@ -3231,8 +3230,8 @@ namespace Game { var time = Time.GetMSTime(); - // 0 1 2 3 4 5 6 7 8 9 10 - SQLResult result = DB.World.Query("SELECT creature.guid, id, map, modelid, equipment_id, position_x, position_y, position_z, orientation, spawntimesecs, spawndist, " + + // 0 1 2 3 4 5 6 7 8 9 10 + SQLResult result = DB.World.Query("SELECT creature.guid, id, map, position_x, position_y, position_z, orientation, modelid, equipment_id, spawntimesecs, spawndist, " + //11 12 13 14 15 16 17 18 19 20 21 "currentwaypoint, curhealth, curmana, MovementType, spawnDifficulties, eventEntry, pool_entry, creature.npcflag, creature.unit_flags, creature.unit_flags2, creature.unit_flags3, " + //22 23 24 25 26 27 @@ -3274,21 +3273,18 @@ namespace Game } CreatureData data = new CreatureData(); - data.id = entry; - data.mapid = result.Read(2); - data.displayid = result.Read(3); - data.equipmentId = result.Read(4); - data.posX = result.Read(5); - data.posY = result.Read(6); - data.posZ = result.Read(7); - data.orientation = result.Read(8); - data.spawntimesecs = result.Read(9); + data.spawnId = guid; + data.Id = entry; + data.spawnPoint = new WorldLocation(result.Read(2), result.Read(3), result.Read(4), result.Read(5), result.Read(6)); + data.displayid = result.Read(7); + data.equipmentId = result.Read(8); + data.spawntimesecs = result.Read(9); data.spawndist = result.Read(10); data.currentwaypoint = result.Read(11); data.curhealth = result.Read(12); data.curmana = result.Read(13); data.movementType = result.Read(14); - data.spawnDifficulties = ParseSpawnDifficulties(result.Read(15), "creature", guid, data.mapid, spawnMasks.LookupByKey(data.mapid)); + data.spawnDifficulties = ParseSpawnDifficulties(result.Read(15), "creature", guid, data.spawnPoint.GetMapId(), spawnMasks.LookupByKey(data.spawnPoint.GetMapId())); short gameEvent = result.Read(16); uint PoolId = result.Read(17); data.npcflag = result.Read(18); @@ -3301,11 +3297,12 @@ namespace Game data.phaseGroup = result.Read(25); data.terrainSwapMap = result.Read(26); data.ScriptId = GetScriptId(result.Read(27)); + data.spawnGroupData = _spawnGroupDataStorage[0]; - var mapEntry = CliDB.MapStorage.LookupByKey(data.mapid); + var mapEntry = CliDB.MapStorage.LookupByKey(data.spawnPoint.GetMapId()); if (mapEntry == null) { - Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0}) that spawned at not existed map (Id: {1}), skipped.", guid, data.mapid); + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0}) that spawned at not existed map (Id: {1}), skipped.", guid, data.spawnPoint.GetMapId()); continue; } @@ -3318,9 +3315,9 @@ namespace Game bool ok = true; for (uint diff = 0; diff < SharedConst.MaxCreatureDifficulties && ok; ++diff) { - if (_difficultyEntries[diff].Contains(data.id)) + if (_difficultyEntries[diff].Contains(data.Id)) { - Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0}) that listed as difficulty {1} template (entry: {2}) in `creaturetemplate`, skipped.", guid, diff + 1, data.id); + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0}) that listed as difficulty {1} template (entry: {2}) in `creaturetemplate`, skipped.", guid, diff + 1, data.Id); ok = false; } } @@ -3330,9 +3327,9 @@ namespace Game // -1 random, 0 no equipment, if (data.equipmentId != 0) { - if (GetEquipmentInfo(data.id, data.equipmentId) == null) + if (GetEquipmentInfo(data.Id, data.equipmentId) == null) { - Log.outError(LogFilter.Sql, "Table `creature` have creature (Entry: {0}) with equipmentid {1} not found in table `creatureequiptemplate`, set to no equipment.", data.id, data.equipmentId); + Log.outError(LogFilter.Sql, "Table `creature` have creature (Entry: {0}) with equipmentid {1} not found in table `creatureequiptemplate`, set to no equipment.", data.Id, data.equipmentId); data.equipmentId = 0; } } @@ -3341,19 +3338,19 @@ namespace Game { if (!mapEntry.IsDungeon()) Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `creature_template`.`flagsextra` including CREATUREFLAGEXTRAINSTANCEBIND " + - "but creature are not in instance.", guid, data.id); + "but creature are not in instance.", guid, data.Id); } if (data.spawndist < 0.0f) { - Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `spawndist`< 0, set to 0.", guid, data.id); + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `spawndist`< 0, set to 0.", guid, data.Id); data.spawndist = 0.0f; } else if (data.movementType == (byte)MovementGeneratorType.Random) { if (MathFunctions.fuzzyEq(data.spawndist, 0.0f)) { - Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `MovementType`=1 (random movement) but with `spawndist`=0, replace by idle movement type (0).", guid, data.id); + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `MovementType`=1 (random movement) but with `spawndist`=0, replace by idle movement type (0).", guid, data.Id); data.movementType = (byte)MovementGeneratorType.Idle; } } @@ -3361,33 +3358,27 @@ namespace Game { if (data.spawndist != 0.0f) { - Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `MovementType`=0 (idle) have `spawndist`<>0, set to 0.", guid, data.id); + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `MovementType`=0 (idle) have `spawndist`<>0, set to 0.", guid, data.Id); data.spawndist = 0.0f; } } - if (Math.Abs(data.orientation) > 2 * MathFunctions.PI) - { - Log.outError(LogFilter.Sql, "Table `creature` has creature (GUID: {0} Entry: {1}) with abs(`orientation`) > 2*PI (orientation is expressed in radians), normalized.", guid, data.id); - data.orientation = Position.NormalizeOrientation(data.orientation); - } - if (Convert.ToBoolean(data.phaseUseFlags & ~PhaseUseFlagsValues.All)) { - Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) has unknown `phaseUseFlags` set, removed unknown value.", guid, data.id); + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) has unknown `phaseUseFlags` set, removed unknown value.", guid, data.Id); data.phaseUseFlags &= PhaseUseFlagsValues.All; } if (data.phaseUseFlags.HasAnyFlag(PhaseUseFlagsValues.AlwaysVisible) && data.phaseUseFlags.HasAnyFlag(PhaseUseFlagsValues.Inverse)) { Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) has both `phaseUseFlags` PHASE_USE_FLAGS_ALWAYS_VISIBLE and PHASE_USE_FLAGS_INVERSE," + - " removing PHASE_USE_FLAGS_INVERSE.", guid, data.id); + " removing PHASE_USE_FLAGS_INVERSE.", guid, data.Id); data.phaseUseFlags &= ~PhaseUseFlagsValues.Inverse; } if (data.phaseGroup != 0 && data.phaseId != 0) { - Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with both `phaseid` and `phasegroup` set, `phasegroup` set to 0", guid, data.id); + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with both `phaseid` and `phasegroup` set, `phasegroup` set to 0", guid, data.Id); data.phaseGroup = 0; } @@ -3395,7 +3386,7 @@ namespace Game { if (!CliDB.PhaseStorage.ContainsKey(data.phaseId)) { - Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `phaseid` {2} does not exist, set to 0", guid, data.id, data.phaseId); + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `phaseid` {2} does not exist, set to 0", guid, data.Id, data.phaseId); data.phaseId = 0; } } @@ -3404,7 +3395,7 @@ namespace Game { if (Global.DB2Mgr.GetPhasesForGroup(data.phaseGroup).Empty()) { - Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `phasegroup` {2} does not exist, set to 0", guid, data.id, data.phaseGroup); + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `phasegroup` {2} does not exist, set to 0", guid, data.Id, data.phaseGroup); data.phaseGroup = 0; } } @@ -3414,12 +3405,12 @@ namespace Game MapRecord terrainSwapEntry = CliDB.MapStorage.LookupByKey(data.terrainSwapMap); if (terrainSwapEntry == null) { - Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `terrainSwapMap` {2} does not exist, set to -1", guid, data.id, data.terrainSwapMap); + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `terrainSwapMap` {2} does not exist, set to -1", guid, data.Id, data.terrainSwapMap); data.terrainSwapMap = -1; } - else if (terrainSwapEntry.ParentMapID != data.mapid) + else if (terrainSwapEntry.ParentMapID != data.spawnPoint.GetMapId()) { - Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `terrainSwapMap` {2} which cannot be used on spawn map, set to -1", guid, data.id, data.terrainSwapMap); + Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0} Entry: {1}) with `terrainSwapMap` {2} which cannot be used on spawn map, set to -1", guid, data.Id, data.terrainSwapMap); data.terrainSwapMap = -1; } } @@ -3427,7 +3418,7 @@ namespace Game if (WorldConfig.GetBoolValue(WorldCfg.CalculateCreatureZoneAreaData)) { PhasingHandler.InitDbVisibleMapId(phaseShift, data.terrainSwapMap); - Global.MapMgr.GetZoneAndAreaId(phaseShift, out uint zoneId, out uint areaId, data.mapid, data.posX, data.posY, data.posZ); + Global.MapMgr.GetZoneAndAreaId(phaseShift, out uint zoneId, out uint areaId, data.spawnPoint); PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_CREATURE_ZONE_AREA_DATA); stmt.AddValue(0, zoneId); @@ -3452,8 +3443,8 @@ namespace Game { foreach (Difficulty difficulty in data.spawnDifficulties) { - CellCoord cellCoord = GridDefines.ComputeCellCoord(data.posX, data.posY); - var cellguids = CreateCellObjectGuids(data.mapid, difficulty, cellCoord.GetId()); + CellCoord cellCoord = GridDefines.ComputeCellCoord(data.spawnPoint.GetPositionX(), data.spawnPoint.GetPositionY()); + var cellguids = CreateCellObjectGuids(data.spawnPoint.GetMapId(), difficulty, cellCoord.GetId()); cellguids.creatures.Add(guid); } } @@ -3461,8 +3452,8 @@ namespace Game { foreach (Difficulty difficulty in data.spawnDifficulties) { - CellCoord cellCoord = GridDefines.ComputeCellCoord(data.posX, data.posY); - CellObjectGuids cellguids = GetCellObjectGuids(data.mapid, difficulty, cellCoord.GetId()); + CellCoord cellCoord = GridDefines.ComputeCellCoord(data.spawnPoint.GetPositionX(), data.spawnPoint.GetPositionY()); + CellObjectGuids cellguids = GetCellObjectGuids(data.spawnPoint.GetMapId(), difficulty, cellCoord.GetId()); if (cellguids == null) return; @@ -3483,14 +3474,15 @@ namespace Game CreatureLevelScaling scaling = cInfo.GetLevelScaling(map.GetDifficultyID()); - ulong guid = GenerateCreatureSpawnId(); - CreatureData data = NewOrExistCreatureData(guid); - data.id = entry; - data.mapid = (ushort)mapId; + ulong spawnId = GenerateCreatureSpawnId(); + CreatureData data = NewOrExistCreatureData(spawnId); + data.spawnId = spawnId; + data.Id = entry; + data.spawnPoint.WorldRelocate(mapId, pos); data.displayid = 0; data.equipmentId = 0; - pos.GetPosition(out data.posX, out data.posY, out data.posZ, out data.orientation); - data.spawntimesecs = spawntimedelay; + + data.spawntimesecs = (int)spawntimedelay; data.spawndist = 0; data.currentwaypoint = 0; data.curhealth = (uint)(Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureHealth, level, cInfo.HealthScalingExpansion, scaling.ContentTuningID, (Class)cInfo.UnitClass) * cInfo.ModHealth * cInfo.ModHealthExtra); @@ -3501,13 +3493,14 @@ namespace Game data.npcflag = (uint)cInfo.Npcflag; data.unit_flags = (uint)cInfo.UnitFlags; data.dynamicflags = cInfo.DynamicFlags; + data.spawnGroupData = GetLegacySpawnGroup(); - AddCreatureToGrid(guid, data); + AddCreatureToGrid(spawnId, data); // We use spawn coords to spawn - if (!map.Instanceable() && !map.IsRemovalGrid(data.posX, data.posY)) + if (!map.Instanceable() && !map.IsRemovalGrid(data.spawnPoint)) { - Creature creature = Creature.CreateCreatureFromDB(guid, map); + Creature creature = Creature.CreateCreatureFromDB(spawnId, map, true, true); if (!creature) { Log.outError(LogFilter.Server, "AddCreature: Cannot add creature entry {0} to map", entry); @@ -3515,7 +3508,7 @@ namespace Game } } - return guid; + return spawnId; } public List GetCreatureQuestItemList(uint id) { @@ -3562,7 +3555,7 @@ namespace Game return false; CreatureData master = GetCreatureData(guidLow); - ObjectGuid guid = ObjectGuid.Create(HighGuid.Creature, master.mapid, master.id, guidLow); + ObjectGuid guid = ObjectGuid.Create(HighGuid.Creature, master.spawnPoint.GetMapId(), master.Id, guidLow); PreparedStatement stmt; if (linkedGuidLow == 0) // we're removing the linking @@ -3581,8 +3574,8 @@ namespace Game return false; } - MapRecord map = CliDB.MapStorage.LookupByKey(master.mapid); - if (map == null || !map.Instanceable() || (master.mapid != slave.mapid)) + MapRecord map = CliDB.MapStorage.LookupByKey(master.spawnPoint.GetMapId()); + if (map == null || !map.Instanceable() || (master.spawnPoint.GetMapId() != slave.spawnPoint.GetMapId())) { Log.outError(LogFilter.Sql, "Creature '{0}' linking to '{1}' on an unpermitted map.", guidLow, linkedGuidLow); return false; @@ -3595,7 +3588,7 @@ namespace Game return false; } - ObjectGuid linkedGuid = ObjectGuid.Create(HighGuid.Creature, slave.mapid, slave.id, linkedGuidLow); + ObjectGuid linkedGuid = ObjectGuid.Create(HighGuid.Creature, slave.spawnPoint.GetMapId(), slave.Id, linkedGuidLow); linkedRespawnStorage[guid] = linkedGuid; stmt = DB.World.GetPreparedStatement(WorldStatements.REP_CREATURE_LINKED_RESPAWN); @@ -3614,8 +3607,11 @@ namespace Game { CreatureData data = GetCreatureData(guid); if (data != null) + { RemoveCreatureFromGrid(guid, data); - + OnDeleteSpawnData(data); + } + creatureDataStorage.Remove(guid); } public CreatureBaseStats GetCreatureBaseStats(uint level, uint unitClass) @@ -3919,14 +3915,14 @@ namespace Game gameObjectAddon.WorldEffectID = 0; } - _gameObjectTemplateAddonStore[entry] = gameObjectAddon; + _gameObjectTemplateAddonStorage[entry] = gameObjectAddon; ++count; } while (result.NextRow()); Log.outInfo(LogFilter.ServerLoading, "Loaded {0} game object template addons in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } - public void LoadGameobjects() + public void LoadGameObjects() { var time = Time.GetMSTime(); // 0 1 2 3 4 5 6 @@ -3993,28 +3989,26 @@ namespace Game } GameObjectData data = new GameObjectData(); - data.id = entry; - data.mapid = result.Read(2); - data.posX = result.Read(3); - data.posY = result.Read(4); - data.posZ = result.Read(5); - data.orientation = result.Read(6); + data.spawnId = guid; + data.Id = entry; + data.spawnPoint = new WorldLocation(result.Read(2), result.Read(3), result.Read(4), result.Read(5), result.Read(6)); data.rotation.X = result.Read(7); data.rotation.Y = result.Read(8); data.rotation.Z = result.Read(9); data.rotation.W = result.Read(10); data.spawntimesecs = result.Read(11); + data.spawnGroupData = _spawnGroupDataStorage[0]; - var mapEntry = CliDB.MapStorage.LookupByKey(data.mapid); + var mapEntry = CliDB.MapStorage.LookupByKey(data.spawnPoint.GetMapId()); if (mapEntry == null) { - Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) spawned on a non-existed map (Id: {2}), skip", guid, data.id, data.mapid); + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) spawned on a non-existed map (Id: {2}), skip", guid, data.Id, data.spawnPoint.GetMapId()); continue; } if (data.spawntimesecs == 0 && gInfo.IsDespawnAtAction()) { - Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with `spawntimesecs` (0) value, but the gameobejct is marked as despawnable at action.", guid, data.id); + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with `spawntimesecs` (0) value, but the gameobejct is marked as despawnable at action.", guid, data.Id); } data.animprogress = result.Read(12); @@ -4025,13 +4019,13 @@ namespace Game { if (gInfo.type != GameObjectTypes.Transport || gostate > (int)GameObjectState.TransportActive + SharedConst.MaxTransportStopFrames) { - Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid `state` ({2}) value, skip", guid, data.id, gostate); + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid `state` ({2}) value, skip", guid, data.Id, gostate); continue; } } - data.go_state = (GameObjectState)gostate; + data.goState = (GameObjectState)gostate; - data.spawnDifficulties = ParseSpawnDifficulties(result.Read(14), "gameobject", guid, data.mapid, spawnMasks.LookupByKey(data.mapid)); + data.spawnDifficulties = ParseSpawnDifficulties(result.Read(14), "gameobject", guid, data.spawnPoint.GetMapId(), spawnMasks.LookupByKey(data.spawnPoint.GetMapId())); if (data.spawnDifficulties.Empty()) { Log.outError(LogFilter.Sql, $"Table `creature` has creature (GUID: {guid}) that is not spawned in any difficulty, skipped."); @@ -4046,20 +4040,20 @@ namespace Game if (Convert.ToBoolean(data.phaseUseFlags & ~PhaseUseFlagsValues.All)) { - Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) has unknown `phaseUseFlags` set, removed unknown value.", guid, data.id); + Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) has unknown `phaseUseFlags` set, removed unknown value.", guid, data.Id); data.phaseUseFlags &= PhaseUseFlagsValues.All; } if (data.phaseUseFlags.HasAnyFlag(PhaseUseFlagsValues.AlwaysVisible) && data.phaseUseFlags.HasAnyFlag(PhaseUseFlagsValues.Inverse)) { Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) has both `phaseUseFlags` PHASE_USE_FLAGS_ALWAYS_VISIBLE and PHASE_USE_FLAGS_INVERSE," + - " removing PHASE_USE_FLAGS_INVERSE.", guid, data.id); + " removing PHASE_USE_FLAGS_INVERSE.", guid, data.Id); data.phaseUseFlags &= ~PhaseUseFlagsValues.Inverse; } if (data.phaseGroup != 0 && data.phaseId != 0) { - Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with both `phaseid` and `phasegroup` set, `phasegroup` set to 0", guid, data.id); + Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with both `phaseid` and `phasegroup` set, `phasegroup` set to 0", guid, data.Id); data.phaseGroup = 0; } @@ -4067,7 +4061,7 @@ namespace Game { if (!CliDB.PhaseStorage.ContainsKey(data.phaseId)) { - Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with `phaseid` {2} does not exist, set to 0", guid, data.id, data.phaseId); + Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with `phaseid` {2} does not exist, set to 0", guid, data.Id, data.phaseId); data.phaseId = 0; } } @@ -4076,7 +4070,7 @@ namespace Game { if (Global.DB2Mgr.GetPhasesForGroup(data.phaseGroup).Empty()) { - Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with `phaseGroup` {2} does not exist, set to 0", guid, data.id, data.phaseGroup); + Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with `phaseGroup` {2} does not exist, set to 0", guid, data.Id, data.phaseGroup); data.phaseGroup = 0; } } @@ -4087,58 +4081,52 @@ namespace Game MapRecord terrainSwapEntry = CliDB.MapStorage.LookupByKey(data.terrainSwapMap); if (terrainSwapEntry == null) { - Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with `terrainSwapMap` {2} does not exist, set to -1", guid, data.id, data.terrainSwapMap); + Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with `terrainSwapMap` {2} does not exist, set to -1", guid, data.Id, data.terrainSwapMap); data.terrainSwapMap = -1; } - else if (terrainSwapEntry.ParentMapID != data.mapid) + else if (terrainSwapEntry.ParentMapID != data.spawnPoint.GetMapId()) { - Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with `terrainSwapMap` {2} which cannot be used on spawn map, set to -1", guid, data.id, data.terrainSwapMap); + Log.outError(LogFilter.Sql, "Table `gameobject` have gameobject (GUID: {0} Entry: {1}) with `terrainSwapMap` {2} which cannot be used on spawn map, set to -1", guid, data.Id, data.terrainSwapMap); data.terrainSwapMap = -1; } } data.ScriptId = GetScriptId(result.Read(21)); - if (Math.Abs(data.orientation) > 2 * MathFunctions.PI) - { - Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with abs(`orientation`) > 2*PI (orientation is expressed in radians), normalized.", guid, data.id); - data.orientation = Position.NormalizeOrientation(data.orientation); - } - if (data.rotation.X < -1.0f || data.rotation.X > 1.0f) { - Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid rotationX ({2}) value, skip", guid, data.id, data.rotation.X); + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid rotationX ({2}) value, skip", guid, data.Id, data.rotation.X); continue; } if (data.rotation.Y < -1.0f || data.rotation.Y > 1.0f) { - Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid rotationY ({2}) value, skip", guid, data.id, data.rotation.Y); + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid rotationY ({2}) value, skip", guid, data.Id, data.rotation.Y); continue; } if (data.rotation.Z < -1.0f || data.rotation.Z > 1.0f) { - Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid rotationZ ({2}) value, skip", guid, data.id, data.rotation.Z); + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid rotationZ ({2}) value, skip", guid, data.Id, data.rotation.Z); continue; } if (data.rotation.W < -1.0f || data.rotation.W > 1.0f) { - Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid rotationW ({2}) value, skip", guid, data.id, data.rotation.W); + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid rotationW ({2}) value, skip", guid, data.Id, data.rotation.W); continue; } - if (!GridDefines.IsValidMapCoord(data.mapid, data.posX, data.posY, data.posZ, data.orientation)) + if (!GridDefines.IsValidMapCoord(data.spawnPoint)) { - Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid coordinates, skip", guid, data.id); + Log.outError(LogFilter.Sql, "Table `gameobject` has gameobject (GUID: {0} Entry: {1}) with invalid coordinates, skip", guid, data.Id); continue; } if (WorldConfig.GetBoolValue(WorldCfg.CalculateGameobjectZoneAreaData)) { PhasingHandler.InitDbVisibleMapId(phaseShift, data.terrainSwapMap); - Global.MapMgr.GetZoneAndAreaId(phaseShift, out uint zoneId, out uint areaId, data.mapid, data.posX, data.posY, data.posZ); + Global.MapMgr.GetZoneAndAreaId(phaseShift, out uint zoneId, out uint areaId, data.spawnPoint); PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.UPD_GAMEOBJECT_ZONE_AREA_DATA); stmt.AddValue(0, zoneId); @@ -4164,7 +4152,6 @@ namespace Game // 0 1 2 3 4 5 6 7 SQLResult result = DB.World.Query("SELECT guid, parent_rotation0, parent_rotation1, parent_rotation2, parent_rotation3, invisibilityType, invisibilityValue, WorldEffectID FROM gameobject_addon"); - if (result.IsEmpty()) { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 gameobject addon definitions. DB table `gameobject_addon` is empty."); @@ -4176,7 +4163,7 @@ namespace Game { ulong guid = result.Read(0); - GameObjectData goData = GetGOData(guid); + GameObjectData goData = GetGameObjectData(guid); if (goData == null) { Log.outError(LogFilter.Sql, $"GameObject (GUID: {guid}) does not exist but has a record in `gameobject_addon`"); @@ -4325,8 +4312,8 @@ namespace Game { foreach (Difficulty difficulty in data.spawnDifficulties) { - CellCoord cellCoord = GridDefines.ComputeCellCoord(data.posX, data.posY); - var cellguids = CreateCellObjectGuids(data.mapid, difficulty, cellCoord.GetId()); + CellCoord cellCoord = GridDefines.ComputeCellCoord(data.spawnPoint.GetPositionX(), data.spawnPoint.GetPositionY()); + var cellguids = CreateCellObjectGuids(data.spawnPoint.GetMapId(), difficulty, cellCoord.GetId()); cellguids.gameobjects.Add(guid); } } @@ -4334,15 +4321,15 @@ namespace Game { foreach (Difficulty difficulty in data.spawnDifficulties) { - CellCoord cellCoord = GridDefines.ComputeCellCoord(data.posX, data.posY); - CellObjectGuids cellguids = GetCellObjectGuids(data.mapid, difficulty, cellCoord.GetId()); + CellCoord cellCoord = GridDefines.ComputeCellCoord(data.spawnPoint.GetPositionX(), data.spawnPoint.GetPositionY()); + CellObjectGuids cellguids = GetCellObjectGuids(data.spawnPoint.GetMapId(), difficulty, cellCoord.GetId()); if (cellguids == null) return; cellguids.gameobjects.Remove(guid); } } - public ulong AddGOData(uint entry, uint mapId, Position pos, Quaternion rot, uint spawntimedelay) + public ulong AddGameObjectData(uint entry, uint mapId, Position pos, Quaternion rot, uint spawntimedelay) { GameObjectTemplate goinfo = GetGameObjectTemplate(entry); if (goinfo == null) @@ -4352,37 +4339,37 @@ namespace Game if (map == null) return 0; - ulong guid = GenerateGameObjectSpawnId(); - GameObjectData data = new GameObjectData(); - data.id = entry; - data.mapid = (ushort)mapId; - pos.GetPosition(out data.posX, out data.posY, out data.posZ, out data.orientation); + ulong spawnId = GenerateGameObjectSpawnId(); + GameObjectData data = NewOrExistGameObjectData(spawnId); + data.spawnId = spawnId; + data.Id = entry; + data.spawnPoint.WorldRelocate(mapId, pos); data.rotation = rot; data.spawntimesecs = (int)spawntimedelay; data.animprogress = 100; data.spawnDifficulties.Add(Difficulty.None); - data.go_state = GameObjectState.Ready; + data.goState = GameObjectState.Ready; data.artKit = (byte)(goinfo.type == GameObjectTypes.ControlZone ? 21 : 0); data.dbData = false; + data.spawnGroupData = GetLegacySpawnGroup(); - NewGOData(guid, data); - AddGameObjectToGrid(guid, data); + AddGameObjectToGrid(spawnId, data); // Spawn if necessary (loaded grids only) // We use spawn coords to spawn - if (!map.Instanceable() && map.IsGridLoaded(data.posX, data.posY)) + if (!map.Instanceable() && map.IsGridLoaded(data.spawnPoint)) { - GameObject go = GameObject.CreateGameObjectFromDB(guid, map); + GameObject go = GameObject.CreateGameObjectFromDB(spawnId, map); if (!go) { - Log.outError(LogFilter.Server, "AddGOData: cannot add gameobject entry {0} to map", entry); + Log.outError(LogFilter.Server, "AddGameObjectData: cannot add gameobject entry {0} to map", entry); return 0; } } - Log.outDebug(LogFilter.Maps, "AddGOData: dbguid:{0} entry:{1} map:{2} x:{3} y:{4} z:{5} o:{6}", guid, entry, mapId, data.posX, data.posY, data.posZ, data.orientation); + Log.outDebug(LogFilter.Maps, $"AddGameObjectData: dbguid:{spawnId} entry:{entry} map:{mapId} pos:{data.spawnPoint}"); - return guid; + return spawnId; } public GameObjectAddon GetGameObjectAddon(ulong lowguid) @@ -4394,21 +4381,26 @@ namespace Game return _gameObjectQuestItemStorage.LookupByKey(id); } MultiMap GetGameObjectQuestItemMap() { return _gameObjectQuestItemStorage; } - public GameObjectData GetGOData(ulong guid) + public GameObjectData GetGameObjectData(ulong guid) { return gameObjectDataStorage.LookupByKey(guid); } - public void DeleteGOData(ulong guid) + public void DeleteGameObjectData(ulong guid) { - GameObjectData data = GetGOData(guid); + GameObjectData data = GetGameObjectData(guid); if (data != null) + { RemoveGameObjectFromGrid(guid, data); + OnDeleteSpawnData(data); + } gameObjectDataStorage.Remove(guid); } - public void NewGOData(ulong guid, GameObjectData data) + public GameObjectData NewOrExistGameObjectData(ulong guid) { - gameObjectDataStorage.Add(guid, data); + if (!gameObjectDataStorage.ContainsKey(guid)) + gameObjectDataStorage[guid] = new GameObjectData(); + return gameObjectDataStorage[guid]; } public GameObjectTemplate GetGameObjectTemplate(uint entry) { @@ -4416,7 +4408,7 @@ namespace Game } public GameObjectTemplateAddon GetGameObjectTemplateAddon(uint entry) { - return _gameObjectTemplateAddonStore.LookupByKey(entry); + return _gameObjectTemplateAddonStorage.LookupByKey(entry); } public Dictionary GetGameObjectTemplates() { @@ -5259,6 +5251,278 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} instance encounters in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } + public void LoadSpawnGroupTemplates() + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 + SQLResult result = DB.World.Query("SELECT groupId, groupName, groupFlags FROM spawn_group_template"); + if (!result.IsEmpty()) + { + do + { + uint groupId = result.Read(0); + SpawnGroupTemplateData group = new SpawnGroupTemplateData(); + group.groupId = groupId; + group.name = result.Read(1); + group.mapId = 0xFFFFFFFF; + SpawnGroupFlags flags = (SpawnGroupFlags)result.Read(2); + if (flags.HasAnyFlag(~SpawnGroupFlags.All)) + { + flags &= SpawnGroupFlags.All; + Log.outError(LogFilter.ServerLoading, $"Invalid spawn group flag {flags} on group ID {groupId} ({group.name}), reduced to valid flag {group.flags}."); + } + if (flags.HasAnyFlag(SpawnGroupFlags.System) && flags.HasAnyFlag(SpawnGroupFlags.ManualSpawn)) + { + flags &= ~SpawnGroupFlags.ManualSpawn; + Log.outError(LogFilter.ServerLoading, $"System spawn group {groupId} ({group.name}) has invalid manual spawn flag. Ignored."); + } + group.flags = flags; + group.isActive = !group.flags.HasAnyFlag(SpawnGroupFlags.ManualSpawn); + + _spawnGroupDataStorage[groupId] = group; + } while (result.NextRow()); + } + + if (!_spawnGroupDataStorage.ContainsKey(0)) + { + Log.outError(LogFilter.ServerLoading, "Default spawn group (index 0) is missing from DB! Manually inserted."); + SpawnGroupTemplateData data = new SpawnGroupTemplateData(); + data.groupId = 0; + data.name = "Default Group"; + data.mapId = 0; + data.flags = SpawnGroupFlags.System; + data.isActive = true; + _spawnGroupDataStorage[0] = data; + } + + if (!_spawnGroupDataStorage.ContainsKey(1)) + { + + Log.outError(LogFilter.ServerLoading, "Default legacy spawn group (index 1) is missing from DB! Manually inserted."); + SpawnGroupTemplateData data = new SpawnGroupTemplateData(); + data.groupId = 1; + data.name = "Legacy Group"; + data.mapId = 0; + data.flags = SpawnGroupFlags.System | SpawnGroupFlags.CompatibilityMode; + data.isActive = true; + _spawnGroupDataStorage[1] = data; + } + + if (!result.IsEmpty()) + Log.outInfo(LogFilter.ServerLoading, $"Loaded {_spawnGroupDataStorage.Count} spawn group templates in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); + else + Log.outError(LogFilter.ServerLoading, "Loaded 0 spawn group templates. DB table `spawn_group_template` is empty."); + } + public void LoadSpawnGroups() + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 + SQLResult result = DB.World.Query("SELECT groupId, spawnType, spawnId FROM spawn_group"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 spawn group members. DB table `spawn_group` is empty."); + return; + } + + uint numMembers = 0; + do + { + uint groupId = result.Read(0); + SpawnObjectType spawnType = (SpawnObjectType)result.Read(1); + if (spawnType >= SpawnObjectType.Max) + { + Log.outError(LogFilter.ServerLoading, $"Spawn data with invalid type {spawnType} listed for spawn group {groupId}. Skipped."); + continue; + } + ulong spawnId = result.Read(2); + + SpawnData data = GetSpawnData(spawnType, spawnId); + if (data == null) + { + Log.outError(LogFilter.ServerLoading, $"Spawn data with ID ({spawnType},{spawnId}) not found, but is listed as a member of spawn group {groupId}!"); + continue; + } + else if (data.spawnGroupData.groupId != 0) + { + Log.outError(LogFilter.ServerLoading, $"Spawn with ID ({spawnType},{spawnId}) is listed as a member of spawn group {groupId}, but is already a member of spawn group {data.spawnGroupData.groupId}. Skipping."); + continue; + } + var groupTemplate = _spawnGroupDataStorage.LookupByKey(groupId); + if (groupTemplate == null) + { + Log.outError(LogFilter.ServerLoading, $"Spawn group {groupId} assigned to spawn ID ({spawnType},{spawnId}), but group is found!"); + continue; + } + else + { + if (groupTemplate.mapId == 0xFFFFFFFF) + groupTemplate.mapId = data.spawnPoint.GetMapId(); + else if (groupTemplate.mapId != data.spawnPoint.GetMapId() && !groupTemplate.flags.HasAnyFlag(SpawnGroupFlags.System)) + { + Log.outError(LogFilter.ServerLoading, $"Spawn group {groupId} has map ID {groupTemplate.mapId}, but spawn ({spawnType},{spawnId}) has map id {data.spawnPoint.GetMapId()} - spawn NOT added to group!"); + continue; + } + data.spawnGroupData = groupTemplate; + if (!groupTemplate.flags.HasAnyFlag(SpawnGroupFlags.System)) + _spawnGroupMapStorage.Add(groupId, data); + ++numMembers; + } + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, $"Loaded {numMembers} spawn group members in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); + } + void OnDeleteSpawnData(SpawnData data) + { + var templateIt = _spawnGroupDataStorage.LookupByKey(data.spawnGroupData.groupId); + Cypher.Assert(templateIt != null, $"Creature data for ({data.type},{data.spawnId}) is being deleted and has invalid spawn group index {data.spawnGroupData.groupId}!"); + + if (templateIt.flags.HasAnyFlag(SpawnGroupFlags.System)) // system groups don't store their members in the map + return; + + var spawnDatas = _spawnGroupMapStorage.LookupByKey(data.spawnGroupData.groupId); + foreach (var it in spawnDatas) + { + if (it != data) + continue; + _spawnGroupMapStorage.Remove(data.spawnGroupData.groupId, it); + return; + } + + Cypher.Assert(false, $"Spawn data ({data.type},{data.spawnId}) being removed is member of spawn group {data.spawnGroupData.groupId}, but not actually listed in the lookup table for that group!"); + } + public bool SpawnGroupSpawn(uint groupId, Map map, bool ignoreRespawn, bool force = false, List spawnedObjects = null) + { + var spawnGroup = _spawnGroupDataStorage.LookupByKey(groupId); + if (spawnGroup == null || spawnGroup.flags.HasAnyFlag(SpawnGroupFlags.System)) + { + Log.outError(LogFilter.Maps, $"Tried to despawn non-existing (or system) spawn group {groupId}. Blocked."); + return false; + } + + if (!map) + { + Log.outError(LogFilter.Maps, $"Tried to despawn creature group {groupId}, but no map was supplied. Blocked."); + return false; + } + + if (spawnGroup.mapId != map.GetId()) + { + Log.outError(LogFilter.Maps, $"Tried to despawn creature group {groupId}, but supplied map is {map.GetId()}, creature group has map {spawnGroup.mapId}. Blocked."); + return false; + } + + foreach (var data in _spawnGroupMapStorage.LookupByKey(groupId)) + { + Cypher.Assert(spawnGroup.mapId == data.spawnPoint.GetMapId()); + // Check if there's already an instance spawned + if (!force) + { + WorldObject obj = map.GetWorldObjectBySpawnId(data.type, data.spawnId); + if (obj != null) + if ((data.type != SpawnObjectType.Creature) || obj.ToCreature().IsAlive()) + continue; + } + + long respawnTime = map.GetRespawnTime(data.type, data.spawnId); + if (respawnTime != 0 && respawnTime > Time.UnixTime) + { + if (!force && !ignoreRespawn) + continue; + + // we need to remove the respawn time, otherwise we'd end up double spawning + map.RemoveRespawnTime(data.type, data.spawnId, false); + } + + // don't spawn if the grid isn't loaded (will be handled in grid loader) + if (!map.IsGridLoaded(data.spawnPoint)) + continue; + + // Everything OK, now do the actual (re)spawn + switch (data.type) + { + case SpawnObjectType.Creature: + { + Creature creature = new Creature(); + if (!creature.LoadFromDB(data.spawnId, map, true, force)) + creature.Dispose(); + else if (spawnedObjects != null) + spawnedObjects.Add(creature); + break; + } + case SpawnObjectType.GameObject: + { + GameObject gameobject = new GameObject(); + if (!gameobject.LoadFromDB(data.spawnId, map, true)) + gameobject.Dispose(); + else if (spawnedObjects != null) + spawnedObjects.Add(gameobject); + break; + } + default: + Cypher.Assert(false, $"Invalid spawn type {data.type} with spawnId {data.spawnId}"); + return false; + } + } + spawnGroup.isActive = true; // start processing respawns for the group + return true; + } + public bool SpawnGroupDespawn(uint groupId, Map map, bool deleteRespawnTimes) + { + var spawnGroup = _spawnGroupDataStorage.LookupByKey(groupId); + if (spawnGroup == null || spawnGroup.flags.HasAnyFlag(SpawnGroupFlags.System)) + { + Log.outError(LogFilter.Maps, $"Tried to despawn non-existing (or system) spawn group {groupId}. Blocked."); + return false; + } + + if (!map) + { + Log.outError(LogFilter.Maps, $"Tried to despawn creature group {groupId}, but no map was supplied. Blocked."); + return false; + } + + if (spawnGroup.mapId != map.GetId()) + { + Log.outError(LogFilter.Maps, $"Tried to despawn creature group {groupId}, but supplied map is {map.GetId()}, creature group has map {spawnGroup.mapId}. Blocked."); + return false; + } + + List toUnload = new List(); // unload after iterating, otherwise iterator invalidation + foreach (var data in _spawnGroupMapStorage.LookupByKey(groupId)) + { + if (deleteRespawnTimes) + map.RemoveRespawnTime(data.type, data.spawnId); + switch (data.type) + { + case SpawnObjectType.Creature: + { + var bounds = map.GetCreatureBySpawnIdStore().LookupByKey(data.spawnId); + foreach (var creature in bounds) + toUnload.Add(creature); + break; + } + case SpawnObjectType.GameObject: + { + var bounds = map.GetGameObjectBySpawnIdStore().LookupByKey(data.spawnId); + foreach (var go in bounds) + toUnload.Add(go); + break; + } + default: + Cypher.Assert(false, $"Invalid spawn type {data.type} in spawn data with spawnId {data.spawnId}."); + return false; + } + } + + // now do the actual despawning + foreach (WorldObject obj in toUnload) + obj.AddObjectToRemoveList(); + + spawnGroup.isActive = false; // stop processing respawns for the group, too + return true; + } public InstanceTemplate GetInstanceTemplate(uint mapID) { @@ -5277,7 +5541,31 @@ namespace Game return _dungeonEncounterStorage.LookupByKey(MathFunctions.MakePair64(mapId, (uint)difficulty)); } public bool IsTransportMap(uint mapId) { return _transportMaps.Contains((ushort)mapId); } + void SetSpawnGroupActive(uint groupId, bool state) + { + var spawnGroup = _spawnGroupDataStorage.LookupByKey(groupId); + if (spawnGroup != null) + spawnGroup.isActive = state; + } + bool IsSpawnGroupActive(uint groupId) + { + var spawnGroup = _spawnGroupDataStorage.LookupByKey(groupId); + return (spawnGroup != null) && spawnGroup.isActive; + } + public SpawnGroupTemplateData GetDefaultSpawnGroup() { return _spawnGroupDataStorage.ElementAt(0).Value; } + SpawnGroupTemplateData GetLegacySpawnGroup() { return _spawnGroupDataStorage.ElementAt(1).Value; } + public SpawnData GetSpawnData(SpawnObjectType type, ulong guid) + { + if (type == SpawnObjectType.Creature) + return GetCreatureData(guid); + else if (type == SpawnObjectType.GameObject) + return GetGameObjectData(guid); + else + Cypher.Assert(false, $"Invalid spawn object type {type}"); + return null; + } + //Player public void LoadPlayerInfo() { @@ -9391,7 +9679,7 @@ namespace Game { if (_gameObjectSpawnId >= 0xFFFFFFFFFFFFFFFE) { - Log.outFatal(LogFilter.Server, "Gameobject spawn id overflow!! Can't continue, shutting down server. "); + Log.outFatal(LogFilter.Server, "GameObject spawn id overflow!! Can't continue, shutting down server. "); Global.WorldMgr.StopNow(); } return _gameObjectSpawnId++; @@ -9806,6 +10094,8 @@ namespace Game Dictionary instanceTemplateStorage = new Dictionary(); public MultiMap GraveYardStorage = new MultiMap(); List _transportMaps = new List(); + Dictionary _spawnGroupDataStorage = new Dictionary(); + MultiMap _spawnGroupMapStorage = new MultiMap(); //Spells /Skills / Phases Dictionary _phaseInfoById = new Dictionary(); @@ -9841,7 +10131,7 @@ namespace Game //GameObject Dictionary _gameObjectTemplateStorage = new Dictionary(); Dictionary gameObjectDataStorage = new Dictionary(); - Dictionary _gameObjectTemplateAddonStore = new Dictionary(); + Dictionary _gameObjectTemplateAddonStorage = new Dictionary(); Dictionary _gameObjectAddonStorage = new Dictionary(); MultiMap _gameObjectQuestItemStorage = new MultiMap(); List _gameObjectForQuestStorage = new List(); @@ -9893,9 +10183,9 @@ namespace Game ulong _equipmentSetGuid; uint _mailId; uint _hiPetNumber; - ulong _voidItemId; ulong _creatureSpawnId; ulong _gameObjectSpawnId; + ulong _voidItemId; uint[] _playerXPperLevel; Dictionary _baseXPTable = new Dictionary(); #endregion diff --git a/Source/Game/Maps/GridNotifiers.cs b/Source/Game/Maps/GridNotifiers.cs index a46b57789..f7ba5d339 100644 --- a/Source/Game/Maps/GridNotifiers.cs +++ b/Source/Game/Maps/GridNotifiers.cs @@ -2010,6 +2010,31 @@ namespace Game.Maps bool _reqAlive; } + class AnyPlayerInPositionRangeCheck : ICheck + { + public AnyPlayerInPositionRangeCheck(Position pos, float range, bool reqAlive = true) + { + _pos = pos; + _range = range; + _reqAlive = reqAlive; + } + + public bool Invoke(Player u) + { + if (_reqAlive && !u.IsAlive()) + return false; + + if (!u.IsWithinDist3d(_pos, _range)) + return false; + + return true; + } + + Position _pos; + float _range; + bool _reqAlive; + } + class NearestPlayerInObjectRangeCheck : ICheck { public NearestPlayerInObjectRangeCheck(WorldObject obj, float range) diff --git a/Source/Game/Maps/Map.cs b/Source/Game/Maps/Map.cs index de1c3c0d3..910e15c7d 100644 --- a/Source/Game/Maps/Map.cs +++ b/Source/Game/Maps/Map.cs @@ -74,6 +74,8 @@ namespace Game.Maps } } + _zonePlayerCountMap.Clear(); + //lets initialize visibility distance for map InitVisibilityDistance(); _weatherUpdateTimer = new IntervalTimer(); @@ -90,6 +92,10 @@ namespace Game.Maps { Global.ScriptMgr.OnDestroyMap(this); + // Delete all waiting spawns + // This doesn't delete from database. + DeleteRespawnInfo(); + for (var i = 0; i < i_worldObjects.Count; ++i) { WorldObject obj = i_worldObjects[i]; @@ -279,7 +285,7 @@ namespace Game.Maps m_VisibilityNotifyPeriod = Global.WorldMgr.GetVisibilityNotifyPeriodOnContinents(); } - public void AddToGrid(T obj, Cell cell)where T : WorldObject + public void AddToGrid(T obj, Cell cell) where T : WorldObject { Grid grid = GetGrid(cell.GetGridX(), cell.GetGridY()); switch (obj.GetTypeId()) @@ -429,7 +435,7 @@ namespace Game.Maps GridMaps[gx][gy] = m_parentMap.GridMaps[gx][gy]; ++rootParentTerrainMap.GridMapReference[gx][gy]; } - } + } } } @@ -475,6 +481,29 @@ namespace Game.Maps loader.LoadN(); } + void GridMarkNoUnload(uint x, uint y) + { + // First make sure this grid is loaded + float gX = (((float)x - 0.5f - MapConst.CenterGridId) * MapConst.SizeofGrids) + (MapConst.CenterGridOffset * 2); + float gY = (((float)y - 0.5f - MapConst.CenterGridId) * MapConst.SizeofGrids) + (MapConst.CenterGridOffset * 2); + Cell cell = new Cell(gX, gY); + EnsureGridLoaded(cell); + + // Mark as don't unload + var grid = GetGrid(x, y); + grid.SetUnloadExplicitLock(true); + } + + void GridUnmarkNoUnload(uint x, uint y) + { + // If grid is loaded, clear unload lock + if (IsGridLoaded(new GridCoord(x, y))) + { + var grid = GetGrid(x, y); + grid.SetUnloadExplicitLock(false); + } + } + public void LoadGrid(float x, float y) { EnsureGridLoaded(new Cell(x, y)); @@ -598,10 +627,11 @@ namespace Game.Maps return true; } - public bool IsGridLoaded(float x, float y) - { - return IsGridLoaded(GridDefines.ComputeGridCoord(x, y)); - } + public bool IsGridLoaded(uint gridId) { return IsGridLoaded(new GridCoord(gridId % MapConst.MaxGrids, gridId / MapConst.MaxGrids)); } + + public bool IsGridLoaded(float x, float y) { return IsGridLoaded(GridDefines.ComputeGridCoord(x, y)); } + + public bool IsGridLoaded(Position pos) { return IsGridLoaded(pos.GetPositionX(), pos.GetPositionY()); } public bool IsGridLoaded(GridCoord p) { @@ -637,6 +667,20 @@ namespace Game.Maps } } + public void UpdatePlayerZoneStats(uint oldZone, uint newZone) + { + // Nothing to do if no change + if (oldZone == newZone) + return; + + if (oldZone != MapConst.InvalidZone) + { + Cypher.Assert(_zonePlayerCountMap[oldZone] != 0, $"A player left zone {oldZone} (went to {newZone}) - but there were no players in the zone!"); + --_zonePlayerCountMap[oldZone]; + } + ++_zonePlayerCountMap[newZone]; + } + public virtual void Update(uint diff) { _dynamicTree.Update(diff); @@ -653,9 +697,18 @@ namespace Game.Maps } } + /// process any due respawns + if (_respawnCheckTimer <= diff) + { + ProcessRespawns(); + _respawnCheckTimer = WorldConfig.GetUIntValue(WorldCfg.RespawnMinCheckIntervalMs); + } + else + _respawnCheckTimer -= diff; + // update active cells around players and active objects ResetMarkedCells(); - + var update = new UpdaterNotifier(diff); var grid_object_update = new Visitor(update, GridMapTypeMask.AllGrid); @@ -666,10 +719,10 @@ namespace Game.Maps Player player = m_activePlayers[i]; if (!player.IsInWorld) continue; - + // update players at tick player.Update(diff); - + VisitNearbyCellsOf(player, grid_object_update, world_object_update); // If player is using far sight or mind vision, visit that object too @@ -698,7 +751,7 @@ namespace Game.Maps VisitNearbyCellsOf(c, grid_object_update, world_object_update); } } - + for (var i = 0; i < m_activeNonPlayers.Count; ++i) { WorldObject obj = m_activeNonPlayers[i]; @@ -707,7 +760,7 @@ namespace Game.Maps VisitNearbyCellsOf(obj, grid_object_update, world_object_update); } - + for (var i = 0; i < _transports.Count; ++i) { Transport transport = _transports[i]; @@ -716,7 +769,7 @@ namespace Game.Maps transport.Update(diff); } - + SendObjectUpdates(); // Process necessary scripts @@ -841,6 +894,8 @@ namespace Game.Maps public virtual void RemovePlayerFromMap(Player player, bool remove) { + // Before leaving map, update zone/area for stats + player.UpdateZone(MapConst.InvalidZone, 0); Global.ScriptMgr.OnPlayerLeaveMap(this, player); player.GetHostileRefManager().DeleteReferences(); // multithreading crashfix @@ -1167,7 +1222,7 @@ namespace Game.Maps { _creatureToMoveLock = true; - for (var i = 0; i< creaturesToMove.Count; ++i) + for (var i = 0; i < creaturesToMove.Count; ++i) { Creature creature = creaturesToMove[i]; if (creature.GetMap() != this) //pet is teleported to another map @@ -1493,7 +1548,7 @@ namespace Game.Maps // if in same cell then none do if (old_cell.DiffCell(new_cell)) { - Log.outDebug(LogFilter.Maps, "AreaTrigger ({0}) moved in grid[{0}, {1}] from cell[{2}, {3}] to cell[{4}, {5}].", at.GetGUID().ToString(), old_cell.GetGridX(), old_cell.GetGridY(), + Log.outDebug(LogFilter.Maps, "AreaTrigger ({0}) moved in grid[{0}, {1}] from cell[{2}, {3}] to cell[{4}, {5}].", at.GetGUID().ToString(), old_cell.GetGridX(), old_cell.GetGridY(), old_cell.GetCellX(), old_cell.GetCellY(), new_cell.GetCellX(), new_cell.GetCellY()); RemoveFromGrid(at, old_cell); @@ -1810,6 +1865,16 @@ namespace Game.Maps return mapHeight; // explicitly use map data } + public float GetHeight(PhaseShift phaseShift, float x, float y, float z, bool vmap = true, float maxSearchDist = MapConst.DefaultHeightSearch) + { + return Math.Max(GetStaticHeight(phaseShift, x, y, z, vmap, maxSearchDist), GetGameObjectFloor(phaseShift, x, y, z, maxSearchDist)); + } + + public float GetHeight(PhaseShift phaseShift, Position pos, bool vmap = true, float maxSearchDist = MapConst.DefaultHeightSearch) + { + return GetHeight(phaseShift, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), vmap, maxSearchDist); + } + public float GetMinHeight(PhaseShift phaseShift, float x, float y) { GridMap grid = GetGridMap(PhasingHandler.GetTerrainMapId(phaseShift, this, x, y), x, y); @@ -1974,6 +2039,8 @@ namespace Game.Maps return areaId; } + public uint GetAreaId(PhaseShift phaseShift, Position pos) { return GetAreaId(phaseShift, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } + public uint GetZoneId(PhaseShift phaseShift, float x, float y, float z) { uint areaId = GetAreaId(phaseShift, x, y, z); @@ -1985,6 +2052,8 @@ namespace Game.Maps return areaId; } + public uint GetZoneId(PhaseShift phaseShift, Position pos) { return GetZoneId(phaseShift, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } + public void GetZoneAndAreaId(PhaseShift phaseShift, out uint zoneid, out uint areaid, float x, float y, float z) { areaid = zoneid = GetAreaId(phaseShift, x, y, z); @@ -1994,6 +2063,8 @@ namespace Game.Maps zoneid = area.ParentAreaID; } + public void GetZoneAndAreaId(PhaseShift phaseShift, out uint zoneid, out uint areaid, Position pos) { GetZoneAndAreaId(phaseShift, out zoneid, out areaid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } + private byte GetTerrainType(PhaseShift phaseShift, float x, float y) { GridMap gmap = GetGridMap(PhasingHandler.GetTerrainMapId(phaseShift, this, x, y), x, y); @@ -2229,11 +2300,6 @@ namespace Game.Maps return result; } - public float GetHeight(PhaseShift phaseShift, float x, float y, float z, bool vmap = true, float maxSearchDist = MapConst.DefaultHeightSearch) - { - return Math.Max(GetStaticHeight(phaseShift, x, y, z, vmap, maxSearchDist), GetGameObjectFloor(phaseShift, x, y, z, maxSearchDist)); - } - public bool IsInWater(PhaseShift phaseShift, float x, float y, float pZ) { return Convert.ToBoolean(GetLiquidStatus(phaseShift, x, y, pZ, MapConst.MapAllLiquidTypes) & (ZLiquidStatus.InWater | ZLiquidStatus.UnderWater)); @@ -2379,6 +2445,349 @@ namespace Game.Maps } } + bool CheckRespawn(RespawnInfo info) + { + uint poolId = info.spawnId != 0 ? Global.PoolMgr.IsPartOfAPool(info.type, info.spawnId) : 0; + // First, check if there's already an instance of this object that would block the respawn + // Only do this for unpooled spawns + if (poolId == 0) + { + bool doDelete = false; + switch (info.type) + { + case SpawnObjectType.Creature: + { + // escort check for creatures only (if the world config boolean is set) + bool isEscort = false; + if (WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc) && info.type == SpawnObjectType.Creature) + { + CreatureData cdata = Global.ObjectMgr.GetCreatureData(info.spawnId); + if (cdata != null) + if (cdata.spawnGroupData.flags.HasAnyFlag(SpawnGroupFlags.EscortQuestNpc)) + isEscort = true; + } + + var range = _creatureBySpawnIdStore.LookupByKey(info.spawnId); + foreach (var creature in range) + { + if (!creature.IsAlive()) + continue; + + // escort NPCs are allowed to respawn as long as all other instances are already escorting + if (isEscort && creature.IsEscortNPC(true)) + continue; + + doDelete = true; + break; + } + break; + } + case SpawnObjectType.GameObject: + // gameobject check is simpler - they cannot be dead or escorting + if (_gameobjectBySpawnIdStore.ContainsKey(info.spawnId)) + doDelete = true; + break; + default: + Cypher.Assert(false, $"Invalid spawn type {info.type} with spawnId {info.spawnId} on map {GetId()}"); + return true; + } + if (doDelete) + { + info.respawnTime = 0; + return false; + } + } + + // next, check linked respawn time + ObjectGuid thisGUID = info.type == SpawnObjectType.GameObject ? ObjectGuid.Create(HighGuid.GameObject, GetId(), info.entry, info.spawnId) : ObjectGuid.Create(HighGuid.Creature, GetId(), info.entry, info.spawnId); + long linkedTime = GetLinkedRespawnTime(thisGUID); + if (linkedTime != 0) + { + long now = Time.UnixTime; + long respawnTime; + if (Global.ObjectMgr.GetLinkedRespawnGuid(thisGUID) == thisGUID) // never respawn, save "something" in DB + respawnTime = now + Time.Week; + else // set us to check again shortly after linked unit + respawnTime = Math.Max(now, linkedTime) + RandomHelper.URand(5, 15); + info.respawnTime = respawnTime; + return false; + } + + // now, check if we're part of a pool + if (poolId != 0) + { + // ok, part of a pool - hand off to pool logic to handle this, we're just going to remove the respawn and call it a day + if (info.type == SpawnObjectType.GameObject) + Global.PoolMgr.UpdatePool(poolId, info.spawnId); + else if (info.type == SpawnObjectType.Creature) + Global.PoolMgr.UpdatePool(poolId, info.spawnId); + else + Cypher.Assert(false, $"Invalid spawn type {info.type} (spawnid {info.spawnId}) on map {GetId()}"); + info.respawnTime = 0; + return false; + } + + // if we're a creature, see if the script objects to us spawning + if (info.type == SpawnObjectType.Creature) + { + if (!Global.ScriptMgr.CanSpawn(info.spawnId, info.entry, Global.ObjectMgr.GetCreatureData(info.spawnId), this)) + { // if a script blocks our respawn, schedule next check in a little bit + info.respawnTime = Time.UnixTime + RandomHelper.URand(4, 7); + return false; + } + } + return true; + } + + void DoRespawn(SpawnObjectType type, ulong spawnId, uint gridId) + { + if (!IsGridLoaded(gridId)) // if grid isn't loaded, this will be processed in grid load handler + return; + + switch (type) + { + case SpawnObjectType.Creature: + { + Creature obj = new Creature(); + if (!obj.LoadFromDB(spawnId, this, true, true)) + obj.Dispose(); + break; + } + case SpawnObjectType.GameObject: + { + GameObject obj = new GameObject(); + if (!obj.LoadFromDB(spawnId, this, true)) + obj.Dispose(); + break; + } + default: + Cypher.Assert(false, $"Invalid spawn type {type} (spawnid {spawnId}) on map {GetId()}"); + break; + } + } + + void Respawn(RespawnInfo info, bool force = false, SQLTransaction dbTrans = null) + { + if (!force && !CheckRespawn(info)) + { + if (info.respawnTime != 0) + SaveRespawnTime(info.type, info.spawnId, info.entry, info.respawnTime, info.zoneId, info.gridId, true, true, dbTrans); + else + RemoveRespawnTime(info); + + return; + } + + // remove the actual respawn record first - since this deletes it, we save what we need + SpawnObjectType type = info.type; + uint gridId = info.gridId; + ulong spawnId = info.spawnId; + RemoveRespawnTime(info); + DoRespawn(type, spawnId, gridId); + } + + void Respawn(List respawnData, bool force, SQLTransaction dbTrans) + { + SQLTransaction trans = dbTrans != null ? dbTrans : new SQLTransaction(); + foreach (RespawnInfo info in respawnData) + Respawn(info, force, trans); + + if (dbTrans == null) + DB.Characters.CommitTransaction(trans); + } + + void AddRespawnInfo(RespawnInfo info, bool replace) + { + if (info.spawnId == 0) + return; + + var bySpawnIdMap = GetRespawnMapForType(info.type); + + var existing = bySpawnIdMap.LookupByKey(info.spawnId); + if (existing != null) // spawnid already has a respawn scheduled + { + if (replace || info.respawnTime < existing.respawnTime) // delete existing in this case + DeleteRespawnInfo(existing); + else // don't delete existing, instead replace respawn time so caller saves the correct time + { + info.respawnTime = existing.respawnTime; + return; + } + } + + // if we get to this point, we should insert the respawninfo (there either was no prior entry, or it was deleted already) + RespawnInfo ri = new RespawnInfo(info); + _respawnTimes.Add(ri); + bySpawnIdMap.Add(ri.spawnId, ri); + } + + static void PushRespawnInfoFrom(List data, Dictionary map, uint zoneId) + { + foreach (var pair in map) + if (zoneId == 0 || pair.Value.zoneId == zoneId) + data.Add(pair.Value); + } + + public void GetRespawnInfo(List respawnData, SpawnObjectTypeMask types, uint zoneId) + { + if (types.HasAnyFlag(SpawnObjectTypeMask.Creature)) + PushRespawnInfoFrom(respawnData, _creatureRespawnTimesBySpawnId, zoneId); + if (types.HasAnyFlag(SpawnObjectTypeMask.GameObject)) + PushRespawnInfoFrom(respawnData, _gameObjectRespawnTimesBySpawnId, zoneId); + } + + RespawnInfo GetRespawnInfo(SpawnObjectType type, ulong spawnId) + { + var map = GetRespawnMapForType(type); + var respawnInfo = map.LookupByKey(spawnId); + if (respawnInfo == null) + return null; + + return respawnInfo; + } + + Dictionary GetRespawnMapForType(SpawnObjectType type) { return (type == SpawnObjectType.GameObject) ? _gameObjectRespawnTimesBySpawnId : _creatureRespawnTimesBySpawnId; } + + void DeleteRespawnInfo() // delete everything + { + _respawnTimes.Clear(); + _creatureRespawnTimesBySpawnId.Clear(); + _gameObjectRespawnTimesBySpawnId.Clear(); + } + + void DeleteRespawnInfo(RespawnInfo info) + { + // Delete from all relevant containers to ensure consistency + Cypher.Assert(info != null); + + // spawnid store + GetRespawnMapForType(info.type).Remove(info.spawnId); + Cypher.Assert(GetRespawnMapForType(info.type).Count == 1, $"Respawn stores inconsistent for map {GetId()}, spawnid {info.spawnId} (type {info.type})"); + + //respawn heap + _respawnTimes.Remove(info); + } + + public void RemoveRespawnTime(RespawnInfo info, bool doRespawn = false, SQLTransaction dbTrans = null) + { + PreparedStatement stmt; + switch (info.type) + { + case SpawnObjectType.Creature: + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CREATURE_RESPAWN); + break; + case SpawnObjectType.GameObject: + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GO_RESPAWN); + break; + default: + Cypher.Assert(false, $"Invalid respawninfo type {info.type} for spawnid {info.spawnId} map {GetId()}"); + return; + } + + stmt.AddValue(0, info.spawnId); + stmt.AddValue(1, GetId()); + stmt.AddValue(2, GetInstanceId()); + DB.Characters.ExecuteOrAppend(dbTrans, stmt); + + if (doRespawn) + Respawn(info); + else + DeleteRespawnInfo(info); + } + + public void RemoveRespawnTime(List respawnData, bool doRespawn, SQLTransaction dbTrans = null) + { + SQLTransaction trans = dbTrans != null ? dbTrans : new SQLTransaction(); + foreach (RespawnInfo info in respawnData) + RemoveRespawnTime(info, doRespawn, trans); + + if (dbTrans == null) + DB.Characters.CommitTransaction(trans); + } + + public void RemoveRespawnTime(SpawnObjectTypeMask types = SpawnObjectTypeMask.All, uint zoneId = 0, bool doRespawn = false, SQLTransaction dbTrans = null) + { + List v = new List(); + GetRespawnInfo(v, types, zoneId); + if (!v.Empty()) + RemoveRespawnTime(v, doRespawn, dbTrans); + } + + public void RemoveRespawnTime(SpawnObjectType type, ulong spawnId, bool doRespawn = false, SQLTransaction dbTrans = null) + { + RespawnInfo info = GetRespawnInfo(type, spawnId); + if (info != null) + RemoveRespawnTime(info, doRespawn, dbTrans); + } + + void ProcessRespawns() + { + long now = Time.UnixTime; + while (!_respawnTimes.Empty()) + { + RespawnInfo next = _respawnTimes.First(); + if (now < next.respawnTime) // done for this tick + break; + + if (CheckRespawn(next)) // see if we're allowed to respawn + { + // ok, respawn + _respawnTimes.Remove(next); + GetRespawnMapForType(next.type).Remove(next.spawnId); + DoRespawn(next.type, next.spawnId, next.gridId); + } + else if (next.respawnTime == 0) // just remove respawn entry without rescheduling + { + _respawnTimes.Remove(next); + GetRespawnMapForType(next.type).Remove(next.spawnId); + } + else // value changed, update heap position + { + Cypher.Assert(now < next.respawnTime); // infinite loop guard + //_respawnTimes.decrease(next->handle); + } + } + } + + public void ApplyDynamicModeRespawnScaling(WorldObject obj, ulong spawnId, ref uint respawnDelay, uint mode) + { + Cypher.Assert(mode == 1); + Cypher.Assert(obj.GetMap() == this); + SpawnObjectType type; + switch (obj.GetTypeId()) + { + case TypeId.Unit: + type = SpawnObjectType.Creature; + break; + case TypeId.GameObject: + type = SpawnObjectType.GameObject; + break; + default: + return; + } + + SpawnData data = Global.ObjectMgr.GetSpawnData(type, spawnId); + if (data == null || !data.spawnGroupData.flags.HasAnyFlag(SpawnGroupFlags.DynamicSpawnRate)) + return; + + if (!_zonePlayerCountMap.ContainsKey(obj.GetZoneId())) + return; + + uint playerCount = _zonePlayerCountMap[obj.GetZoneId()]; + if (playerCount == 0) + return; + + double adjustFactor = WorldConfig.GetFloatValue(type == SpawnObjectType.GameObject ? WorldCfg.RespawnDynamicRateGameobject : WorldCfg.RespawnDynamicRateCreature) / playerCount; + if (adjustFactor >= 1.0) // nothing to do here + return; + + uint timeMinimum = WorldConfig.GetUIntValue(type == SpawnObjectType.GameObject ? WorldCfg.RespawnDynamicMinimumGameObject : WorldCfg.RespawnDynamicMinimumCreature); + if (respawnDelay <= timeMinimum) + return; + + respawnDelay = (uint)Math.Max(Math.Ceiling(respawnDelay * adjustFactor), timeMinimum); + } + public virtual void DelayedUpdate(uint diff) { for (var i = 0; i < _transports.Count; ++i) @@ -2617,64 +3026,37 @@ namespace Game.Maps m_activeNonPlayers.Remove(obj); } - public void SaveCreatureRespawnTime(ulong dbGuid, long respawnTime) + public void SaveRespawnTime(SpawnObjectType type, ulong spawnId, uint entry, long respawnTime, uint zoneId, uint gridId = 0, bool writeDB = true, bool replace = false, SQLTransaction dbTrans = null) { if (respawnTime == 0) { // Delete only - RemoveCreatureRespawnTime(dbGuid); + RemoveRespawnTime(type, spawnId, false, dbTrans); return; } - _creatureRespawnTimes[dbGuid] = respawnTime; + RespawnInfo ri = new RespawnInfo(); + ri.type = type; + ri.spawnId = spawnId; + ri.entry = entry; + ri.respawnTime = respawnTime; + ri.gridId = gridId; + ri.zoneId = zoneId; + AddRespawnInfo(ri, replace); - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_CREATURE_RESPAWN); - stmt.AddValue(0, dbGuid); + if (writeDB) + SaveRespawnTimeDB(type, spawnId, ri.respawnTime, dbTrans); // might be different from original respawn time if we didn't replace + } + + public void SaveRespawnTimeDB(SpawnObjectType type, ulong spawnId, long respawnTime, SQLTransaction dbTrans = null) + { + // Just here for support of compatibility mode + PreparedStatement stmt = DB.Characters.GetPreparedStatement((type == SpawnObjectType.GameObject) ? CharStatements.REP_GO_RESPAWN : CharStatements.REP_CREATURE_RESPAWN); + stmt.AddValue(0, spawnId); stmt.AddValue(1, respawnTime); stmt.AddValue(2, GetId()); stmt.AddValue(3, GetInstanceId()); - DB.Characters.Execute(stmt); - } - - public void RemoveCreatureRespawnTime(ulong dbGuid) - { - _creatureRespawnTimes.Remove(dbGuid); - - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CREATURE_RESPAWN); - stmt.AddValue(0, dbGuid); - stmt.AddValue(1, GetId()); - stmt.AddValue(2, GetInstanceId()); - DB.Characters.Execute(stmt); - } - - public void SaveGORespawnTime(ulong dbGuid, long respawnTime) - { - if (respawnTime == 0) - { - // Delete only - RemoveGORespawnTime(dbGuid); - return; - } - - _goRespawnTimes[dbGuid] = respawnTime; - - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_GO_RESPAWN); - stmt.AddValue(0, dbGuid); - stmt.AddValue(1, respawnTime); - stmt.AddValue(2, GetId()); - stmt.AddValue(3, GetInstanceId()); - DB.Characters.Execute(stmt); - } - - public void RemoveGORespawnTime(ulong dbGuid) - { - _goRespawnTimes.Remove(dbGuid); - - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GO_RESPAWN); - stmt.AddValue(0, dbGuid); - stmt.AddValue(1, GetId()); - stmt.AddValue(2, GetInstanceId()); - DB.Characters.Execute(stmt); + DB.Characters.ExecuteOrAppend(dbTrans, stmt); } public void LoadRespawnTimes() @@ -2690,7 +3072,10 @@ namespace Game.Maps var loguid = result.Read(0); var respawnTime = result.Read(1); - _creatureRespawnTimes[loguid] = respawnTime; + CreatureData cdata = Global.ObjectMgr.GetCreatureData(loguid); + if (cdata != null) + SaveRespawnTime(SpawnObjectType.Creature, loguid, cdata.Id, respawnTime, GetZoneId(PhasingHandler.EmptyPhaseShift, cdata.spawnPoint), GridDefines.ComputeGridCoord(cdata.spawnPoint.GetPositionX(), cdata.spawnPoint.GetPositionY()).GetId(), false); + } while (result.NextRow()); } @@ -2705,16 +3090,19 @@ namespace Game.Maps var loguid = result.Read(0); var respawnTime = result.Read(1); - _goRespawnTimes[loguid] = respawnTime; + GameObjectData godata = Global.ObjectMgr.GetGameObjectData(loguid); + if (godata != null) + SaveRespawnTime(SpawnObjectType.GameObject, loguid, godata.Id, respawnTime, GetZoneId(PhasingHandler.EmptyPhaseShift, godata.spawnPoint), GridDefines.ComputeGridCoord(godata.spawnPoint.GetPositionX(), godata.spawnPoint.GetPositionY()).GetId(), false); + } while (result.NextRow()); } } + public long GetRespawnTime(SpawnObjectType type, ulong spawnId) { return (type == SpawnObjectType.GameObject) ? GetGORespawnTime(spawnId) : GetCreatureRespawnTime(spawnId); } + public void DeleteRespawnTimes() { - _creatureRespawnTimes.Clear(); - _goRespawnTimes.Clear(); - + DeleteRespawnInfo(); DeleteRespawnTimesInDB(GetId(), GetInstanceId()); } @@ -3106,7 +3494,8 @@ namespace Game.Maps return GetGrid(p.X_coord, p.Y_coord) == null || GetGrid(p.X_coord, p.Y_coord).GetGridState() == GridState.Removal; } - + public bool IsRemovalGrid(Position pos) { return IsRemovalGrid(pos.GetPositionX(), pos.GetPositionY()); } + private bool GetUnloadLock(GridCoord p) { return GetGrid(p.X_coord, p.Y_coord).GetUnloadLock(); @@ -3320,7 +3709,7 @@ namespace Game.Maps { return _dynamicTree.GetHeight(x, y, z, maxSearchDist, phaseShift); } - + public virtual uint GetOwnerGuildId(Team team = Team.Other) { return 0; @@ -3328,12 +3717,12 @@ namespace Game.Maps public long GetCreatureRespawnTime(ulong dbGuid) { - return _creatureRespawnTimes.LookupByKey(dbGuid); + return _creatureRespawnTimesBySpawnId.ContainsKey(dbGuid) ? _creatureRespawnTimesBySpawnId[dbGuid].respawnTime : 0; } public long GetGORespawnTime(ulong dbGuid) { - return _goRespawnTimes.LookupByKey(dbGuid); + return _gameObjectRespawnTimesBySpawnId.ContainsKey(dbGuid) ? _gameObjectRespawnTimesBySpawnId[dbGuid].respawnTime : 0; } void SetTimer(uint t) @@ -3421,6 +3810,33 @@ namespace Game.Maps return go ? go.ToTransport() : null; } + Creature GetCreatureBySpawnId(ulong spawnId) + { + var bounds = GetCreatureBySpawnIdStore().LookupByKey(spawnId); + if (bounds.Empty()) + return null; + + var foundCreature = bounds.Find(creature => creature.IsAlive()); + + return foundCreature != null ? foundCreature : bounds[0]; + } + + GameObject GetGameObjectBySpawnId(ulong spawnId) + { + var bounds = GetGameObjectBySpawnIdStore().LookupByKey(spawnId); + if (bounds.Empty()) + return null; + + var foundGameObject = bounds.Find(gameobject => gameobject.IsSpawned()); + + return foundGameObject != null ? foundGameObject : bounds[0]; + } + + public WorldObject GetWorldObjectBySpawnId(SpawnObjectType type, ulong spawnId) + { + return type == SpawnObjectType.GameObject ? (WorldObject)GetGameObjectBySpawnId(spawnId) : (WorldObject)GetCreatureBySpawnId(spawnId); + } + public void Visit(Cell cell, Visitor visitor) { uint x = cell.GetGridX(); @@ -3507,7 +3923,7 @@ namespace Game.Maps return null; } - if (!summon.Create(GenerateLowGuid(HighGuid.Creature), this, entry, pos.posX, pos.posY, pos.posZ, pos.Orientation, null, vehId)) + if (!summon.Create(GenerateLowGuid(HighGuid.Creature), this, entry, pos, null, vehId)) return null; // Set the summon to the summoner's phase @@ -3554,7 +3970,7 @@ namespace Game.Maps _updateObjects.Remove(obj); } - public static implicit operator bool (Map map) + public static implicit operator bool(Map map) { return map != null; } @@ -4178,7 +4594,7 @@ namespace Game.Maps uTarget = target?.ToUnit(); break; case eScriptFlags.CastspellSourceToSource: // source . source - uSource =source?.ToUnit(); + uSource = source?.ToUnit(); uTarget = uSource; break; case eScriptFlags.CastspellTargetToTarget: // target . target @@ -4442,10 +4858,14 @@ namespace Game.Maps GridMap[][] GridMaps = new GridMap[MapConst.MaxGrids][]; ushort[][] GridMapReference = new ushort[MapConst.MaxGrids][]; - Dictionary _creatureRespawnTimes = new Dictionary(); DynamicMapTree _dynamicTree = new DynamicMapTree(); - Dictionary _goRespawnTimes = new Dictionary(); + SortedSet _respawnTimes = new SortedSet(new CompareRespawnInfo()); + Dictionary _creatureRespawnTimesBySpawnId = new Dictionary(); + Dictionary _gameObjectRespawnTimesBySpawnId = new Dictionary(); + uint _respawnCheckTimer; + Dictionary _zonePlayerCountMap = new Dictionary(); + List _transports = new List(); Grid[][] i_grids = new Grid[MapConst.MaxGrids][]; MapRecord i_mapRecord; @@ -5050,4 +5470,39 @@ namespace Game.Maps public Optional areaInfo; public Optional LiquidInfo; } + + public class RespawnInfo + { + public SpawnObjectType type; + public ulong spawnId; + public uint entry; + public long respawnTime; + public uint gridId; + public uint zoneId; + + public RespawnInfo() { } + public RespawnInfo(RespawnInfo info) + { + type = info.type; + spawnId = info.spawnId; + entry = info.entry; + respawnTime = info.respawnTime; + gridId = info.gridId; + zoneId = info.zoneId; + } + } + + struct CompareRespawnInfo : IComparer + { + public int Compare(RespawnInfo a, RespawnInfo b) + { + if (a.respawnTime != b.respawnTime) + return a.respawnTime.CompareTo(b.respawnTime); + if (a.spawnId != b.spawnId) + return a.spawnId.CompareTo(b.spawnId); + + Cypher.Assert(a.type != b.type, $"Duplicate respawn entry for spawnId ({a.type},{a.spawnId}) found!"); + return a.type.CompareTo(b.type); + } + } } \ No newline at end of file diff --git a/Source/Game/Maps/MapManager.cs b/Source/Game/Maps/MapManager.cs index 859fe27e0..34635a4a0 100644 --- a/Source/Game/Maps/MapManager.cs +++ b/Source/Game/Maps/MapManager.cs @@ -430,6 +430,16 @@ namespace Game.Entities m.GetZoneAndAreaId(phaseShift, out zoneid, out areaid, x, y, z); } + public void GetZoneAndAreaId(PhaseShift phaseShift, out uint zoneid, out uint areaid, uint mapid, Position pos) + { + GetZoneAndAreaId(phaseShift, out zoneid, out areaid, mapid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); + } + + public void GetZoneAndAreaId(PhaseShift phaseShift, out uint zoneid, out uint areaid, WorldLocation loc) + { + GetZoneAndAreaId(phaseShift, out zoneid, out areaid, loc.GetMapId(), loc); + } + public void DoForAllMaps(Action worker) { lock (_mapsLock) diff --git a/Source/Game/Maps/ObjectGridLoader.cs b/Source/Game/Maps/ObjectGridLoader.cs index 71cce97f2..4fa6ab81c 100644 --- a/Source/Game/Maps/ObjectGridLoader.cs +++ b/Source/Game/Maps/ObjectGridLoader.cs @@ -80,10 +80,46 @@ namespace Game.Maps foreach (var guid in guid_set) { T obj = new T(); - if (!obj.LoadFromDB(guid, map)) - continue; + // Don't spawn at all if there's a respawn time + if ((obj.IsTypeId(TypeId.Unit) && map.GetCreatureRespawnTime(guid) == 0) || (obj.IsTypeId(TypeId.GameObject) && map.GetGORespawnTime(guid) == 0)) + { + //TC_LOG_INFO("misc", "DEBUG: LoadHelper from table: %s for (guid: %u) Loading", table, guid); + if (obj.IsTypeId(TypeId.Unit)) + { + CreatureData cdata = Global.ObjectMgr.GetCreatureData(guid); + Cypher.Assert(cdata != null, $"Tried to load creature with spawnId {guid}, but no such creature exists."); - AddObjectHelper(cell, ref count, map, obj); + SpawnGroupTemplateData group = cdata.spawnGroupData; + // If creature in manual spawn group, don't spawn here, unless group is already active. + if (group.flags.HasFlag(SpawnGroupFlags.ManualSpawn) && !group.isActive) + continue; + + // If script is blocking spawn, don't spawn but queue for a re-check in a little bit + if (!group.flags.HasFlag(SpawnGroupFlags.CompatibilityMode) && !Global.ScriptMgr.CanSpawn(guid, cdata.Id, cdata, map)) + { + map.SaveRespawnTime(SpawnObjectType.Creature, guid, cdata.Id, Time.UnixTime + RandomHelper.URand(4, 7), map.GetZoneId(PhasingHandler.EmptyPhaseShift, cdata.spawnPoint), GridDefines.ComputeGridCoord(cdata.spawnPoint.GetPositionX(), cdata.spawnPoint.GetPositionY()).GetId(), false); + continue; + } + } + else if (obj.IsTypeId(TypeId.GameObject)) + { + // If gameobject in manual spawn group, don't spawn here, unless group is already active. + GameObjectData godata = Global.ObjectMgr.GetGameObjectData(guid); + Cypher.Assert(godata != null, $"Tried to load gameobject with spawnId {guid}, but no such object exists."); + + if (godata.spawnGroupData.flags.HasFlag(SpawnGroupFlags.ManualSpawn) && !godata.spawnGroupData.isActive) + continue; + } + + if (!obj.LoadFromDB(guid, map, false, false)) + { + obj.Dispose(); + continue; + } + AddObjectHelper(cell, ref count, map, obj); + } + else + obj.Dispose(); } } diff --git a/Source/Game/Maps/SpawnData.cs b/Source/Game/Maps/SpawnData.cs new file mode 100644 index 000000000..e3a6f6073 --- /dev/null +++ b/Source/Game/Maps/SpawnData.cs @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2012-2020 CypherCore + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Framework.Constants; +using Game.Entities; +using System.Collections.Generic; + +namespace Game.Maps +{ + public class SpawnGroupTemplateData + { + public uint groupId; + public string name; + public uint mapId; + public SpawnGroupFlags flags; + public bool isActive; + } + + public class SpawnData + { + public SpawnObjectType type; + public ulong spawnId; + public uint Id; // entry in respective _template table + public WorldLocation spawnPoint; + public PhaseUseFlagsValues phaseUseFlags; + public uint phaseId; + public uint phaseGroup; + public int terrainSwapMap = -1; + public int spawntimesecs; + public List spawnDifficulties; + public SpawnGroupTemplateData spawnGroupData; + public uint ScriptId; + public bool dbData = true; + + + public SpawnData(SpawnObjectType t) + { + type = t; + } + } +} diff --git a/Source/Game/Maps/ZoneScript.cs b/Source/Game/Maps/ZoneScript.cs index 989076eef..e1d011bee 100644 --- a/Source/Game/Maps/ZoneScript.cs +++ b/Source/Game/Maps/ZoneScript.cs @@ -22,7 +22,7 @@ namespace Game.Maps { public class ZoneScript { - public virtual uint GetCreatureEntry(ulong guidlow, CreatureData data) { return data.id; } + public virtual uint GetCreatureEntry(ulong guidlow, CreatureData data) { return data.Id; } public virtual uint GetGameObjectEntry(ulong spawnId, uint entry) { return entry; } public virtual void OnCreatureCreate(Creature creature) { } diff --git a/Source/Game/OutdoorPVP/OutdoorPvP.cs b/Source/Game/OutdoorPVP/OutdoorPvP.cs index 17864e575..b0677068f 100644 --- a/Source/Game/OutdoorPVP/OutdoorPvP.cs +++ b/Source/Game/OutdoorPVP/OutdoorPvP.cs @@ -358,7 +358,7 @@ namespace Game.PvP void AddGO(uint type, ulong guid) { - GameObjectData data = Global.ObjectMgr.GetGOData(guid); + GameObjectData data = Global.ObjectMgr.GetGameObjectData(guid); if (data == null) return; @@ -378,7 +378,7 @@ namespace Game.PvP public bool AddObject(uint type, uint entry, uint map, Position pos, Quaternion rot) { - ulong guid = Global.ObjectMgr.AddGOData(entry, map, pos, rot, 0); + ulong guid = Global.ObjectMgr.AddGameObjectData(entry, map, pos, rot, 0); if (guid != 0) { AddGO(type, guid); @@ -412,7 +412,7 @@ namespace Game.PvP return false; } - m_capturePointSpawnId = Global.ObjectMgr.AddGOData(entry, map, pos, rot, 0); + m_capturePointSpawnId = Global.ObjectMgr.AddGameObjectData(entry, map, pos, rot, 0); if (m_capturePointSpawnId == 0) return false; @@ -472,7 +472,7 @@ namespace Game.PvP gameobject.Delete(); } - Global.ObjectMgr.DeleteGOData(spawnId); + Global.ObjectMgr.DeleteGameObjectData(spawnId); m_ObjectTypes.Remove(spawnId); m_Objects.Remove(type); return true; @@ -480,7 +480,7 @@ namespace Game.PvP bool DelCapturePoint() { - Global.ObjectMgr.DeleteGOData(m_capturePointSpawnId); + Global.ObjectMgr.DeleteGameObjectData(m_capturePointSpawnId); m_capturePointSpawnId = 0; if (m_capturePoint) diff --git a/Source/Game/Pools/PoolManager.cs b/Source/Game/Pools/PoolManager.cs index 566137d85..bf7cc75ca 100644 --- a/Source/Game/Pools/PoolManager.cs +++ b/Source/Game/Pools/PoolManager.cs @@ -144,14 +144,14 @@ namespace Game uint pool_id = result.Read(1); float chance = result.Read(2); - GameObjectData data = Global.ObjectMgr.GetGOData(guid); + GameObjectData data = Global.ObjectMgr.GetGameObjectData(guid); if (data == null) { Log.outError(LogFilter.Sql, "`pool_gameobject` has a non existing gameobject spawn (GUID: {0}) defined for pool id ({1}), skipped.", guid, pool_id); continue; } - GameObjectTemplate goinfo = Global.ObjectMgr.GetGameObjectTemplate(data.id); + GameObjectTemplate goinfo = Global.ObjectMgr.GetGameObjectTemplate(data.Id); if (goinfo.type != GameObjectTypes.Chest && goinfo.type != GameObjectTypes.FishingHole && goinfo.type != GameObjectTypes.GatheringNode && @@ -534,6 +534,21 @@ namespace Game return 0; } + // Selects proper template overload to call based on passed type + public uint IsPartOfAPool(SpawnObjectType type, ulong spawnId) + { + switch (type) + { + case SpawnObjectType.Creature: + return IsPartOfAPool(spawnId); + case SpawnObjectType.GameObject: + return IsPartOfAPool(spawnId); + default: + Cypher.Assert(false, $"Invalid spawn type {type} passed to PoolMgr.IsPartOfPool (with spawnId {spawnId})"); + return 0; + } + } + public enum QuestTypes { None = 0, @@ -655,28 +670,40 @@ namespace Game if (data != null) { Global.ObjectMgr.RemoveCreatureFromGrid(guid, data); - Map map = Global.MapMgr.FindMap(data.mapid, 0); + Map map = Global.MapMgr.FindMap(data.spawnPoint.GetMapId(), 0); if (map != null && !map.Instanceable()) { var creatureBounds = map.GetCreatureBySpawnIdStore().LookupByKey(guid); foreach (var creature in creatureBounds) + { + // For dynamic spawns, save respawn time here + if (!creature.GetRespawnCompatibilityMode()) + creature.SaveRespawnTime(0, false); + creature.AddObjectToRemoveList(); + } } } break; } case "GameObject": { - var data = Global.ObjectMgr.GetGOData(guid); + var data = Global.ObjectMgr.GetGameObjectData(guid); if (data != null) { Global.ObjectMgr.RemoveGameObjectFromGrid(guid, data); - Map map = Global.MapMgr.FindMap(data.mapid, 0); + Map map = Global.MapMgr.FindMap(data.spawnPoint.GetMapId(), 0); if (map != null && !map.Instanceable()) { var gameobjectBounds = map.GetGameObjectBySpawnIdStore().LookupByKey(guid); foreach (var go in gameobjectBounds) + { + // For dynamic spawns, save respawn time here + if (!go.GetRespawnCompatibilityMode()) + go.SaveRespawnTime(0, false); + go.AddObjectToRemoveList(); + } } } break; @@ -863,24 +890,24 @@ namespace Game Global.ObjectMgr.AddCreatureToGrid(obj.guid, data); // Spawn if necessary (loaded grids only) - Map map = Global.MapMgr.FindMap(data.mapid, 0); + Map map = Global.MapMgr.FindMap(data.spawnPoint.GetMapId(), 0); // We use spawn coords to spawn - if (map != null && !map.Instanceable() && map.IsGridLoaded(data.posX, data.posY)) + if (map != null && !map.Instanceable() && map.IsGridLoaded(data.spawnPoint)) Creature.CreateCreatureFromDB(obj.guid, map); } } break; case "GameObject": { - GameObjectData data = Global.ObjectMgr.GetGOData(obj.guid); + GameObjectData data = Global.ObjectMgr.GetGameObjectData(obj.guid); if (data != null) { Global.ObjectMgr.AddGameObjectToGrid(obj.guid, data); // Spawn if necessary (loaded grids only) // this base map checked as non-instanced and then only existed - Map map = Global.MapMgr.FindMap(data.mapid, 0); + Map map = Global.MapMgr.FindMap(data.spawnPoint.GetMapId(), 0); // We use current coords to unspawn, not spawn coords since creature can have changed grid - if (map != null && !map.Instanceable() && map.IsGridLoaded(data.posX, data.posY)) + if (map != null && !map.Instanceable() && map.IsGridLoaded(data.spawnPoint)) { GameObject go = GameObject.CreateGameObjectFromDB(obj.guid, map, false); if (go) diff --git a/Source/Game/Scripting/ScriptManager.cs b/Source/Game/Scripting/ScriptManager.cs index 9a04807b4..50107fb8d 100644 --- a/Source/Game/Scripting/ScriptManager.cs +++ b/Source/Game/Scripting/ScriptManager.cs @@ -682,13 +682,36 @@ namespace Game.Scripting } //CreatureScript - public bool CanSpawn(ulong spawnId, uint entry, CreatureTemplate actTemplate, CreatureData cData, Map map) + public bool CanSpawn(ulong spawnId, uint entry, CreatureData cData, Map map) { - Cypher.Assert(actTemplate != null); + Cypher.Assert(map != null); CreatureTemplate baseTemplate = Global.ObjectMgr.GetCreatureTemplate(entry); - if (baseTemplate == null) - baseTemplate = actTemplate; + Cypher.Assert(baseTemplate != null); + + // find out which template we'd be using + CreatureTemplate actTemplate = null; + DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(map.GetDifficultyID()); + while (actTemplate == null && difficultyEntry != null) + { + int idx = CreatureTemplate.DifficultyIDToDifficultyEntryIndex(difficultyEntry.Id); + if (idx == -1) + break; + + if (baseTemplate.DifficultyEntry[idx] != 0) + { + actTemplate = Global.ObjectMgr.GetCreatureTemplate(baseTemplate.DifficultyEntry[idx]); + break; + } + + if (difficultyEntry.FallbackDifficultyID == 0) + break; + + difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficultyEntry.FallbackDifficultyID); + } + + if (actTemplate == null) + actTemplate = baseTemplate; uint scriptId = baseTemplate.ScriptID; if (cData != null && cData.ScriptId != 0) diff --git a/Source/Game/Server/WorldConfig.cs b/Source/Game/Server/WorldConfig.cs index 0a55dfae9..7fce846f3 100644 --- a/Source/Game/Server/WorldConfig.cs +++ b/Source/Game/Server/WorldConfig.cs @@ -776,6 +776,49 @@ namespace Game Values[WorldCfg.NoGrayAggroBelow] = Values[WorldCfg.NoGrayAggroAbove]; } + // Respawn Settings + Values[WorldCfg.RespawnMinCheckIntervalMs] = GetDefaultValue("Respawn.MinCheckIntervalMS", 5000); + Values[WorldCfg.RespawnDynamicMode] = GetDefaultValue("Respawn.DynamicMode", 0); + if ((int)Values[WorldCfg.RespawnDynamicMode] > 1) + { + Log.outError(LogFilter.ServerLoading, $"Invalid value for Respawn.DynamicMode ({Values[WorldCfg.RespawnDynamicMode]}). Set to 0."); + Values[WorldCfg.RespawnDynamicMode] = 0; + } + Values[WorldCfg.RespawnDynamicEscortNpc] = GetDefaultValue("Respawn.DynamicEscortNPC", true); + Values[WorldCfg.RespawnGuidWarnLevel] = GetDefaultValue("Respawn.GuidWarnLevel", 12000000); + if ((int)Values[WorldCfg.RespawnGuidWarnLevel] > 16777215) + { + Log.outError(LogFilter.ServerLoading, $"Respawn.GuidWarnLevel ({Values[WorldCfg.RespawnGuidWarnLevel]}) cannot be greater than maximum GUID (16777215). Set to 12000000."); + Values[WorldCfg.RespawnGuidWarnLevel] = 12000000; + } + Values[WorldCfg.RespawnGuidAlertLevel] = GetDefaultValue("Respawn.GuidAlertLevel", 16000000); + if ((int)Values[WorldCfg.RespawnGuidAlertLevel] > 16777215) + { + Log.outError(LogFilter.ServerLoading, $"Respawn.GuidWarnLevel ({Values[WorldCfg.RespawnGuidAlertLevel]}) cannot be greater than maximum GUID (16777215). Set to 16000000."); + Values[WorldCfg.RespawnGuidAlertLevel] = 16000000; + } + Values[WorldCfg.RespawnRestartQuietTime] = GetDefaultValue("Respawn.RestartQuietTime", 3); + if ((int)Values[WorldCfg.RespawnRestartQuietTime] > 23) + { + Log.outError(LogFilter.ServerLoading, $"Respawn.RestartQuietTime ({Values[WorldCfg.RespawnRestartQuietTime]}) must be an hour, between 0 and 23. Set to 3."); + Values[WorldCfg.RespawnRestartQuietTime] = 3; + } + Values[WorldCfg.RespawnDynamicRateCreature] = GetDefaultValue("Respawn.DynamicRateCreature", 10.0f); + if ((float)Values[WorldCfg.RespawnDynamicRateCreature] < 0.0f) + { + Log.outError(LogFilter.ServerLoading, $"Respawn.DynamicRateCreature ({Values[WorldCfg.RespawnDynamicRateCreature]}) must be positive. Set to 10."); + Values[WorldCfg.RespawnDynamicRateCreature] = 10.0f; + } + Values[WorldCfg.RespawnDynamicMinimumCreature] = GetDefaultValue("Respawn.DynamicMinimumCreature", 10); + Values[WorldCfg.RespawnDynamicRateGameobject] = GetDefaultValue("Respawn.DynamicRateGameObject", 10.0f); + if ((float)Values[WorldCfg.RespawnDynamicRateGameobject] < 0.0f) + { + Log.outError(LogFilter.ServerLoading, $"Respawn.DynamicRateGameObject ({Values[WorldCfg.RespawnDynamicRateGameobject]}) must be positive. Set to 10."); + Values[WorldCfg.RespawnDynamicRateGameobject] = 10.0f; + } + Values[WorldCfg.RespawnDynamicMinimumGameObject] = GetDefaultValue("Respawn.DynamicMinimumGameObject", 10); + Values[WorldCfg.RespawnGuidWarningFrequency] = GetDefaultValue("Respawn.WarningFrequency", 1800); + Values[WorldCfg.EnableMmaps] = GetDefaultValue("mmap.EnablePathFinding", false); Values[WorldCfg.VmapIndoorCheck] = GetDefaultValue("vmap.EnableIndoorCheck", false); diff --git a/Source/Game/Server/WorldManager.cs b/Source/Game/Server/WorldManager.cs index e098fb58a..d1d79fda2 100644 --- a/Source/Game/Server/WorldManager.cs +++ b/Source/Game/Server/WorldManager.cs @@ -49,6 +49,7 @@ namespace Game _realm = new Realm(); _worldUpdateTime = new WorldUpdateTime(); + _warnShutdownTime = Time.UnixTime; } public Player FindPlayerInZone(uint zone) @@ -92,6 +93,61 @@ namespace Game return m_motd; } + public void TriggerGuidWarning() + { + // Lock this only to prevent multiple maps triggering at the same time + lock (_guidAlertLock) + { + long gameTime = GameTime.GetGameTime(); + long today = (gameTime / Time.Day) * Time.Day; + + // Check if our window to restart today has passed. 5 mins until quiet time + while (gameTime >= (today + (WorldConfig.GetIntValue(WorldCfg.RespawnRestartQuietTime) * Time.Hour) - 1810)) + today += Time.Day; + + // Schedule restart for 30 minutes before quiet time, or as long as we have + _warnShutdownTime = today + (WorldConfig.GetIntValue(WorldCfg.RespawnRestartQuietTime) * Time.Hour) - 1800; + + _guidWarn = true; + SendGuidWarning(); + } + } + + public void TriggerGuidAlert() + { + // Lock this only to prevent multiple maps triggering at the same time + lock (_guidAlertLock) + { + DoGuidAlertRestart(); + _guidAlert = true; + _guidWarn = false; + } + } + + void DoGuidWarningRestart() + { + if (m_ShutdownTimer != 0) + return; + + ShutdownServ(1800, ShutdownMask.Restart, ShutdownExitCode.Restart); + _warnShutdownTime += Time.Hour; + } + + void DoGuidAlertRestart() + { + if (m_ShutdownTimer != 0) + return; + + ShutdownServ(300, ShutdownMask.Restart, ShutdownExitCode.Restart, _alertRestartReason); + } + + void SendGuidWarning() + { + if (m_ShutdownTimer == 0 && _guidWarn && WorldConfig.GetIntValue(WorldCfg.RespawnGuidWarningFrequency) > 0) + SendServerMessage(ServerMessageType.String, _guidWarningMsg); + _warnDiff = 0; + } + public WorldSession FindSession(uint id) { return m_sessions.LookupByKey(id); @@ -527,6 +583,9 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loading Creature Base Stats..."); Global.ObjectMgr.LoadCreatureClassLevelStats(); + Log.outInfo(LogFilter.ServerLoading, "Loading Spawn Group Templates..."); + Global.ObjectMgr.LoadSpawnGroupTemplates(); + Log.outInfo(LogFilter.ServerLoading, "Loading Creature Data..."); Global.ObjectMgr.LoadCreatures(); @@ -543,7 +602,10 @@ namespace Game Global.ObjectMgr.LoadCreatureAddons(); Log.outInfo(LogFilter.ServerLoading, "Loading GameObjects..."); - Global.ObjectMgr.LoadGameobjects(); + Global.ObjectMgr.LoadGameObjects(); + + Log.outInfo(LogFilter.ServerLoading, "Loading Spawn Group Data..."); + Global.ObjectMgr.LoadSpawnGroups(); Log.outInfo(LogFilter.ServerLoading, "Loading GameObject Addon Data..."); Global.ObjectMgr.LoadGameObjectAddons(); // must be after LoadGameObjectTemplate() and LoadGameobjects() @@ -1083,6 +1145,9 @@ namespace Game m_visibility_notify_periodInInstances = ConfigMgr.GetDefaultValue("Visibility.Notify.Period.InInstances", SharedConst.DefaultVisibilityNotifyPeriod); m_visibility_notify_periodInBGArenas = ConfigMgr.GetDefaultValue("Visibility.Notify.Period.InBGArenas", SharedConst.DefaultVisibilityNotifyPeriod); + _guidWarningMsg = WorldConfig.GetDefaultValue("Respawn.WarningMessage", "There will be an unscheduled server restart at 03:00. The server will be available again shortly after."); + _alertRestartReason = WorldConfig.GetDefaultValue("Respawn.AlertRestartReason", "Urgent Maintenance"); + string dataPath = ConfigMgr.GetDefaultValue("DataDir", "./"); if (reload) { @@ -1332,6 +1397,16 @@ namespace Game // update the instance reset times Global.InstanceSaveMgr.Update(); + // Check for shutdown warning + if (_guidWarn && !_guidAlert) + { + _warnDiff += diff; + if (GameTime.GetGameTime() >= _warnShutdownTime) + DoGuidWarningRestart(); + else if (_warnDiff > WorldConfig.GetIntValue(WorldCfg.RespawnGuidWarningFrequency) * Time.InMilliseconds) + SendGuidWarning(); + } + Global.ScriptMgr.OnWorldUpdate(diff); } @@ -2288,6 +2363,9 @@ namespace Game public CleaningFlags GetCleaningFlags() { return m_CleaningFlags; } public void SetCleaningFlags(CleaningFlags flags) { m_CleaningFlags = flags; } + public bool IsGuidWarning() { return _guidWarn; } + public bool IsGuidAlert() { return _guidAlert; } + public WorldUpdateTime GetWorldUpdateTime() { return _worldUpdateTime; } #region Fields @@ -2349,6 +2427,16 @@ namespace Game string _dataPath; WorldUpdateTime _worldUpdateTime; + + string _guidWarningMsg; + string _alertRestartReason; + + object _guidAlertLock = new object(); + + bool _guidWarn; + bool _guidAlert; + uint _warnDiff; + long _warnShutdownTime; #endregion } diff --git a/Source/Scripts/Northrend/IcecrownCitadel/IcecrownCitadel.cs b/Source/Scripts/Northrend/IcecrownCitadel/IcecrownCitadel.cs index 89c5f175b..0f21a82ba 100644 --- a/Source/Scripts/Northrend/IcecrownCitadel/IcecrownCitadel.cs +++ b/Source/Scripts/Northrend/IcecrownCitadel/IcecrownCitadel.cs @@ -96,7 +96,7 @@ namespace Scripts.Northrend.IcecrownCitadel CreatureData data = creature.GetCreatureData(); if (data != null) - creature.UpdatePosition(data.posX, data.posY, data.posZ, data.orientation); + creature.UpdatePosition(data.spawnPoint); creature.DespawnOrUnsummon(); creature.SetCorpseDelay(corpseDelay); @@ -496,7 +496,7 @@ namespace Scripts.Northrend.IcecrownCitadel Creature crusader = ObjectAccessor.GetCreature(me, instance.GetGuidData(DataTypes.CaptainArnath + i)); if (crusader) { - if (crusader.IsAlive() && crusader.GetEntry() == crusader.GetCreatureData().id) + if (crusader.IsAlive() && crusader.GetEntry() == crusader.GetCreatureData().Id) { crusader.m_Events.AddEvent(new CaptainSurviveTalk(crusader), crusader.m_Events.CalculateTime(delay)); delay += 6000; diff --git a/Source/Scripts/Northrend/IcecrownCitadel/InstanceIcecrownCitadel.cs b/Source/Scripts/Northrend/IcecrownCitadel/InstanceIcecrownCitadel.cs index 446d34023..cf978a279 100644 --- a/Source/Scripts/Northrend/IcecrownCitadel/InstanceIcecrownCitadel.cs +++ b/Source/Scripts/Northrend/IcecrownCitadel/InstanceIcecrownCitadel.cs @@ -346,7 +346,7 @@ namespace Scripts.Northrend.IcecrownCitadel // Weekly quest spawn prevention public override uint GetCreatureEntry(ulong guidLow, CreatureData data) { - uint entry = data.id; + uint entry = data.Id; switch (entry) { case CreatureIds.InfiltratorMinchar: @@ -384,13 +384,13 @@ namespace Scripts.Northrend.IcecrownCitadel case CreatureIds.ZafodBoombox: GameObjectTemplate go = Global.ObjectMgr.GetGameObjectTemplate(GameObjectIds.TheSkybreaker_A); if (go != null) - if ((TeamInInstance == Team.Alliance && data.mapid == go.MoTransport.SpawnMap) || - (TeamInInstance == Team.Horde && data.mapid != go.MoTransport.SpawnMap)) + if ((TeamInInstance == Team.Alliance && data.spawnPoint.GetMapId() == go.MoTransport.SpawnMap) || + (TeamInInstance == Team.Horde && data.spawnPoint.GetMapId() != go.MoTransport.SpawnMap)) return entry; return 0; case CreatureIds.IGBMuradinBrozebeard: - if ((TeamInInstance == Team.Alliance && data.posX > 10.0f) || - (TeamInInstance == Team.Horde && data.posX < 10.0f)) + if ((TeamInInstance == Team.Alliance && data.spawnPoint.GetPositionX() > 10.0f) || + (TeamInInstance == Team.Horde && data.spawnPoint.GetPositionX() < 10.0f)) return entry; return 0; default: diff --git a/Source/WorldServer/WorldServer.conf.dist b/Source/WorldServer/WorldServer.conf.dist index 1e0920d76..ca0448086 100644 --- a/Source/WorldServer/WorldServer.conf.dist +++ b/Source/WorldServer/WorldServer.conf.dist @@ -1661,6 +1661,122 @@ MonsterSight = 50.000000 # ################################################################################################### +################################################################################################### +# SPAWN/RESPAWN SETTINGS +# +# Respawn.MinCheckIntervalMS +# Description: Minimum time that needs to pass between respawn checks for any given map. +# Default: 5000 - 5 seconds + +Respawn.MinCheckIntervalMS = 5000 + +# +# Respawn.GuidWarnLevel +# Description: The point at which the highest guid for creatures or gameobjects in any map must reach +# before the warning logic is enabled. A restart will then be queued at the next quiet time +# The maximum guid per map is 16,777,216. So, it must be less than this value. +# Default: 12000000 - 12 million + +Respawn.GuidWarnLevel = 12000000 + +# +# Respawn.WarningMessage +# Description: This message will be periodically shown (Frequency specified by Respawn.WarningFrequency) to +# all users of the server, once the Respawn.GuidWarnLevel has been passed, and a restart scheduled. +# It's used to warn users that there will be an out of schedule server restart soon. +# Default: "There will be an unscheduled server restart at 03:00 server time. The server will be available again shortly after." + +Respawn.WarningMessage = "There will be an unscheduled server restart at 03:00. The server will be available again shortly after." + +# +# Respawn.WarningFrequency +# Description: The frequency (in seconds) that the warning message will be sent to users after a quiet time restart is triggered. +# The message will repeat each time this many seconds passed until the server is restarted. +# If set to 0, no warnings will be sent. +# Default: 1800 - (30 minutes) + +Respawn.WarningFrequency = 1800 + +# +# Respawn.GuidAlertLevel +# Description: The point at which the highest guid for creatures or gameobjects in any map must reach +# before the alert logic is enabled. A restart will then be triggered for 30 mins from that +# point. The maximum guid per map is 16,777,216. So, it must be less than this value. +# Default: 16000000 - 16 million + +Respawn.GuidAlertLevel = 16000000 + +# +# Respawn.AlertRestartReason +# Description: The shutdown reason given when the alert level is reached. The server will use a fixed time of +# 5 minutes and the reason for shutdown will be this message +# Default: "Urgent Maintenance" + +Respawn.AlertRestartReason = "Urgent Maintenance" + +# +# Respawn.RestartQuietTime +# Description: The hour at which the server will be restarted after the Respawn.GuidWarnLevel +# threshold has been reached. This can be between 0 and 23. 20 will be 8pm server time +# Default: 3 - 3am + +Respawn.RestartQuietTime = 3 + +# +# Respawn.DynamicMode +# Description: Select which mode (if any) should be used to adjust respawn of creatures. +# This will only affect creatures that have dynamic spawn rate scaling enabled in +# the spawn group table (by default, gathering nodes and quest targets with respawn time <30min +# 1 - Use number of players in zone +# Default: 0 - No dynamic respawn function + +Respawn.DynamicMode = 0 + +# +# Respawn.DynamicEscortNPC +# Description: This switch controls the dynamic respawn system for escort NPCs not in instancable maps (base maps only). +# This will cause the respawn timer to begin when an escort event begins, and potentially +# allow multiple instances of the NPC to be alive at the same time (when combined with Respawn.DynamicMode > 0) +# 1 - Enabled +# Default: 0 - Disabled + +Respawn.DynamicEscortNPC = 1 + +# +# Respawn.DynamicRateCreature +# Description: The rate at which the respawn time is adjusted for high player counts in a zone (for creatures). +# Up to this number of players, the respawn rate is unchanged. +# At double this number in players, you get twice as many respawns, at three times this number, three times the respawns, and so forth. +# Default: 10 + +Respawn.DynamicRateCreature = 10 + +# +# Respawn.DynamicMinimumCreature +# Description: The minimum respawn time for a creature under dynamic scaling. +# Default: 10 - (10 seconds) + +Respawn.DynamicMinimumCreature = 10 + +# +# Respawn.DynamicRateGameObject +# Description: The rate at which the respawn time is adjusted for high player counts in a zone (for gameobjects). +# Up to this number of players, the respawn rate is unchanged. +# At double this number in players, you get twice as many respawns, at three times this number, three times the respawns, and so forth. +# Default: 10 + +Respawn.DynamicRateGameObject = 10 + +# +# Respawn.DynamicMinimumGameObject +# Description: The minimum respawn time for a gameobject under dynamic scaling. +# Default: 10 - (10 seconds) + +Respawn.DynamicMinimumGameObject = 10 + +# +################################################################################################### + ################################################################################################### # CHAT SETTINGS # diff --git a/sql/updates/world/master/2020_08_22_00_world_2017_07_31_00_world.sql b/sql/updates/world/master/2020_08_22_00_world_2017_07_31_00_world.sql new file mode 100644 index 000000000..76502fd66 --- /dev/null +++ b/sql/updates/world/master/2020_08_22_00_world_2017_07_31_00_world.sql @@ -0,0 +1,214 @@ +-- Create databases for spawn group template, and spawn group membership +-- Current flags +-- 0x01 Legacy Spawn Mode (spawn using legacy spawn system) +-- 0x02 Manual Spawn (don't automatically spawn, instead spawned by core as part of script) +DROP TABLE IF EXISTS `spawn_group_template`; +CREATE TABLE `spawn_group_template` ( + `groupId` int(10) unsigned NOT NULL, + `groupName` varchar(100) NOT NULL, + `groupFlags` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`groupId`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `spawn_group`; +CREATE TABLE `spawn_group` ( + `groupId` int(10) unsigned NOT NULL, + `spawnType` tinyint(10) unsigned NOT NULL, + `spawnId` bigint(20) unsigned NOT NULL, + PRIMARY KEY (`groupId`,`spawnType`,`spawnId`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- Create the default groups +INSERT INTO `spawn_group_template` (`groupId`, `groupName`, `groupFlags`) VALUES +(0, 'Default Group', 0x01), +(1, 'Legacy Group', (0x01|0x02)), +(2, 'Dynamic Scaling (Quest objectives)', (0x01|0x08)), +(3, 'Dynamic Scaling (Escort NPCs)', (0x01|0x08|0x10)), +(4, 'Dynamic Scaling (Gathering nodes)', (0x01|0x08)); + +-- Create creature dynamic spawns group (creatures with quest items, or subjects of quests with less than 30min spawn time) +DROP TABLE IF EXISTS `creature_temp_group`; +CREATE TEMPORARY TABLE `creature_temp_group` +( + `creatureId` int(10) unsigned NOT NULL DEFAULT '0' +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +INSERT INTO `creature_temp_group` +SELECT `guid` +FROM `creature` C +INNER JOIN `creature_questitem` ON `CreatureEntry` = C.`id` +WHERE `spawntimesecs` < 1800 +AND `map` IN (0, 1, 530, 571); + +INSERT INTO `creature_temp_group` +SELECT `guid` +FROM `creature` C +INNER JOIN `quest_objectives` QO ON QO.`ObjectID` = C.`id` AND QO.`Type` = 0 +WHERE `spawntimesecs` < 1800 +AND `map` IN (0, 1, 530, 571); + +INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`) +SELECT DISTINCT 2, 0, `creatureId` +FROM `creature_temp_group`; + +DROP TABLE `creature_temp_group`; + +-- Create gameobject dynamic spawns group (gameobjects with quest items, or subjects of quests with less than 30min spawn time) + +DROP TABLE IF EXISTS `gameobject_temp_group`; +CREATE TEMPORARY TABLE `gameobject_temp_group` +( + `gameobjectId` int(10) unsigned NOT NULL DEFAULT '0' +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `gameobject_temp_group_ids`; +CREATE TEMPORARY TABLE `gameobject_temp_group_ids` +( + `entryid` int(10) NOT NULL DEFAULT '0' +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +ALTER TABLE `gameobject_temp_group_ids` ADD INDEX (`entryid`); + +INSERT INTO `gameobject_temp_group` +SELECT `guid` +FROM `gameobject` G +INNER JOIN `gameobject_questitem` ON `GameObjectEntry` = G.`id` +WHERE `spawntimesecs` < 1800 +AND `map` IN (0, 1, 530, 571); + +INSERT INTO `gameobject_temp_group_ids` (`entryid`) +SELECT DISTINCT `ObjectID` +FROM `quest_objectives` +WHERE `Type`=2; + +INSERT INTO `gameobject_temp_group` +SELECT `guid` +FROM `gameobject` G +INNER JOIN `gameobject_temp_group_ids` ON `entryid` = G.`id` +WHERE `spawntimesecs` < 1800 +AND `map` IN (0, 1, 530, 571); + +INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`) +SELECT DISTINCT 2, 1, `gameobjectId` +FROM `gameobject_temp_group`; + +DROP TABLE `gameobject_temp_group`; +ALTER TABLE `gameobject_temp_group_ids` DROP INDEX `entryid`; +DROP TABLE `gameobject_temp_group_ids`; + +-- Add mining nodes/herb nodes to profession node group +INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`) +SELECT 4, 1, `guid` +FROM `gameobject` g +INNER JOIN `gameobject_template` gt + ON gt.`entry` = g.`id` +WHERE `type` IN (3, 50) +AND `Data0` IN (2, 8, 9, 10, 11, 18, 19, 20, 21, 22, 25, 26, 27, 29, 30, 31, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 47, 48, 49, 50, 51, 379, 380, 399, 400, 439, 440, 441, 442, 443, 444, 519, 521, 719, 939, 1119, 1120, + 1121, 1122, 1123, 1124, 1632, 1639, 1641, 1642, 1643, 1644, 1645, 1646, 1649, 1650, 1651, 1652, 1782, 1783, 1784, 1785, 1786, 1787, 1788, 1789, 1790, 1791, 1792, 1793, 1800, 1860); + +-- Add Escort NPCs +INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`) VALUES +(3, 0, 10873), +(3, 0, 17874), +(3, 0, 40210), +(3, 0, 11348), +(3, 0, 93301), +(3, 0, 93194), +(3, 0, 19107), +(3, 0, 21692), +(3, 0, 21584), +(3, 0, 23229), +(3, 0, 24268), +(3, 0, 21594), +(3, 0, 14387), +(3, 0, 50381), +(3, 0, 15031), +(3, 0, 26987), +(3, 0, 29241), +(3, 0, 32333), +(3, 0, 33115), +(3, 0, 37085), +(3, 0, 41759), +(3, 0, 84459), +(3, 0, 78685), +(3, 0, 62090), +(3, 0, 72388), +(3, 0, 86832), +(3, 0, 67040), +(3, 0, 78781), +(3, 0, 65108), +(3, 0, 63688), +(3, 0, 59383), +(3, 0, 63625), +(3, 0, 70021), +(3, 0, 82071), +(3, 0, 117903), +(3, 0, 111075), +(3, 0, 101136), +(3, 0, 101303), +(3, 0, 122686), +(3, 0, 117065), +(3, 0, 202337), +(3, 0, 2017), +(3, 0, 132683); +-- remove potential duplicates +DELETE FROM `spawn_group` WHERE `groupId` != 3 AND `spawnType`=0 AND `spawnId` IN (SELECT `spawnId` FROM (SELECT `spawnId` FROM `spawn_group` WHERE `groupId`=3 AND `spawnType`=0) as `temp`); +DELETE FROM `spawn_group` WHERE `groupId` != 4 AND `spawnType`=1 AND `spawnId` IN (SELECT `spawnId` FROM (SELECT `spawnId` FROM `spawn_group` WHERE `groupId`=4 AND `spawnType`=1) as `temp`); + + +-- Update trinity strings for various cs_list strings, to support showing spawn ID and guid. +UPDATE `trinity_string` +SET `content_default` = '%s (Entry: %d) - |cffffffff|Hgameobject:%s|h[%s X:%f Y:%f Z:%f MapId:%d]|h|r %s %s' +WHERE `entry` = 517; + +UPDATE `trinity_string` +SET `content_default` = '%s - |cffffffff|Hcreature:%s|h[%s X:%f Y:%f Z:%f MapId:%d]|h|r %s %s' +WHERE `entry` = 515; + +UPDATE `trinity_string` +SET `content_default` = '%s - %s X:%f Y:%f Z:%f MapId:%d %s %s' +WHERE `entry` = 1111; + +UPDATE `trinity_string` +SET `content_default` = '%s - %s X:%f Y:%f Z:%f MapId:%d %s %s' +WHERE `entry` = 1110; + +UPDATE `trinity_string` +SET `entry`=5084 +WHERE `entry`=5070; + +UPDATE `trinity_string` +SET `entry`=5085 +WHERE `entry`=5071; + +UPDATE `trinity_string` +SET `entry`=5086 +WHERE `entry`=5072; + +-- Add new trinity strings for extra npc/gobject info lines +DELETE FROM `trinity_string` WHERE `entry` BETWEEN 5070 AND 5082; +INSERT INTO `trinity_string` (`entry`, `content_default`) VALUES +(5070, 'Spawn group: %s (ID: %u, Flags: %u, Active: %u)'), +(5071, 'Compatibility Mode: %u'), +(5072, 'GUID: %s'), +(5073, 'SpawnID: %s, location (%f, %f, %f)'), +(5074, 'Distance from player %f'), +(5075, 'Spawn group %u not found'), +(5076, 'Spawned a total of %zu objects:'), +(5077, 'Listing %s respawns within %uyd'), +(5078, 'Listing %s respawns for %s (zone %u)'), +(5079, 'SpawnID | Entry | GridXY| Zone | Respawn time (Full)'), +(5080, 'overdue'), +(5081, 'creatures'), +(5082, 'gameobjects'); + +-- Add new NPC/Gameobject commands +DELETE FROM `command` WHERE `name` IN ('npc spawngroup', 'npc despawngroup', 'gobject spawngroup', 'gobject despawngroup', 'list respawns'); +INSERT INTO `command` (`name`, `permission`, `help`) VALUES +('npc spawngroup', 856, 'Syntax: .npc spawngroup $groupId [ignorerespawn] [force]'), +('npc despawngroup', 857, 'Syntax: .npc despawngroup $groupId [removerespawntime]'), +('gobject spawngroup', 858, 'Syntax: .gobject spawngroup $groupId [ignorerespawn] [force]'), +('gobject despawngroup', 859, 'Syntax: .gobject despawngroup $groupId [removerespawntime]'), +('list respawns', 860, 'Syntax: .list respawns [distance] + +Lists all pending respawns within yards, or within current zone if not specified.');