From 24a9e5e1d8966cdfba014f244815c064655c983a Mon Sep 17 00:00:00 2001 From: hondacrx Date: Mon, 24 Aug 2020 11:13:45 -0400 Subject: [PATCH] Scripts/InstanceScript: Implement database framework for managing spawn groups based on boss state. Port From (https://github.com/TrinityCore/TrinityCore/commit/608c9aaabfcdbaf09edd11cdf268c243b3e69478) --- Source/Framework/Constants/MapConst.cs | 11 ++- Source/Game/Globals/ObjectManager.cs | 82 ++++++++++++++-- Source/Game/Maps/Instances/InstanceScript.cs | 95 ++++++++++++++----- Source/Game/Maps/Map.cs | 2 + Source/Game/Server/WorldManager.cs | 3 + ...020_08_22_02_world_2017_08_04_00_world.sql | 10 ++ 6 files changed, 172 insertions(+), 31 deletions(-) create mode 100644 sql/updates/world/master/2020_08_22_02_world_2017_08_04_00_world.sql diff --git a/Source/Framework/Constants/MapConst.cs b/Source/Framework/Constants/MapConst.cs index 3e571bc12..1a29983a4 100644 --- a/Source/Framework/Constants/MapConst.cs +++ b/Source/Framework/Constants/MapConst.cs @@ -113,7 +113,7 @@ namespace Framework.Constants AreaTrigger = 0x20, Conversation = 0x40, All = 0x7F, - + //GameObjects, Creatures(except pets), DynamicObject, Corpse(Bones), AreaTrigger AllGrid = GameObject | Creature | DynamicObject | Corpse | AreaTrigger | Conversation, @@ -231,4 +231,13 @@ namespace Framework.Constants All = (System | CompatibilityMode | ManualSpawn | DynamicSpawnRate | EscortQuestNpc) } + + [Flags] + public enum InstanceSpawnGroupFlags + { + ActivateSpawn = 0x01, + BlockSpawn = 0x02, + + All = (ActivateSpawn | BlockSpawn) + } } diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index 1de58cf9e..8d443aa3a 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -18,7 +18,9 @@ using Framework.Collections; using Framework.Constants; using Framework.Database; +using Framework.Dynamic; using Framework.GameMath; +using Framework.IO; using Game.Conditions; using Game.DataStorage; using Game.Entities; @@ -31,8 +33,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; -using Framework.Dynamic; -using Framework.IO; namespace Game { @@ -5375,6 +5375,61 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, $"Loaded {numMembers} spawn group members in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } + public void LoadInstanceSpawnGroups() + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 3 4 + SQLResult result = DB.World.Query("SELECT instanceMapId, bossStateId, bossStates, spawnGroupId, flags FROM instance_spawn_groups"); + if (result.IsEmpty()) + { + Log.outError(LogFilter.ServerLoading, "Loaded 0 instance spawn groups. DB table `instance_spawn_groups` is empty."); + return; + } + + uint count = 0; + do + { + ushort instanceMapId = result.Read(0); + uint spawnGroupId = result.Read(3); + + var spawnGroupTemplate = _spawnGroupDataStorage.LookupByKey(spawnGroupId); + if (spawnGroupTemplate == null || spawnGroupTemplate.flags.HasAnyFlag(SpawnGroupFlags.System)) + { + Log.outError(LogFilter.ServerLoading, $"Invalid spawn group {spawnGroupId} specified for instance {instanceMapId}. Skipped."); + continue; + } + + InstanceSpawnGroupInfo info = new InstanceSpawnGroupInfo(); + info.SpawnGroupId = spawnGroupId; + info.BossStateId = result.Read(1); + + byte ALL_STATES = (1 << (int)EncounterState.ToBeDecided) - 1; + byte states = result.Read(2); + if ((states & ~ALL_STATES) != 0) + { + info.BossStates = (byte)(states & ALL_STATES); + Log.outError(LogFilter.ServerLoading, $"Instance spawn group ({instanceMapId},{spawnGroupId}) had invalid boss state mask {states} - truncated to {info.BossStates}."); + } + else + info.BossStates = states; + + InstanceSpawnGroupFlags flags = (InstanceSpawnGroupFlags)result.Read(4); + if ((flags & ~InstanceSpawnGroupFlags.All) != 0) + { + info.Flags = flags & InstanceSpawnGroupFlags.All; + Log.outError(LogFilter.ServerLoading, $"Instance spawn group ({instanceMapId},{spawnGroupId}) had invalid flags {flags} - truncated to {info.Flags}."); + } + else + info.Flags = flags; + + _instanceSpawnGroupStorage.Add(instanceMapId, info); + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} instance spawn groups in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); + } void OnDeleteSpawnData(SpawnData data) { var templateIt = _spawnGroupDataStorage.LookupByKey(data.spawnGroupData.groupId); @@ -5394,24 +5449,24 @@ namespace Game 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) + public bool SpawnGroupSpawn(uint groupId, Map map, bool ignoreRespawn = false, 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."); + Log.outError(LogFilter.Maps, $"Tried to spawn 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."); + Log.outError(LogFilter.Maps, $"Tried to spawn 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."); + Log.outError(LogFilter.Maps, $"Tried to spawn creature group {groupId}, but supplied map is {map.GetId()}, creature group has map {spawnGroup.mapId}. Blocked."); return false; } @@ -5543,13 +5598,14 @@ 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) + SpawnGroupTemplateData GetSpawnGroupData(uint groupId) { return _spawnGroupDataStorage.LookupByKey(groupId); } + public void SetSpawnGroupActive(uint groupId, bool state) { var spawnGroup = _spawnGroupDataStorage.LookupByKey(groupId); if (spawnGroup != null) spawnGroup.isActive = state; } - bool IsSpawnGroupActive(uint groupId) + public bool IsSpawnGroupActive(uint groupId) { var spawnGroup = _spawnGroupDataStorage.LookupByKey(groupId); return (spawnGroup != null) && spawnGroup.isActive; @@ -5567,6 +5623,7 @@ namespace Game return null; } + public List GetSpawnGroupsForInstance(uint instanceId) { return _instanceSpawnGroupStorage.LookupByKey(instanceId); } //Player public void LoadPlayerInfo() @@ -10098,6 +10155,7 @@ namespace Game List _transportMaps = new List(); Dictionary _spawnGroupDataStorage = new Dictionary(); MultiMap _spawnGroupMapStorage = new MultiMap(); + MultiMap _instanceSpawnGroupStorage = new MultiMap(); //Spells /Skills / Phases Dictionary _phaseInfoById = new Dictionary(); @@ -10512,6 +10570,14 @@ namespace Game public uint armor; } + public struct InstanceSpawnGroupInfo + { + public byte BossStateId; + public byte BossStates; + public uint SpawnGroupId; + public InstanceSpawnGroupFlags Flags; + } + public class SpellClickInfo { public uint spellId; diff --git a/Source/Game/Maps/Instances/InstanceScript.cs b/Source/Game/Maps/Instances/InstanceScript.cs index aa3932e7c..561737b6a 100644 --- a/Source/Game/Maps/Instances/InstanceScript.cs +++ b/Source/Game/Maps/Instances/InstanceScript.cs @@ -22,6 +22,7 @@ using Game.Entities; using Game.Groups; using Game.Networking.Packets; using Game.Scenarios; +using System; using System.Collections.Generic; using System.Text; @@ -32,6 +33,7 @@ namespace Game.Maps public InstanceScript(InstanceMap map) { instance = map; + _instanceSpawnGroups = Global.ObjectMgr.GetSpawnGroupsForInstance(map.GetId()); } public void SaveToDB() @@ -161,27 +163,6 @@ namespace Game.Maps } } - void UpdateMinionState(Creature minion, EncounterState state) - { - switch (state) - { - case EncounterState.NotStarted: - if (!minion.IsAlive()) - minion.Respawn(); - else if (minion.IsInCombat()) - minion.GetAI().EnterEvadeMode(); - break; - case EncounterState.InProgress: - if (!minion.IsAlive()) - minion.Respawn(); - else if (minion.GetVictim() == null) - minion.GetAI().DoZoneInCombat(); - break; - default: - break; - } - } - public virtual void UpdateDoorState(GameObject door) { var range = doors.LookupByKey(door.GetEntry()); @@ -213,6 +194,65 @@ namespace Game.Maps door.SetGoState(open ? GameObjectState.Active : GameObjectState.Ready); } + void UpdateMinionState(Creature minion, EncounterState state) + { + switch (state) + { + case EncounterState.NotStarted: + if (!minion.IsAlive()) + minion.Respawn(); + else if (minion.IsInCombat()) + minion.GetAI().EnterEvadeMode(); + break; + case EncounterState.InProgress: + if (!minion.IsAlive()) + minion.Respawn(); + else if (minion.GetVictim() == null) + minion.GetAI().DoZoneInCombat(); + break; + default: + break; + } + } + + enum states { Block, Spawn, ForceBlock }; + void UpdateSpawnGroups() + { + if (_instanceSpawnGroups.Empty()) + return; + + Dictionary newStates = new Dictionary(); + foreach (var info in _instanceSpawnGroups) + { + if (!newStates.ContainsKey(info.SpawnGroupId)) + newStates[info.SpawnGroupId] = 0;// makes sure there's a BLOCK value in the map + + if (newStates[info.SpawnGroupId] == states.ForceBlock) // nothing will change this + continue; + + if (((1 << (int)GetBossState(info.BossStateId)) & info.BossStates) == 0) + continue; + + if (info.Flags.HasAnyFlag(InstanceSpawnGroupFlags.BlockSpawn)) + newStates[info.SpawnGroupId] = states.ForceBlock; + else if (info.Flags.HasAnyFlag(InstanceSpawnGroupFlags.ActivateSpawn)) + newStates[info.SpawnGroupId] = states.Spawn; + } + + foreach (var pair in newStates) + { + uint groupId = pair.Key; + bool doSpawn = pair.Value == states.Spawn; + if (Global.ObjectMgr.IsSpawnGroupActive(groupId) == doSpawn) + continue; // nothing to do here + // if we should spawn group, then spawn it... + if (doSpawn) + Global.ObjectMgr.SpawnGroupSpawn(groupId, instance); + else // otherwise, set it as inactive so it no longer respawns (but don't despawn it) + Global.ObjectMgr.SetSpawnGroupActive(groupId, false); + } + } + public BossInfo GetBossInfo(uint id) { Cypher.Assert(id < bosses.Count); @@ -291,7 +331,7 @@ namespace Game.Maps if (bossInfo.state == EncounterState.ToBeDecided) // loading { bossInfo.state = state; - //Log.outError(LogFilter.General "Inialize boss {0} state as {1}.", id, (uint32)state); + Log.outDebug(LogFilter.Scripts, $"InstanceScript: Inialize boss {id} state as {state}."); return false; } else @@ -354,6 +394,7 @@ namespace Game.Maps UpdateMinionState(minion, state); } + UpdateSpawnGroups(); return true; } return false; @@ -364,6 +405,14 @@ namespace Game.Maps return player && player.GetSession().HasPermission(RBACPermissions.SkipCheckInstanceRequiredBosses); } + public virtual void Create() + { + for (uint i = 0; i < bosses.Count; ++i) + SetBossState(i, EncounterState.NotStarted); + + UpdateSpawnGroups(); + } + public virtual void Load(string data) { if (string.IsNullOrEmpty(data)) @@ -411,6 +460,7 @@ namespace Game.Maps if (buff < EncounterState.ToBeDecided) SetBossState(pair.Key, buff); } + UpdateSpawnGroups(); } public virtual string GetSaveData() @@ -852,6 +902,7 @@ namespace Game.Maps Dictionary _gameObjectInfo = new Dictionary(); Dictionary _objectGuids = new Dictionary(); uint completedEncounters; + List _instanceSpawnGroups = new List(); uint _entranceId; uint _temporaryEntranceId; uint _combatResurrectionTimer; diff --git a/Source/Game/Maps/Map.cs b/Source/Game/Maps/Map.cs index 910e15c7d..302bc32db 100644 --- a/Source/Game/Maps/Map.cs +++ b/Source/Game/Maps/Map.cs @@ -5173,6 +5173,8 @@ namespace Game.Maps } } } + else + i_data.Create(); } public bool Reset(InstanceResetMethod method) diff --git a/Source/Game/Server/WorldManager.cs b/Source/Game/Server/WorldManager.cs index d1d79fda2..31ac4fe1c 100644 --- a/Source/Game/Server/WorldManager.cs +++ b/Source/Game/Server/WorldManager.cs @@ -586,6 +586,9 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loading Spawn Group Templates..."); Global.ObjectMgr.LoadSpawnGroupTemplates(); + Log.outInfo(LogFilter.ServerLoading, "Loading instance spawn groups..."); + Global.ObjectMgr.LoadInstanceSpawnGroups(); + Log.outInfo(LogFilter.ServerLoading, "Loading Creature Data..."); Global.ObjectMgr.LoadCreatures(); diff --git a/sql/updates/world/master/2020_08_22_02_world_2017_08_04_00_world.sql b/sql/updates/world/master/2020_08_22_02_world_2017_08_04_00_world.sql new file mode 100644 index 000000000..2506288f3 --- /dev/null +++ b/sql/updates/world/master/2020_08_22_02_world_2017_08_04_00_world.sql @@ -0,0 +1,10 @@ +-- +DROP TABLE IF EXISTS `instance_spawn_groups`; +CREATE TABLE `instance_spawn_groups` ( + `instanceMapId` smallint(5) unsigned not null, + `bossStateId` tinyint(3) unsigned not null, + `bossStates` tinyint(3) unsigned not null, + `spawnGroupId` int(10) unsigned not null, + `flags` tinyint(3) unsigned not null, + PRIMARY KEY (`instanceMapId`,`bossStateId`,`spawnGroupId`,`bossStates`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8;