From 296706601daccb2d6d943606f204c5e843fc705d Mon Sep 17 00:00:00 2001 From: killerwife Date: Thu, 26 Dec 2024 18:33:43 +0100 Subject: [PATCH] [s2480] SpawnGroup: Implement spawn_group_squad and respawn override --- sql/base/mangos.sql | 17 +- .../s2480_01_mangos_spawn_group_squad.sql | 15 + src/game/Entities/Creature.cpp | 4 +- src/game/Entities/Creature.h | 2 +- src/game/Entities/GameObject.cpp | 6 +- src/game/Entities/GameObject.h | 2 +- src/game/Entities/Object.h | 2 + src/game/Globals/ObjectMgr.cpp | 46 ++- src/game/Maps/SpawnGroup.cpp | 290 +++++++++++------- src/game/Maps/SpawnGroup.h | 5 + src/game/Maps/SpawnGroupDefines.h | 11 + src/shared/revision_sql.h | 2 +- 12 files changed, 281 insertions(+), 121 deletions(-) create mode 100644 sql/updates/mangos/s2480_01_mangos_spawn_group_squad.sql diff --git a/sql/base/mangos.sql b/sql/base/mangos.sql index 4ca0c51cd91..a93dfe3fcd8 100644 --- a/sql/base/mangos.sql +++ b/sql/base/mangos.sql @@ -23,7 +23,7 @@ DROP TABLE IF EXISTS `db_version`; CREATE TABLE `db_version` ( `version` varchar(120) DEFAULT NULL, `creature_ai_version` varchar(120) DEFAULT NULL, - `required_s2479_01_mangos_displayid_probability` bit(1) DEFAULT NULL + `required_s2480_01_mangos_spawn_group_squad` bit(1) DEFAULT NULL ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='Used DB version notes'; -- @@ -12891,6 +12891,8 @@ CREATE TABLE `spawn_group` ( `WorldStateExpression` int(11) NOT NULL DEFAULT 0 COMMENT 'Worldstate expression Id', `Flags` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Flags for various behaviour', `StringId` INT(11) UNSIGNED NOT NULL DEFAULT '0', + `RespawnOverrideMin` INT UNSIGNED COMMENT 'Respawn time override' DEFAULT NULL, + `RespawnOverrideMax` INT UNSIGNED COMMENT 'Respawn time override' DEFAULT NULL, PRIMARY KEY (`Id`) ); @@ -12944,6 +12946,19 @@ CREATE TABLE `spawn_group_linked_group` ( PRIMARY KEY (`Id`, `LinkedId`) ); +-- ---------------------------- +-- Table structure for spawn_group_squad +-- ---------------------------- + +DROP TABLE IF EXISTS spawn_group_squad; +CREATE TABLE spawn_group_squad( +Id INT NOT NULL COMMENT 'Spawn Group ID', +SquadId INT NOT NULL COMMENT 'Squad Id within Spawn Group', +Guid INT NOT NULL COMMENT 'Guid of creature or GO', +Entry INT NOT NULL COMMENT 'Entry of creature or GO', +PRIMARY KEY(Id, SquadId, Guid) +); + -- -- Table structure for table `spam_records` -- diff --git a/sql/updates/mangos/s2480_01_mangos_spawn_group_squad.sql b/sql/updates/mangos/s2480_01_mangos_spawn_group_squad.sql new file mode 100644 index 00000000000..ce078d67dce --- /dev/null +++ b/sql/updates/mangos/s2480_01_mangos_spawn_group_squad.sql @@ -0,0 +1,15 @@ +ALTER TABLE db_version CHANGE COLUMN required_s2479_01_mangos_displayid_probability required_s2480_01_mangos_spawn_group_squad bit; + +DROP TABLE IF EXISTS spawn_group_squad; +CREATE TABLE spawn_group_squad( +Id INT NOT NULL COMMENT 'Spawn Group ID', +SquadId INT NOT NULL COMMENT 'Squad Id within Spawn Group', +Guid INT NOT NULL COMMENT 'Guid of creature or GO', +Entry INT NOT NULL COMMENT 'Entry of creature or GO', +PRIMARY KEY(Id, SquadId, Guid) +); + +ALTER TABLE spawn_group ADD COLUMN `RespawnOverrideMin` INT UNSIGNED COMMENT 'Respawn time override' DEFAULT NULL AFTER `StringId`; +ALTER TABLE spawn_group ADD COLUMN `RespawnOverrideMax` INT UNSIGNED COMMENT 'Respawn time override' DEFAULT NULL AFTER `RespawnOverrideMin`; + + diff --git a/src/game/Entities/Creature.cpp b/src/game/Entities/Creature.cpp index 22d4d561d56..538212d66d5 100644 --- a/src/game/Entities/Creature.cpp +++ b/src/game/Entities/Creature.cpp @@ -1870,7 +1870,9 @@ void Creature::SetDeathState(DeathState s) { if (!m_respawnOverriden) { - if (CreatureData const* data = sObjectMgr.GetCreatureData(GetDbGuid())) + if (GetCreatureGroup() && GetCreatureGroup()->IsRespawnOverriden()) + m_respawnDelay = GetCreatureGroup()->GetRandomRespawnTime(); + else if (CreatureData const* data = sObjectMgr.GetCreatureData(GetDbGuid())) m_respawnDelay = data->GetRandomRespawnTime(); } else if (m_respawnOverrideOnce) diff --git a/src/game/Entities/Creature.h b/src/game/Entities/Creature.h index ad579b85755..5b2ab86efd3 100644 --- a/src/game/Entities/Creature.h +++ b/src/game/Entities/Creature.h @@ -769,7 +769,7 @@ class Creature : public Unit void Respawn(); void SaveRespawnTime() override; - uint32 GetRespawnDelay() const { return m_respawnDelay; } + uint32 GetRespawnDelay() const override { return m_respawnDelay; } void SetRespawnDelay(uint32 delay, bool once = false) { m_respawnDelay = delay; m_respawnOverriden = true; m_respawnOverrideOnce = once; } // in seconds void SetRespawnDelay(std::chrono::seconds delay, bool once = false) { SetRespawnDelay(delay.count(), once); } diff --git a/src/game/Entities/GameObject.cpp b/src/game/Entities/GameObject.cpp index 3bc7df90a5e..46bce090500 100644 --- a/src/game/Entities/GameObject.cpp +++ b/src/game/Entities/GameObject.cpp @@ -692,8 +692,12 @@ void GameObject::Update(const uint32 diff) { // since pool system can fail to roll unspawned object, this one can remain spawned, so must set respawn nevertheless if (IsSpawnedByDefault()) - if (GameObjectData const* data = sObjectMgr.GetGOData(GetDbGuid())) + { + if (GetGameObjectGroup() && GetGameObjectGroup()->IsRespawnOverriden()) + m_respawnDelay = GetGameObjectGroup()->GetRandomRespawnTime(); + else if (GameObjectData const* data = sObjectMgr.GetGOData(GetDbGuid())) m_respawnDelay = data->GetRandomRespawnTime(); + } } else if (m_respawnOverrideOnce) m_respawnOverriden = false; diff --git a/src/game/Entities/GameObject.h b/src/game/Entities/GameObject.h index 91fde7814cd..90b0a187cc2 100644 --- a/src/game/Entities/GameObject.h +++ b/src/game/Entities/GameObject.h @@ -789,7 +789,7 @@ class GameObject : public WorldObject (m_respawnTime == 0 && m_spawnedByDefault); } bool IsSpawnedByDefault() const { return m_spawnedByDefault; } - uint32 GetRespawnDelay() const { return m_respawnDelay; } + uint32 GetRespawnDelay() const override { return m_respawnDelay; } void SetRespawnDelay(uint32 delay, bool once = false) { m_respawnDelay = delay; m_respawnOverriden = true; m_respawnOverrideOnce = once; } void SetForcedDespawn() { m_forcedDespawn = true; }; void SetChestDespawn(); diff --git a/src/game/Entities/Object.h b/src/game/Entities/Object.h index 08b0bb82a41..ec0ff1432c7 100644 --- a/src/game/Entities/Object.h +++ b/src/game/Entities/Object.h @@ -1212,6 +1212,8 @@ class WorldObject : public Object bool HasStringId(uint32 stringId) const; // not to be used in sd2 void SetStringId(uint32 stringId, bool apply); // not to be used outside of scriptmgr + virtual uint32 GetRespawnDelay() const { return 0; } + protected: explicit WorldObject(); diff --git a/src/game/Globals/ObjectMgr.cpp b/src/game/Globals/ObjectMgr.cpp index 75c066f4d33..c8fc2788d6d 100644 --- a/src/game/Globals/ObjectMgr.cpp +++ b/src/game/Globals/ObjectMgr.cpp @@ -1198,7 +1198,7 @@ void ObjectMgr::LoadSpawnGroups() std::shared_ptr newContainer = std::make_shared(); uint32 count = 0; - std::unique_ptr result(WorldDatabase.Query("SELECT Id, Name, Type, MaxCount, WorldState, WorldStateExpression, Flags, StringId FROM spawn_group")); + std::unique_ptr result(WorldDatabase.Query("SELECT Id, Name, Type, MaxCount, WorldState, WorldStateExpression, Flags, StringId, RespawnOverrideMin, RespawnOverrideMax FROM spawn_group")); if (result) { do @@ -1257,6 +1257,10 @@ void ObjectMgr::LoadSpawnGroups() entry.EnabledByDefault = true; entry.Formation = nullptr; entry.HasChancedSpawns = false; + if (!fields[8].IsNULL()) + entry.RespawnOverrideMin = fields[8].GetUInt32(); + if (!fields[9].IsNULL()) + entry.RespawnOverrideMax = fields[9].GetUInt32(); newContainer->spawnGroupMap.emplace(entry.Id, std::move(entry)); } while (result->NextRow()); } @@ -1495,6 +1499,46 @@ void ObjectMgr::LoadSpawnGroups() } while (result->NextRow()); } + result = WorldDatabase.Query("SELECT Id, SquadId, Guid, Entry FROM spawn_group_squad"); + if (result) + { + do + { + Field* fields = result->Fetch(); + uint32 Id = fields[0].GetUInt32(); + + uint32 squadId = fields[1].GetUInt32(); + uint32 dbGuid = fields[2].GetUInt32(); + uint32 entry = fields[3].GetUInt32(); + + auto itr = newContainer->spawnGroupMap.find(Id); + if (itr == newContainer->spawnGroupMap.end()) + { + sLog.outErrorDb("LoadSpawnGroups: Invalid spawn_group_squad Id %u. Skipping.", Id); + continue; + } + + auto& spawnGroup = itr->second; + if (!spawnGroup.RandomEntries.empty()) + sLog.outErrorDb("LoadSpawnGroups: spawn_group_squad Id %u has spawn_group_entry. Will be overriden by squad", Id); + + auto squadItr = std::find_if(spawnGroup.Squads.begin(), spawnGroup.Squads.end(), [squadId](const SpawnGroupSquad& obj) -> bool { return obj.SquadId == squadId; }); + + if (squadItr == spawnGroup.Squads.end()) + { + SpawnGroupSquad squad; + squad.SquadId = squadId; + squad.GuidToEntry.emplace(dbGuid, entry); + spawnGroup.Squads.push_back(std::move(squad)); + } + else + { + squadItr->GuidToEntry.emplace(dbGuid, entry); + } + } + while (result->NextRow()); + } + for (auto& data : newContainer->spawnGroupMap) { SpawnGroupEntry& entry = data.second; diff --git a/src/game/Maps/SpawnGroup.cpp b/src/game/Maps/SpawnGroup.cpp index 3ec1377ba82..bfdae9d8326 100644 --- a/src/game/Maps/SpawnGroup.cpp +++ b/src/game/Maps/SpawnGroup.cpp @@ -62,7 +62,7 @@ namespace } } -SpawnGroup::SpawnGroup(SpawnGroupEntry const& entry, Map& map, uint32 typeId) : m_entry(entry), m_map(map), m_objectTypeId(typeId), m_enabled(m_entry.EnabledByDefault) +SpawnGroup::SpawnGroup(SpawnGroupEntry const& entry, Map& map, uint32 typeId) : m_entry(entry), m_map(map), m_chosenSquad(-1), m_objectTypeId(typeId), m_enabled(m_entry.EnabledByDefault) { } @@ -75,17 +75,27 @@ void SpawnGroup::RemoveObject(WorldObject* wo) { m_objects.erase(wo->GetDbGuid()); - if (!m_map.IsDungeon() && m_objects.empty() && m_entry.HasChancedSpawns) + if (!m_map.IsDungeon() && m_objects.empty()) { - m_chosenSpawns.clear(); - // save same respawn time for all guids so they all respawn at the same time if chanced - time_t lastRespawnTime = m_map.GetPersistentState()->GetObjectRespawnTime(GetObjectTypeId(), wo->GetDbGuid()); - for (auto& dbGuid : m_entry.DbGuids) + if (m_entry.RespawnOverrideMin) + m_cooldown = m_map.GetCurrentClockTime() + std::chrono::seconds(urand(*m_entry.RespawnOverrideMin, m_entry.RespawnOverrideMax ? *m_entry.RespawnOverrideMax : *m_entry.RespawnOverrideMin)); + else if (m_chosenSquad != -1) // avoid instant respawn artifacts - schedule respawn so full group is around after full wipe + m_cooldown = m_map.GetCurrentClockTime() + std::chrono::seconds(wo->GetRespawnDelay()); + + m_chosenSquad = -1; + + if (m_entry.HasChancedSpawns) { - if (dbGuid.DbGuid == wo->GetDbGuid()) - continue; + m_chosenSpawns.clear(); + // save same respawn time for all guids so they all respawn at the same time if chanced + time_t lastRespawnTime = m_map.GetPersistentState()->GetObjectRespawnTime(GetObjectTypeId(), wo->GetDbGuid()); + for (auto& dbGuid : m_entry.DbGuids) + { + if (dbGuid.DbGuid == wo->GetDbGuid()) + continue; - m_map.GetPersistentState()->SaveObjectRespawnTime(GetObjectTypeId(), dbGuid.DbGuid, lastRespawnTime); + m_map.GetPersistentState()->SaveObjectRespawnTime(GetObjectTypeId(), dbGuid.DbGuid, lastRespawnTime); + } } } } @@ -149,6 +159,9 @@ void SpawnGroup::Spawn(bool force) if (!m_enabled && !force) return; + if (m_cooldown > m_map.GetCurrentClockTime()) + return; + // duplicated code for optimization - way fewer cond fails if ((m_entry.Flags & SPAWN_GROUP_DESPAWN_ON_COND_FAIL) != 0) // must be before count check { @@ -168,38 +181,101 @@ void SpawnGroup::Spawn(bool force) if (m_entry.HasChancedSpawns && m_chosenSpawns.size() >= m_entry.MaxCount) return; + if (!m_entry.Squads.empty() && m_chosenSquad != -1 && m_entry.Squads[m_chosenSquad].GuidToEntry.size() == m_objects.size()) + return; + std::vector eligibleGuids; std::map validEntries; std::map minEntries; - for (auto& randomEntry : m_entry.RandomEntries) + auto pickCreatureEntry = [&](const SpawnGroupDbGuids* guids) -> uint32 { - validEntries[randomEntry.Entry] = randomEntry.MaxCount > 0 ? randomEntry.MaxCount : std::numeric_limits::max(); - if (randomEntry.MinCount > 0) - minEntries.emplace(randomEntry.Entry, randomEntry.MinCount); - } + uint32 entry; + // some group members can have static entry, or selfcontained random entry + if (guids->RandomEntry) + entry = sObjectMgr.GetRandomCreatureEntry(guids->DbGuid); + else if (guids->OwnEntry) + entry = guids->OwnEntry; + else + entry = GetEligibleEntry(validEntries, minEntries); - for (auto& guid : m_entry.DbGuids) - eligibleGuids.push_back(&guid); + return entry; + }; - for (auto& data : m_objects) + auto pickGoEntry = [&](const SpawnGroupDbGuids* guids) -> uint32 { - eligibleGuids.erase(std::remove_if(eligibleGuids.begin(), eligibleGuids.end(), [dbGuid = data.first](SpawnGroupDbGuids const* entry) { return entry->DbGuid == dbGuid; }), eligibleGuids.end()); - if (validEntries.size() > 0) - { - uint32 curCount = validEntries[data.second]; - validEntries[data.second] = curCount > 0 ? curCount - 1 : 0; - } - if (minEntries.size() > 0) + uint32 entry; + // some group members can have static entry, or selfcontained random entry + if (guids->RandomEntry) + entry = sObjectMgr.GetRandomGameObjectEntry(guids->DbGuid); + else if (guids->OwnEntry) + entry = guids->OwnEntry; + else + entry = GetEligibleEntry(validEntries, minEntries); + + return entry; + }; + + auto eraseEntry = [&](uint32 entry) + { + if (entry) { - auto itr = minEntries.find(data.second); + if (validEntries[entry]) + --validEntries[entry]; + + auto itr = minEntries.find(entry); if (itr != minEntries.end()) { - --(*itr).second; + (*itr).second -= 1; if ((*itr).second == 0) minEntries.erase(itr); } } + }; + + if (m_entry.Squads.empty()) + { + for (auto& guid : m_entry.DbGuids) + eligibleGuids.push_back(&guid); + + for (auto& randomEntry : m_entry.RandomEntries) + { + validEntries[randomEntry.Entry] = randomEntry.MaxCount > 0 ? randomEntry.MaxCount : std::numeric_limits::max(); + if (randomEntry.MinCount > 0) + minEntries.emplace(randomEntry.Entry, randomEntry.MinCount); + } + + for (auto& data : m_objects) + { + eligibleGuids.erase(std::remove_if(eligibleGuids.begin(), eligibleGuids.end(), [dbGuid = data.first](SpawnGroupDbGuids const* entry) { return entry->DbGuid == dbGuid; }), eligibleGuids.end()); + if (validEntries.size() > 0) + { + uint32 curCount = validEntries[data.second]; + validEntries[data.second] = curCount > 0 ? curCount - 1 : 0; + } + if (minEntries.size() > 0) + { + auto itr = minEntries.find(data.second); + if (itr != minEntries.end()) + { + --(*itr).second; + if ((*itr).second == 0) + minEntries.erase(itr); + } + } + } + } + else // squad code + { + if (!m_map.IsDungeon() || m_chosenSquad == -1) // choose only on first spawn in dungeon + { + auto randIdx = urand(0, m_entry.Squads.size() - 1); + m_chosenSquad = randIdx; + } + auto& chosenSquad = m_entry.Squads[m_chosenSquad]; + for (auto& guid : m_entry.DbGuids) + if (chosenSquad.GuidToEntry.find(guid.DbGuid) != chosenSquad.GuidToEntry.end()) + eligibleGuids.push_back(&guid); } time_t now = time(nullptr); @@ -235,106 +311,71 @@ void SpawnGroup::Spawn(bool force) ++itr; } - std::shuffle(eligibleGuids.begin(), eligibleGuids.end(), *GetRandomGenerator()); - - for (auto itr = eligibleGuids.begin(); itr != eligibleGuids.end() && !eligibleGuids.empty() && m_objects.size() < m_entry.MaxCount;) + if (m_entry.Squads.empty()) { - uint32 dbGuid = (*itr)->DbGuid; - if (m_entry.HasChancedSpawns) + std::shuffle(eligibleGuids.begin(), eligibleGuids.end(), *GetRandomGenerator()); + + for (auto itr = eligibleGuids.begin(); itr != eligibleGuids.end() && !eligibleGuids.empty() && m_objects.size() < m_entry.MaxCount;) { - if ((*itr)->Chance) + uint32 dbGuid = (*itr)->DbGuid; + if (m_entry.HasChancedSpawns) { - auto spawnItr = m_chosenSpawns.find(dbGuid); - bool spawn = true; - if (spawnItr == m_chosenSpawns.end()) + if ((*itr)->Chance) { - spawn = roll_chance_i((*itr)->Chance); - m_chosenSpawns[dbGuid] = spawn; - } - else - spawn = spawnItr->second; + auto spawnItr = m_chosenSpawns.find(dbGuid); + bool spawn = true; + if (spawnItr == m_chosenSpawns.end()) + { + spawn = roll_chance_i((*itr)->Chance); + m_chosenSpawns[dbGuid] = spawn; + } + else + spawn = spawnItr->second; - if (!spawn) - { - itr = eligibleGuids.erase(itr); - continue; + if (!spawn) + { + itr = eligibleGuids.erase(itr); + continue; + } } + else // filling redundant entries when a group has chanced spawns for optimization so we can stop at start + m_chosenSpawns[dbGuid] = true; } - else // filling redundant entries when a group has chanced spawns for optimization so we can stop at start - m_chosenSpawns[dbGuid] = true; + ++itr; } - ++itr; - } - - eligibleGuids.resize(std::min(eligibleGuids.size(), m_entry.MaxCount - m_objects.size())); // now we have final count for processing - - auto pickCreatureEntry = [&](const SpawnGroupDbGuids* guids) -> uint32 - { - uint32 entry; - // some group members can have static entry, or selfcontained random entry - if (guids->RandomEntry) - entry = sObjectMgr.GetRandomCreatureEntry(guids->DbGuid); - else if (guids->OwnEntry) - entry = guids->OwnEntry; - else - entry = GetEligibleEntry(validEntries, minEntries); - - return entry; - }; - - auto pickGoEntry = [&](const SpawnGroupDbGuids* guids) -> uint32 - { - uint32 entry; - // some group members can have static entry, or selfcontained random entry - if (guids->RandomEntry) - entry = sObjectMgr.GetRandomGameObjectEntry(guids->DbGuid); - else if (guids->OwnEntry) - entry = guids->OwnEntry; - else - entry = GetEligibleEntry(validEntries, minEntries); - - return entry; - }; - - auto eraseEntry = [&](uint32 entry) - { - if (entry) - { - if (validEntries[entry]) - --validEntries[entry]; - auto itr = minEntries.find(entry); - if (itr != minEntries.end()) - { - (*itr).second -= 1; - if ((*itr).second == 0) - minEntries.erase(itr); - } - } - }; + eligibleGuids.resize(std::min(eligibleGuids.size(), m_entry.MaxCount - m_objects.size())); // now we have final count for processing - // pick static and random entry first in dungeons so spawn group logic can decide after - if (m_map.IsDungeon() && GetObjectTypeId() == TYPEID_UNIT) - { - for (auto data : eligibleGuids) + // pick static and random entry first in dungeons so spawn group logic can decide after + if (m_map.IsDungeon() && GetObjectTypeId() == TYPEID_UNIT) { - if (data->RandomEntry || data->OwnEntry) + for (auto data : eligibleGuids) { - uint32 entry = pickCreatureEntry(data); - m_chosenEntries[data->DbGuid] = entry; - eraseEntry(entry); + if (data->RandomEntry || data->OwnEntry) + { + uint32 entry = pickCreatureEntry(data); + m_chosenEntries[data->DbGuid] = entry; + eraseEntry(entry); + } } - } - for (auto data : eligibleGuids) - { - if (!data->RandomEntry && !data->OwnEntry) + for (auto data : eligibleGuids) { - uint32 entry = pickCreatureEntry(data); - m_chosenEntries[data->DbGuid] = entry; - eraseEntry(entry); + if (!data->RandomEntry && !data->OwnEntry) + { + uint32 entry = pickCreatureEntry(data); + m_chosenEntries[data->DbGuid] = entry; + eraseEntry(entry); + } } } } + else // squad code + { + auto& chosenSquad = m_entry.Squads[m_chosenSquad]; + if (m_map.IsDungeon() && GetObjectTypeId() == TYPEID_UNIT) + for (auto& squadSpawn : chosenSquad.GuidToEntry) + m_chosenEntries[squadSpawn.first] = squadSpawn.second; + } for (auto itr = eligibleGuids.begin(); itr != eligibleGuids.end(); ++itr) { @@ -345,11 +386,19 @@ void SpawnGroup::Spawn(bool force) entry = m_chosenEntries[dbGuid]; // only held in memory - implement saving to db if it becomes a major issue else { - if (GetObjectTypeId() == TYPEID_UNIT) - entry = pickCreatureEntry(*itr); - else // GOs always pick random entry - entry = pickGoEntry(*itr); - eraseEntry(entry); + if (m_chosenSquad == -1) + { + if (GetObjectTypeId() == TYPEID_UNIT) + entry = pickCreatureEntry(*itr); + else // GOs always pick random entry + entry = pickGoEntry(*itr); + eraseEntry(entry); + } + else + { + auto& chosenSquad = m_entry.Squads[m_chosenSquad]; + entry = chosenSquad.GuidToEntry.find(dbGuid)->second; // always exists + } } float x, y; @@ -425,6 +474,19 @@ void SpawnGroup::RespawnIfInVicinity(Position pos, float range) m_map.GetPersistentState()->SaveObjectRespawnTime(GetObjectTypeId(), dbGuid.DbGuid, now); } +bool SpawnGroup::IsRespawnOverriden() const +{ + return m_entry.RespawnOverrideMin.has_value(); +} + +uint32 SpawnGroup::GetRandomRespawnTime() const +{ + if (!m_entry.RespawnOverrideMax.has_value()) + return *m_entry.RespawnOverrideMin; + + return urand(*m_entry.RespawnOverrideMin, *m_entry.RespawnOverrideMax); +} + std::string SpawnGroup::to_string() const { std::stringstream result; diff --git a/src/game/Maps/SpawnGroup.h b/src/game/Maps/SpawnGroup.h index fc010fdb450..58d850f3654 100644 --- a/src/game/Maps/SpawnGroup.h +++ b/src/game/Maps/SpawnGroup.h @@ -60,14 +60,19 @@ class SpawnGroup void RespawnIfInVicinity(Position pos, float range); + bool IsRespawnOverriden() const; + uint32 GetRandomRespawnTime() const; + protected: SpawnGroupEntry const& m_entry; Map& m_map; std::map m_objects; std::map m_chosenEntries; // dungeon saving for entry per dynguid std::map m_chosenSpawns; + int32 m_chosenSquad; uint32 m_objectTypeId; bool m_enabled; + TimePoint m_cooldown; // used for full wipe scenario only - data is still saved per spawn to db }; class CreatureGroup : public SpawnGroup diff --git a/src/game/Maps/SpawnGroupDefines.h b/src/game/Maps/SpawnGroupDefines.h index 5bb1bef367b..5087488edfa 100644 --- a/src/game/Maps/SpawnGroupDefines.h +++ b/src/game/Maps/SpawnGroupDefines.h @@ -24,6 +24,7 @@ #include #include #include +#include struct FormationEntry; class FormationData; @@ -53,6 +54,12 @@ struct SpawnGroupDbGuids bool RandomEntry; }; +struct SpawnGroupSquad +{ + uint32 SquadId; + std::map GuidToEntry; +}; + enum SpawnGroupType { SPAWN_GROUP_CREATURE = 0, @@ -92,6 +99,8 @@ struct SpawnGroupEntry int32 WorldStateExpression; // Exclusive with condition uint32 Flags; uint32 StringId; + std::optional RespawnOverrideMin; + std::optional RespawnOverrideMax; bool Active; bool EnabledByDefault; bool HasChancedSpawns; @@ -101,6 +110,8 @@ struct SpawnGroupEntry std::vector ExplicitlyChanced; std::vector LinkedGroups; + std::vector Squads; + // may be nullptr std::unique_ptr Formation; diff --git a/src/shared/revision_sql.h b/src/shared/revision_sql.h index 1c8e528276c..77ad042756d 100644 --- a/src/shared/revision_sql.h +++ b/src/shared/revision_sql.h @@ -3,5 +3,5 @@ #define REVISION_DB_REALMD "required_s2474_01_realmd_joindate_datetime" #define REVISION_DB_LOGS "required_s2433_01_logs_anticheat" #define REVISION_DB_CHARACTERS "required_s2473_01_characters_item_instance_text_id_fix" - #define REVISION_DB_MANGOS "required_s2479_01_mangos_displayid_probability" + #define REVISION_DB_MANGOS "required_s2480_01_mangos_spawn_group_squad" #endif // __REVISION_SQL_H__