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:
parent
a8d327f21c
commit
f5c4de92eb
33 changed files with 1456 additions and 114 deletions
|
|
@ -4754,6 +4754,27 @@ Respawn.DynamicRateGameObject = 1
|
|||
|
||||
Respawn.DynamicMinimumGameObject = 10
|
||||
|
||||
#
|
||||
# Respawn.DynamicEscortNPC
|
||||
# Description: Enable dynamic respawn behavior for escort quest NPCs.
|
||||
# When enabled, escort NPCs in spawn groups flagged as ESCORTQUESTNPC
|
||||
# will use special respawn handling.
|
||||
# Default: 0 - (Disabled)
|
||||
#
|
||||
|
||||
Respawn.DynamicEscortNPC = 0
|
||||
|
||||
#
|
||||
# Respawn.ForceCompatibilityMode
|
||||
# Description: Force all spawns to use legacy (compatibility mode) respawn behavior,
|
||||
# regardless of their spawn group flags. When enabled, creatures and
|
||||
# gameobjects respawn in-place as they always have in AzerothCore.
|
||||
# Set to 1 to force legacy behavior for all spawns.
|
||||
# Default: 0 - (Disabled, spawn groups control respawn mode)
|
||||
#
|
||||
|
||||
Respawn.ForceCompatibilityMode = 0
|
||||
|
||||
#
|
||||
###################################################################################################
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ void WorldDatabaseConnection::DoPrepareStatements()
|
|||
// 0: uint8
|
||||
PrepareStatement(WORLD_SEL_REQ_XP, "SELECT Experience FROM player_xp_for_level WHERE Level = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(WORLD_UPD_VERSION, "UPDATE version SET core_version = ?, core_revision = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(WORLD_DEL_SPAWNGROUP_MEMBER, "DELETE FROM spawn_group WHERE spawnType = ? AND spawnId = ?", CONNECTION_ASYNC);
|
||||
}
|
||||
|
||||
WorldDatabaseConnection::WorldDatabaseConnection(MySQLConnectionInfo& connInfo) : MySQLConnection(connInfo)
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ enum WorldDatabaseStatements : uint32
|
|||
WORLD_SEL_REQ_XP,
|
||||
WORLD_INS_GAMEOBJECT_ADDON,
|
||||
WORLD_UPD_VERSION,
|
||||
WORLD_DEL_SPAWNGROUP_MEMBER,
|
||||
|
||||
MAX_WORLDDATABASE_STATEMENTS
|
||||
};
|
||||
|
|
|
|||
|
|
@ -118,6 +118,9 @@ public:
|
|||
// Called in Creature::Update when deathstate = DEAD. Inherited classes may maniuplate the ability to respawn based on scripted events.
|
||||
virtual bool CanRespawn() { return true; }
|
||||
|
||||
// Whether this creature is an escort NPC (override in escort AI)
|
||||
virtual bool IsEscortNPC(bool /*onlyIfActive*/ = true) const { return false; }
|
||||
|
||||
// Called for reaction at stopping attack at no attackers or targets
|
||||
virtual void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER);
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ public:
|
|||
|
||||
void JustRespawned() override;
|
||||
|
||||
bool IsEscortNPC(bool /*onlyIfActive*/ = true) const override { return true; }
|
||||
|
||||
void ReturnToLastPoint();
|
||||
|
||||
void EnterEvadeMode(EvadeReason /*why*/ = EVADE_REASON_OTHER) override;
|
||||
|
|
|
|||
|
|
@ -2968,6 +2968,27 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
|
|||
}
|
||||
break;
|
||||
}
|
||||
case SMART_ACTION_SPAWN_SPAWNGROUP:
|
||||
{
|
||||
WorldObject* obj = GetBaseObject();
|
||||
if (!obj)
|
||||
break;
|
||||
|
||||
obj->GetMap()->SpawnGroupSpawn(e.action.groupSpawn.groupId,
|
||||
e.action.groupSpawn.ignoreRespawn != 0,
|
||||
e.action.groupSpawn.force != 0);
|
||||
break;
|
||||
}
|
||||
case SMART_ACTION_DESPAWN_SPAWNGROUP:
|
||||
{
|
||||
WorldObject* obj = GetBaseObject();
|
||||
if (!obj)
|
||||
break;
|
||||
|
||||
obj->GetMap()->SpawnGroupDespawn(e.action.groupSpawn.groupId,
|
||||
e.action.groupSpawn.ignoreRespawn != 0); // reuse ignoreRespawn as deleteRespawnTimes
|
||||
break;
|
||||
}
|
||||
case SMART_ACTION_SET_GUID:
|
||||
{
|
||||
for (WorldObject* target : targets)
|
||||
|
|
|
|||
|
|
@ -846,8 +846,8 @@ bool SmartAIMgr::CheckUnusedActionParams(SmartScriptHolder const& e)
|
|||
case SMART_ACTION_PLAY_ANIMKIT: return sizeof(SmartAction::raw);
|
||||
case SMART_ACTION_SCENE_PLAY: return sizeof(SmartAction::raw);
|
||||
case SMART_ACTION_SCENE_CANCEL: return sizeof(SmartAction::raw);
|
||||
// case SMART_ACTION_SPAWN_SPAWNGROUP: return sizeof(SmartAction::groupSpawn);
|
||||
// case SMART_ACTION_DESPAWN_SPAWNGROUP: return sizeof(SmartAction::groupSpawn);
|
||||
case SMART_ACTION_SPAWN_SPAWNGROUP: return sizeof(SmartAction::groupSpawn);
|
||||
case SMART_ACTION_DESPAWN_SPAWNGROUP: return sizeof(SmartAction::groupSpawn);
|
||||
// case SMART_ACTION_RESPAWN_BY_SPAWNID: return sizeof(SmartAction::respawnData);
|
||||
case SMART_ACTION_PLAY_CINEMATIC: return sizeof(SmartAction::cinematic);
|
||||
case SMART_ACTION_SET_MOVEMENT_SPEED: return sizeof(SmartAction::movementSpeed);
|
||||
|
|
@ -1013,12 +1013,21 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e)
|
|||
case SMART_ACTION_SET_CAN_FLY:
|
||||
case SMART_ACTION_REMOVE_AURAS_BY_TYPE:
|
||||
case SMART_ACTION_REMOVE_MOVEMENT:
|
||||
case SMART_ACTION_SPAWN_SPAWNGROUP:
|
||||
case SMART_ACTION_DESPAWN_SPAWNGROUP:
|
||||
case SMART_ACTION_RESPAWN_BY_SPAWNID:
|
||||
LOG_ERROR("sql.sql", "SmartAIMgr: EntryOrGuid {} using event({}) has an action type that is not yet supported on AzerothCore ({}), skipped.",
|
||||
e.entryOrGuid, e.event_id, e.GetActionType());
|
||||
return false;
|
||||
case SMART_ACTION_SPAWN_SPAWNGROUP:
|
||||
case SMART_ACTION_DESPAWN_SPAWNGROUP:
|
||||
{
|
||||
if (!sObjectMgr->GetSpawnGroupData(e.action.groupSpawn.groupId))
|
||||
{
|
||||
LOG_ERROR("sql.sql", "SmartAIMgr: EntryOrGuid {} using event({}) has action type {} with invalid spawn group id {}.",
|
||||
e.entryOrGuid, e.event_id, e.GetActionType(), e.action.groupSpawn.groupId);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -670,8 +670,8 @@ enum SMART_ACTION
|
|||
SMART_ACTION_PLAY_ANIMKIT = 128, // don't use on 3.3.5a
|
||||
SMART_ACTION_SCENE_PLAY = 129, // don't use on 3.3.5a
|
||||
SMART_ACTION_SCENE_CANCEL = 130, // don't use on 3.3.5a
|
||||
SMART_ACTION_SPAWN_SPAWNGROUP = 131, /// @todo: NOT SUPPORTED YET
|
||||
SMART_ACTION_DESPAWN_SPAWNGROUP = 132, /// @todo: NOT SUPPORTED YET
|
||||
SMART_ACTION_SPAWN_SPAWNGROUP = 131, // groupId, ignoreRespawn, force
|
||||
SMART_ACTION_DESPAWN_SPAWNGROUP = 132, // groupId, deleteRespawnTimes
|
||||
SMART_ACTION_RESPAWN_BY_SPAWNID = 133, /// @todo: NOT SUPPORTED YET
|
||||
SMART_ACTION_INVOKER_CAST = 134, // spellID, castFlags, triggerFlags, targetsLimit
|
||||
SMART_ACTION_PLAY_CINEMATIC = 135, // entry
|
||||
|
|
@ -1523,6 +1523,13 @@ struct SmartAction
|
|||
{
|
||||
uint32 group;
|
||||
} gameobjectGroup;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32 groupId;
|
||||
uint32 ignoreRespawn;
|
||||
uint32 force;
|
||||
} groupSpawn;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -420,34 +420,55 @@ void Creature::RemoveCorpse(bool setSpawnTime, bool skipVisibility)
|
|||
if (getDeathState() != DeathState::Corpse)
|
||||
return;
|
||||
|
||||
m_corpseRemoveTime = GameTime::GetGameTime().count();
|
||||
setDeathState(DeathState::Dead);
|
||||
RemoveAllAuras();
|
||||
if (!skipVisibility) // pussywizard
|
||||
DestroyForVisiblePlayers(); // pussywizard: previous UpdateObjectVisibility()
|
||||
loot.clear();
|
||||
uint32 respawnDelay = m_respawnDelay;
|
||||
if (IsAIEnabled)
|
||||
AI()->CorpseRemoved(respawnDelay);
|
||||
|
||||
// Should get removed later, just keep "compatibility" with scripts
|
||||
if (setSpawnTime)
|
||||
if (_respawnCompatibilityMode)
|
||||
{
|
||||
m_respawnTime = GameTime::GetGameTime().count() + respawnDelay;
|
||||
//SaveRespawnTime();
|
||||
m_corpseRemoveTime = GameTime::GetGameTime().count();
|
||||
setDeathState(DeathState::Dead);
|
||||
RemoveAllAuras();
|
||||
if (!skipVisibility) // pussywizard
|
||||
DestroyForVisiblePlayers(); // pussywizard: previous UpdateObjectVisibility()
|
||||
loot.clear();
|
||||
uint32 respawnDelay = m_respawnDelay;
|
||||
if (IsAIEnabled)
|
||||
AI()->CorpseRemoved(respawnDelay);
|
||||
|
||||
// Should get removed later, just keep "compatibility" with scripts
|
||||
if (setSpawnTime)
|
||||
{
|
||||
m_respawnTime = GameTime::GetGameTime().count() + respawnDelay;
|
||||
//SaveRespawnTime();
|
||||
}
|
||||
|
||||
float x, y, z, o;
|
||||
GetRespawnPosition(x, y, z, &o);
|
||||
SetHomePosition(x, y, z, o);
|
||||
SetPosition(x, y, z, o);
|
||||
|
||||
// xinef: relocate notifier
|
||||
m_last_notify_position.Relocate(-5000.0f, -5000.0f, -5000.0f, 0.0f);
|
||||
|
||||
// pussywizard: if corpse was removed during falling then the falling will continue after respawn, so stop falling is such case
|
||||
if (IsFalling())
|
||||
StopMoving();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dynamic spawn mode: save respawn time and remove the object entirely.
|
||||
// A fresh creature will be spawned by ProcessRespawns() when the timer expires.
|
||||
loot.clear();
|
||||
uint32 respawnDelay = m_respawnDelay;
|
||||
if (IsAIEnabled)
|
||||
AI()->CorpseRemoved(respawnDelay);
|
||||
|
||||
float x, y, z, o;
|
||||
GetRespawnPosition(x, y, z, &o);
|
||||
SetHomePosition(x, y, z, o);
|
||||
SetPosition(x, y, z, o);
|
||||
// Always save respawn time in non-compat mode since the creature is being
|
||||
// destroyed — ProcessRespawns() needs the entry to know when to recreate it.
|
||||
// m_respawnTime was already set in setDeathState(JustDied).
|
||||
if (setSpawnTime)
|
||||
m_respawnTime = std::max<time_t>(GameTime::GetGameTime().count() + respawnDelay, m_respawnTime);
|
||||
SaveRespawnTime();
|
||||
|
||||
// xinef: relocate notifier
|
||||
m_last_notify_position.Relocate(-5000.0f, -5000.0f, -5000.0f, 0.0f);
|
||||
|
||||
// pussywizard: if corpse was removed during falling then the falling will continue after respawn, so stop falling is such case
|
||||
if (IsFalling())
|
||||
StopMoving();
|
||||
AddObjectToRemoveList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1696,6 +1717,11 @@ bool Creature::LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool ad
|
|||
m_creatureData = data;
|
||||
m_spawnId = spawnId;
|
||||
|
||||
// Set respawn compatibility mode based on spawn group flags
|
||||
SpawnGroupTemplateData const* groupData = sObjectMgr->GetSpawnGroupData(data->spawnGroupId);
|
||||
_respawnCompatibilityMode = sWorld->getBoolConfig(CONFIG_RESPAWN_FORCE_COMPATIBILITY_MODE)
|
||||
|| !groupData || (groupData->flags & SPAWNGROUP_FLAG_COMPATIBILITY_MODE);
|
||||
|
||||
// Add to world
|
||||
uint32 entry = GetRandomId(data->id1, data->id2, data->id3);
|
||||
|
||||
|
|
@ -2020,74 +2046,90 @@ void Creature::Respawn(bool force)
|
|||
|
||||
if (!linkedRespawntime || (cInfo && cInfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_HARD_RESET)) || force) // Should respawn
|
||||
{
|
||||
RemoveCorpse(false, false);
|
||||
|
||||
if (getDeathState() == DeathState::Dead)
|
||||
if (_respawnCompatibilityMode)
|
||||
{
|
||||
RemoveCorpse(false, false);
|
||||
|
||||
if (getDeathState() == DeathState::Dead)
|
||||
{
|
||||
if (m_spawnId)
|
||||
{
|
||||
GetMap()->RemoveCreatureRespawnTime(m_spawnId);
|
||||
CreatureData const* data = sObjectMgr->GetCreatureData(m_spawnId);
|
||||
// Respawn check if spawn has 2 entries
|
||||
if (data->id2)
|
||||
{
|
||||
uint32 entry = GetRandomId(data->id1, data->id2, data->id3);
|
||||
UpdateEntry(entry, data, true); // Select Random Entry
|
||||
m_defaultMovementType = MovementGeneratorType(data->movementType); // Reload Movement Type
|
||||
LoadEquipment(data->equipmentId); // Reload Equipment
|
||||
AIM_Initialize(); // Reload AI
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_originalEntry != GetEntry())
|
||||
UpdateEntry(m_originalEntry);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DEBUG("entities.unit", "Respawning creature {} (SpawnId: {}, {})", GetName(), GetSpawnId(), GetGUID().ToString());
|
||||
m_respawnTime = 0;
|
||||
ResetPickPocketLootTime();
|
||||
loot.clear();
|
||||
SelectLevel();
|
||||
|
||||
m_respawnedTime = GameTime::GetGameTime().count();
|
||||
setDeathState(DeathState::JustRespawned);
|
||||
|
||||
// MDic - Acidmanifesto: Do not override transform auras
|
||||
if (GetAuraEffectsByType(SPELL_AURA_TRANSFORM).empty())
|
||||
{
|
||||
CreatureModel display(GetNativeDisplayId(), GetNativeObjectScale(), 1.0f);
|
||||
CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelRandomGender(&display, GetCreatureTemplate());
|
||||
if (minfo) // Cancel load if no model defined
|
||||
{
|
||||
SetDisplayId(display.CreatureDisplayID, display.DisplayScale);
|
||||
SetNativeDisplayId(display.CreatureDisplayID);
|
||||
}
|
||||
}
|
||||
|
||||
GetMotionMaster()->InitDefault();
|
||||
|
||||
//Call AI respawn virtual function
|
||||
if (IsAIEnabled)
|
||||
{
|
||||
//reset the AI to be sure no dirty or uninitialized values will be used till next tick
|
||||
AI()->Reset();
|
||||
TriggerJustRespawned = true; //delay event to next tick so all creatures are created on the map before processing
|
||||
}
|
||||
|
||||
uint32 poolid = m_spawnId ? sPoolMgr->IsPartOfAPool<Creature>(m_spawnId) : 0;
|
||||
if (poolid)
|
||||
sPoolMgr->UpdatePool<Creature>(poolid, m_spawnId);
|
||||
|
||||
//Re-initialize reactstate that could be altered by movementgenerators
|
||||
InitializeReactState();
|
||||
|
||||
}
|
||||
m_respawnedTime = GameTime::GetGameTime().count();
|
||||
// xinef: relocate notifier, fixes npc appearing in corpse position after forced respawn (instead of spawn)
|
||||
m_last_notify_position.Relocate(-5000.0f, -5000.0f, -5000.0f, 0.0f);
|
||||
UpdateObjectVisibility(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Non-compat mode: destroy and let ProcessRespawns() recreate
|
||||
if (IsAlive())
|
||||
return;
|
||||
|
||||
if (m_spawnId)
|
||||
{
|
||||
GetMap()->RemoveCreatureRespawnTime(m_spawnId);
|
||||
CreatureData const* data = sObjectMgr->GetCreatureData(m_spawnId);
|
||||
// Respawn check if spawn has 2 entries
|
||||
if (data->id2)
|
||||
{
|
||||
uint32 entry = GetRandomId(data->id1, data->id2, data->id3);
|
||||
UpdateEntry(entry, data, true); // Select Random Entry
|
||||
m_defaultMovementType = MovementGeneratorType(data->movementType); // Reload Movement Type
|
||||
LoadEquipment(data->equipmentId); // Reload Equipment
|
||||
AIM_Initialize(); // Reload AI
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_originalEntry != GetEntry())
|
||||
UpdateEntry(m_originalEntry);
|
||||
}
|
||||
// Set respawn time to now so ProcessRespawns() picks it up
|
||||
time_t now = GameTime::GetGameTime().count();
|
||||
GetMap()->SaveCreatureRespawnTime(m_spawnId, now);
|
||||
}
|
||||
|
||||
LOG_DEBUG("entities.unit", "Respawning creature {} (SpawnId: {}, {})", GetName(), GetSpawnId(), GetGUID().ToString());
|
||||
m_respawnTime = 0;
|
||||
ResetPickPocketLootTime();
|
||||
loot.clear();
|
||||
SelectLevel();
|
||||
|
||||
m_respawnedTime = GameTime::GetGameTime().count();
|
||||
setDeathState(DeathState::JustRespawned);
|
||||
|
||||
// MDic - Acidmanifesto: Do not override transform auras
|
||||
if (GetAuraEffectsByType(SPELL_AURA_TRANSFORM).empty())
|
||||
{
|
||||
CreatureModel display(GetNativeDisplayId(), GetNativeObjectScale(), 1.0f);
|
||||
CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelRandomGender(&display, GetCreatureTemplate());
|
||||
if (minfo) // Cancel load if no model defined
|
||||
{
|
||||
SetDisplayId(display.CreatureDisplayID, display.DisplayScale);
|
||||
SetNativeDisplayId(display.CreatureDisplayID);
|
||||
}
|
||||
}
|
||||
|
||||
GetMotionMaster()->InitDefault();
|
||||
|
||||
//Call AI respawn virtual function
|
||||
if (IsAIEnabled)
|
||||
{
|
||||
//reset the AI to be sure no dirty or uninitialized values will be used till next tick
|
||||
AI()->Reset();
|
||||
TriggerJustRespawned = true; //delay event to next tick so all creatures are created on the map before processing
|
||||
}
|
||||
|
||||
uint32 poolid = m_spawnId ? sPoolMgr->IsPartOfAPool<Creature>(m_spawnId) : 0;
|
||||
if (poolid)
|
||||
sPoolMgr->UpdatePool<Creature>(poolid, m_spawnId);
|
||||
|
||||
//Re-initialize reactstate that could be altered by movementgenerators
|
||||
InitializeReactState();
|
||||
|
||||
AddObjectToRemoveList();
|
||||
}
|
||||
m_respawnedTime = GameTime::GetGameTime().count();
|
||||
// xinef: relocate notifier, fixes npc appearing in corpse position after forced respawn (instead of spawn)
|
||||
m_last_notify_position.Relocate(-5000.0f, -5000.0f, -5000.0f, 0.0f);
|
||||
UpdateObjectVisibility(false);
|
||||
|
||||
}
|
||||
else // the master is dead
|
||||
{
|
||||
|
|
@ -2114,13 +2156,27 @@ void Creature::ForcedDespawn(Milliseconds timeMSToDespawn, Seconds forceRespawnT
|
|||
return;
|
||||
}
|
||||
|
||||
// Override respawn delay BEFORE setDeathState, because setDeathState(JustDied)
|
||||
// computes m_respawnTime = now + m_respawnDelay + m_corpseDelay and immediately
|
||||
// saves it to DB for bosses/elites. We must have the correct delay in place
|
||||
// before that happens.
|
||||
if (forceRespawnTimer > 0s)
|
||||
m_respawnDelay = forceRespawnTimer.count();
|
||||
|
||||
if (IsAlive())
|
||||
setDeathState(DeathState::JustDied, true);
|
||||
|
||||
// Xinef: Set new respawn time, ignore corpse decay time...
|
||||
// After setDeathState, m_respawnTime includes m_corpseDelay which we don't
|
||||
// want for a forced respawn. Override it so RemoveCorpse's max() picks ours.
|
||||
if (forceRespawnTimer > 0s)
|
||||
m_respawnTime = GameTime::GetGameTime().count() + forceRespawnTimer.count();
|
||||
|
||||
RemoveCorpse(true);
|
||||
|
||||
if (forceRespawnTimer > 0s)
|
||||
// In compat mode the creature stays in the world as a dead body and needs
|
||||
// an event-based kick to call Respawn() after the timer expires.
|
||||
if (forceRespawnTimer > 0s && _respawnCompatibilityMode)
|
||||
if (GetMap())
|
||||
GetMap()->ScheduleCreatureRespawn(GetGUID(), forceRespawnTimer);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -221,6 +221,7 @@ public:
|
|||
|
||||
bool LoadFromDB(ObjectGuid::LowType guid, Map* map, bool allowDuplicate = false) { return LoadCreatureFromDB(guid, map, false, allowDuplicate); }
|
||||
bool LoadCreatureFromDB(ObjectGuid::LowType guid, Map* map, bool addToMap = true, bool allowDuplicate = false);
|
||||
[[nodiscard]] bool IsRespawnCompatibilityMode() const { return _respawnCompatibilityMode; }
|
||||
void SaveToDB();
|
||||
|
||||
virtual void SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask); // overriden in Pet
|
||||
|
|
@ -465,6 +466,8 @@ protected:
|
|||
ObjectGuid m_lootRecipient;
|
||||
ObjectGuid::LowType m_lootRecipientGroup;
|
||||
|
||||
bool _respawnCompatibilityMode{true};
|
||||
|
||||
/// Timers
|
||||
time_t m_corpseRemoveTime; // (secs) timer for death or corpse disappearance
|
||||
time_t m_respawnTime; // (secs) time of next respawn
|
||||
|
|
|
|||
|
|
@ -1121,6 +1121,11 @@ bool GameObject::LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, boo
|
|||
m_goData = data;
|
||||
m_spawnId = spawnId;
|
||||
|
||||
// Set respawn compatibility mode based on spawn group flags
|
||||
SpawnGroupTemplateData const* groupData = sObjectMgr->GetSpawnGroupData(data->spawnGroupId);
|
||||
_respawnCompatibilityMode = sWorld->getBoolConfig(CONFIG_RESPAWN_FORCE_COMPATIBILITY_MODE)
|
||||
|| !groupData || (groupData->flags & SPAWNGROUP_FLAG_COMPATIBILITY_MODE);
|
||||
|
||||
if (!Create(map->GenerateLowGuid<HighGuid::GameObject>(), entry, map, phaseMask, x, y, z, ang, data->rotation, animprogress, go_state, artKit))
|
||||
return false;
|
||||
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ public:
|
|||
void SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask, bool saveAddon = false);
|
||||
virtual bool LoadFromDB(ObjectGuid::LowType guid, Map* map) { return LoadGameObjectFromDB(guid, map, false); }
|
||||
virtual bool LoadGameObjectFromDB(ObjectGuid::LowType guid, Map* map, bool addToMap = true);
|
||||
[[nodiscard]] bool IsRespawnCompatibilityMode() const { return _respawnCompatibilityMode; }
|
||||
void DeleteFromDB();
|
||||
|
||||
void SetOwnerGUID(ObjectGuid owner)
|
||||
|
|
@ -369,6 +370,7 @@ protected:
|
|||
bool AIM_Initialize();
|
||||
GameObjectModel* CreateModel();
|
||||
void UpdateModel(); // updates model in case displayId were changed
|
||||
bool _respawnCompatibilityMode{true};
|
||||
uint32 m_spellId;
|
||||
time_t m_respawnTime; // (secs) time of next respawn (or despawn if GO have owner()),
|
||||
uint32 m_respawnDelayTime; // (secs) if 0 then current GO state no dependent from timer
|
||||
|
|
|
|||
|
|
@ -311,8 +311,9 @@ ObjectMgr::ObjectMgr():
|
|||
_playerClassInfo[i] = nullptr;
|
||||
}
|
||||
|
||||
// Initialize default spawn group
|
||||
_spawnGroupDataStore[0] = {0, "Default Group", 0, SpawnGroupFlags(SPAWNGROUP_FLAG_SYSTEM)};
|
||||
// Initialize default spawn groups
|
||||
_spawnGroupDataStore[0] = {0, "Default Group", SPAWNGROUP_MAP_UNSET, SpawnGroupFlags(SPAWNGROUP_FLAG_SYSTEM)};
|
||||
_spawnGroupDataStore[1] = {1, "Legacy Group", SPAWNGROUP_MAP_UNSET, SpawnGroupFlags(SPAWNGROUP_FLAG_SYSTEM | SPAWNGROUP_FLAG_COMPATIBILITY_MODE)};
|
||||
}
|
||||
|
||||
ObjectMgr::~ObjectMgr()
|
||||
|
|
@ -2380,6 +2381,7 @@ void ObjectMgr::LoadCreatures()
|
|||
continue;
|
||||
}
|
||||
CreatureData& data = _creatureDataStore[spawnId];
|
||||
data.spawnId = spawnId;
|
||||
data.id1 = id1;
|
||||
data.id2 = id2;
|
||||
data.id3 = id3;
|
||||
|
|
@ -2748,6 +2750,7 @@ ObjectGuid::LowType ObjectMgr::AddGOData(uint32 entry, uint32 mapId, float x, fl
|
|||
ObjectGuid::LowType spawnId = GenerateGameObjectSpawnId();
|
||||
|
||||
GameObjectData& data = NewGOData(spawnId);
|
||||
data.spawnId = spawnId;
|
||||
data.id = entry;
|
||||
data.mapid = mapId;
|
||||
data.posX = x;
|
||||
|
|
@ -2801,6 +2804,7 @@ ObjectGuid::LowType ObjectMgr::AddCreData(uint32 entry, uint32 mapId, float x, f
|
|||
|
||||
ObjectGuid::LowType spawnId = GenerateCreatureSpawnId();
|
||||
CreatureData& data = NewOrExistCreatureData(spawnId);
|
||||
data.spawnId = spawnId;
|
||||
data.spawnMask = spawnId;
|
||||
data.id1 = entry;
|
||||
data.id2 = 0;
|
||||
|
|
@ -2909,6 +2913,7 @@ void ObjectMgr::LoadGameobjects()
|
|||
|
||||
GameObjectData& data = _gameObjectDataStore[guid];
|
||||
|
||||
data.spawnId = guid;
|
||||
data.id = entry;
|
||||
data.mapid = fields[2].Get<uint16>();
|
||||
data.posX = fields[3].Get<float>();
|
||||
|
|
@ -8703,6 +8708,172 @@ SpawnData const* ObjectMgr::GetSpawnData(SpawnObjectType type, ObjectGuid::LowTy
|
|||
}
|
||||
}
|
||||
|
||||
void ObjectMgr::LoadSpawnGroupTemplates()
|
||||
{
|
||||
uint32 oldMSTime = getMSTime();
|
||||
|
||||
_spawnGroupDataStore.clear();
|
||||
|
||||
// 0 1 2
|
||||
QueryResult result = WorldDatabase.Query("SELECT groupId, groupName, groupFlags FROM spawn_group_template");
|
||||
|
||||
if (result)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 groupId = fields[0].Get<uint32>();
|
||||
SpawnGroupTemplateData& group = _spawnGroupDataStore[groupId];
|
||||
group.groupId = groupId;
|
||||
group.name = fields[1].Get<std::string>();
|
||||
group.mapId = SPAWNGROUP_MAP_UNSET;
|
||||
uint32 flags = fields[2].Get<uint32>();
|
||||
if (flags & ~uint32(SPAWNGROUP_FLAG_ALL))
|
||||
{
|
||||
flags &= uint32(SPAWNGROUP_FLAG_ALL);
|
||||
LOG_ERROR("sql.sql", "Invalid spawn group flag {} on group ID {} ({}), reduced to valid flags {}.",
|
||||
fields[2].Get<uint32>(), groupId, group.name, flags);
|
||||
}
|
||||
if ((flags & SPAWNGROUP_FLAG_SYSTEM) && (flags & SPAWNGROUP_FLAG_MANUAL_SPAWN))
|
||||
{
|
||||
flags &= ~SPAWNGROUP_FLAG_MANUAL_SPAWN;
|
||||
LOG_ERROR("sql.sql", "System spawn group {} ({}) has invalid manual spawn flag. Ignored.", groupId, group.name);
|
||||
}
|
||||
group.flags = SpawnGroupFlags(flags);
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
if (_spawnGroupDataStore.find(0) == _spawnGroupDataStore.end())
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Default spawn group (index 0) is missing from DB! Manually inserted.");
|
||||
SpawnGroupTemplateData& data = _spawnGroupDataStore[0];
|
||||
data.groupId = 0;
|
||||
data.name = "Default Group";
|
||||
data.mapId = SPAWNGROUP_MAP_UNSET;
|
||||
data.flags = SpawnGroupFlags(SPAWNGROUP_FLAG_SYSTEM);
|
||||
}
|
||||
if (_spawnGroupDataStore.find(1) == _spawnGroupDataStore.end())
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Default legacy spawn group (index 1) is missing from DB! Manually inserted.");
|
||||
SpawnGroupTemplateData& data = _spawnGroupDataStore[1];
|
||||
data.groupId = 1;
|
||||
data.name = "Legacy Group";
|
||||
data.mapId = SPAWNGROUP_MAP_UNSET;
|
||||
data.flags = SpawnGroupFlags(SPAWNGROUP_FLAG_SYSTEM | SPAWNGROUP_FLAG_COMPATIBILITY_MODE);
|
||||
}
|
||||
|
||||
LOG_INFO("server.loading", ">> Loaded {} spawn group templates in {} ms", _spawnGroupDataStore.size(), GetMSTimeDiffToNow(oldMSTime));
|
||||
LOG_INFO("server.loading", " ");
|
||||
}
|
||||
|
||||
void ObjectMgr::LoadSpawnGroups()
|
||||
{
|
||||
uint32 oldMSTime = getMSTime();
|
||||
|
||||
// Reset prior state for hot-reload support
|
||||
_spawnGroupMapStore.clear();
|
||||
for (auto& [id, data] : _creatureDataStore)
|
||||
data.spawnGroupId = 0;
|
||||
for (auto& [id, data] : _gameObjectDataStore)
|
||||
data.spawnGroupId = 0;
|
||||
|
||||
// 0 1 2
|
||||
QueryResult result = WorldDatabase.Query("SELECT groupId, spawnType, spawnId FROM spawn_group");
|
||||
|
||||
if (!result)
|
||||
{
|
||||
LOG_INFO("server.loading", ">> Loaded 0 spawn group members. DB table `spawn_group` is empty.");
|
||||
LOG_INFO("server.loading", " ");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 numMembers = 0;
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 groupId = fields[0].Get<uint32>();
|
||||
uint32 type = fields[1].Get<uint8>();
|
||||
if (type >= SPAWN_TYPE_MAX)
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Spawn data with invalid type {} listed for spawn group {}. Skipped.", type, groupId);
|
||||
continue;
|
||||
}
|
||||
SpawnObjectType spawnType = SpawnObjectType(type);
|
||||
ObjectGuid::LowType spawnId = fields[2].Get<uint32>();
|
||||
|
||||
SpawnData const* data = GetSpawnData(spawnType, spawnId);
|
||||
if (!data)
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Spawn data with ID ({},{}) not found, but is listed as a member of spawn group {}!",
|
||||
uint32(spawnType), spawnId, groupId);
|
||||
continue;
|
||||
}
|
||||
if (data->spawnGroupId)
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Spawn with ID ({},{}) is listed as a member of spawn group {}, but is already a member of spawn group {}. Skipping.",
|
||||
uint32(spawnType), spawnId, groupId, data->spawnGroupId);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = _spawnGroupDataStore.find(groupId);
|
||||
if (it == _spawnGroupDataStore.end())
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Spawn group {} assigned to spawn ID ({},{}), but group is not found!", groupId, uint32(spawnType), spawnId);
|
||||
continue;
|
||||
}
|
||||
|
||||
SpawnGroupTemplateData& groupTemplate = it->second;
|
||||
if (groupTemplate.mapId == SPAWNGROUP_MAP_UNSET)
|
||||
groupTemplate.mapId = data->mapid;
|
||||
else if (groupTemplate.mapId != data->mapid && !(groupTemplate.flags & SPAWNGROUP_FLAG_SYSTEM))
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Spawn group {} has map ID {}, but spawn ({},{}) has map id {} - spawn NOT added to group!",
|
||||
groupId, groupTemplate.mapId, uint32(spawnType), spawnId, data->mapid);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Warn if spawn is also in a pool (non-system groups and pools are mutually exclusive)
|
||||
if (!(groupTemplate.flags & SPAWNGROUP_FLAG_SYSTEM))
|
||||
{
|
||||
uint32 poolId = 0;
|
||||
if (spawnType == SPAWN_TYPE_CREATURE)
|
||||
poolId = sPoolMgr->IsPartOfAPool<Creature>(spawnId);
|
||||
else if (spawnType == SPAWN_TYPE_GAMEOBJECT)
|
||||
poolId = sPoolMgr->IsPartOfAPool<GameObject>(spawnId);
|
||||
|
||||
if (poolId)
|
||||
LOG_WARN("sql.sql", "Spawn ({},{}) is a member of spawn group {} and also part of pool {}. This may cause issues!",
|
||||
uint32(spawnType), spawnId, groupId, poolId);
|
||||
}
|
||||
|
||||
const_cast<SpawnData*>(data)->spawnGroupId = groupId;
|
||||
if (!(groupTemplate.flags & SPAWNGROUP_FLAG_SYSTEM))
|
||||
_spawnGroupMapStore.emplace(groupId, data);
|
||||
++numMembers;
|
||||
} while (result->NextRow());
|
||||
|
||||
LOG_INFO("server.loading", ">> Loaded {} spawn group members in {} ms", numMembers, GetMSTimeDiffToNow(oldMSTime));
|
||||
LOG_INFO("server.loading", " ");
|
||||
}
|
||||
|
||||
void ObjectMgr::OnDeleteSpawnData(SpawnData const* data)
|
||||
{
|
||||
auto templateIt = _spawnGroupDataStore.find(data->spawnGroupId);
|
||||
ASSERT(templateIt != _spawnGroupDataStore.end(), "Spawn data is being deleted and has invalid spawn group index {}!", data->spawnGroupId);
|
||||
if (templateIt->second.flags & SPAWNGROUP_FLAG_SYSTEM)
|
||||
return;
|
||||
|
||||
auto pair = _spawnGroupMapStore.equal_range(data->spawnGroupId);
|
||||
for (auto it = pair.first; it != pair.second; ++it)
|
||||
{
|
||||
if (it->second != data)
|
||||
continue;
|
||||
_spawnGroupMapStore.erase(it);
|
||||
return;
|
||||
}
|
||||
ASSERT(false, "Spawn data being removed is member of spawn group {}, but not found in lookup table!", data->spawnGroupId);
|
||||
}
|
||||
|
||||
void ObjectMgr::LoadQuestRelationsHelper(QuestRelations& map, std::string const& table, bool starter, bool go)
|
||||
{
|
||||
uint32 oldMSTime = getMSTime();
|
||||
|
|
|
|||
|
|
@ -507,6 +507,7 @@ typedef std::map<ObjectGuid, ObjectGuid> LinkedRespawnContainer;
|
|||
typedef std::unordered_map<ObjectGuid::LowType, CreatureData> CreatureDataContainer;
|
||||
typedef std::unordered_map<ObjectGuid::LowType, GameObjectData> GameObjectDataContainer;
|
||||
typedef std::unordered_map<uint32, SpawnGroupTemplateData> SpawnGroupDataContainer;
|
||||
typedef std::multimap<uint32, SpawnData const*> SpawnGroupLinkContainer;
|
||||
typedef std::map<TempSummonGroupKey, std::vector<TempSummonData> > TempSummonDataContainer;
|
||||
typedef std::map<TempSummonGroupKey, std::vector<GameObjectSummonData> > GameObjectSummonDataContainer;
|
||||
typedef std::unordered_map<uint32, CreatureLocale> CreatureLocaleContainer;
|
||||
|
|
@ -1040,6 +1041,8 @@ public:
|
|||
void LoadCreatureQuestItems();
|
||||
void LoadTempSummons();
|
||||
void LoadGameObjectSummons();
|
||||
void LoadSpawnGroupTemplates();
|
||||
void LoadSpawnGroups();
|
||||
void LoadCreatures();
|
||||
void LoadCreatureSparring();
|
||||
void LoadLinkedRespawn();
|
||||
|
|
@ -1277,6 +1280,13 @@ public:
|
|||
auto itr = _spawnGroupDataStore.find(groupId);
|
||||
return itr != _spawnGroupDataStore.end() ? &itr->second : nullptr;
|
||||
}
|
||||
[[nodiscard]] SpawnGroupTemplateData const* GetDefaultSpawnGroup() const { return &_spawnGroupDataStore.at(0); }
|
||||
[[nodiscard]] SpawnGroupTemplateData const* GetLegacySpawnGroup() const { return &_spawnGroupDataStore.at(1); }
|
||||
std::pair<SpawnGroupLinkContainer::const_iterator, SpawnGroupLinkContainer::const_iterator> GetSpawnDataForGroup(uint32 groupId) const
|
||||
{
|
||||
return _spawnGroupMapStore.equal_range(groupId);
|
||||
}
|
||||
void OnDeleteSpawnData(SpawnData const* data);
|
||||
|
||||
[[nodiscard]] CreatureLocale const* GetCreatureLocale(uint32 entry) const
|
||||
{
|
||||
|
|
@ -1653,6 +1663,7 @@ private:
|
|||
CreatureLocaleContainer _creatureLocaleStore;
|
||||
GameObjectDataContainer _gameObjectDataStore;
|
||||
SpawnGroupDataContainer _spawnGroupDataStore;
|
||||
SpawnGroupLinkContainer _spawnGroupMapStore;
|
||||
GameObjectLocaleContainer _gameObjectLocaleStore;
|
||||
GameObjectTemplateContainer _gameObjectTemplateStore;
|
||||
GameObjectTemplateAddonContainer _gameObjectTemplateAddonStore;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include "DynamicObject.h"
|
||||
#include "GameObject.h"
|
||||
#include "GridNotifiers.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Transport.h"
|
||||
|
||||
template <class T>
|
||||
|
|
@ -38,6 +39,11 @@ void GridObjectLoader::LoadCreatures(CellGuidSet const& guid_set, Map* map)
|
|||
{
|
||||
for (ObjectGuid::LowType const& guid : guid_set)
|
||||
{
|
||||
// Skip spawns whose spawn group is not active on this map
|
||||
CreatureData const* cData = sObjectMgr->GetCreatureData(guid);
|
||||
if (cData && !map->IsSpawnGroupActive(cData->spawnGroupId))
|
||||
continue;
|
||||
|
||||
Creature* obj = new Creature();
|
||||
if (!obj->LoadFromDB(guid, map))
|
||||
{
|
||||
|
|
@ -65,6 +71,10 @@ void GridObjectLoader::LoadGameObjects(CellGuidSet const& guid_set, Map* map)
|
|||
{
|
||||
GameObjectData const* data = sObjectMgr->GetGameObjectData(guid);
|
||||
|
||||
// Skip spawns whose spawn group is not active on this map
|
||||
if (data && !map->IsSpawnGroupActive(data->spawnGroupId))
|
||||
continue;
|
||||
|
||||
if (data && sObjectMgr->IsGameObjectStaticTransport(data->id))
|
||||
{
|
||||
StaticTransport* transport = new StaticTransport();
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
#include "ObjectAccessor.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Pet.h"
|
||||
#include "PoolMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "Transport.h"
|
||||
#include "VMapFactory.h"
|
||||
|
|
@ -457,6 +458,18 @@ void Map::Update(const uint32 t_diff, const uint32 s_diff, bool /*thread*/)
|
|||
return;
|
||||
}
|
||||
|
||||
/// Process any due respawns (non-compatibility mode spawns)
|
||||
if (!sWorld->getBoolConfig(CONFIG_RESPAWN_FORCE_COMPATIBILITY_MODE))
|
||||
{
|
||||
if (_respawnCheckTimer <= t_diff)
|
||||
{
|
||||
ProcessRespawns();
|
||||
_respawnCheckTimer = 5000; // Check every 5 seconds
|
||||
}
|
||||
else
|
||||
_respawnCheckTimer -= t_diff;
|
||||
}
|
||||
|
||||
_updatableObjectListRecheckTimer.Update(t_diff);
|
||||
resetMarkedCells();
|
||||
|
||||
|
|
@ -2382,7 +2395,13 @@ void Map::SaveCreatureRespawnTime(ObjectGuid::LowType spawnId, time_t& respawnTi
|
|||
if (GetInstanceResetPeriod() > 0 && respawnTime - now + 5 >= GetInstanceResetPeriod())
|
||||
respawnTime = now + YEAR;
|
||||
|
||||
// Remove old queue entry if updating an existing respawn time
|
||||
auto itr = _creatureRespawnTimes.find(spawnId);
|
||||
if (itr != _creatureRespawnTimes.end())
|
||||
_respawnQueue.erase({itr->second, SPAWN_TYPE_CREATURE, spawnId});
|
||||
|
||||
_creatureRespawnTimes[spawnId] = respawnTime;
|
||||
_respawnQueue.insert({respawnTime, SPAWN_TYPE_CREATURE, spawnId});
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CREATURE_RESPAWN);
|
||||
stmt->SetData(0, spawnId);
|
||||
|
|
@ -2394,7 +2413,12 @@ void Map::SaveCreatureRespawnTime(ObjectGuid::LowType spawnId, time_t& respawnTi
|
|||
|
||||
void Map::RemoveCreatureRespawnTime(ObjectGuid::LowType spawnId)
|
||||
{
|
||||
_creatureRespawnTimes.erase(spawnId);
|
||||
auto itr = _creatureRespawnTimes.find(spawnId);
|
||||
if (itr != _creatureRespawnTimes.end())
|
||||
{
|
||||
_respawnQueue.erase({itr->second, SPAWN_TYPE_CREATURE, spawnId});
|
||||
_creatureRespawnTimes.erase(itr);
|
||||
}
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CREATURE_RESPAWN);
|
||||
stmt->SetData(0, spawnId);
|
||||
|
|
@ -2416,7 +2440,13 @@ void Map::SaveGORespawnTime(ObjectGuid::LowType spawnId, time_t& respawnTime)
|
|||
if (GetInstanceResetPeriod() > 0 && respawnTime - now + 5 >= GetInstanceResetPeriod())
|
||||
respawnTime = now + YEAR;
|
||||
|
||||
// Remove old queue entry if updating an existing respawn time
|
||||
auto itr = _goRespawnTimes.find(spawnId);
|
||||
if (itr != _goRespawnTimes.end())
|
||||
_respawnQueue.erase({itr->second, SPAWN_TYPE_GAMEOBJECT, spawnId});
|
||||
|
||||
_goRespawnTimes[spawnId] = respawnTime;
|
||||
_respawnQueue.insert({respawnTime, SPAWN_TYPE_GAMEOBJECT, spawnId});
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_GO_RESPAWN);
|
||||
stmt->SetData(0, spawnId);
|
||||
|
|
@ -2428,7 +2458,12 @@ void Map::SaveGORespawnTime(ObjectGuid::LowType spawnId, time_t& respawnTime)
|
|||
|
||||
void Map::RemoveGORespawnTime(ObjectGuid::LowType spawnId)
|
||||
{
|
||||
_goRespawnTimes.erase(spawnId);
|
||||
auto itr = _goRespawnTimes.find(spawnId);
|
||||
if (itr != _goRespawnTimes.end())
|
||||
{
|
||||
_respawnQueue.erase({itr->second, SPAWN_TYPE_GAMEOBJECT, spawnId});
|
||||
_goRespawnTimes.erase(itr);
|
||||
}
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GO_RESPAWN);
|
||||
stmt->SetData(0, spawnId);
|
||||
|
|
@ -2448,9 +2483,10 @@ void Map::LoadRespawnTimes()
|
|||
{
|
||||
Field* fields = result->Fetch();
|
||||
ObjectGuid::LowType lowguid = fields[0].Get<uint32>();
|
||||
uint32 respawnTime = fields[1].Get<uint32>();
|
||||
time_t respawnTime = time_t(fields[1].Get<uint32>());
|
||||
|
||||
_creatureRespawnTimes[lowguid] = time_t(respawnTime);
|
||||
_creatureRespawnTimes[lowguid] = respawnTime;
|
||||
_respawnQueue.insert({respawnTime, SPAWN_TYPE_CREATURE, lowguid});
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
|
|
@ -2463,9 +2499,10 @@ void Map::LoadRespawnTimes()
|
|||
{
|
||||
Field* fields = result->Fetch();
|
||||
ObjectGuid::LowType lowguid = fields[0].Get<uint32>();
|
||||
uint32 respawnTime = fields[1].Get<uint32>();
|
||||
time_t respawnTime = time_t(fields[1].Get<uint32>());
|
||||
|
||||
_goRespawnTimes[lowguid] = time_t(respawnTime);
|
||||
_goRespawnTimes[lowguid] = respawnTime;
|
||||
_respawnQueue.insert({respawnTime, SPAWN_TYPE_GAMEOBJECT, lowguid});
|
||||
} while (result->NextRow());
|
||||
}
|
||||
}
|
||||
|
|
@ -2474,6 +2511,7 @@ void Map::DeleteRespawnTimes()
|
|||
{
|
||||
_creatureRespawnTimes.clear();
|
||||
_goRespawnTimes.clear();
|
||||
_respawnQueue.clear();
|
||||
|
||||
DeleteRespawnTimesInDB(GetId(), GetInstanceId());
|
||||
}
|
||||
|
|
@ -2491,6 +2529,309 @@ void Map::DeleteRespawnTimesInDB(uint16 mapId, uint32 instanceId)
|
|||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
|
||||
bool Map::IsSpawnGroupActive(uint32 groupId) const
|
||||
{
|
||||
SpawnGroupTemplateData const* data = sObjectMgr->GetSpawnGroupData(groupId);
|
||||
if (!data)
|
||||
return false;
|
||||
|
||||
// System groups are always active
|
||||
if (data->flags & SPAWNGROUP_FLAG_SYSTEM)
|
||||
return true;
|
||||
|
||||
// Per-map toggled state: XOR with default.
|
||||
// MANUAL_SPAWN groups default to inactive; toggling makes them active.
|
||||
// Non-MANUAL groups default to active; toggling makes them inactive.
|
||||
bool toggled = _toggledSpawnGroupIds.count(groupId) != 0;
|
||||
bool defaultActive = !(data->flags & SPAWNGROUP_FLAG_MANUAL_SPAWN);
|
||||
return toggled != defaultActive; // XOR: toggled flips the default
|
||||
}
|
||||
|
||||
bool Map::SpawnGroupSpawn(uint32 groupId, bool ignoreRespawn /*= false*/, bool force /*= false*/)
|
||||
{
|
||||
SpawnGroupTemplateData const* groupData = sObjectMgr->GetSpawnGroupData(groupId);
|
||||
if (!groupData || (groupData->flags & SPAWNGROUP_FLAG_SYSTEM))
|
||||
{
|
||||
LOG_ERROR("maps", "Tried to spawn non-existing (or system) spawn group {}. Blocked.", groupId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (groupData->mapId != SPAWNGROUP_MAP_UNSET && groupData->mapId != GetId())
|
||||
{
|
||||
LOG_ERROR("maps", "Tried to spawn group {} on map {}, but group has map {}. Blocked.",
|
||||
groupId, GetId(), groupData->mapId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark group as active on this map (toggle to active state)
|
||||
if (groupData->flags & SPAWNGROUP_FLAG_MANUAL_SPAWN)
|
||||
_toggledSpawnGroupIds.insert(groupId);
|
||||
else
|
||||
_toggledSpawnGroupIds.erase(groupId);
|
||||
|
||||
auto range = sObjectMgr->GetSpawnDataForGroup(groupId);
|
||||
for (auto it = range.first; it != range.second; ++it)
|
||||
{
|
||||
SpawnData const* data = it->second;
|
||||
ObjectGuid::LowType spawnId = data->spawnId;
|
||||
|
||||
// Check if there's already an alive instance
|
||||
if (!force)
|
||||
{
|
||||
if (data->type == SPAWN_TYPE_CREATURE)
|
||||
{
|
||||
auto bounds = _creatureBySpawnIdStore.equal_range(spawnId);
|
||||
bool alive = false;
|
||||
for (auto itr = bounds.first; itr != bounds.second; ++itr)
|
||||
if (itr->second->IsAlive())
|
||||
alive = true;
|
||||
if (alive)
|
||||
continue;
|
||||
}
|
||||
else if (data->type == SPAWN_TYPE_GAMEOBJECT)
|
||||
{
|
||||
if (_gameobjectBySpawnIdStore.count(spawnId))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
time_t respawnTime = GetRespawnTime(data->type, spawnId);
|
||||
if (respawnTime && respawnTime > GameTime::GetGameTime().count())
|
||||
{
|
||||
if (!force && !ignoreRespawn)
|
||||
continue;
|
||||
RemoveRespawnTime(data->type, spawnId);
|
||||
}
|
||||
|
||||
// Don't spawn if grid isn't loaded (will be handled in grid loader)
|
||||
if (!IsGridLoaded(data->posX, data->posY))
|
||||
continue;
|
||||
|
||||
switch (data->type)
|
||||
{
|
||||
case SPAWN_TYPE_CREATURE:
|
||||
{
|
||||
Creature* creature = new Creature();
|
||||
if (!creature->LoadCreatureFromDB(spawnId, this, true, true))
|
||||
delete creature;
|
||||
break;
|
||||
}
|
||||
case SPAWN_TYPE_GAMEOBJECT:
|
||||
{
|
||||
GameObject* gameobject = new GameObject();
|
||||
if (!gameobject->LoadGameObjectFromDB(spawnId, this, true))
|
||||
delete gameobject;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Map::SpawnGroupDespawn(uint32 groupId, bool deleteRespawnTimes /*= false*/)
|
||||
{
|
||||
SpawnGroupTemplateData const* groupData = sObjectMgr->GetSpawnGroupData(groupId);
|
||||
if (!groupData || (groupData->flags & SPAWNGROUP_FLAG_SYSTEM))
|
||||
{
|
||||
LOG_ERROR("maps", "Tried to despawn non-existing (or system) spawn group {}. Blocked.", groupId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (groupData->mapId != SPAWNGROUP_MAP_UNSET && groupData->mapId != GetId())
|
||||
{
|
||||
LOG_ERROR("maps", "Tried to despawn group {} on map {}, but group has map {}. Blocked.",
|
||||
groupId, GetId(), groupData->mapId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark group as inactive on this map (toggle to inactive state)
|
||||
if (groupData->flags & SPAWNGROUP_FLAG_MANUAL_SPAWN)
|
||||
_toggledSpawnGroupIds.erase(groupId);
|
||||
else
|
||||
_toggledSpawnGroupIds.insert(groupId);
|
||||
|
||||
std::vector<WorldObject*> toUnload;
|
||||
auto range = sObjectMgr->GetSpawnDataForGroup(groupId);
|
||||
for (auto it = range.first; it != range.second; ++it)
|
||||
{
|
||||
SpawnData const* data = it->second;
|
||||
ObjectGuid::LowType spawnId = data->spawnId;
|
||||
|
||||
if (deleteRespawnTimes)
|
||||
RemoveRespawnTime(data->type, spawnId);
|
||||
|
||||
switch (data->type)
|
||||
{
|
||||
case SPAWN_TYPE_CREATURE:
|
||||
{
|
||||
auto bounds = _creatureBySpawnIdStore.equal_range(spawnId);
|
||||
for (auto itr = bounds.first; itr != bounds.second; ++itr)
|
||||
toUnload.emplace_back(itr->second);
|
||||
break;
|
||||
}
|
||||
case SPAWN_TYPE_GAMEOBJECT:
|
||||
{
|
||||
auto bounds = _gameobjectBySpawnIdStore.equal_range(spawnId);
|
||||
for (auto itr = bounds.first; itr != bounds.second; ++itr)
|
||||
toUnload.emplace_back(itr->second);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (WorldObject* obj : toUnload)
|
||||
obj->AddObjectToRemoveList();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Map::ProcessRespawns()
|
||||
{
|
||||
time_t now = GameTime::GetGameTime().count();
|
||||
|
||||
// Process due respawns from the time-ordered queue.
|
||||
// Entries are sorted by respawnTime — once we hit a future time, we're done.
|
||||
while (!_respawnQueue.empty())
|
||||
{
|
||||
auto it = _respawnQueue.begin();
|
||||
if (it->respawnTime > now)
|
||||
break; // nothing else is due this tick
|
||||
|
||||
SpawnObjectType type = it->type;
|
||||
ObjectGuid::LowType spawnId = it->spawnId;
|
||||
|
||||
// Remove from queue first — handlers below call Remove*RespawnTime()
|
||||
// which also erases from queue, so we must pop before processing.
|
||||
_respawnQueue.erase(it);
|
||||
|
||||
if (type == SPAWN_TYPE_CREATURE)
|
||||
ProcessCreatureRespawn(spawnId);
|
||||
else if (type == SPAWN_TYPE_GAMEOBJECT)
|
||||
ProcessGameObjectRespawn(spawnId);
|
||||
}
|
||||
}
|
||||
|
||||
void Map::ProcessCreatureRespawn(ObjectGuid::LowType spawnId)
|
||||
{
|
||||
// Pool members are handled entirely by PoolMgr
|
||||
if (uint32 poolId = sPoolMgr->IsPartOfAPool<Creature>(spawnId))
|
||||
{
|
||||
sPoolMgr->UpdatePool<Creature>(poolId, spawnId);
|
||||
RemoveCreatureRespawnTime(spawnId);
|
||||
return;
|
||||
}
|
||||
|
||||
CreatureData const* data = sObjectMgr->GetCreatureData(spawnId);
|
||||
if (!data)
|
||||
{
|
||||
RemoveCreatureRespawnTime(spawnId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Compat-mode creatures handle their own respawn in-place — don't interfere.
|
||||
// Clean up the stale respawn time entry since the legacy system manages these.
|
||||
SpawnGroupTemplateData const* groupData = sObjectMgr->GetSpawnGroupData(data->spawnGroupId);
|
||||
if (!groupData || (groupData->flags & SPAWNGROUP_FLAG_COMPATIBILITY_MODE))
|
||||
{
|
||||
RemoveCreatureRespawnTime(spawnId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't respawn if the spawn group is not active
|
||||
if (!IsSpawnGroupActive(data->spawnGroupId))
|
||||
{
|
||||
// Re-queue — will be checked again next ProcessRespawns() tick
|
||||
_respawnQueue.insert({GameTime::GetGameTime().count() + 5, SPAWN_TYPE_CREATURE, spawnId});
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if grid isn't loaded (will be handled in grid loader)
|
||||
if (!IsGridLoaded(data->posX, data->posY))
|
||||
{
|
||||
_respawnQueue.insert({GameTime::GetGameTime().count() + 5, SPAWN_TYPE_CREATURE, spawnId});
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if already alive
|
||||
auto bounds = _creatureBySpawnIdStore.equal_range(spawnId);
|
||||
for (auto itr = bounds.first; itr != bounds.second; ++itr)
|
||||
{
|
||||
if (itr->second->IsAlive())
|
||||
{
|
||||
RemoveCreatureRespawnTime(spawnId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove respawn time BEFORE LoadFromDB, otherwise the creature
|
||||
// reads it back and loads as DEAD instead of ALIVE
|
||||
RemoveCreatureRespawnTime(spawnId);
|
||||
|
||||
Creature* creature = new Creature();
|
||||
if (!creature->LoadCreatureFromDB(spawnId, this, true, true))
|
||||
delete creature;
|
||||
}
|
||||
|
||||
void Map::ProcessGameObjectRespawn(ObjectGuid::LowType spawnId)
|
||||
{
|
||||
// Pool members are handled entirely by PoolMgr
|
||||
if (uint32 poolId = sPoolMgr->IsPartOfAPool<GameObject>(spawnId))
|
||||
{
|
||||
sPoolMgr->UpdatePool<GameObject>(poolId, spawnId);
|
||||
RemoveGORespawnTime(spawnId);
|
||||
return;
|
||||
}
|
||||
|
||||
GameObjectData const* data = sObjectMgr->GetGameObjectData(spawnId);
|
||||
if (!data)
|
||||
{
|
||||
RemoveGORespawnTime(spawnId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Compat-mode gameobjects handle their own respawn — don't interfere.
|
||||
// Clean up the stale respawn time entry since the legacy system manages these.
|
||||
SpawnGroupTemplateData const* groupData = sObjectMgr->GetSpawnGroupData(data->spawnGroupId);
|
||||
if (!groupData || (groupData->flags & SPAWNGROUP_FLAG_COMPATIBILITY_MODE))
|
||||
{
|
||||
RemoveGORespawnTime(spawnId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't respawn if the spawn group is not active
|
||||
if (!IsSpawnGroupActive(data->spawnGroupId))
|
||||
{
|
||||
_respawnQueue.insert({GameTime::GetGameTime().count() + 5, SPAWN_TYPE_GAMEOBJECT, spawnId});
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if grid isn't loaded (will be handled in grid loader)
|
||||
if (!IsGridLoaded(data->posX, data->posY))
|
||||
{
|
||||
_respawnQueue.insert({GameTime::GetGameTime().count() + 5, SPAWN_TYPE_GAMEOBJECT, spawnId});
|
||||
return;
|
||||
}
|
||||
|
||||
if (_gameobjectBySpawnIdStore.count(spawnId))
|
||||
{
|
||||
RemoveGORespawnTime(spawnId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove respawn time BEFORE LoadFromDB, otherwise the GO
|
||||
// reads it back and loads as despawned
|
||||
RemoveGORespawnTime(spawnId);
|
||||
|
||||
GameObject* gameobject = new GameObject();
|
||||
if (!gameobject->LoadGameObjectFromDB(spawnId, this, true))
|
||||
delete gameobject;
|
||||
}
|
||||
|
||||
void Map::UpdateEncounterState(EncounterCreditType type, uint32 creditEntry, Unit* source)
|
||||
{
|
||||
Difficulty difficulty_fixed = (IsSharedDifficultyMap(GetId()) ? Difficulty(GetDifficulty() % 2) : GetDifficulty());
|
||||
|
|
|
|||
|
|
@ -35,11 +35,13 @@
|
|||
#include "PathGenerator.h"
|
||||
#include "Position.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "SpawnData.h"
|
||||
#include "Timer.h"
|
||||
#include "GridTerrainData.h"
|
||||
#include <bitset>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <shared_mutex>
|
||||
|
||||
class Unit;
|
||||
|
|
@ -424,6 +426,8 @@ public:
|
|||
void RemoveCreatureRespawnTime(ObjectGuid::LowType dbGuid);
|
||||
void SaveGORespawnTime(ObjectGuid::LowType dbGuid, time_t& respawnTime);
|
||||
void RemoveGORespawnTime(ObjectGuid::LowType dbGuid);
|
||||
[[nodiscard]] std::unordered_map<ObjectGuid::LowType, time_t> const& GetCreatureRespawnTimes() const { return _creatureRespawnTimes; }
|
||||
[[nodiscard]] std::unordered_map<ObjectGuid::LowType, time_t> const& GetGORespawnTimes() const { return _goRespawnTimes; }
|
||||
void LoadRespawnTimes();
|
||||
void DeleteRespawnTimes();
|
||||
[[nodiscard]] time_t GetInstanceResetPeriod() const { return _instanceResetPeriod; }
|
||||
|
|
@ -431,6 +435,40 @@ public:
|
|||
void UpdatePlayerZoneStats(uint32 oldZone, uint32 newZone);
|
||||
[[nodiscard]] uint32 ApplyDynamicModeRespawnScaling(WorldObject const* obj, uint32 respawnDelay) const;
|
||||
|
||||
bool SpawnGroupSpawn(uint32 groupId, bool ignoreRespawn = false, bool force = false);
|
||||
bool SpawnGroupDespawn(uint32 groupId, bool deleteRespawnTimes = false);
|
||||
[[nodiscard]] bool IsSpawnGroupActive(uint32 groupId) const;
|
||||
void ProcessRespawns();
|
||||
void ProcessCreatureRespawn(ObjectGuid::LowType spawnId);
|
||||
void ProcessGameObjectRespawn(ObjectGuid::LowType spawnId);
|
||||
|
||||
[[nodiscard]] time_t GetRespawnTime(SpawnObjectType type, ObjectGuid::LowType spawnId) const
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case SPAWN_TYPE_CREATURE:
|
||||
return GetCreatureRespawnTime(spawnId);
|
||||
case SPAWN_TYPE_GAMEOBJECT:
|
||||
return GetGORespawnTime(spawnId);
|
||||
default:
|
||||
return time_t(0);
|
||||
}
|
||||
}
|
||||
void RemoveRespawnTime(SpawnObjectType type, ObjectGuid::LowType spawnId)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case SPAWN_TYPE_CREATURE:
|
||||
RemoveCreatureRespawnTime(spawnId);
|
||||
break;
|
||||
case SPAWN_TYPE_GAMEOBJECT:
|
||||
RemoveGORespawnTime(spawnId);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EventProcessor Events;
|
||||
|
||||
void ScheduleCreatureRespawn(ObjectGuid /*creatureGuid*/, Milliseconds /*respawnTimer*/, Position pos = Position());
|
||||
|
|
@ -602,6 +640,27 @@ private:
|
|||
std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t> _creatureRespawnTimes;
|
||||
std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t> _goRespawnTimes;
|
||||
|
||||
// Time-ordered index for ProcessRespawns() — avoids O(n) full scan.
|
||||
// Based on TrinityCore's priority queue approach (r00ty-tc, 59db2eee).
|
||||
struct RespawnEntry
|
||||
{
|
||||
time_t respawnTime;
|
||||
SpawnObjectType type;
|
||||
ObjectGuid::LowType spawnId;
|
||||
bool operator<(RespawnEntry const& other) const
|
||||
{
|
||||
if (respawnTime != other.respawnTime)
|
||||
return respawnTime < other.respawnTime;
|
||||
if (type != other.type)
|
||||
return type < other.type;
|
||||
return spawnId < other.spawnId;
|
||||
}
|
||||
};
|
||||
std::set<RespawnEntry> _respawnQueue;
|
||||
|
||||
std::unordered_set<uint32> _toggledSpawnGroupIds;
|
||||
uint32 _respawnCheckTimer{0};
|
||||
|
||||
std::unordered_map<uint32, uint32> _zonePlayerCountMap;
|
||||
|
||||
ZoneDynamicInfoMap _zoneDynamicInfo;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#define AZEROTHCORE_SPAWNDATA_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include <string>
|
||||
|
||||
enum SpawnObjectType : uint8
|
||||
|
|
@ -41,26 +42,30 @@ enum SpawnGroupFlags : uint32
|
|||
{
|
||||
SPAWNGROUP_FLAG_NONE = 0x00,
|
||||
SPAWNGROUP_FLAG_SYSTEM = 0x01,
|
||||
SPAWNGROUP_FLAG_COMPATIBILITY_MODE = 0x02,
|
||||
SPAWNGROUP_FLAG_MANUAL_SPAWN = 0x04,
|
||||
SPAWNGROUP_FLAG_DYNAMIC_SPAWN_RATE = 0x08,
|
||||
SPAWNGROUP_FLAG_ESCORTQUESTNPC = 0x10,
|
||||
|
||||
SPAWNGROUP_FLAG_ALL = SPAWNGROUP_FLAG_SYSTEM |
|
||||
SPAWNGROUP_FLAG_ALL = SPAWNGROUP_FLAG_SYSTEM | SPAWNGROUP_FLAG_COMPATIBILITY_MODE |
|
||||
SPAWNGROUP_FLAG_MANUAL_SPAWN | SPAWNGROUP_FLAG_DYNAMIC_SPAWN_RATE |
|
||||
SPAWNGROUP_FLAG_ESCORTQUESTNPC
|
||||
};
|
||||
|
||||
constexpr uint32 SPAWNGROUP_MAP_UNSET = 0xFFFFFFFF;
|
||||
|
||||
struct SpawnGroupTemplateData
|
||||
{
|
||||
uint32 groupId;
|
||||
std::string name;
|
||||
uint16 mapid;
|
||||
uint32 mapId;
|
||||
SpawnGroupFlags flags;
|
||||
};
|
||||
|
||||
struct SpawnData
|
||||
{
|
||||
SpawnObjectType const type;
|
||||
ObjectGuid::LowType spawnId{0};
|
||||
uint16 mapid{0};
|
||||
uint32 phaseMask{0};
|
||||
float posX{0.0f};
|
||||
|
|
|
|||
|
|
@ -1449,6 +1449,32 @@ enum AcoreStrings
|
|||
|
||||
// Achievement commands
|
||||
LANG_ACHIEVEMENT_ADD_ONLINE = 30126,
|
||||
LANG_ACHIEVEMENT_ADD_OFFLINE = 30127
|
||||
LANG_ACHIEVEMENT_ADD_OFFLINE = 30127,
|
||||
|
||||
// Spawn group commands
|
||||
LANG_SPAWNGROUP_SPAWN_SYSTEM_ERROR = 35411,
|
||||
LANG_SPAWNGROUP_SPAWN_SUCCESS = 35412,
|
||||
LANG_SPAWNGROUP_SPAWN_FAILED = 35413,
|
||||
LANG_SPAWNGROUP_DESPAWN_SYSTEM_ERROR = 35414,
|
||||
LANG_SPAWNGROUP_DESPAWN_SUCCESS = 35415,
|
||||
LANG_SPAWNGROUP_DESPAWN_FAILED = 35416,
|
||||
LANG_LIST_RESPAWNS_CREATURE_HEADER = 35419,
|
||||
LANG_LIST_RESPAWNS_CREATURE_ENTRY = 35420,
|
||||
LANG_LIST_RESPAWNS_GO_HEADER = 35421,
|
||||
LANG_LIST_RESPAWNS_GO_ENTRY = 35422,
|
||||
LANG_LIST_RESPAWNS_LIMIT = 35423,
|
||||
LANG_SPAWNGROUP_NOT_FOUND = 35424,
|
||||
|
||||
// Pool debug commands
|
||||
LANG_POOL_NOT_FOUND = 35425,
|
||||
LANG_POOL_INFO_HEADER = 35426,
|
||||
LANG_POOL_INFO_MEMBERS_HEADER = 35427,
|
||||
LANG_POOL_INFO_MEMBER = 35428,
|
||||
LANG_POOL_INFO_SUBPOOLS_HEADER = 35429,
|
||||
LANG_POOL_INFO_SUBPOOL = 35430,
|
||||
LANG_POOL_LOOKUP_IN_POOL = 35431,
|
||||
LANG_POOL_LOOKUP_NOT_IN_POOL = 35432,
|
||||
LANG_POOL_LOOKUP_USE_INFO = 35433,
|
||||
LANG_POOL_LOOKUP_NOTARGET = 35434
|
||||
};
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -580,7 +580,7 @@ void PoolMgr::LoadFromDB()
|
|||
{
|
||||
uint32 oldMSTime = getMSTime();
|
||||
|
||||
QueryResult result = WorldDatabase.Query("SELECT entry, max_limit FROM pool_template");
|
||||
QueryResult result = WorldDatabase.Query("SELECT entry, max_limit, description FROM pool_template");
|
||||
if (!result)
|
||||
{
|
||||
mPoolTemplate.clear();
|
||||
|
|
@ -597,7 +597,8 @@ void PoolMgr::LoadFromDB()
|
|||
uint32 pool_id = fields[0].Get<uint32>();
|
||||
|
||||
PoolTemplateData& pPoolTemplate = mPoolTemplate[pool_id];
|
||||
pPoolTemplate.MaxLimit = fields[1].Get<uint32>();
|
||||
pPoolTemplate.MaxLimit = fields[1].Get<uint32>();
|
||||
pPoolTemplate.Description = fields[2].Get<std::string>();
|
||||
|
||||
++count;
|
||||
} while (result->NextRow());
|
||||
|
|
@ -1166,3 +1167,39 @@ template void PoolMgr::UpdatePool<Pool>(uint32 pool_id, uint32 db_guid_or_pool_i
|
|||
template void PoolMgr::UpdatePool<GameObject>(uint32 pool_id, uint32 db_guid_or_pool_id);
|
||||
template void PoolMgr::UpdatePool<Creature>(uint32 pool_id, uint32 db_guid_or_pool_id);
|
||||
template void PoolMgr::UpdatePool<Quest>(uint32 pool_id, uint32 db_guid_or_pool_id);
|
||||
|
||||
PoolTemplateData const* PoolMgr::GetPoolTemplate(uint32 poolId) const
|
||||
{
|
||||
auto itr = mPoolTemplate.find(poolId);
|
||||
return itr != mPoolTemplate.end() ? &itr->second : nullptr;
|
||||
}
|
||||
|
||||
PoolGroup<Creature> const* PoolMgr::GetPoolCreatureGroup(uint32 poolId) const
|
||||
{
|
||||
auto itr = mPoolCreatureGroups.find(poolId);
|
||||
return itr != mPoolCreatureGroups.end() ? &itr->second : nullptr;
|
||||
}
|
||||
|
||||
PoolGroup<GameObject> const* PoolMgr::GetPoolGameObjectGroup(uint32 poolId) const
|
||||
{
|
||||
auto itr = mPoolGameobjectGroups.find(poolId);
|
||||
return itr != mPoolGameobjectGroups.end() ? &itr->second : nullptr;
|
||||
}
|
||||
|
||||
PoolGroup<Pool> const* PoolMgr::GetPoolPoolGroup(uint32 poolId) const
|
||||
{
|
||||
auto itr = mPoolPoolGroups.find(poolId);
|
||||
return itr != mPoolPoolGroups.end() ? &itr->second : nullptr;
|
||||
}
|
||||
|
||||
uint32 PoolMgr::GetCreaturePoolId(uint32 guid) const
|
||||
{
|
||||
SearchMap::const_iterator itr = mCreatureSearchMap.find(guid);
|
||||
return itr != mCreatureSearchMap.end() ? itr->second : 0;
|
||||
}
|
||||
|
||||
uint32 PoolMgr::GetGameObjectPoolId(uint32 guid) const
|
||||
{
|
||||
SearchMap::const_iterator itr = mGameobjectSearchMap.find(guid);
|
||||
return itr != mGameobjectSearchMap.end() ? itr->second : 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@
|
|||
|
||||
struct PoolTemplateData
|
||||
{
|
||||
uint32 MaxLimit;
|
||||
uint32 MaxLimit;
|
||||
std::string Description;
|
||||
};
|
||||
|
||||
struct PoolObject
|
||||
|
|
@ -89,6 +90,8 @@ public:
|
|||
return EqualChanced.front().guid;
|
||||
}
|
||||
uint32 GetPoolId() const { return poolId; }
|
||||
std::vector<PoolObject> const& GetExplicitlyChanced() const { return ExplicitlyChanced; }
|
||||
std::vector<PoolObject> const& GetEqualChanced() const { return EqualChanced; }
|
||||
private:
|
||||
uint32 poolId;
|
||||
PoolObjectList ExplicitlyChanced;
|
||||
|
|
@ -135,6 +138,15 @@ public:
|
|||
PooledQuestRelation mQuestCreatureRelation;
|
||||
PooledQuestRelation mQuestGORelation;
|
||||
|
||||
// Pool info accessors for debug commands
|
||||
PoolTemplateData const* GetPoolTemplate(uint32 poolId) const;
|
||||
PoolGroup<Creature> const* GetPoolCreatureGroup(uint32 poolId) const;
|
||||
PoolGroup<GameObject> const* GetPoolGameObjectGroup(uint32 poolId) const;
|
||||
PoolGroup<Pool> const* GetPoolPoolGroup(uint32 poolId) const;
|
||||
ActivePoolData const& GetSpawnedData() const { return mSpawnedData; }
|
||||
uint32 GetCreaturePoolId(uint32 guid) const;
|
||||
uint32 GetGameObjectPoolId(uint32 guid) const;
|
||||
|
||||
friend class PoolQuestReloadFixTest;
|
||||
private:
|
||||
template<typename T>
|
||||
|
|
|
|||
|
|
@ -553,6 +553,9 @@ void World::SetInitialWorldSettings()
|
|||
LOG_INFO("server.loading", "Loading Creature Base Stats...");
|
||||
sObjectMgr->LoadCreatureClassLevelStats();
|
||||
|
||||
LOG_INFO("server.loading", "Loading Spawn Group Templates...");
|
||||
sObjectMgr->LoadSpawnGroupTemplates();
|
||||
|
||||
LOG_INFO("server.loading", "Loading Creature Data...");
|
||||
sObjectMgr->LoadCreatures();
|
||||
|
||||
|
|
@ -580,6 +583,9 @@ void World::SetInitialWorldSettings()
|
|||
LOG_INFO("server.loading", "Loading Gameobject Data...");
|
||||
sObjectMgr->LoadGameobjects();
|
||||
|
||||
LOG_INFO("server.loading", "Loading Spawn Group Data...");
|
||||
sObjectMgr->LoadSpawnGroups(); // must be after LoadCreatures() and LoadGameobjects()
|
||||
|
||||
LOG_INFO("server.loading", "Loading GameObject Addon Data...");
|
||||
sObjectMgr->LoadGameObjectAddons(); // must be after LoadGameObjectTemplate() and LoadGameobjects()
|
||||
|
||||
|
|
|
|||
|
|
@ -521,6 +521,8 @@ void WorldConfig::BuildConfigCache()
|
|||
|
||||
SetConfigValue<float>(CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT, "Respawn.DynamicRateGameObject", 1.0f);
|
||||
SetConfigValue<uint32>(CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT, "Respawn.DynamicMinimumGameObject", 10);
|
||||
SetConfigValue<bool>(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC, "Respawn.DynamicEscortNPC", false);
|
||||
SetConfigValue<bool>(CONFIG_RESPAWN_FORCE_COMPATIBILITY_MODE, "Respawn.ForceCompatibilityMode", false);
|
||||
|
||||
SetConfigValue<bool>(CONFIG_VMAP_INDOOR_CHECK, "vmap.enableIndoorCheck", true);
|
||||
SetConfigValue<bool>(CONFIG_VMAP_ENABLE_LOS, "vmap.enableLOS", true);
|
||||
|
|
|
|||
|
|
@ -391,6 +391,8 @@ enum ServerConfigs
|
|||
CONFIG_SCOURGEINVASION_COUNTER_THIRD,
|
||||
CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT,
|
||||
CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE,
|
||||
CONFIG_RESPAWN_DYNAMIC_ESCORTNPC,
|
||||
CONFIG_RESPAWN_FORCE_COMPATIBILITY_MODE,
|
||||
RATE_HEALTH,
|
||||
RATE_POWER_MANA,
|
||||
RATE_POWER_RAGE_INCOME,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
src/server/scripts/Commands/cs_pool.cpp
Normal file
235
src/server/scripts/Commands/cs_pool.cpp
Normal 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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue