feat(Scripts/Commands): add respawn guid/entry' command and expand list resp… (#25787)

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Andrew
2026-05-14 06:50:43 -03:00
committed by GitHub
parent 1dd2236a06
commit 485769354c
6 changed files with 371 additions and 9 deletions

View File

@@ -677,6 +677,10 @@ enum RBACPermissions
RBAC_PERM_COMMAND_BF_QUEUE = 913,
RBAC_PERM_COMMAND_PET_LIST = 914,
RBAC_PERM_COMMAND_PET_DELETE = 915,
RBAC_PERM_COMMAND_RESPAWN_CREATURE_GUID = 916,
RBAC_PERM_COMMAND_RESPAWN_GAMEOBJECT_GUID = 917,
RBAC_PERM_COMMAND_RESPAWN_CREATURE_ENTRY = 918,
RBAC_PERM_COMMAND_RESPAWN_GAMEOBJECT_ENTRY = 919,
// custom permissions 1000+
RBAC_PERM_MAX
};

View File

@@ -1489,6 +1489,25 @@ enum AcoreStrings
LANG_PET_DELETE_NOT_FOUND = 35438,
LANG_PET_DELETE_SUCCESS = 35439,
LANG_CHARACTER_DELETED_LIST_LIMIT = 35440
LANG_CHARACTER_DELETED_LIST_LIMIT = 35440,
// Respawn creature/gameobject by spawn GUID commands
LANG_RESPAWN_GUID_CREATURE_NOT_FOUND = 35441,
LANG_RESPAWN_GUID_MAP_NOT_LOADED = 35442,
LANG_RESPAWN_GUID_CREATURE_ALIVE = 35443,
LANG_RESPAWN_GUID_CREATURE_QUEUED = 35444,
LANG_RESPAWN_GUID_GAMEOBJECT_ACTIVE = 35445,
LANG_RESPAWN_GUID_GAMEOBJECT_QUEUED = 35446,
// 35447 is reserved for LANG_LIST_RESPAWNS_NO_MAP (list respawns command, defined at the end of this enum)
LANG_RESPAWN_GUID_GAMEOBJECT_NOT_FOUND = 35448,
// Respawn creature/gameobject by entry commands
LANG_RESPAWN_ENTRY_CREATURE_NOT_FOUND = 35449,
LANG_RESPAWN_ENTRY_GAMEOBJECT_NOT_FOUND = 35450,
LANG_RESPAWN_ENTRY_CREATURE_QUEUED = 35451,
LANG_RESPAWN_ENTRY_GAMEOBJECT_QUEUED = 35452,
// List respawns console support
LANG_LIST_RESPAWNS_NO_MAP = 35447
};
#endif

View File

@@ -51,7 +51,7 @@ public:
{ "item", HandleListItemCommand, rbac::RBAC_PERM_COMMAND_LIST_ITEM, Console::Yes },
{ "object", HandleListObjectCommand, rbac::RBAC_PERM_COMMAND_LIST_OBJECT, Console::Yes },
{ "auras", listAurasCommandTable },
{ "respawns", HandleListRespawnsCommand, rbac::RBAC_PERM_COMMAND_LIST_CREATURE, Console::No },
{ "respawns", HandleListRespawnsCommand, rbac::RBAC_PERM_COMMAND_LIST_RESPAWNS, Console::Yes },
};
static ChatCommandTable commandTable =
{
@@ -521,13 +521,35 @@ public:
return true;
}
static bool HandleListRespawnsCommand(ChatHandler* handler)
static bool HandleListRespawnsCommand(ChatHandler* handler, Optional<uint32> firstArg, Optional<uint32> secondArg, Optional<uint32> thirdArg)
{
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return false;
Map* map = nullptr;
Optional<uint32> entryFilter;
if (handler->GetSession())
{
// In-game: first arg = entryId (optional), use player's current map
map = handler->GetSession()->GetPlayer()->GetMap();
entryFilter = firstArg;
}
else
{
// Console: first arg = mapId (required), second = instanceId, third = entryId
if (!firstArg)
{
handler->SendSysMessage(LANG_LIST_RESPAWNS_NO_MAP);
return false;
}
map = sMapMgr->FindMap(*firstArg, secondArg.value_or(0));
entryFilter = thirdArg;
}
if (!map)
{
handler->PSendSysMessage(LANG_RESPAWN_GUID_MAP_NOT_LOADED, firstArg.value_or(0));
return false;
}
Map* map = player->GetMap();
uint32 count = 0;
time_t now = GameTime::GetGameTime().count();
@@ -535,7 +557,7 @@ public:
for (auto const& pair : map->GetCreatureRespawnTimes())
{
CreatureData const* data = sObjectMgr->GetCreatureData(pair.first);
if (!data)
if (!data || (entryFilter && data->id1 != *entryFilter))
continue;
CreatureTemplate const* cTemplate = sObjectMgr->GetCreatureTemplate(data->id1);
@@ -555,7 +577,7 @@ public:
for (auto const& pair : map->GetGORespawnTimes())
{
GameObjectData const* data = sObjectMgr->GetGameObjectData(pair.first);
if (!data)
if (!data || (entryFilter && data->id != *entryFilter))
continue;
GameObjectTemplate const* goTemplate = sObjectMgr->GetGameObjectTemplate(data->id);

View File

@@ -24,6 +24,7 @@
#include "CommandScript.h"
#include "Common.h"
#include "GameGraveyard.h"
#include "GameObject.h"
#include "GameTime.h"
#include "GridNotifiers.h"
#include "GridTerrainLoader.h"
@@ -177,6 +178,10 @@ public:
{ "pinfo", HandlePInfoCommand, rbac::RBAC_PERM_COMMAND_PINFO, Console::Yes },
{ "respawn", HandleRespawnCommand, rbac::RBAC_PERM_COMMAND_RESPAWN, Console::No },
{ "respawn all", HandleRespawnAllCommand, rbac::RBAC_PERM_COMMAND_RESPAWN_ALL, Console::No },
{ "respawn creature guid", HandleRespawnCreatureByGuidCommand, rbac::RBAC_PERM_COMMAND_RESPAWN_CREATURE_GUID, Console::Yes },
{ "respawn gameobject guid", HandleRespawnGameObjectByGuidCommand, rbac::RBAC_PERM_COMMAND_RESPAWN_GAMEOBJECT_GUID, Console::Yes },
{ "respawn creature entry", HandleRespawnCreatureByEntryCommand, rbac::RBAC_PERM_COMMAND_RESPAWN_CREATURE_ENTRY, Console::Yes },
{ "respawn gameobject entry", HandleRespawnGameObjectByEntryCommand, rbac::RBAC_PERM_COMMAND_RESPAWN_GAMEOBJECT_ENTRY, Console::Yes },
{ "mute", HandleMuteCommand, rbac::RBAC_PERM_COMMAND_MUTE, Console::Yes },
{ "mutehistory", HandleMuteInfoCommand, rbac::RBAC_PERM_COMMAND_MUTEHISTORY, Console::Yes },
{ "unmute", HandleUnmuteCommand, rbac::RBAC_PERM_COMMAND_UNMUTE, Console::Yes },
@@ -2448,6 +2453,265 @@ public:
return true;
}
static bool HandleRespawnCreatureByGuidCommand(ChatHandler* handler, ObjectGuid::LowType spawnId)
{
CreatureData const* creData = sObjectMgr->GetCreatureData(spawnId);
if (!creData)
{
handler->SendErrorMessage(LANG_RESPAWN_GUID_CREATURE_NOT_FOUND, spawnId);
return false;
}
Map* map = nullptr;
if (handler->GetSession())
{
Player* player = handler->GetSession()->GetPlayer();
if (player->GetMapId() == creData->mapid)
map = player->GetMap();
}
if (!map)
map = sMapMgr->FindMap(creData->mapid, 0);
if (!map)
{
handler->PSendSysMessage(LANG_RESPAWN_GUID_MAP_NOT_LOADED, creData->mapid);
return true;
}
// First pass: check if any instance is alive
bool isAlive = false;
auto const creBounds = map->GetCreatureBySpawnIdStore().equal_range(spawnId);
for (auto itr = creBounds.first; itr != creBounds.second; ++itr)
{
if (itr->second->IsAlive())
{
isAlive = true;
break;
}
}
if (isAlive)
{
handler->PSendSysMessage(LANG_RESPAWN_GUID_CREATURE_ALIVE, spawnId, creData->id1);
return true;
}
// Second pass: respawn any dead corpses in the world
for (auto itr = creBounds.first; itr != creBounds.second; ++itr)
{
if (itr->second->isDead())
itr->second->Respawn(true);
}
// Also trigger via respawn time queue for fully-removed spawns
if (map->GetCreatureRespawnTime(spawnId) > 0)
{
time_t now = GameTime::GetGameTime().count();
map->SaveCreatureRespawnTime(spawnId, now);
}
handler->PSendSysMessage(LANG_RESPAWN_GUID_CREATURE_QUEUED, spawnId, creData->id1);
return true;
}
static bool HandleRespawnGameObjectByGuidCommand(ChatHandler* handler, ObjectGuid::LowType spawnId)
{
GameObjectData const* goData = sObjectMgr->GetGameObjectData(spawnId);
if (!goData)
{
handler->SendErrorMessage(LANG_RESPAWN_GUID_GAMEOBJECT_NOT_FOUND, spawnId);
return false;
}
Map* map = nullptr;
if (handler->GetSession())
{
Player* player = handler->GetSession()->GetPlayer();
if (player->GetMapId() == goData->mapid)
map = player->GetMap();
}
if (!map)
map = sMapMgr->FindMap(goData->mapid, 0);
if (!map)
{
handler->PSendSysMessage(LANG_RESPAWN_GUID_MAP_NOT_LOADED, goData->mapid);
return true;
}
// First pass: check if any instance is already active
bool isActive = false;
auto const goBounds = map->GetGameObjectBySpawnIdStore().equal_range(spawnId);
for (auto itr = goBounds.first; itr != goBounds.second; ++itr)
{
if (itr->second->isSpawned())
{
isActive = true;
break;
}
}
if (isActive)
{
handler->PSendSysMessage(LANG_RESPAWN_GUID_GAMEOBJECT_ACTIVE, spawnId, goData->id);
return true;
}
// Second pass: respawn inactive objects in the world
for (auto itr = goBounds.first; itr != goBounds.second; ++itr)
itr->second->Respawn();
// Also trigger via respawn time queue for fully-removed spawns
if (map->GetGORespawnTime(spawnId) > 0)
{
time_t now = GameTime::GetGameTime().count();
map->SaveGORespawnTime(spawnId, now);
}
handler->PSendSysMessage(LANG_RESPAWN_GUID_GAMEOBJECT_QUEUED, spawnId, goData->id);
return true;
}
static bool HandleRespawnCreatureByEntryCommand(ChatHandler* handler, uint32 entry, Optional<uint32> mapIdArg, Optional<uint32> instanceIdArg)
{
if (!sObjectMgr->GetCreatureTemplate(entry))
{
handler->SendErrorMessage(LANG_RESPAWN_ENTRY_CREATURE_NOT_FOUND, entry);
return false;
}
Map* map = nullptr;
if (handler->GetSession())
{
// In-game: always use the player's current map
map = handler->GetSession()->GetPlayer()->GetMap();
}
else
{
// Console: mapId required, instanceId optional
if (!mapIdArg)
{
handler->SendSysMessage(LANG_LIST_RESPAWNS_NO_MAP);
return false;
}
map = sMapMgr->FindMap(*mapIdArg, instanceIdArg.value_or(0));
}
if (!map)
{
handler->PSendSysMessage(LANG_RESPAWN_GUID_MAP_NOT_LOADED, mapIdArg.value_or(0));
return false;
}
time_t now = GameTime::GetGameTime().count();
uint32 count = 0;
// Phase 1: respawn dead corpses that are still tracked in the spawn-id store.
// Collect first to avoid iterator invalidation caused by Respawn().
std::vector<Creature*> deadCreatures;
for (auto const& [spawnId, creature] : map->GetCreatureBySpawnIdStore())
{
CreatureData const* data = sObjectMgr->GetCreatureData(spawnId);
if (!data || data->id1 != entry)
continue;
if (creature->isDead())
deadCreatures.push_back(creature);
}
for (Creature* creature : deadCreatures)
{
creature->Respawn(true);
++count;
}
// Phase 2: set respawn time to now for fully-removed spawns, skipping pools
std::vector<ObjectGuid::LowType> toRespawn;
for (auto const& [spawnId, respawnTime] : map->GetCreatureRespawnTimes())
{
CreatureData const* data = sObjectMgr->GetCreatureData(spawnId);
if (!data || data->id1 != entry)
continue;
if (sPoolMgr->IsPartOfAPool<Creature>(spawnId))
continue;
toRespawn.push_back(spawnId);
}
for (ObjectGuid::LowType spawnId : toRespawn)
{
map->SaveCreatureRespawnTime(spawnId, now);
++count;
}
handler->PSendSysMessage(LANG_RESPAWN_ENTRY_CREATURE_QUEUED, count, entry);
return true;
}
static bool HandleRespawnGameObjectByEntryCommand(ChatHandler* handler, uint32 entry, Optional<uint32> mapIdArg, Optional<uint32> instanceIdArg)
{
if (!sObjectMgr->GetGameObjectTemplate(entry))
{
handler->SendErrorMessage(LANG_RESPAWN_ENTRY_GAMEOBJECT_NOT_FOUND, entry);
return false;
}
Map* map = nullptr;
if (handler->GetSession())
{
// In-game: always use the player's current map
map = handler->GetSession()->GetPlayer()->GetMap();
}
else
{
// Console: mapId required, instanceId optional
if (!mapIdArg)
{
handler->SendSysMessage(LANG_LIST_RESPAWNS_NO_MAP);
return false;
}
map = sMapMgr->FindMap(*mapIdArg, instanceIdArg.value_or(0));
}
if (!map)
{
handler->PSendSysMessage(LANG_RESPAWN_GUID_MAP_NOT_LOADED, mapIdArg.value_or(0));
return false;
}
time_t now = GameTime::GetGameTime().count();
uint32 count = 0;
// Phase 1: respawn inactive objects that are still tracked in the spawn-id store.
// Collect first to avoid iterator invalidation caused by Respawn().
std::vector<GameObject*> inactiveGOs;
for (auto const& [spawnId, go] : map->GetGameObjectBySpawnIdStore())
{
GameObjectData const* data = sObjectMgr->GetGameObjectData(spawnId);
if (!data || data->id != entry)
continue;
if (!go->isSpawned())
inactiveGOs.push_back(go);
}
for (GameObject* go : inactiveGOs)
{
go->Respawn();
++count;
}
// Phase 2: set respawn time to now for fully-removed spawns, skipping pools
std::vector<ObjectGuid::LowType> toRespawn;
for (auto const& [spawnId, respawnTime] : map->GetGORespawnTimes())
{
GameObjectData const* data = sObjectMgr->GetGameObjectData(spawnId);
if (!data || data->id != entry)
continue;
if (sPoolMgr->IsPartOfAPool<GameObject>(spawnId))
continue;
toRespawn.push_back(spawnId);
}
for (ObjectGuid::LowType spawnId : toRespawn)
{
map->SaveGORespawnTime(spawnId, now);
++count;
}
handler->PSendSysMessage(LANG_RESPAWN_ENTRY_GAMEOBJECT_QUEUED, count, entry);
return true;
}
static bool HandleRespawnCommand(ChatHandler* handler)
{
Player* player = handler->GetSession()->GetPlayer();