feat(Core/Maps): port spawn system/dynamic spawns from TrinityCore (#25206)

Co-authored-by: r00ty-tc <r00ty-tc@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Andrew
2026-04-12 17:52:01 -03:00
committed by GitHub
parent a8d327f21c
commit f5c4de92eb
33 changed files with 1456 additions and 114 deletions
+57 -1
View File
@@ -57,7 +57,9 @@ public:
{ "load", HandleGameObjectLoadCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "set phase", HandleGameObjectSetPhaseCommand, SEC_ADMINISTRATOR, Console::No },
{ "set state", HandleGameObjectSetStateCommand, SEC_ADMINISTRATOR, Console::No },
{ "respawn", HandleGameObjectRespawn, SEC_GAMEMASTER, Console::No }
{ "respawn", HandleGameObjectRespawn, SEC_GAMEMASTER, Console::No },
{ "spawngroup", HandleGameObjectSpawnGroupCommand, SEC_ADMINISTRATOR, Console::No },
{ "despawngroup", HandleGameObjectDespawnGroupCommand, SEC_ADMINISTRATOR, Console::No }
};
static ChatCommandTable commandTable =
{
@@ -688,6 +690,60 @@ public:
handler->PSendSysMessage(LANG_CMD_GO_RESPAWN, object->GetNameForLocaleIdx(handler->GetSessionDbcLocale()), object->GetEntry(), object->GetSpawnId());
return true;
}
static bool HandleGameObjectSpawnGroupCommand(ChatHandler* handler, uint32 groupId)
{
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return false;
SpawnGroupTemplateData const* groupData = sObjectMgr->GetSpawnGroupData(groupId);
if (!groupData)
{
handler->SendErrorMessage(LANG_SPAWNGROUP_NOT_FOUND, groupId);
return false;
}
if (groupData->flags & SPAWNGROUP_FLAG_SYSTEM)
{
handler->SendErrorMessage(LANG_SPAWNGROUP_SPAWN_SYSTEM_ERROR, groupId, groupData->name);
return false;
}
if (player->GetMap()->SpawnGroupSpawn(groupId, true, true))
handler->PSendSysMessage(LANG_SPAWNGROUP_SPAWN_SUCCESS, groupId, groupData->name);
else
handler->SendErrorMessage(LANG_SPAWNGROUP_SPAWN_FAILED, groupId, groupData->name);
return true;
}
static bool HandleGameObjectDespawnGroupCommand(ChatHandler* handler, uint32 groupId)
{
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return false;
SpawnGroupTemplateData const* groupData = sObjectMgr->GetSpawnGroupData(groupId);
if (!groupData)
{
handler->SendErrorMessage(LANG_SPAWNGROUP_NOT_FOUND, groupId);
return false;
}
if (groupData->flags & SPAWNGROUP_FLAG_SYSTEM)
{
handler->SendErrorMessage(LANG_SPAWNGROUP_DESPAWN_SYSTEM_ERROR, groupId, groupData->name);
return false;
}
if (player->GetMap()->SpawnGroupDespawn(groupId, true))
handler->PSendSysMessage(LANG_SPAWNGROUP_DESPAWN_SUCCESS, groupId, groupData->name);
else
handler->SendErrorMessage(LANG_SPAWNGROUP_DESPAWN_FAILED, groupId, groupData->name);
return true;
}
};
void AddSC_gobject_commandscript()
+58 -4
View File
@@ -20,6 +20,7 @@
#include "Creature.h"
#include "DBCStores.h"
#include "DatabaseEnv.h"
#include "GameTime.h"
#include "GameObject.h"
#include "Language.h"
#include "MapMgr.h"
@@ -45,10 +46,11 @@ public:
static ChatCommandTable listCommandTable =
{
{ "creature", HandleListCreatureCommand, SEC_MODERATOR, Console::Yes },
{ "item", HandleListItemCommand, SEC_MODERATOR, Console::Yes },
{ "object", HandleListObjectCommand, SEC_MODERATOR, Console::Yes },
{ "auras", listAurasCommandTable },
{ "creature", HandleListCreatureCommand, SEC_MODERATOR, Console::Yes },
{ "item", HandleListItemCommand, SEC_MODERATOR, Console::Yes },
{ "object", HandleListObjectCommand, SEC_MODERATOR, Console::Yes },
{ "auras", listAurasCommandTable },
{ "respawns", HandleListRespawnsCommand, SEC_GAMEMASTER, Console::No },
};
static ChatCommandTable commandTable =
{
@@ -517,6 +519,58 @@ public:
return true;
}
static bool HandleListRespawnsCommand(ChatHandler* handler)
{
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return false;
Map* map = player->GetMap();
uint32 count = 0;
time_t now = GameTime::GetGameTime().count();
handler->PSendSysMessage(LANG_LIST_RESPAWNS_CREATURE_HEADER, map->GetId(), map->GetInstanceId());
for (auto const& pair : map->GetCreatureRespawnTimes())
{
CreatureData const* data = sObjectMgr->GetCreatureData(pair.first);
if (!data)
continue;
CreatureTemplate const* cTemplate = sObjectMgr->GetCreatureTemplate(data->id1);
std::string name = cTemplate ? cTemplate->Name : "Unknown";
time_t remaining = pair.second > now ? pair.second - now : 0;
handler->PSendSysMessage(LANG_LIST_RESPAWNS_CREATURE_ENTRY, pair.first, name, data->id1, remaining);
++count;
if (count >= 50)
{
handler->SendSysMessage(LANG_LIST_RESPAWNS_LIMIT);
break;
}
}
count = 0;
handler->SendSysMessage(LANG_LIST_RESPAWNS_GO_HEADER);
for (auto const& pair : map->GetGORespawnTimes())
{
GameObjectData const* data = sObjectMgr->GetGameObjectData(pair.first);
if (!data)
continue;
GameObjectTemplate const* goTemplate = sObjectMgr->GetGameObjectTemplate(data->id);
std::string name = goTemplate ? goTemplate->name : "Unknown";
time_t remaining = pair.second > now ? pair.second - now : 0;
handler->PSendSysMessage(LANG_LIST_RESPAWNS_GO_ENTRY, pair.first, name, data->id, remaining);
++count;
if (count >= 50)
{
handler->SendSysMessage(LANG_LIST_RESPAWNS_LIMIT);
break;
}
}
return true;
}
};
void AddSC_list_commandscript()
+43
View File
@@ -40,6 +40,7 @@
#include "ObjectAccessor.h"
#include "Pet.h"
#include "Player.h"
#include "PoolMgr.h"
#include "Realm.h"
#include "ScriptMgr.h"
#include "SpellAuras.h"
@@ -2474,10 +2475,52 @@ public:
{
Player* player = handler->GetSession()->GetPlayer();
// Phase 1: respawn creatures/GOs that still have corpses in the grid
Acore::RespawnDo u_do;
Acore::WorldObjectWorker<Acore::RespawnDo> worker(player, u_do);
Cell::VisitObjects(player, worker, player->GetGridActivationRange());
// Phase 2: force-respawn creatures/GOs that were fully removed (non-compat mode)
// by setting their respawn times to now so ProcessRespawns() picks them up
Map* map = player->GetMap();
uint32 gridId = Acore::ComputeGridCoord(player->GetPositionX(), player->GetPositionY()).GetId();
time_t now = GameTime::GetGameTime().count();
std::vector<ObjectGuid::LowType> creaturesToRespawn;
for (auto const& pair : map->GetCreatureRespawnTimes())
{
CreatureData const* data = sObjectMgr->GetCreatureData(pair.first);
if (!data || Acore::ComputeGridCoord(data->posX, data->posY).GetId() != gridId)
continue;
// Skip pooled spawns — Phase 1 already triggered pool rotation via
// Creature::Respawn() -> PoolMgr::UpdatePool(). Forcing a respawn time
// here would cause ProcessRespawns() to call UpdatePool() again,
// spawning duplicates beyond the pool's max_limit.
if (sPoolMgr->IsPartOfAPool<Creature>(pair.first))
continue;
creaturesToRespawn.push_back(pair.first);
}
for (ObjectGuid::LowType spawnId : creaturesToRespawn)
map->SaveCreatureRespawnTime(spawnId, now);
std::vector<ObjectGuid::LowType> goesToRespawn;
for (auto const& pair : map->GetGORespawnTimes())
{
GameObjectData const* data = sObjectMgr->GetGameObjectData(pair.first);
if (!data || Acore::ComputeGridCoord(data->posX, data->posY).GetId() != gridId)
continue;
// Skip pooled spawns — same reason as creatures above.
if (sPoolMgr->IsPartOfAPool<GameObject>(pair.first))
continue;
goesToRespawn.push_back(pair.first);
}
for (ObjectGuid::LowType spawnId : goesToRespawn)
map->SaveGORespawnTime(spawnId, now);
return true;
}
+57 -1
View File
@@ -199,7 +199,9 @@ public:
{ "delete", npcDeleteCommandTable },
{ "follow", npcFollowCommandTable },
{ "load", HandleNpcLoadCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "set", npcSetCommandTable }
{ "set", npcSetCommandTable },
{ "spawngroup", HandleNpcSpawnGroupCommand, SEC_ADMINISTRATOR, Console::No },
{ "despawngroup", HandleNpcDespawnGroupCommand, SEC_ADMINISTRATOR, Console::No }
};
static ChatCommandTable commandTable =
{
@@ -1435,6 +1437,60 @@ public:
handler->PSendSysMessage("LinkGUID '{}' added to creature with DBTableGUID: '{}'", linkguid, creature->GetSpawnId());
return true;
}
static bool HandleNpcSpawnGroupCommand(ChatHandler* handler, uint32 groupId)
{
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return false;
SpawnGroupTemplateData const* groupData = sObjectMgr->GetSpawnGroupData(groupId);
if (!groupData)
{
handler->SendErrorMessage(LANG_SPAWNGROUP_NOT_FOUND, groupId);
return false;
}
if (groupData->flags & SPAWNGROUP_FLAG_SYSTEM)
{
handler->SendErrorMessage(LANG_SPAWNGROUP_SPAWN_SYSTEM_ERROR, groupId, groupData->name);
return false;
}
if (player->GetMap()->SpawnGroupSpawn(groupId, true, true))
handler->PSendSysMessage(LANG_SPAWNGROUP_SPAWN_SUCCESS, groupId, groupData->name);
else
handler->SendErrorMessage(LANG_SPAWNGROUP_SPAWN_FAILED, groupId, groupData->name);
return true;
}
static bool HandleNpcDespawnGroupCommand(ChatHandler* handler, uint32 groupId)
{
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return false;
SpawnGroupTemplateData const* groupData = sObjectMgr->GetSpawnGroupData(groupId);
if (!groupData)
{
handler->SendErrorMessage(LANG_SPAWNGROUP_NOT_FOUND, groupId);
return false;
}
if (groupData->flags & SPAWNGROUP_FLAG_SYSTEM)
{
handler->SendErrorMessage(LANG_SPAWNGROUP_DESPAWN_SYSTEM_ERROR, groupId, groupData->name);
return false;
}
if (player->GetMap()->SpawnGroupDespawn(groupId, true))
handler->PSendSysMessage(LANG_SPAWNGROUP_DESPAWN_SUCCESS, groupId, groupData->name);
else
handler->SendErrorMessage(LANG_SPAWNGROUP_DESPAWN_FAILED, groupId, groupData->name);
return true;
}
};
void AddSC_npc_commandscript()
+235
View File
@@ -0,0 +1,235 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* 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 2 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 <http://www.gnu.org/licenses/>.
*/
#include "Chat.h"
#include "CommandScript.h"
#include "Creature.h"
#include "GameTime.h"
#include "GameObject.h"
#include "Language.h"
#include "MapMgr.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "PoolMgr.h"
using namespace Acore::ChatCommands;
class pool_commandscript : public CommandScript
{
public:
pool_commandscript() : CommandScript("pool_commandscript") { }
ChatCommandTable GetCommands() const override
{
static ChatCommandTable poolCommandTable =
{
{ "info", HandlePoolInfoCommand, SEC_GAMEMASTER, Console::Yes },
{ "lookup", HandlePoolLookupCommand, SEC_GAMEMASTER, Console::No },
};
static ChatCommandTable commandTable =
{
{ "pool", poolCommandTable }
};
return commandTable;
}
private:
static char const* StatusTag(bool active)
{
return active ? "ACTIVE" : "inactive";
}
static void ListPoolMembers(ChatHandler* handler, char const* typeName,
std::vector<PoolObject> const& explicitly,
std::vector<PoolObject> const& equal, bool isCreature)
{
if (explicitly.empty() && equal.empty())
return;
uint32 total = explicitly.size() + equal.size();
handler->PSendSysMessage(LANG_POOL_INFO_MEMBERS_HEADER, typeName, total);
auto printMember = [&](PoolObject const& obj)
{
bool spawned = isCreature
? sPoolMgr->IsSpawnedObject<Creature>(obj.guid)
: sPoolMgr->IsSpawnedObject<GameObject>(obj.guid);
std::string name = "Unknown";
uint32 entry = 0;
uint32 mapId = 0;
float x = 0, y = 0, z = 0;
if (isCreature)
{
if (CreatureData const* data = sObjectMgr->GetCreatureData(obj.guid))
{
entry = data->id1;
mapId = data->mapid;
x = data->posX;
y = data->posY;
z = data->posZ;
if (CreatureTemplate const* tpl = sObjectMgr->GetCreatureTemplate(entry))
name = tpl->Name;
}
}
else
{
if (GameObjectData const* data = sObjectMgr->GetGameObjectData(obj.guid))
{
entry = data->id;
mapId = data->mapid;
x = data->posX;
y = data->posY;
z = data->posZ;
if (GameObjectTemplate const* tpl = sObjectMgr->GetGameObjectTemplate(entry))
name = tpl->name;
}
}
handler->PSendSysMessage(LANG_POOL_INFO_MEMBER,
StatusTag(spawned), obj.guid, name, entry, mapId, x, y, z);
};
for (auto const& obj : explicitly)
printMember(obj);
for (auto const& obj : equal)
printMember(obj);
}
static bool HandlePoolInfoCommand(ChatHandler* handler, uint32 poolId)
{
PoolTemplateData const* tpl = sPoolMgr->GetPoolTemplate(poolId);
if (!tpl)
{
handler->PSendSysMessage(LANG_POOL_NOT_FOUND, poolId);
handler->SetSentErrorMessage(true);
return false;
}
uint32 activeCount = sPoolMgr->GetSpawnedData().GetActiveObjectCount(poolId);
handler->PSendSysMessage(LANG_POOL_INFO_HEADER, poolId,
tpl->Description.empty() ? "(none)" : tpl->Description,
tpl->MaxLimit, activeCount);
// Show parent pool if nested
uint32 parentPool = sPoolMgr->IsPartOfAPool<Pool>(poolId);
if (parentPool)
handler->PSendSysMessage(" Parent pool: {}", parentPool);
// Creature members
if (PoolGroup<Creature> const* creGroup = sPoolMgr->GetPoolCreatureGroup(poolId))
{
if (!creGroup->IsEmpty())
ListPoolMembers(handler, "Creatures",
creGroup->GetExplicitlyChanced(), creGroup->GetEqualChanced(), true);
}
// GameObject members
if (PoolGroup<GameObject> const* goGroup = sPoolMgr->GetPoolGameObjectGroup(poolId))
{
if (!goGroup->IsEmpty())
ListPoolMembers(handler, "GameObjects",
goGroup->GetExplicitlyChanced(), goGroup->GetEqualChanced(), false);
}
// Sub-pool members
if (PoolGroup<Pool> const* poolGroup = sPoolMgr->GetPoolPoolGroup(poolId))
{
auto const& explicitly = poolGroup->GetExplicitlyChanced();
auto const& equal = poolGroup->GetEqualChanced();
if (!explicitly.empty() || !equal.empty())
{
uint32 total = explicitly.size() + equal.size();
handler->PSendSysMessage(LANG_POOL_INFO_SUBPOOLS_HEADER, total);
auto printSubPool = [&](PoolObject const& obj)
{
bool active = sPoolMgr->GetSpawnedData().IsActiveObject<Pool>(obj.guid);
PoolTemplateData const* subTpl = sPoolMgr->GetPoolTemplate(obj.guid);
std::string desc = subTpl ? subTpl->Description : "Unknown";
handler->PSendSysMessage(LANG_POOL_INFO_SUBPOOL,
StatusTag(active), obj.guid, desc);
};
for (auto const& obj : explicitly)
printSubPool(obj);
for (auto const& obj : equal)
printSubPool(obj);
}
}
return true;
}
static bool HandlePoolLookupCommand(ChatHandler* handler)
{
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return false;
// Check targeted creature
Creature* target = handler->getSelectedCreature();
if (target && target->GetSpawnId())
{
uint32 spawnId = target->GetSpawnId();
uint32 poolId = sPoolMgr->GetCreaturePoolId(spawnId);
if (poolId)
{
bool spawned = sPoolMgr->IsSpawnedObject<Creature>(spawnId);
handler->PSendSysMessage(LANG_POOL_LOOKUP_IN_POOL,
target->GetName(), spawnId, poolId, StatusTag(spawned));
handler->PSendSysMessage(LANG_POOL_LOOKUP_USE_INFO, poolId);
}
else
handler->PSendSysMessage(LANG_POOL_LOOKUP_NOT_IN_POOL,
target->GetName(), spawnId);
return true;
}
// Check nearby gameobject
GameObject* goTarget = handler->GetNearbyGameObject();
if (goTarget && goTarget->GetSpawnId())
{
uint32 spawnId = goTarget->GetSpawnId();
uint32 poolId = sPoolMgr->GetGameObjectPoolId(spawnId);
if (poolId)
{
bool spawned = sPoolMgr->IsSpawnedObject<GameObject>(spawnId);
handler->PSendSysMessage(LANG_POOL_LOOKUP_IN_POOL,
goTarget->GetName(), spawnId, poolId, StatusTag(spawned));
handler->PSendSysMessage(LANG_POOL_LOOKUP_USE_INFO, poolId);
}
else
handler->PSendSysMessage(LANG_POOL_LOOKUP_NOT_IN_POOL,
goTarget->GetName(), spawnId);
return true;
}
handler->SendSysMessage(LANG_POOL_LOOKUP_NOTARGET);
handler->SetSentErrorMessage(true);
return false;
}
};
void AddSC_pool_commandscript()
{
new pool_commandscript();
}
+10
View File
@@ -150,6 +150,7 @@ public:
{ "skill_fishing_base_level", HandleReloadSkillFishingBaseLevelCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "skinning_loot_template", HandleReloadLootTemplatesSkinningCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "smart_scripts", HandleReloadSmartScripts, SEC_ADMINISTRATOR, Console::Yes },
{ "spawn_group", HandleReloadSpawnGroupCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "spell_required", HandleReloadSpellRequiredCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "spell_area", HandleReloadSpellAreaCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "spell_bonus_data", HandleReloadSpellBonusesCommand, SEC_ADMINISTRATOR, Console::Yes },
@@ -1258,6 +1259,15 @@ public:
handler->SendGlobalGMSysMessage("DB table `game_graveyard` reloaded.");
return true;
}
static bool HandleReloadSpawnGroupCommand(ChatHandler* handler)
{
LOG_INFO("server.loading", "Reloading spawn_group_template and spawn_group tables...");
sObjectMgr->LoadSpawnGroupTemplates();
sObjectMgr->LoadSpawnGroups();
handler->SendGlobalGMSysMessage("DB tables `spawn_group_template` and `spawn_group` reloaded.");
return true;
}
};
void AddSC_reload_commandscript()
@@ -51,6 +51,7 @@ void AddSC_modify_commandscript();
void AddSC_npc_commandscript();
void AddSC_pet_commandscript();
void AddSC_player_commandscript();
void AddSC_pool_commandscript();
void AddSC_pooltools_commandscript();
void AddSC_quest_commandscript();
void AddSC_reload_commandscript();
@@ -107,6 +108,7 @@ void AddCommandsScripts()
AddSC_npc_commandscript();
AddSC_pet_commandscript();
AddSC_player_commandscript();
AddSC_pool_commandscript();
AddSC_pooltools_commandscript();
AddSC_quest_commandscript();
AddSC_reload_commandscript();