diff options
author | r00ty-tc <r00ty-tc@users.noreply.github.com> | 2017-05-07 21:48:41 +0100 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2020-08-22 12:59:57 +0200 |
commit | 03b125e6d1947258316c931499746696a95aded2 (patch) | |
tree | 34d7ebc57cd3669d6d1a118e1491d3ecf353470a /src/server/game | |
parent | bf5be2839652e038eeb87c9fa301fd9dd6de8982 (diff) |
Dynamic Creature/Go spawning:
- True blizzlike creature spawn/respawn behavior - new creature = new object
- Toggleable spawn groups (with C++/SAI/command options to use them)
- Custom feature: dynamic spawn rate scaling. Accelerates respawn rate based on players in the zone.
- Backward compatibility mode (set via group and for summons)
to support creatures/gos that currently don't work well with this
(this should be removed once the exceptions are fixed)
Fixes and closes #2858
Tags #8661 as fixable.
Fixes and closes #13787
Fixes #15222.
(cherry picked from commit 59db2eeea0a35028779fd76372ae06cc98c8086f)
Diffstat (limited to 'src/server/game')
40 files changed, 2008 insertions, 666 deletions
diff --git a/src/server/game/AI/CreatureAI.h b/src/server/game/AI/CreatureAI.h index 76eec1909d8..09f4744171e 100644 --- a/src/server/game/AI/CreatureAI.h +++ b/src/server/game/AI/CreatureAI.h @@ -180,6 +180,9 @@ class TC_GAME_API CreatureAI : public UnitAI // If a PlayerAI* is returned, that AI is placed on the player instead of the default charm AI // Object destruction is handled by Unit::RemoveCharmedBy virtual PlayerAI* GetAIForCharmedPlayer(Player* /*who*/) { return nullptr; } + // Should return true if the NPC is target of an escort quest + // If onlyIfActive is set, should return true only if the escort quest is currently active + virtual bool IsEscortNPC(bool /*onlyIfActive*/) const { return false; } // intended for encounter design/debugging. do not use for other purposes. expensive. int32 VisualizeBoundary(uint32 duration, Unit* owner = nullptr, bool fill = false) const; diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp index ff1e5591fb8..ee6d0770604 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp @@ -462,7 +462,7 @@ void BossAI::_Reset() events.Reset(); summons.DespawnAll(); scheduler.CancelAll(); - if (instance) + if (instance && instance->GetBossState(_bossId) != DONE) instance->SetBossState(_bossId, NOT_STARTED); } @@ -548,12 +548,12 @@ bool BossAI::CanAIAttack(Unit const* target) const return CheckBoundary(target); } -void BossAI::_DespawnAtEvade(uint32 delayToRespawn /*= 30*/, Creature* who /*= nullptr*/) +void BossAI::_DespawnAtEvade(Seconds delayToRespawn, Creature* who) { - if (delayToRespawn < 2) + if (delayToRespawn < Seconds(2)) { - TC_LOG_ERROR("scripts", "_DespawnAtEvade called with delay of %u seconds, defaulting to 2.", delayToRespawn); - delayToRespawn = 2; + TC_LOG_ERROR("scripts", "_DespawnAtEvade called with delay of " SI64FMTD " seconds, defaulting to 2.", delayToRespawn.count()); + delayToRespawn = Seconds(2); } if (!who) diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.h b/src/server/game/AI/ScriptedAI/ScriptedCreature.h index 084f05552c4..c255f97ae24 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.h +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.h @@ -364,8 +364,8 @@ class TC_GAME_API BossAI : public ScriptedAI void _EnterCombat(); void _JustDied(); void _JustReachedHome(); - void _DespawnAtEvade(uint32 delayToRespawn = 30, Creature* who = nullptr); - void _DespawnAtEvade(Seconds const& time, Creature* who = nullptr) { _DespawnAtEvade(uint32(time.count()), who); } + void _DespawnAtEvade(Seconds delayToRespawn, Creature* who = nullptr); + void _DespawnAtEvade(uint32 delayToRespawn = 30, Creature* who = nullptr) { _DespawnAtEvade(Seconds(delayToRespawn), who); } void TeleportCheaters(); diff --git a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp index fc0ed3fa9e0..02e41275b0c 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp @@ -30,6 +30,7 @@ EndScriptData */ #include "MotionMaster.h" #include "ObjectAccessor.h" #include "Player.h" +#include "World.h" enum Points { @@ -238,13 +239,17 @@ void npc_escortAI::UpdateAI(uint32 diff) return; } - if (m_bCanInstantRespawn) + if (m_bCanInstantRespawn && !sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC)) { me->setDeathState(JUST_DIED); me->Respawn(); } else + { + if (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC)) + me->GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, me->GetSpawnId(), true); me->DespawnOrUnsummon(); + } return; } @@ -279,11 +284,17 @@ void npc_escortAI::UpdateAI(uint32 diff) { TC_LOG_DEBUG("scripts", "EscortAI failed because player/group was to far away or not found"); - if (m_bCanInstantRespawn) + bool isEscort = false; + if (CreatureData const* cdata = me->GetCreatureData()) + isEscort = (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC) && (cdata->spawnGroupData->flags & SPAWNGROUP_FLAG_ESCORTQUESTNPC)); + + if (m_bCanInstantRespawn && !isEscort) { me->setDeathState(JUST_DIED); me->Respawn(); } + else if (m_bCanInstantRespawn && isEscort) + me->GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, me->GetSpawnId(), true); else me->DespawnOrUnsummon(); @@ -423,6 +434,22 @@ void npc_escortAI::SetRun(bool on) /// @todo get rid of this many variables passed in function. void npc_escortAI::Start(bool isActiveAttacker /* = true*/, bool run /* = false */, ObjectGuid playerGUID /* = 0 */, Quest const* quest /* = nullptr */, bool instantRespawn /* = false */, bool canLoopPath /* = false */, bool resetWaypoints /* = true */) { + // Queue respawn from the point it starts + if (Map* map = me->GetMap()) + { + if (CreatureData const* cdata = me->GetCreatureData()) + { + if (SpawnGroupTemplateData const* groupdata = cdata->spawnGroupData) + { + if (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC) && (groupdata->flags & SPAWNGROUP_FLAG_ESCORTQUESTNPC) && !map->GetCreatureRespawnTime(me->GetSpawnId())) + { + me->SetRespawnTime(me->GetRespawnDelay()); + me->SaveRespawnTime(); + } + } + } + } + if (me->GetVictim()) { TC_LOG_ERROR("scripts.escortai", "TSCR ERROR: EscortAI (script: %s, creature entry: %u) attempts to Start while in combat", me->GetScriptName().c_str(), me->GetEntry()); @@ -567,3 +594,14 @@ bool npc_escortAI::GetWaypointPosition(uint32 pointId, float& x, float& y, float return false; } + +bool npc_escortAI::IsEscortNPC(bool onlyIfActive) const +{ + if (!onlyIfActive) + return true; + + if (!GetEventStarterGUID().IsEmpty()) + return true; + + return false; +} diff --git a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h index c72421ddc4b..81126b704e8 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h +++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h @@ -106,6 +106,7 @@ struct TC_GAME_API npc_escortAI : public ScriptedAI bool GetAttack() const { return m_bIsActiveAttacker; }//used in EnterEvadeMode override void SetCanAttack(bool attack) { m_bIsActiveAttacker = attack; } ObjectGuid GetEventStarterGUID() const { return m_uiPlayerGUID; } + virtual bool IsEscortNPC(bool isEscorting) const override; protected: Player* GetPlayerForEscort(); diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index 28c10607072..8b6616a2ee3 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -2107,6 +2107,92 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (IsCreature(target)) target->ToCreature()->SetCorpseDelay(e.action.corpseDelay.timer); } + + break; + } + case SMART_ACTION_SPAWN_SPAWNGROUP: + { + if (e.action.groupSpawn.minDelay == 0 && e.action.groupSpawn.maxDelay == 0) + { + bool const ignoreRespawn = ((e.action.groupSpawn.spawnflags & SMARTAI_SPAWN_FLAGS::SMARTAI_SPAWN_FLAG_IGNORE_RESPAWN) != 0); + bool const force = ((e.action.groupSpawn.spawnflags & SMARTAI_SPAWN_FLAGS::SMARTAI_SPAWN_FLAG_FORCE_SPAWN) != 0); + + // Instant spawn + sObjectMgr->SpawnGroupSpawn(e.action.groupSpawn.groupId, GetBaseObject()->GetMap(), ignoreRespawn, force); + } + else + { + // Delayed spawn (use values from parameter to schedule event to call us back + SmartEvent ne = SmartEvent(); + ne.type = (SMART_EVENT)SMART_EVENT_UPDATE; + ne.event_chance = 100; + + ne.minMaxRepeat.min = e.action.groupSpawn.minDelay; + ne.minMaxRepeat.max = e.action.groupSpawn.maxDelay; + ne.minMaxRepeat.repeatMin = 0; + ne.minMaxRepeat.repeatMax = 0; + + ne.event_flags = 0; + ne.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; + + SmartAction ac = SmartAction(); + ac.type = (SMART_ACTION)SMART_ACTION_SPAWN_SPAWNGROUP; + ac.groupSpawn.groupId = e.action.groupSpawn.groupId; + ac.groupSpawn.minDelay = 0; + ac.groupSpawn.maxDelay = 0; + ac.groupSpawn.spawnflags = e.action.groupSpawn.spawnflags; + ac.timeEvent.id = e.action.timeEvent.id; + + SmartScriptHolder ev = SmartScriptHolder(); + ev.event = ne; + ev.event_id = e.event_id; + ev.target = e.target; + ev.action = ac; + InitTimer(ev); + mStoredEvents.push_back(ev); + } + break; + } + case SMART_ACTION_DESPAWN_SPAWNGROUP: + { + if (e.action.groupSpawn.minDelay == 0 && e.action.groupSpawn.maxDelay == 0) + { + bool const deleteRespawnTimes = ((e.action.groupSpawn.spawnflags & SMARTAI_SPAWN_FLAGS::SMARTAI_SPAWN_FLAG_NOSAVE_RESPAWN) != 0); + + // Instant spawn + sObjectMgr->SpawnGroupDespawn(e.action.groupSpawn.groupId, GetBaseObject()->GetMap(), deleteRespawnTimes); + } + else + { + // Delayed spawn (use values from parameter to schedule event to call us back + SmartEvent ne = SmartEvent(); + ne.type = (SMART_EVENT)SMART_EVENT_UPDATE; + ne.event_chance = 100; + + ne.minMaxRepeat.min = e.action.groupSpawn.minDelay; + ne.minMaxRepeat.max = e.action.groupSpawn.maxDelay; + ne.minMaxRepeat.repeatMin = 0; + ne.minMaxRepeat.repeatMax = 0; + + ne.event_flags = 0; + ne.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; + + SmartAction ac = SmartAction(); + ac.type = (SMART_ACTION)SMART_ACTION_DESPAWN_SPAWNGROUP; + ac.groupSpawn.groupId = e.action.groupSpawn.groupId; + ac.groupSpawn.minDelay = 0; + ac.groupSpawn.maxDelay = 0; + ac.groupSpawn.spawnflags = e.action.groupSpawn.spawnflags; + ac.timeEvent.id = e.action.timeEvent.id; + + SmartScriptHolder ev = SmartScriptHolder(); + ev.event = ne; + ev.event_id = e.event_id; + ev.target = e.target; + ev.action = ac; + InitTimer(ev); + mStoredEvents.push_back(ev); + } break; } case SMART_ACTION_DISABLE_EVADE: diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp index 40a9cdbd9ff..f1240950f1e 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp @@ -237,7 +237,7 @@ void SmartAIMgr::LoadSmartAIFromDB() } case SMART_SCRIPT_TYPE_GAMEOBJECT: { - GameObjectData const* gameObject = sObjectMgr->GetGOData(uint64(-temp.entryOrGuid)); + GameObjectData const* gameObject = sObjectMgr->GetGameObjectData(uint64(-temp.entryOrGuid)); if (!gameObject) { TC_LOG_ERROR("sql.sql", "SmartAIMgr::LoadSmartAIFromDB: GameObject guid (" SI64FMTD ") does not exist, skipped loading.", -temp.entryOrGuid); @@ -964,7 +964,7 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) return false; } - if (e.event.distance.guid != 0 && !sObjectMgr->GetGOData(e.event.distance.guid)) + if (e.event.distance.guid != 0 && !sObjectMgr->GetGameObjectData(e.event.distance.guid)) { TC_LOG_ERROR("sql.sql", "SmartAIMgr: Event SMART_EVENT_DISTANCE_GAMEOBJECT using invalid gameobject guid %u, skipped.", e.event.distance.guid); return false; @@ -1629,6 +1629,8 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) case SMART_ACTION_TRIGGER_RANDOM_TIMED_EVENT: case SMART_ACTION_SET_COUNTER: case SMART_ACTION_REMOVE_ALL_GAMEOBJECTS: + case SMART_ACTION_SPAWN_SPAWNGROUP: + case SMART_ACTION_DESPAWN_SPAWNGROUP: break; default: TC_LOG_ERROR("sql.sql", "SmartAIMgr: Not handled action_type(%u), event_type(%u), Entry " SI64FMTD " SourceType %u Event %u, skipped.", e.GetActionType(), e.GetEventType(), e.entryOrGuid, e.GetScriptType(), e.event_id); diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.h b/src/server/game/AI/SmartScripts/SmartScriptMgr.h index 1bb6c6b7fdb..6285726bb33 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.h +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.h @@ -603,7 +603,9 @@ enum SMART_ACTION SMART_ACTION_PLAY_ANIMKIT = 128, // id, type (0 = oneShot, 1 = aiAnim, 2 = meleeAnim, 3 = movementAnim) SMART_ACTION_SCENE_PLAY = 129, // sceneId SMART_ACTION_SCENE_CANCEL = 130, // sceneId - // 131 - 134 : 3.3.5 reserved + SMART_ACTION_SPAWN_SPAWNGROUP = 131, // Group ID, min secs, max secs, spawnflags + SMART_ACTION_DESPAWN_SPAWNGROUP = 132, // Group ID, min secs, max secs, spawnflags + // 134 : 3.3.5 reserved SMART_ACTION_PLAY_CINEMATIC = 135, // reserved for future uses SMART_ACTION_SET_MOVEMENT_SPEED = 136, // movementType, speedInteger, speedFraction SMART_ACTION_PLAY_SPELL_VISUAL_KIT = 137, // spellVisualKitId, kitType (unknown values, copypaste from packet dumps), duration @@ -1119,6 +1121,13 @@ struct SmartAction { uint32 disable; } disableEvade; + struct + { + uint32 groupId; + uint32 minDelay; + uint32 maxDelay; + uint32 spawnflags; + } groupSpawn; struct { @@ -1188,6 +1197,14 @@ struct SmartAction }; }; +enum SMARTAI_SPAWN_FLAGS +{ + SMARTAI_SPAWN_FLAG_NONE = 0x00, + SMARTAI_SPAWN_FLAG_IGNORE_RESPAWN = 0x01, + SMARTAI_SPAWN_FLAG_FORCE_SPAWN = 0x02, + SMARTAI_SPAWN_FLAG_NOSAVE_RESPAWN = 0x04, +}; + enum SMARTAI_TEMPLATE { SMARTAI_TEMPLATE_BASIC = 0, //nothing is preset diff --git a/src/server/game/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp index cbc704b38fa..1e411d9d56c 100644 --- a/src/server/game/Conditions/ConditionMgr.cpp +++ b/src/server/game/Conditions/ConditionMgr.cpp @@ -2135,7 +2135,7 @@ bool ConditionMgr::isConditionTypeValid(Condition* cond) const } if (cond->ConditionValue3) { - if (GameObjectData const* goData = sObjectMgr->GetGOData(cond->ConditionValue3)) + if (GameObjectData const* goData = sObjectMgr->GetGameObjectData(cond->ConditionValue3)) { if (cond->ConditionValue2 && goData->id != cond->ConditionValue2) { diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 17d3be260ee..93489d5516b 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -287,7 +287,7 @@ _pickpocketLootRestore(0), m_corpseRemoveTime(0), m_respawnTime(0), m_respawnDelay(300), m_corpseDelay(60), m_respawnradius(0.0f), m_boundaryCheckTime(2500), m_combatPulseTime(0), m_combatPulseDelay(0), m_reactState(REACT_AGGRESSIVE), m_defaultMovementType(IDLE_MOTION_TYPE), m_spawnId(UI64LIT(0)), m_equipmentId(0), m_originalEquipmentId(0), m_AlreadyCallAssistance(false), m_AlreadySearchedAssistance(false), m_regenHealth(true), m_cannotReachTarget(false), m_cannotReachTimer(0), m_AI_locked(false), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL), -m_originalEntry(0), m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_waypointID(0), m_path_id(0), m_formation(nullptr), m_focusSpell(nullptr), m_focusDelay(0), m_shouldReacquireTarget(false), m_suppressedOrientation(0.0f), +m_originalEntry(0), m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_waypointID(0), m_path_id(0), m_formation(nullptr), m_respawnCompatibilityMode(false), m_focusSpell(nullptr), m_focusDelay(0), m_shouldReacquireTarget(false), m_suppressedOrientation(0.0f), _lastDamagedTime(0) { m_regenTimer = CREATURE_REGEN_INTERVAL; @@ -376,44 +376,60 @@ void Creature::RemoveCorpse(bool setSpawnTime, bool destroyForNearbyPlayers) if (getDeathState() != CORPSE) return; - m_corpseRemoveTime = time(nullptr); - setDeathState(DEAD); - RemoveAllAuras(); - DestroyForNearbyPlayers(); // old UpdateObjectVisibility() - loot.clear(); - uint32 respawnDelay = m_respawnDelay; - if (IsAIEnabled) - AI()->CorpseRemoved(respawnDelay); + if (m_respawnCompatibilityMode) + { + m_corpseRemoveTime = time(nullptr); + setDeathState(DEAD); + RemoveAllAuras(); + loot.clear(); + uint32 respawnDelay = m_respawnDelay; + if (IsAIEnabled) + AI()->CorpseRemoved(respawnDelay); - if (destroyForNearbyPlayers) - DestroyForNearbyPlayers(); + if (destroyForNearbyPlayers) + DestroyForNearbyPlayers(); - // Should get removed later, just keep "compatibility" with scripts - if (setSpawnTime) - m_respawnTime = std::max<time_t>(time(nullptr) + respawnDelay, m_respawnTime); + // Should get removed later, just keep "compatibility" with scripts + if (setSpawnTime) + m_respawnTime = std::max<time_t>(time(nullptr) + respawnDelay, m_respawnTime); - // if corpse was removed during falling, the falling will continue and override relocation to respawn position - if (IsFalling()) - StopMoving(); + // if corpse was removed during falling, the falling will continue and override relocation to respawn position + if (IsFalling()) + StopMoving(); - float x, y, z, o; - GetRespawnPosition(x, y, z, &o); + float x, y, z, o; + GetRespawnPosition(x, y, z, &o); - // We were spawned on transport, calculate real position - if (IsSpawnedOnTransport()) - { - Position& pos = m_movementInfo.transport.pos; - pos.m_positionX = x; - pos.m_positionY = y; - pos.m_positionZ = z; - pos.SetOrientation(o); + // We were spawned on transport, calculate real position + if (IsSpawnedOnTransport()) + { + Position& pos = m_movementInfo.transport.pos; + pos.m_positionX = x; + pos.m_positionY = y; + pos.m_positionZ = z; + pos.SetOrientation(o); + + if (TransportBase* transport = GetDirectTransport()) + transport->CalculatePassengerPosition(x, y, z, &o); + } - if (TransportBase* transport = GetDirectTransport()) - transport->CalculatePassengerPosition(x, y, z, &o); + SetHomePosition(x, y, z, o); + GetMap()->CreatureRelocation(this, x, y, z, o); } + else + { + // In case this is called directly and normal respawn timer not set + // Since this timer will be longer than the already present time it + // will be ignored if the correct place added a respawn timer + if (setSpawnTime) + { + uint32 respawnDelay = m_respawnDelay; + m_respawnTime = std::max<time_t>(time(NULL) + respawnDelay, m_respawnTime); - SetHomePosition(x, y, z, o); - GetMap()->CreatureRelocation(this, x, y, z, o); + SaveRespawnTime(0, false); + } + AddObjectToRemoveList(); + } } /** @@ -635,7 +651,7 @@ void Creature::Update(uint32 diff) break; case JUST_DIED: // Must not be called, see Creature::setDeathState JUST_DIED -> CORPSE promoting. - TC_LOG_ERROR("entities.unit", "Creature (%s) in wrong state: JUST_DEAD (1)", GetGUID().ToString().c_str()); + TC_LOG_ERROR("entities.unit", "Creature (%s) in wrong state: JUST_DIED (1)", GetGUID().ToString().c_str()); break; case DEAD: { @@ -643,22 +659,25 @@ void Creature::Update(uint32 diff) if (m_respawnTime <= now) { // First check if there are any scripts that object to us respawning - if (!sScriptMgr->CanSpawn(GetSpawnId(), GetEntry(), GetCreatureTemplate(), GetCreatureData(), GetMap())) - break; // Will be rechecked on next Update call + if (!sScriptMgr->CanSpawn(GetSpawnId(), GetEntry(), GetCreatureData(), GetMap())) + { + m_respawnTime = now + urand(4,7); + break; // Will be rechecked on next Update call after delay expires + } ObjectGuid dbtableHighGuid = ObjectGuid::Create<HighGuid::Creature>(GetMapId(), GetEntry(), m_spawnId); - time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid); - if (!linkedRespawntime) // Can respawn + time_t linkedRespawnTime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid); + if (!linkedRespawnTime) // Can respawn Respawn(); else // the master is dead { ObjectGuid targetGuid = sObjectMgr->GetLinkedRespawnGuid(dbtableHighGuid); - if (targetGuid == dbtableHighGuid) // if linking self, never respawn (check delayed to next day) - SetRespawnTime(DAY); + if (targetGuid == dbtableHighGuid) // if linking self, never respawn + SetRespawnTime(WEEK); else { // else copy time from master and add a little - time_t baseRespawnTime = std::max(linkedRespawntime, now); + time_t baseRespawnTime = std::max(linkedRespawnTime, now); time_t const offset = urand(5, MINUTE); // linked guid can be a boss, uses std::numeric_limits<time_t>::max to never respawn in that instance @@ -1008,7 +1027,7 @@ void Creature::Motion_Initialize() GetMotionMaster()->Initialize(); } -bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 entry, float x, float y, float z, float ang, CreatureData const* data /*= nullptr*/, uint32 vehId /*= 0*/) +bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 entry, Position const& pos, CreatureData const* data /*= nullptr*/, uint32 vehId /*= 0*/, bool dynamic) { ASSERT(map); SetMap(map); @@ -1019,6 +1038,10 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 entry, float PhasingHandler::InitDbVisibleMapId(GetPhaseShift(), data->terrainSwapMap); } + // Set if this creature can handle dynamic spawns + if (!dynamic) + SetRespawnCompatibilityMode(); + CreatureTemplate const* cinfo = sObjectMgr->GetCreatureTemplate(entry); if (!cinfo) { @@ -1028,13 +1051,13 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 entry, float //! Relocate before CreateFromProto, to initialize coords and allow //! returning correct zone id for selecting OutdoorPvP/Battlefield script - Relocate(x, y, z, ang); + Relocate(pos); // Check if the position is valid before calling CreateFromProto(), otherwise we might add Auras to Creatures at // invalid position, triggering a crash about Auras not removed in the destructor if (!IsPositionValid()) { - TC_LOG_ERROR("entities.unit", "Creature::Create(): given coordinates for creature (guidlow " UI64FMTD ", entry %d) are not valid (X: %f, Y: %f, Z: %f, O: %f)", guidlow, entry, x, y, z, ang); + TC_LOG_ERROR("entities.unit", "Creature::Create(): given coordinates for creature (guidlow " UI64FMTD ", entry %d) are not valid (X: %f, Y: %f, Z: %f, O: %f)", guidlow, entry, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation()); return false; } UpdatePositionData(); @@ -1074,10 +1097,8 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 entry, float //! Need to be called after LoadCreaturesAddon - MOVEMENTFLAG_HOVER is set there if (HasUnitMovementFlag(MOVEMENTFLAG_HOVER)) { - z += m_unitData->HoverHeight; - //! Relocate again with updated Z coord - Relocate(x, y, z, ang); + m_positionZ += m_unitData->HoverHeight; } LastUsedScriptID = GetScriptId(); @@ -1113,7 +1134,7 @@ Creature* Creature::CreateCreature(uint32 entry, Map* map, Position const& pos, lowGuid = map->GenerateLowGuid<HighGuid::Creature>(); Creature* creature = new Creature(); - if (!creature->Create(lowGuid, map, entry, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), nullptr, vehId)) + if (!creature->Create(lowGuid, map, entry, pos, nullptr, vehId)) { delete creature; return nullptr; @@ -1125,7 +1146,7 @@ Creature* Creature::CreateCreature(uint32 entry, Map* map, Position const& pos, Creature* Creature::CreateCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap /*= true*/, bool allowDuplicate /*= false*/) { Creature* creature = new Creature(); - if (!creature->LoadCreatureFromDB(spawnId, map, addToMap, allowDuplicate)) + if (!creature->LoadFromDB(spawnId, map, addToMap, allowDuplicate)) { delete creature; return nullptr; @@ -1300,26 +1321,16 @@ void Creature::SaveToDB(uint32 mapid, std::vector<Difficulty> const& spawnDiffic dynamicflags = 0; } - // data->guid = guid must not be updated at save + if (!data.spawnId) + data.spawnId = m_spawnId; + ASSERT(data.spawnId == m_spawnId); data.id = GetEntry(); - data.mapid = mapid; data.displayid = displayId; data.equipmentId = GetCurrentEquipmentId(); if (!GetTransport()) - { - data.posX = GetPositionX(); - data.posY = GetPositionY(); - data.posZ = GetPositionZMinusOffset(); - data.orientation = GetOrientation(); - } + data.spawnPoint.WorldRelocate(this); else - { - data.posX = GetTransOffsetX(); - data.posY = GetTransOffsetY(); - data.posZ = GetTransOffsetZ(); - data.orientation = GetTransOffsetO(); - } - + data.spawnPoint.WorldRelocate(mapid, GetTransOffsetX(), GetTransOffsetY(), GetTransOffsetZ(), GetTransOffsetO()); data.spawntimesecs = m_respawnDelay; // prevent add data integrity problems data.spawndist = GetDefaultMovementType() == IDLE_MOTION_TYPE ? 0.0f : m_respawnradius; @@ -1335,6 +1346,8 @@ void Creature::SaveToDB(uint32 mapid, std::vector<Difficulty> const& spawnDiffic data.unit_flags2 = unitFlags2; data.unit_flags3 = unitFlags3; data.dynamicflags = dynamicflags; + if (!data.spawnGroupData) + data.spawnGroupData = sObjectMgr->GetDefaultSpawnGroup(); data.phaseId = GetDBPhase() > 0 ? GetDBPhase() : data.phaseId; data.phaseGroup = GetDBPhase() < 0 ? -GetDBPhase() : data.phaseGroup; @@ -1580,7 +1593,7 @@ bool Creature::CreateFromProto(ObjectGuid::LowType guidlow, uint32 entry, Creatu return true; } -bool Creature::LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool allowDuplicate) +bool Creature::LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool allowDuplicate) { if (!allowDuplicate) { @@ -1620,31 +1633,41 @@ bool Creature::LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool ad } m_spawnId = spawnId; + + m_respawnCompatibilityMode = ((data->spawnGroupData->flags & SPAWNGROUP_FLAG_COMPATIBILITY_MODE) != 0); m_creatureData = data; m_respawnradius = data->spawndist; m_respawnDelay = data->spawntimesecs; - if (!Create(map->GenerateLowGuid<HighGuid::Creature>(), map, data->id, data->posX, data->posY, data->posZ, data->orientation, data, 0)) + + // Is the creature script objecting to us spawning? If yes, delay by a little bit (then re-check in ::Update) + if (!m_respawnCompatibilityMode && !m_respawnTime && !sScriptMgr->CanSpawn(spawnId, data->id, data, map)) + { + SaveRespawnTime(urand(4,7)); + return false; + } + + if (!Create(map->GenerateLowGuid<HighGuid::Creature>(), map, data->id, data->spawnPoint, data, 0U , !m_respawnCompatibilityMode)) return false; //We should set first home position, because then AI calls home movement - SetHomePosition(data->posX, data->posY, data->posZ, data->orientation); + SetHomePosition(data->spawnPoint); m_deathState = ALIVE; m_respawnTime = GetMap()->GetCreatureRespawnTime(m_spawnId); - // Is the creature script objecting to us spawning? If yes, delay by one second (then re-check in ::Update) - if (!m_respawnTime && !sScriptMgr->CanSpawn(spawnId, GetEntry(), GetCreatureTemplate(), GetCreatureData(), map)) - m_respawnTime = time(nullptr)+1; + // Is the creature script objecting to us spawning? If yes, delay by a little bit (then re-check in ::Update) + if (m_respawnCompatibilityMode && !m_respawnTime && !sScriptMgr->CanSpawn(spawnId, GetEntry(), GetCreatureData(), map)) + m_respawnTime = time(nullptr)+urand(4,7); if (m_respawnTime) // respawn on Update { m_deathState = DEAD; if (CanFly()) { - float tz = map->GetHeight(GetPhaseShift(), data->posX, data->posY, data->posZ, true, MAX_FALL_DISTANCE); - if (data->posZ - tz > 0.1f && Trinity::IsValidMapCoord(tz)) - Relocate(data->posX, data->posY, tz); + float tz = map->GetHeight(GetPhaseShift(), data->spawnPoint, true, MAX_FALL_DISTANCE); + if (data->spawnPoint.GetPositionZ() - tz > 0.1f && Trinity::IsValidMapCoord(tz)) + Relocate(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY(), tz); } } @@ -1653,7 +1676,7 @@ bool Creature::LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool ad // checked at creature_template loading m_defaultMovementType = MovementGeneratorType(data->movementType); - loot.SetGUID(ObjectGuid::Create<HighGuid::LootObject>(data->mapid, data->id, GetMap()->GenerateLowGuid<HighGuid::LootObject>())); + loot.SetGUID(ObjectGuid::Create<HighGuid::LootObject>(GetMapId(), data->id, GetMap()->GenerateLowGuid<HighGuid::LootObject>())); if (addToMap && !GetMap()->AddToMap(this)) return false; @@ -1742,7 +1765,7 @@ void Creature::DeleteFromDB() return; } - GetMap()->RemoveCreatureRespawnTime(m_spawnId); + GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, m_spawnId); sObjectMgr->DeleteCreatureData(m_spawnId); WorldDatabaseTransaction trans = WorldDatabase.BeginTransaction(); @@ -1751,6 +1774,11 @@ void Creature::DeleteFromDB() stmt->setUInt64(0, m_spawnId); trans->Append(stmt); + stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_SPAWNGROUP_MEMBER); + stmt->setUInt8(0, uint8(SPAWN_TYPE_CREATURE)); + stmt->setUInt64(1, m_spawnId); + trans->Append(stmt); + stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_CREATURE_ADDON); stmt->setUInt64(0, m_spawnId); trans->Append(stmt); @@ -1897,14 +1925,31 @@ void Creature::setDeathState(DeathState s) if (s == JUST_DIED) { m_corpseRemoveTime = time(nullptr) + m_corpseDelay; - if (IsDungeonBoss() && !m_respawnDelay) - m_respawnTime = std::numeric_limits<time_t>::max(); // never respawn in this instance + + uint32 respawnDelay = m_respawnDelay; + if (uint32 scalingMode = sWorld->getIntConfig(CONFIG_RESPAWN_DYNAMICMODE)) + GetMap()->ApplyDynamicModeRespawnScaling(this, m_spawnId, respawnDelay, scalingMode); + // @todo remove the boss respawn time hack in a dynspawn follow-up once we have creature groups in instances + if (m_respawnCompatibilityMode) + { + if (IsDungeonBoss() && !m_respawnDelay) + m_respawnTime = std::numeric_limits<time_t>::max(); // never respawn in this instance + else + m_respawnTime = time(nullptr) + respawnDelay + m_corpseDelay; + } else - m_respawnTime = time(nullptr) + m_respawnDelay + m_corpseDelay; + { + if (IsDungeonBoss() && !m_respawnDelay) + m_respawnTime = std::numeric_limits<time_t>::max(); // never respawn in this instance + else + m_respawnTime = time(nullptr) + respawnDelay; + } // always save boss respawn time at death to prevent crash cheating if (sWorld->getBoolConfig(CONFIG_SAVE_RESPAWN_TIME_IMMEDIATELY) || isWorldBoss()) SaveRespawnTime(); + else if (!m_respawnCompatibilityMode) + SaveRespawnTime(0, false); ReleaseFocus(nullptr, false); // remove spellcast focus DoNotReacquireTarget(); // cancel delayed re-target @@ -1980,8 +2025,6 @@ void Creature::setDeathState(DeathState s) void Creature::Respawn(bool force) { - DestroyForNearbyPlayers(); - if (force) { if (IsAlive()) @@ -1990,51 +2033,62 @@ void Creature::Respawn(bool force) setDeathState(CORPSE); } - RemoveCorpse(false, false); - - if (getDeathState() == DEAD) + if (m_respawnCompatibilityMode) { - if (m_spawnId) - GetMap()->RemoveCreatureRespawnTime(m_spawnId); + DestroyForNearbyPlayers(); + RemoveCorpse(false, false); - TC_LOG_DEBUG("entities.unit", "Respawning creature %s (%s)", - GetName().c_str(), GetGUID().ToString().c_str()); - m_respawnTime = 0; - ResetPickPocketRefillTimer(); - loot.clear(); + if (getDeathState() == DEAD) + { + if (m_spawnId) + GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, m_spawnId); + + TC_LOG_DEBUG("entities.unit", "Respawning creature %s (%s)", GetName().c_str(), GetGUID().ToString().c_str()); + m_respawnTime = 0; + ResetPickPocketRefillTimer(); + loot.clear(); + + if (m_originalEntry != GetEntry()) + UpdateEntry(m_originalEntry); - if (m_originalEntry != GetEntry()) - UpdateEntry(m_originalEntry); - else SelectLevel(); - setDeathState(JUST_RESPAWNED); + setDeathState(JUST_RESPAWNED); - CreatureModel display(GetNativeDisplayId(), GetNativeDisplayScale(), 1.0f); - if (sObjectMgr->GetCreatureModelRandomGender(&display, GetCreatureTemplate())) - { - SetDisplayId(display.CreatureDisplayID, display.DisplayScale); - SetNativeDisplayId(display.CreatureDisplayID, display.DisplayScale); - } + CreatureModel display(GetNativeDisplayId(), GetNativeDisplayScale(), 1.0f); + if (sObjectMgr->GetCreatureModelRandomGender(&display, GetCreatureTemplate())) + { + SetDisplayId(display.CreatureDisplayID, display.DisplayScale); + SetNativeDisplayId(display.CreatureDisplayID, display.DisplayScale); + } - GetMotionMaster()->InitDefault(); - //Re-initialize reactstate that could be altered by movementgenerators - InitializeReactState(); + GetMotionMaster()->InitDefault(); + //Re-initialize reactstate that could be altered by movementgenerators + InitializeReactState(); - //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(); - m_TriggerJustRespawned = true;//delay event to next tick so all creatures are created on the map before processing - } + //Call AI respawn virtual function//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(); + m_TriggerJustRespawned = true;//delay event to next tick so all creatures are created on the map before processing + } - uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool<Creature>(GetSpawnId()) : 0; - if (poolid) - sPoolMgr->UpdatePool<Creature>(poolid, GetSpawnId()); + uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool<Creature>(GetSpawnId()) : 0; + if (poolid) + sPoolMgr->UpdatePool<Creature>(poolid, GetSpawnId()); + } + UpdateObjectVisibility(); + } + else + { + if (m_spawnId) + GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, m_spawnId, true); } - UpdateObjectVisibility(); + TC_LOG_DEBUG("entities.unit", "Respawning creature %s (%s)", + GetName().c_str(), GetGUID().ToString().c_str()); + } void Creature::ForcedDespawn(uint32 timeMSToDespawn, Seconds const& forceRespawnTimer) @@ -2045,30 +2099,48 @@ void Creature::ForcedDespawn(uint32 timeMSToDespawn, Seconds const& forceRespawn return; } - uint32 corpseDelay = GetCorpseDelay(); - uint32 respawnDelay = GetRespawnDelay(); + if (m_respawnCompatibilityMode) + { + uint32 corpseDelay = GetCorpseDelay(); + uint32 respawnDelay = GetRespawnDelay(); + + // do it before killing creature + DestroyForNearbyPlayers(); + + bool overrideRespawnTime = false; + if (IsAlive()) + { + if (forceRespawnTimer > Seconds::zero()) + { + SetCorpseDelay(0); + SetRespawnDelay(forceRespawnTimer.count()); + overrideRespawnTime = true; + } + + setDeathState(JUST_DIED); + } - // do it before killing creature - DestroyForNearbyPlayers(); + // Skip corpse decay time + RemoveCorpse(!overrideRespawnTime, false); - bool overrideRespawnTime = false; - if (IsAlive()) + SetCorpseDelay(corpseDelay); + SetRespawnDelay(respawnDelay); + } + else { if (forceRespawnTimer > Seconds::zero()) + SaveRespawnTime(forceRespawnTimer.count()); + else { - SetCorpseDelay(0); - SetRespawnDelay(forceRespawnTimer.count()); - overrideRespawnTime = false; + uint32 respawnDelay = m_respawnDelay; + if (uint32 scalingMode = sWorld->getIntConfig(CONFIG_RESPAWN_DYNAMICMODE)) + GetMap()->ApplyDynamicModeRespawnScaling(this, m_spawnId, respawnDelay, scalingMode); + m_respawnTime = time(NULL) + respawnDelay; + SaveRespawnTime(); } - setDeathState(JUST_DIED); + AddObjectToRemoveList(); } - - // Skip corpse decay time - RemoveCorpse(overrideRespawnTime, false); - - SetCorpseDelay(corpseDelay); - SetRespawnDelay(respawnDelay); } void Creature::DespawnOrUnsummon(uint32 msTimeToDespawn /*= 0*/, Seconds const& forceRespawnTimer /*= 0*/) @@ -2429,12 +2501,19 @@ bool Creature::_IsTargetAcceptable(Unit const* target) const return false; } -void Creature::SaveRespawnTime() +void Creature::SaveRespawnTime(uint32 forceDelay, bool savetodb) { if (IsSummon() || !m_spawnId || (m_creatureData && !m_creatureData->dbData)) return; - GetMap()->SaveCreatureRespawnTime(m_spawnId, m_respawnTime); + if (m_respawnCompatibilityMode) + { + GetMap()->SaveRespawnTimeDB(SPAWN_TYPE_CREATURE, m_spawnId, m_respawnTime); + return; + } + + uint32 thisRespawnTime = forceDelay ? time(NULL) + forceDelay : m_respawnTime; + GetMap()->SaveRespawnTime(SPAWN_TYPE_CREATURE, m_spawnId, GetEntry(), thisRespawnTime, GetMap()->GetZoneId(GetPhaseShift(), GetHomePosition()), Trinity::ComputeGridCoord(GetHomePosition().GetPositionX(), GetHomePosition().GetPositionY()).GetId(), m_creatureData->dbData && savetodb); } // this should not be called by petAI or @@ -2644,32 +2723,26 @@ time_t Creature::GetRespawnTimeEx() const void Creature::GetRespawnPosition(float &x, float &y, float &z, float* ori, float* dist) const { - // for npcs on transport, this will return transport offset - if (m_spawnId) + if (m_creatureData) { - if (CreatureData const* data = sObjectMgr->GetCreatureData(GetSpawnId())) - { - x = data->posX; - y = data->posY; - z = data->posZ; - if (ori) - *ori = data->orientation; - if (dist) - *dist = data->spawndist; - - return; - } - } + if (ori) + m_creatureData->spawnPoint.GetPosition(x, y, z, *ori); + else + m_creatureData->spawnPoint.GetPosition(x, y, z); - // changed this from current position to home position, fixes world summons with infinite duration (wg npcs for example) - Position homePos = GetHomePosition(); - x = homePos.GetPositionX(); - y = homePos.GetPositionY(); - z = homePos.GetPositionZ(); - if (ori) - *ori = homePos.GetOrientation(); - if (dist) - *dist = 0; + if (dist) + *dist = m_creatureData->spawndist; + } + else + { + Position const& homePos = GetHomePosition(); + if (ori) + homePos.GetPosition(x, y, z, *ori); + else + homePos.GetPosition(x, y, z); + if (dist) + *dist = 0; + } } void Creature::AllLootRemovedFromCorpse() @@ -2812,7 +2885,7 @@ std::string Creature::GetScriptName() const uint32 Creature::GetScriptId() const { if (CreatureData const* creatureData = GetCreatureData()) - if (uint32 scriptId = creatureData->ScriptId) + if (uint32 scriptId = creatureData->scriptId) return scriptId; return sObjectMgr->GetCreatureTemplate(GetEntry())->ScriptID; @@ -3245,3 +3318,11 @@ bool Creature::CanGiveExperience() const && !IsTotem() && !(GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_XP_AT_KILL); } + +bool Creature::IsEscortNPC(bool onlyIfActive) +{ + if (!IsAIEnabled) + return false; + + return AI()->IsEscortNPC(onlyIfActive); +} diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index b897454e6ad..a693b00d026 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -73,7 +73,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma void DisappearAndDie(); - bool Create(ObjectGuid::LowType guidlow, Map* map, uint32 entry, float x, float y, float z, float ang, CreatureData const* data, uint32 vehId); + bool Create(ObjectGuid::LowType guidlow, Map* map, uint32 entry, Position const& pos, CreatureData const* data, uint32 vehId, bool dynamic = false); static Creature* CreateCreature(uint32 entry, Map* map, Position const& pos, uint32 vehId = 0); static Creature* CreateCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap = true, bool allowDuplicate = false); @@ -88,7 +88,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma void Update(uint32 time) override; // overwrited Unit::Update void GetRespawnPosition(float &x, float &y, float &z, float* ori = nullptr, float* dist = nullptr) const; - bool IsSpawnedOnTransport() const { return m_creatureData && m_creatureData->mapid != GetMapId(); } + bool IsSpawnedOnTransport() const { return m_creatureData && m_creatureData->spawnPoint.GetMapId() != GetMapId(); } void SetCorpseDelay(uint32 delay) { m_corpseDelay = delay; } uint32 GetCorpseDelay() const { return m_corpseDelay; } @@ -181,10 +181,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma void setDeathState(DeathState s) override; // override virtual Unit::setDeathState - bool LoadFromDB(ObjectGuid::LowType spawnId, Map* map) { return LoadCreatureFromDB(spawnId, map, false, false); } - private: - bool LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool allowDuplicate); - public: + bool LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool allowDuplicate); void SaveToDB(); // overriden in Pet virtual void SaveToDB(uint32 mapid, std::vector<Difficulty> const& spawnDifficulties); @@ -246,7 +243,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma time_t GetRespawnTimeEx() const; void SetRespawnTime(uint32 respawn) { m_respawnTime = respawn ? time(nullptr) + respawn : 0; } void Respawn(bool force = false); - void SaveRespawnTime() override; + void SaveRespawnTime(uint32 forceDelay = 0, bool savetodb = true) override; uint32 GetRespawnDelay() const { return m_respawnDelay; } void SetRespawnDelay(uint32 delay) { m_respawnDelay = delay; } @@ -314,6 +311,10 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma uint32 GetOriginalEntry() const { return m_originalEntry; } void SetOriginalEntry(uint32 entry) { m_originalEntry = entry; } + // There's many places not ready for dynamic spawns. This allows them to live on for now. + void SetRespawnCompatibilityMode(bool mode = true) { m_respawnCompatibilityMode = mode; } + bool GetRespawnCompatibilityMode() { return m_respawnCompatibilityMode; } + static float _GetDamageMod(int32 Rank); float m_SightDistance, m_CombatDistance; @@ -337,6 +338,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma CreatureTextRepeatIds GetTextRepeatGroup(uint8 textGroup); void SetTextRepeatId(uint8 textGroup, uint8 id); void ClearTextRepeatGroup(uint8 textGroup); + bool IsEscortNPC(bool onlyIfActive = true); bool CanGiveExperience() const; @@ -405,6 +407,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma //Formation var CreatureGroup* m_formation; bool m_TriggerJustRespawned; + bool m_respawnCompatibilityMode; /* Spell focus system */ Spell const* m_focusSpell; // Locks the target during spell cast for proper facing diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index 3f86b49f0aa..0fc310f1fce 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -21,11 +21,11 @@ #include "DBCEnums.h" #include "Optional.h" #include "SharedDefines.h" +#include "SpawnData.h" #include "UnitDefines.h" #include "WorldPacket.h" #include <string> #include <unordered_map> -#include <vector> #include <cmath> struct ItemTemplate; @@ -519,39 +519,21 @@ struct EquipmentInfo }; // from `creature` table -struct CreatureData +struct CreatureData : public SpawnData { - CreatureData() : id(0), mapid(0), displayid(0), equipmentId(0), - posX(0.0f), posY(0.0f), posZ(0.0f), orientation(0.0f), spawntimesecs(0), - spawndist(0.0f), currentwaypoint(0), curhealth(0), curmana(0), movementType(0), - spawnDifficulties(), npcflag(0), unit_flags(0), unit_flags2(0), unit_flags3(0), dynamicflags(0), - phaseUseFlags(0), phaseId(0), phaseGroup(0), terrainSwapMap(-1), ScriptId(0), dbData(true) { } - uint32 id; // entry in creature_template - uint16 mapid; - uint32 displayid; - int8 equipmentId; - float posX; - float posY; - float posZ; - float orientation; - uint32 spawntimesecs; - float spawndist; - uint32 currentwaypoint; - uint32 curhealth; - uint32 curmana; - uint8 movementType; - std::vector<Difficulty> spawnDifficulties; + CreatureData() : SpawnData(SPAWN_TYPE_CREATURE) { } + uint32 displayid = 0; + int8 equipmentId = 0; + float spawndist = 0.0f; + uint32 currentwaypoint = 0; + uint32 curhealth = 0; + uint32 curmana = 0; + uint8 movementType = 0; uint64 npcflag; - uint32 unit_flags; // enum UnitFlags mask values - uint32 unit_flags2; // enum UnitFlags2 mask values - uint32 unit_flags3; // enum UnitFlags3 mask values - uint32 dynamicflags; - uint8 phaseUseFlags; - uint32 phaseId; - uint32 phaseGroup; - int32 terrainSwapMap; - uint32 ScriptId; - bool dbData; + uint32 unit_flags = 0; // enum UnitFlags mask values + uint32 unit_flags2 = 0; // enum UnitFlags2 mask values + uint32 unit_flags3 = 0; // enum UnitFlags3 mask values + uint32 dynamicflags = 0; }; struct CreatureModelInfo diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index 921fc20f5e7..3fc26262ede 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -107,7 +107,7 @@ QuaternionData QuaternionData::fromEulerAnglesZYX(float Z, float Y, float X) } GameObject::GameObject() : WorldObject(false), MapObject(), - m_model(nullptr), m_goValue(), m_AI(nullptr), _animKitId(0), _worldEffectID(0) + m_model(nullptr), m_goValue(), m_AI(nullptr), m_respawnCompatibilityMode(false), _animKitId(0), _worldEffectID(0) { m_objectType |= TYPEMASK_GAMEOBJECT; m_objectTypeId = TYPEID_GAMEOBJECT; @@ -245,7 +245,7 @@ void GameObject::RemoveFromWorld() } } -bool GameObject::Create(uint32 entry, Map* map, Position const& pos, QuaternionData const& rotation, uint32 animProgress, GOState goState, uint32 artKit) +bool GameObject::Create(uint32 entry, Map* map, Position const& pos, QuaternionData const& rotation, uint32 animProgress, GOState goState, uint32 artKit, bool dynamic, ObjectGuid::LowType spawnid) { ASSERT(map); SetMap(map); @@ -258,6 +258,10 @@ bool GameObject::Create(uint32 entry, Map* map, Position const& pos, QuaternionD return false; } + // Set if this object can handle dynamic spawns + if (!dynamic) + SetRespawnCompatibilityMode(); + UpdatePositionData(); SetZoneScript(); @@ -442,6 +446,9 @@ bool GameObject::Create(uint32 entry, Map* map, Position const& pos, QuaternionD if (map->Is25ManRaid()) loot.maxDuplicates = 3; + if (spawnid) + m_spawnId = spawnid; + if (uint32 linkedEntry = GetGOInfo()->GetLinkedGameObjectEntry()) { if (GameObject* linkedGo = GameObject::CreateGameObject(linkedEntry, map, pos, rotation, 255, GO_STATE_READY)) @@ -474,7 +481,7 @@ GameObject* GameObject::CreateGameObject(uint32 entry, Map* map, Position const& return nullptr; GameObject* go = new GameObject(); - if (!go->Create(entry, map, pos, rotation, animProgress, goState, artKit)) + if (!go->Create(entry, map, pos, rotation, animProgress, goState, artKit, false, 0)) { delete go; return nullptr; @@ -486,7 +493,7 @@ GameObject* GameObject::CreateGameObject(uint32 entry, Map* map, Position const& GameObject* GameObject::CreateGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap /*= true*/) { GameObject* go = new GameObject(); - if (!go->LoadGameObjectFromDB(spawnId, map, addToMap)) + if (!go->LoadFromDB(spawnId, map, addToMap)) { delete go; return nullptr; @@ -603,81 +610,89 @@ void GameObject::Update(uint32 diff) /* fallthrough */ case GO_READY: { - if (m_respawnTime > 0) // timer on + if (m_respawnCompatibilityMode) { - time_t now = time(nullptr); - if (m_respawnTime <= now) // timer expired + if (m_respawnTime > 0) // timer on { - ObjectGuid dbtableHighGuid = ObjectGuid::Create<HighGuid::GameObject>(GetMapId(), GetEntry(), m_spawnId); - time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid); - if (linkedRespawntime) // Can't respawn, the master is dead + time_t now = time(nullptr); + if (m_respawnTime <= now) // timer expired { - ObjectGuid targetGuid = sObjectMgr->GetLinkedRespawnGuid(dbtableHighGuid); - if (targetGuid == dbtableHighGuid) // if linking self, never respawn (check delayed to next day) - SetRespawnTime(DAY); - else - m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime) + urand(5, MINUTE); // else copy time from master and add a little - SaveRespawnTime(); // also save to DB immediately - return; - } + ObjectGuid dbtableHighGuid = ObjectGuid::Create<HighGuid::GameObject>(GetMapId(), GetEntry(), m_spawnId); + time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid); + if (linkedRespawntime) // Can't respawn, the master is dead + { + ObjectGuid targetGuid = sObjectMgr->GetLinkedRespawnGuid(dbtableHighGuid); + if (targetGuid == dbtableHighGuid) // if linking self, never respawn + SetRespawnTime(WEEK); + else + m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime) + urand(5, MINUTE); // else copy time from master and add a little + SaveRespawnTime(); // also save to DB immediately + return; + } - m_respawnTime = 0; - m_SkillupList.clear(); - m_usetimes = 0; + m_respawnTime = 0; + m_SkillupList.clear(); + m_usetimes = 0; - // If nearby linked trap exists, respawn it - if (GameObject* linkedTrap = GetLinkedTrap()) - linkedTrap->SetLootState(GO_READY); + // If nearby linked trap exists, respawn it + if (GameObject* linkedTrap = GetLinkedTrap()) + linkedTrap->SetLootState(GO_READY); - switch (GetGoType()) - { - case GAMEOBJECT_TYPE_FISHINGNODE: // can't fish now + switch (GetGoType()) { - Unit* caster = GetOwner(); - if (caster && caster->GetTypeId() == TYPEID_PLAYER) + case GAMEOBJECT_TYPE_FISHINGNODE: // can't fish now { - caster->ToPlayer()->RemoveGameObject(this, false); - caster->ToPlayer()->SendDirectMessage(WorldPackets::GameObject::FishEscaped().Write()); + Unit* caster = GetOwner(); + if (caster && caster->GetTypeId() == TYPEID_PLAYER) + { + caster->ToPlayer()->RemoveGameObject(this, false); + + caster->ToPlayer()->SendDirectMessage(WorldPackets::GameObject::FishEscaped().Write()); + } + // can be delete + m_lootState = GO_JUST_DEACTIVATED; + return; } - // can be delete - m_lootState = GO_JUST_DEACTIVATED; - return; + case GAMEOBJECT_TYPE_DOOR: + case GAMEOBJECT_TYPE_BUTTON: + // We need to open doors if they are closed (add there another condition if this code breaks some usage, but it need to be here for battlegrounds) + if (GetGoState() != GO_STATE_READY) + ResetDoorOrButton(); + break; + case GAMEOBJECT_TYPE_FISHINGHOLE: + // Initialize a new max fish count on respawn + m_goValue.FishingHole.MaxOpens = urand(GetGOInfo()->fishingHole.minRestock, GetGOInfo()->fishingHole.maxRestock); + break; + default: + break; } - case GAMEOBJECT_TYPE_DOOR: - case GAMEOBJECT_TYPE_BUTTON: - // We need to open doors if they are closed (add there another condition if this code breaks some usage, but it need to be here for battlegrounds) - if (GetGoState() != GO_STATE_READY) - ResetDoorOrButton(); - break; - case GAMEOBJECT_TYPE_FISHINGHOLE: - // Initialize a new max fish count on respawn - m_goValue.FishingHole.MaxOpens = urand(GetGOInfo()->fishingHole.minRestock, GetGOInfo()->fishingHole.maxRestock); - break; - default: - break; - } - // Despawn timer - if (!m_spawnedByDefault) - { - // Can be despawned or destroyed - SetLootState(GO_JUST_DEACTIVATED); - return; - } + // Despawn timer + if (!m_spawnedByDefault) + { + // Can be despawned or destroyed + SetLootState(GO_JUST_DEACTIVATED); + return; + } - // Call AI Reset (required for example in SmartAI to clear one time events) - if (AI()) - AI()->Reset(); + // Call AI Reset (required for example in SmartAI to clear one time events) + if (AI()) + AI()->Reset(); - // Respawn timer - uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool<GameObject>(GetSpawnId()) : 0; - if (poolid) - sPoolMgr->UpdatePool<GameObject>(poolid, GetSpawnId()); - else - GetMap()->AddToMap(this); + // Respawn timer + uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool<GameObject>(GetSpawnId()) : 0; + if (poolid) + sPoolMgr->UpdatePool<GameObject>(poolid, GetSpawnId()); + else + GetMap()->AddToMap(this); + } } } + // Set respawn timer + if (!m_respawnCompatibilityMode && m_respawnTime > 0) + SaveRespawnTime(0, false); + if (isSpawned()) { GameObjectTemplate const* goInfo = GetGOInfo(); @@ -868,6 +883,7 @@ void GameObject::Update(uint32 diff) if (!m_respawnDelayTime) return; + // ToDo: Decide if we should properly despawn these. Maybe they expect to be able to manually respawn from script? if (!m_spawnedByDefault) { m_respawnTime = 0; @@ -875,12 +891,28 @@ void GameObject::Update(uint32 diff) return; } - m_respawnTime = time(nullptr) + m_respawnDelayTime; + uint32 respawnDelay = m_respawnDelayTime; + if (uint32 scalingMode = sWorld->getIntConfig(CONFIG_RESPAWN_DYNAMICMODE)) + GetMap()->ApplyDynamicModeRespawnScaling(this, this->m_spawnId, respawnDelay, scalingMode); + m_respawnTime = time(nullptr) + respawnDelay; // if option not set then object will be saved at grid unload + // Otherwise just save respawn time to map object memory if (sWorld->getBoolConfig(CONFIG_SAVE_RESPAWN_TIME_IMMEDIATELY)) SaveRespawnTime(); + if (!m_respawnCompatibilityMode) + { + // Respawn time was just saved if set to save to DB + // If not, we save only to map memory + if (!sWorld->getBoolConfig(CONFIG_SAVE_RESPAWN_TIME_IMMEDIATELY)) + SaveRespawnTime(0, false); + + // Then despawn + AddObjectToRemoveList(); + return; + } + DestroyForNearbyPlayers(); // old UpdateObjectVisibility() break; @@ -974,7 +1006,7 @@ void GameObject::SaveToDB() { // this should only be used when the gameobject has already been loaded // preferably after adding to map, because mapid may not be valid otherwise - GameObjectData const* data = sObjectMgr->GetGOData(m_spawnId); + GameObjectData const* data = sObjectMgr->GetGameObjectData(m_spawnId); if (!data) { TC_LOG_ERROR("misc", "GameObject::SaveToDB failed, cannot get gameobject data!"); @@ -995,21 +1027,21 @@ void GameObject::SaveToDB(uint32 mapid, std::vector<Difficulty> const& spawnDiff m_spawnId = sObjectMgr->GenerateGameObjectSpawnId(); // update in loaded data (changing data only in this place) - GameObjectData& data = sObjectMgr->NewGOData(m_spawnId); + GameObjectData& data = sObjectMgr->NewOrExistGameObjectData(m_spawnId); - // data->guid = guid must not be updated at save + if (!data.spawnId) + data.spawnId = m_spawnId; + ASSERT(data.spawnId == m_spawnId); data.id = GetEntry(); - data.mapid = mapid; - data.posX = GetPositionX(); - data.posY = GetPositionY(); - data.posZ = GetPositionZ(); - data.orientation = GetOrientation(); + data.spawnPoint.WorldRelocate(this); data.rotation = m_worldRotation; data.spawntimesecs = m_spawnedByDefault ? m_respawnDelayTime : -(int32)m_respawnDelayTime; data.animprogress = GetGoAnimProgress(); - data.go_state = GetGoState(); + data.goState = GetGoState(); data.spawnDifficulties = spawnDifficulties; data.artKit = GetGoArtKit(); + if (!data.spawnGroupData) + data.spawnGroupData = sObjectMgr->GetDefaultSpawnGroup(); data.phaseId = GetDBPhase() > 0 ? GetDBPhase() : data.phaseId; data.phaseGroup = GetDBPhase() < 0 ? -GetDBPhase() : data.phaseGroup; @@ -1059,9 +1091,9 @@ void GameObject::SaveToDB(uint32 mapid, std::vector<Difficulty> const& spawnDiff WorldDatabase.CommitTransaction(trans); } -bool GameObject::LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap) +bool GameObject::LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool) { - GameObjectData const* data = sObjectMgr->GetGOData(spawnId); + GameObjectData const* data = sObjectMgr->GetGameObjectData(spawnId); if (!data) { TC_LOG_ERROR("sql.sql", "Gameobject (GUID: " UI64FMTD ") not found in table `gameobject`, can't load. ", spawnId); @@ -1070,14 +1102,14 @@ bool GameObject::LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, boo uint32 entry = data->id; //uint32 map_id = data->mapid; // already used before call - Position pos(data->posX, data->posY, data->posZ, data->orientation); uint32 animprogress = data->animprogress; - GOState go_state = data->go_state; + GOState go_state = data->goState; uint32 artKit = data->artKit; m_spawnId = spawnId; - if (!Create(entry, map, pos, data->rotation, animprogress, go_state, artKit)) + m_respawnCompatibilityMode = ((data->spawnGroupData->flags & SPAWNGROUP_FLAG_COMPATIBILITY_MODE) != 0); + if (!Create(entry, map, data->spawnPoint, data->rotation, animprogress, go_state, artKit, !m_respawnCompatibilityMode, spawnId)) return false; PhasingHandler::InitDbPhaseShift(GetPhaseShift(), data->phaseUseFlags, data->phaseId, data->phaseGroup); @@ -1102,7 +1134,7 @@ bool GameObject::LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, boo if (m_respawnTime && m_respawnTime <= time(nullptr)) { m_respawnTime = 0; - GetMap()->RemoveGORespawnTime(m_spawnId); + GetMap()->RemoveRespawnTime(SPAWN_TYPE_GAMEOBJECT, m_spawnId); } } } @@ -1123,20 +1155,25 @@ bool GameObject::LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, boo void GameObject::DeleteFromDB() { - GetMap()->RemoveGORespawnTime(m_spawnId); - sObjectMgr->DeleteGOData(m_spawnId); + GetMap()->RemoveRespawnTime(SPAWN_TYPE_GAMEOBJECT, m_spawnId); + sObjectMgr->DeleteGameObjectData(m_spawnId); - WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT); + WorldDatabaseTransaction trans = WorldDatabase.BeginTransaction(); + WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT); stmt->setUInt64(0, m_spawnId); + trans->Append(stmt); - WorldDatabase.Execute(stmt); + stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_SPAWNGROUP_MEMBER); + stmt->setUInt8(0, uint8(SPAWN_TYPE_GAMEOBJECT)); + stmt->setUInt64(1, m_spawnId); + trans->Append(stmt); stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_EVENT_GAMEOBJECT); - stmt->setUInt64(0, m_spawnId); + trans->Append(stmt); - WorldDatabase.Execute(stmt); + WorldDatabase.CommitTransaction(trans); } /*********************************************************/ @@ -1199,10 +1236,19 @@ Unit* GameObject::GetOwner() const return ObjectAccessor::GetUnit(*this, GetOwnerGUID()); } -void GameObject::SaveRespawnTime() +void GameObject::SaveRespawnTime(uint32 forceDelay, bool savetodb) { - if (m_goData && m_goData->dbData && m_respawnTime > time(nullptr) && m_spawnedByDefault) - GetMap()->SaveGORespawnTime(m_spawnId, m_respawnTime); + if (m_goData && m_respawnTime > time(nullptr) && m_spawnedByDefault) + { + if (m_respawnCompatibilityMode) + { + GetMap()->SaveRespawnTimeDB(SPAWN_TYPE_GAMEOBJECT, m_spawnId, m_respawnTime); + return; + } + + uint32 thisRespawnTime = forceDelay ? time(nullptr) + forceDelay : m_respawnTime; + GetMap()->SaveRespawnTime(SPAWN_TYPE_GAMEOBJECT, m_spawnId, GetEntry(), thisRespawnTime, GetZoneId(), Trinity::ComputeGridCoord(GetPositionX(), GetPositionY()).GetId(), m_goData->dbData ? savetodb : false); + } } bool GameObject::IsNeverVisibleFor(WorldObject const* seer) const @@ -1270,7 +1316,7 @@ void GameObject::Respawn() if (m_spawnedByDefault && m_respawnTime > 0) { m_respawnTime = time(nullptr); - GetMap()->RemoveGORespawnTime(m_spawnId); + GetMap()->RemoveRespawnTime(SPAWN_TYPE_GAMEOBJECT, m_spawnId, true); } } @@ -1374,7 +1420,7 @@ void GameObject::UseDoorOrButton(uint32 time_to_restore, bool alternative /* = f void GameObject::SetGoArtKit(uint8 kit) { SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::ArtKit), kit); - GameObjectData* data = const_cast<GameObjectData*>(sObjectMgr->GetGOData(m_spawnId)); + GameObjectData* data = const_cast<GameObjectData*>(sObjectMgr->GetGameObjectData(m_spawnId)); if (data) data->artKit = kit; } @@ -1385,10 +1431,10 @@ void GameObject::SetGoArtKit(uint8 artkit, GameObject* go, ObjectGuid::LowType l if (go) { go->SetGoArtKit(artkit); - data = go->GetGOData(); + data = go->GetGameObjectData(); } else if (lowguid) - data = sObjectMgr->GetGOData(lowguid); + data = sObjectMgr->GetGameObjectData(lowguid); if (data) const_cast<GameObjectData*>(data)->artKit = artkit; @@ -2177,8 +2223,8 @@ void GameObject::EventInform(uint32 eventId, WorldObject* invoker /*= nullptr*/) uint32 GameObject::GetScriptId() const { - if (GameObjectData const* gameObjectData = GetGOData()) - if (uint32 scriptId = gameObjectData->ScriptId) + if (GameObjectData const* gameObjectData = GetGameObjectData()) + if (uint32 scriptId = gameObjectData->scriptId) return scriptId; return GetGOInfo()->ScriptId; @@ -2636,24 +2682,20 @@ void GameObject::ClearUpdateMask(bool remove) void GameObject::GetRespawnPosition(float &x, float &y, float &z, float* ori /* = nullptr*/) const { - if (m_spawnId) + if (m_goData) { - if (GameObjectData const* data = sObjectMgr->GetGOData(GetSpawnId())) - { - x = data->posX; - y = data->posY; - z = data->posZ; - if (ori) - *ori = data->orientation; - return; - } + if (ori) + m_goData->spawnPoint.GetPosition(x, y, z, *ori); + else + m_goData->spawnPoint.GetPosition(x, y, z); + } + else + { + if (ori) + GetPosition(x, y, z, *ori); + else + GetPosition(x, y, z); } - - x = GetPositionX(); - y = GetPositionY(); - z = GetPositionZ(); - if (ori) - *ori = GetOrientation(); } float GameObject::GetInteractionDistance() const diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h index 315210ce2e9..d49a8363e51 100644 --- a/src/server/game/Entities/GameObject/GameObject.h +++ b/src/server/game/Entities/GameObject/GameObject.h @@ -98,7 +98,7 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> void CleanupsBeforeDelete(bool finalCleanup = true) override; private: - bool Create(uint32 entry, Map* map, Position const& pos, QuaternionData const& rotation, uint32 animProgress, GOState goState, uint32 artKit); + bool Create(uint32 entry, Map* map, Position const& pos, QuaternionData const& rotation, uint32 animProgress, GOState goState, uint32 artKit, bool dynamic, ObjectGuid::LowType spawnid); public: static GameObject* CreateGameObject(uint32 entry, Map* map, Position const& pos, QuaternionData const& rotation, uint32 animProgress, GOState goState, uint32 artKit = 0); static GameObject* CreateGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap = true); @@ -106,7 +106,7 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> void Update(uint32 p_time) override; GameObjectTemplate const* GetGOInfo() const { return m_goInfo; } GameObjectTemplateAddon const* GetTemplateAddon() const { return m_goTemplateAddon; } - GameObjectData const* GetGOData() const { return m_goData; } + GameObjectData const* GetGameObjectData() const { return m_goData; } GameObjectValue const* GetGOValue() const { return &m_goValue; } bool IsTransport() const; @@ -126,10 +126,7 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> void SaveToDB(); void SaveToDB(uint32 mapid, std::vector<Difficulty> const& spawnDifficulties); - bool LoadFromDB(ObjectGuid::LowType spawnId, Map* map) { return LoadGameObjectFromDB(spawnId, map, false); } - private: - bool LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap); - public: + bool LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool = true); // arg4 is unused, only present to match the signature on Creature void DeleteFromDB(); void SetOwnerGUID(ObjectGuid owner) @@ -229,7 +226,7 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> uint32 GetUseCount() const { return m_usetimes; } uint32 GetUniqueUseCount() const { return uint32(m_unique_users.size()); } - void SaveRespawnTime() override; + void SaveRespawnTime(uint32 forceDelay = 0, bool savetodb = true) override; Loot loot; @@ -280,6 +277,10 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> void EventInform(uint32 eventId, WorldObject* invoker = nullptr); + // There's many places not ready for dynamic spawns. This allows them to live on for now. + void SetRespawnCompatibilityMode(bool mode = true) { m_respawnCompatibilityMode = mode; } + bool GetRespawnCompatibilityMode() {return m_respawnCompatibilityMode; } + uint32 GetScriptId() const; GameObjectAI* AI() const { return m_AI; } @@ -370,6 +371,7 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> } GameObjectAI* m_AI; + bool m_respawnCompatibilityMode; uint16 _animKitId; uint32 _worldEffectID; }; diff --git a/src/server/game/Entities/GameObject/GameObjectData.h b/src/server/game/Entities/GameObject/GameObjectData.h index 7477b9b5137..9b382bc84ab 100644 --- a/src/server/game/Entities/GameObject/GameObjectData.h +++ b/src/server/game/Entities/GameObject/GameObjectData.h @@ -22,9 +22,9 @@ #include "DBCEnums.h" #include "QuaternionData.h" #include "SharedDefines.h" +#include "SpawnData.h" #include "WorldPacket.h" #include <string> -#include <vector> // from `gameobject_template` struct GameObjectTemplate @@ -978,30 +978,14 @@ struct GameObjectAddon uint32 WorldEffectID; }; -// from `gameobject` -struct GameObjectData +// `gameobject` table +struct GameObjectData : public SpawnData { - explicit GameObjectData() : id(0), mapid(0), posX(0.0f), posY(0.0f), posZ(0.0f), orientation(0.0f), spawntimesecs(0), - animprogress(0), go_state(GO_STATE_ACTIVE), spawnDifficulties(), artKit(0), - phaseUseFlags(0), phaseId(0), phaseGroup(0), terrainSwapMap(-1), ScriptId(0), dbData(true) { } - uint32 id; // entry in gamobject_template - uint16 mapid; - float posX; - float posY; - float posZ; - float orientation; + GameObjectData() : SpawnData(SPAWN_TYPE_GAMEOBJECT) { } QuaternionData rotation; - int32 spawntimesecs; - uint32 animprogress; - GOState go_state; - std::vector<Difficulty> spawnDifficulties; - uint8 artKit; - uint8 phaseUseFlags; - uint32 phaseId; - uint32 phaseGroup; - int32 terrainSwapMap; - uint32 ScriptId; - bool dbData; + uint32 animprogress = 0; + GOState goState = GO_STATE_ACTIVE; + uint8 artKit = 0; }; #endif // GameObjectData_h__ diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 2a7d63156fa..3efc014ffaf 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -1742,7 +1742,7 @@ TempSummon* Map::SummonCreature(uint32 entry, Position const& pos, SummonPropert break; } - if (!summon->Create(GenerateLowGuid<HighGuid::Creature>(), this, entry, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), nullptr, vehId)) + if (!summon->Create(GenerateLowGuid<HighGuid::Creature>(), this, entry, pos, nullptr, vehId)) { delete summon; return nullptr; diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index 29a7e970da0..865d4095bff 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -474,7 +474,7 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation void PlayDirectSound(uint32 soundId, Player* target = nullptr); void PlayDirectMusic(uint32 musicId, Player* target = nullptr); - virtual void SaveRespawnTime() { } + virtual void SaveRespawnTime(uint32 /*forceDelay*/ = 0, bool /*saveToDB*/ = true) { } void AddObjectToRemoveList(); float GetGridActivationRange() const; diff --git a/src/server/game/Entities/Object/ObjectGuid.cpp b/src/server/game/Entities/Object/ObjectGuid.cpp index 7806c7fb897..fd468f4e6ea 100644 --- a/src/server/game/Entities/Object/ObjectGuid.cpp +++ b/src/server/game/Entities/Object/ObjectGuid.cpp @@ -434,6 +434,14 @@ void ObjectGuidGeneratorBase::HandleCounterOverflow(HighGuid high) World::StopNow(ERROR_EXIT_CODE); } +void ObjectGuidGeneratorBase::CheckGuidTrigger(ObjectGuid::LowType guidlow) +{ + if (!sWorld->IsGuidAlert() && guidlow > sWorld->getIntConfig(CONFIG_RESPAWN_GUIDALERTLEVEL)) + sWorld->TriggerGuidAlert(); + else if (!sWorld->IsGuidWarning() && guidlow > sWorld->getIntConfig(CONFIG_RESPAWN_GUIDWARNLEVEL)) + sWorld->TriggerGuidWarning(); +} + template class TC_GAME_API ObjectGuidGenerator<HighGuid::Null>; template class TC_GAME_API ObjectGuidGenerator<HighGuid::Uniq>; template class TC_GAME_API ObjectGuidGenerator<HighGuid::Player>; diff --git a/src/server/game/Entities/Object/ObjectGuid.h b/src/server/game/Entities/Object/ObjectGuid.h index c0ed13122e4..bd23084e19e 100644 --- a/src/server/game/Entities/Object/ObjectGuid.h +++ b/src/server/game/Entities/Object/ObjectGuid.h @@ -380,13 +380,14 @@ public: ObjectGuidGeneratorBase(ObjectGuid::LowType start = UI64LIT(1)) : _nextGuid(start) { } virtual ~ObjectGuidGeneratorBase() = default; - virtual void Set(uint64 val) { _nextGuid = val; } + virtual void Set(ObjectGuid::LowType val) { _nextGuid = val; } virtual ObjectGuid::LowType Generate() = 0; ObjectGuid::LowType GetNextAfterMaxUsed() const { return _nextGuid; } protected: static void HandleCounterOverflow(HighGuid high); - uint64 _nextGuid; + static void CheckGuidTrigger(ObjectGuid::LowType guid); + ObjectGuid::LowType _nextGuid; }; template<HighGuid high> @@ -399,6 +400,10 @@ public: { if (_nextGuid >= ObjectGuid::GetMaxCounter(high) - 1) HandleCounterOverflow(high); + + if (high == HighGuid::Creature || high == HighGuid::Vehicle || high == HighGuid::GameObject || high == HighGuid::Transport) + CheckGuidTrigger(_nextGuid); + return _nextGuid++; } }; diff --git a/src/server/game/Entities/Object/Position.h b/src/server/game/Entities/Object/Position.h index e2ee7f12384..84342433aa0 100644 --- a/src/server/game/Entities/Object/Position.h +++ b/src/server/game/Entities/Object/Position.h @@ -205,15 +205,9 @@ public: return GetExactDist2dSq(pos) < dist * dist; } - bool IsInDist(float x, float y, float z, float dist) const - { - return GetExactDistSq(x, y, z) < dist * dist; - } - - bool IsInDist(Position const* pos, float dist) const - { - return GetExactDistSq(pos) < dist * dist; - } + bool IsInDist(float x, float y, float z, float dist) const { return GetExactDistSq(x, y, z) < dist * dist; } + bool IsInDist(Position const& pos, float dist) const { return GetExactDistSq(pos) < dist * dist; } + bool IsInDist(Position const* pos, float dist) const { return GetExactDistSq(pos) < dist * dist; } bool IsWithinBox(const Position& center, float xradius, float yradius, float zradius) const; @@ -240,12 +234,9 @@ public: WorldLocation(uint32 mapId, Position const& position) : Position(position), m_mapId(mapId) { } - void WorldRelocate(WorldLocation const& loc) - { - m_mapId = loc.GetMapId(); - Relocate(loc); - } - + void WorldRelocate(WorldLocation const& loc) { m_mapId = loc.GetMapId(); Relocate(loc); } + void WorldRelocate(WorldLocation const* loc) { m_mapId = loc->GetMapId(); Relocate(loc); } + void WorldRelocate(uint32 mapId, Position const& pos) { m_mapId = mapId; Relocate(pos); } void WorldRelocate(uint32 mapId = MAPID_INVALID, float x = 0.f, float y = 0.f, float z = 0.f, float o = 0.f) { m_mapId = mapId; diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index c4134b4f3b5..4a441af47b1 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -7164,6 +7164,8 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea) guild->UpdateMemberData(this, GUILD_MEMBER_DATA_ZONEID, newZone); } + GetMap()->UpdatePlayerZoneStats(m_zoneUpdateId, newZone); + // group update if (GetGroup()) { diff --git a/src/server/game/Entities/Transport/Transport.cpp b/src/server/game/Entities/Transport/Transport.cpp index c5393ea21c9..f2667e84a37 100644 --- a/src/server/game/Entities/Transport/Transport.cpp +++ b/src/server/game/Entities/Transport/Transport.cpp @@ -300,16 +300,14 @@ Creature* Transport::CreateNPCPassenger(ObjectGuid::LowType guid, CreatureData c { Map* map = GetMap(); - Creature* creature = Creature::CreateCreatureFromDB(guid, map, false); + Creature* creature = Creature::CreateCreatureFromDB(guid, map, false, true); if (!creature) return nullptr; ASSERT(data); - float x = data->posX; - float y = data->posY; - float z = data->posZ; - float o = data->orientation; + float x, y, z, o; + data->spawnPoint.GetPosition(x, y, z, o); creature->SetTransport(this); creature->m_movementInfo.transport.guid = GetGUID(); @@ -355,10 +353,8 @@ GameObject* Transport::CreateGOPassenger(ObjectGuid::LowType guid, GameObjectDat ASSERT(data); - float x = data->posX; - float y = data->posY; - float z = data->posZ; - float o = data->orientation; + float x, y, z, o; + data->spawnPoint.GetPosition(x, y, z, o); go->SetTransport(this); go->m_movementInfo.transport.guid = GetGUID(); @@ -466,7 +462,7 @@ TempSummon* Transport::SummonPassenger(uint32 entry, Position const& pos, TempSu pos.GetPosition(x, y, z, o); CalculatePassengerPosition(x, y, z, &o); - if (!summon->Create(map->GenerateLowGuid<HighGuid::Creature>(), map, entry, x, y, z, o, nullptr, vehId)) + if (!summon->Create(map->GenerateLowGuid<HighGuid::Creature>(), map, entry, { x, y, z, o }, nullptr, vehId)) { delete summon; return nullptr; @@ -545,7 +541,7 @@ void Transport::LoadStaticPassengers() // GameObjects on transport guidEnd = cellItr->second.gameobjects.end(); for (CellGuidSet::const_iterator guidItr = cellItr->second.gameobjects.begin(); guidItr != guidEnd; ++guidItr) - CreateGOPassenger(*guidItr, sObjectMgr->GetGOData(*guidItr)); + CreateGOPassenger(*guidItr, sObjectMgr->GetGameObjectData(*guidItr)); } } } diff --git a/src/server/game/Events/GameEventMgr.cpp b/src/server/game/Events/GameEventMgr.cpp index 8958f62b5c7..64443d58e90 100644 --- a/src/server/game/Events/GameEventMgr.cpp +++ b/src/server/game/Events/GameEventMgr.cpp @@ -437,7 +437,7 @@ void GameEventMgr::LoadFromDB() int32 internal_event_id = mGameEvent.size() + event_id - 1; - GameObjectData const* data = sObjectMgr->GetGOData(guid); + GameObjectData const* data = sObjectMgr->GetGameObjectData(guid); if (!data) { TC_LOG_ERROR("sql.sql", "`game_event_gameobject` contains gameobject (GUID: " UI64FMTD ") not found in `gameobject` table.", guid); @@ -1148,7 +1148,7 @@ void GameEventMgr::UpdateEventNPCFlags(uint16 event_id) for (NPCFlagList::iterator itr = mGameEventNPCFlags[event_id].begin(); itr != mGameEventNPCFlags[event_id].end(); ++itr) // get the creature data from the low guid to get the entry, to be able to find out the whole guid if (CreatureData const* data = sObjectMgr->GetCreatureData(itr->first)) - creaturesByMap[data->mapid].insert(itr->first); + creaturesByMap[data->spawnPoint.GetMapId()].insert(itr->first); for (auto const& p : creaturesByMap) { @@ -1215,9 +1215,9 @@ void GameEventMgr::GameEventSpawn(int16 event_id) sObjectMgr->AddCreatureToGrid(*itr, data); // Spawn if necessary (loaded grids only) - Map* map = sMapMgr->FindMap(data->mapid, 0); + Map* map = sMapMgr->FindMap(data->spawnPoint.GetMapId(), 0); // We use spawn coords to spawn - if (map && !map->Instanceable() && map->IsGridLoaded(data->posX, data->posY)) + if (map && !map->Instanceable() && map->IsGridLoaded(data->spawnPoint)) Creature::CreateCreatureFromDB(*itr, map); } } @@ -1232,14 +1232,14 @@ void GameEventMgr::GameEventSpawn(int16 event_id) for (GuidList::iterator itr = mGameEventGameobjectGuids[internal_event_id].begin(); itr != mGameEventGameobjectGuids[internal_event_id].end(); ++itr) { // Add to correct cell - if (GameObjectData const* data = sObjectMgr->GetGOData(*itr)) + if (GameObjectData const* data = sObjectMgr->GetGameObjectData(*itr)) { sObjectMgr->AddGameobjectToGrid(*itr, data); // Spawn if necessary (loaded grids only) // this base map checked as non-instanced and then only existed - Map* map = sMapMgr->FindMap(data->mapid, 0); + Map* map = sMapMgr->FindMap(data->spawnPoint.GetMapId(), 0); // We use current coords to unspawn, not spawn coords since creature can have changed grid - if (map && !map->Instanceable() && map->IsGridLoaded(data->posX, data->posY)) + if (map && !map->Instanceable() && map->IsGridLoaded(data->spawnPoint)) { if (GameObject* go = GameObject::CreateGameObjectFromDB(*itr, map, false)) { @@ -1286,7 +1286,7 @@ void GameEventMgr::GameEventUnspawn(int16 event_id) { sObjectMgr->RemoveCreatureFromGrid(*itr, data); - sMapMgr->DoForAllMapsWithMapId(data->mapid, [&itr](Map* map) + sMapMgr->DoForAllMapsWithMapId(data->spawnPoint.GetMapId(), [&itr](Map* map) { auto creatureBounds = map->GetCreatureBySpawnIdStore().equal_range(*itr); for (auto itr2 = creatureBounds.first; itr2 != creatureBounds.second;) @@ -1312,11 +1312,11 @@ void GameEventMgr::GameEventUnspawn(int16 event_id) if (event_id >0 && hasGameObjectActiveEventExcept(*itr, event_id)) continue; // Remove the gameobject from grid - if (GameObjectData const* data = sObjectMgr->GetGOData(*itr)) + if (GameObjectData const* data = sObjectMgr->GetGameObjectData(*itr)) { sObjectMgr->RemoveGameobjectFromGrid(*itr, data); - sMapMgr->DoForAllMapsWithMapId(data->mapid, [&itr](Map* map) + sMapMgr->DoForAllMapsWithMapId(data->spawnPoint.GetMapId(), [&itr](Map* map) { auto gameobjectBounds = map->GetGameObjectBySpawnIdStore().equal_range(*itr); for (auto itr2 = gameobjectBounds.first; itr2 != gameobjectBounds.second;) @@ -1351,7 +1351,7 @@ void GameEventMgr::ChangeEquipOrModel(int16 event_id, bool activate) continue; // Update if spawned - sMapMgr->DoForAllMapsWithMapId(data->mapid, [&itr, activate](Map* map) + sMapMgr->DoForAllMapsWithMapId(data->spawnPoint.GetMapId(), [&itr, activate](Map* map) { auto creatureBounds = map->GetCreatureBySpawnIdStore().equal_range(itr->first); for (auto itr2 = creatureBounds.first; itr2 != creatureBounds.second; ++itr2) diff --git a/src/server/game/Garrison/Garrison.cpp b/src/server/game/Garrison/Garrison.cpp index 83755d4877f..0a16ac42e36 100644 --- a/src/server/game/Garrison/Garrison.cpp +++ b/src/server/game/Garrison/Garrison.cpp @@ -683,7 +683,7 @@ template<class T, void(T::*SecondaryRelocate)(float,float,float,float)> T* BuildingSpawnHelper(GameObject* building, ObjectGuid::LowType spawnId, Map* map) { T* spawn = new T(); - if (!spawn->LoadFromDB(spawnId, map)) + if (!spawn->LoadFromDB(spawnId, map, false, false)) { delete spawn; return nullptr; diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index eb6ef70b5f9..0728465d34c 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -260,9 +260,9 @@ ObjectMgr::ObjectMgr(): _equipmentSetGuid(1), _mailId(1), _hiPetNumber(1), - _voidItemId(1), _creatureSpawnId(1), _gameObjectSpawnId(1), + _voidItemId(1), DBCLocaleIndex(LOCALE_enUS) { for (uint8 i = 0; i < MAX_CLASSES; ++i) @@ -1218,7 +1218,7 @@ void ObjectMgr::LoadGameObjectAddons() ObjectGuid::LowType guid = fields[0].GetUInt64(); - GameObjectData const* goData = GetGOData(guid); + GameObjectData const* goData = GetGameObjectData(guid); if (!goData) { TC_LOG_ERROR("sql.sql", "GameObject (GUID: " UI64FMTD ") does not exist but has a record in `gameobject_addon`", guid); @@ -1612,8 +1612,8 @@ void ObjectMgr::LoadLinkedRespawn() break; } - const MapEntry* const map = sMapStore.LookupEntry(master->mapid); - if (!map || !map->Instanceable() || (master->mapid != slave->mapid)) + MapEntry const* const map = sMapStore.LookupEntry(master->spawnPoint.GetMapId()); + if (!map || !map->Instanceable() || (master->spawnPoint.GetMapId() != slave->spawnPoint.GetMapId())) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature '" UI64FMTD "' linking to Creature '" UI64FMTD "' on an unpermitted map.", guidLow, linkedGuidLow); error = true; @@ -1628,8 +1628,8 @@ void ObjectMgr::LoadLinkedRespawn() break; } - guid = ObjectGuid::Create<HighGuid::Creature>(slave->mapid, slave->id, guidLow); - linkedGuid = ObjectGuid::Create<HighGuid::Creature>(master->mapid, master->id, linkedGuidLow); + guid = ObjectGuid::Create<HighGuid::Creature>(slave->spawnPoint.GetMapId(), slave->id, guidLow); + linkedGuid = ObjectGuid::Create<HighGuid::Creature>(master->spawnPoint.GetMapId(), master->id, linkedGuidLow); break; } case CREATURE_TO_GO: @@ -1642,7 +1642,7 @@ void ObjectMgr::LoadLinkedRespawn() break; } - const GameObjectData* master = GetGOData(linkedGuidLow); + GameObjectData const* master = GetGameObjectData(linkedGuidLow); if (!master) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (linkedGuid) '" UI64FMTD "' not found in gameobject table", linkedGuidLow); @@ -1650,8 +1650,8 @@ void ObjectMgr::LoadLinkedRespawn() break; } - const MapEntry* const map = sMapStore.LookupEntry(master->mapid); - if (!map || !map->Instanceable() || (master->mapid != slave->mapid)) + MapEntry const* const map = sMapStore.LookupEntry(master->spawnPoint.GetMapId()); + if (!map || !map->Instanceable() || (master->spawnPoint.GetMapId() != slave->spawnPoint.GetMapId())) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature '" UI64FMTD "' linking to Gameobject '" UI64FMTD "' on an unpermitted map.", guidLow, linkedGuidLow); error = true; @@ -1666,13 +1666,13 @@ void ObjectMgr::LoadLinkedRespawn() break; } - guid = ObjectGuid::Create<HighGuid::Creature>(slave->mapid, slave->id, guidLow); - linkedGuid = ObjectGuid::Create<HighGuid::GameObject>(master->mapid, master->id, linkedGuidLow); + guid = ObjectGuid::Create<HighGuid::Creature>(slave->spawnPoint.GetMapId(), slave->id, guidLow); + linkedGuid = ObjectGuid::Create<HighGuid::GameObject>(master->spawnPoint.GetMapId(), master->id, linkedGuidLow); break; } case GO_TO_GO: { - const GameObjectData* slave = GetGOData(guidLow); + GameObjectData const* slave = GetGameObjectData(guidLow); if (!slave) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (guid) '" UI64FMTD "' not found in gameobject table", guidLow); @@ -1680,7 +1680,7 @@ void ObjectMgr::LoadLinkedRespawn() break; } - const GameObjectData* master = GetGOData(linkedGuidLow); + GameObjectData const* master = GetGameObjectData(linkedGuidLow); if (!master) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (linkedGuid) '" UI64FMTD "' not found in gameobject table", linkedGuidLow); @@ -1688,8 +1688,8 @@ void ObjectMgr::LoadLinkedRespawn() break; } - const MapEntry* const map = sMapStore.LookupEntry(master->mapid); - if (!map || !map->Instanceable() || (master->mapid != slave->mapid)) + MapEntry const* const map = sMapStore.LookupEntry(master->spawnPoint.GetMapId()); + if (!map || !map->Instanceable() || (master->spawnPoint.GetMapId() != slave->spawnPoint.GetMapId())) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject '" UI64FMTD "' linking to Gameobject '" UI64FMTD "' on an unpermitted map.", guidLow, linkedGuidLow); error = true; @@ -1704,13 +1704,13 @@ void ObjectMgr::LoadLinkedRespawn() break; } - guid = ObjectGuid::Create<HighGuid::GameObject>(slave->mapid, slave->id, guidLow); - linkedGuid = ObjectGuid::Create<HighGuid::GameObject>(master->mapid, master->id, linkedGuidLow); + guid = ObjectGuid::Create<HighGuid::GameObject>(slave->spawnPoint.GetMapId(), slave->id, guidLow); + linkedGuid = ObjectGuid::Create<HighGuid::GameObject>(master->spawnPoint.GetMapId(), master->id, linkedGuidLow); break; } case GO_TO_CREATURE: { - const GameObjectData* slave = GetGOData(guidLow); + GameObjectData const* slave = GetGameObjectData(guidLow); if (!slave) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (guid) '" UI64FMTD "' not found in gameobject table", guidLow); @@ -1726,8 +1726,8 @@ void ObjectMgr::LoadLinkedRespawn() break; } - const MapEntry* const map = sMapStore.LookupEntry(master->mapid); - if (!map || !map->Instanceable() || (master->mapid != slave->mapid)) + MapEntry const* const map = sMapStore.LookupEntry(master->spawnPoint.GetMapId()); + if (!map || !map->Instanceable() || (master->spawnPoint.GetMapId() != slave->spawnPoint.GetMapId())) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject '" UI64FMTD "' linking to Creature '" UI64FMTD "' on an unpermitted map.", guidLow, linkedGuidLow); error = true; @@ -1742,8 +1742,8 @@ void ObjectMgr::LoadLinkedRespawn() break; } - guid = ObjectGuid::Create<HighGuid::GameObject>(slave->mapid, slave->id, guidLow); - linkedGuid = ObjectGuid::Create<HighGuid::Creature>(master->mapid, master->id, linkedGuidLow); + guid = ObjectGuid::Create<HighGuid::GameObject>(slave->spawnPoint.GetMapId(), slave->id, guidLow); + linkedGuid = ObjectGuid::Create<HighGuid::Creature>(master->spawnPoint.GetMapId(), master->id, linkedGuidLow); break; } } @@ -1763,7 +1763,7 @@ bool ObjectMgr::SetCreatureLinkedRespawn(ObjectGuid::LowType guidLow, ObjectGuid CreatureData const* master = GetCreatureData(guidLow); ASSERT(master); - ObjectGuid guid = ObjectGuid::Create<HighGuid::Creature>(master->mapid, master->id, guidLow); + ObjectGuid guid = ObjectGuid::Create<HighGuid::Creature>(master->spawnPoint.GetMapId(), master->id, guidLow); if (!linkedGuidLow) // we're removing the linking { @@ -1781,8 +1781,8 @@ bool ObjectMgr::SetCreatureLinkedRespawn(ObjectGuid::LowType guidLow, ObjectGuid return false; } - MapEntry const* map = sMapStore.LookupEntry(master->mapid); - if (!map || !map->Instanceable() || (master->mapid != slave->mapid)) + MapEntry const* map = sMapStore.LookupEntry(master->spawnPoint.GetMapId()); + if (!map || !map->Instanceable() || (master->spawnPoint.GetMapId() != slave->spawnPoint.GetMapId())) { TC_LOG_ERROR("sql.sql", "Creature '" UI64FMTD "' linking to '" UI64FMTD "' on an unpermitted map.", guidLow, linkedGuidLow); return false; @@ -1795,7 +1795,7 @@ bool ObjectMgr::SetCreatureLinkedRespawn(ObjectGuid::LowType guidLow, ObjectGuid return false; } - ObjectGuid linkedGuid = ObjectGuid::Create<HighGuid::Creature>(slave->mapid, slave->id, linkedGuidLow); + ObjectGuid linkedGuid = ObjectGuid::Create<HighGuid::Creature>(slave->spawnPoint.GetMapId(), slave->id, linkedGuidLow); _linkedRespawnStore[guid] = linkedGuid; WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_REP_CREATURE_LINKED_RESPAWN); @@ -1927,8 +1927,8 @@ void ObjectMgr::LoadCreatures() { uint32 oldMSTime = getMSTime(); - // 0 1 2 3 4 5 6 7 8 9 10 - QueryResult result = WorldDatabase.Query("SELECT creature.guid, id, map, modelid, equipment_id, position_x, position_y, position_z, orientation, spawntimesecs, spawndist, " + // 0 1 2 3 4 5 6 7 8 9 10 + QueryResult result = WorldDatabase.Query("SELECT creature.guid, id, map, position_x, position_y, position_z, orientation, modelid, equipment_id, spawntimesecs, spawndist, " // 11 12 13 14 15 16 17 18 19 20 21 "currentwaypoint, curhealth, curmana, MovementType, spawnDifficulties, eventEntry, pool_entry, creature.npcflag, creature.unit_flags, creature.unit_flags2, creature.unit_flags3, " // 22 23 24 25 26 27 @@ -1968,21 +1968,18 @@ void ObjectMgr::LoadCreatures() } CreatureData& data = _creatureDataStore[guid]; + data.spawnId = guid; data.id = entry; - data.mapid = fields[2].GetUInt16(); - data.displayid = fields[3].GetUInt32(); - data.equipmentId = fields[4].GetInt8(); - data.posX = fields[5].GetFloat(); - data.posY = fields[6].GetFloat(); - data.posZ = fields[7].GetFloat(); - data.orientation = fields[8].GetFloat(); + data.spawnPoint.WorldRelocate(fields[2].GetUInt16(), fields[3].GetFloat(), fields[4].GetFloat(), fields[5].GetFloat(), fields[6].GetFloat()); + data.displayid = fields[7].GetUInt32(); + data.equipmentId = fields[8].GetInt8(); data.spawntimesecs = fields[9].GetUInt32(); data.spawndist = fields[10].GetFloat(); data.currentwaypoint= fields[11].GetUInt32(); data.curhealth = fields[12].GetUInt32(); data.curmana = fields[13].GetUInt32(); data.movementType = fields[14].GetUInt8(); - data.spawnDifficulties = ParseSpawnDifficulties(fields[15].GetString(), "creature", guid, data.mapid, spawnMasks[data.mapid]); + data.spawnDifficulties = ParseSpawnDifficulties(fields[15].GetString(), "creature", guid, data.spawnPoint.GetMapId(), spawnMasks[data.spawnPoint.GetMapId()]); int16 gameEvent = fields[16].GetInt8(); uint32 PoolId = fields[17].GetUInt32(); data.npcflag = fields[18].GetUInt64(); @@ -1994,12 +1991,13 @@ void ObjectMgr::LoadCreatures() data.phaseId = fields[24].GetUInt32(); data.phaseGroup = fields[25].GetUInt32(); data.terrainSwapMap = fields[26].GetInt32(); - data.ScriptId = GetScriptId(fields[27].GetString()); + data.scriptId = GetScriptId(fields[27].GetString()); + data.spawnGroupData = &_spawnGroupDataStore[0]; - MapEntry const* mapEntry = sMapStore.LookupEntry(data.mapid); + MapEntry const* mapEntry = sMapStore.LookupEntry(data.spawnPoint.GetMapId()); if (!mapEntry) { - TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: " UI64FMTD ") that spawned at nonexistent map (Id: %u), skipped.", guid, data.mapid); + TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: " UI64FMTD ") that spawned at nonexistent map (Id: %u), skipped.", guid, data.spawnPoint.GetMapId()); continue; } @@ -2007,16 +2005,16 @@ void ObjectMgr::LoadCreatures() { if (VMAP::IVMapManager* vmgr = VMAP::VMapFactory::createOrGetVMapManager()) { - if (vmgr->isMapLoadingEnabled() && !IsTransportMap(data.mapid)) + if (vmgr->isMapLoadingEnabled() && !IsTransportMap(data.spawnPoint.GetMapId())) { - GridCoord gridCoord = Trinity::ComputeGridCoord(data.posX, data.posY); + GridCoord gridCoord = Trinity::ComputeGridCoord(data.spawnPoint.GetPositionX(), data.spawnPoint.GetPositionY()); int gx = (MAX_NUMBER_OF_GRIDS - 1) - gridCoord.x_coord; int gy = (MAX_NUMBER_OF_GRIDS - 1) - gridCoord.y_coord; - VMAP::LoadResult result = vmgr->existsMap((sWorld->GetDataPath() + "vmaps").c_str(), data.mapid, gx, gy); + VMAP::LoadResult result = vmgr->existsMap((sWorld->GetDataPath() + "vmaps").c_str(), data.spawnPoint.GetMapId(), gx, gy); if (result != VMAP::LoadResult::Success) - TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: " UI64FMTD " Entry: %u MapID: %u) spawned on a possible invalid position (X: %f Y: %f Z: %f)", - guid, data.id, data.mapid, data.posX, data.posY, data.posZ); + TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: " UI64FMTD " Entry: %u MapID: %u) spawned on a possible invalid position (%s)", + guid, data.id, data.spawnPoint.GetMapId(), data.spawnPoint.ToString().c_str()); } } } @@ -2084,12 +2082,6 @@ void ObjectMgr::LoadCreatures() } } - if (std::abs(data.orientation) > 2 * float(M_PI)) - { - TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: " UI64FMTD " Entry: %u) with abs(`orientation`) > 2*PI (orientation is expressed in radians), normalized.", guid, data.id); - data.orientation = Position::NormalizeOrientation(data.orientation); - } - if (data.phaseUseFlags & ~PHASE_USE_FLAGS_ALL) { TC_LOG_ERROR("sql.sql", "Table `creature` have creature (GUID: " UI64FMTD " Entry: %u) has unknown `phaseUseFlags` set, removed unknown value.", guid, data.id); @@ -2135,7 +2127,7 @@ void ObjectMgr::LoadCreatures() TC_LOG_ERROR("sql.sql", "Table `creature` have creature (GUID: " UI64FMTD " Entry: %u) with `terrainSwapMap` %u does not exist, set to -1", guid, data.id, data.terrainSwapMap); data.terrainSwapMap = -1; } - else if (terrainSwapEntry->ParentMapID != data.mapid) + else if (terrainSwapEntry->ParentMapID != int16(data.spawnPoint.GetMapId())) { TC_LOG_ERROR("sql.sql", "Table `creature` have creature (GUID: " UI64FMTD " Entry: %u) with `terrainSwapMap` %u which cannot be used on spawn map, set to -1", guid, data.id, data.terrainSwapMap); data.terrainSwapMap = -1; @@ -2147,7 +2139,7 @@ void ObjectMgr::LoadCreatures() uint32 zoneId = 0; uint32 areaId = 0; PhasingHandler::InitDbVisibleMapId(phaseShift, data.terrainSwapMap); - sMapMgr->GetZoneAndAreaId(phaseShift, zoneId, areaId, data.mapid, data.posX, data.posY, data.posZ); + sMapMgr->GetZoneAndAreaId(phaseShift, zoneId, areaId, data.spawnPoint); WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_CREATURE_ZONE_AREA_DATA); @@ -2171,8 +2163,8 @@ void ObjectMgr::AddCreatureToGrid(ObjectGuid::LowType guid, CreatureData const* { for (Difficulty difficulty : data->spawnDifficulties) { - CellCoord cellCoord = Trinity::ComputeCellCoord(data->posX, data->posY); - CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->mapid, difficulty)][cellCoord.GetId()]; + CellCoord cellCoord = Trinity::ComputeCellCoord(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY()); + CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->spawnPoint.GetMapId(), difficulty)][cellCoord.GetId()]; cell_guids.creatures.insert(guid); } } @@ -2181,13 +2173,13 @@ void ObjectMgr::RemoveCreatureFromGrid(ObjectGuid::LowType guid, CreatureData co { for (Difficulty difficulty : data->spawnDifficulties) { - CellCoord cellCoord = Trinity::ComputeCellCoord(data->posX, data->posY); - CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->mapid, difficulty)][cellCoord.GetId()]; + CellCoord cellCoord = Trinity::ComputeCellCoord(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY()); + CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->spawnPoint.GetMapId(), difficulty)][cellCoord.GetId()]; cell_guids.creatures.erase(guid); } } -ObjectGuid::LowType ObjectMgr::AddGOData(uint32 entry, uint32 mapId, Position const& pos, QuaternionData const& rot, uint32 spawntimedelay /*= 0*/) +ObjectGuid::LowType ObjectMgr::AddGameObjectData(uint32 entry, uint32 mapId, Position const& pos, QuaternionData const& rot, uint32 spawntimedelay /*= 0*/) { GameObjectTemplate const* goinfo = GetGameObjectTemplate(entry); if (!goinfo) @@ -2197,38 +2189,37 @@ ObjectGuid::LowType ObjectMgr::AddGOData(uint32 entry, uint32 mapId, Position co if (!map) return UI64LIT(0); - ObjectGuid::LowType guid = GenerateGameObjectSpawnId(); - GameObjectData& data = NewGOData(guid); + ObjectGuid::LowType spawnId = GenerateGameObjectSpawnId(); + GameObjectData& data = NewOrExistGameObjectData(spawnId); + data.spawnId = spawnId; data.id = entry; - data.mapid = mapId; - - pos.GetPosition(data.posX, data.posY, data.posZ, data.orientation); - + data.spawnPoint.WorldRelocate(mapId,pos); data.rotation = rot; data.spawntimesecs = spawntimedelay; data.animprogress = 100; data.spawnDifficulties.push_back(DIFFICULTY_NONE); - data.go_state = GO_STATE_READY; + data.goState = GO_STATE_READY; data.artKit = goinfo->type == GAMEOBJECT_TYPE_CONTROL_ZONE ? 21 : 0; - data.dbData = false; + data.dbData = false; + data.spawnGroupData = GetLegacySpawnGroup(); - AddGameobjectToGrid(guid, &data); + AddGameobjectToGrid(spawnId, &data); // Spawn if necessary (loaded grids only) // We use spawn coords to spawn - if (!map->Instanceable() && map->IsGridLoaded(data.posX, data.posY)) + if (!map->Instanceable() && map->IsGridLoaded(data.spawnPoint)) { - GameObject* go = GameObject::CreateGameObjectFromDB(guid, map); + GameObject* go = GameObject::CreateGameObjectFromDB(spawnId, map); if (!go) { - TC_LOG_ERROR("misc", "AddGOData: cannot add gameobject entry %u to map", entry); + TC_LOG_ERROR("misc", "AddGameObjectData: cannot add gameobject entry %u to map", entry); return UI64LIT(0); } } - TC_LOG_DEBUG("maps", "AddGOData: dbguid " UI64FMTD " entry %u map %u x %f y %f z %f o %f", guid, entry, mapId, data.posX, data.posY, data.posZ, data.orientation); + TC_LOG_DEBUG("maps", "AddGameObjectData: dbguid " UI64FMTD " entry %u map %u pos %s", spawnId, entry, mapId, data.spawnPoint.ToString().c_str()); - return guid; + return spawnId; } ObjectGuid::LowType ObjectMgr::AddCreatureData(uint32 entry, uint32 mapId, Position const& pos, uint32 spawntimedelay /*= 0*/) @@ -2246,15 +2237,13 @@ ObjectGuid::LowType ObjectMgr::AddCreatureData(uint32 entry, uint32 mapId, Posit CreatureLevelScaling const* scaling = cInfo->GetLevelScaling(map->GetDifficultyID()); - ObjectGuid::LowType guid = GenerateCreatureSpawnId(); - CreatureData& data = NewOrExistCreatureData(guid); + ObjectGuid::LowType spawnId = GenerateCreatureSpawnId(); + CreatureData& data = NewOrExistCreatureData(spawnId); + data.spawnId = spawnId; data.id = entry; - data.mapid = mapId; + data.spawnPoint.WorldRelocate(mapId, pos); data.displayid = 0; data.equipmentId = 0; - - pos.GetPosition(data.posX, data.posY, data.posZ, data.orientation); - data.spawntimesecs = spawntimedelay; data.spawndist = 0; data.currentwaypoint = 0; @@ -2266,13 +2255,14 @@ ObjectGuid::LowType ObjectMgr::AddCreatureData(uint32 entry, uint32 mapId, Posit data.npcflag = cInfo->npcflag; data.unit_flags = cInfo->unit_flags; data.dynamicflags = cInfo->dynamicflags; + data.spawnGroupData = GetLegacySpawnGroup(); - AddCreatureToGrid(guid, &data); + AddCreatureToGrid(spawnId, &data); // We use spawn coords to spawn - if (!map->Instanceable() && !map->IsRemovalGrid(data.posX, data.posY)) + if (!map->Instanceable() && !map->IsRemovalGrid(data.spawnPoint)) { - Creature* creature = Creature::CreateCreatureFromDB(guid, map); + Creature* creature = Creature::CreateCreatureFromDB(spawnId, map, true, true); if (!creature) { TC_LOG_ERROR("misc", "AddCreature: Cannot add creature entry %u to map", entry); @@ -2280,10 +2270,10 @@ ObjectGuid::LowType ObjectMgr::AddCreatureData(uint32 entry, uint32 mapId, Posit } } - return guid; + return spawnId; } -void ObjectMgr::LoadGameobjects() +void ObjectMgr::LoadGameObjects() { uint32 oldMSTime = getMSTime(); @@ -2347,22 +2337,20 @@ void ObjectMgr::LoadGameobjects() GameObjectData& data = _gameObjectDataStore[guid]; + data.spawnId = guid; data.id = entry; - data.mapid = fields[2].GetUInt16(); - data.posX = fields[3].GetFloat(); - data.posY = fields[4].GetFloat(); - data.posZ = fields[5].GetFloat(); - data.orientation = fields[6].GetFloat(); + data.spawnPoint.WorldRelocate(fields[2].GetUInt16(), fields[3].GetFloat(), fields[4].GetFloat(), fields[5].GetFloat(), fields[6].GetFloat()); data.rotation.x = fields[7].GetFloat(); data.rotation.y = fields[8].GetFloat(); data.rotation.z = fields[9].GetFloat(); data.rotation.w = fields[10].GetFloat(); data.spawntimesecs = fields[11].GetInt32(); + data.spawnGroupData = &_spawnGroupDataStore[0]; - MapEntry const* mapEntry = sMapStore.LookupEntry(data.mapid); + MapEntry const* mapEntry = sMapStore.LookupEntry(data.spawnPoint.GetMapId()); if (!mapEntry) { - TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: " UI64FMTD " Entry: %u) spawned on a non-existed map (Id: %u), skip", guid, data.id, data.mapid); + TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: " UI64FMTD " Entry: %u) spawned on a non-existed map (Id: %u), skip", guid, data.id, data.spawnPoint.GetMapId()); continue; } @@ -2370,16 +2358,16 @@ void ObjectMgr::LoadGameobjects() { if (VMAP::IVMapManager* vmgr = VMAP::VMapFactory::createOrGetVMapManager()) { - if (vmgr->isMapLoadingEnabled() && !IsTransportMap(data.mapid)) + if (vmgr->isMapLoadingEnabled() && !IsTransportMap(data.spawnPoint.GetMapId())) { - GridCoord gridCoord = Trinity::ComputeGridCoord(data.posX, data.posY); + GridCoord gridCoord = Trinity::ComputeGridCoord(data.spawnPoint.GetPositionX(), data.spawnPoint.GetPositionY()); int gx = (MAX_NUMBER_OF_GRIDS - 1) - gridCoord.x_coord; int gy = (MAX_NUMBER_OF_GRIDS - 1) - gridCoord.y_coord; - VMAP::LoadResult result = vmgr->existsMap((sWorld->GetDataPath() + "vmaps").c_str(), data.mapid, gx, gy); + VMAP::LoadResult result = vmgr->existsMap((sWorld->GetDataPath() + "vmaps").c_str(), data.spawnPoint.GetMapId(), gx, gy); if (result != VMAP::LoadResult::Success) - TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: " UI64FMTD " Entry: %u MapID: %u) spawned on a possible invalid position (X: %f Y: %f Z: %f)", - guid, data.id, data.mapid, data.posX, data.posY, data.posZ); + TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: " UI64FMTD " Entry: %u MapID: %u) spawned on a possible invalid position (%s)", + guid, data.id, data.spawnPoint.GetMapId(), data.spawnPoint.ToString().c_str()); } } } @@ -2401,9 +2389,9 @@ void ObjectMgr::LoadGameobjects() continue; } } - data.go_state = GOState(go_state); + data.goState = GOState(go_state); - data.spawnDifficulties = ParseSpawnDifficulties(fields[14].GetString(), "gameobject", guid, data.mapid, spawnMasks[data.mapid]); + data.spawnDifficulties = ParseSpawnDifficulties(fields[14].GetString(), "gameobject", guid, data.spawnPoint.GetMapId(), spawnMasks[data.spawnPoint.GetMapId()]); if (data.spawnDifficulties.empty()) { TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: " UI64FMTD ") that is not spawned in any difficulty, skipped.", guid); @@ -2462,20 +2450,14 @@ void ObjectMgr::LoadGameobjects() TC_LOG_ERROR("sql.sql", "Table `gameobject` have gameobject (GUID: " UI64FMTD " Entry: %u) with `terrainSwapMap` %u does not exist, set to -1", guid, data.id, data.terrainSwapMap); data.terrainSwapMap = -1; } - else if (terrainSwapEntry->ParentMapID != data.mapid) + else if (terrainSwapEntry->ParentMapID != int16(data.spawnPoint.GetMapId())) { TC_LOG_ERROR("sql.sql", "Table `gameobject` have gameobject (GUID: " UI64FMTD " Entry: %u) with `terrainSwapMap` %u which cannot be used on spawn map, set to -1", guid, data.id, data.terrainSwapMap); data.terrainSwapMap = -1; } } - data.ScriptId = GetScriptId(fields[21].GetString()); - - if (std::abs(data.orientation) > 2 * float(M_PI)) - { - TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: " UI64FMTD " Entry: %u) with abs(`orientation`) > 2*PI (orientation is expressed in radians), normalized.", guid, data.id); - data.orientation = Position::NormalizeOrientation(data.orientation); - } + data.scriptId = GetScriptId(fields[21].GetString()); if (data.rotation.x < -1.0f || data.rotation.x > 1.0f) { @@ -2501,7 +2483,7 @@ void ObjectMgr::LoadGameobjects() continue; } - if (!MapManager::IsValidMapCoord(data.mapid, data.posX, data.posY, data.posZ, data.orientation)) + if (!MapManager::IsValidMapCoord(data.spawnPoint)) { TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: " UI64FMTD " Entry: %u) with invalid coordinates, skip", guid, data.id); continue; @@ -2512,7 +2494,7 @@ void ObjectMgr::LoadGameobjects() uint32 zoneId = 0; uint32 areaId = 0; PhasingHandler::InitDbVisibleMapId(phaseShift, data.terrainSwapMap); - sMapMgr->GetZoneAndAreaId(phaseShift, zoneId, areaId, data.mapid, data.posX, data.posY, data.posZ); + sMapMgr->GetZoneAndAreaId(phaseShift, zoneId, areaId, data.spawnPoint); WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_GAMEOBJECT_ZONE_AREA_DATA); @@ -2531,12 +2513,159 @@ void ObjectMgr::LoadGameobjects() TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " gameobjects in %u ms", _gameObjectDataStore.size(), GetMSTimeDiffToNow(oldMSTime)); } +void ObjectMgr::LoadSpawnGroupTemplates() +{ + uint32 oldMSTime = getMSTime(); + + // 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].GetUInt32(); + SpawnGroupTemplateData& group = _spawnGroupDataStore[groupId]; + group.groupId = groupId; + group.name = fields[1].GetString(); + group.mapId = SPAWNGROUP_MAP_UNSET; + uint32 flags = fields[2].GetUInt32(); + if (flags & ~SPAWNGROUP_FLAGS_ALL) + { + flags &= SPAWNGROUP_FLAGS_ALL; + TC_LOG_ERROR("server.loading", "Invalid spawn group flag %u on group ID %u (%s), reduced to valid flag %u.", flags, groupId, group.name.c_str(), uint32(group.flags)); + } + if (flags & SPAWNGROUP_FLAG_SYSTEM && flags & SPAWNGROUP_FLAG_MANUAL_SPAWN) + { + flags &= ~SPAWNGROUP_FLAG_MANUAL_SPAWN; + TC_LOG_ERROR("server.loading", "System spawn group %u (%s) has invalid manual spawn flag. Ignored.", groupId, group.name.c_str()); + } + group.flags = SpawnGroupFlags(flags); + group.isActive = !(group.flags & SPAWNGROUP_FLAG_MANUAL_SPAWN); + } while (result->NextRow()); + } + + if (_spawnGroupDataStore.find(0) == _spawnGroupDataStore.end()) + { + TC_LOG_ERROR("server.loading", "Default spawn group (index 0) is missing from DB! Manually inserted."); + SpawnGroupTemplateData& data = _spawnGroupDataStore[0]; + data.groupId = 0; + data.name = "Default Group"; + data.mapId = 0; + data.flags = SPAWNGROUP_FLAG_SYSTEM; + data.isActive = true; + } + if (_spawnGroupDataStore.find(1) == _spawnGroupDataStore.end()) + { + TC_LOG_ERROR("server.loading", "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 = 0; + data.flags = SpawnGroupFlags(SPAWNGROUP_FLAG_SYSTEM | SPAWNGROUP_FLAG_COMPATIBILITY_MODE); + data.isActive = true; + } + + if (result) + TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " spawn group templates in %u ms", _spawnGroupDataStore.size(), GetMSTimeDiffToNow(oldMSTime)); + else + TC_LOG_ERROR("server.loading", ">> Loaded 0 spawn group templates. DB table `spawn_group_template` is empty."); + + return; +} + +void ObjectMgr::LoadSpawnGroups() +{ + uint32 oldMSTime = getMSTime(); + + // 0 1 2 + QueryResult result = WorldDatabase.Query("SELECT groupId, spawnType, spawnId FROM spawn_group"); + + if (!result) + { + TC_LOG_ERROR("server.loading", ">> Loaded 0 spawn group members. DB table `spawn_group` is empty."); + return; + } + + uint32 numMembers = 0; + do + { + Field* fields = result->Fetch(); + uint32 groupId = fields[0].GetUInt32(); + SpawnObjectType spawnType; + { + uint32 type = fields[1].GetUInt8(); + if (type >= SPAWN_TYPE_MAX) + { + TC_LOG_ERROR("server.loading", "Spawn data with invalid type %u listed for spawn group %u. Skipped.", type, groupId); + continue; + } + spawnType = SpawnObjectType(type); + } + ObjectGuid::LowType spawnId = fields[2].GetUInt64(); + + SpawnData const* data = GetSpawnData(spawnType, spawnId); + if (!data) + { + TC_LOG_ERROR("server.loading", "Spawn data with ID (%u," UI64FMTD ") not found, but is listed as a member of spawn group %u!", uint32(spawnType), spawnId, groupId); + continue; + } + else if (data->spawnGroupData->groupId) + { + TC_LOG_ERROR("server.loading", "Spawn with ID (%u," UI64FMTD ") is listed as a member of spawn group %u, but is already a member of spawn group %u. Skipping.", uint32(spawnType), spawnId, groupId, data->spawnGroupData->groupId); + continue; + } + auto it = _spawnGroupDataStore.find(groupId); + if (it == _spawnGroupDataStore.end()) + { + TC_LOG_ERROR("server.loading", "Spawn group %u assigned to spawn ID (%u," UI64FMTD "), but group is found!", groupId, uint32(spawnType), spawnId); + continue; + } + else + { + SpawnGroupTemplateData& groupTemplate = it->second; + if (groupTemplate.mapId == SPAWNGROUP_MAP_UNSET) + groupTemplate.mapId = data->spawnPoint.GetMapId(); + else if (groupTemplate.mapId != data->spawnPoint.GetMapId() && !(groupTemplate.flags & SPAWNGROUP_FLAG_SYSTEM)) + { + TC_LOG_ERROR("server.loading", "Spawn group %u has map ID %u, but spawn (%u," UI64FMTD ") has map id %u - spawn NOT added to group!", groupId, groupTemplate.mapId, uint32(spawnType), spawnId, data->spawnPoint.GetMapId()); + continue; + } + const_cast<SpawnData*>(data)->spawnGroupData = &groupTemplate; + if (!(groupTemplate.flags & SPAWNGROUP_FLAG_SYSTEM)) + _spawnGroupMapStore.emplace(groupId, data); + ++numMembers; + } + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded %u spawn group members in %u ms", numMembers, GetMSTimeDiffToNow(oldMSTime)); +} + +void ObjectMgr::OnDeleteSpawnData(SpawnData const* data) +{ + auto templateIt = _spawnGroupDataStore.find(data->spawnGroupData->groupId); + ASSERT(templateIt != _spawnGroupDataStore.end(), "Creature data for (%u," UI64FMTD ") is being deleted and has invalid spawn group index %u!", uint32(data->type), data->spawnId, data->spawnGroupData->groupId); + if (templateIt->second.flags & SPAWNGROUP_FLAG_SYSTEM) // system groups don't store their members in the map + return; + + auto pair = _spawnGroupMapStore.equal_range(data->spawnGroupData->groupId); + for (auto it = pair.first; it != pair.second; ++it) + { + if (it->second != data) + continue; + _spawnGroupMapStore.erase(it); + return; + } + ASSERT(false, "Spawn data (%u," UI64FMTD ") being removed is member of spawn group %u, but not actually listed in the lookup table for that group!", uint32(data->type), data->spawnId, data->spawnGroupData->groupId); +} + void ObjectMgr::AddGameobjectToGrid(ObjectGuid::LowType guid, GameObjectData const* data) { for (Difficulty difficulty : data->spawnDifficulties) { - CellCoord cellCoord = Trinity::ComputeCellCoord(data->posX, data->posY); - CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->mapid, difficulty)][cellCoord.GetId()]; + CellCoord cellCoord = Trinity::ComputeCellCoord(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY()); + CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->spawnPoint.GetMapId(), difficulty)][cellCoord.GetId()]; cell_guids.gameobjects.insert(guid); } } @@ -2545,8 +2674,8 @@ void ObjectMgr::RemoveGameobjectFromGrid(ObjectGuid::LowType guid, GameObjectDat { for (Difficulty difficulty : data->spawnDifficulties) { - CellCoord cellCoord = Trinity::ComputeCellCoord(data->posX, data->posY); - CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->mapid, difficulty)][cellCoord.GetId()]; + CellCoord cellCoord = Trinity::ComputeCellCoord(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY()); + CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->spawnPoint.GetMapId(), difficulty)][cellCoord.GetId()]; cell_guids.gameobjects.erase(guid); } } @@ -5067,7 +5196,7 @@ void ObjectMgr::LoadScripts(ScriptsType type) case SCRIPT_COMMAND_RESPAWN_GAMEOBJECT: { - GameObjectData const* data = GetGOData(tmp.RespawnGameobject.GOGuid); + GameObjectData const* data = GetGameObjectData(tmp.RespawnGameobject.GOGuid); if (!data) { TC_LOG_ERROR("sql.sql", "Table `%s` has invalid gameobject (GUID: %u) in SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id %u", @@ -5117,7 +5246,7 @@ void ObjectMgr::LoadScripts(ScriptsType type) case SCRIPT_COMMAND_OPEN_DOOR: case SCRIPT_COMMAND_CLOSE_DOOR: { - GameObjectData const* data = GetGOData(tmp.ToggleDoor.GOGuid); + GameObjectData const* data = GetGameObjectData(tmp.ToggleDoor.GOGuid); if (!data) { TC_LOG_ERROR("sql.sql", "Table `%s` has invalid gameobject (GUID: %u) in %s for script id %u", @@ -6913,12 +7042,142 @@ uint64 ObjectMgr::GenerateGameObjectSpawnId() { if (_gameObjectSpawnId >= uint64(0xFFFFFFFFFFFFFFFELL)) { - TC_LOG_ERROR("misc", "Creature spawn id overflow!! Can't continue, shutting down server. Search on forum for TCE00007 for more info. "); + TC_LOG_ERROR("misc", "GameObject spawn id overflow!! Can't continue, shutting down server. Search on forum for TCE00007 for more info. "); World::StopNow(ERROR_EXIT_CODE); } return _gameObjectSpawnId++; } +bool ObjectMgr::SpawnGroupSpawn(uint32 groupId, Map* map, bool ignoreRespawn, bool force, std::vector<WorldObject*>* spawnedObjects) +{ + auto itr = _spawnGroupDataStore.find(groupId); + if (itr == _spawnGroupDataStore.end() || itr->second.flags & SPAWNGROUP_FLAG_SYSTEM) + { + TC_LOG_ERROR("maps", "Tried to despawn non-existing (or system) spawn group %u. Blocked.", groupId); + return false; + } + + if (!map) + { + TC_LOG_ERROR("maps", "Tried to despawn creature group %u, but no map was supplied. Blocked.", groupId); + return false; + } + + if (itr->second.mapId != map->GetId()) + { + TC_LOG_ERROR("maps", "Tried to despawn creature group %u, but supplied map is %u, creature group has map %u. Blocked.", groupId, map->GetId(), itr->second.mapId); + return false; + } + + for (auto& pair : GetSpawnDataForGroup(groupId)) + { + SpawnData const* data = pair.second; + ASSERT(itr->second.mapId == data->spawnPoint.GetMapId()); + // Check if there's already an instance spawned + if (!force) + if (WorldObject* obj = map->GetWorldObjectBySpawnId(data->type, data->spawnId)) + if ((data->type != SPAWN_TYPE_CREATURE) || obj->ToCreature()->IsAlive()) + continue; + + time_t respawnTime = map->GetRespawnTime(data->type, data->spawnId); + if (respawnTime && respawnTime > time(NULL)) + { + if (!force && !ignoreRespawn) + continue; + + // we need to remove the respawn time, otherwise we'd end up double spawning + map->RemoveRespawnTime(data->type, data->spawnId, false); + } + + // don't spawn if the grid isn't loaded (will be handled in grid loader) + if (!map->IsGridLoaded(data->spawnPoint)) + continue; + + // Everything OK, now do the actual (re)spawn + switch (data->type) + { + case SPAWN_TYPE_CREATURE: + { + Creature* creature = new Creature(); + if (!creature->LoadFromDB(data->spawnId, map, true, force)) + delete creature; + else if (spawnedObjects) + spawnedObjects->push_back(creature); + break; + } + case SPAWN_TYPE_GAMEOBJECT: + { + GameObject* gameobject = new GameObject(); + if (!gameobject->LoadFromDB(data->spawnId, map, true)) + delete gameobject; + else if (spawnedObjects) + spawnedObjects->push_back(gameobject); + break; + } + default: + ASSERT(false, "Invalid spawn type %u with spawnId " UI64FMTD, uint32(data->type), data->spawnId); + return false; + } + } + itr->second.isActive = true; // start processing respawns for the group + return true; +} + +bool ObjectMgr::SpawnGroupDespawn(uint32 groupId, Map* map, bool deleteRespawnTimes) +{ + auto itr = _spawnGroupDataStore.find(groupId); + if (itr == _spawnGroupDataStore.end() || itr->second.flags & SPAWNGROUP_FLAG_SYSTEM) + { + TC_LOG_ERROR("maps", "Tried to despawn non-existing (or system) spawn group %u. Blocked.", groupId); + return false; + } + + if (!map) + { + TC_LOG_ERROR("maps", "Tried to despawn creature group %u, but no map was supplied. Blocked.", groupId); + return false; + } + + if (itr->second.mapId != map->GetId()) + { + TC_LOG_ERROR("maps", "Tried to despawn creature group %u, but supplied map is %u, creature group has map %u. Blocked.", groupId, map->GetId(), itr->second.mapId); + return false; + } + + std::vector<WorldObject*> toUnload; // unload after iterating, otherwise iterator invalidation + for (auto const& pair : GetSpawnDataForGroup(groupId)) + { + SpawnData const* data = pair.second; + if (deleteRespawnTimes) + map->RemoveRespawnTime(data->type, data->spawnId); + switch (data->type) + { + case SPAWN_TYPE_CREATURE: + { + auto bounds = map->GetCreatureBySpawnIdStore().equal_range(data->spawnId); + for (auto it = bounds.first; it != bounds.second; ++it) + toUnload.emplace_back(it->second); + break; + } + case SPAWN_TYPE_GAMEOBJECT: + { + auto bounds = map->GetGameObjectBySpawnIdStore().equal_range(data->spawnId); + for (auto it = bounds.first; it != bounds.second; ++it) + toUnload.emplace_back(it->second); + break; + } + default: + ASSERT(false, "Invalid spawn type %u in spawn data with spawnId " UI64FMTD ".", uint32(data->type), data->spawnId); + return false; + } + } + // now do the actual despawning + for (WorldObject* obj : toUnload) + obj->AddObjectToRemoveList(); + itr->second.isActive = false; // stop processing respawns for the group, too + return true; +} + void ObjectMgr::LoadGameObjectLocales() { uint32 oldMSTime = getMSTime(); @@ -7854,17 +8113,23 @@ void ObjectMgr::DeleteCreatureData(ObjectGuid::LowType guid) // remove mapid*cellid -> guid_set map CreatureData const* data = GetCreatureData(guid); if (data) + { RemoveCreatureFromGrid(guid, data); + OnDeleteSpawnData(data); + } _creatureDataStore.erase(guid); } -void ObjectMgr::DeleteGOData(ObjectGuid::LowType guid) +void ObjectMgr::DeleteGameObjectData(ObjectGuid::LowType guid) { // remove mapid*cellid -> guid_set map - GameObjectData const* data = GetGOData(guid); + GameObjectData const* data = GetGameObjectData(guid); if (data) + { RemoveGameobjectFromGrid(guid, data); + OnDeleteSpawnData(data); + } _gameObjectDataStore.erase(guid); } diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index e5b3ba78226..5f93284920e 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -22,6 +22,7 @@ #include "ConditionMgr.h" #include "CreatureData.h" #include "DatabaseEnvFwd.h" +#include "Errors.h" #include "GameObjectData.h" #include "ItemTemplate.h" #include "IteratorPair.h" @@ -40,6 +41,7 @@ class Item; class Unit; class Vehicle; +class Map; struct AccessRequirement; struct DeclinedName; struct DungeonEncounterEntry; @@ -492,6 +494,8 @@ typedef std::unordered_map<uint32, GameObjectTemplateAddon> GameObjectTemplateAd typedef std::unordered_map<ObjectGuid::LowType, GameObjectData> GameObjectDataContainer; typedef std::unordered_map<ObjectGuid::LowType, GameObjectAddon> GameObjectAddonContainer; typedef std::unordered_map<uint32, std::vector<uint32>> GameObjectQuestItemMap; +typedef std::unordered_map<uint32, SpawnGroupTemplateData> SpawnGroupDataContainer; +typedef std::multimap<uint32, SpawnData const*> SpawnGroupLinkContainer; typedef std::map<TempSummonGroupKey, std::vector<TempSummonData>> TempSummonDataContainer; typedef std::unordered_map<uint32, CreatureLocale> CreatureLocaleContainer; typedef std::unordered_map<uint32, GameObjectLocale> GameObjectLocaleContainer; @@ -874,6 +878,7 @@ SkillRangeType GetSkillRangeType(SkillRaceClassInfoEntry const* rcEntry); #define MAX_CHARTER_NAME 24 // max allowed by client name length TC_GAME_API bool normalizePlayerName(std::string& name); +#define SPAWNGROUP_MAP_UNSET 0xFFFFFFFF struct ExtendedPlayerName { @@ -1241,7 +1246,9 @@ class TC_GAME_API ObjectMgr void LoadCreatureModelInfo(); void LoadEquipmentTemplates(); void LoadGameObjectLocales(); - void LoadGameobjects(); + void LoadGameObjects(); + void LoadSpawnGroupTemplates(); + void LoadSpawnGroups(); void LoadItemTemplates(); void LoadItemTemplateAddon(); void LoadItemScriptNames(); @@ -1347,10 +1354,18 @@ class TC_GAME_API ObjectMgr uint32 GenerateMailID(); uint32 GeneratePetNumber(); uint64 GenerateVoidStorageItemId(); - uint64 GenerateCreatureSpawnId(); - uint64 GenerateGameObjectSpawnId(); + ObjectGuid::LowType GenerateCreatureSpawnId(); + ObjectGuid::LowType GenerateGameObjectSpawnId(); - MailLevelReward const* GetMailLevelReward(uint8 level, uint8 race) + bool SpawnGroupSpawn(uint32 groupId, Map* map, bool ignoreRespawn = false, bool force = false, std::vector<WorldObject*>* spawnedObjects = nullptr); + bool SpawnGroupDespawn(uint32 groupId, Map* map, bool deleteRespawnTimes = false); + void SetSpawnGroupActive(uint32 groupId, bool state) { auto it = _spawnGroupDataStore.find(groupId); if (it != _spawnGroupDataStore.end()) it->second.isActive = state; } + bool IsSpawnGroupActive(uint32 groupId) const { auto it = _spawnGroupDataStore.find(groupId); return (it != _spawnGroupDataStore.end()) && it->second.isActive; } + SpawnGroupTemplateData const* GetDefaultSpawnGroup() const { return &_spawnGroupDataStore.at(0); } + SpawnGroupTemplateData const* GetLegacySpawnGroup() const { return &_spawnGroupDataStore.at(1); } + Trinity::IteratorPair<SpawnGroupLinkContainer::const_iterator> GetSpawnDataForGroup(uint32 groupId) const { return Trinity::Containers::MapEqualRange(_spawnGroupMapStore, groupId); } + + MailLevelReward const* GetMailLevelReward(uint8 level, uint8 race) const { MailLevelRewardContainer::const_iterator map_itr = _mailLevelRewardStore.find(level); if (map_itr == _mailLevelRewardStore.end()) @@ -1391,6 +1406,17 @@ class TC_GAME_API ObjectMgr return nullptr; } + SpawnData const* GetSpawnData(SpawnObjectType type, ObjectGuid::LowType guid) + { + if (type == SPAWN_TYPE_CREATURE) + return GetCreatureData(guid); + else if (type == SPAWN_TYPE_GAMEOBJECT) + return GetGameObjectData(guid); + else + ASSERT(false, "Invalid spawn object type %u", uint32(type)); + return nullptr; + } + void OnDeleteSpawnData(SpawnData const* data); CreatureData const* GetCreatureData(ObjectGuid::LowType guid) const { CreatureDataContainer::const_iterator itr = _creatureDataStore.find(guid); @@ -1411,6 +1437,14 @@ class TC_GAME_API ObjectMgr if (itr == _creatureLocaleStore.end()) return nullptr; return &itr->second; } + GameObjectData const* GetGameObjectData(ObjectGuid::LowType guid) const + { + GameObjectDataContainer::const_iterator itr = _gameObjectDataStore.find(guid); + if (itr == _gameObjectDataStore.end()) return nullptr; + return &itr->second; + } + GameObjectData& NewOrExistGameObjectData(ObjectGuid::LowType guid) { return _gameObjectDataStore[guid]; } + void DeleteGameObjectData(ObjectGuid::LowType guid); GameObjectLocale const* GetGameObjectLocale(uint32 entry) const { GameObjectLocaleContainer::const_iterator itr = _gameObjectLocaleStore.find(entry); @@ -1465,15 +1499,6 @@ class TC_GAME_API ObjectMgr if (itr == _playerChoiceLocales.end()) return nullptr; return &itr->second; } - GameObjectData const* GetGOData(ObjectGuid::LowType guid) const - { - GameObjectDataContainer::const_iterator itr = _gameObjectDataStore.find(guid); - if (itr == _gameObjectDataStore.end()) return nullptr; - return &itr->second; - } - GameObjectData& NewGOData(ObjectGuid::LowType guid) { return _gameObjectDataStore[guid]; } - void DeleteGOData(ObjectGuid::LowType guid); - TrinityString const* GetTrinityString(uint32 entry) const { TrinityStringContainer::const_iterator itr = _trinityStringStore.find(entry); @@ -1491,7 +1516,7 @@ class TC_GAME_API ObjectMgr void RemoveCreatureFromGrid(ObjectGuid::LowType guid, CreatureData const* data); void AddGameobjectToGrid(ObjectGuid::LowType guid, GameObjectData const* data); void RemoveGameobjectFromGrid(ObjectGuid::LowType guid, GameObjectData const* data); - ObjectGuid::LowType AddGOData(uint32 entry, uint32 map, Position const& pos, QuaternionData const& rot, uint32 spawntimedelay = 0); + ObjectGuid::LowType AddGameObjectData(uint32 entry, uint32 map, Position const& pos, QuaternionData const& rot, uint32 spawntimedelay = 0); ObjectGuid::LowType AddCreatureData(uint32 entry, uint32 map, Position const& pos, uint32 spawntimedelay = 0); // reserved names @@ -1628,9 +1653,9 @@ class TC_GAME_API ObjectMgr uint64 _equipmentSetGuid; std::atomic<uint32> _mailId; std::atomic<uint32> _hiPetNumber; + ObjectGuid::LowType _creatureSpawnId; + ObjectGuid::LowType _gameObjectSpawnId; uint64 _voidItemId; - uint64 _creatureSpawnId; - uint64 _gameObjectSpawnId; // first free low guid for selected guid type template<HighGuid high> @@ -1763,6 +1788,8 @@ class TC_GAME_API ObjectMgr GameObjectLocaleContainer _gameObjectLocaleStore; GameObjectTemplateContainer _gameObjectTemplateStore; GameObjectTemplateAddonContainer _gameObjectTemplateAddonStore; + SpawnGroupDataContainer _spawnGroupDataStore; + SpawnGroupLinkContainer _spawnGroupMapStore; /// Stores temp summon data grouped by summoner's entry, summoner's type and group id TempSummonDataContainer _tempSummonDataStore; std::unordered_map<int32 /*choiceId*/, PlayerChoice> _playerChoices; diff --git a/src/server/game/Grids/Notifiers/GridNotifiers.h b/src/server/game/Grids/Notifiers/GridNotifiers.h index 266ebdd3efb..85c0fab72d0 100644 --- a/src/server/game/Grids/Notifiers/GridNotifiers.h +++ b/src/server/game/Grids/Notifiers/GridNotifiers.h @@ -1352,6 +1352,27 @@ namespace Trinity bool _reqAlive; }; + class AnyPlayerInPositionRangeCheck + { + public: + AnyPlayerInPositionRangeCheck(Position const* pos, float range, bool reqAlive = true) : _pos(pos), _range(range), _reqAlive(reqAlive) { } + bool operator()(Player* u) + { + if (_reqAlive && !u->IsAlive()) + return false; + + if (!u->IsWithinDist3d(_pos, _range)) + return false; + + return true; + } + + private: + Position const* _pos; + float _range; + bool _reqAlive; + }; + class NearestPlayerInObjectRangeCheck { public: diff --git a/src/server/game/Grids/ObjectGridLoader.cpp b/src/server/game/Grids/ObjectGridLoader.cpp index a64771876d9..7b2ead0e1cc 100644 --- a/src/server/game/Grids/ObjectGridLoader.cpp +++ b/src/server/game/Grids/ObjectGridLoader.cpp @@ -16,18 +16,20 @@ */ #include "ObjectGridLoader.h" -#include "ObjectAccessor.h" -#include "ObjectMgr.h" -#include "Creature.h" -#include "GameObject.h" -#include "DynamicObject.h" -#include "Corpse.h" #include "AreaTrigger.h" -#include "Conversation.h" -#include "World.h" #include "CellImpl.h" +#include "Conversation.h" +#include "Corpse.h" +#include "Creature.h" #include "CreatureAI.h" +#include "DynamicObject.h" +#include "GameObject.h" #include "Log.h" +#include "ObjectAccessor.h" +#include "ObjectMgr.h" +#include "PhasingHandler.h" +#include "World.h" +#include "ScriptMgr.h" void ObjectGridEvacuator::Visit(CreatureMapType &m) { @@ -120,15 +122,47 @@ void LoadHelper(CellGuidSet const& guid_set, CellCoord &cell, GridRefManager<T> for (CellGuidSet::const_iterator i_guid = guid_set.begin(); i_guid != guid_set.end(); ++i_guid) { T* obj = new T; - ObjectGuid::LowType guid = *i_guid; - //TC_LOG_INFO("misc", "DEBUG: LoadHelper from table: %s for (guid: %u) Loading", table, guid); - if (!obj->LoadFromDB(guid, map)) + + // Don't spawn at all if there's a respawn time + if ((obj->GetTypeId() == TYPEID_UNIT && !map->GetCreatureRespawnTime(*i_guid)) || (obj->GetTypeId() == TYPEID_GAMEOBJECT && !map->GetGORespawnTime(*i_guid))) { - delete obj; - continue; - } + ObjectGuid::LowType guid = *i_guid; + //TC_LOG_INFO("misc", "DEBUG: LoadHelper from table: %s for (guid: %u) Loading", table, guid); + + if (obj->GetTypeId() == TYPEID_UNIT) + { + CreatureData const* cdata = sObjectMgr->GetCreatureData(guid); + ASSERT(cdata, "Tried to load creature with spawnId " UI64FMTD ", but no such creature exists.", guid); + SpawnGroupTemplateData const* const group = cdata->spawnGroupData; + // If creature in manual spawn group, don't spawn here, unless group is already active. + if ((group->flags & SPAWNGROUP_FLAG_MANUAL_SPAWN) && !group->isActive) + continue; + + // If script is blocking spawn, don't spawn but queue for a re-check in a little bit + if (!(group->flags & SPAWNGROUP_FLAG_COMPATIBILITY_MODE) && !sScriptMgr->CanSpawn(guid, cdata->id, cdata, map)) + { + map->SaveRespawnTime(SPAWN_TYPE_CREATURE, guid, cdata->id, time(NULL) + urand(4,7), map->GetZoneId(PhasingHandler::GetEmptyPhaseShift(), cdata->spawnPoint), Trinity::ComputeGridCoord(cdata->spawnPoint.GetPositionX(), cdata->spawnPoint.GetPositionY()).GetId(), false); + continue; + } + } + else if (obj->GetTypeId() == TYPEID_GAMEOBJECT) + { + // If gameobject in manual spawn group, don't spawn here, unless group is already active. + GameObjectData const* godata = sObjectMgr->GetGameObjectData(guid); + ASSERT(godata, "Tried to load gameobject with spawnId " UI64FMTD ", but no such object exists.", guid); + if ((godata->spawnGroupData->flags & SPAWNGROUP_FLAG_MANUAL_SPAWN) && !godata->spawnGroupData->isActive) + continue; + } - AddObjectHelper(cell, m, count, map, obj); + if (!obj->LoadFromDB(guid, map, false, false)) + { + delete obj; + continue; + } + AddObjectHelper(cell, m, count, map, obj); + } + else + delete obj; } } diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 8b2e2cab53e..984e67d2a5d 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -23,6 +23,7 @@ #include "DisableMgr.h" #include "DynamicTree.h" #include "GameObjectModel.h" +#include "GameTime.h" #include "GridNotifiers.h" #include "GridNotifiersImpl.h" #include "GridStates.h" @@ -41,6 +42,7 @@ #include "ObjectMgr.h" #include "Pet.h" #include "PhasingHandler.h" +#include "PoolMgr.h" #include "ScriptMgr.h" #include "Transport.h" #include "Vehicle.h" @@ -59,6 +61,7 @@ u_map_magic MapLiquidMagic = { {'M','L','I','Q'} }; #define DEFAULT_GRID_EXPIRY 300 #define MAX_GRID_LOAD_TIME 50 #define MAX_CREATURE_ATTACK_RADIUS (45.0f * sWorld->getRate(RATE_CREATURE_AGGRO)) +#define MAP_INVALID_ZONE 0xFFFFFFFF GridState* si_GridStates[MAX_GRID_STATE]; @@ -72,6 +75,10 @@ Map::~Map() sScriptMgr->OnDestroyMap(this); + // Delete all waiting spawns, else there will be a memory leak + // This doesn't delete from database. + DeleteRespawnInfo(); + while (!i_worldObjects.empty()) { WorldObject* obj = *i_worldObjects.begin(); @@ -318,7 +325,7 @@ m_unloadTimer(0), m_VisibleDistance(DEFAULT_VISIBILITY_DISTANCE), m_VisibilityNotifyPeriod(DEFAULT_VISIBILITY_NOTIFY_PERIOD), m_activeNonPlayersIter(m_activeNonPlayers.end()), _transportsUpdateIter(_transports.end()), i_gridExpiry(expiry), -i_scriptLock(false), _defaultLight(DB2Manager::GetDefaultMapLight(id)) +i_scriptLock(false), _respawnCheckTimer(0), _defaultLight(DB2Manager::GetDefaultMapLight(id)) { if (_parent) { @@ -344,6 +351,8 @@ i_scriptLock(false), _defaultLight(DB2Manager::GetDefaultMapLight(id)) } } + _zonePlayerCountMap.clear(); + //lets initialize visibility distance for map Map::InitVisibilityDistance(); @@ -635,6 +644,29 @@ void Map::LoadGridObjects(NGridType* grid, Cell const& cell) loader.LoadN(); } +void Map::GridMarkNoUnload(uint32 x, uint32 y) +{ + // First make sure this grid is loaded + float gX = ((float(x) - 0.5f - CENTER_GRID_ID) * SIZE_OF_GRIDS) + (CENTER_GRID_OFFSET * 2); + float gY = ((float(y) - 0.5f - CENTER_GRID_ID) * SIZE_OF_GRIDS) + (CENTER_GRID_OFFSET * 2); + Cell cell = Cell(gX, gY); + EnsureGridLoaded(cell); + + // Mark as don't unload + NGridType* grid = getNGrid(x, y); + grid->setUnloadExplicitLock(true); +} + +void Map::GridUnmarkNoUnload(uint32 x, uint32 y) +{ + // If grid is loaded, clear unload lock + if (IsGridLoaded(GridCoord(x, y))) + { + NGridType* grid = getNGrid(x, y); + grid->setUnloadExplicitLock(false); + } +} + void Map::LoadGrid(float x, float y) { EnsureGridLoaded(Cell(x, y)); @@ -807,7 +839,22 @@ void Map::VisitNearbyCellsOf(WorldObject* obj, TypeContainerVisitor<Trinity::Obj } } -void Map::Update(const uint32 t_diff) +void Map::UpdatePlayerZoneStats(uint32 oldZone, uint32 newZone) +{ + // Nothing to do if no change + if (oldZone == newZone) + return; + + if (oldZone != MAP_INVALID_ZONE) + { + uint32& oldZoneCount = _zonePlayerCountMap[oldZone]; + ASSERT(oldZoneCount, "A player left zone %u (went to %u) - but there were no players in the zone!", oldZone, newZone); + --oldZoneCount; + } + ++_zonePlayerCountMap[newZone]; +} + +void Map::Update(uint32 t_diff) { _dynamicTree.update(t_diff); /// update worldsessions for existing players @@ -822,6 +869,16 @@ void Map::Update(const uint32 t_diff) session->Update(t_diff, updater); } } + + /// process any due respawns + if (_respawnCheckTimer <= t_diff) + { + ProcessRespawns(); + _respawnCheckTimer = sWorld->getIntConfig(CONFIG_RESPAWN_MINCHECKINTERVALMS); + } + else + _respawnCheckTimer -= t_diff; + /// update active cells around players and active objects resetMarkedCells(); @@ -1013,6 +1070,8 @@ void Map::ProcessRelocationNotifies(const uint32 diff) void Map::RemovePlayerFromMap(Player* player, bool remove) { + // Before leaving map, update zone/area for stats + player->UpdateZone(MAP_INVALID_ZONE, 0); sScriptMgr->OnPlayerLeaveMap(this, player); player->getHostileRefManager().deleteReferences(); // multithreading crashfix @@ -3067,11 +3126,6 @@ bool Map::getObjectHitPos(PhaseShift const& phaseShift, float x1, float y1, floa return result; } -float Map::GetHeight(PhaseShift const& phaseShift, float x, float y, float z, bool vmap /*= true*/, float maxSearchDist /*= DEFAULT_HEIGHT_SEARCH*/) -{ - return std::max<float>(GetStaticHeight(phaseShift, x, y, z, vmap, maxSearchDist), GetGameObjectFloor(phaseShift, x, y, z, maxSearchDist)); -} - bool Map::IsInWater(PhaseShift const& phaseShift, float x, float y, float pZ, LiquidData* data) { LiquidData liquid_status; @@ -3227,7 +3281,329 @@ void Map::SendObjectUpdates() } } -void Map::DelayedUpdate(const uint32 t_diff) +bool Map::CheckRespawn(RespawnInfo* info) +{ + uint32 poolId = info->spawnId ? sPoolMgr->IsPartOfAPool(info->type, info->spawnId) : 0; + // First, check if there's already an instance of this object that would block the respawn + // Only do this for unpooled spawns + if (!poolId) + { + bool doDelete = false; + switch (info->type) + { + case SPAWN_TYPE_CREATURE: + { + // escort check for creatures only (if the world config boolean is set) + bool isEscort = false; + if (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC) && info->type == SPAWN_TYPE_CREATURE) + if (CreatureData const* cdata = sObjectMgr->GetCreatureData(info->spawnId)) + if (cdata->spawnGroupData->flags & SPAWNGROUP_FLAG_ESCORTQUESTNPC) + isEscort = true; + + auto range = _creatureBySpawnIdStore.equal_range(info->spawnId); + for (auto it = range.first; it != range.second; ++it) + { + Creature* creature = it->second; + if (!creature->IsAlive()) + continue; + // escort NPCs are allowed to respawn as long as all other instances are already escorting + if (isEscort && creature->IsEscortNPC(true)) + continue; + doDelete = true; + break; + } + break; + } + case SPAWN_TYPE_GAMEOBJECT: + // gameobject check is simpler - they cannot be dead or escorting + if (_gameobjectBySpawnIdStore.find(info->spawnId) != _gameobjectBySpawnIdStore.end()) + doDelete = true; + break; + default: + ASSERT(false, "Invalid spawn type %u with spawnId " UI64FMTD " on map %u", uint32(info->type), info->spawnId, GetId()); + return true; + } + if (doDelete) + { + info->respawnTime = 0; + return false; + } + } + + // next, check linked respawn time + ObjectGuid thisGUID = info->type == SPAWN_TYPE_GAMEOBJECT + ? ObjectGuid::Create<HighGuid::GameObject>(GetId(), info->entry, info->spawnId) + : ObjectGuid::Create<HighGuid::Creature>(GetId(), info->entry, info->spawnId); + if (time_t linkedTime = GetLinkedRespawnTime(thisGUID)) + { + time_t now = time(NULL); + time_t respawnTime; + if (sObjectMgr->GetLinkedRespawnGuid(thisGUID) == thisGUID) // never respawn, save "something" in DB + respawnTime = now + WEEK; + else // set us to check again shortly after linked unit + respawnTime = std::max<time_t>(now, linkedTime) + urand(5, 15); + info->respawnTime = respawnTime; + return false; + } + + // now, check if we're part of a pool + if (poolId) + { + // ok, part of a pool - hand off to pool logic to handle this, we're just going to remove the respawn and call it a day + if (info->type == SPAWN_TYPE_GAMEOBJECT) + sPoolMgr->UpdatePool<GameObject>(poolId, info->spawnId); + else if (info->type == SPAWN_TYPE_CREATURE) + sPoolMgr->UpdatePool<Creature>(poolId, info->spawnId); + else + ASSERT(false, "Invalid spawn type %u (spawnid " UI64FMTD ") on map %u", uint32(info->type), info->spawnId, GetId()); + info->respawnTime = 0; + return false; + } + + // if we're a creature, see if the script objects to us spawning + if (info->type == SPAWN_TYPE_CREATURE) + { + if (!sScriptMgr->CanSpawn(info->spawnId, info->entry, sObjectMgr->GetCreatureData(info->spawnId), this)) + { // if a script blocks our respawn, schedule next check in a little bit + info->respawnTime = time(NULL) + urand(4, 7); + return false; + } + } + return true; +} + +void Map::DoRespawn(SpawnObjectType type, ObjectGuid::LowType spawnId, uint32 gridId) +{ + if (!IsGridLoaded(gridId)) // if grid isn't loaded, this will be processed in grid load handler + return; + + switch (type) + { + case SPAWN_TYPE_CREATURE: + { + Creature* obj = new Creature(); + if (!obj->LoadFromDB(spawnId, this, true, true)) + delete obj; + break; + } + case SPAWN_TYPE_GAMEOBJECT: + { + GameObject* obj = new GameObject(); + if (!obj->LoadFromDB(spawnId, this, true)) + delete obj; + break; + } + default: + ASSERT(false, "Invalid spawn type %u (spawnid " UI64FMTD ") on map %u", uint32(type), spawnId, GetId()); + } +} + +void Map::Respawn(RespawnInfo* info, bool force, CharacterDatabaseTransaction dbTrans) +{ + if (!force && !CheckRespawn(info)) + { + if (info->respawnTime) + SaveRespawnTime(info->type, info->spawnId, info->entry, info->respawnTime, info->zoneId, info->gridId, true, true, dbTrans); + else + RemoveRespawnTime(info); + return; + } + + // remove the actual respawn record first - since this deletes it, we save what we need + SpawnObjectType const type = info->type; + uint32 const gridId = info->gridId; + ObjectGuid::LowType const spawnId = info->spawnId; + RemoveRespawnTime(info); + DoRespawn(type, spawnId, gridId); +} + +void Map::Respawn(RespawnVector& respawnData, bool force, CharacterDatabaseTransaction dbTrans) +{ + CharacterDatabaseTransaction trans = dbTrans ? dbTrans : CharacterDatabase.BeginTransaction(); + for (RespawnInfo* info : respawnData) + Respawn(info, force, trans); + if (!dbTrans) + CharacterDatabase.CommitTransaction(trans); +} + +void Map::AddRespawnInfo(RespawnInfo& info, bool replace) +{ + if (!info.spawnId) + return; + + RespawnInfoMap& bySpawnIdMap = GetRespawnMapForType(info.type); + + auto it = bySpawnIdMap.find(info.spawnId); + if (it != bySpawnIdMap.end()) // spawnid already has a respawn scheduled + { + RespawnInfo* const existing = it->second; + if (replace || info.respawnTime < existing->respawnTime) // delete existing in this case + DeleteRespawnInfo(existing); + else // don't delete existing, instead replace respawn time so caller saves the correct time + { + info.respawnTime = existing->respawnTime; + return; + } + } + + // if we get to this point, we should insert the respawninfo (there either was no prior entry, or it was deleted already) + RespawnInfo * ri = new RespawnInfo(info); + ri->handle = _respawnTimes.push(ri); + bool success = bySpawnIdMap.emplace(ri->spawnId, ri).second; + ASSERT(success, "Insertion of respawn info with id (%u," UI64FMTD ") into spawn id map failed - state desync.", uint32(ri->type), ri->spawnId); +} + +static void PushRespawnInfoFrom(RespawnVector& data, RespawnInfoMap const& map, uint32 zoneId) +{ + for (auto const& pair : map) + if (!zoneId || pair.second->zoneId == zoneId) + data.push_back(pair.second); +} +void Map::GetRespawnInfo(RespawnVector& respawnData, SpawnObjectTypeMask types, uint32 zoneId) const +{ + if (types & SPAWN_TYPEMASK_CREATURE) + PushRespawnInfoFrom(respawnData, _creatureRespawnTimesBySpawnId, zoneId); + if (types & SPAWN_TYPEMASK_GAMEOBJECT) + PushRespawnInfoFrom(respawnData, _gameObjectRespawnTimesBySpawnId, zoneId); +} + +RespawnInfo* Map::GetRespawnInfo(SpawnObjectType type, ObjectGuid::LowType spawnId) const +{ + RespawnInfoMap const& map = GetRespawnMapForType(type); + auto it = map.find(spawnId); + if (it == map.end()) + return nullptr; + return it->second; +} + +void Map::DeleteRespawnInfo() // delete everything +{ + for (RespawnInfo* info : _respawnTimes) + delete info; + _respawnTimes.clear(); + _creatureRespawnTimesBySpawnId.clear(); + _gameObjectRespawnTimesBySpawnId.clear(); +} + +void Map::DeleteRespawnInfo(RespawnInfo* info) +{ + // Delete from all relevant containers to ensure consistency + ASSERT(info); + + // spawnid store + size_t const n = GetRespawnMapForType(info->type).erase(info->spawnId); + ASSERT(n == 1, "Respawn stores inconsistent for map %u, spawnid " UI64FMTD " (type %u)", GetId(), info->spawnId, uint32(info->type)); + + //respawn heap + _respawnTimes.erase(info->handle); + + // then cleanup the object + delete info; +} + +void Map::RemoveRespawnTime(RespawnInfo* info, bool doRespawn, CharacterDatabaseTransaction dbTrans) +{ + CharacterDatabasePreparedStatement* stmt; + switch (info->type) + { + case SPAWN_TYPE_CREATURE: + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CREATURE_RESPAWN); + break; + case SPAWN_TYPE_GAMEOBJECT: + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GO_RESPAWN); + break; + default: + ASSERT(false, "Invalid respawninfo type %u for spawnid " UI64FMTD " map %u", uint32(info->type), info->spawnId, GetId()); + return; + } + stmt->setUInt64(0, info->spawnId); + stmt->setUInt16(1, GetId()); + stmt->setUInt32(2, GetInstanceId()); + CharacterDatabase.ExecuteOrAppend(dbTrans, stmt); + + if (doRespawn) + Respawn(info); + else + DeleteRespawnInfo(info); +} + +void Map::RemoveRespawnTime(RespawnVector& respawnData, bool doRespawn, CharacterDatabaseTransaction dbTrans) +{ + CharacterDatabaseTransaction trans = dbTrans ? dbTrans : CharacterDatabase.BeginTransaction(); + for (RespawnInfo* info : respawnData) + RemoveRespawnTime(info, doRespawn, trans); + if (!dbTrans) + CharacterDatabase.CommitTransaction(trans); +} + +void Map::ProcessRespawns() +{ + time_t now = time(NULL); + while (!_respawnTimes.empty()) + { + RespawnInfo* next = _respawnTimes.top(); + if (now < next->respawnTime) // done for this tick + break; + if (CheckRespawn(next)) // see if we're allowed to respawn + { + // ok, respawn + _respawnTimes.pop(); + GetRespawnMapForType(next->type).erase(next->spawnId); + DoRespawn(next->type, next->spawnId, next->gridId); + delete next; + } + else if (!next->respawnTime) // just remove respawn entry without rescheduling + { + _respawnTimes.pop(); + GetRespawnMapForType(next->type).erase(next->spawnId); + delete next; + } + else // value changed, update heap position + { + ASSERT(now < next->respawnTime); // infinite loop guard + _respawnTimes.decrease(next->handle); + } + } +} + +void Map::ApplyDynamicModeRespawnScaling(WorldObject const* obj, ObjectGuid::LowType spawnId, uint32& respawnDelay, uint32 mode) const +{ + ASSERT(mode == 1); + ASSERT(obj->GetMap() == this); + SpawnObjectType type; + switch (obj->GetTypeId()) + { + case TYPEID_UNIT: + type = SPAWN_TYPE_CREATURE; + break; + case TYPEID_GAMEOBJECT: + type = SPAWN_TYPE_GAMEOBJECT; + break; + default: + return; + } + + SpawnData const* data = sObjectMgr->GetSpawnData(type, spawnId); + if (!data || !(data->spawnGroupData->flags & SPAWNGROUP_FLAG_DYNAMIC_SPAWN_RATE)) + return; + + auto it = _zonePlayerCountMap.find(obj->GetZoneId()); + if (it == _zonePlayerCountMap.end()) + return; + uint32 const playerCount = it->second; + if (!playerCount) + return; + double const adjustFactor = sWorld->getFloatConfig(type == SPAWN_TYPE_GAMEOBJECT ? CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT : CONFIG_RESPAWN_DYNAMICRATE_CREATURE) / playerCount; + if (adjustFactor >= 1.0) // nothing to do here + return; + uint32 const timeMinimum = sWorld->getIntConfig(type == SPAWN_TYPE_GAMEOBJECT ? CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT : CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE); + if (respawnDelay <= timeMinimum) + return; + + respawnDelay = std::max<uint32>(ceil(respawnDelay * adjustFactor), timeMinimum); +} + +void Map::DelayedUpdate(uint32 t_diff) { for (_transportsUpdateIter = _transports.begin(); _transportsUpdateIter != _transports.end();) { @@ -3681,7 +4057,7 @@ bool InstanceMap::AddPlayerToMap(Player* player, bool initPlayer /*= true*/) return true; } -void InstanceMap::Update(const uint32 t_diff) +void InstanceMap::Update(uint32 t_diff) { Map::Update(t_diff); @@ -4123,70 +4499,71 @@ Transport* Map::GetTransport(ObjectGuid const& guid) return go ? go->ToTransport() : nullptr; } -void Map::UpdateIteratorBack(Player* player) +Creature* Map::GetCreatureBySpawnId(ObjectGuid::LowType spawnId) const { - if (m_mapRefIter == player->GetMapRef()) - m_mapRefIter = m_mapRefIter->nocheck_prev(); + auto const bounds = GetCreatureBySpawnIdStore().equal_range(spawnId); + if (bounds.first == bounds.second) + return nullptr; + + std::unordered_multimap<ObjectGuid::LowType, Creature*>::const_iterator creatureItr = std::find_if(bounds.first, bounds.second, [](Map::CreatureBySpawnIdContainer::value_type const& pair) + { + return pair.second->IsAlive(); + }); + + return creatureItr != bounds.second ? creatureItr->second : bounds.first->second; } -void Map::SaveCreatureRespawnTime(ObjectGuid::LowType dbGuid, time_t respawnTime) +GameObject* Map::GetGameObjectBySpawnId(ObjectGuid::LowType spawnId) const { - if (!respawnTime) - { - // Delete only - RemoveCreatureRespawnTime(dbGuid); - return; - } + auto const bounds = GetGameObjectBySpawnIdStore().equal_range(spawnId); + if (bounds.first == bounds.second) + return nullptr; - _creatureRespawnTimes[dbGuid] = respawnTime; + std::unordered_multimap<ObjectGuid::LowType, GameObject*>::const_iterator creatureItr = std::find_if(bounds.first, bounds.second, [](Map::GameObjectBySpawnIdContainer::value_type const& pair) + { + return pair.second->isSpawned(); + }); - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CREATURE_RESPAWN); - stmt->setUInt64(0, dbGuid); - stmt->setUInt64(1, uint64(respawnTime)); - stmt->setUInt16(2, GetId()); - stmt->setUInt32(3, GetInstanceId()); - CharacterDatabase.Execute(stmt); + return creatureItr != bounds.second ? creatureItr->second : bounds.first->second; } -void Map::RemoveCreatureRespawnTime(ObjectGuid::LowType dbGuid) +void Map::UpdateIteratorBack(Player* player) { - _creatureRespawnTimes.erase(dbGuid); - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CREATURE_RESPAWN); - stmt->setUInt64(0, dbGuid); - stmt->setUInt16(1, GetId()); - stmt->setUInt32(2, GetInstanceId()); - CharacterDatabase.Execute(stmt); + if (m_mapRefIter == player->GetMapRef()) + m_mapRefIter = m_mapRefIter->nocheck_prev(); } -void Map::SaveGORespawnTime(ObjectGuid::LowType dbGuid, time_t respawnTime) +void Map::SaveRespawnTime(SpawnObjectType type, ObjectGuid::LowType spawnId, uint32 entry, time_t respawnTime, uint32 zoneId, uint32 gridId, bool writeDB, bool replace, CharacterDatabaseTransaction dbTrans) { if (!respawnTime) { // Delete only - RemoveGORespawnTime(dbGuid); + RemoveRespawnTime(type, spawnId, false, dbTrans); return; } - _goRespawnTimes[dbGuid] = respawnTime; + RespawnInfo ri; + ri.type = type; + ri.spawnId = spawnId; + ri.entry = entry; + ri.respawnTime = respawnTime; + ri.gridId = gridId; + ri.zoneId = zoneId; + AddRespawnInfo(ri, replace); - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_GO_RESPAWN); - stmt->setUInt64(0, dbGuid); - stmt->setUInt64(1, uint64(respawnTime)); - stmt->setUInt16(2, GetId()); - stmt->setUInt32(3, GetInstanceId()); - CharacterDatabase.Execute(stmt); + if (writeDB) + SaveRespawnTimeDB(type, spawnId, ri.respawnTime, dbTrans); // might be different from original respawn time if we didn't replace } -void Map::RemoveGORespawnTime(ObjectGuid::LowType dbGuid) +void Map::SaveRespawnTimeDB(SpawnObjectType type, ObjectGuid::LowType spawnId, time_t respawnTime, CharacterDatabaseTransaction dbTrans) { - _goRespawnTimes.erase(dbGuid); - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GO_RESPAWN); - stmt->setUInt64(0, dbGuid); - stmt->setUInt16(1, GetId()); - stmt->setUInt32(2, GetInstanceId()); - CharacterDatabase.Execute(stmt); + // Just here for support of compatibility mode + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement((type == SPAWN_TYPE_GAMEOBJECT) ? CHAR_REP_GO_RESPAWN : CHAR_REP_CREATURE_RESPAWN); + stmt->setUInt64(0, spawnId); + stmt->setUInt64(1, uint64(respawnTime)); + stmt->setUInt16(2, GetId()); + stmt->setUInt32(3, GetInstanceId()); + CharacterDatabase.ExecuteOrAppend(dbTrans, stmt); } void Map::LoadRespawnTimes() @@ -4202,7 +4579,9 @@ void Map::LoadRespawnTimes() ObjectGuid::LowType loguid = fields[0].GetUInt64(); uint64 respawnTime = fields[1].GetUInt64(); - _creatureRespawnTimes[loguid] = time_t(respawnTime); + if (CreatureData const* cdata = sObjectMgr->GetCreatureData(loguid)) + SaveRespawnTime(SPAWN_TYPE_CREATURE, loguid, cdata->id, time_t(respawnTime), GetZoneId(PhasingHandler::GetEmptyPhaseShift(), cdata->spawnPoint), Trinity::ComputeGridCoord(cdata->spawnPoint.GetPositionX(), cdata->spawnPoint.GetPositionY()).GetId(), false); + } while (result->NextRow()); } @@ -4217,19 +4596,13 @@ void Map::LoadRespawnTimes() ObjectGuid::LowType loguid = fields[0].GetUInt64(); uint64 respawnTime = fields[1].GetUInt64(); - _goRespawnTimes[loguid] = time_t(respawnTime); + if (GameObjectData const* godata = sObjectMgr->GetGameObjectData(loguid)) + SaveRespawnTime(SPAWN_TYPE_GAMEOBJECT, loguid, godata->id, time_t(respawnTime), GetZoneId(PhasingHandler::GetEmptyPhaseShift(), godata->spawnPoint), Trinity::ComputeGridCoord(godata->spawnPoint.GetPositionX(), godata->spawnPoint.GetPositionY()).GetId(), false); + } while (result->NextRow()); } } -void Map::DeleteRespawnTimes() -{ - _creatureRespawnTimes.clear(); - _goRespawnTimes.clear(); - - DeleteRespawnTimesInDB(GetId(), GetInstanceId()); -} - void Map::DeleteRespawnTimesInDB(uint16 mapId, uint32 instanceId) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CREATURE_RESPAWN_BY_INSTANCE); diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h index d52a8976f20..7b3426c8d6e 100644 --- a/src/server/game/Maps/Map.h +++ b/src/server/game/Maps/Map.h @@ -20,16 +20,18 @@ #include "Define.h" -#include "GridDefines.h" #include "Cell.h" -#include "Timer.h" -#include "SharedDefines.h" +#include "DatabaseEnvFwd.h" +#include "DynamicTree.h" +#include "GridDefines.h" #include "GridRefManager.h" #include "MapRefManager.h" -#include "DynamicTree.h" #include "ObjectGuid.h" #include "Optional.h" - +#include "SharedDefines.h" +#include "SpawnData.h" +#include "Timer.h" +#include <boost/heap/fibonacci_heap.hpp> #include <bitset> #include <list> #include <memory> @@ -281,7 +283,37 @@ struct ZoneDynamicInfo typedef std::map<ObjectGuid::LowType/*leaderDBGUID*/, CreatureGroup*> CreatureGroupHolderType; +struct RespawnInfo; // forward declaration +struct CompareRespawnInfo +{ + bool operator()(RespawnInfo const* a, RespawnInfo const* b) const; +}; typedef std::unordered_map<uint32 /*zoneId*/, ZoneDynamicInfo> ZoneDynamicInfoMap; +typedef boost::heap::fibonacci_heap<RespawnInfo*, boost::heap::compare<CompareRespawnInfo>> RespawnListContainer; +typedef RespawnListContainer::handle_type RespawnListHandle; +typedef std::unordered_map<ObjectGuid::LowType, RespawnInfo*> RespawnInfoMap; +typedef std::vector<RespawnInfo*> RespawnVector; +struct RespawnInfo +{ + SpawnObjectType type; + ObjectGuid::LowType spawnId; + uint32 entry; + time_t respawnTime; + uint32 gridId; + uint32 zoneId; + RespawnListHandle handle; +}; +inline bool CompareRespawnInfo::operator()(RespawnInfo const* a, RespawnInfo const* b) const +{ + if (a == b) + return false; + if (a->respawnTime != b->respawnTime) + return (a->respawnTime > b->respawnTime); + if (a->spawnId != b->spawnId) + return a->spawnId < b->spawnId; + ASSERT(a->type != b->type, "Duplicate respawn entry for spawnId (%u," UI64FMTD ") found!", a->type, a->spawnId); + return a->type < b->type; +} typedef TypeUnorderedMapContainer<AllMapStoredObjectTypes, ObjectGuid> MapStoredObjectTypesContainer; @@ -314,7 +346,7 @@ class TC_GAME_API Map : public GridRefManager<NGridType> template<class T> void RemoveFromMap(T *, bool); void VisitNearbyCellsOf(WorldObject* obj, TypeContainerVisitor<Trinity::ObjectUpdater, GridTypeMapContainer> &gridVisitor, TypeContainerVisitor<Trinity::ObjectUpdater, WorldTypeMapContainer> &worldVisitor); - virtual void Update(const uint32); + virtual void Update(uint32); float GetVisibilityRange() const { return m_VisibleDistance; } //function for setting up visibility distance for maps on per-type/per-Id basis @@ -334,17 +366,19 @@ class TC_GAME_API Map : public GridRefManager<NGridType> GridCoord p = Trinity::ComputeGridCoord(x, y); return !getNGrid(p.x_coord, p.y_coord) || getNGrid(p.x_coord, p.y_coord)->GetGridState() == GRID_STATE_REMOVAL; } + bool IsRemovalGrid(Position const& pos) const { return IsRemovalGrid(pos.GetPositionX(), pos.GetPositionY()); } - bool IsGridLoaded(float x, float y) const - { - return IsGridLoaded(Trinity::ComputeGridCoord(x, y)); - } + bool IsGridLoaded(uint32 gridId) const { return IsGridLoaded(GridCoord(gridId % MAX_NUMBER_OF_GRIDS, gridId / MAX_NUMBER_OF_GRIDS)); } + bool IsGridLoaded(float x, float y) const { return IsGridLoaded(Trinity::ComputeGridCoord(x, y)); } + bool IsGridLoaded(Position const& pos) const { return IsGridLoaded(pos.GetPositionX(), pos.GetPositionY()); } bool GetUnloadLock(const GridCoord &p) const { return getNGrid(p.x_coord, p.y_coord)->getUnloadLock(); } void SetUnloadLock(const GridCoord &p, bool on) { getNGrid(p.x_coord, p.y_coord)->setUnloadExplicitLock(on); } void LoadGrid(float x, float y); void LoadAllCells(); bool UnloadGrid(NGridType& ngrid, bool pForce); + void GridMarkNoUnload(uint32 x, uint32 y); + void GridUnmarkNoUnload(uint32 x, uint32 y); virtual void UnloadAll(); void ResetGridExpiry(NGridType &grid, float factor = 1) const @@ -367,18 +401,17 @@ class TC_GAME_API Map : public GridRefManager<NGridType> void AddChildTerrainMap(Map* map) { m_childTerrainMaps->push_back(map); map->m_parentTerrainMap = this; } void UnlinkAllChildTerrainMaps() { m_childTerrainMaps->clear(); } - // some calls like isInWater should not use vmaps due to processor power - // can return INVALID_HEIGHT if under z+2 z coord not found height - float GetStaticHeight(PhaseShift const& phaseShift, float x, float y, float z, bool checkVMap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH); - float GetMinHeight(PhaseShift const& phaseShift, float x, float y); - + void GetFullTerrainStatusForPosition(float x, float y, float z, PositionFullTerrainStatus& data, uint8 reqLiquidType = MAP_ALL_LIQUIDS) const; void GetFullTerrainStatusForPosition(PhaseShift const& phaseShift, float x, float y, float z, PositionFullTerrainStatus& data, uint8 reqLiquidType = MAP_ALL_LIQUIDS); ZLiquidStatus GetLiquidStatus(PhaseShift const& phaseShift, float x, float y, float z, uint8 ReqLiquidType, LiquidData* data = nullptr); bool GetAreaInfo(PhaseShift const& phaseShift, float x, float y, float z, uint32& mogpflags, int32& adtId, int32& rootId, int32& groupId); uint32 GetAreaId(PhaseShift const& phaseShift, float x, float y, float z, bool *isOutdoors = nullptr); + uint32 GetAreaId(PhaseShift const& phaseShift, Position const& pos) { return GetAreaId(phaseShift, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } uint32 GetZoneId(PhaseShift const& phaseShift, float x, float y, float z); + uint32 GetZoneId(PhaseShift const& phaseShift, Position const& pos) { return GetZoneId(phaseShift, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } void GetZoneAndAreaId(PhaseShift const& phaseShift, uint32& zoneid, uint32& areaid, float x, float y, float z); + void GetZoneAndAreaId(PhaseShift const& phaseShift, uint32& zoneid, uint32& areaid, Position const& pos) { GetZoneAndAreaId(phaseShift, zoneid, areaid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } bool IsOutdoors(PhaseShift const& phaseShift, float x, float y, float z); @@ -488,14 +521,19 @@ class TC_GAME_API Map : public GridRefManager<NGridType> GameObject* GetGameObject(ObjectGuid const& guid); Pet* GetPet(ObjectGuid const& guid); Transport* GetTransport(ObjectGuid const& guid); + Creature* GetCreatureBySpawnId(ObjectGuid::LowType spawnId) const; + GameObject* GetGameObjectBySpawnId(ObjectGuid::LowType spawnId) const; + WorldObject* GetWorldObjectBySpawnId(SpawnObjectType type, ObjectGuid::LowType spawnId) const { return (type == SPAWN_TYPE_GAMEOBJECT) ? reinterpret_cast<WorldObject*>(GetGameObjectBySpawnId(spawnId)) : reinterpret_cast<WorldObject*>(GetCreatureBySpawnId(spawnId)); } MapStoredObjectTypesContainer& GetObjectsStore() { return _objectsStore; } typedef std::unordered_multimap<ObjectGuid::LowType, Creature*> CreatureBySpawnIdContainer; CreatureBySpawnIdContainer& GetCreatureBySpawnIdStore() { return _creatureBySpawnIdStore; } + CreatureBySpawnIdContainer const& GetCreatureBySpawnIdStore() const { return _creatureBySpawnIdStore; } typedef std::unordered_multimap<ObjectGuid::LowType, GameObject*> GameObjectBySpawnIdContainer; GameObjectBySpawnIdContainer& GetGameObjectBySpawnIdStore() { return _gameobjectBySpawnIdStore; } + GameObjectBySpawnIdContainer const& GetGameObjectBySpawnIdStore() const { return _gameobjectBySpawnIdStore; } std::unordered_set<Corpse*> const* GetCorpsesInCell(uint32 cellId) const { @@ -525,7 +563,11 @@ class TC_GAME_API Map : public GridRefManager<NGridType> BattlegroundMap const* ToBattlegroundMap() const { if (IsBattlegroundOrArena()) return reinterpret_cast<BattlegroundMap const*>(this); return nullptr; } float GetWaterOrGroundLevel(PhaseShift const& phaseShift, float x, float y, float z, float* ground = nullptr, bool swim = false); - float GetHeight(PhaseShift const& phaseShift, float x, float y, float z, bool vmap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH); + float GetMinHeight(PhaseShift const& phaseShift, float x, float y); + float GetStaticHeight(PhaseShift const& phaseShift, float x, float y, float z, bool checkVMap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH); + float GetStaticHeight(PhaseShift const& phaseShift, Position const& pos, bool checkVMap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) { return GetStaticHeight(phaseShift, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), checkVMap, maxSearchDist); } + float GetHeight(PhaseShift const& phaseShift, float x, float y, float z, bool vmap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) { return std::max<float>(GetStaticHeight(phaseShift, x, y, z, vmap, maxSearchDist), GetGameObjectFloor(phaseShift, x, y, z, maxSearchDist)); } + float GetHeight(PhaseShift const& phaseShift, Position const& pos, bool vmap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) { return GetHeight(phaseShift, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), vmap, maxSearchDist); } bool isInLineOfSight(PhaseShift const& phaseShift, float x1, float y1, float z1, float x2, float y2, float z2, LineOfSightChecks checks, VMAP::ModelIgnoreFlags ignoreFlags) const; void Balance() { _dynamicTree.balance(); } void RemoveGameObjectModel(const GameObjectModel& model) { _dynamicTree.remove(model); } @@ -544,28 +586,24 @@ class TC_GAME_API Map : public GridRefManager<NGridType> time_t GetLinkedRespawnTime(ObjectGuid guid) const; time_t GetCreatureRespawnTime(ObjectGuid::LowType dbGuid) const { - std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t>::const_iterator itr = _creatureRespawnTimes.find(dbGuid); - if (itr != _creatureRespawnTimes.end()) - return itr->second; - - return time_t(0); + RespawnInfoMap::const_iterator itr = _creatureRespawnTimesBySpawnId.find(dbGuid); + return itr != _creatureRespawnTimesBySpawnId.end() ? itr->second->respawnTime : 0; } time_t GetGORespawnTime(ObjectGuid::LowType dbGuid) const { - std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t>::const_iterator itr = _goRespawnTimes.find(dbGuid); - if (itr != _goRespawnTimes.end()) - return itr->second; - - return time_t(0); + RespawnInfoMap::const_iterator itr = _gameObjectRespawnTimesBySpawnId.find(dbGuid); + return itr != _gameObjectRespawnTimesBySpawnId.end() ? itr->second->respawnTime : 0; } - void SaveCreatureRespawnTime(ObjectGuid::LowType dbGuid, time_t respawnTime); - void RemoveCreatureRespawnTime(ObjectGuid::LowType dbGuid); - void SaveGORespawnTime(ObjectGuid::LowType dbGuid, time_t respawnTime); - void RemoveGORespawnTime(ObjectGuid::LowType dbGuid); + time_t GetRespawnTime(SpawnObjectType type, ObjectGuid::LowType spawnId) const { return (type == SPAWN_TYPE_GAMEOBJECT) ? GetGORespawnTime(spawnId) : GetCreatureRespawnTime(spawnId); } + + void UpdatePlayerZoneStats(uint32 oldZone, uint32 newZone); + + void SaveRespawnTime(SpawnObjectType type, ObjectGuid::LowType spawnId, uint32 entry, time_t respawnTime, uint32 zoneId, uint32 gridId = 0, bool writeDB = true, bool replace = false, CharacterDatabaseTransaction dbTrans = nullptr); + void SaveRespawnTimeDB(SpawnObjectType type, ObjectGuid::LowType spawnId, time_t respawnTime, CharacterDatabaseTransaction dbTrans = nullptr); void LoadRespawnTimes(); - void DeleteRespawnTimes(); + void DeleteRespawnTimes() { DeleteRespawnInfo(); DeleteRespawnTimesInDB(GetId(), GetInstanceId()); } void LoadCorpseData(); void DeleteCorpseData(); @@ -733,6 +771,59 @@ class TC_GAME_API Map : public GridRefManager<NGridType> typedef std::multimap<time_t, ScriptAction> ScriptScheduleMap; ScriptScheduleMap m_scriptSchedule; + public: + void ProcessRespawns(); + void ApplyDynamicModeRespawnScaling(WorldObject const* obj, ObjectGuid::LowType spawnId, uint32& respawnDelay, uint32 mode) const; + + private: + // if return value is true, we can respawn + // if return value is false, reschedule the respawn to new value of info->respawnTime iff nonzero, delete otherwise + // if return value is false and info->respawnTime is nonzero, it is guaranteed to be greater than time(NULL) + bool CheckRespawn(RespawnInfo* info); + void DoRespawn(SpawnObjectType type, ObjectGuid::LowType spawnId, uint32 gridId); + void Respawn(RespawnInfo* info, bool force = false, CharacterDatabaseTransaction dbTrans = nullptr); + void Respawn(RespawnVector& respawnData, bool force = false, CharacterDatabaseTransaction dbTrans = nullptr); + void AddRespawnInfo(RespawnInfo& info, bool replace = false); + void DeleteRespawnInfo(); + void DeleteRespawnInfo(RespawnInfo* info); + void DeleteRespawnInfo(RespawnVector& toDelete) + { + for (RespawnInfo* info : toDelete) + DeleteRespawnInfo(info); + toDelete.clear(); + } + void DeleteRespawnInfo(SpawnObjectTypeMask types, uint32 zoneId = 0) + { + RespawnVector v; + GetRespawnInfo(v, types, zoneId); + if (!v.empty()) + DeleteRespawnInfo(v); + } + void DeleteRespawnInfo(SpawnObjectType type, ObjectGuid::LowType spawnId) + { + if (RespawnInfo* info = GetRespawnInfo(type, spawnId)) + DeleteRespawnInfo(info); + } + + public: + void GetRespawnInfo(RespawnVector& respawnData, SpawnObjectTypeMask types, uint32 zoneId = 0) const; + RespawnInfo* GetRespawnInfo(SpawnObjectType type, ObjectGuid::LowType spawnId) const; + void RemoveRespawnTime(RespawnInfo* info, bool doRespawn = false, CharacterDatabaseTransaction dbTrans = nullptr); + void RemoveRespawnTime(RespawnVector& respawnData, bool doRespawn = false, CharacterDatabaseTransaction dbTrans = nullptr); + void RemoveRespawnTime(SpawnObjectTypeMask types = SPAWN_TYPEMASK_ALL, uint32 zoneId = 0, bool doRespawn = false, CharacterDatabaseTransaction dbTrans = nullptr) + { + RespawnVector v; + GetRespawnInfo(v, types, zoneId); + if (!v.empty()) + RemoveRespawnTime(v, doRespawn, dbTrans); + } + void RemoveRespawnTime(SpawnObjectType type, ObjectGuid::LowType spawnId, bool doRespawn = false, CharacterDatabaseTransaction dbTrans = nullptr) + { + if (RespawnInfo* info = GetRespawnInfo(type, spawnId)) + RemoveRespawnTime(info, doRespawn, dbTrans); + } + + private: // Type specific code for add/remove to/from grid template<class T> void AddToGrid(T* object, Cell const& cell); @@ -761,8 +852,14 @@ class TC_GAME_API Map : public GridRefManager<NGridType> m_activeNonPlayers.erase(obj); } - std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t> _creatureRespawnTimes; - std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t> _goRespawnTimes; + RespawnListContainer _respawnTimes; + RespawnInfoMap _creatureRespawnTimesBySpawnId; + RespawnInfoMap _gameObjectRespawnTimesBySpawnId; + RespawnInfoMap& GetRespawnMapForType(SpawnObjectType type) { return (type == SPAWN_TYPE_GAMEOBJECT) ? _gameObjectRespawnTimesBySpawnId : _creatureRespawnTimesBySpawnId; } + RespawnInfoMap const& GetRespawnMapForType(SpawnObjectType type) const { return (type == SPAWN_TYPE_GAMEOBJECT) ? _gameObjectRespawnTimesBySpawnId : _creatureRespawnTimesBySpawnId; } + + uint32 _respawnCheckTimer; + std::unordered_map<uint32, uint32> _zonePlayerCountMap; ZoneDynamicInfoMap _zoneDynamicInfo; IntervalTimer _weatherUpdateTimer; @@ -806,7 +903,7 @@ class TC_GAME_API InstanceMap : public Map ~InstanceMap(); bool AddPlayerToMap(Player* player, bool initPlayer = true) override; void RemovePlayerFromMap(Player*, bool) override; - void Update(const uint32) override; + void Update(uint32) override; void CreateInstanceData(bool load); bool Reset(uint8 method); uint32 GetScriptId() const { return i_script_id; } diff --git a/src/server/game/Maps/MapManager.h b/src/server/game/Maps/MapManager.h index 345cab478e1..9ff42ff557f 100644 --- a/src/server/game/Maps/MapManager.h +++ b/src/server/game/Maps/MapManager.h @@ -44,16 +44,22 @@ class TC_GAME_API MapManager Map* m = CreateBaseMap(mapid); return m->GetAreaId(phaseShift, x, y, z); } + uint32 GetAreaId(PhaseShift const& phaseShift, uint32 mapid, Position const& pos) { return GetAreaId(phaseShift, mapid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } + uint32 GetAreaId(PhaseShift const& phaseShift, WorldLocation const& loc) { return GetAreaId(phaseShift, loc.GetMapId(), loc); } uint32 GetZoneId(PhaseShift const& phaseShift, uint32 mapid, float x, float y, float z) { Map* m = CreateBaseMap(mapid); return m->GetZoneId(phaseShift, x, y, z); } + uint32 GetZoneId(PhaseShift const& phaseShift, uint32 mapid, Position const& pos) { return GetZoneId(phaseShift, mapid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } + uint32 GetZoneId(PhaseShift const& phaseShift, WorldLocation const& loc) { return GetZoneId(phaseShift, loc.GetMapId(), loc); } void GetZoneAndAreaId(PhaseShift const& phaseShift, uint32& zoneid, uint32& areaid, uint32 mapid, float x, float y, float z) { Map* m = CreateBaseMap(mapid); m->GetZoneAndAreaId(phaseShift, zoneid, areaid, x, y, z); } + void GetZoneAndAreaId(PhaseShift const& phaseShift, uint32& zoneid, uint32& areaid, uint32 mapid, Position const& pos) { GetZoneAndAreaId(phaseShift, zoneid, areaid, mapid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } + void GetZoneAndAreaId(PhaseShift const& phaseShift, uint32& zoneid, uint32& areaid, WorldLocation const& loc) { GetZoneAndAreaId(phaseShift, zoneid, areaid, loc.GetMapId(), loc); } void Initialize(); void InitializeParentMapData(std::unordered_map<uint32, std::vector<uint32>> const& mapData); diff --git a/src/server/game/Maps/SpawnData.h b/src/server/game/Maps/SpawnData.h new file mode 100644 index 00000000000..5d0c8d17e9d --- /dev/null +++ b/src/server/game/Maps/SpawnData.h @@ -0,0 +1,82 @@ +/* + * This file is part of the TrinityCore 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/>. + */ + +#ifndef TRINITY_SPAWNDATA_H +#define TRINITY_SPAWNDATA_H + +#include "DBCEnums.h" +#include "Position.h" +#include <vector> + +enum SpawnObjectType +{ + SPAWN_TYPE_CREATURE = 0, + SPAWN_TYPE_GAMEOBJECT = 1, + + SPAWN_TYPE_MAX +}; + +enum SpawnObjectTypeMask +{ + SPAWN_TYPEMASK_CREATURE = (1 << SPAWN_TYPE_CREATURE), + SPAWN_TYPEMASK_GAMEOBJECT = (1 << SPAWN_TYPE_GAMEOBJECT), + + SPAWN_TYPEMASK_ALL = (1 << SPAWN_TYPE_MAX)-1 +}; + +enum SpawnGroupFlags +{ + 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_FLAGS_ALL = (SPAWNGROUP_FLAG_SYSTEM | SPAWNGROUP_FLAG_COMPATIBILITY_MODE | SPAWNGROUP_FLAG_MANUAL_SPAWN | SPAWNGROUP_FLAG_DYNAMIC_SPAWN_RATE | SPAWNGROUP_FLAG_ESCORTQUESTNPC) +}; + +struct SpawnGroupTemplateData +{ + uint32 groupId; + std::string name; + uint32 mapId; + SpawnGroupFlags flags; + bool isActive; +}; + +struct SpawnData +{ + SpawnObjectType const type; + uint64 spawnId = 0; + uint32 id = 0; // entry in respective _template table + WorldLocation spawnPoint; + uint8 phaseUseFlags = 0; + uint32 phaseId = 0; + uint32 phaseGroup = 0; + int32 terrainSwapMap = -1; + int32 spawntimesecs = 0; + std::vector<Difficulty> spawnDifficulties; + SpawnGroupTemplateData const* spawnGroupData = nullptr; + uint32 scriptId = 0; + bool dbData = true; + + protected: + SpawnData(SpawnObjectType t) : type(t) {} +}; + +#endif diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index df3f8a55ab1..dc0d9fbdee4 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -1054,12 +1054,26 @@ enum TrinityStrings LANG_DEBUG_SCENE_OBJECT_LIST = 5068, LANG_DEBUG_SCENE_OBJECT_DETAIL = 5069, - LANG_NPCINFO_UNIT_FIELD_FLAGS_2 = 5070, - LANG_NPCINFO_UNIT_FIELD_FLAGS_3 = 5071, - LANG_NPCINFO_NPC_FLAGS = 5072, - - // Room for more Trinity strings 5073-9999 - + // Strings added for dynamic_spawning + LANG_SPAWNINFO_GROUP_ID = 5070, + LANG_SPAWNINFO_COMPATIBILITY_MODE = 5071, + LANG_SPAWNINFO_GUIDINFO = 5072, + LANG_SPAWNINFO_SPAWNID_LOCATION = 5073, + LANG_SPAWNINFO_DISTANCEFROMPLAYER = 5074, + LANG_SPAWNGROUP_BADGROUP = 5075, + LANG_SPAWNGROUP_SPAWNCOUNT = 5076, + LANG_LIST_RESPAWNS_RANGE = 5077, + LANG_LIST_RESPAWNS_ZONE = 5078, + LANG_LIST_RESPAWNS_LISTHEADER = 5079, + LANG_LIST_RESPAWNS_OVERDUE = 5080, + LANG_LIST_RESPAWNS_CREATURES = 5081, + LANG_LIST_RESPAWNS_GAMEOBJECTS = 5082, + + LANG_NPCINFO_UNIT_FIELD_FLAGS_2 = 5084, + LANG_NPCINFO_UNIT_FIELD_FLAGS_3 = 5085, + LANG_NPCINFO_NPC_FLAGS = 5086, + + // Room for more Trinity strings 5087-6603 // Level requirement notifications LANG_SAY_REQ = 6604, LANG_WHISPER_REQ = 6605, diff --git a/src/server/game/OutdoorPvP/OutdoorPvP.cpp b/src/server/game/OutdoorPvP/OutdoorPvP.cpp index e02383b54cc..379dc7e1d07 100644 --- a/src/server/game/OutdoorPvP/OutdoorPvP.cpp +++ b/src/server/game/OutdoorPvP/OutdoorPvP.cpp @@ -91,7 +91,7 @@ void OPvPCapturePoint::SendChangePhase() void OPvPCapturePoint::AddGO(uint32 type, ObjectGuid::LowType guid) { - GameObjectData const* data = sObjectMgr->GetGOData(guid); + GameObjectData const* data = sObjectMgr->GetGameObjectData(guid); if (!data) return; @@ -111,7 +111,7 @@ void OPvPCapturePoint::AddCre(uint32 type, ObjectGuid::LowType guid) bool OPvPCapturePoint::AddObject(uint32 type, uint32 entry, uint32 map, Position const& pos, QuaternionData const& rot) { - if (ObjectGuid::LowType guid = sObjectMgr->AddGOData(entry, map, pos, rot, 0)) + if (ObjectGuid::LowType guid = sObjectMgr->AddGameObjectData(entry, map, pos, rot, 0)) { AddGO(type, guid); return true; @@ -143,7 +143,7 @@ bool OPvPCapturePoint::SetCapturePointData(uint32 entry, uint32 map, Position co return false; } - m_capturePointSpawnId = sObjectMgr->AddGOData(entry, map, pos, rot, 0); + m_capturePointSpawnId = sObjectMgr->AddGameObjectData(entry, map, pos, rot, 0); if (!m_capturePointSpawnId) return false; @@ -210,8 +210,7 @@ bool OPvPCapturePoint::DelObject(uint32 type) go->SetRespawnTime(0); go->Delete(); } - - sObjectMgr->DeleteGOData(spawnId); + sObjectMgr->DeleteGameObjectData(spawnId); m_ObjectTypes[m_Objects[type]] = 0; m_Objects[type] = 0; return true; @@ -219,7 +218,7 @@ bool OPvPCapturePoint::DelObject(uint32 type) bool OPvPCapturePoint::DelCapturePoint() { - sObjectMgr->DeleteGOData(m_capturePointSpawnId); + sObjectMgr->DeleteGameObjectData(m_capturePointSpawnId); m_capturePointSpawnId = 0; if (m_capturePoint) diff --git a/src/server/game/Pools/PoolMgr.cpp b/src/server/game/Pools/PoolMgr.cpp index d075b7eeabb..f7f1facd90c 100644 --- a/src/server/game/Pools/PoolMgr.cpp +++ b/src/server/game/Pools/PoolMgr.cpp @@ -228,7 +228,7 @@ void PoolGroup<Creature>::Despawn1Object(uint64 guid) { sObjectMgr->RemoveCreatureFromGrid(guid, data); - Map* map = sMapMgr->FindMap(data->mapid, 0); + Map* map = sMapMgr->FindMap(data->spawnPoint.GetMapId(), 0); if (map && !map->Instanceable()) { auto creatureBounds = map->GetCreatureBySpawnIdStore().equal_range(guid); @@ -236,6 +236,9 @@ void PoolGroup<Creature>::Despawn1Object(uint64 guid) { Creature* creature = itr->second; ++itr; + // For dynamic spawns, save respawn time here + if (!creature->GetRespawnCompatibilityMode()) + creature->SaveRespawnTime(0, false); creature->AddObjectToRemoveList(); } } @@ -246,11 +249,11 @@ void PoolGroup<Creature>::Despawn1Object(uint64 guid) template<> void PoolGroup<GameObject>::Despawn1Object(uint64 guid) { - if (GameObjectData const* data = sObjectMgr->GetGOData(guid)) + if (GameObjectData const* data = sObjectMgr->GetGameObjectData(guid)) { sObjectMgr->RemoveGameobjectFromGrid(guid, data); - Map* map = sMapMgr->FindMap(data->mapid, 0); + Map* map = sMapMgr->FindMap(data->spawnPoint.GetMapId(), 0); if (map && !map->Instanceable()) { auto gameobjectBounds = map->GetGameObjectBySpawnIdStore().equal_range(guid); @@ -258,6 +261,10 @@ void PoolGroup<GameObject>::Despawn1Object(uint64 guid) { GameObject* go = itr->second; ++itr; + + // For dynamic spawns, save respawn time here + if (!go->GetRespawnCompatibilityMode()) + go->SaveRespawnTime(0, false); go->AddObjectToRemoveList(); } } @@ -385,9 +392,9 @@ void PoolGroup<Creature>::Spawn1Object(PoolObject* obj) sObjectMgr->AddCreatureToGrid(obj->guid, data); // Spawn if necessary (loaded grids only) - Map* map = sMapMgr->FindMap(data->mapid, 0); + Map* map = sMapMgr->FindMap(data->spawnPoint.GetMapId(), 0); // We use spawn coords to spawn - if (map && !map->Instanceable() && map->IsGridLoaded(data->posX, data->posY)) + if (map && !map->Instanceable() && map->IsGridLoaded(data->spawnPoint)) Creature::CreateCreatureFromDB(obj->guid, map); } } @@ -396,14 +403,14 @@ void PoolGroup<Creature>::Spawn1Object(PoolObject* obj) template <> void PoolGroup<GameObject>::Spawn1Object(PoolObject* obj) { - if (GameObjectData const* data = sObjectMgr->GetGOData(obj->guid)) + if (GameObjectData const* data = sObjectMgr->GetGameObjectData(obj->guid)) { sObjectMgr->AddGameobjectToGrid(obj->guid, data); // Spawn if necessary (loaded grids only) // this base map checked as non-instanced and then only existed - Map* map = sMapMgr->FindMap(data->mapid, 0); + Map* map = sMapMgr->FindMap(data->spawnPoint.GetMapId(), 0); // We use current coords to unspawn, not spawn coords since creature can have changed grid - if (map && !map->Instanceable() && map->IsGridLoaded(data->posX, data->posY)) + if (map && !map->Instanceable() && map->IsGridLoaded(data->spawnPoint)) { if (GameObject* go = GameObject::CreateGameObjectFromDB(obj->guid, map, false)) { @@ -670,7 +677,7 @@ void PoolMgr::LoadFromDB() uint32 pool_id = fields[1].GetUInt32(); float chance = fields[2].GetFloat(); - GameObjectData const* data = sObjectMgr->GetGOData(guid); + GameObjectData const* data = sObjectMgr->GetGameObjectData(guid); if (!data) { TC_LOG_ERROR("sql.sql", "`pool_gameobject` has a non existing gameobject spawn (GUID: " UI64FMTD ") defined for pool id (%u), skipped.", guid, pool_id); @@ -1080,6 +1087,21 @@ void PoolMgr::DespawnPool(uint32 pool_id) } } +// Selects proper template overload to call based on passed type +uint32 PoolMgr::IsPartOfAPool(SpawnObjectType type, ObjectGuid::LowType spawnId) const +{ + switch (type) + { + case SPAWN_TYPE_CREATURE: + return IsPartOfAPool<Creature>(spawnId); + case SPAWN_TYPE_GAMEOBJECT: + return IsPartOfAPool<GameObject>(spawnId); + default: + ASSERT(false, "Invalid spawn type %u passed to PoolMgr::IsPartOfPool (with spawnId " UI64FMTD ")", uint32(type), spawnId); + return 0; + } +} + // Method that check chance integrity of the creatures and gameobjects in this pool bool PoolMgr::CheckPool(uint32 pool_id) const { diff --git a/src/server/game/Pools/PoolMgr.h b/src/server/game/Pools/PoolMgr.h index e89fb8b565b..4961164536b 100644 --- a/src/server/game/Pools/PoolMgr.h +++ b/src/server/game/Pools/PoolMgr.h @@ -19,6 +19,7 @@ #define TRINITY_POOLHANDLER_H #include "Define.h" +#include "SpawnData.h" #include <map> #include <set> #include <unordered_map> @@ -122,6 +123,7 @@ class TC_GAME_API PoolMgr template<typename T> uint32 IsPartOfAPool(uint64 db_guid_or_pool_id) const; + uint32 IsPartOfAPool(SpawnObjectType type, uint64 spawnId) const; template<typename T> bool IsSpawnedObject(uint64 db_guid_or_pool_id) const { return mSpawnedData.IsActiveObject<T>(db_guid_or_pool_id); } diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 3ca9702128d..531900b8141 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -1600,16 +1600,40 @@ bool ScriptMgr::OnCastItemCombatSpell(Player* player, Unit* victim, SpellInfo co return tmpscript->OnCastItemCombatSpell(player, victim, spellInfo, item); } -bool ScriptMgr::CanSpawn(ObjectGuid::LowType spawnId, uint32 entry, CreatureTemplate const* actTemplate, CreatureData const* cData, Map const* map) +bool ScriptMgr::CanSpawn(ObjectGuid::LowType spawnId, uint32 entry, CreatureData const* cData, Map const* map) { - ASSERT(actTemplate); - + ASSERT(map); CreatureTemplate const* baseTemplate = sObjectMgr->GetCreatureTemplate(entry); - if (!baseTemplate) - baseTemplate = actTemplate; + ASSERT(baseTemplate); + + // find out which template we'd be using + CreatureTemplate const* actTemplate = nullptr; + DifficultyEntry const* difficultyEntry = sDifficultyStore.LookupEntry(map->GetDifficultyID()); + while (!actTemplate && difficultyEntry) + { + int32 idx = CreatureTemplate::DifficultyIDToDifficultyEntryIndex(difficultyEntry->ID); + if (idx == -1) + break; + + if (baseTemplate->DifficultyEntry[idx]) + { + actTemplate = sObjectMgr->GetCreatureTemplate(baseTemplate->DifficultyEntry[idx]); + break; + } + + if (!difficultyEntry->FallbackDifficultyID) + break; + + difficultyEntry = sDifficultyStore.LookupEntry(difficultyEntry->FallbackDifficultyID); + } + + if (!actTemplate) + actTemplate = baseTemplate; + uint32 scriptId = baseTemplate->ScriptID; - if (cData && cData->ScriptId) - scriptId = cData->ScriptId; + if (cData && cData->scriptId) + scriptId = cData->scriptId; + GET_SCRIPT_RET(CreatureScript, scriptId, tmpscript, true); return tmpscript->CanSpawn(spawnId, entry, baseTemplate, actTemplate, cData, map); } diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index c5e5d1456c5..79925f338ce 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -973,7 +973,7 @@ class TC_GAME_API ScriptMgr public: /* CreatureScript */ - bool CanSpawn(ObjectGuid::LowType spawnId, uint32 entry, CreatureTemplate const* actTemplate, CreatureData const* cData, Map const* map); + bool CanSpawn(ObjectGuid::LowType spawnId, uint32 entry, CreatureData const* cData, Map const* map); CreatureAI* GetCreatureAI(Creature* creature); public: /* GameObjectScript */ diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 2f8c0f3f510..ad81daa1eac 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -148,6 +148,11 @@ World::World() memset(m_int64_configs, 0, sizeof(m_int64_configs)); memset(m_bool_configs, 0, sizeof(m_bool_configs)); memset(m_float_configs, 0, sizeof(m_float_configs)); + + _guidWarn = false; + _guidAlert = false; + _warnDiff = 0; + _warnShutdownTime = time(NULL); } /// World destructor @@ -224,13 +229,66 @@ std::vector<std::string> const& World::GetMotd() const return _motd; } +void World::TriggerGuidWarning() +{ + // Lock this only to prevent multiple maps triggering at the same time + std::lock_guard<std::mutex> lock(_guidAlertLock); + + time_t gameTime = GameTime::GetGameTime(); + time_t today = (gameTime / DAY) * DAY; + + // Check if our window to restart today has passed. 5 mins until quiet time + while (gameTime >= (today + (getIntConfig(CONFIG_RESPAWN_RESTARTQUIETTIME) * HOUR) - 1810)) + today += DAY; + + // Schedule restart for 30 minutes before quiet time, or as long as we have + _warnShutdownTime = today + (getIntConfig(CONFIG_RESPAWN_RESTARTQUIETTIME) * HOUR) - 1800; + + _guidWarn = true; + SendGuidWarning(); +} + +void World::TriggerGuidAlert() +{ + // Lock this only to prevent multiple maps triggering at the same time + std::lock_guard<std::mutex> lock(_guidAlertLock); + + DoGuidAlertRestart(); + _guidAlert = true; + _guidWarn = false; +} + +void World::DoGuidWarningRestart() +{ + if (m_ShutdownTimer) + return; + + ShutdownServ(1800, SHUTDOWN_MASK_RESTART, RESTART_EXIT_CODE); + _warnShutdownTime += HOUR; +} + +void World::DoGuidAlertRestart() +{ + if (m_ShutdownTimer) + return; + + ShutdownServ(300, SHUTDOWN_MASK_RESTART, RESTART_EXIT_CODE, _alertRestartReason); +} + +void World::SendGuidWarning() +{ + if (!m_ShutdownTimer && _guidWarn && getIntConfig(CONFIG_RESPAWN_GUIDWARNING_FREQUENCY) > 0) + SendServerMessage(SERVER_MSG_STRING, _guidWarningMsg.c_str()); + _warnDiff = 0; +} + /// Find a session by its id WorldSession* World::FindSession(uint32 id) const { SessionMap::const_iterator itr = m_sessions.find(id); if (itr != m_sessions.end()) - return itr->second; // also can return NULL for kicked session + return itr->second; // also can return nullptr for kicked session else return nullptr; } @@ -1295,6 +1353,50 @@ void World::LoadConfigSettings(bool reload) m_int_configs[CONFIG_NO_GRAY_AGGRO_BELOW] = m_int_configs[CONFIG_NO_GRAY_AGGRO_ABOVE]; } + // Respawn Settings + m_int_configs[CONFIG_RESPAWN_MINCHECKINTERVALMS] = sConfigMgr->GetIntDefault("Respawn.MinCheckIntervalMS", 5000); + m_int_configs[CONFIG_RESPAWN_DYNAMICMODE] = sConfigMgr->GetIntDefault("Respawn.DynamicMode", 0); + if (m_int_configs[CONFIG_RESPAWN_DYNAMICMODE] > 1) + { + TC_LOG_ERROR("server.loading", "Invalid value for Respawn.DynamicMode (%u). Set to 0.", m_int_configs[CONFIG_RESPAWN_DYNAMICMODE]); + m_int_configs[CONFIG_RESPAWN_DYNAMICMODE] = 0; + } + m_bool_configs[CONFIG_RESPAWN_DYNAMIC_ESCORTNPC] = sConfigMgr->GetBoolDefault("Respawn.DynamicEscortNPC", true); + m_int_configs[CONFIG_RESPAWN_GUIDWARNLEVEL] = sConfigMgr->GetIntDefault("Respawn.GuidWarnLevel", 12000000); + if (m_int_configs[CONFIG_RESPAWN_GUIDWARNLEVEL] > 16777215) + { + TC_LOG_ERROR("server.loading", "Respawn.GuidWarnLevel (%u) cannot be greater than maximum GUID (16777215). Set to 12000000.", m_int_configs[CONFIG_RESPAWN_GUIDWARNLEVEL]); + m_int_configs[CONFIG_RESPAWN_GUIDWARNLEVEL] = 12000000; + } + m_int_configs[CONFIG_RESPAWN_GUIDALERTLEVEL] = sConfigMgr->GetIntDefault("Respawn.GuidAlertLevel", 16000000); + if (m_int_configs[CONFIG_RESPAWN_GUIDALERTLEVEL] > 16777215) + { + TC_LOG_ERROR("server.loading", "Respawn.GuidWarnLevel (%u) cannot be greater than maximum GUID (16777215). Set to 16000000.", m_int_configs[CONFIG_RESPAWN_GUIDALERTLEVEL]); + m_int_configs[CONFIG_RESPAWN_GUIDALERTLEVEL] = 16000000; + } + m_int_configs[CONFIG_RESPAWN_RESTARTQUIETTIME] = sConfigMgr->GetIntDefault("Respawn.RestartQuietTime", 3); + if (m_int_configs[CONFIG_RESPAWN_RESTARTQUIETTIME] > 23) + { + TC_LOG_ERROR("server.loading", "Respawn.RestartQuietTime (%u) must be an hour, between 0 and 23. Set to 3.", m_int_configs[CONFIG_RESPAWN_RESTARTQUIETTIME]); + m_int_configs[CONFIG_RESPAWN_RESTARTQUIETTIME] = 3; + } + m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_CREATURE] = sConfigMgr->GetFloatDefault("Respawn.DynamicRateCreature", 10.0f); + if (m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_CREATURE] < 0.0f) + { + TC_LOG_ERROR("server.loading", "Respawn.DynamicRateCreature (%f) must be positive. Set to 10.", m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_CREATURE]); + m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_CREATURE] = 10.0f; + } + m_int_configs[CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE] = sConfigMgr->GetIntDefault("Respawn.DynamicMinimumCreature", 10); + m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT] = sConfigMgr->GetFloatDefault("Respawn.DynamicRateGameObject", 10.0f); + if (m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT] < 0.0f) + { + TC_LOG_ERROR("server.loading", "Respawn.DynamicRateGameObject (%f) must be positive. Set to 10.", m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT]); + m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT] = 10.0f; + } + m_int_configs[CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT] = sConfigMgr->GetIntDefault("Respawn.DynamicMinimumGameObject", 10); + _guidWarningMsg = sConfigMgr->GetStringDefault("Respawn.WarningMessage", "There will be an unscheduled server restart at 03:00. The server will be available again shortly after."); + _alertRestartReason = sConfigMgr->GetStringDefault("Respawn.AlertRestartReason", "Urgent Maintenance"); + m_int_configs[CONFIG_RESPAWN_GUIDWARNING_FREQUENCY] = sConfigMgr->GetIntDefault("Respawn.WarningFrequency", 1800); ///- Read the "Data" directory from the config file std::string dataPath = sConfigMgr->GetStringDefault("DataDir", "./"); if (dataPath.empty() || (dataPath.at(dataPath.length()-1) != '/' && dataPath.at(dataPath.length()-1) != '\\')) @@ -1763,6 +1865,9 @@ void World::SetInitialWorldSettings() TC_LOG_INFO("server.loading", "Loading Creature Base Stats..."); sObjectMgr->LoadCreatureClassLevelStats(); + TC_LOG_INFO("server.loading", "Loading Spawn Group Templates..."); + sObjectMgr->LoadSpawnGroupTemplates(); + TC_LOG_INFO("server.loading", "Loading Creature Data..."); sObjectMgr->LoadCreatures(); @@ -1779,10 +1884,13 @@ void World::SetInitialWorldSettings() sObjectMgr->LoadCreatureAddons(); // must be after LoadCreatureTemplates() and LoadCreatures() TC_LOG_INFO("server.loading", "Loading Gameobject Data..."); - sObjectMgr->LoadGameobjects(); + sObjectMgr->LoadGameObjects(); + + TC_LOG_INFO("server.loading", "Loading Spawn Group Data..."); + sObjectMgr->LoadSpawnGroups(); TC_LOG_INFO("server.loading", "Loading GameObject Addon Data..."); - sObjectMgr->LoadGameObjectAddons(); // must be after LoadGameObjectTemplate() and LoadGameobjects() + sObjectMgr->LoadGameObjectAddons(); // must be after LoadGameObjectTemplate() and LoadGameObjects() TC_LOG_INFO("server.loading", "Loading GameObject Quest Items..."); sObjectMgr->LoadGameObjectQuestItems(); @@ -2507,6 +2615,16 @@ void World::Update(uint32 diff) // update the instance reset times sInstanceSaveMgr->Update(); + // Check for shutdown warning + if (_guidWarn && !_guidAlert) + { + _warnDiff += diff; + if (GameTime::GetGameTime() >= _warnShutdownTime) + DoGuidWarningRestart(); + else if (_warnDiff > getIntConfig(CONFIG_RESPAWN_GUIDWARNING_FREQUENCY) * IN_MILLISECONDS) + SendGuidWarning(); + } + // And last, but not least handle the issued cli commands ProcessCliCommands(); diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index 3f71a0e912e..cd14025fce1 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -216,7 +216,6 @@ enum WorldFloatConfigs CONFIG_ARENA_WIN_RATING_MODIFIER_2, CONFIG_ARENA_LOSE_RATING_MODIFIER, CONFIG_ARENA_MATCHMAKER_RATING_MODIFIER, - CONFIG_RESPAWN_DYNAMICRADIUS, CONFIG_RESPAWN_DYNAMICRATE_CREATURE, CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT, FLOAT_CONFIG_VALUE_COUNT @@ -403,13 +402,11 @@ enum WorldIntConfigs CONFIG_AUCTION_SEARCH_DELAY, CONFIG_AUCTION_TAINTED_SEARCH_DELAY, CONFIG_TALENTS_INSPECTING, - CONFIG_RESPAWN_MINCELLCHECKMS, + CONFIG_RESPAWN_MINCHECKINTERVALMS, CONFIG_RESPAWN_DYNAMICMODE, CONFIG_RESPAWN_GUIDWARNLEVEL, CONFIG_RESPAWN_GUIDALERTLEVEL, CONFIG_RESPAWN_RESTARTQUIETTIME, - CONFIG_RESPAWN_ACTIVITYSCOPECREATURE, - CONFIG_RESPAWN_ACTIVITYSCOPEGAMEOBJECT, CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE, CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT, CONFIG_RESPAWN_GUIDWARNING_FREQUENCY, @@ -795,6 +792,10 @@ class TC_GAME_API World void ReloadRBAC(); void RemoveOldCorpses(); + void TriggerGuidWarning(); + void TriggerGuidAlert(); + bool IsGuidWarning() { return _guidWarn; } + bool IsGuidAlert() { return _guidAlert; } protected: void _UpdateGameTime(); @@ -899,7 +900,21 @@ class TC_GAME_API World AutobroadcastContainer m_Autobroadcasts; void ProcessQueryCallbacks(); + + void SendGuidWarning(); + void DoGuidWarningRestart(); + void DoGuidAlertRestart(); QueryCallbackProcessor _queryProcessor; + + std::string _guidWarningMsg; + std::string _alertRestartReason; + + std::mutex _guidAlertLock; + + bool _guidWarn; + bool _guidAlert; + uint32 _warnDiff; + time_t _warnShutdownTime; }; TC_GAME_API extern Realm realm; |