diff --git a/Source/Framework/Constants/ConditionConst.cs b/Source/Framework/Constants/ConditionConst.cs index b196492ff..cf9f434f1 100644 --- a/Source/Framework/Constants/ConditionConst.cs +++ b/Source/Framework/Constants/ConditionConst.cs @@ -113,6 +113,7 @@ namespace Framework.Constants AreatriggerClientTriggered = 30, TrainerSpell = 31, ObjectIdVisibility = 32, + SpawnGroup = 33, Max } diff --git a/Source/Framework/Constants/MapConst.cs b/Source/Framework/Constants/MapConst.cs index 2bc5a73f7..206b477a1 100644 --- a/Source/Framework/Constants/MapConst.cs +++ b/Source/Framework/Constants/MapConst.cs @@ -231,8 +231,9 @@ namespace Framework.Constants ManualSpawn = 0x04, DynamicSpawnRate = 0x08, EscortQuestNpc = 0x10, + DespawnOnConditionFailure = 0x20, - All = (System | CompatibilityMode | ManualSpawn | DynamicSpawnRate | EscortQuestNpc) + All = (System | CompatibilityMode | ManualSpawn | DynamicSpawnRate | EscortQuestNpc | DespawnOnConditionFailure) } [Flags] diff --git a/Source/Game/Conditions/Condition.cs b/Source/Game/Conditions/Condition.cs index bcb265dd2..2a66a4e32 100644 --- a/Source/Game/Conditions/Condition.cs +++ b/Source/Game/Conditions/Condition.cs @@ -37,23 +37,100 @@ namespace Game.Conditions public bool Meets(ConditionSourceInfo sourceInfo) { Cypher.Assert(ConditionTarget < SharedConst.MaxConditionTargets); + + Map map = sourceInfo.mConditionMap; + bool condMeets = false; + bool needsObject = false; + switch (ConditionType) + { + case ConditionTypes.None: + condMeets = true; // empty condition, always met + break; + case ConditionTypes.ActiveEvent: + condMeets = Global.GameEventMgr.IsActiveEvent((ushort)ConditionValue1); + break; + case ConditionTypes.InstanceInfo: + { + if (map.IsDungeon()) + { + InstanceScript instance = ((InstanceMap)map).GetInstanceScript(); + if (instance != null) + { + switch ((InstanceInfo)ConditionValue3) + { + case InstanceInfo.Data: + condMeets = instance.GetData(ConditionValue1) == ConditionValue2; + break; + //case INSTANCE_INFO_GUID_DATA: + // condMeets = instance->GetGuidData(ConditionValue1) == ObjectGuid(uint64(ConditionValue2)); + // break; + case InstanceInfo.BossState: + condMeets = instance.GetBossState(ConditionValue1) == (EncounterState)ConditionValue2; + break; + case InstanceInfo.Data64: + condMeets = instance.GetData64(ConditionValue1) == ConditionValue2; + break; + default: + condMeets = false; + break; + } + } + } + break; + } + case ConditionTypes.Mapid: + condMeets = map.GetId() == ConditionValue1; + break; + case ConditionTypes.WorldState: + { + condMeets = ConditionValue2 == Global.WorldMgr.GetWorldState(ConditionValue1); + break; + } + case ConditionTypes.RealmAchievement: + { + var achievement = CliDB.AchievementStorage.LookupByKey(ConditionValue1); + if (achievement != null && Global.AchievementMgr.IsRealmCompleted(achievement)) + condMeets = true; + break; + } + case ConditionTypes.DifficultyId: + { + condMeets = (uint)map.GetDifficultyID() == ConditionValue1; + break; + } + case ConditionTypes.ScenarioStep: + { + InstanceMap instanceMap = map.ToInstanceMap(); + if (instanceMap != null) + { + Scenario scenario = instanceMap.GetInstanceScenario(); + if (scenario != null) + { + ScenarioStepRecord step = scenario.GetStep(); + if (step != null) + condMeets = step.Id == ConditionValue1; + } + } + break; + } + default: + needsObject = true; + break; + } + WorldObject obj = sourceInfo.mConditionTargets[ConditionTarget]; // object not present, return false - if (obj == null) + if (needsObject && obj == null) { Log.outDebug(LogFilter.Condition, "Condition object not found for condition (Entry: {0} Type: {1} Group: {2})", SourceEntry, SourceType, SourceGroup); return false; } - bool condMeets = false; Player player = obj.ToPlayer(); Unit unit = obj.ToUnit(); switch (ConditionType) { - case ConditionTypes.None: - condMeets = true; // empty condition, always met - break; case ConditionTypes.Aura: if (unit != null) condMeets = unit.HasAuraEffect(ConditionValue1, (byte)ConditionValue2); @@ -131,36 +208,6 @@ namespace Game.Conditions condMeets = (status == QuestStatus.None); } break; - case ConditionTypes.ActiveEvent: - condMeets = Global.GameEventMgr.IsActiveEvent((ushort)ConditionValue1); - break; - case ConditionTypes.InstanceInfo: - { - var map = obj.GetMap(); - if (map.IsDungeon()) - { - InstanceScript instance = ((InstanceMap)map).GetInstanceScript(); - if (instance != null) - { - switch ((InstanceInfo)ConditionValue3) - { - case InstanceInfo.Data: - condMeets = instance.GetData(ConditionValue1) == ConditionValue2; - break; - case InstanceInfo.Data64: - condMeets = instance.GetData64(ConditionValue1) == ConditionValue2; - break; - case InstanceInfo.BossState: - condMeets = instance.GetBossState(ConditionValue1) == (EncounterState)ConditionValue2; - break; - } - } - } - break; - } - case ConditionTypes.Mapid: - condMeets = obj.GetMapId() == ConditionValue1; - break; case ConditionTypes.Areaid: condMeets = obj.GetAreaId() == ConditionValue1; break; @@ -267,9 +314,6 @@ namespace Game.Conditions if (unit != null) condMeets = MathFunctions.CompareValues((ComparisionType)ConditionValue2, unit.GetHealthPct(), ConditionValue1); break; - case ConditionTypes.WorldState: - condMeets = (ConditionValue2 == Global.WorldMgr.GetWorldState((WorldStates)ConditionValue1)); - break; case ConditionTypes.PhaseId: condMeets = obj.GetPhaseShift().HasPhase(ConditionValue1); break; @@ -288,13 +332,6 @@ namespace Game.Conditions condMeets = (uint)creature.GetCreatureTemplate().CreatureType == ConditionValue1; break; } - case ConditionTypes.RealmAchievement: - { - AchievementRecord achievement = CliDB.AchievementStorage.LookupByKey(ConditionValue1); - if (achievement != null && Global.AchievementMgr.IsRealmCompleted(achievement)) - condMeets = true; - break; - } case ConditionTypes.InWater: if (unit) condMeets = unit.IsInWater(); @@ -378,11 +415,6 @@ namespace Game.Conditions } break; } - case ConditionTypes.DifficultyId: - { - condMeets = (uint)obj.GetMap().GetDifficultyID() == ConditionValue1; - break; - } case ConditionTypes.Gamemaster: { if (player != null) @@ -400,17 +432,6 @@ namespace Game.Conditions condMeets = MathFunctions.CompareValues((ComparisionType)ConditionValue3, player.GetSession().GetBattlePetMgr().GetPetCount(CliDB.BattlePetSpeciesStorage.LookupByKey(ConditionValue1), player.GetGUID()), ConditionValue2); break; } - case ConditionTypes.ScenarioStep: - { - Scenario scenario = obj.GetScenario(); - if (scenario != null) - { - ScenarioStepRecord step = scenario.GetStep(); - if (step != null) - condMeets = step.Id == ConditionValue1; - } - break; - } case ConditionTypes.SceneInProgress: { if (player != null) @@ -418,7 +439,6 @@ namespace Game.Conditions break; } default: - condMeets = false; break; } @@ -620,10 +640,18 @@ namespace Game.Conditions mConditionTargets[0] = target0; mConditionTargets[1] = target1; mConditionTargets[2] = target2; + mConditionMap = target0 != null ? target0.GetMap() : null; + mLastFailedCondition = null; + } + + public ConditionSourceInfo(Map map) + { + mConditionMap = map; mLastFailedCondition = null; } public WorldObject[] mConditionTargets = new WorldObject[SharedConst.MaxConditionTargets]; // an array of targets available for conditions + public Map mConditionMap; public Condition mLastFailedCondition; } } diff --git a/Source/Game/Conditions/ConditionManager.cs b/Source/Game/Conditions/ConditionManager.cs index 16d32862e..8ac48ed12 100644 --- a/Source/Game/Conditions/ConditionManager.cs +++ b/Source/Game/Conditions/ConditionManager.cs @@ -23,6 +23,7 @@ using Game.DataStorage; using Game.Entities; using Game.Groups; using Game.Loots; +using Game.Maps; using Game.Spells; using System; using System.Collections.Generic; @@ -171,6 +172,31 @@ namespace Game return (sourceType == ConditionSourceType.SmartEvent); } + bool CanHaveConditionType(ConditionSourceType sourceType, ConditionTypes conditionType) + { + switch (sourceType) + { + case ConditionSourceType.SpawnGroup: + switch (conditionType) + { + case ConditionTypes.None: + case ConditionTypes.ActiveEvent: + case ConditionTypes.InstanceInfo: + case ConditionTypes.Mapid: + case ConditionTypes.WorldState: + case ConditionTypes.RealmAchievement: + case ConditionTypes.DifficultyId: + case ConditionTypes.ScenarioStep: + return true; + default: + return false; + } + default: + break; + } + return true; + } + public bool IsObjectMeetingNotGroupedConditions(ConditionSourceType sourceType, uint entry, ConditionSourceInfo sourceInfo) { if (sourceType > ConditionSourceType.None && sourceType < ConditionSourceType.Max) @@ -192,6 +218,12 @@ namespace Game return IsObjectMeetingNotGroupedConditions(sourceType, entry, conditionSource); } + public bool IsMapMeetingNotGroupedConditions(ConditionSourceType sourceType, uint entry, Map map) + { + ConditionSourceInfo conditionSource = new(map); + return IsObjectMeetingNotGroupedConditions(sourceType, entry, conditionSource); + } + public bool HasConditionsForNotGroupedEntry(ConditionSourceType sourceType, uint entry) { if (sourceType > ConditionSourceType.None && sourceType < ConditionSourceType.Max) @@ -1234,6 +1266,21 @@ namespace Game } break; } + case ConditionSourceType.SpawnGroup: + { + SpawnGroupTemplateData spawnGroup = Global.ObjectMgr.GetSpawnGroupData((uint)cond.SourceEntry); + if (spawnGroup == null) + { + Log.outError(LogFilter.Sql, $"{cond.ToString()} SourceEntry in `condition` table, does not exist in `spawn_group_template`, ignoring."); + return false; + } + if (spawnGroup.flags.HasAnyFlag(SpawnGroupFlags.System | SpawnGroupFlags.ManualSpawn)) + { + Log.outError(LogFilter.Sql, $"{cond.ToString()} in `spawn_group_template` table cannot have SPAWNGROUP_FLAG_SYSTEM or SPAWNGROUP_FLAG_MANUAL_SPAWN flags, ignoring."); + return false; + } + break; + } default: Log.outError(LogFilter.Sql, $"{cond.ToString()} Invalid ConditionSourceType in `condition` table, ignoring."); return false; diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index 5a8428815..bad24227d 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -5639,7 +5639,10 @@ namespace Game else { if (groupTemplate.mapId == 0xFFFFFFFF) + { groupTemplate.mapId = data.MapId; + _spawnGroupsByMap.Add(data.MapId, groupId); + } else if (groupTemplate.mapId != data.MapId && !groupTemplate.flags.HasAnyFlag(SpawnGroupFlags.System)) { Log.outError(LogFilter.Sql, $"Spawn group {groupId} has map ID {groupTemplate.mapId}, but spawn ({spawnType},{spawnId}) has map id {data.MapId} - spawn NOT added to group!"); @@ -5808,6 +5811,7 @@ namespace Game public SpawnGroupTemplateData GetDefaultSpawnGroup() { return _spawnGroupDataStorage.ElementAt(0).Value; } public SpawnGroupTemplateData GetLegacySpawnGroup() { return _spawnGroupDataStorage.ElementAt(1).Value; } public List GetSpawnMetadataForGroup(uint groupId) { return _spawnGroupMapStorage.LookupByKey(groupId); } + public List GetSpawnGroupsForMap(uint mapId) { return _spawnGroupsByMap.LookupByKey(mapId); } public SpawnMetadata GetSpawnMetadata(SpawnObjectType type, ulong spawnId) { if (SpawnData.TypeHasData(type)) @@ -10794,6 +10798,7 @@ namespace Game List _transportMaps = new(); Dictionary _spawnGroupDataStorage = new(); MultiMap _spawnGroupMapStorage = new(); + MultiMap _spawnGroupsByMap = new(); MultiMap _instanceSpawnGroupStorage = new(); //Spells /Skills / Phases diff --git a/Source/Game/Maps/Map.cs b/Source/Game/Maps/Map.cs index 16dfd3680..7b822b83c 100644 --- a/Source/Game/Maps/Map.cs +++ b/Source/Game/Maps/Map.cs @@ -740,6 +740,7 @@ namespace Game.Maps if (_respawnCheckTimer <= diff) { ProcessRespawns(); + UpdateSpawnGroupConditions(); _respawnCheckTimer = WorldConfig.GetUIntValue(WorldCfg.RespawnMinCheckIntervalMs); } else @@ -2849,7 +2850,7 @@ namespace Game.Maps return true; } - public bool SpawnGroupDespawn(uint groupId, bool deleteRespawnTimes) + public bool SpawnGroupDespawn(uint groupId, bool deleteRespawnTimes = false) { return SpawnGroupDespawn(groupId, deleteRespawnTimes, out _); } @@ -2910,6 +2911,25 @@ namespace Game.Maps return _toggledSpawnGroupIds.Contains(groupId) != !data.flags.HasAnyFlag(SpawnGroupFlags.ManualSpawn); } + void UpdateSpawnGroupConditions() + { + var spawnGroups = Global.ObjectMgr.GetSpawnGroupsForMap(GetId()); + foreach (uint spawnGroupId in spawnGroups) + { + bool isActive = IsSpawnGroupActive(spawnGroupId); + bool shouldBeActive = Global.ConditionMgr.IsMapMeetingNotGroupedConditions(ConditionSourceType.SpawnGroup, spawnGroupId, this); + if (isActive == shouldBeActive) + continue; + + if (shouldBeActive) + SpawnGroupSpawn(spawnGroupId); + else if (GetSpawnGroupData(spawnGroupId).flags.HasFlag(SpawnGroupFlags.DespawnOnConditionFailure)) + SpawnGroupDespawn(spawnGroupId); + else + SetSpawnGroupInactive(spawnGroupId); + } + } + public void AddFarSpellCallback(FarSpellCallback callback) { _farSpellCallbacks.Enqueue(new FarSpellCallback(callback));