diff --git a/sql/base/auth_database.sql b/sql/base/auth_database.sql index d4ce727090d..8d4e3dcb282 100644 --- a/sql/base/auth_database.sql +++ b/sql/base/auth_database.sql @@ -1819,6 +1819,7 @@ INSERT INTO `rbac_permissions` VALUES (863,'Command: group set maintank'), (864,'Command: group set mainassist'), (865,'Command: npc showloot'), +(866,'Command: list spawnpoints'), (867,'Command: reload quest_greeting_locale'), (872,'Command: server debug'); /*!40000 ALTER TABLE `rbac_permissions` ENABLE KEYS */; diff --git a/sql/updates/auth/4.3.4/2018_07_21_00_auth.sql b/sql/updates/auth/4.3.4/2018_07_21_00_auth.sql new file mode 100644 index 00000000000..c304ba746d8 --- /dev/null +++ b/sql/updates/auth/4.3.4/2018_07_21_00_auth.sql @@ -0,0 +1,7 @@ +DELETE FROM `rbac_permissions` WHERE `id`=866; +INSERT INTO `rbac_permissions` (`id`,`name`) VALUES +(866, 'Command: list spawnpoints'); + +DELETE FROM `rbac_linked_permissions` WHERE `linkedId`=866; +INSERT INTO `rbac_linked_permissions` (`id`,`linkedId`) VALUES +(196,866); diff --git a/sql/updates/world/4.3.4/2018_07_21_00_world.sql b/sql/updates/world/4.3.4/2018_07_21_00_world.sql new file mode 100644 index 00000000000..dde858acfc3 --- /dev/null +++ b/sql/updates/world/4.3.4/2018_07_21_00_world.sql @@ -0,0 +1,234 @@ +-- Create databases for spawn group template, and spawn group membership +-- Current flags +-- 0x01 Legacy Spawn Mode (spawn using legacy spawn system) +-- 0x02 Manual Spawn (don't automatically spawn, instead spawned by core as part of script) +DROP TABLE IF EXISTS `spawn_group_template`; +CREATE TABLE `spawn_group_template` ( + `groupId` int(10) unsigned NOT NULL, + `groupName` varchar(100) NOT NULL, + `groupFlags` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`groupId`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `spawn_group`; +CREATE TABLE `spawn_group` ( + `groupId` int(10) unsigned NOT NULL, + `spawnType` tinyint(10) unsigned NOT NULL, + `spawnId` int(10) unsigned NOT NULL, + PRIMARY KEY (`groupId`,`spawnType`,`spawnId`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- Create the default groups +INSERT INTO `spawn_group_template` (`groupId`, `groupName`, `groupFlags`) VALUES +(0, 'Default Group', 0x01), +(1, 'Legacy Group', (0x01|0x02)), +(2, 'Dynamic Scaling (Quest objectives)', (0x01|0x08)), +(3, 'Dynamic Scaling (Escort NPCs)', (0x01|0x08|0x10)), +(4, 'Dynamic Scaling (Gathering nodes)', (0x01|0x08)); + +-- Create creature dynamic spawns group (creatures with quest items, or subjects of quests with less than 30min spawn time) +DROP TABLE IF EXISTS `creature_temp_group`; +CREATE TEMPORARY TABLE `creature_temp_group` +( + `creatureId` int(10) unsigned NOT NULL DEFAULT '0' +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +INSERT INTO `creature_temp_group` +SELECT `guid` +FROM `creature` C +INNER JOIN `creature_questitem` ON `CreatureEntry` = C.`id` +WHERE `spawntimesecs` < 1800 +AND `map` IN (0, 1, 530, 571); + +INSERT INTO `creature_temp_group` +SELECT `guid` +FROM `creature` C +INNER JOIN `quest_template` ON `RequiredNpcOrGo1` = C.`id` +WHERE `spawntimesecs` < 1800 +AND `map` IN (0, 1, 530, 571); + +INSERT INTO `creature_temp_group` +SELECT `guid` +FROM `creature` C +INNER JOIN `quest_template` ON `RequiredNpcOrGo2` = C.`id` +WHERE `spawntimesecs` < 1800 +AND `map` IN (0, 1, 530, 571); + +INSERT INTO `creature_temp_group` +SELECT `guid` +FROM `creature` C +INNER JOIN `quest_template` ON `RequiredNpcOrGo3` = C.`id` +WHERE `spawntimesecs` < 1800 +AND `map` IN (0, 1, 530, 571); + +INSERT INTO `creature_temp_group` +SELECT `guid` +FROM `creature` C +INNER JOIN `quest_template` ON `RequiredNpcOrGo4` = C.`id` +WHERE `spawntimesecs` < 1800 +AND `map` IN (0, 1, 530, 571); + +INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`) +SELECT DISTINCT 2, 0, `creatureId` +FROM `creature_temp_group`; + +DROP TABLE `creature_temp_group`; + +-- Create gameobject dynamic spawns group (gameobjects with quest items, or subjects of quests with less than 30min spawn time) + +DROP TABLE IF EXISTS `gameobject_temp_group`; +CREATE TEMPORARY TABLE `gameobject_temp_group` +( + `gameobjectId` int(10) unsigned NOT NULL DEFAULT '0' +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `gameobject_temp_group_ids`; +CREATE TEMPORARY TABLE `gameobject_temp_group_ids` +( + `entryid` int(10) NOT NULL DEFAULT '0' +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +ALTER TABLE `gameobject_temp_group_ids` ADD INDEX (`entryid`); + +INSERT INTO `gameobject_temp_group` +SELECT `guid` +FROM `gameobject` G +INNER JOIN `gameobject_questitem` ON `GameObjectEntry` = G.`id` +WHERE `spawntimesecs` < 1800 +AND `map` IN (0, 1, 530, 571); + +INSERT INTO `gameobject_temp_group_ids` (`entryid`) +SELECT DISTINCT `RequiredNpcOrGo1` * -1 +FROM `quest_template`; + +INSERT INTO `gameobject_temp_group_ids` (`entryid`) +SELECT DISTINCT `RequiredNpcOrGo2` * -1 +FROM `quest_template`; + +INSERT INTO `gameobject_temp_group_ids` (`entryid`) +SELECT DISTINCT `RequiredNpcOrGo3` * -1 +FROM `quest_template`; + +INSERT INTO `gameobject_temp_group_ids` (`entryid`) +SELECT DISTINCT `RequiredNpcOrGo4` * -1 +FROM `quest_template`; + +INSERT INTO `gameobject_temp_group` +SELECT `guid` +FROM `gameobject` G +INNER JOIN `gameobject_temp_group_ids` ON `entryid` = G.`id` +WHERE `spawntimesecs` < 1800 +AND `map` IN (0, 1, 530, 571); + +INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`) +SELECT DISTINCT 2, 1, `gameobjectId` +FROM `gameobject_temp_group`; + +DROP TABLE `gameobject_temp_group`; +ALTER TABLE `gameobject_temp_group_ids` DROP INDEX `entryid`; +DROP TABLE `gameobject_temp_group_ids`; + +-- Add mining nodes/herb nodes to profession node group +INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`) +SELECT 4, 1, `guid` +FROM `gameobject` g +INNER JOIN `gameobject_template` gt + ON gt.`entry` = g.`id` +WHERE `type` = 3 +AND `Data0` IN (2, 8, 9, 10, 11, 18, 19, 20, 21, 22, 25, 26, 27, 29, 30, 31, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 47, 48, 49, 50, 51, 379, 380, 399, 400, 439, 440, 441, 442, 443, 444, 519, 521, 719, 939, 1119, 1120, + 1121, 1122, 1123, 1124, 1632, 1639, 1641, 1642, 1643, 1644, 1645, 1646, 1649, 1650, 1651, 1652, 1782, 1783, 1784, 1785, 1786, 1787, 1788, 1789, 1790, 1791, 1792, 1793, 1800, 1860); + +-- Add Escort NPCs +INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`) VALUES +(3, 0, 10873), +(3, 0, 17874), +(3, 0, 40210), +(3, 0, 11348), +(3, 0, 93301), +(3, 0, 93194), +(3, 0, 19107), +(3, 0, 21692), +(3, 0, 21584), +(3, 0, 23229), +(3, 0, 24268), +(3, 0, 21594), +(3, 0, 14387), +(3, 0, 50381), +(3, 0, 15031), +(3, 0, 26987), +(3, 0, 29241), +(3, 0, 32333), +(3, 0, 33115), +(3, 0, 37085), +(3, 0, 41759), +(3, 0, 84459), +(3, 0, 78685), +(3, 0, 62090), +(3, 0, 72388), +(3, 0, 86832), +(3, 0, 67040), +(3, 0, 78781), +(3, 0, 65108), +(3, 0, 63688), +(3, 0, 59383), +(3, 0, 63625), +(3, 0, 70021), +(3, 0, 82071), +(3, 0, 117903), +(3, 0, 111075), +(3, 0, 101136), +(3, 0, 101303), +(3, 0, 122686), +(3, 0, 117065), +(3, 0, 202337), +(3, 0, 2017), +(3, 0, 132683); +-- remove potential duplicates +DELETE FROM `spawn_group` WHERE `groupId` != 3 AND `spawnType`=0 AND `spawnId` IN (SELECT `spawnId` FROM (SELECT `spawnId` FROM `spawn_group` WHERE `groupId`=3 AND `spawnType`=0) as `temp`); +DELETE FROM `spawn_group` WHERE `groupId` != 4 AND `spawnType`=1 AND `spawnId` IN (SELECT `spawnId` FROM (SELECT `spawnId` FROM `spawn_group` WHERE `groupId`=4 AND `spawnType`=1) as `temp`); + + +-- Update trinity strings for various cs_list strings, to support showing spawn ID and guid. +UPDATE `trinity_string` +SET `content_default` = '%d (Entry: %d) - |cffffffff|Hgameobject:%d|h[%s X:%f Y:%f Z:%f MapId:%d]|h|r %s %s' +WHERE `entry` = 517; + +UPDATE `trinity_string` +SET `content_default` = '%d - |cffffffff|Hcreature:%d|h[%s X:%f Y:%f Z:%f MapId:%d]|h|r %s %s' +WHERE `entry` = 515; + +UPDATE `trinity_string` +SET `content_default` = '%d - %s X:%f Y:%f Z:%f MapId:%d %s %s' +WHERE `entry` = 1111; + +UPDATE `trinity_string` +SET `content_default` = '%d - %s X:%f Y:%f Z:%f MapId:%d %s %s' +WHERE `entry` = 1110; + +-- Add new trinity strings for extra npc/gobject info lines +DELETE FROM `trinity_string` WHERE `entry` BETWEEN 5070 AND 5082; +INSERT INTO `trinity_string` (`entry`, `content_default`) VALUES +(5070, 'Spawn group: %s (ID: %u, Flags: %u, Active: %u)'), +(5071, 'Compatibility Mode: %u'), +(5072, 'GUID: %s'), +(5073, 'SpawnID: %u, location (%f, %f, %f)'), +(5074, 'Distance from player %f'), +(5075, 'Spawn group %u not found'), +(5076, 'Spawned a total of %zu objects:'), +(5077, 'Listing %s respawns within %uyd'), +(5078, 'Listing %s respawns for %s (zone %u)'), +(5079, 'SpawnID | Entry | GridXY| Zone | Respawn time (Full)'), +(5080, 'overdue'), +(5081, 'creatures'), +(5082, 'gameobjects'); + +-- Add new NPC/Gameobject commands +DELETE FROM `command` WHERE `name` IN ('npc spawngroup', 'npc despawngroup', 'gobject spawngroup', 'gobject despawngroup', 'list respawns'); +INSERT INTO `command` (`name`, `permission`, `help`) VALUES +('npc spawngroup', 856, 'Syntax: .npc spawngroup $groupId [ignorerespawn] [force]'), +('npc despawngroup', 857, 'Syntax: .npc despawngroup $groupId [removerespawntime]'), +('gobject spawngroup', 858, 'Syntax: .gobject spawngroup $groupId [ignorerespawn] [force]'), +('gobject despawngroup', 859, 'Syntax: .gobject despawngroup $groupId [removerespawntime]'), +('list respawns', 860, 'Syntax: .list respawns [distance] + +Lists all pending respawns within yards, or within current zone if not specified.'); diff --git a/sql/updates/world/4.3.4/2018_07_21_01_world.sql b/sql/updates/world/4.3.4/2018_07_21_01_world.sql new file mode 100644 index 00000000000..135a50ae851 --- /dev/null +++ b/sql/updates/world/4.3.4/2018_07_21_01_world.sql @@ -0,0 +1,6 @@ +-- +DELETE FROM `command` WHERE `name`="list spawnpoints"; +INSERT INTO `command` (`name`,`permission`,`help`) VALUES +('list spawnpoints', 866, 'Syntax: .list spawnpoints + +Lists all spawn points (both creatures and GOs) in the current zone.'); diff --git a/sql/updates/world/4.3.4/2018_07_21_02_world.sql b/sql/updates/world/4.3.4/2018_07_21_02_world.sql new file mode 100644 index 00000000000..d70076f042e --- /dev/null +++ b/sql/updates/world/4.3.4/2018_07_21_02_world.sql @@ -0,0 +1,10 @@ +-- +DROP TABLE IF EXISTS `instance_spawn_groups`; +CREATE TABLE `instance_spawn_groups` ( + `instanceMapId` smallint(5) unsigned not null, + `bossStateId` tinyint unsigned not null, + `bossStates` tinyint unsigned not null, + `spawnGroupId` int unsigned not null, + `flags` tinyint unsigned not null, + PRIMARY KEY (`instanceMapId`,`bossStateId`,`spawnGroupId`,`bossStates`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; diff --git a/src/common/Utilities/EventProcessor.cpp b/src/common/Utilities/EventProcessor.cpp index 40dc745a302..c2d59953ba2 100644 --- a/src/common/Utilities/EventProcessor.cpp +++ b/src/common/Utilities/EventProcessor.cpp @@ -111,15 +111,24 @@ void EventProcessor::KillAllEvents(bool force) m_events.clear(); } -void EventProcessor::AddEvent(BasicEvent* Event, uint64 e_time, bool set_addtime) +void EventProcessor::AddEvent(BasicEvent* event, uint64 e_time, bool set_addtime) { if (set_addtime) - Event->m_addTime = m_time; - Event->m_execTime = e_time; - m_events.insert(std::pair(e_time, Event)); + event->m_addTime = m_time; + event->m_execTime = e_time; + m_events.insert(std::pair(e_time, event)); } -uint64 EventProcessor::CalculateTime(uint64 t_offset) const +void EventProcessor::ModifyEventTime(BasicEvent* event, uint64 newTime) { - return(m_time + t_offset); + for (auto itr = m_events.begin(); itr != m_events.end(); ++itr) + { + if (itr->second != event) + continue; + + event->m_execTime = newTime; + m_events.erase(itr); + m_events.insert(std::pair(newTime, event)); + break; + } } diff --git a/src/common/Utilities/EventProcessor.h b/src/common/Utilities/EventProcessor.h index ee15fdb5071..7686c5faeb7 100644 --- a/src/common/Utilities/EventProcessor.h +++ b/src/common/Utilities/EventProcessor.h @@ -20,6 +20,7 @@ #define __EVENTPROCESSOR_H #include "Define.h" +#include "Duration.h" #include class EventProcessor; @@ -78,8 +79,10 @@ class TC_COMMON_API EventProcessor void Update(uint32 p_time); void KillAllEvents(bool force); - void AddEvent(BasicEvent* Event, uint64 e_time, bool set_addtime = true); - uint64 CalculateTime(uint64 t_offset) const; + void AddEvent(BasicEvent* event, uint64 e_time, bool set_addtime = true); + void AddEventAtOffset(BasicEvent* event, Milliseconds const& offset) { AddEvent(event, CalculateTime(offset.count())); } + void ModifyEventTime(BasicEvent* event, uint64 newTime); + uint64 CalculateTime(uint64 t_offset) const { return m_time + t_offset; } protected: uint64 m_time; diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp index e81f6d2307b..451aa0d739f 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.cpp +++ b/src/server/database/Database/Implementation/WorldDatabase.cpp @@ -92,6 +92,7 @@ void WorldDatabaseConnection::DoPrepareStatements() PrepareStatement(WORLD_DEL_DISABLES, "DELETE FROM disables WHERE entry = ? AND sourceType = ?", CONNECTION_ASYNC); PrepareStatement(WORLD_UPD_CREATURE_ZONE_AREA_DATA, "UPDATE creature SET zoneId = ?, areaId = ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(WORLD_UPD_GAMEOBJECT_ZONE_AREA_DATA, "UPDATE gameobject SET zoneId = ?, areaId = ? WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(WORLD_DEL_SPAWNGROUP_MEMBER, "DELETE FROM spawn_group WHERE spawnType = ? AND spawnId = ?", CONNECTION_ASYNC); } WorldDatabaseConnection::WorldDatabaseConnection(MySQLConnectionInfo& connInfo) : MySQLConnection(connInfo) diff --git a/src/server/database/Database/Implementation/WorldDatabase.h b/src/server/database/Database/Implementation/WorldDatabase.h index 84ff1f3e8c8..bca73bf22d5 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.h +++ b/src/server/database/Database/Implementation/WorldDatabase.h @@ -98,6 +98,7 @@ enum WorldDatabaseStatements : uint32 WORLD_DEL_DISABLES, WORLD_UPD_CREATURE_ZONE_AREA_DATA, WORLD_UPD_GAMEOBJECT_ZONE_AREA_DATA, + WORLD_DEL_SPAWNGROUP_MEMBER, MAX_WORLDDATABASE_STATEMENTS }; diff --git a/src/server/game/AI/CreatureAI.h b/src/server/game/AI/CreatureAI.h index 65741437904..accaedce7eb 100644 --- a/src/server/game/AI/CreatureAI.h +++ b/src/server/game/AI/CreatureAI.h @@ -142,8 +142,8 @@ class TC_GAME_API CreatureAI : public UnitAI virtual bool IsEscorted() const { return false; } - // Called when creature is spawned or respawned - virtual void JustRespawned() { } + // Called when creature appears in the world (spawn, respawn, grid load etc...) + virtual void JustAppeared() { } // Called at waypoint reached or point movement finished virtual void MovementInform(uint32 /*type*/, uint32 /*id*/) { } @@ -201,6 +201,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 d1665ff6b25..0e26410495b 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp @@ -467,7 +467,7 @@ void BossAI::_Reset() events.Reset(); summons.DespawnAll(); scheduler.CancelAll(); - if (instance) + if (instance && instance->GetBossState(_bossId) != DONE) instance->SetBossState(_bossId, NOT_STARTED); } @@ -553,12 +553,12 @@ bool BossAI::CanAIAttack(Unit const* target) const return CheckBoundary(target); } -void BossAI::_DespawnAtEvade(uint32 delayToRespawn, Creature* who) +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 %ld 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 578cf644bea..516fbd4f868 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.h +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.h @@ -361,8 +361,8 @@ class TC_GAME_API BossAI : public ScriptedAI void _JustEngagedWith(); 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 0829ada44eb..bb5ade1f853 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp @@ -31,6 +31,7 @@ EndScriptData */ #include "MotionMaster.h" #include "ObjectAccessor.h" #include "Player.h" +#include "World.h" enum Points { @@ -167,7 +168,7 @@ void npc_escortAI::JustDied(Unit* /*killer*/) } } -void npc_escortAI::JustRespawned() +void npc_escortAI::JustAppeared() { m_uiEscortState = STATE_ESCORT_NONE; @@ -257,13 +258,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; } @@ -298,11 +303,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(); @@ -443,6 +454,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()); @@ -588,3 +615,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()) + return true; + + return false; +} diff --git a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h index 926327f1d52..bf47ad00a1d 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h +++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h @@ -65,7 +65,7 @@ struct TC_GAME_API npc_escortAI : public ScriptedAI void JustDied(Unit*) override; - void JustRespawned() override; + void JustAppeared() override; void ReturnToLastPoint(); @@ -107,6 +107,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/ScriptedAI/ScriptedFollowerAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp index cb06347a43a..e436c5d5779 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp @@ -160,7 +160,7 @@ void FollowerAI::JustDied(Unit* /*killer*/) } } -void FollowerAI::JustRespawned() +void FollowerAI::JustAppeared() { m_uiFollowState = STATE_FOLLOW_NONE; diff --git a/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h index a3f1ae5db66..4e273923710 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h +++ b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h @@ -53,7 +53,7 @@ class TC_GAME_API FollowerAI : public ScriptedAI void JustDied(Unit*) override; - void JustRespawned() override; + void JustAppeared() override; void UpdateAI(uint32) override; //the "internal" update, calls UpdateFollowerAI() virtual void UpdateFollowerAI(uint32); //used when it's needed to add code in update (abilities, scripted events, etc) diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index fc8fb5f9870..4f5c31a5735 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -570,7 +570,7 @@ bool SmartAI::AssistPlayerInCombatAgainst(Unit* who) return false; } -void SmartAI::JustRespawned() +void SmartAI::JustAppeared() { mDespawnTime = 0; mDespawnState = 0; diff --git a/src/server/game/AI/SmartScripts/SmartAI.h b/src/server/game/AI/SmartScripts/SmartAI.h index eebe3d9821e..85394868235 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.h +++ b/src/server/game/AI/SmartScripts/SmartAI.h @@ -71,7 +71,7 @@ class TC_GAME_API SmartAI : public CreatureAI bool IsEscortInvokerInRange(); // Called when creature is spawned or respawned - void JustRespawned() override; + void JustAppeared() override; // Called at reaching home after evade, InitializeAI(), EnterEvadeMode() for resetting variables void JustReachedHome() override; diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index 5853c8914ee..f4e2b0c5b4e 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -1066,7 +1066,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u creatureTarget->DespawnOrUnsummon(respawnDelay); } else if (GameObject* goTarget = target->ToGameObject()) - goTarget->SetRespawnTime(respawnDelay); + goTarget->DespawnOrUnsummon(Milliseconds(respawnDelay)); } break; } @@ -1469,19 +1469,18 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u } break; } - case SMART_ACTION_RESPAWN_TARGET: + case SMART_ACTION_ENABLE_TEMP_GOBJ: { for (WorldObject* target : targets) { if (IsCreature(target)) - target->ToCreature()->Respawn(); + TC_LOG_WARN("sql.sql", "Invalid creature target '%s' (entry %u, spawnId %u) specified for SMART_ACTION_ENABLE_TEMP_GOBJ", target->GetName().c_str(), target->GetEntry(), target->ToCreature()->GetSpawnId()); else if (IsGameObject(target)) { - // do not modify respawndelay of already spawned gameobjects if (target->ToGameObject()->isSpawnedByDefault()) - target->ToGameObject()->Respawn(); + TC_LOG_WARN("sql.sql", "Invalid gameobject target '%s' (entry %u, spawnId %u) for SMART_ACTION_ENABLE_TEMP_GOBJ - the object is spawned by default", target->GetName().c_str(), target->GetEntry(), target->ToGameObject()->GetSpawnId()); else - target->ToGameObject()->SetRespawnTime(e.action.RespawnTarget.goRespawnTime); + target->ToGameObject()->SetRespawnTime(e.action.enableTempGO.duration); } } break; @@ -2132,6 +2131,91 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u 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 + GetBaseObject()->GetMap()->SpawnGroupSpawn(e.action.groupSpawn.groupId, 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 + GetBaseObject()->GetMap()->SpawnGroupSpawn(e.action.groupSpawn.groupId, 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: { if (!IsSmart()) @@ -2234,6 +2318,20 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u } break; } + case SMART_ACTION_RESPAWN_BY_SPAWNID: + { + Map* map = nullptr; + if (WorldObject* obj = GetBaseObject()) + map = obj->GetMap(); + else if (!targets.empty()) + map = targets.front()->GetMap(); + + if (map) + map->RemoveRespawnTime(SpawnObjectType(e.action.respawnData.spawnType), e.action.respawnData.spawnId, true); + else + TC_LOG_ERROR("sql.sql", "SmartScript::ProcessAction: Entry %d SourceType %u, Event %u - tries to respawn by spawnId but does not provide a map", e.entryOrGuid, e.GetScriptType(), e.event_id); + break; + } default: TC_LOG_ERROR("sql.sql", "SmartScript::ProcessAction: Entry %d SourceType %u, Event %u, Unhandled Action type %u", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType()); break; diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp index 397393bf6c3..b8c9c4c7894 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp @@ -228,7 +228,7 @@ void SmartAIMgr::LoadSmartAIFromDB() } case SMART_SCRIPT_TYPE_GAMEOBJECT: { - GameObjectData const* gameObject = sObjectMgr->GetGOData(uint32(std::abs(temp.entryOrGuid))); + GameObjectData const* gameObject = sObjectMgr->GetGameObjectData(uint32(std::abs(temp.entryOrGuid))); if (!gameObject) { TC_LOG_ERROR("sql.sql", "SmartAIMgr::LoadSmartAIFromDB: GameObject guid (%u) does not exist, skipped loading.", uint32(std::abs(temp.entryOrGuid))); @@ -998,7 +998,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; @@ -1532,6 +1532,19 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) } break; } + case SMART_ACTION_RESPAWN_BY_SPAWNID: + if (!sObjectMgr->GetSpawnData(SpawnObjectType(e.action.respawnData.spawnType), e.action.respawnData.spawnId)) + { + TC_LOG_ERROR("sql.sql", "Entry %u SourceType %u Event %u Action %u specifies invalid spawn data (%u,%u)", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.action.respawnData.spawnType, e.action.respawnData.spawnId); + return false; + } + break; + case SMART_ACTION_ENABLE_TEMP_GOBJ: + if (!e.action.enableTempGO.duration) + { + TC_LOG_ERROR("sql.sql", "Entry %u SourceType %u Event %u Action %u does not specify duration", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType()); + return false; + } case SMART_ACTION_START_CLOSEST_WAYPOINT: case SMART_ACTION_FOLLOW: case SMART_ACTION_SET_ORIENTATION: @@ -1565,7 +1578,6 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) case SMART_ACTION_REMOVE_UNIT_FLAG: case SMART_ACTION_PLAYMOVIE: case SMART_ACTION_MOVE_TO_POS: - case SMART_ACTION_RESPAWN_TARGET: case SMART_ACTION_CLOSE_GOSSIP: case SMART_ACTION_TRIGGER_TIMED_EVENT: case SMART_ACTION_REMOVE_TIMED_EVENT: @@ -1609,6 +1621,9 @@ 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: + case SMART_ACTION_STOP_MOTION: break; default: TC_LOG_ERROR("sql.sql", "SmartAIMgr: Not handled action_type(%u), event_type(%u), Entry %d 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 a77e2374751..e12baebd1a8 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.h +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.h @@ -532,7 +532,7 @@ enum SMART_ACTION SMART_ACTION_CREATE_TIMED_EVENT = 67, // id, InitialMin, InitialMax, RepeatMin(only if it repeats), RepeatMax(only if it repeats), chance SMART_ACTION_PLAYMOVIE = 68, // entry SMART_ACTION_MOVE_TO_POS = 69, // PointId, transport, disablePathfinding, ContactDistance - SMART_ACTION_RESPAWN_TARGET = 70, // + SMART_ACTION_ENABLE_TEMP_GOBJ = 70, // despawnTimer (sec) SMART_ACTION_EQUIP = 71, // entry, slotmask slot1, slot2, slot3 , only slots with mask set will be sent to client, bits are 1, 2, 4, leaving mask 0 is defaulted to mask 7 (send all), slots1-3 are only used if no entry is set SMART_ACTION_CLOSE_GOSSIP = 72, // none SMART_ACTION_TRIGGER_TIMED_EVENT = 73, // id(>1) @@ -591,8 +591,14 @@ enum SMART_ACTION SMART_ACTION_REMOVE_ALL_GAMEOBJECTS = 126, SMART_ACTION_STOP_MOTION = 127, // stopMoving, movementExpired SMART_ACTION_PLAY_ANIMKIT = 128, // id, type (0 = oneShot, 1 = aiAnim, 2 = meleeAnim, 3 = movementAnim) + SMART_ACTION_SCENE_PLAY = 129, // don't use on 3.3.5a + SMART_ACTION_SCENE_CANCEL = 130, // don't use on 3.3.5a + SMART_ACTION_SPAWN_SPAWNGROUP = 131, // Group ID, min secs, max secs, spawnflags + SMART_ACTION_DESPAWN_SPAWNGROUP = 132, // Group ID, min secs, max secs, spawnflags + SMART_ACTION_RESPAWN_BY_SPAWNID = 133, // spawnType, spawnId - SMART_ACTION_END = 129 + + SMART_ACTION_END = 134 }; struct SmartAction @@ -1015,8 +1021,8 @@ struct SmartAction struct { - uint32 goRespawnTime; - } RespawnTarget; + uint32 duration; + } enableTempGO; struct { @@ -1112,6 +1118,14 @@ struct SmartAction uint32 disable; } disableEvade; + struct + { + uint32 groupId; + uint32 minDelay; + uint32 maxDelay; + uint32 spawnflags; + } groupSpawn; + struct { uint32 animKit; @@ -1146,6 +1160,12 @@ struct SmartAction uint32 movementExpired; } stopMotion; + struct + { + uint32 spawnType; + uint32 spawnId; + } respawnData; + //! Note for any new future actions //! All parameters must have type uint32 @@ -1161,6 +1181,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/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h index fbf05f1bc18..12f86edd6d8 100644 --- a/src/server/game/Accounts/RBAC.h +++ b/src/server/game/Accounts/RBAC.h @@ -754,11 +754,11 @@ enum RBACPermissions RBAC_PERM_COMMAND_RELOAD_CONVERSATION_TEMPLATE = 853, // not on 3.3.5a or 4.3.4 RBAC_PERM_COMMAND_DEBUG_CONVERSATION = 854, // not on 3.3.5a or 4.3.4 RBAC_PERM_COMMAND_DEBUG_PLAY_MUSIC = 855, - RBAC_PERM_COMMAND_NPC_SPAWNGROUP = 856, // reserved for dynamic_spawning - RBAC_PERM_COMMAND_NPC_DESPAWNGROUP = 857, // reserved for dynamic_spawning - RBAC_PERM_COMMAND_GOBJECT_SPAWNGROUP = 858, // reserved for dynamic_spawning - RBAC_PERM_COMMAND_GOBJECT_DESPAWNGROUP = 859, // reserved for dynamic_spawning - RBAC_PERM_COMMAND_LIST_RESPAWNS = 860, // reserved for dynamic_spawning + RBAC_PERM_COMMAND_NPC_SPAWNGROUP = 856, + RBAC_PERM_COMMAND_NPC_DESPAWNGROUP = 857, + RBAC_PERM_COMMAND_GOBJECT_SPAWNGROUP = 858, + RBAC_PERM_COMMAND_GOBJECT_DESPAWNGROUP = 859, + RBAC_PERM_COMMAND_LIST_RESPAWNS = 860, RBAC_PERM_COMMAND_GROUP_SET = 861, RBAC_PERM_COMMAND_GROUP_ASSISTANT = 862, RBAC_PERM_COMMAND_GROUP_MAINTANK = 863, diff --git a/src/server/game/Battlefield/Battlefield.cpp b/src/server/game/Battlefield/Battlefield.cpp index a5e3592cff3..e6ec98ef07d 100644 --- a/src/server/game/Battlefield/Battlefield.cpp +++ b/src/server/game/Battlefield/Battlefield.cpp @@ -776,17 +776,14 @@ Creature* Battlefield::SpawnCreature(uint32 entry, Position const& pos) return nullptr; } - float x, y, z, o; - pos.GetPosition(x, y, z, o); - Creature* creature = new Creature(); - if (!creature->Create(map->GenerateLowGuid(), map, PHASEMASK_NORMAL, entry, x, y, z, o)) + if (!creature->Create(map->GenerateLowGuid(), map, PHASEMASK_NORMAL, entry, pos)) { TC_LOG_ERROR("bg.battlefield", "Battlefield::SpawnCreature: Can't create creature entry: %u", entry); delete creature; return nullptr; } - creature->SetHomePosition(x, y, z, o); + creature->SetHomePosition(pos); // Set creature in world map->AddToMap(creature); diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp index e203a260210..036d31bfd2e 100644 --- a/src/server/game/Battlegrounds/Battleground.cpp +++ b/src/server/game/Battlegrounds/Battleground.cpp @@ -1614,7 +1614,7 @@ Creature* Battleground::AddCreature(uint32 entry, uint32 type, float x, float y, } Creature* creature = new Creature(); - if (!creature->Create(map->GenerateLowGuid(), map, PHASEMASK_NORMAL, entry, x, y, z, o)) + if (!creature->Create(map->GenerateLowGuid(), map, PHASEMASK_NORMAL, entry, { x, y, z, o })) { TC_LOG_ERROR("bg.battleground", "Battleground::AddCreature: cannot create creature (entry: %u) for BG (map: %u, instance id: %u)!", entry, m_MapId, m_InstanceID); diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundAV.cpp b/src/server/game/Battlegrounds/Zones/BattlegroundAV.cpp index 1bb1e766199..1ee9f16cd33 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundAV.cpp +++ b/src/server/game/Battlegrounds/Zones/BattlegroundAV.cpp @@ -316,6 +316,7 @@ Creature* BattlegroundAV::AddAVCreature(uint16 cinfoid, uint16 type) || (cinfoid >= AV_NPC_H_GRAVEDEFENSE0 && cinfoid <= AV_NPC_H_GRAVEDEFENSE3))) { CreatureData &data = sObjectMgr->NewOrExistCreatureData(creature->GetSpawnId()); + data.spawnGroupData = sObjectMgr->GetDefaultSpawnGroup(); data.spawndist = 5; } //else spawndist will be 15, so creatures move maximum=10 diff --git a/src/server/game/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp index 4f83707b80c..96f7347ed46 100644 --- a/src/server/game/Conditions/ConditionMgr.cpp +++ b/src/server/game/Conditions/ConditionMgr.cpp @@ -2120,7 +2120,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 a61f554dc7b..6419fdff1f5 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -190,7 +190,7 @@ m_lootRecipient(), m_lootRecipientGroup(0), _skinner(), _pickpocketLootRestore(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(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_triggerJustAppeared(true), m_respawnCompatibilityMode(false), m_focusSpell(nullptr), m_focusDelay(0), m_shouldReacquireTarget(false), m_suppressedOrientation(0.0f) { m_regenTimer = CREATURE_REGEN_INTERVAL; m_valuesCount = UNIT_END; @@ -204,7 +204,6 @@ m_originalEntry(0), m_homePosition(), m_transportHomePosition(), m_creatureInfo( m_CombatDistance = 0;//MELEE_RANGE; ResetLootMode(); // restore default loot mode - m_TriggerJustRespawned = false; m_isTempWorldObject = false; } @@ -288,27 +287,48 @@ void Creature::RemoveCorpse(bool setSpawnTime) 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(); + DestroyForNearbyPlayers(); // old UpdateObjectVisibility() + loot.clear(); + uint32 respawnDelay = m_respawnDelay; + if (IsAIEnabled) + AI()->CorpseRemoved(respawnDelay); - // Should get removed later, just keep "compatibility" with scripts - if (setSpawnTime) - m_respawnTime = std::max(time(nullptr) + respawnDelay, m_respawnTime); + // Should get removed later, just keep "compatibility" with scripts + if (setSpawnTime) + m_respawnTime = std::max(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); - SetHomePosition(x, y, z, o); - GetMap()->CreatureRelocation(this, x, y, z, o); + float x, y, z, o; + GetRespawnPosition(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(nullptr) + respawnDelay, m_respawnTime); + + SaveRespawnTime(0, false); + } + + if (TempSummon* summon = ToTempSummon()) + summon->UnSummon(); + else + AddObjectToRemoveList(); + } } /** @@ -502,11 +522,11 @@ bool Creature::UpdateEntry(uint32 entry, CreatureData const* data /*= nullptr*/, void Creature::Update(uint32 diff) { - if (IsAIEnabled && m_TriggerJustRespawned) + if (IsAIEnabled && m_triggerJustAppeared && m_deathState == ALIVE) { - m_TriggerJustRespawned = false; - AI()->JustRespawned(); - if (m_vehicleKit) + m_triggerJustAppeared = false; + AI()->JustAppeared(); + if (m_respawnCompatibilityMode && m_vehicleKit) m_vehicleKit->Reset(); } @@ -520,30 +540,38 @@ 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 (GUID: %u Entry: %u) in wrong state: JUST_DEAD (1)", GetGUID().GetCounter(), GetEntry()); + TC_LOG_ERROR("entities.unit", "Creature (GUID: %u Entry: %u) in wrong state: JUST_DIED (1)", GetGUID().GetCounter(), GetEntry()); break; case DEAD: { + if (!m_respawnCompatibilityMode) + { + TC_LOG_ERROR("entities.unit", "Creature (GUID: %u Entry: %u) in wrong state: DEAD (3)", GetGUID().GetCounter(), GetEntry()); + break; + } time_t now = time(nullptr); 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 + // Delay respawn if spawn group is not active + if (m_creatureData && !GetMap()->IsSpawnGroupActive(m_creatureData->spawnGroupData->groupId)) + { + m_respawnTime = now + urand(4, 7); + break; // Will be rechecked on next Update call after delay expires + } ObjectGuid dbtableHighGuid(HighGuid::Unit, GetEntry(), m_spawnId); - time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid); - if (!linkedRespawntime) // Can respawn + time_t linkedRespawnTime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid); + if (!linkedRespawnTime) // Can respawn // 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); + 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::max to never respawn in that instance @@ -888,7 +916,7 @@ void Creature::Motion_Initialize() GetMotionMaster()->Initialize(); } -bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 /*phaseMask*/, 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 phaseMask, uint32 entry, Position const& pos, CreatureData const* data /*= nullptr*/, uint32 vehId /*= 0*/, bool dynamic) { ASSERT(map); SetMap(map); @@ -899,6 +927,10 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 /*phaseMask* PhasingHandler::InitDbVisibleMapId(GetPhaseShift(), data->terrainSwapMap); } + // Set if this creature can handle dynamic spawns + if (!dynamic) + SetRespawnCompatibilityMode(); + CreatureTemplate const* cinfo = sObjectMgr->GetCreatureTemplate(entry); if (!cinfo) { @@ -908,13 +940,13 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 /*phaseMask* //! 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 %d, 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 %d, 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; } @@ -948,10 +980,8 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 /*phaseMask* //! Need to be called after LoadCreaturesAddon - MOVEMENTFLAG_HOVER is set there if (HasUnitMovementFlag(MOVEMENTFLAG_HOVER)) { - z += GetFloatValue(UNIT_FIELD_HOVERHEIGHT); - //! Relocate again with updated Z coord - Relocate(x, y, z, ang); + m_positionZ += GetFloatValue(UNIT_FIELD_HOVERHEIGHT); } LastUsedScriptID = GetScriptId(); @@ -1126,26 +1156,17 @@ void Creature::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask) 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.phaseMask = phaseMask; 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 @@ -1160,6 +1181,8 @@ void Creature::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask) data.npcflag = npcflag; data.unit_flags = unit_flags; data.dynamicflags = dynamicflags; + if (!data.spawnGroupData) + data.spawnGroupData = sObjectMgr->GetDefaultSpawnGroup(); data.phaseId = GetDBPhase() > 0 ? GetDBPhase() : 0; data.phaseGroup = GetDBPhase() < 0 ? abs(GetDBPhase()) : 0; @@ -1378,7 +1401,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) { @@ -1414,36 +1437,43 @@ bool Creature::LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool ad if (!data) { - TC_LOG_ERROR("sql.sql", "Creature (GUID: %u) not found in table `creature`, can't load. ", spawnId); + TC_LOG_ERROR("sql.sql", "Creature (SpawnID %u) not found in table `creature`, can't load. ", spawnId); return false; } 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(), map, data->phaseMask, data->id, data->posX, data->posY, data->posZ, data->orientation, data)) + + if (!Create(map->GenerateLowGuid(), map, data->phaseMask, 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; + if (!m_respawnTime && !map->IsSpawnGroupActive(data->spawnGroupData->groupId)) + { + // @todo pools need fixing! this is just a temporary crashfix, but they violate dynspawn principles + ASSERT(m_respawnCompatibilityMode || sPoolMgr->IsPartOfAPool(spawnId), "Creature (SpawnID %u) trying to load in inactive spawn group %s.", spawnId, data->spawnGroupData->name.c_str()); + m_respawnTime = GameTime::GetGameTime() + urand(4, 7); + } if (m_respawnTime) // respawn on Update { + ASSERT(m_respawnCompatibilityMode || sPoolMgr->IsPartOfAPool(spawnId), "Creature (SpawnID %u) trying to load despite a respawn timer in progress.", spawnId); 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); } } @@ -1539,15 +1569,24 @@ void Creature::DeleteFromDB() return; } - GetMap()->RemoveCreatureRespawnTime(m_spawnId); + // remove any scheduled respawns + GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, m_spawnId); + + // delete data from memory sObjectMgr->DeleteCreatureData(m_spawnId); + // delete data and all its associations from DB SQLTransaction trans = WorldDatabase.BeginTransaction(); PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_CREATURE); stmt->setUInt32(0, m_spawnId); trans->Append(stmt); + stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_SPAWNGROUP_MEMBER); + stmt->setUInt8(0, uint8(SPAWN_TYPE_CREATURE)); + stmt->setUInt32(1, m_spawnId); + trans->Append(stmt); + stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_CREATURE_ADDON); stmt->setUInt32(0, m_spawnId); trans->Append(stmt); @@ -1561,6 +1600,11 @@ void Creature::DeleteFromDB() trans->Append(stmt); WorldDatabase.CommitTransaction(trans); + + // then delete any active instances of the creature + auto const& spawnMap = GetMap()->GetCreatureBySpawnIdStore(); + for (auto it = spawnMap.find(m_spawnId); it != spawnMap.end(); it = spawnMap.find(m_spawnId)) + it->second->AddObjectToRemoveList(); } bool Creature::IsInvisibleDueToDespawn() const @@ -1695,14 +1739,30 @@ void Creature::setDeathState(DeathState s) if (s == JUST_DIED) { m_corpseRemoveTime = time(nullptr) + m_corpseDelay; - if (IsDungeonBoss() && !m_respawnDelay) - m_respawnTime = std::numeric_limits::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::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::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 @@ -1772,8 +1832,6 @@ void Creature::setDeathState(DeathState s) void Creature::Respawn(bool force) { - DestroyForNearbyPlayers(); - if (force) { if (IsAlive()) @@ -1782,85 +1840,110 @@ void Creature::Respawn(bool force) setDeathState(CORPSE); } - RemoveCorpse(false); + if (m_respawnCompatibilityMode) + { + DestroyForNearbyPlayers(); + RemoveCorpse(false); - if (getDeathState() == DEAD) + 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); + + SelectLevel(); + + setDeathState(JUST_RESPAWNED); + + uint32 displayID = GetNativeDisplayId(); + if (sObjectMgr->GetCreatureModelRandomGender(&displayID)) + { + SetDisplayId(displayID); + SetNativeDisplayId(displayID); + } + + GetMotionMaster()->InitDefault(); + //Re-initialize reactstate that could be altered by movementgenerators + InitializeReactState(); + + if (IsAIEnabled) // reset the AI to be sure no dirty or uninitialized values will be used till next tick + AI()->Reset(); + + m_triggerJustAppeared = true; + + uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool(GetSpawnId()) : 0; + if (poolid) + sPoolMgr->UpdatePool(poolid, GetSpawnId()); + } + UpdateObjectVisibility(); + } + else { if (m_spawnId) - GetMap()->RemoveCreatureRespawnTime(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); - - SelectLevel(); - - setDeathState(JUST_RESPAWNED); - - uint32 displayID = GetNativeDisplayId(); - if (sObjectMgr->GetCreatureModelRandomGender(&displayID)) - { - SetDisplayId(displayID); - SetNativeDisplayId(displayID); - } - - 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 - } - - uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool(GetSpawnId()) : 0; - if (poolid) - sPoolMgr->UpdatePool(poolid, GetSpawnId()); + 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) { if (timeMSToDespawn) { - ForcedDespawnDelayEvent* pEvent = new ForcedDespawnDelayEvent(*this, forceRespawnTimer); - - m_Events.AddEvent(pEvent, m_Events.CalculateTime(timeMSToDespawn)); + m_Events.AddEvent(new ForcedDespawnDelayEvent(*this, forceRespawnTimer), m_Events.CalculateTime(timeMSToDespawn)); return; } - if (forceRespawnTimer > Seconds::zero()) + if (m_respawnCompatibilityMode) { + uint32 corpseDelay = GetCorpseDelay(); + uint32 respawnDelay = GetRespawnDelay(); + + // do it before killing creature + DestroyForNearbyPlayers(); + + bool overrideRespawnTime = false; if (IsAlive()) { - uint32 respawnDelay = m_respawnDelay; - uint32 corpseDelay = m_corpseDelay; - m_respawnDelay = forceRespawnTimer.count(); - m_corpseDelay = 0; + if (forceRespawnTimer > Seconds::zero()) + { + SetCorpseDelay(0); + SetRespawnDelay(forceRespawnTimer.count()); + overrideRespawnTime = true; + } + setDeathState(JUST_DIED); - m_respawnDelay = respawnDelay; - m_corpseDelay = corpseDelay; - } - else - { - m_corpseRemoveTime = time(nullptr); - m_respawnTime = time(nullptr) + forceRespawnTimer.count(); } + + // Skip corpse decay time + RemoveCorpse(!overrideRespawnTime); + + SetCorpseDelay(corpseDelay); + SetRespawnDelay(respawnDelay); } else - if (IsAlive()) - setDeathState(JUST_DIED); + { + if (forceRespawnTimer > Seconds::zero()) + SaveRespawnTime(forceRespawnTimer.count()); + else + { + 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(); + } - RemoveCorpse(false); + AddObjectToRemoveList(); + } } void Creature::DespawnOrUnsummon(uint32 msTimeToDespawn /*= 0*/, Seconds const& forceRespawnTimer /*= 0*/) @@ -2209,12 +2292,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; + } + + time_t 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(), savetodb && m_creatureData && m_creatureData->dbData); } // this should not be called by petAI or @@ -2405,29 +2495,26 @@ time_t Creature::GetRespawnTimeEx() const void Creature::GetRespawnPosition(float &x, float &y, float &z, float* ori, float* dist) const { - 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; + if (ori) + m_creatureData->spawnPoint.GetPosition(x, y, z, *ori); + else + m_creatureData->spawnPoint.GetPosition(x, y, z); - return; - } + 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; } - - x = GetPositionX(); - y = GetPositionY(); - z = GetPositionZ(); - if (ori) - *ori = GetOrientation(); - if (dist) - *dist = 0; } void Creature::AllLootRemovedFromCorpse() @@ -2478,7 +2565,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; @@ -2916,3 +3003,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 456d85b98a7..12c9bb3618a 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -72,7 +72,7 @@ class TC_GAME_API Creature : public Unit, public GridObject, public Ma void DisappearAndDie(); - bool Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, uint32 entry, float x, float y, float z, float ang, CreatureData const* data = nullptr, uint32 vehId = 0); + bool Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, uint32 entry, Position const& pos, CreatureData const* data = nullptr, uint32 vehId = 0, bool dynamic = false); bool LoadCreaturesAddon(); void SelectLevel(); void UpdateLevelDependantStats(); @@ -168,8 +168,7 @@ class TC_GAME_API Creature : public Unit, public GridObject, public Ma void setDeathState(DeathState s) override; // override virtual Unit::setDeathState - bool LoadFromDB(ObjectGuid::LowType spawnId, Map* map) { return LoadCreatureFromDB(spawnId, map, false); } - bool LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap = true, bool allowDuplicate = false); + bool LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool allowDuplicate); void SaveToDB(); // overriden in Pet virtual void SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask); @@ -232,7 +231,7 @@ class TC_GAME_API Creature : public Unit, public GridObject, 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; } @@ -303,6 +302,10 @@ class TC_GAME_API Creature : public Unit, public GridObject, 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; @@ -320,6 +323,7 @@ class TC_GAME_API Creature : public Unit, public GridObject, public Ma CreatureTextRepeatIds GetTextRepeatGroup(uint8 textGroup); void SetTextRepeatId(uint8 textGroup, uint8 id); void ClearTextRepeatGroup(uint8 textGroup); + bool IsEscortNPC(bool onlyIfActive = true); bool CanGiveExperience() const; @@ -389,7 +393,8 @@ class TC_GAME_API Creature : public Unit, public GridObject, public Ma //Formation var CreatureGroup* m_formation; - bool m_TriggerJustRespawned; + bool m_triggerJustAppeared; + 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 a3f7234993b..960c1bce9f5 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -20,6 +20,7 @@ #include "DBCEnums.h" #include "SharedDefines.h" +#include "SpawnData.h" #include "UnitDefines.h" #include "WorldPacket.h" #include @@ -237,38 +238,19 @@ struct EquipmentInfo }; // from `creature` table -struct CreatureData +struct CreatureData : public SpawnData { - CreatureData() : id(0), mapid(0), phaseMask(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), - spawnMask(0), npcflag(0), unit_flags(0), dynamicflags(0), phaseUseFlags(0), - phaseId(0), phaseGroup(0), terrainSwapMap(-1), ScriptId(0), dbData(true) { } - uint32 id; // entry in creature_template - uint16 mapid; - uint32 phaseMask; - uint32 displayid; - int8 equipmentId; - float posX; - float posY; - float posZ; - float orientation; - uint32 spawntimesecs; - float spawndist; - uint32 currentwaypoint; - uint32 curhealth; - uint32 curmana; - uint8 movementType; - uint8 spawnMask; - uint32 npcflag; - uint32 unit_flags; // enum UnitFlags mask values - uint32 dynamicflags; - uint8 phaseUseFlags; - uint32 phaseId; - uint32 phaseGroup; - int32 terrainSwapMap; - uint32 ScriptId; - bool dbData; + 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; + uint32 npcflag = 0; + uint32 unit_flags = 0; + uint32 dynamicflags = 0; }; struct CreatureModelInfo diff --git a/src/server/game/Entities/Creature/TemporarySummon.cpp b/src/server/game/Entities/Creature/TemporarySummon.cpp index 331b3e00274..c6ad3876173 100644 --- a/src/server/game/Entities/Creature/TemporarySummon.cpp +++ b/src/server/game/Entities/Creature/TemporarySummon.cpp @@ -57,6 +57,7 @@ void TempSummon::Update(uint32 diff) switch (m_type) { case TEMPSUMMON_MANUAL_DESPAWN: + case TEMPSUMMON_DEAD_DESPAWN: break; case TEMPSUMMON_TIMED_DESPAWN: { @@ -104,7 +105,7 @@ void TempSummon::Update(uint32 diff) case TEMPSUMMON_CORPSE_DESPAWN: { // if m_deathState is DEAD, CORPSE was skipped - if (m_deathState == CORPSE || m_deathState == DEAD) + if (m_deathState == CORPSE) { UnSummon(); return; @@ -112,19 +113,9 @@ void TempSummon::Update(uint32 diff) break; } - case TEMPSUMMON_DEAD_DESPAWN: - { - if (m_deathState == DEAD) - { - UnSummon(); - return; - } - break; - } case TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN: { - // if m_deathState is DEAD, CORPSE was skipped - if (m_deathState == CORPSE || m_deathState == DEAD) + if (m_deathState == CORPSE) { UnSummon(); return; @@ -146,13 +137,6 @@ void TempSummon::Update(uint32 diff) } case TEMPSUMMON_TIMED_OR_DEAD_DESPAWN: { - // if m_deathState is DEAD, CORPSE was skipped - if (m_deathState == DEAD) - { - UnSummon(); - return; - } - if (!IsInCombat() && IsAlive()) { if (m_timer <= diff) diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index 4f24bd3a5e6..3d936750c46 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -55,7 +55,7 @@ QuaternionData QuaternionData::fromEulerAnglesZYX(float Z, float Y, float X) } GameObject::GameObject() : WorldObject(false), MapObject(), - m_model(nullptr), m_goValue(), m_AI(nullptr) + m_model(nullptr), m_goValue(), m_AI(nullptr), m_respawnCompatibilityMode(false) { m_objectType |= TYPEMASK_GAMEOBJECT; m_objectTypeId = TYPEID_GAMEOBJECT; @@ -65,6 +65,7 @@ GameObject::GameObject() : WorldObject(false), MapObject(), m_valuesCount = GAMEOBJECT_END; m_respawnTime = 0; m_respawnDelayTime = 300; + m_despawnDelay = 0; m_lootState = GO_NOT_READY; m_spawnedByDefault = true; m_usetimes = 0; @@ -196,8 +197,7 @@ void GameObject::RemoveFromWorld() } } -bool GameObject::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, uint32 /*phaseMask*/, Position const& pos, QuaternionData const& rotation, uint32 animprogress, GOState go_state, uint32 artKit /*= 0*/) - +bool GameObject::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, uint32 /*phaseMask*/, Position const& pos, QuaternionData const& rotation, uint32 animprogress, GOState go_state, uint32 artKit /*= 0*/, bool dynamic, ObjectGuid::LowType spawnid) { ASSERT(map); SetMap(map); @@ -210,6 +210,10 @@ bool GameObject::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, u return false; } + // Set if this object can handle dynamic spawns + if (!dynamic) + SetRespawnCompatibilityMode(); + SetZoneScript(); if (m_zoneScript) { @@ -348,6 +352,9 @@ bool GameObject::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, u if (map->Is25ManRaid()) loot.maxDuplicates = 3; + if (spawnid) + m_spawnId = spawnid; + if (uint32 linkedEntry = GetGOInfo()->GetLinkedGameObjectEntry()) { GameObject* linkedGO = new GameObject(); @@ -370,6 +377,14 @@ void GameObject::Update(uint32 diff) else if (!AIM_Initialize()) TC_LOG_ERROR("misc", "Could not initialize GameObjectAI"); + if (m_despawnDelay) + { + if (m_despawnDelay > diff) + m_despawnDelay -= diff; + else + DespawnOrUnsummon(0ms, m_despawnRespawnTime);; + } + switch (m_lootState) { case GO_NOT_READY: @@ -471,83 +486,90 @@ void GameObject::Update(uint32 diff) } 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(HighGuid::GameObject, 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; - } - - m_respawnTime = 0; - m_SkillupList.clear(); - m_usetimes = 0; - - // 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 + ObjectGuid dbtableHighGuid(HighGuid::GameObject, GetEntry(), m_spawnId); + time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid); + if (linkedRespawntime) // Can't respawn, the master is dead { - Unit* caster = GetOwner(); - if (caster && caster->GetTypeId() == TYPEID_PLAYER) - { - caster->ToPlayer()->RemoveGameObject(this, false); - - WorldPacket data(SMSG_FISH_ESCAPED, 0); - caster->ToPlayer()->SendDirectMessage(&data); - } - // can be delete - m_lootState = GO_JUST_DEACTIVATED; + 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; } - 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.minSuccessOpens, GetGOInfo()->fishinghole.maxSuccessOpens); - break; - default: - break; + + m_respawnTime = 0; + m_SkillupList.clear(); + m_usetimes = 0; + + // 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 + { + Unit* caster = GetOwner(); + if (caster && caster->GetTypeId() == TYPEID_PLAYER) + { + caster->ToPlayer()->RemoveGameObject(this, false); + + WorldPacket data(SMSG_FISH_ESCAPED, 0); + caster->ToPlayer()->SendDirectMessage(&data); + } + // 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.minSuccessOpens, GetGOInfo()->fishinghole.maxSuccessOpens); + break; + default: + break; + } + + // 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(); + + // Respawn timer + uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool(GetSpawnId()) : 0; + if (poolid) + sPoolMgr->UpdatePool(poolid, GetSpawnId()); + else + GetMap()->AddToMap(this); } - - // 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(); - - // Respawn timer - uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool(GetSpawnId()) : 0; - if (poolid) - sPoolMgr->UpdatePool(poolid, GetSpawnId()); - else - GetMap()->AddToMap(this); } } + // Set respawn timer + if (!m_respawnCompatibilityMode && m_respawnTime > 0) + SaveRespawnTime(0, false); + if (isSpawned()) { GameObjectTemplate const* goInfo = GetGOInfo(); @@ -726,6 +748,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; @@ -733,12 +756,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; @@ -762,6 +801,25 @@ void GameObject::AddUniqueUse(Player* player) m_unique_users.insert(player->GetGUID()); } +void GameObject::DespawnOrUnsummon(Milliseconds const& delay, Seconds const& forceRespawnTime) +{ + if (delay > 0ms) + { + if (!m_despawnDelay || m_despawnDelay > delay.count()) + { + m_despawnDelay = delay.count(); + m_despawnRespawnTime = forceRespawnTime; + } + } + else + { + uint32 const respawnDelay = (forceRespawnTime > 0s) ? forceRespawnTime.count() : m_respawnDelayTime; + if (m_goData && respawnDelay) + SaveRespawnTime(respawnDelay); + Delete(); + } +} + void GameObject::Delete() { SetLootState(GO_NOT_READY); @@ -838,7 +896,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!"); @@ -858,22 +916,22 @@ void GameObject::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask) 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.spawnPoint.WorldRelocate(this); data.phaseMask = phaseMask; - data.posX = GetPositionX(); - data.posY = GetPositionY(); - data.posZ = GetPositionZ(); - data.orientation = GetOrientation(); data.rotation = m_worldRotation; data.spawntimesecs = m_spawnedByDefault ? m_respawnDelayTime : -(int32)m_respawnDelayTime; data.animprogress = GetGoAnimProgress(); - data.go_state = GetGoState(); + data.goState = GetGoState(); data.spawnMask = spawnMask; data.artKit = GetGoArtKit(); + if (!data.spawnGroupData) + data.spawnGroupData = sObjectMgr->GetDefaultSpawnGroup(); // Update in DB SQLTransaction trans = WorldDatabase.BeginTransaction(); @@ -906,9 +964,9 @@ void GameObject::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask) 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: %u) not found in table `gameobject`, can't load. ", spawnId); @@ -918,14 +976,14 @@ bool GameObject::LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, boo uint32 entry = data->id; //uint32 map_id = data->mapid; // already used before call uint32 phaseMask = data->phaseMask; - 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(map->GenerateLowGuid(), entry, map, phaseMask, pos, data->rotation, animprogress, go_state, artKit)) + m_respawnCompatibilityMode = ((data->spawnGroupData->flags & SPAWNGROUP_FLAG_COMPATIBILITY_MODE) != 0); + if (!Create(map->GenerateLowGuid(), entry, map, phaseMask, data->spawnPoint, data->rotation, animprogress, go_state, artKit, !m_respawnCompatibilityMode)) return false; PhasingHandler::InitDbPhaseShift(GetPhaseShift(), data->phaseUseFlags, data->phaseId, data->phaseGroup); @@ -950,12 +1008,18 @@ 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); } } } else { + if (!m_respawnCompatibilityMode) + { + TC_LOG_WARN("sql.sql", "GameObject %u (SpawnID %u) is not spawned by default, but tries to use a non-hack spawn system. This will not work. Defaulting to compatibility mode.", entry, spawnId); + m_respawnCompatibilityMode = true; + } + m_spawnedByDefault = false; m_respawnDelayTime = -data->spawntimesecs; m_respawnTime = 0; @@ -971,20 +1035,24 @@ 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); + + SQLTransaction trans = WorldDatabase.BeginTransaction(); PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT); + trans->Append(stmt); - stmt->setUInt32(0, m_spawnId); - - WorldDatabase.Execute(stmt); + stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_SPAWNGROUP_MEMBER); + stmt->setUInt8(0, uint8(SPAWN_TYPE_GAMEOBJECT)); + stmt->setUInt32(1, m_spawnId); + trans->Append(stmt); stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_EVENT_GAMEOBJECT); - stmt->setUInt32(0, m_spawnId); + trans->Append(stmt); - WorldDatabase.Execute(stmt); + WorldDatabase.CommitTransaction(trans); } /*********************************************************/ @@ -1047,10 +1115,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 && (forceDelay || m_respawnTime > GameTime::GetGameTime()) && 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::IsNeverVisible() const @@ -1110,12 +1187,29 @@ uint8 GameObject::getLevelForTarget(WorldObject const* target) const return 1; } +time_t GameObject::GetRespawnTimeEx() const +{ + time_t now = GameTime::GetGameTime(); + if (m_respawnTime > now) + return m_respawnTime; + else + return now; +} + +void GameObject::SetRespawnTime(int32 respawn) +{ + m_respawnTime = respawn > 0 ? GameTime::GetGameTime() + respawn : 0; + m_respawnDelayTime = respawn > 0 ? respawn : 0; + if (respawn && !m_spawnedByDefault) + UpdateObjectVisibility(true); +} + 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); } } @@ -1219,7 +1313,7 @@ void GameObject::UseDoorOrButton(uint32 time_to_restore, bool alternative /* = f void GameObject::SetGoArtKit(uint8 kit) { SetByteValue(GAMEOBJECT_BYTES_1, 2, kit); - GameObjectData* data = const_cast(sObjectMgr->GetGOData(m_spawnId)); + GameObjectData* data = const_cast(sObjectMgr->GetGameObjectData(m_spawnId)); if (data) data->artKit = kit; } @@ -1230,10 +1324,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(data)->artKit = artkit; @@ -1963,8 +2057,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; @@ -2447,24 +2541,20 @@ void GameObject::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* t 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 @@ -2508,7 +2598,7 @@ public: virtual G3D::Vector3 GetPosition() const override { return G3D::Vector3(_owner->GetPositionX(), _owner->GetPositionY(), _owner->GetPositionZ()); } virtual float GetOrientation() const override { return _owner->GetOrientation(); } virtual float GetScale() const override { return _owner->GetObjectScale(); } - virtual void DebugVisualizeCorner(G3D::Vector3 const& corner) const override { _owner->SummonCreature(1, corner.x, corner.y, corner.z, 0, TEMPSUMMON_MANUAL_DESPAWN); } + virtual void DebugVisualizeCorner(G3D::Vector3 const& corner) const override { const_cast(_owner)->SummonCreature(1, corner.x, corner.y, corner.z, 0, TEMPSUMMON_MANUAL_DESPAWN); } private: GameObject* _owner; diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h index 0b82b915935..8654c8b2bc7 100644 --- a/src/server/game/Entities/GameObject/GameObject.h +++ b/src/server/game/Entities/GameObject/GameObject.h @@ -91,11 +91,11 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject void RemoveFromWorld() override; void CleanupsBeforeDelete(bool finalCleanup = true) override; - bool Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, uint32 phaseMask, Position const& pos, QuaternionData const& rotation, uint32 animprogress, GOState go_state, uint32 artKit = 0); + bool Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, uint32 phaseMask, Position const& pos, QuaternionData const& rotation, uint32 animprogress, GOState go_state, uint32 artKit = 0, bool dynamic = false, ObjectGuid::LowType spawnid = 0); 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; @@ -115,8 +115,7 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject void SaveToDB(); void SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask); - bool LoadFromDB(ObjectGuid::LowType spawnId, Map* map) { return LoadGameObjectFromDB(spawnId, map, false); } - bool LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap = true); + 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) @@ -140,20 +139,9 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject uint32 GetSpellId() const { return m_spellId;} time_t GetRespawnTime() const { return m_respawnTime; } - time_t GetRespawnTimeEx() const - { - time_t now = time(nullptr); - if (m_respawnTime > now) - return m_respawnTime; - else - return now; - } + time_t GetRespawnTimeEx() const; - void SetRespawnTime(int32 respawn) - { - m_respawnTime = respawn > 0 ? time(nullptr) + respawn : 0; - m_respawnDelayTime = respawn > 0 ? respawn : 0; - } + void SetRespawnTime(int32 respawn); void Respawn(); bool isSpawned() const { @@ -165,6 +153,7 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject void SetSpawnedByDefault(bool b) { m_spawnedByDefault = b; } uint32 GetRespawnDelay() const { return m_respawnDelayTime; } void Refresh(); + void DespawnOrUnsummon(Milliseconds const& delay = 0ms, Seconds const& forceRespawnTime = 0s); void Delete(); void SendGameObjectDespawn(); void getFishLoot(Loot* loot, Player* loot_owner); @@ -216,7 +205,7 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject uint32 GetUseCount() const { return m_usetimes; } uint32 GetUniqueUseCount() const { return m_unique_users.size(); } - void SaveRespawnTime() override; + void SaveRespawnTime(uint32 forceDelay = 0, bool savetodb = true) override; Loot loot; @@ -268,6 +257,10 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject 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; } + virtual uint32 GetScriptId() const; GameObjectAI* AI() const { return m_AI; } @@ -303,6 +296,8 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject uint32 m_spellId; time_t m_respawnTime; // (secs) time of next respawn (or despawn if GO have owner()), uint32 m_respawnDelayTime; // (secs) if 0 then current GO state no dependent from timer + uint32 m_despawnDelay; + Seconds m_despawnRespawnTime; // override respawn time after delayed despawn LootState m_lootState; ObjectGuid m_lootStateUnitGUID; // GUID of the unit passed with SetLootState(LootState, Unit*) bool m_spawnedByDefault; @@ -349,5 +344,6 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject } GameObjectAI* m_AI; + bool m_respawnCompatibilityMode; }; #endif diff --git a/src/server/game/Entities/GameObject/GameObjectData.h b/src/server/game/Entities/GameObject/GameObjectData.h index d86d2bd4448..31a99eca2fc 100644 --- a/src/server/game/Entities/GameObject/GameObjectData.h +++ b/src/server/game/Entities/GameObject/GameObjectData.h @@ -20,6 +20,7 @@ #include "Common.h" #include "SharedDefines.h" +#include "SpawnData.h" #include "WorldPacket.h" #include #include @@ -594,31 +595,14 @@ struct GameObjectAddon uint32 InvisibilityValue; }; -// from `gameobject` -struct GameObjectData +// `gameobject` table +struct GameObjectData : public SpawnData { - explicit GameObjectData() : id(0), mapid(0), phaseMask(0), posX(0.0f), posY(0.0f), posZ(0.0f), orientation(0.0f), spawntimesecs(0), - animprogress(0), go_state(GO_STATE_ACTIVE), spawnMask(0), artKit(0), phaseUseFlags(0), phaseId(0), - phaseGroup(0), terrainSwapMap(-1), ScriptId(0), dbData(true) { } - uint32 id; // entry in gamobject_template - uint16 mapid; - uint32 phaseMask; - float posX; - float posY; - float posZ; - float orientation; + GameObjectData() : SpawnData(SPAWN_TYPE_GAMEOBJECT) { } QuaternionData rotation; - int32 spawntimesecs; - uint32 animprogress; - GOState go_state; - uint8 spawnMask; - 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 acabe1ea1b5..2672c22ee1a 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -2102,7 +2102,7 @@ TempSummon* Map::SummonCreature(uint32 entry, Position const& pos, SummonPropert break; } - if (!summon->Create(GenerateLowGuid(), this, 0, entry, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), nullptr, vehId)) + if (!summon->Create(GenerateLowGuid(), this, 0, entry, pos, nullptr, vehId, true)) { delete summon; return nullptr; @@ -2170,13 +2170,13 @@ void WorldObject::ClearZoneScript() m_zoneScript = nullptr; } -TempSummon* WorldObject::SummonCreature(uint32 entry, Position const& pos, TempSummonType spwtype /*= TEMPSUMMON_MANUAL_DESPAWN*/, uint32 duration /*= 0*/, uint32 vehId /*= 0*/, bool visibleBySummonerOnly /*= false*/) +TempSummon* WorldObject::SummonCreature(uint32 entry, Position const& pos, TempSummonType despawnType /*= TEMPSUMMON_MANUAL_DESPAWN*/, uint32 despawnTime /*= 0*/, uint32 vehId /*= 0*/, bool visibleBySummonerOnly /*= false*/) { if (Map* map = FindMap()) { - if (TempSummon* summon = map->SummonCreature(entry, pos, nullptr, duration, ToUnit(), 0, vehId, visibleBySummonerOnly)) + if (TempSummon* summon = map->SummonCreature(entry, pos, nullptr, despawnTime, ToUnit(), 0, vehId, visibleBySummonerOnly)) { - summon->SetTempSummonType(spwtype); + summon->SetTempSummonType(despawnType); return summon; } } @@ -2184,17 +2184,13 @@ TempSummon* WorldObject::SummonCreature(uint32 entry, Position const& pos, TempS return nullptr; } -TempSummon* WorldObject::SummonCreature(uint32 id, float x, float y, float z, float ang /*= 0*/, TempSummonType spwtype /*= TEMPSUMMON_MANUAL_DESPAWN*/, uint32 despwtime /*= 0*/, bool visibleBySummonerOnly /*= false*/) +TempSummon* WorldObject::SummonCreature(uint32 id, float x, float y, float z, float o /*= 0*/, TempSummonType despawnType /*= TEMPSUMMON_MANUAL_DESPAWN*/, uint32 despawnTime /*= 0*/, bool visibleBySummonerOnly /*= false*/) { if (!x && !y && !z) - { GetClosePoint(x, y, z, GetObjectSize()); - ang = GetOrientation(); - } - - Position pos; - pos.Relocate(x, y, z, ang); - return SummonCreature(id, pos, spwtype, despwtime, 0, visibleBySummonerOnly); + if (!o) + o = GetOrientation(); + return SummonCreature(id, { x, y, z, o }, despawnType, despawnTime, 0, visibleBySummonerOnly); } GameObject* WorldObject::SummonGameObject(uint32 entry, Position const& pos, QuaternionData const& rot, uint32 respawnTime, GOSummonType summonType) diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index 988d36df36e..2b84e0b9f1f 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -20,6 +20,7 @@ #define _OBJECT_H #include "Common.h" +#include "Duration.h" #include "GridReference.h" #include "GridRefManager.h" #include "ModelIgnoreFlags.h" @@ -362,7 +363,7 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation void PlayDirectSound(uint32 sound_id, Player* target = nullptr); void PlayDirectMusic(uint32 music_id, Player* target = nullptr); - virtual void SaveRespawnTime() { } + virtual void SaveRespawnTime(uint32 /*forceDelay*/ = 0, bool /*saveToDB*/ = true) { } void AddObjectToRemoveList(); float GetGridActivationRange() const; @@ -392,8 +393,9 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation void ClearZoneScript(); ZoneScript* GetZoneScript() const { return m_zoneScript; } - TempSummon* SummonCreature(uint32 id, Position const& pos, TempSummonType spwtype = TEMPSUMMON_MANUAL_DESPAWN, uint32 despwtime = 0, uint32 vehId = 0, bool visibleBySummonerOnly = false); - TempSummon* SummonCreature(uint32 id, float x, float y, float z, float ang = 0, TempSummonType spwtype = TEMPSUMMON_MANUAL_DESPAWN, uint32 despwtime = 0, bool visibleBySummonerOnly = false); + TempSummon* SummonCreature(uint32 entry, Position const& pos, TempSummonType despawnType = TEMPSUMMON_MANUAL_DESPAWN, uint32 despawnTime = 0, uint32 vehId = 0, bool visibleBySummonerOnly = false); + TempSummon* SummonCreature(uint32 entry, Position const& pos, TempSummonType despawnType, Milliseconds const& despawnTime, uint32 vehId = 0, bool visibleBySummonerOnly = false) { return SummonCreature(entry, pos, despawnType, uint32(despawnTime.count()), vehId, visibleBySummonerOnly); } + TempSummon* SummonCreature(uint32 entry, float x, float y, float z, float o = 0, TempSummonType despawnType = TEMPSUMMON_MANUAL_DESPAWN, uint32 despawnTime = 0, bool visibleBySummonerOnly = false); GameObject* SummonGameObject(uint32 entry, Position const& pos, QuaternionData const& rot, uint32 respawnTime /* s */, GOSummonType summonType = GO_SUMMON_TIMED_OR_CORPSE_DESPAWN); GameObject* SummonGameObject(uint32 entry, float x, float y, float z, float ang, QuaternionData const& rot, uint32 respawnTime /* s */); Creature* SummonTrigger(float x, float y, float z, float ang, uint32 dur, CreatureAI* (*GetAI)(Creature*) = nullptr); diff --git a/src/server/game/Entities/Object/ObjectGuid.cpp b/src/server/game/Entities/Object/ObjectGuid.cpp index b8e5e9290a8..ea45a44c564 100644 --- a/src/server/game/Entities/Object/ObjectGuid.cpp +++ b/src/server/game/Entities/Object/ObjectGuid.cpp @@ -114,6 +114,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(); +} + #define GUID_TRAIT_INSTANTIATE_GUID( HIGH_GUID ) \ template class TC_GAME_API ObjectGuidGenerator< HIGH_GUID >; diff --git a/src/server/game/Entities/Object/ObjectGuid.h b/src/server/game/Entities/Object/ObjectGuid.h index 97edc5e7e26..9022cf12722 100644 --- a/src/server/game/Entities/Object/ObjectGuid.h +++ b/src/server/game/Entities/Object/ObjectGuid.h @@ -318,6 +318,7 @@ class TC_GAME_API ObjectGuidGeneratorBase protected: static void HandleCounterOverflow(HighGuid high); + static void CheckGuidTrigger(ObjectGuid::LowType guid); ObjectGuid::LowType _nextGuid; }; @@ -331,6 +332,10 @@ class ObjectGuidGenerator : public ObjectGuidGeneratorBase { if (_nextGuid >= ObjectGuid::GetMaxCounter(high) - 1) HandleCounterOverflow(high); + + if (high == HighGuid::Unit || high == HighGuid::GameObject) + CheckGuidTrigger(_nextGuid); + return _nextGuid++; } }; diff --git a/src/server/game/Entities/Object/Position.h b/src/server/game/Entities/Object/Position.h index 1724a3cf917..3ed046caae2 100644 --- a/src/server/game/Entities/Object/Position.h +++ b/src/server/game/Entities/Object/Position.h @@ -207,15 +207,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; bool HasInArc(float arcangle, Position const* pos, float border = 2.0f) const; @@ -240,16 +234,13 @@ class WorldLocation : public Position WorldLocation(WorldLocation const& loc) : Position(loc), m_mapId(loc.GetMapId()) { } - void WorldRelocate(WorldLocation const& 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 = loc.GetMapId(); - Relocate(loc); - } - - 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; - Relocate(_x, _y, _z, _o); + m_mapId = mapId; + Relocate(x, y, z, o); } WorldLocation GetWorldLocation() const diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 4829e90a6fc..c12b354bb31 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -7315,24 +7315,26 @@ void Player::UpdateArea(uint32 newArea) void Player::UpdateZone(uint32 newZone, uint32 newArea) { - if (m_zoneUpdateId != newZone) + if (!IsInWorld()) + return; + + uint32 const oldZone = m_zoneUpdateId; + m_zoneUpdateId = newZone; + m_zoneUpdateTimer = ZONE_UPDATE_INTERVAL; + + GetMap()->UpdatePlayerZoneStats(oldZone, newZone); + + // call leave script hooks immedately (before updating flags) + if (oldZone != newZone) { sOutdoorPvPMgr->HandlePlayerLeaveZone(this, m_zoneUpdateId); - sOutdoorPvPMgr->HandlePlayerEnterZone(this, newZone); sBattlefieldMgr->HandlePlayerLeaveZone(this, m_zoneUpdateId); - sBattlefieldMgr->HandlePlayerEnterZone(this, newZone); - SendInitWorldStates(newZone, newArea); // only if really enters to new zone, not just area change, works strange... - if (Guild* guild = GetGuild()) - guild->UpdateMemberData(this, GUILD_MEMBER_DATA_ZONEID, newZone); } // group update if (GetGroup()) SetGroupUpdateFlag(GROUP_UPDATE_FULL); - m_zoneUpdateId = newZone; - m_zoneUpdateTimer = ZONE_UPDATE_INTERVAL; - // zone changed, so area changed as well, update it UpdateArea(newArea); @@ -7349,8 +7351,6 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea) WeatherMgr::SendFineWeatherUpdateToPlayer(this); } - sScriptMgr->OnPlayerUpdateZone(this, newZone, newArea); - // in PvP, any not controlled zone (except zone->team == 6, default case) // in PvE, only opposition team capital switch (zone->team) @@ -7397,6 +7397,17 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea) UpdateLocalChannels(newZone); UpdateZoneDependentAuras(newZone); + + // call enter script hooks after everyting else has processed + sScriptMgr->OnPlayerUpdateZone(this, newZone, newArea); + if (oldZone != newZone) + { + sOutdoorPvPMgr->HandlePlayerEnterZone(this, newZone); + sBattlefieldMgr->HandlePlayerEnterZone(this, newZone); + SendInitWorldStates(newZone, newArea); // only if really enters to new zone, not just area change, works strange... + if (Guild* guild = GetGuild()) + guild->UpdateMemberData(this, GUILD_MEMBER_DATA_ZONEID, newZone); + } } //If players are too far away from the duel flag... they lose the duel diff --git a/src/server/game/Entities/Transport/Transport.cpp b/src/server/game/Entities/Transport/Transport.cpp index 67567843778..31e2c988a26 100644 --- a/src/server/game/Entities/Transport/Transport.cpp +++ b/src/server/game/Entities/Transport/Transport.cpp @@ -299,16 +299,14 @@ Creature* Transport::CreateNPCPassenger(ObjectGuid::LowType guid, CreatureData c Map* map = GetMap(); Creature* creature = new Creature(); - if (!creature->LoadCreatureFromDB(guid, map, false)) + if (!creature->LoadFromDB(guid, map, false, true)) { delete creature; return nullptr; } - 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(); @@ -348,7 +346,7 @@ GameObject* Transport::CreateGOPassenger(ObjectGuid::LowType guid, GameObjectDat Map* map = GetMap(); GameObject* go = new GameObject(); - if (!go->LoadGameObjectFromDB(guid, map, false)) + if (!go->LoadFromDB(guid, map, false)) { delete go; return nullptr; @@ -356,10 +354,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(), map, 0, entry, x, y, z, o, nullptr, vehId)) + if (!summon->Create(map->GenerateLowGuid(), map, 0, 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 fab4f09bb09..86ab636931a 100644 --- a/src/server/game/Events/GameEventMgr.cpp +++ b/src/server/game/Events/GameEventMgr.cpp @@ -447,7 +447,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: %u) not found in `gameobject` table.", guid); @@ -1193,7 +1193,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) { @@ -1256,13 +1256,13 @@ void GameEventMgr::GameEventSpawn(int16 event_id) sObjectMgr->AddCreatureToGrid(*itr, data); // Spawn if necessary (loaded grids only) - Map* map = sMapMgr->CreateBaseMap(data->mapid); + Map* map = sMapMgr->CreateBaseMap(data->spawnPoint.GetMapId()); // We use spawn coords to spawn - if (!map->Instanceable() && map->IsGridLoaded(data->posX, data->posY)) + if (!map->Instanceable() && map->IsGridLoaded(data->spawnPoint)) { Creature* creature = new Creature(); //TC_LOG_DEBUG("misc", "Spawning creature %u", *itr); - if (!creature->LoadCreatureFromDB(*itr, map)) + if (!creature->LoadFromDB(*itr, map, true, false)) delete creature; } } @@ -1278,19 +1278,19 @@ 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->CreateBaseMap(data->mapid); + Map* map = sMapMgr->CreateBaseMap(data->spawnPoint.GetMapId()); // We use current coords to unspawn, not spawn coords since creature can have changed grid - if (!map->Instanceable() && map->IsGridLoaded(data->posX, data->posY)) + if (!map->Instanceable() && map->IsGridLoaded(data->spawnPoint)) { GameObject* pGameobject = new GameObject; //TC_LOG_DEBUG("misc", "Spawning gameobject %u", *itr); /// @todo find out when it is add to map - if (!pGameobject->LoadGameObjectFromDB(*itr, map, false)) + if (!pGameobject->LoadFromDB(*itr, map, false)) delete pGameobject; else { @@ -1333,13 +1333,13 @@ 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 it = creatureBounds.first; it != creatureBounds.second; ) + for (auto itr2 = creatureBounds.first; itr2 != creatureBounds.second;) { - Creature* creature = it->second; - ++it; + Creature* creature = itr2->second; + ++itr2; creature->AddObjectToRemoveList(); } }); @@ -1359,17 +1359,17 @@ 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 it = gameObjectBounds.first; it != gameObjectBounds.second; ) + auto gameobjectBounds = map->GetGameObjectBySpawnIdStore().equal_range(*itr); + for (auto itr2 = gameobjectBounds.first; itr2 != gameobjectBounds.second;) { - GameObject* go = it->second; - ++it; + GameObject* go = itr2->second; + ++itr2; go->AddObjectToRemoveList(); } }); @@ -1397,7 +1397,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 it = creatureBounds.first; it != creatureBounds.second; ++it) diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index a7a33a3193a..292cce2606f 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -34,6 +34,7 @@ #include "GroupMgr.h" #include "GuildMgr.h" #include "InstanceSaveMgr.h" +#include "InstanceScript.h" #include "Language.h" #include "LFGMgr.h" #include "Log.h" @@ -1216,7 +1217,7 @@ void ObjectMgr::LoadGameObjectAddons() ObjectGuid::LowType guid = fields[0].GetUInt32(); - GameObjectData const* goData = GetGOData(guid); + GameObjectData const* goData = GetGameObjectData(guid); if (!goData) { TC_LOG_ERROR("sql.sql", "GameObject (GUID: %u) does not exist but has a record in `gameobject_addon`", guid); @@ -1552,8 +1553,8 @@ void ObjectMgr::LoadLinkedRespawn() break; } - MapEntry const* 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 '%u' linking to Creature '%u' on an unpermitted map.", guidLow, linkedGuidLow); error = true; @@ -1581,7 +1582,7 @@ void ObjectMgr::LoadLinkedRespawn() break; } - GameObjectData const* master = GetGOData(linkedGuidLow); + GameObjectData const* master = GetGameObjectData(linkedGuidLow); if (!master) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (linkedGuid) '%u' not found in gameobject table", linkedGuidLow); @@ -1589,8 +1590,8 @@ void ObjectMgr::LoadLinkedRespawn() break; } - MapEntry const* 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 '%u' linking to Gameobject '%u' on an unpermitted map.", guidLow, linkedGuidLow); error = true; @@ -1610,7 +1611,7 @@ void ObjectMgr::LoadLinkedRespawn() } case GO_TO_GO: { - GameObjectData const* slave = GetGOData(guidLow); + GameObjectData const* slave = GetGameObjectData(guidLow); if (!slave) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (guid) '%u' not found in gameobject table", guidLow); @@ -1618,7 +1619,7 @@ void ObjectMgr::LoadLinkedRespawn() break; } - GameObjectData const* master = GetGOData(linkedGuidLow); + GameObjectData const* master = GetGameObjectData(linkedGuidLow); if (!master) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (linkedGuid) '%u' not found in gameobject table", linkedGuidLow); @@ -1626,8 +1627,8 @@ void ObjectMgr::LoadLinkedRespawn() break; } - MapEntry const* 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 '%u' linking to Gameobject '%u' on an unpermitted map.", guidLow, linkedGuidLow); error = true; @@ -1647,7 +1648,7 @@ void ObjectMgr::LoadLinkedRespawn() } case GO_TO_CREATURE: { - GameObjectData const* slave = GetGOData(guidLow); + GameObjectData const* slave = GetGameObjectData(guidLow); if (!slave) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (guid) '%u' not found in gameobject table", guidLow); @@ -1663,8 +1664,8 @@ void ObjectMgr::LoadLinkedRespawn() break; } - MapEntry const* 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 '%u' linking to Creature '%u' on an unpermitted map.", guidLow, linkedGuidLow); error = true; @@ -1717,8 +1718,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 '%u' linking to '%u' on an unpermitted map.", guidLow, linkedGuidLow); return false; @@ -1832,8 +1833,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 22 "currentwaypoint, curhealth, curmana, MovementType, spawnMask, phaseMask, eventEntry, pool_entry, creature.npcflag, creature.unit_flags, creature.dynamicflags, creature.phaseUseFlags, " // 23 24 25 26 @@ -1875,14 +1876,11 @@ 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(); @@ -1900,18 +1898,19 @@ void ObjectMgr::LoadCreatures() data.phaseId = fields[23].GetUInt32(); data.phaseGroup = fields[24].GetUInt32(); data.terrainSwapMap = fields[25].GetInt32(); - data.ScriptId = GetScriptId(fields[26].GetString()); + data.scriptId = GetScriptId(fields[22].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: %u) that spawned at nonexistent map (Id: %u), skipped.", guid, data.mapid); + TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u) that spawned at nonexistent map (Id: %u), skipped.", guid, data.spawnPoint.GetMapId()); continue; } // Skip spawnMask check for transport maps - if (!IsTransportMap(data.mapid) && data.spawnMask & ~spawnMasks[data.mapid]) - TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u) that have wrong spawn mask %u including unsupported difficulty modes for map (Id: %u).", guid, data.spawnMask, data.mapid); + if (!IsTransportMap(data.spawnPoint.GetMapId()) && data.spawnMask & ~spawnMasks[data.spawnPoint.GetMapId()]) + TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u) that have wrong spawn mask %u including unsupported difficulty modes for map (Id: %u).", guid, data.spawnMask, data.spawnPoint.GetMapId()); bool ok = true; for (uint32 diff = 0; diff < MAX_DIFFICULTY - 1 && ok; ++diff) @@ -1970,12 +1969,6 @@ void ObjectMgr::LoadCreatures() } } - if (std::abs(data.orientation) > 2 * float(M_PI)) - { - TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u Entry: %u) with abs(`orientation`) > 2*PI (orientation is expressed in radians), normalized.", guid, data.id); - data.orientation = Position::NormalizeOrientation(data.orientation); - } - if (data.phaseMask == 0) { TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u Entry: %u) with `phaseMask`=0 (not visible for anyone), set to 1.", guid, data.id); @@ -2015,7 +2008,7 @@ void ObjectMgr::LoadCreatures() TC_LOG_ERROR("sql.sql", "Table `creature` have creature (GUID: %u Entry: %u) with `terrainSwapMap` %u does not exist, set to -1", guid, data.id, data.terrainSwapMap); data.terrainSwapMap = -1; } - else if (terrainSwapEntry->rootPhaseMap != data.mapid) + else if (terrainSwapEntry->rootPhaseMap != data.spawnPoint.GetMapId()) { TC_LOG_ERROR("sql.sql", "Table `creature` have creature (GUID: %u Entry: %u) with `terrainSwapMap` %u which cannot be used on spawn map, set to -1", guid, data.id, data.terrainSwapMap); data.terrainSwapMap = -1; @@ -2027,7 +2020,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); PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_CREATURE_ZONE_AREA_DATA); @@ -2054,8 +2047,8 @@ void ObjectMgr::AddCreatureToGrid(ObjectGuid::LowType guid, CreatureData const* { if (mask & 1) { - CellCoord cellCoord = Trinity::ComputeCellCoord(data->posX, data->posY); - CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->mapid, i)][cellCoord.GetId()]; + CellCoord cellCoord = Trinity::ComputeCellCoord(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY()); + CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->spawnPoint.GetMapId(), i)][cellCoord.GetId()]; cell_guids.creatures.insert(guid); } } @@ -2068,14 +2061,14 @@ void ObjectMgr::RemoveCreatureFromGrid(ObjectGuid::LowType guid, CreatureData co { if (mask & 1) { - CellCoord cellCoord = Trinity::ComputeCellCoord(data->posX, data->posY); - CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->mapid, i)][cellCoord.GetId()]; + CellCoord cellCoord = Trinity::ComputeCellCoord(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY()); + CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->spawnPoint.GetMapId(), i)][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) @@ -2085,41 +2078,40 @@ ObjectGuid::LowType ObjectMgr::AddGOData(uint32 entry, uint32 mapId, Position co if (!map) return 0; - ObjectGuid::LowType guid = GenerateGameObjectSpawnId(); + ObjectGuid::LowType spawnId = GenerateGameObjectSpawnId(); - GameObjectData& data = NewGOData(guid); - data.id = entry; - data.mapid = mapId; - - pos.GetPosition(data.posX, data.posY, data.posZ, data.orientation); + GameObjectData& data = NewOrExistGameObjectData(spawnId); + data.spawnId = spawnId; + data.id = entry; + data.spawnPoint.WorldRelocate(mapId, pos); data.rotation = rot; data.spawntimesecs = spawntimedelay; data.animprogress = 100; data.spawnMask = 1; - data.go_state = GO_STATE_READY; + data.goState = GO_STATE_READY; data.phaseMask = PHASEMASK_NORMAL; data.artKit = goinfo->type == GAMEOBJECT_TYPE_CAPTURE_POINT ? 21 : 0; - data.dbData = false; + data.dbData = false; - 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 = new GameObject; - if (!go->LoadGameObjectFromDB(guid, map)) + if (!go->LoadFromDB(spawnId, map, true)) { - 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); delete go; return 0; } } - TC_LOG_DEBUG("maps", "AddGOData: dbguid %u 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 %u 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*/) @@ -2134,15 +2126,14 @@ ObjectGuid::LowType ObjectMgr::AddCreatureData(uint32 entry, uint32 mapId, Posit if (!map) return 0; - 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; @@ -2155,14 +2146,15 @@ 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 = new Creature(); - if (!creature->LoadCreatureFromDB(guid, map)) + if (!creature->LoadFromDB(spawnId, map, true, true)) { TC_LOG_ERROR("misc", "AddCreature: Cannot add creature entry %u to map", entry); delete creature; @@ -2170,10 +2162,10 @@ ObjectGuid::LowType ObjectMgr::AddCreatureData(uint32 entry, uint32 mapId, Posit } } - return guid; + return spawnId; } -void ObjectMgr::LoadGameobjects() +void ObjectMgr::LoadGameObjects() { uint32 oldMSTime = getMSTime(); @@ -2240,21 +2232,20 @@ void ObjectMgr::LoadGameobjects() GameObjectData& data = _gameObjectDataStore[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.spawnId = guid; + data.id = entry; + 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: %u 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: %u Entry: %u) spawned on a non-existed map (Id: %u), skip", guid, data.id, data.spawnPoint.GetMapId()); continue; } @@ -2275,12 +2266,12 @@ void ObjectMgr::LoadGameobjects() continue; } } - data.go_state = GOState(go_state); + data.goState = GOState(go_state); data.spawnMask = fields[14].GetUInt8(); - if (!IsTransportMap(data.mapid) && data.spawnMask & ~spawnMasks[data.mapid]) - TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) that has wrong spawn mask %u including unsupported difficulty modes for map (Id: %u), skip", guid, data.id, data.spawnMask, data.mapid); + if (!IsTransportMap(data.spawnPoint.GetMapId()) && data.spawnMask & ~spawnMasks[data.spawnPoint.GetMapId()]) + TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) that has wrong spawn mask %u including unsupported difficulty modes for map (Id: %u), skip", guid, data.id, data.spawnMask, data.spawnPoint.GetMapId()); data.phaseMask = fields[15].GetUInt32(); int16 gameEvent = fields[16].GetInt8(); @@ -2323,20 +2314,14 @@ void ObjectMgr::LoadGameobjects() TC_LOG_ERROR("sql.sql", "Table `gameobject` have gameobject (GUID: %u Entry: %u) with `terrainSwapMap` %u does not exist, set to -1", guid, data.id, data.terrainSwapMap); data.terrainSwapMap = -1; } - else if (terrainSwapEntry->rootPhaseMap != data.mapid) + else if (terrainSwapEntry->rootPhaseMap != data.spawnPoint.GetMapId()) { TC_LOG_ERROR("sql.sql", "Table `gameobject` have gameobject (GUID: %u 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[22].GetString()); - - if (std::abs(data.orientation) > 2 * float(M_PI)) - { - TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u 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[22].GetString()); if (data.rotation.x < -1.0f || data.rotation.x > 1.0f) { @@ -2362,7 +2347,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: %u Entry: %u) with invalid coordinates, skip", guid, data.id); continue; @@ -2379,7 +2364,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); PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_GAMEOBJECT_ZONE_AREA_DATA); @@ -2398,6 +2383,207 @@ 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); + } 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; + } + 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); + } + + 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].GetUInt32(); + + SpawnData const* data = GetSpawnData(spawnType, spawnId); + if (!data) + { + TC_LOG_ERROR("server.loading", "Spawn data with ID (%u,%u) 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,%u) 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,%u), 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,%u) has map id %u - spawn NOT added to group!", groupId, groupTemplate.mapId, uint32(spawnType), spawnId, data->spawnPoint.GetMapId()); + continue; + } + const_cast(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::LoadInstanceSpawnGroups() +{ + uint32 oldMSTime = getMSTime(); + + // 0 1 2 3 4 + QueryResult result = WorldDatabase.Query("SELECT instanceMapId, bossStateId, bossStates, spawnGroupId, flags FROM instance_spawn_groups"); + + if (!result) + { + TC_LOG_ERROR("server.loading", ">> Loaded 0 instance spawn groups. DB table `instance_spawn_groups` is empty."); + return; + } + + uint32 n = 0; + do + { + Field* fields = result->Fetch(); + uint32 const spawnGroupId = fields[3].GetUInt32(); + auto it = _spawnGroupDataStore.find(spawnGroupId); + if (it == _spawnGroupDataStore.end() || (it->second.flags & SPAWNGROUP_FLAG_SYSTEM)) + { + TC_LOG_ERROR("server.loading", "Invalid spawn group %u specified for instance %u. Skipped.", spawnGroupId, fields[0].GetUInt16()); + continue; + } + + uint16 const instanceMapId = fields[0].GetUInt16(); + auto& vector = _instanceSpawnGroupStore[instanceMapId]; + vector.emplace_back(); + InstanceSpawnGroupInfo& info = vector.back(); + info.SpawnGroupId = spawnGroupId; + info.BossStateId = fields[1].GetUInt8(); + + uint8 const ALL_STATES = (1 << TO_BE_DECIDED) - 1; + uint8 const states = fields[2].GetUInt8(); + if (states & ~ALL_STATES) + { + info.BossStates = states & ALL_STATES; + TC_LOG_ERROR("server.loading", "Instance spawn group (%u,%u) had invalid boss state mask %u - truncated to %u.", instanceMapId, spawnGroupId, states, info.BossStates); + } + else + info.BossStates = states; + + uint8 const flags = fields[4].GetUInt8(); + if (flags & ~InstanceSpawnGroupInfo::FLAG_ALL) + { + info.Flags = flags & InstanceSpawnGroupInfo::FLAG_ALL; + TC_LOG_ERROR("server.loading", "Instance spawn group (%u,%u) had invalid flags %u - truncated to %u.", instanceMapId, spawnGroupId, flags, info.Flags); + } + else + info.Flags = flags; + + ++n; + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded %u instance spawn groups in %u ms", n, GetMSTimeDiffToNow(oldMSTime)); +} + +void ObjectMgr::OnDeleteSpawnData(SpawnData const* data) +{ + auto templateIt = _spawnGroupDataStore.find(data->spawnGroupData->groupId); + ASSERT(templateIt != _spawnGroupDataStore.end(), "Creature data for (%u,%u) 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,%u) 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) { uint8 mask = data->spawnMask; @@ -2405,8 +2591,8 @@ void ObjectMgr::AddGameobjectToGrid(ObjectGuid::LowType guid, GameObjectData con { if (mask & 1) { - CellCoord cellCoord = Trinity::ComputeCellCoord(data->posX, data->posY); - CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->mapid, i)][cellCoord.GetId()]; + CellCoord cellCoord = Trinity::ComputeCellCoord(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY()); + CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->spawnPoint.GetMapId(), i)][cellCoord.GetId()]; cell_guids.gameobjects.insert(guid); } } @@ -2419,8 +2605,8 @@ void ObjectMgr::RemoveGameobjectFromGrid(ObjectGuid::LowType guid, GameObjectDat { if (mask & 1) { - CellCoord cellCoord = Trinity::ComputeCellCoord(data->posX, data->posY); - CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->mapid, i)][cellCoord.GetId()]; + CellCoord cellCoord = Trinity::ComputeCellCoord(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY()); + CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->spawnPoint.GetMapId(), i)][cellCoord.GetId()]; cell_guids.gameobjects.erase(guid); } } @@ -5037,7 +5223,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", @@ -5087,7 +5273,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", @@ -7811,17 +7997,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 820fd883fed..82b6b7bc4b0 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -24,8 +24,10 @@ #include "Corpse.h" #include "CreatureData.h" #include "DatabaseEnvFwd.h" +#include "Errors.h" #include "GameObjectData.h" #include "ItemTemplate.h" +#include "IteratorPair.h" #include "NPCHandler.h" #include "ObjectDefines.h" #include "ObjectGuid.h" @@ -39,6 +41,7 @@ class Item; class Unit; class Vehicle; +class Map; struct AccessRequirement; struct DeclinedName; struct DungeonEncounterEntry; @@ -425,6 +428,21 @@ std::string GetScriptsTableNameByType(ScriptsType type); ScriptMapMap* GetScriptsMapByType(ScriptsType type); std::string GetScriptCommandName(ScriptCommands command); +struct TC_GAME_API InstanceSpawnGroupInfo +{ + enum + { + FLAG_ACTIVATE_SPAWN = 0x01, + FLAG_BLOCK_SPAWN = 0x02, + + FLAG_ALL = (FLAG_ACTIVATE_SPAWN | FLAG_BLOCK_SPAWN) + }; + uint8 BossStateId; + uint8 BossStates; + uint32 SpawnGroupId; + uint8 Flags; +}; + struct TC_GAME_API SpellClickInfo { uint32 spellId; @@ -529,6 +547,9 @@ typedef std::unordered_map GameObjectTemplateAd typedef std::unordered_map GameObjectDataContainer; typedef std::unordered_map GameObjectAddonContainer; typedef std::unordered_map> GameObjectQuestItemMap; +typedef std::unordered_map SpawnGroupDataContainer; +typedef std::multimap SpawnGroupLinkContainer; +typedef std::unordered_map> InstanceSpawnGroupContainer; typedef std::map> TempSummonDataContainer; typedef std::unordered_map CreatureLocaleContainer; typedef std::unordered_map GameObjectLocaleContainer; @@ -799,6 +820,7 @@ SkillRangeType GetSkillRangeType(SkillRaceClassInfoEntry const* rcEntry); #define MAX_CHARTER_NAME 24 // max allowed by client name length bool TC_GAME_API normalizePlayerName(std::string& name); +#define SPAWNGROUP_MAP_UNSET 0xFFFFFFFF struct LanguageDesc { @@ -1121,7 +1143,10 @@ class TC_GAME_API ObjectMgr void LoadCreatureModelInfo(); void LoadEquipmentTemplates(); void LoadGameObjectLocales(); - void LoadGameobjects(); + void LoadGameObjects(); + void LoadSpawnGroupTemplates(); + void LoadSpawnGroups(); + void LoadInstanceSpawnGroups(); void LoadItemTemplates(); void LoadItemTemplateAddon(); void LoadItemScriptNames(); @@ -1210,8 +1235,14 @@ class TC_GAME_API ObjectMgr uint32 GenerateMailID(); uint32 GeneratePetNumber(); uint64 GenerateVoidStorageItemId(); - uint32 GenerateCreatureSpawnId(); - uint32 GenerateGameObjectSpawnId(); + ObjectGuid::LowType GenerateCreatureSpawnId(); + ObjectGuid::LowType GenerateGameObjectSpawnId(); + + SpawnGroupTemplateData const* GetSpawnGroupData(uint32 groupId) const { auto it = _spawnGroupDataStore.find(groupId); return it != _spawnGroupDataStore.end() ? &it->second : nullptr; } + SpawnGroupTemplateData const* GetDefaultSpawnGroup() const { return &_spawnGroupDataStore.at(0); } + SpawnGroupTemplateData const* GetLegacySpawnGroup() const { return &_spawnGroupDataStore.at(1); } + Trinity::IteratorPair GetSpawnDataForGroup(uint32 groupId) const { return Trinity::Containers::MapEqualRange(_spawnGroupMapStore, groupId); } + std::vector const* GetSpawnGroupsForInstance(uint32 instanceId) const { auto it = _instanceSpawnGroupStore.find(instanceId); return it != _instanceSpawnGroupStore.end() ? &it->second : nullptr; } typedef std::multimap ExclusiveQuestGroups; typedef std::pair ExclusiveQuestGroupsBounds; @@ -1267,6 +1298,18 @@ 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); + CreatureDataContainer const& GetAllCreatureData() const { return _creatureDataStore; } CreatureData const* GetCreatureData(ObjectGuid::LowType guid) const { CreatureDataContainer::const_iterator itr = _creatureDataStore.find(guid); @@ -1287,6 +1330,15 @@ class TC_GAME_API ObjectMgr if (itr == _creatureLocaleStore.end()) return nullptr; return &itr->second; } + GameObjectDataContainer const& GetAllGameObjectData() const { return _gameObjectDataStore; } + 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); @@ -1329,16 +1381,6 @@ class TC_GAME_API ObjectMgr if (itr == _pointOfInterestLocaleStore.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); - QuestGreetingLocale const* GetQuestGreetingLocale(uint32 id) const { QuestGreetingLocaleContainer::const_iterator itr = _questGreetingLocaleStore.find(id); @@ -1363,7 +1405,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 @@ -1498,8 +1540,8 @@ class TC_GAME_API ObjectMgr std::atomic _hiPetNumber; uint64 _voidItemId; - uint32 _creatureSpawnId; - uint32 _gameObjectSpawnId; + ObjectGuid::LowType _creatureSpawnId; + ObjectGuid::LowType _gameObjectSpawnId; // first free low guid for selected guid type template @@ -1626,6 +1668,9 @@ class TC_GAME_API ObjectMgr GameObjectLocaleContainer _gameObjectLocaleStore; GameObjectTemplateContainer _gameObjectTemplateStore; GameObjectTemplateAddonContainer _gameObjectTemplateAddonStore; + SpawnGroupDataContainer _spawnGroupDataStore; + SpawnGroupLinkContainer _spawnGroupMapStore; + InstanceSpawnGroupContainer _instanceSpawnGroupStore; /// Stores temp summon data grouped by summoner's entry, summoner's type and group id TempSummonDataContainer _tempSummonDataStore; diff --git a/src/server/game/Grids/Notifiers/GridNotifiers.h b/src/server/game/Grids/Notifiers/GridNotifiers.h index d4e997a83fb..a5cc17fa10a 100644 --- a/src/server/game/Grids/Notifiers/GridNotifiers.h +++ b/src/server/game/Grids/Notifiers/GridNotifiers.h @@ -565,6 +565,11 @@ namespace Trinity : ContainerInserter(container), _searcher(searcher), i_check(check) { } + template + PlayerListSearcher(Container& container, Check & check) + : ContainerInserter(container), + i_check(check) { } + void Visit(PlayerMapType &m); template void Visit(GridRefManager &) { } @@ -1288,6 +1293,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 3083d504061..8a2db63f3a7 100644 --- a/src/server/game/Grids/ObjectGridLoader.cpp +++ b/src/server/game/Grids/ObjectGridLoader.cpp @@ -28,6 +28,7 @@ #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "World.h" +#include "ScriptMgr.h" void ObjectGridEvacuator::Visit(CreatureMapType &m) { @@ -120,15 +121,48 @@ void LoadHelper(CellGuidSet const& guid_set, CellCoord &cell, GridRefManager 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)) - { - delete obj; - continue; - } - AddObjectHelper(cell, m, count, map, obj); + // 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))) + { + 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 %u, 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_SYSTEM)) + if (!map->IsSpawnGroupActive(group->groupId)) + { + delete obj; + 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 %u, but no such object exists.", guid); + if (!(godata->spawnGroupData->flags & SPAWNGROUP_FLAG_SYSTEM)) + if (!map->IsSpawnGroupActive(godata->spawnGroupData->groupId)) + { + delete obj; + continue; + } + } + + 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/Instances/InstanceScript.cpp b/src/server/game/Instances/InstanceScript.cpp index 8ba41990865..b8fb662b9a4 100644 --- a/src/server/game/Instances/InstanceScript.cpp +++ b/src/server/game/Instances/InstanceScript.cpp @@ -47,7 +47,7 @@ BossBoundaryData::~BossBoundaryData() delete it->Boundary; } -InstanceScript::InstanceScript(Map* map) : instance(map), completedEncounters(0) +InstanceScript::InstanceScript(Map* map) : instance(map), completedEncounters(0), _instanceSpawnGroups(sObjectMgr->GetSpawnGroupsForInstance(map->GetId())) { #ifdef TRINITY_API_USE_DYNAMIC_LINKING uint32 scriptId = sObjectMgr->GetInstanceTemplate(map->GetId())->ScriptId; @@ -187,27 +187,6 @@ void InstanceScript::LoadObjectData(ObjectData const* data, ObjectInfoMap& objec } } -void InstanceScript::UpdateMinionState(Creature* minion, EncounterState state) -{ - switch (state) - { - case NOT_STARTED: - if (!minion->IsAlive()) - minion->Respawn(); - else if (minion->IsInCombat()) - minion->AI()->EnterEvadeMode(); - break; - case IN_PROGRESS: - if (!minion->IsAlive()) - minion->Respawn(); - else if (!minion->GetVictim()) - minion->AI()->DoZoneInCombat(); - break; - default: - break; - } -} - void InstanceScript::UpdateDoorState(GameObject* door) { DoorInfoMapBounds range = doors.equal_range(door->GetEntry()); @@ -237,6 +216,60 @@ void InstanceScript::UpdateDoorState(GameObject* door) door->SetGoState(open ? GO_STATE_ACTIVE : GO_STATE_READY); } +void InstanceScript::UpdateMinionState(Creature* minion, EncounterState state) +{ + switch (state) + { + case NOT_STARTED: + if (!minion->IsAlive()) + minion->Respawn(); + else if (minion->IsInCombat()) + minion->AI()->EnterEvadeMode(); + break; + case IN_PROGRESS: + if (!minion->IsAlive()) + minion->Respawn(); + else if (!minion->GetVictim()) + minion->AI()->DoZoneInCombat(); + break; + default: + break; + } +} + +void InstanceScript::UpdateSpawnGroups() +{ + if (!_instanceSpawnGroups) + return; + enum states { BLOCK, SPAWN, FORCEBLOCK }; + std::unordered_map newStates; + for (auto it = _instanceSpawnGroups->begin(), end = _instanceSpawnGroups->end(); it != end; ++it) + { + InstanceSpawnGroupInfo const& info = *it; + states& curValue = newStates[info.SpawnGroupId]; // makes sure there's a BLOCK value in the map + if (curValue == FORCEBLOCK) // nothing will change this + continue; + if (!((1 << GetBossState(info.BossStateId)) & info.BossStates)) + continue; + if (info.Flags & InstanceSpawnGroupInfo::FLAG_BLOCK_SPAWN) + curValue = FORCEBLOCK; + else if (info.Flags & InstanceSpawnGroupInfo::FLAG_ACTIVATE_SPAWN) + curValue = SPAWN; + } + for (auto const& pair : newStates) + { + uint32 const groupId = pair.first; + bool const doSpawn = (pair.second == SPAWN); + if (instance->IsSpawnGroupActive(groupId) == doSpawn) + continue; // nothing to do here + // if we should spawn group, then spawn it... + if (doSpawn) + instance->SpawnGroupSpawn(groupId); + else // otherwise, set it as inactive so it no longer respawns (but don't despawn it) + instance->SetSpawnGroupInactive(groupId); + } +} + BossInfo* InstanceScript::GetBossInfo(uint32 id) { ASSERT(id < bosses.size()); @@ -311,7 +344,7 @@ bool InstanceScript::SetBossState(uint32 id, EncounterState state) if (bossInfo->state == TO_BE_DECIDED) // loading { bossInfo->state = state; - //TC_LOG_ERROR("misc", "Inialize boss %u state as %u.", id, (uint32)state); + TC_LOG_DEBUG("scripts", "InstanceScript: Initialize boss %u state as %s (map %u, %u).", id, GetBossStateName(state), instance->GetId(), instance->GetInstanceId()); return false; } @@ -320,6 +353,12 @@ bool InstanceScript::SetBossState(uint32 id, EncounterState state) if (bossInfo->state == state) return false; + if (bossInfo->state == DONE) + { + TC_LOG_ERROR("map", "InstanceScript: Tried to set instance state from %s back to %s for map %u, instance id %u. Blocked!", GetBossStateName(bossInfo->state), GetBossStateName(state), instance->GetId(), instance->GetInstanceId()); + return false; + } + if (state == DONE) for (GuidSet::iterator i = bossInfo->minion.begin(); i != bossInfo->minion.end(); ++i) if (Creature* minion = instance->GetCreature(*i)) @@ -363,6 +402,7 @@ bool InstanceScript::SetBossState(uint32 id, EncounterState state) if (Creature* minion = instance->GetCreature(*i)) UpdateMinionState(minion, state); + UpdateSpawnGroups(); return true; } return false; @@ -373,6 +413,13 @@ bool InstanceScript::_SkipCheckRequiredBosses(Player const* player /*= nullptr*/ return player && player->GetSession()->HasPermission(rbac::RBAC_PERM_SKIP_CHECK_INSTANCE_REQUIRED_BOSSES); } +void InstanceScript::Create() +{ + for (size_t i = 0; i < bosses.size(); ++i) + SetBossState(i, NOT_STARTED); + UpdateSpawnGroups(); +} + void InstanceScript::Load(char const* data) { if (!data) @@ -423,6 +470,7 @@ void InstanceScript::ReadSaveDataBossStates(std::istringstream& data) if (buff < TO_BE_DECIDED) SetBossState(bossId, EncounterState(buff)); } + UpdateSpawnGroups(); } std::string InstanceScript::GetSaveData() @@ -720,7 +768,7 @@ void InstanceScript::UpdateEncounterStateForSpellCast(uint32 spellId, Unit* sour UpdateEncounterState(ENCOUNTER_CREDIT_CAST_SPELL, spellId, source); } -std::string InstanceScript::GetBossStateName(uint8 state) +/*static*/ std::string InstanceScript::GetBossStateName(uint8 state) { // See enum EncounterState in InstanceScript.h switch (state) diff --git a/src/server/game/Instances/InstanceScript.h b/src/server/game/Instances/InstanceScript.h index 747ffaf1ad8..25a104b9542 100644 --- a/src/server/game/Instances/InstanceScript.h +++ b/src/server/game/Instances/InstanceScript.h @@ -34,6 +34,7 @@ class AreaBoundary; class Creature; class GameObject; +struct InstanceSpawnGroupInfo; class Map; class ModuleReference; class Player; @@ -159,7 +160,10 @@ class TC_GAME_API InstanceScript : public ZoneScript // KEEPING THIS METHOD ONLY FOR BACKWARD COMPATIBILITY !!! virtual void Initialize() { } - // On load + // On instance load, exactly ONE of these methods will ALWAYS be called: + // if we're starting without any saved instance data + virtual void Create(); + // if we're loading existing instance save data virtual void Load(char const* data); // When save is needed, this function generates the data @@ -286,6 +290,8 @@ class TC_GAME_API InstanceScript : public ZoneScript virtual void UpdateDoorState(GameObject* door); void UpdateMinionState(Creature* minion, EncounterState state); + void UpdateSpawnGroups(); + // Exposes private data that should never be modified unless exceptional cases. // Pay very much attention at how the returned BossInfo data is modified to avoid issues. BossInfo* GetBossInfo(uint32 id); @@ -316,6 +322,7 @@ class TC_GAME_API InstanceScript : public ZoneScript uint8 _combatResurrectionCharges; // the counter for available battle resurrections bool _combatResurrectionTimerStarted; std::unordered_set _activatedAreaTriggers; + std::vector const* const _instanceSpawnGroups; #ifdef TRINITY_API_USE_DYNAMIC_LINKING // Strong reference to the associated script module diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 9eb3419fbf5..476b369c1e6 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" @@ -37,6 +38,7 @@ #include "ObjectGridLoader.h" #include "ObjectMgr.h" #include "Pet.h" +#include "PoolMgr.h" #include "PhasingHandler.h" #include "ScriptMgr.h" #include "Transport.h" @@ -62,6 +64,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(); @@ -272,7 +278,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(GetDefaultMapLight(id)) +i_scriptLock(false), _respawnCheckTimer(0), _defaultLight(GetDefaultMapLight(id)) { if (_parent) { @@ -295,6 +301,8 @@ i_scriptLock(false), _defaultLight(GetDefaultMapLight(id)) } } + _zonePlayerCountMap.clear(); + //lets initialize visibility distance for map Map::InitVisibilityDistance(); @@ -553,6 +561,29 @@ bool Map::EnsureGridLoaded(const Cell &cell) return false; } +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)); @@ -722,6 +753,21 @@ void Map::VisitNearbyCellsOf(WorldObject* obj, TypeContainerVisitorUpdate(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(); @@ -918,6 +974,8 @@ void Map::ProcessRelocationNotifies(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 @@ -2630,10 +2688,7 @@ void Map::GetFullTerrainStatusForPosition(PhaseShift const& phaseShift, float x, else { data.floorZ = mapHeight; - if (gmap) - data.areaId = gmap->getArea(x, y); - else - data.areaId = 0; + data.areaId = gmap->getArea(x, y); if (!data.areaId) data.areaId = i_mapEntry->linked_zone; @@ -2735,11 +2790,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*/) const -{ - return std::max(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) const { LiquidData liquid_status; @@ -2896,6 +2946,480 @@ void Map::SendObjectUpdates() } } +// CheckRespawn MUST do one of the following: +// -) return true +// -) set info->respawnTime to zero, which indicates the respawn time should be deleted (and will never be processed again without outside intervention) +// -) set info->respawnTime to a new respawn time, which must be strictly GREATER than the current time (GameTime::GetGameTime()) +bool Map::CheckRespawn(RespawnInfo* info) +{ + SpawnData const* data = sObjectMgr->GetSpawnData(info->type, info->spawnId); + ASSERT(data, "Invalid respawn info with type %u, spawnID %u in respawn queue.", info->type, info->spawnId); + + // First, check if this creature's spawn group is inactive + if (!IsSpawnGroupActive(data->spawnGroupData->groupId)) + { + info->respawnTime = 0; + return false; + } + uint32 poolId = info->spawnId ? sPoolMgr->IsPartOfAPool(info->type, info->spawnId) : 0; + // Next, 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 const isEscort = (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC) && data->spawnGroupData->flags & SPAWNGROUP_FLAG_ESCORTQUESTNPC); + + 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 %u 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 = ObjectGuid((info->type == SPAWN_TYPE_GAMEOBJECT) ? HighGuid::GameObject : HighGuid::Unit, info->entry, info->spawnId); + if (time_t linkedTime = GetLinkedRespawnTime(thisGUID)) + { + time_t now = time(NULL); + time_t respawnTime; + if (linkedTime == std::numeric_limits::max()) + respawnTime = linkedTime; + else 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(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(poolId, info->spawnId); + else if (info->type == SPAWN_TYPE_CREATURE) + sPoolMgr->UpdatePool(poolId, info->spawnId); + else + ASSERT(false, "Invalid spawn type %u (spawnid %u) on map %u", uint32(info->type), info->spawnId, GetId()); + info->respawnTime = 0; + return false; + } + + // everything ok, let's spawn + 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 %u) on map %u", uint32(type), spawnId, GetId()); + } +} + +void Map::Respawn(RespawnInfo* info, bool force, SQLTransaction 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(std::vector& respawnData, bool force, SQLTransaction dbTrans) +{ + SQLTransaction 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,%u) into spawn id map failed - state desync.", uint32(ri->type), ri->spawnId); +} + +static void PushRespawnInfoFrom(std::vector& 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(std::vector& 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 %u (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, SQLTransaction dbTrans) +{ + PreparedStatement* 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 %u map %u", uint32(info->type), info->spawnId, GetId()); + return; + } + stmt->setUInt32(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(std::vector& respawnData, bool doRespawn, SQLTransaction dbTrans) +{ + SQLTransaction 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); + + if (IsBattlegroundOrArena()) + return; + + 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 || !(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(ceil(respawnDelay * adjustFactor), timeMinimum); +} + +SpawnGroupTemplateData const* Map::GetSpawnGroupData(uint32 groupId) const +{ + SpawnGroupTemplateData const* data = sObjectMgr->GetSpawnGroupData(groupId); + if (data && (data->flags & SPAWNGROUP_FLAG_SYSTEM || data->mapId == GetId())) + return data; + return nullptr; +} + +bool Map::SpawnGroupSpawn(uint32 groupId, bool ignoreRespawn, bool force, std::vector* spawnedObjects) +{ + SpawnGroupTemplateData const* groupData = GetSpawnGroupData(groupId); + if (!groupData || groupData->flags & SPAWNGROUP_FLAG_SYSTEM) + { + TC_LOG_ERROR("maps", "Tried to spawn non-existing (or system) spawn group %u on map %u. Blocked.", groupId, GetId()); + return false; + } + + for (auto& pair : sObjectMgr->GetSpawnDataForGroup(groupId)) + { + SpawnData const* data = pair.second; + ASSERT(groupData->mapId == data->spawnPoint.GetMapId()); + // Check if there's already an instance spawned + if (!force) + if (WorldObject* obj = GetWorldObjectBySpawnId(data->type, data->spawnId)) + if ((data->type != SPAWN_TYPE_CREATURE) || obj->ToCreature()->IsAlive()) + continue; + + time_t respawnTime = 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 + RemoveRespawnTime(data->type, data->spawnId, false); + } + + // don't spawn if the grid isn't loaded (will be handled in grid loader) + if (!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, this, 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, this, true)) + delete gameobject; + else if (spawnedObjects) + spawnedObjects->push_back(gameobject); + break; + } + default: + ASSERT(false, "Invalid spawn type %u with spawnId %u", uint32(data->type), data->spawnId); + return false; + } + } + SetSpawnGroupActive(groupId, true); // start processing respawns for the group + return true; +} + +bool Map::SpawnGroupDespawn(uint32 groupId, bool deleteRespawnTimes, size_t* count) +{ + SpawnGroupTemplateData const* groupData = GetSpawnGroupData(groupId); + if (!groupData || groupData->flags & SPAWNGROUP_FLAG_SYSTEM) + { + TC_LOG_ERROR("maps", "Tried to despawn non-existing (or system) spawn group %u on map %u. Blocked.", groupId, GetId()); + return false; + } + + std::vector toUnload; // unload after iterating, otherwise iterator invalidation + for (auto const& pair : sObjectMgr->GetSpawnDataForGroup(groupId)) + { + SpawnData const* data = pair.second; + ASSERT(groupData->mapId == data->spawnPoint.GetMapId()); + if (deleteRespawnTimes) + RemoveRespawnTime(data->type, data->spawnId); + switch (data->type) + { + case SPAWN_TYPE_CREATURE: + { + auto bounds = 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 = 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 %u.", uint32(data->type), data->spawnId); + return false; + } + } + + if (count) + *count = toUnload.size(); + + // now do the actual despawning + for (WorldObject* obj : toUnload) + obj->AddObjectToRemoveList(); + SetSpawnGroupActive(groupId, false); // stop processing respawns for the group, too + return true; +} + +void Map::SetSpawnGroupActive(uint32 groupId, bool state) +{ + SpawnGroupTemplateData const* const data = GetSpawnGroupData(groupId); + if (!data || data->flags & SPAWNGROUP_FLAG_SYSTEM) + { + TC_LOG_ERROR("maps", "Tried to set non-existing (or system) spawn group %u to %s on map %u. Blocked.", groupId, state ? "active" : "inactive", GetId()); + return; + } + if (state != !(data->flags & SPAWNGROUP_FLAG_MANUAL_SPAWN)) // toggled + _toggledSpawnGroupIds.insert(groupId); + else + _toggledSpawnGroupIds.erase(groupId); +} + +bool Map::IsSpawnGroupActive(uint32 groupId) const +{ + SpawnGroupTemplateData const* const data = GetSpawnGroupData(groupId); + if (!data) + { + TC_LOG_ERROR("maps", "Tried to query state of non-existing spawn group %u on map %u.", groupId, GetId()); + return false; + } + if (data->flags & SPAWNGROUP_FLAG_SYSTEM) + return true; + // either manual spawn group and toggled, or not manual spawn group and not toggled... + return (_toggledSpawnGroupIds.find(groupId) != _toggledSpawnGroupIds.end()) != !(data->flags & SPAWNGROUP_FLAG_MANUAL_SPAWN); +} + void Map::DelayedUpdate(uint32 t_diff) { for (_transportsUpdateIter = _transports.begin(); _transportsUpdateIter != _transports.end();) @@ -3402,6 +3926,8 @@ void InstanceMap::CreateInstanceData(bool load) } } } + else + i_data->Create(); } /* @@ -3741,6 +4267,34 @@ DynamicObject* Map::GetDynamicObject(ObjectGuid const& guid) return _objectsStore.Find(guid); } +Creature* Map::GetCreatureBySpawnId(ObjectGuid::LowType spawnId) const +{ + auto const bounds = GetCreatureBySpawnIdStore().equal_range(spawnId); + if (bounds.first == bounds.second) + return nullptr; + + std::unordered_multimap::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; +} + +GameObject* Map::GetGameObjectBySpawnId(ObjectGuid::LowType spawnId) const +{ + auto const bounds = GetGameObjectBySpawnIdStore().equal_range(spawnId); + if (bounds.first == bounds.second) + return nullptr; + + std::unordered_multimap::const_iterator creatureItr = std::find_if(bounds.first, bounds.second, [](Map::GameObjectBySpawnIdContainer::value_type const& pair) + { + return pair.second->isSpawned(); + }); + + return creatureItr != bounds.second ? creatureItr->second : bounds.first->second; +} + GameObject* Map::GetGameObject(ObjectGuid const& guid) { return _objectsStore.Find(guid); @@ -3766,64 +4320,37 @@ void Map::UpdateIteratorBack(Player* player) m_mapRefIter = m_mapRefIter->nocheck_prev(); } -void Map::SaveCreatureRespawnTime(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, SQLTransaction dbTrans) { if (!respawnTime) { // Delete only - RemoveCreatureRespawnTime(dbGuid); + RemoveRespawnTime(type, spawnId, false, dbTrans); return; } - _creatureRespawnTimes[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); - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CREATURE_RESPAWN); - stmt->setUInt32(0, dbGuid); + if (writeDB) + SaveRespawnTimeDB(type, spawnId, ri.respawnTime, dbTrans); // might be different from original respawn time if we didn't replace +} + +void Map::SaveRespawnTimeDB(SpawnObjectType type, ObjectGuid::LowType spawnId, time_t respawnTime, SQLTransaction dbTrans) +{ + // Just here for support of compatibility mode + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement((type == SPAWN_TYPE_GAMEOBJECT) ? CHAR_REP_GO_RESPAWN : CHAR_REP_CREATURE_RESPAWN); + stmt->setUInt32(0, spawnId); stmt->setUInt64(1, uint64(respawnTime)); stmt->setUInt16(2, GetId()); stmt->setUInt32(3, GetInstanceId()); - CharacterDatabase.Execute(stmt); -} - -void Map::RemoveCreatureRespawnTime(ObjectGuid::LowType dbGuid) -{ - _creatureRespawnTimes.erase(dbGuid); - - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CREATURE_RESPAWN); - stmt->setUInt32(0, dbGuid); - stmt->setUInt16(1, GetId()); - stmt->setUInt32(2, GetInstanceId()); - CharacterDatabase.Execute(stmt); -} - -void Map::SaveGORespawnTime(ObjectGuid::LowType dbGuid, time_t respawnTime) -{ - if (!respawnTime) - { - // Delete only - RemoveGORespawnTime(dbGuid); - return; - } - - _goRespawnTimes[dbGuid] = respawnTime; - - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_GO_RESPAWN); - stmt->setUInt32(0, dbGuid); - stmt->setUInt64(1, uint64(respawnTime)); - stmt->setUInt16(2, GetId()); - stmt->setUInt32(3, GetInstanceId()); - CharacterDatabase.Execute(stmt); -} - -void Map::RemoveGORespawnTime(ObjectGuid::LowType dbGuid) -{ - _goRespawnTimes.erase(dbGuid); - - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GO_RESPAWN); - stmt->setUInt32(0, dbGuid); - stmt->setUInt16(1, GetId()); - stmt->setUInt32(2, GetInstanceId()); - CharacterDatabase.Execute(stmt); + CharacterDatabase.ExecuteOrAppend(dbTrans, stmt); } void Map::LoadRespawnTimes() @@ -3839,7 +4366,9 @@ void Map::LoadRespawnTimes() ObjectGuid::LowType loguid = fields[0].GetUInt32(); 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()); } @@ -3854,19 +4383,13 @@ void Map::LoadRespawnTimes() ObjectGuid::LowType loguid = fields[0].GetUInt32(); 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) { PreparedStatement* 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 8c54cd76139..bb05167e650 100644 --- a/src/server/game/Maps/Map.h +++ b/src/server/game/Maps/Map.h @@ -21,16 +21,18 @@ #include "Define.h" -#include "GridDefines.h" #include "Cell.h" -#include "Timer.h" -#include "SharedDefines.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 "Transaction.h" +#include #include #include #include @@ -265,10 +267,40 @@ struct ZoneDynamicInfo #define MAX_FALL_DISTANCE 250000.0f // "unlimited fall" to find VMap ground if it is available, just larger than MAX_HEIGHT - INVALID_HEIGHT #define DEFAULT_HEIGHT_SEARCH 50.0f // default search distance to find height at nearby locations #define MIN_UNLOAD_DELAY 1 // immediate unload +#define MAP_INVALID_ZONE 0xFFFFFFFF typedef std::map CreatureGroupHolderType; +struct RespawnInfo; // forward declaration +struct CompareRespawnInfo +{ + bool operator()(RespawnInfo const* a, RespawnInfo const* b) const; +}; typedef std::unordered_map ZoneDynamicInfoMap; +typedef boost::heap::fibonacci_heap> RespawnListContainer; +typedef RespawnListContainer::handle_type RespawnListHandle; +typedef std::unordered_map RespawnInfoMap; +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,%u) found!", a->type, a->spawnId); + return a->type < b->type; +} class TC_GAME_API Map : public GridRefManager { @@ -318,17 +350,19 @@ class TC_GAME_API Map : public GridRefManager 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(GridCoord const& p) const { return getNGrid(p.x_coord, p.y_coord)->getUnloadLock(); } void SetUnloadLock(GridCoord const& 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 @@ -359,8 +393,11 @@ class TC_GAME_API Map : public GridRefManager bool GetAreaInfo(PhaseShift const& phaseShift, float x, float y, float z, uint32& mogpflags, int32& adtId, int32& rootId, int32& groupId) const; uint32 GetAreaId(PhaseShift const& phaseShift, float x, float y, float z, bool *isOutdoors = nullptr) const; + uint32 GetAreaId(PhaseShift const& phaseShift, Position const& pos) const { return GetAreaId(phaseShift, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } uint32 GetZoneId(PhaseShift const& phaseShift, float x, float y, float z) const; + uint32 GetZoneId(PhaseShift const& phaseShift, Position const& pos) const { return GetZoneId(phaseShift, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } void GetZoneAndAreaId(PhaseShift const& phaseShift, uint32& zoneid, uint32& areaid, float x, float y, float z) const; + void GetZoneAndAreaId(PhaseShift const& phaseShift, uint32& zoneid, uint32& areaid, Position const& pos) const { GetZoneAndAreaId(phaseShift, zoneid, areaid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } bool IsOutdoors(PhaseShift const& phaseShift, float x, float y, float z) const; @@ -465,6 +502,9 @@ class TC_GAME_API Map : public GridRefManager Creature* GetCreature(ObjectGuid const& guid); DynamicObject* GetDynamicObject(ObjectGuid const& guid); GameObject* GetGameObject(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(GetGameObjectBySpawnId(spawnId)) : reinterpret_cast(GetCreatureBySpawnId(spawnId)); } Pet* GetPet(ObjectGuid const& guid); Transport* GetTransport(ObjectGuid const& guid); @@ -472,9 +512,11 @@ class TC_GAME_API Map : public GridRefManager typedef std::unordered_multimap CreatureBySpawnIdContainer; CreatureBySpawnIdContainer& GetCreatureBySpawnIdStore() { return _creatureBySpawnIdStore; } + CreatureBySpawnIdContainer const& GetCreatureBySpawnIdStore() const { return _creatureBySpawnIdStore; } typedef std::unordered_multimap GameObjectBySpawnIdContainer; GameObjectBySpawnIdContainer& GetGameObjectBySpawnIdStore() { return _gameObjectBySpawnIdStore; } + GameObjectBySpawnIdContainer const& GetGameObjectBySpawnIdStore() const { return _gameObjectBySpawnIdStore; } std::unordered_set const* GetCorpsesInCell(uint32 cellId) const { @@ -504,7 +546,10 @@ class TC_GAME_API Map : public GridRefManager BattlegroundMap const* ToBattlegroundMap() const { if (IsBattlegroundOrArena()) return reinterpret_cast(this); return nullptr; } float GetWaterOrGroundLevel(PhaseShift const& phaseShift, float x, float y, float z, float* ground = nullptr, bool swim = false) const; - float GetHeight(PhaseShift const& phaseShifts, float x, float y, float z, bool vmap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const; + + float GetHeight(PhaseShift const& phaseShift, float x, float y, float z, bool vmap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const { return std::max(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) const { 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); } @@ -523,28 +568,24 @@ class TC_GAME_API Map : public GridRefManager time_t GetLinkedRespawnTime(ObjectGuid guid) const; time_t GetCreatureRespawnTime(ObjectGuid::LowType dbGuid) const { - std::unordered_map::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::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, SQLTransaction dbTrans = nullptr); + void SaveRespawnTimeDB(SpawnObjectType type, ObjectGuid::LowType spawnId, time_t respawnTime, SQLTransaction dbTrans = nullptr); void LoadRespawnTimes(); - void DeleteRespawnTimes(); + void DeleteRespawnTimes() { DeleteRespawnInfo(); DeleteRespawnTimesInDB(GetId(), GetInstanceId()); } void LoadCorpseData(); void DeleteCorpseData(); @@ -708,6 +749,80 @@ class TC_GAME_API Map : public GridRefManager typedef std::multimap 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, SQLTransaction dbTrans = nullptr); + void Respawn(std::vector& respawnData, bool force = false, SQLTransaction dbTrans = nullptr); + void AddRespawnInfo(RespawnInfo& info, bool replace = false); + void DeleteRespawnInfo(); + void DeleteRespawnInfo(RespawnInfo* info); + void DeleteRespawnInfo(std::vector& toDelete) + { + for (RespawnInfo* info : toDelete) + DeleteRespawnInfo(info); + toDelete.clear(); + } + void DeleteRespawnInfo(SpawnObjectTypeMask types, uint32 zoneId = 0) + { + std::vector 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(std::vector& respawnData, SpawnObjectTypeMask types, uint32 zoneId = 0) const; + RespawnInfo* GetRespawnInfo(SpawnObjectType type, ObjectGuid::LowType spawnId) const; + void ForceRespawn(SpawnObjectType type, ObjectGuid::LowType spawnId) + { + if (RespawnInfo* info = GetRespawnInfo(type, spawnId)) + Respawn(info, true); + } + void RemoveRespawnTime(RespawnInfo* info, bool doRespawn = false, SQLTransaction dbTrans = nullptr); + void RemoveRespawnTime(std::vector& respawnData, bool doRespawn = false, SQLTransaction dbTrans = nullptr); + void RemoveRespawnTime(SpawnObjectTypeMask types = SPAWN_TYPEMASK_ALL, uint32 zoneId = 0, bool doRespawn = false, SQLTransaction dbTrans = nullptr) + { + std::vector v; + GetRespawnInfo(v, types, zoneId); + if (!v.empty()) + RemoveRespawnTime(v, doRespawn, dbTrans); + } + void RemoveRespawnTime(SpawnObjectType type, ObjectGuid::LowType spawnId, bool doRespawn = false, SQLTransaction dbTrans = nullptr) + { + if (RespawnInfo* info = GetRespawnInfo(type, spawnId)) + RemoveRespawnTime(info, doRespawn, dbTrans); + } + + SpawnGroupTemplateData const* GetSpawnGroupData(uint32 groupId) const; + + bool IsSpawnGroupActive(uint32 groupId) const; + + // Enable the spawn group, which causes all creatures in it to respawn (unless they have a respawn timer) + // The force flag can be used to force spawning additional copies even if old copies are still around from a previous spawn + bool SpawnGroupSpawn(uint32 groupId, bool ignoreRespawn = false, bool force = false, std::vector* spawnedObjects = nullptr); + + // Despawn all creatures in the spawn group if spawned, optionally delete their respawn timer, and disable the group + bool SpawnGroupDespawn(uint32 groupId, bool deleteRespawnTimes = false, size_t* count = nullptr); + + // Disable the spawn group, which prevents any creatures in the group from respawning until re-enabled + // This will not affect any already-present creatures in the group + void SetSpawnGroupInactive(uint32 groupId) { SetSpawnGroupActive(groupId, false); } + + + private: // Type specific code for add/remove to/from grid template void AddToGrid(T* object, Cell const& cell); @@ -736,8 +851,17 @@ class TC_GAME_API Map : public GridRefManager m_activeNonPlayers.erase(obj); } - std::unordered_map _creatureRespawnTimes; - std::unordered_map _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; } + + void SetSpawnGroupActive(uint32 groupId, bool state); + std::unordered_set _toggledSpawnGroupIds; + + uint32 _respawnCheckTimer; + std::unordered_map _zonePlayerCountMap; ZoneDynamicInfoMap _zoneDynamicInfo; uint32 _defaultLight; diff --git a/src/server/game/Maps/MapManager.h b/src/server/game/Maps/MapManager.h index d92dbc55c2a..cb717f0e19b 100644 --- a/src/server/game/Maps/MapManager.h +++ b/src/server/game/Maps/MapManager.h @@ -45,16 +45,22 @@ class TC_GAME_API MapManager Map const* m = const_cast(this)->CreateBaseMap(mapid); return m->GetAreaId(phaseShift, x, y, z); } + uint32 GetAreaId(PhaseShift const& phaseShift, uint32 mapid, Position const& pos) const { return GetAreaId(phaseShift, mapid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } + uint32 GetAreaId(PhaseShift const& phaseShift, WorldLocation const& loc) const { return GetAreaId(phaseShift, loc.GetMapId(), loc); } uint32 GetZoneId(PhaseShift const& phaseShift, uint32 mapid, float x, float y, float z) const { Map const* m = const_cast(this)->CreateBaseMap(mapid); return m->GetZoneId(phaseShift, x, y, z); } - void GetZoneAndAreaId(PhaseShift const& phaseShift, uint32& zoneid, uint32& areaid, uint32 mapid, float x, float y, float z) + uint32 GetZoneId(PhaseShift const& phaseShift, uint32 mapid, Position const& pos) const { return GetZoneId(phaseShift, mapid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } + uint32 GetZoneId(PhaseShift const& phaseShift, WorldLocation const& loc) const { return GetZoneId(phaseShift, loc.GetMapId(), loc); } + void GetZoneAndAreaId(PhaseShift const& phaseShift, uint32& zoneid, uint32& areaid, uint32 mapid, float x, float y, float z) const { Map const* m = const_cast(this)->CreateBaseMap(mapid); m->GetZoneAndAreaId(phaseShift, zoneid, areaid, x, y, z); } + void GetZoneAndAreaId(PhaseShift const& phaseShift, uint32& zoneid, uint32& areaid, uint32 mapid, Position const& pos) const { GetZoneAndAreaId(phaseShift, zoneid, areaid, mapid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } + void GetZoneAndAreaId(PhaseShift const& phaseShift, uint32& zoneid, uint32& areaid, WorldLocation const& loc) const { GetZoneAndAreaId(phaseShift, zoneid, areaid, loc.GetMapId(), loc); } void Initialize(void); void InitializeParentMapData(std::unordered_map> const& mapData); diff --git a/src/server/game/Maps/SpawnData.h b/src/server/game/Maps/SpawnData.h new file mode 100644 index 00000000000..933f4164842 --- /dev/null +++ b/src/server/game/Maps/SpawnData.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2008-2018 TrinityCore + * + * 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 . + */ + +#ifndef TRINITY_SPAWNDATA_H +#define TRINITY_SPAWNDATA_H + +#include "Position.h" + +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; +}; + +struct SpawnData +{ + SpawnObjectType const type; + uint32 spawnId = 0; + uint32 id = 0; // entry in respective _template table + WorldLocation spawnPoint; + uint32 phaseMask = 0; + int32 spawntimesecs = 0; + uint8 spawnMask = 0; + SpawnGroupTemplateData const* spawnGroupData = nullptr; + uint8 phaseUseFlags = 0; + uint32 phaseId = 0; + uint32 phaseGroup = 0; + int32 terrainSwapMap = 0; + 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 51792371ab0..a8f0290d31f 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -1042,7 +1042,33 @@ enum TrinityStrings LANG_COMMAND_MUTEHISTORY_EMPTY = 5060, LANG_COMMAND_MUTEHISTORY_OUTPUT = 5061, LANG_PHASE_NOTFOUND = 5083, - // Room for more Trinity strings 5062-9999 + + // Scene debugs commands [Master only, not used in 3.3.5] + /*LANG_COMMAND_SCENE_DEBUG_ON = 5062, + LANG_COMMAND_SCENE_DEBUG_OFF = 5063, + LANG_COMMAND_SCENE_DEBUG_PLAY = 5064, + LANG_COMMAND_SCENE_DEBUG_TRIGGER = 5065, + LANG_COMMAND_SCENE_DEBUG_CANCEL = 5066, + LANG_COMMAND_SCENE_DEBUG_COMPLETE = 5067, + LANG_DEBUG_SCENE_OBJECT_LIST = 5068, + LANG_DEBUG_SCENE_OBJECT_DETAIL = 5069, */ + + // 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, + + // Room for more Trinity strings 5084-6603 // Level requirement notifications LANG_SAY_REQ = 6604, diff --git a/src/server/game/OutdoorPvP/OutdoorPvP.cpp b/src/server/game/OutdoorPvP/OutdoorPvP.cpp index 84f2dc90a50..088e8778dfc 100644 --- a/src/server/game/OutdoorPvP/OutdoorPvP.cpp +++ b/src/server/game/OutdoorPvP/OutdoorPvP.cpp @@ -91,7 +91,7 @@ void OPvPCapturePoint::AddGO(uint32 type, ObjectGuid::LowType guid, uint32 entry { if (!entry) { - GameObjectData const* data = sObjectMgr->GetGOData(guid); + GameObjectData const* data = sObjectMgr->GetGameObjectData(guid); if (!data) return; entry = data->id; @@ -117,7 +117,7 @@ void OPvPCapturePoint::AddCre(uint32 type, ObjectGuid::LowType guid, uint32 entr 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, entry); return true; @@ -149,7 +149,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 == 0) return false; @@ -218,7 +218,7 @@ bool OPvPCapturePoint::DelObject(uint32 type) go->Delete(); } - sObjectMgr->DeleteGOData(spawnId); + sObjectMgr->DeleteGameObjectData(spawnId); m_ObjectTypes[m_Objects[type]] = 0; m_Objects[type] = 0; return true; @@ -226,7 +226,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 4dbc5850d92..4f6d9e602b7 100644 --- a/src/server/game/Pools/PoolMgr.cpp +++ b/src/server/game/Pools/PoolMgr.cpp @@ -194,7 +194,7 @@ void PoolGroup::Despawn1Object(ObjectGuid::LowType guid) { sObjectMgr->RemoveCreatureFromGrid(guid, data); - Map* map = sMapMgr->CreateBaseMap(data->mapid); + Map* map = sMapMgr->CreateBaseMap(data->spawnPoint.GetMapId()); if (!map->Instanceable()) { auto creatureBounds = map->GetCreatureBySpawnIdStore().equal_range(guid); @@ -202,6 +202,9 @@ void PoolGroup::Despawn1Object(ObjectGuid::LowType guid) { Creature* creature = itr->second; ++itr; + // For dynamic spawns, save respawn time here + if (!creature->GetRespawnCompatibilityMode()) + creature->SaveRespawnTime(0, false); creature->AddObjectToRemoveList(); } } @@ -212,11 +215,11 @@ void PoolGroup::Despawn1Object(ObjectGuid::LowType guid) template<> void PoolGroup::Despawn1Object(ObjectGuid::LowType guid) { - if (GameObjectData const* data = sObjectMgr->GetGOData(guid)) + if (GameObjectData const* data = sObjectMgr->GetGameObjectData(guid)) { sObjectMgr->RemoveGameobjectFromGrid(guid, data); - Map* map = sMapMgr->CreateBaseMap(data->mapid); + Map* map = sMapMgr->CreateBaseMap(data->spawnPoint.GetMapId()); if (!map->Instanceable()) { auto gameObjectBounds = map->GetGameObjectBySpawnIdStore().equal_range(guid); @@ -224,6 +227,10 @@ void PoolGroup::Despawn1Object(ObjectGuid::LowType guid) { GameObject* go = itr->second; ++itr; + + // For dynamic spawns, save respawn time here + if (!go->GetRespawnCompatibilityMode()) + go->SaveRespawnTime(0, false); go->AddObjectToRemoveList(); } } @@ -375,13 +382,13 @@ void PoolGroup::Spawn1Object(PoolObject* obj) sObjectMgr->AddCreatureToGrid(obj->guid, data); // Spawn if necessary (loaded grids only) - Map* map = sMapMgr->CreateBaseMap(data->mapid); + Map* map = sMapMgr->CreateBaseMap(data->spawnPoint.GetMapId()); // We use spawn coords to spawn - if (!map->Instanceable() && map->IsGridLoaded(data->posX, data->posY)) + if (!map->Instanceable() && map->IsGridLoaded(data->spawnPoint)) { Creature* creature = new Creature(); //TC_LOG_DEBUG("pool", "Spawning creature %u", guid); - if (!creature->LoadCreatureFromDB(obj->guid, map)) + if (!creature->LoadFromDB(obj->guid, map, true, false)) { delete creature; return; @@ -394,18 +401,18 @@ void PoolGroup::Spawn1Object(PoolObject* obj) template <> void PoolGroup::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->CreateBaseMap(data->mapid); + Map* map = sMapMgr->CreateBaseMap(data->spawnPoint.GetMapId()); // We use current coords to unspawn, not spawn coords since creature can have changed grid - if (!map->Instanceable() && map->IsGridLoaded(data->posX, data->posY)) + if (!map->Instanceable() && map->IsGridLoaded(data->spawnPoint)) { GameObject* pGameobject = new GameObject; //TC_LOG_DEBUG("pool", "Spawning gameobject %u", guid); - if (!pGameobject->LoadGameObjectFromDB(obj->guid, map, false)) + if (!pGameobject->LoadFromDB(obj->guid, map, false)) { delete pGameobject; return; @@ -672,7 +679,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: %u) 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(spawnId); + case SPAWN_TYPE_GAMEOBJECT: + return IsPartOfAPool(spawnId); + default: + ASSERT(false, "Invalid spawn type %u passed to PoolMgr::IsPartOfPool (with spawnId %u)", 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 2de769cb98c..90997cefe43 100644 --- a/src/server/game/Pools/PoolMgr.h +++ b/src/server/game/Pools/PoolMgr.h @@ -22,6 +22,7 @@ #include "Define.h" #include "Creature.h" #include "GameObject.h" +#include "SpawnData.h" #include "QuestDef.h" struct PoolTemplateData @@ -117,6 +118,7 @@ class TC_GAME_API PoolMgr template uint32 IsPartOfAPool(uint32 db_guid_or_pool_id) const; + uint32 IsPartOfAPool(SpawnObjectType type, ObjectGuid::LowType spawnId) const; template bool IsSpawnedObject(uint32 db_guid_or_pool_id) const { return mSpawnedData.IsActiveObject(db_guid_or_pool_id); } diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index e47cfd97b1e..72b2f7a30cd 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -1566,20 +1566,6 @@ 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) -{ - ASSERT(actTemplate); - - CreatureTemplate const* baseTemplate = sObjectMgr->GetCreatureTemplate(entry); - if (!baseTemplate) - baseTemplate = actTemplate; - uint32 scriptId = baseTemplate->ScriptID; - if (cData && cData->ScriptId) - scriptId = cData->ScriptId; - GET_SCRIPT_RET(CreatureScript, scriptId, tmpscript, true); - return tmpscript->CanSpawn(spawnId, entry, baseTemplate, actTemplate, cData, map); -} - CreatureAI* ScriptMgr::GetCreatureAI(Creature* creature) { ASSERT(creature); diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index e2cec401611..6f83a607da4 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -417,9 +417,6 @@ class TC_GAME_API CreatureScript : public UnitScript public: - // Called when the creature tries to spawn. Return false to block spawn and re-evaluate on next tick. - virtual bool CanSpawn(ObjectGuid::LowType /*spawnId*/, uint32 /*entry*/, CreatureTemplate const* /*baseTemplate*/, CreatureTemplate const* /*actTemplate*/, CreatureData const* /*cData*/, Map const* /*map*/) const { return true; } - // Called when a CreatureAI object is needed for the creature. virtual CreatureAI* GetAI(Creature* /*creature*/) const = 0; }; @@ -920,7 +917,6 @@ class TC_GAME_API ScriptMgr public: /* CreatureScript */ - bool CanSpawn(ObjectGuid::LowType spawnId, uint32 entry, CreatureTemplate const* actTemplate, 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 ead85b9c6cb..2bffc6b58c9 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -137,6 +137,11 @@ World::World() memset(m_int_configs, 0, sizeof(m_int_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(nullptr); } /// World destructor @@ -211,6 +216,59 @@ char const* World::GetMotd() const return m_motd.c_str(); } +void World::TriggerGuidWarning() +{ + // Lock this only to prevent multiple maps triggering at the same time + std::lock_guard 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 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 { @@ -1253,6 +1311,50 @@ void World::LoadConfigSettings(bool reload) TC_LOG_ERROR("server.loading", "NoGrayAggro.Below (%i) cannot be greater than NoGrayAggro.Above (%i). Set to %i.", m_int_configs[CONFIG_NO_GRAY_AGGRO_BELOW], m_int_configs[CONFIG_NO_GRAY_AGGRO_ABOVE], m_int_configs[CONFIG_NO_GRAY_AGGRO_ABOVE]); 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", false); + 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", "./"); @@ -1692,6 +1794,12 @@ 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 instance spawn groups..."); + sObjectMgr->LoadInstanceSpawnGroups(); + TC_LOG_INFO("server.loading", "Loading Creature Data..."); sObjectMgr->LoadCreatures(); @@ -1708,10 +1816,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(); @@ -2365,6 +2476,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 ec0c4f9439c..b68f893fa66 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -210,7 +210,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 @@ -406,13 +405,11 @@ enum WorldIntConfigs CONFIG_AUCTION_GETALL_DELAY, CONFIG_AUCTION_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, @@ -799,6 +796,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(); @@ -896,7 +897,21 @@ class TC_GAME_API World AutobroadcastsWeightMap m_AutobroadcastsWeights; 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; diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp index d13c0274b2c..e7a96ee525f 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -41,6 +41,8 @@ EndScriptData */ #include "WorldSession.h" #include #include +#include +#include class debug_commandscript : public CommandScript { @@ -973,7 +975,7 @@ public: Creature* v = new Creature(); Map* map = handler->GetSession()->GetPlayer()->GetMap(); - if (!v->Create(map->GenerateLowGuid(), map, handler->GetSession()->GetPlayer()->GetPhaseMask(), entry, x, y, z, o, nullptr, id)) + if (!v->Create(map->GenerateLowGuid(), map, handler->GetSession()->GetPlayer()->GetPhaseMask(), entry, { x, y, z, o }, nullptr, id)) { delete v; return false; diff --git a/src/server/scripts/Commands/cs_go.cpp b/src/server/scripts/Commands/cs_go.cpp index a821dff1043..cae3e1a5fac 100644 --- a/src/server/scripts/Commands/cs_go.cpp +++ b/src/server/scripts/Commands/cs_go.cpp @@ -139,8 +139,6 @@ public: float o = fields[3].GetFloat(); uint32 mapId = fields[4].GetUInt16(); - Transport* transport = nullptr; - if (!MapManager::IsValidMapCoord(mapId, x, y, z, o) || sObjectMgr->IsTransportMap(mapId)) { handler->PSendSysMessage(LANG_INVALID_TARGET_COORD, x, y, mapId); @@ -158,11 +156,8 @@ public: else player->SaveRecallPosition(); - if (player->TeleportTo(mapId, x, y, z, o)) - { - if (transport) - transport->AddPassenger(player); - } + player->TeleportTo(mapId, x, y, z, o); + return true; } @@ -250,7 +245,7 @@ public: player->SaveRecallPosition(); Map const* map = sMapMgr->CreateBaseMap(mapId); - float z = std::max(map->GetStaticHeight(PhasingHandler::GetEmptyPhaseShift(), x, y, MAX_HEIGHT), map->GetWaterLevel(PhasingHandler::GetEmptyPhaseShift(), x, y)); + float z = std::max(map->GetHeight(PhasingHandler::GetEmptyPhaseShift(), x, y, MAX_HEIGHT), map->GetWaterLevel(PhasingHandler::GetEmptyPhaseShift(), x, y)); player->TeleportTo(mapId, x, y, z, player->GetOrientation()); return true; @@ -273,28 +268,18 @@ public: if (!guidLow) return false; - float x, y, z, o; - uint32 mapId; - // by DB guid - if (GameObjectData const* goData = sObjectMgr->GetGOData(guidLow)) - { - x = goData->posX; - y = goData->posY; - z = goData->posZ; - o = goData->orientation; - mapId = goData->mapid; - } - else + GameObjectData const* goData = sObjectMgr->GetGameObjectData(guidLow); + if (!goData) { handler->SendSysMessage(LANG_COMMAND_GOOBJNOTFOUND); handler->SetSentErrorMessage(true); return false; } - if (!MapManager::IsValidMapCoord(mapId, x, y, z, o) || sObjectMgr->IsTransportMap(mapId)) + if (!MapManager::IsValidMapCoord(goData->spawnPoint) || sObjectMgr->IsTransportMap(goData->spawnPoint.GetMapId())) { - handler->PSendSysMessage(LANG_INVALID_TARGET_COORD, x, y, mapId); + handler->PSendSysMessage(LANG_INVALID_TARGET_COORD, goData->spawnPoint.GetPositionX(), goData->spawnPoint.GetPositionY(), goData->spawnPoint.GetMapId()); handler->SetSentErrorMessage(true); return false; } @@ -309,7 +294,7 @@ public: else player->SaveRecallPosition(); - player->TeleportTo(mapId, x, y, z, o); + player->TeleportTo(goData->spawnPoint); return true; } @@ -470,7 +455,7 @@ public: else player->SaveRecallPosition(); - float z = std::max(map->GetStaticHeight(PhasingHandler::GetEmptyPhaseShift(), x, y, MAX_HEIGHT), map->GetWaterLevel(PhasingHandler::GetEmptyPhaseShift(), x, y)); + float z = std::max(map->GetHeight(PhasingHandler::GetEmptyPhaseShift(), x, y, MAX_HEIGHT), map->GetWaterLevel(PhasingHandler::GetEmptyPhaseShift(), x, y)); player->TeleportTo(zoneEntry->mapid, x, y, z, player->GetOrientation()); return true; @@ -518,7 +503,7 @@ public: return false; } Map const* map = sMapMgr->CreateBaseMap(mapId); - z = std::max(map->GetStaticHeight(PhasingHandler::GetEmptyPhaseShift(), x, y, MAX_HEIGHT), map->GetWaterLevel(PhasingHandler::GetEmptyPhaseShift(), x, y)); + z = std::max(map->GetHeight(PhasingHandler::GetEmptyPhaseShift(), x, y, MAX_HEIGHT), map->GetWaterLevel(PhasingHandler::GetEmptyPhaseShift(), x, y)); } // stop flight if need diff --git a/src/server/scripts/Commands/cs_gobject.cpp b/src/server/scripts/Commands/cs_gobject.cpp index dde2a8001ba..7b68264316f 100644 --- a/src/server/scripts/Commands/cs_gobject.cpp +++ b/src/server/scripts/Commands/cs_gobject.cpp @@ -39,6 +39,10 @@ EndScriptData */ #include "RBAC.h" #include "WorldSession.h" +// definitions are over in cs_npc.cpp +bool HandleNpcSpawnGroup(ChatHandler* handler, char const* args); +bool HandleNpcDespawnGroup(ChatHandler* handler, char const* args); + class gobject_commandscript : public CommandScript { public: @@ -58,15 +62,17 @@ public: }; static std::vector gobjectCommandTable = { - { "activate", rbac::RBAC_PERM_COMMAND_GOBJECT_ACTIVATE, false, &HandleGameObjectActivateCommand, "" }, - { "delete", rbac::RBAC_PERM_COMMAND_GOBJECT_DELETE, false, &HandleGameObjectDeleteCommand, "" }, - { "info", rbac::RBAC_PERM_COMMAND_GOBJECT_INFO, false, &HandleGameObjectInfoCommand, "" }, - { "move", rbac::RBAC_PERM_COMMAND_GOBJECT_MOVE, false, &HandleGameObjectMoveCommand, "" }, - { "near", rbac::RBAC_PERM_COMMAND_GOBJECT_NEAR, false, &HandleGameObjectNearCommand, "" }, - { "target", rbac::RBAC_PERM_COMMAND_GOBJECT_TARGET, false, &HandleGameObjectTargetCommand, "" }, - { "turn", rbac::RBAC_PERM_COMMAND_GOBJECT_TURN, false, &HandleGameObjectTurnCommand, "" }, - { "add", rbac::RBAC_PERM_COMMAND_GOBJECT_ADD, false, nullptr, "", gobjectAddCommandTable }, - { "set", rbac::RBAC_PERM_COMMAND_GOBJECT_SET, false, nullptr, "", gobjectSetCommandTable }, + { "activate", rbac::RBAC_PERM_COMMAND_GOBJECT_ACTIVATE, false, &HandleGameObjectActivateCommand, "" }, + { "delete", rbac::RBAC_PERM_COMMAND_GOBJECT_DELETE, false, &HandleGameObjectDeleteCommand, "" }, + { "info", rbac::RBAC_PERM_COMMAND_GOBJECT_INFO, false, &HandleGameObjectInfoCommand, "" }, + { "move", rbac::RBAC_PERM_COMMAND_GOBJECT_MOVE, false, &HandleGameObjectMoveCommand, "" }, + { "near", rbac::RBAC_PERM_COMMAND_GOBJECT_NEAR, false, &HandleGameObjectNearCommand, "" }, + { "target", rbac::RBAC_PERM_COMMAND_GOBJECT_TARGET, false, &HandleGameObjectTargetCommand, "" }, + { "turn", rbac::RBAC_PERM_COMMAND_GOBJECT_TURN, false, &HandleGameObjectTurnCommand, "" }, + { "spawngroup", rbac::RBAC_PERM_COMMAND_GOBJECT_SPAWNGROUP, false, &HandleNpcSpawnGroup, "" }, + { "despawngroup", rbac::RBAC_PERM_COMMAND_GOBJECT_DESPAWNGROUP, false, &HandleNpcDespawnGroup,""}, + { "add", rbac::RBAC_PERM_COMMAND_GOBJECT_ADD, false, nullptr, "", gobjectAddCommandTable }, + { "set", rbac::RBAC_PERM_COMMAND_GOBJECT_SET, false, nullptr, "", gobjectSetCommandTable }, }; static std::vector commandTable = { @@ -172,14 +178,14 @@ public: object = new GameObject(); // this will generate a new guid if the object is in an instance - if (!object->LoadGameObjectFromDB(guidLow, map)) + if (!object->LoadFromDB(guidLow, map, true)) { delete object; return false; } /// @todo is it really necessary to add both the real and DB table guid here ? - sObjectMgr->AddGameobjectToGrid(guidLow, sObjectMgr->GetGOData(guidLow)); + sObjectMgr->AddGameobjectToGrid(guidLow, sObjectMgr->GetGameObjectData(guidLow)); handler->PSendSysMessage(LANG_GAMEOBJECT_ADD, objectId, objectInfo->name.c_str(), guidLow, player->GetPositionX(), player->GetPositionY(), player->GetPositionZ()); return true; @@ -348,6 +354,9 @@ public: if (!guidLow) return false; + Player const* const player = handler->GetSession()->GetPlayer(); + // force respawn to make sure we find something + player->GetMap()->ForceRespawn(SPAWN_TYPE_GAMEOBJECT, guidLow); GameObject* object = handler->GetObjectFromPlayerMapByDbGuid(guidLow); if (!object) { @@ -359,7 +368,7 @@ public: ObjectGuid ownerGuid = object->GetOwnerGUID(); if (ownerGuid) { - Unit* owner = ObjectAccessor::GetUnit(*handler->GetSession()->GetPlayer(), ownerGuid); + Unit* owner = ObjectAccessor::GetUnit(*player, ownerGuid); if (!owner || !ownerGuid.IsPlayer()) { handler->PSendSysMessage(LANG_COMMAND_DELOBJREFERCREATURE, ownerGuid.GetCounter(), guidLow); @@ -587,7 +596,7 @@ public: if (!cValue) return false; ObjectGuid::LowType guidLow = atoul(cValue); - GameObjectData const* data = sObjectMgr->GetGOData(guidLow); + GameObjectData const* data = sObjectMgr->GetGameObjectData(guidLow); if (!data) return false; entry = data->id; @@ -599,9 +608,16 @@ public: GameObjectTemplate const* gameObjectInfo = sObjectMgr->GetGameObjectTemplate(entry); + GameObject* thisGO = nullptr; + if (!gameObjectInfo) return false; + if (*args && handler->GetSession()->GetPlayer()) + thisGO = handler->GetSession()->GetPlayer()->FindNearestGameObject(entry, 30); + else if (handler->getSelectedObject() && handler->getSelectedObject()->GetTypeId() == TYPEID_GAMEOBJECT) + thisGO = handler->getSelectedObject()->ToGameObject(); + type = gameObjectInfo->type; displayId = gameObjectInfo->displayId; name = gameObjectInfo->name; @@ -610,10 +626,32 @@ public: else if (type == GAMEOBJECT_TYPE_FISHINGHOLE) lootId = gameObjectInfo->fishinghole.lootId; + // If we have a real object, send some info about it + if (thisGO) + { + handler->PSendSysMessage(LANG_SPAWNINFO_GUIDINFO, thisGO->GetGUID().ToString().c_str()); + handler->PSendSysMessage(LANG_SPAWNINFO_SPAWNID_LOCATION, thisGO->GetSpawnId(), thisGO->GetPositionX(), thisGO->GetPositionY(), thisGO->GetPositionZ()); + if (Player* player = handler->GetSession()->GetPlayer()) + { + Position playerPos = player->GetPosition(); + float dist = thisGO->GetExactDist(&playerPos); + handler->PSendSysMessage(LANG_SPAWNINFO_DISTANCEFROMPLAYER, dist); + } + } handler->PSendSysMessage(LANG_GOINFO_ENTRY, entry); handler->PSendSysMessage(LANG_GOINFO_TYPE, type); handler->PSendSysMessage(LANG_GOINFO_LOOTID, lootId); handler->PSendSysMessage(LANG_GOINFO_DISPLAYID, displayId); + if (WorldObject* object = handler->getSelectedObject()) + { + if (object->ToGameObject() && object->ToGameObject()->GetGameObjectData() && object->ToGameObject()->GetGameObjectData()->spawnGroupData->groupId) + { + SpawnGroupTemplateData const* groupData = object->ToGameObject()->GetGameObjectData()->spawnGroupData; + handler->PSendSysMessage(LANG_SPAWNINFO_GROUP_ID, groupData->name.c_str(), groupData->groupId, groupData->flags, object->GetMap()->IsSpawnGroupActive(groupData->groupId)); + } + if (object->ToGameObject()) + handler->PSendSysMessage(LANG_SPAWNINFO_COMPATIBILITY_MODE, object->ToGameObject()->GetRespawnCompatibilityMode()); + } handler->PSendSysMessage(LANG_GOINFO_NAME, name.c_str()); handler->PSendSysMessage(LANG_GOINFO_SIZE, gameObjectInfo->size); diff --git a/src/server/scripts/Commands/cs_list.cpp b/src/server/scripts/Commands/cs_list.cpp index 9eac8bb1829..544f991875b 100644 --- a/src/server/scripts/Commands/cs_list.cpp +++ b/src/server/scripts/Commands/cs_list.cpp @@ -25,9 +25,12 @@ EndScriptData */ #include "ScriptMgr.h" #include "CharacterCache.h" #include "Chat.h" +#include "Creature.h" #include "DatabaseEnv.h" #include "DBCStores.h" +#include "GameObject.h" #include "Language.h" +#include "MapManager.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "Player.h" @@ -45,11 +48,13 @@ public: { static std::vector listCommandTable = { - { "creature", rbac::RBAC_PERM_COMMAND_LIST_CREATURE, true, &HandleListCreatureCommand, "" }, - { "item", rbac::RBAC_PERM_COMMAND_LIST_ITEM, true, &HandleListItemCommand, "" }, - { "object", rbac::RBAC_PERM_COMMAND_LIST_OBJECT, true, &HandleListObjectCommand, "" }, - { "auras", rbac::RBAC_PERM_COMMAND_LIST_AURAS, false, &HandleListAurasCommand, "" }, - { "mail", rbac::RBAC_PERM_COMMAND_LIST_MAIL, true, &HandleListMailCommand, "" }, + { "creature", rbac::RBAC_PERM_COMMAND_LIST_CREATURE, true, &HandleListCreatureCommand, "" }, + { "item", rbac::RBAC_PERM_COMMAND_LIST_ITEM, true, &HandleListItemCommand, "" }, + { "object", rbac::RBAC_PERM_COMMAND_LIST_OBJECT, true, &HandleListObjectCommand, "" }, + { "auras", rbac::RBAC_PERM_COMMAND_LIST_AURAS, false, &HandleListAurasCommand, "" }, + { "mail", rbac::RBAC_PERM_COMMAND_LIST_MAIL, true, &HandleListMailCommand, "" }, + { "spawnpoints", rbac::RBAC_PERM_COMMAND_LIST_SPAWNPOINTS, false, &HandleListSpawnPointsCommand, "" }, + { "respawns", rbac::RBAC_PERM_COMMAND_LIST_MAIL, false, &HandleListRespawnsCommand, "" } }; static std::vector commandTable = { @@ -117,11 +122,40 @@ public: float y = fields[2].GetFloat(); float z = fields[3].GetFloat(); uint16 mapId = fields[4].GetUInt16(); + bool liveFound = false; + // Get map (only support base map from console) + Map* thisMap; if (handler->GetSession()) - handler->PSendSysMessage(LANG_CREATURE_LIST_CHAT, guid, guid, cInfo->Name.c_str(), x, y, z, mapId); + thisMap = handler->GetSession()->GetPlayer()->GetMap(); else - handler->PSendSysMessage(LANG_CREATURE_LIST_CONSOLE, guid, cInfo->Name.c_str(), x, y, z, mapId); + thisMap = sMapMgr->FindBaseNonInstanceMap(mapId); + + // If map found, try to find active version of this creature + if (thisMap) + { + auto const creBounds = thisMap->GetCreatureBySpawnIdStore().equal_range(guid); + if (creBounds.first != creBounds.second) + { + for (std::unordered_multimap::const_iterator itr = creBounds.first; itr != creBounds.second;) + { + if (handler->GetSession()) + handler->PSendSysMessage(LANG_CREATURE_LIST_CHAT, guid, guid, cInfo->Name.c_str(), x, y, z, mapId, itr->second->GetGUID().ToString().c_str(), itr->second->IsAlive() ? "*" : " "); + else + handler->PSendSysMessage(LANG_CREATURE_LIST_CONSOLE, guid, cInfo->Name.c_str(), x, y, z, mapId, itr->second->GetGUID().ToString().c_str(), itr->second->IsAlive() ? "*" : " "); + ++itr; + } + liveFound = true; + } + } + + if (!liveFound) + { + if (handler->GetSession()) + handler->PSendSysMessage(LANG_CREATURE_LIST_CHAT, guid, guid, cInfo->Name.c_str(), x, y, z, mapId, "", ""); + else + handler->PSendSysMessage(LANG_CREATURE_LIST_CONSOLE, guid, cInfo->Name.c_str(), x, y, z, mapId, "", ""); + } } while (result->NextRow()); } @@ -407,11 +441,40 @@ public: float z = fields[3].GetFloat(); uint16 mapId = fields[4].GetUInt16(); uint32 entry = fields[5].GetUInt32(); + bool liveFound = false; + // Get map (only support base map from console) + Map* thisMap; if (handler->GetSession()) - handler->PSendSysMessage(LANG_GO_LIST_CHAT, guid, entry, guid, gInfo->name.c_str(), x, y, z, mapId); + thisMap = handler->GetSession()->GetPlayer()->GetMap(); else - handler->PSendSysMessage(LANG_GO_LIST_CONSOLE, guid, gInfo->name.c_str(), x, y, z, mapId); + thisMap = sMapMgr->FindBaseNonInstanceMap(mapId); + + // If map found, try to find active version of this object + if (thisMap) + { + auto const goBounds = thisMap->GetGameObjectBySpawnIdStore().equal_range(guid); + if (goBounds.first != goBounds.second) + { + for (std::unordered_multimap::const_iterator itr = goBounds.first; itr != goBounds.second;) + { + if (handler->GetSession()) + handler->PSendSysMessage(LANG_GO_LIST_CHAT, guid, entry, guid, gInfo->name.c_str(), x, y, z, mapId, itr->second->GetGUID().ToString().c_str(), itr->second->isSpawned() ? "*" : " "); + else + handler->PSendSysMessage(LANG_GO_LIST_CONSOLE, guid, gInfo->name.c_str(), x, y, z, mapId, itr->second->GetGUID().ToString().c_str(), itr->second->isSpawned() ? "*" : " "); + ++itr; + } + liveFound = true; + } + } + + if (!liveFound) + { + if (handler->GetSession()) + handler->PSendSysMessage(LANG_GO_LIST_CHAT, guid, entry, guid, gInfo->name.c_str(), x, y, z, mapId, "", ""); + else + handler->PSendSysMessage(LANG_GO_LIST_CONSOLE, guid, gInfo->name.c_str(), x, y, z, mapId, "", ""); + } } while (result->NextRow()); } @@ -581,6 +644,103 @@ public: handler->PSendSysMessage(LANG_LIST_MAIL_NOT_FOUND); return true; } + + static bool HandleListSpawnPointsCommand(ChatHandler* handler, char const* /*args*/) + { + Player const* player = handler->GetSession()->GetPlayer(); + Map const* map = player->GetMap(); + + uint32 const mapId = map->GetId(); + bool const showAll = map->IsBattlegroundOrArena() || map->IsDungeon(); + handler->PSendSysMessage("Listing all spawn points in map %u (%s)%s:", mapId, map->GetMapName(), showAll ? "" : " within 5000yd"); + for (auto const& pair : sObjectMgr->GetAllCreatureData()) + { + SpawnData const& data = pair.second; + if (data.spawnPoint.GetMapId() != mapId) + continue; + CreatureTemplate const* cTemp = sObjectMgr->GetCreatureTemplate(data.id); + if (!cTemp) + continue; + if (showAll || data.spawnPoint.IsInDist2d(player, 5000.0)) + handler->PSendSysMessage("Type: %u | SpawnId: %u | Entry: %u (%s) | X: %.3f | Y: %.3f | Z: %.3f", uint32(data.type), data.spawnId, data.id, cTemp->Name.c_str(), data.spawnPoint.GetPositionX(), data.spawnPoint.GetPositionY(), data.spawnPoint.GetPositionZ()); + } + for (auto const& pair : sObjectMgr->GetAllGameObjectData()) + { + SpawnData const& data = pair.second; + if (data.spawnPoint.GetMapId() != mapId) + continue; + GameObjectTemplate const* goTemp = sObjectMgr->GetGameObjectTemplate(data.id); + if (!goTemp) + continue; + if (showAll || data.spawnPoint.IsInDist2d(player, 5000.0)) + handler->PSendSysMessage("Type: %u | SpawnId: %u | Entry: %u (%s) | X: %.3f | Y: %.3f | Z: %.3f", uint32(data.type), data.spawnId, data.id, goTemp->name.c_str(), data.spawnPoint.GetPositionX(), data.spawnPoint.GetPositionY(), data.spawnPoint.GetPositionZ()); + } + return true; + } + + static char const* GetZoneName(uint32 zoneId, LocaleConstant locale) + { + AreaTableEntry const* zoneEntry = sAreaTableStore.LookupEntry(zoneId); + return zoneEntry ? (const char*)zoneEntry->area_name[locale] : ""; + } + static bool HandleListRespawnsCommand(ChatHandler* handler, char const* args) + { + Player const* player = handler->GetSession()->GetPlayer(); + Map const* map = player->GetMap(); + + uint32 range = 0; + if (*args) + range = atoi((char*)args); + + std::vector respawns; + LocaleConstant locale = handler->GetSession()->GetSessionDbcLocale(); + char const* stringOverdue = sObjectMgr->GetTrinityString(LANG_LIST_RESPAWNS_OVERDUE, locale); + char const* stringCreature = sObjectMgr->GetTrinityString(LANG_LIST_RESPAWNS_CREATURES, locale); + char const* stringGameobject = sObjectMgr->GetTrinityString(LANG_LIST_RESPAWNS_GAMEOBJECTS, locale); + + uint32 zoneId = player->GetZoneId(); + if (range) + handler->PSendSysMessage(LANG_LIST_RESPAWNS_RANGE, stringCreature, range); + else + handler->PSendSysMessage(LANG_LIST_RESPAWNS_ZONE, stringCreature, GetZoneName(zoneId, handler->GetSessionDbcLocale()), zoneId); + handler->PSendSysMessage(LANG_LIST_RESPAWNS_LISTHEADER); + map->GetRespawnInfo(respawns, SPAWN_TYPEMASK_CREATURE, range ? 0 : zoneId); + for (RespawnInfo* ri : respawns) + { + CreatureData const* data = sObjectMgr->GetCreatureData(ri->spawnId); + if (!data) + continue; + if (range && !player->IsInDist(data->spawnPoint, range)) + continue; + uint32 gridY = ri->gridId / MAX_NUMBER_OF_GRIDS; + uint32 gridX = ri->gridId % MAX_NUMBER_OF_GRIDS; + + std::string respawnTime = ri->respawnTime > time(nullptr) ? secsToTimeString(uint64(ri->respawnTime - time(nullptr)), true) : stringOverdue; + handler->PSendSysMessage("%u | %u | [%02u,%02u] | %s (%u) | %s", ri->spawnId, ri->entry, gridX, gridY, GetZoneName(ri->zoneId, handler->GetSessionDbcLocale()), ri->zoneId, map->IsSpawnGroupActive(data->spawnGroupData->groupId) ? respawnTime.c_str() : "inactive"); + } + + respawns.clear(); + if (range) + handler->PSendSysMessage(LANG_LIST_RESPAWNS_RANGE, stringGameobject, range); + else + handler->PSendSysMessage(LANG_LIST_RESPAWNS_ZONE, stringGameobject, GetZoneName(zoneId, handler->GetSessionDbcLocale()), zoneId); + handler->PSendSysMessage(LANG_LIST_RESPAWNS_LISTHEADER); + map->GetRespawnInfo(respawns, SPAWN_TYPEMASK_GAMEOBJECT, range ? 0 : zoneId); + for (RespawnInfo* ri : respawns) + { + GameObjectData const* data = sObjectMgr->GetGameObjectData(ri->spawnId); + if (!data) + continue; + if (range && !player->IsInDist(data->spawnPoint, range)) + continue; + uint32 gridY = ri->gridId / MAX_NUMBER_OF_GRIDS; + uint32 gridX = ri->gridId % MAX_NUMBER_OF_GRIDS; + + std::string respawnTime = ri->respawnTime > time(nullptr) ? secsToTimeString(uint64(ri->respawnTime - time(nullptr)), true) : stringOverdue; + handler->PSendSysMessage("%u | %u | [% 02u, % 02u] | %s (%u) | %s", ri->spawnId, ri->entry, gridX, gridY, GetZoneName(ri->zoneId, handler->GetSessionDbcLocale()), ri->zoneId, map->IsSpawnGroupActive(data->spawnGroupData->groupId) ? respawnTime.c_str() : "inactive"); + } + return true; + } }; void AddSC_list_commandscript() diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp index b0e65f84742..cc19fefed66 100644 --- a/src/server/scripts/Commands/cs_misc.cpp +++ b/src/server/scripts/Commands/cs_misc.cpp @@ -1901,10 +1901,22 @@ public: return true; } + // First handle any creatures that still have a corpse around Trinity::RespawnDo u_do; Trinity::WorldObjectWorker worker(player, u_do); Cell::VisitGridObjects(player, worker, player->GetGridActivationRange()); + // Now handle any that had despawned, but had respawn time logged. + std::vector data; + player->GetMap()->GetRespawnInfo(data, SPAWN_TYPEMASK_ALL, 0); + if (!data.empty()) + { + uint32 const gridId = Trinity::ComputeGridCoord(player->GetPositionX(), player->GetPositionY()).GetId(); + for (RespawnInfo* info : data) + if (info->gridId == gridId) + player->GetMap()->ForceRespawn(info->type, info->spawnId); + } + return true; } diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index d6f8d53b925..8b080f4243a 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -181,6 +181,86 @@ EnumName const flagsExtra[] = uint32 const FLAGS_EXTRA_COUNT = std::extent::value; +bool HandleNpcSpawnGroup(ChatHandler* handler, char const* args) +{ + if (!*args) + return false; + + bool ignoreRespawn = false; + bool force = false; + uint32 groupId = 0; + + // Decode arguments + char* arg = strtok((char*)args, " "); + while (arg) + { + std::string thisArg = arg; + std::transform(thisArg.begin(), thisArg.end(), thisArg.begin(), ::tolower); + if (thisArg == "ignorerespawn") + ignoreRespawn = true; + else if (thisArg == "force") + force = true; + else if (thisArg.empty() || !(std::count_if(thisArg.begin(), thisArg.end(), ::isdigit) == (int)thisArg.size())) + return false; + else + groupId = atoi(thisArg.c_str()); + + arg = strtok(NULL, " "); + } + + Player* player = handler->GetSession()->GetPlayer(); + + std::vector creatureList; + if (!player->GetMap()->SpawnGroupSpawn(groupId, ignoreRespawn, force, &creatureList)) + { + handler->PSendSysMessage(LANG_SPAWNGROUP_BADGROUP, groupId); + handler->SetSentErrorMessage(true); + return false; + } + + handler->PSendSysMessage(LANG_SPAWNGROUP_SPAWNCOUNT, creatureList.size()); + for (WorldObject* obj : creatureList) + handler->PSendSysMessage("%s (%s)", obj->GetName(), obj->GetGUID().ToString().c_str()); + + return true; +} + +bool HandleNpcDespawnGroup(ChatHandler* handler, char const* args) +{ + if (!*args) + return false; + + bool deleteRespawnTimes = false; + uint32 groupId = 0; + + // Decode arguments + char* arg = strtok((char*)args, " "); + while (arg) + { + std::string thisArg = arg; + std::transform(thisArg.begin(), thisArg.end(), thisArg.begin(), ::tolower); + if (thisArg == "removerespawntime") + deleteRespawnTimes = true; + else if (thisArg.empty() || !(std::count_if(thisArg.begin(), thisArg.end(), ::isdigit) == (int)thisArg.size())) + return false; + else + groupId = atoi(thisArg.c_str()); + + arg = strtok(nullptr, " "); + } + + Player* player = handler->GetSession()->GetPlayer(); + + if (!player->GetMap()->SpawnGroupDespawn(groupId, deleteRespawnTimes)) + { + handler->PSendSysMessage(LANG_SPAWNGROUP_BADGROUP, groupId); + handler->SetSentErrorMessage(true); + return false; + } + + return true; +} + class npc_commandscript : public CommandScript { public: @@ -225,21 +305,23 @@ public: }; static std::vector npcCommandTable = { - { "info", rbac::RBAC_PERM_COMMAND_NPC_INFO, false, &HandleNpcInfoCommand, "" }, - { "near", rbac::RBAC_PERM_COMMAND_NPC_NEAR, false, &HandleNpcNearCommand, "" }, - { "move", rbac::RBAC_PERM_COMMAND_NPC_MOVE, false, &HandleNpcMoveCommand, "" }, - { "playemote", rbac::RBAC_PERM_COMMAND_NPC_PLAYEMOTE, false, &HandleNpcPlayEmoteCommand, "" }, - { "say", rbac::RBAC_PERM_COMMAND_NPC_SAY, false, &HandleNpcSayCommand, "" }, - { "textemote", rbac::RBAC_PERM_COMMAND_NPC_TEXTEMOTE, false, &HandleNpcTextEmoteCommand, "" }, - { "whisper", rbac::RBAC_PERM_COMMAND_NPC_WHISPER, false, &HandleNpcWhisperCommand, "" }, - { "yell", rbac::RBAC_PERM_COMMAND_NPC_YELL, false, &HandleNpcYellCommand, "" }, - { "tame", rbac::RBAC_PERM_COMMAND_NPC_TAME, false, &HandleNpcTameCommand, "" }, - { "add", rbac::RBAC_PERM_COMMAND_NPC_ADD, false, nullptr, "", npcAddCommandTable }, - { "delete", rbac::RBAC_PERM_COMMAND_NPC_DELETE, false, nullptr, "", npcDeleteCommandTable }, - { "follow", rbac::RBAC_PERM_COMMAND_NPC_FOLLOW, false, nullptr, "", npcFollowCommandTable }, - { "set", rbac::RBAC_PERM_COMMAND_NPC_SET, false, nullptr, "", npcSetCommandTable }, - { "evade", rbac::RBAC_PERM_COMMAND_NPC_EVADE, false, &HandleNpcEvadeCommand, "" }, - { "showloot", rbac::RBAC_PERM_COMMAND_NPC_SHOWLOOT, false, &HandleNpcShowLootCommand, "" }, + { "info", rbac::RBAC_PERM_COMMAND_NPC_INFO, false, &HandleNpcInfoCommand, "" }, + { "near", rbac::RBAC_PERM_COMMAND_NPC_NEAR, false, &HandleNpcNearCommand, "" }, + { "move", rbac::RBAC_PERM_COMMAND_NPC_MOVE, false, &HandleNpcMoveCommand, "" }, + { "playemote", rbac::RBAC_PERM_COMMAND_NPC_PLAYEMOTE, false, &HandleNpcPlayEmoteCommand, "" }, + { "say", rbac::RBAC_PERM_COMMAND_NPC_SAY, false, &HandleNpcSayCommand, "" }, + { "textemote", rbac::RBAC_PERM_COMMAND_NPC_TEXTEMOTE, false, &HandleNpcTextEmoteCommand, "" }, + { "whisper", rbac::RBAC_PERM_COMMAND_NPC_WHISPER, false, &HandleNpcWhisperCommand, "" }, + { "yell", rbac::RBAC_PERM_COMMAND_NPC_YELL, false, &HandleNpcYellCommand, "" }, + { "tame", rbac::RBAC_PERM_COMMAND_NPC_TAME, false, &HandleNpcTameCommand, "" }, + { "spawngroup", rbac::RBAC_PERM_COMMAND_NPC_SPAWNGROUP, false, &HandleNpcSpawnGroup, "" }, + { "despawngroup", rbac::RBAC_PERM_COMMAND_NPC_DESPAWNGROUP, false, &HandleNpcDespawnGroup, "" }, + { "add", rbac::RBAC_PERM_COMMAND_NPC_ADD, false, nullptr, "", npcAddCommandTable }, + { "delete", rbac::RBAC_PERM_COMMAND_NPC_DELETE, false, nullptr, "", npcDeleteCommandTable }, + { "follow", rbac::RBAC_PERM_COMMAND_NPC_FOLLOW, false, nullptr, "", npcFollowCommandTable }, + { "set", rbac::RBAC_PERM_COMMAND_NPC_SET, false, nullptr, "", npcSetCommandTable }, + { "evade", rbac::RBAC_PERM_COMMAND_NPC_EVADE, false, &HandleNpcEvadeCommand, "" }, + { "showloot", rbac::RBAC_PERM_COMMAND_NPC_SHOWLOOT, false, &HandleNpcShowLootCommand, "" }, }; static std::vector commandTable = { @@ -263,22 +345,16 @@ public: return false; Player* chr = handler->GetSession()->GetPlayer(); - float x = chr->GetPositionX(); - float y = chr->GetPositionY(); - float z = chr->GetPositionZ(); - float o = chr->GetOrientation(); Map* map = chr->GetMap(); if (Transport* trans = chr->GetTransport()) { ObjectGuid::LowType guid = map->GenerateLowGuid(); CreatureData& data = sObjectMgr->NewOrExistCreatureData(guid); + data.spawnId = guid; data.id = id; data.phaseMask = chr->GetPhaseMask(); - data.posX = chr->GetTransOffsetX(); - data.posY = chr->GetTransOffsetY(); - data.posZ = chr->GetTransOffsetZ(); - data.orientation = chr->GetTransOffsetO(); + data.spawnPoint.Relocate(chr->GetTransOffsetX(), chr->GetTransOffsetY(), chr->GetTransOffsetZ(), chr->GetTransOffsetO()); Creature* creature = trans->CreateNPCPassenger(guid, &data); @@ -291,7 +367,7 @@ public: } Creature* creature = new Creature(); - if (!creature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMask(), id, x, y, z, o)) + if (!creature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMask(), id, *chr)) { delete creature; return false; @@ -307,7 +383,7 @@ public: creature->CleanupsBeforeDelete(); delete creature; creature = new Creature(); - if (!creature->LoadCreatureFromDB(db_guid, map)) + if (!creature->LoadFromDB(db_guid, map, true, true)) { delete creature; return false; @@ -484,7 +560,7 @@ public: static bool HandleNpcDeleteCommand(ChatHandler* handler, char const* args) { - Creature* unit = nullptr; + Creature* creature = nullptr; if (*args) { @@ -496,22 +572,30 @@ public: ObjectGuid::LowType lowguid = atoul(cId); if (!lowguid) return false; - unit = handler->GetCreatureFromPlayerMapByDbGuid(lowguid); + // force respawn to make sure we find something + handler->GetSession()->GetPlayer()->GetMap()->ForceRespawn(SPAWN_TYPE_CREATURE, lowguid); + // then try to find it + creature = handler->GetCreatureFromPlayerMapByDbGuid(lowguid); } else - unit = handler->getSelectedCreature(); + creature = handler->getSelectedCreature(); - if (!unit || unit->IsPet() || unit->IsTotem()) + if (!creature || creature->IsPet() || creature->IsTotem()) { handler->SendSysMessage(LANG_SELECT_CREATURE); handler->SetSentErrorMessage(true); return false; } - // Delete the creature - unit->CombatStop(); - unit->DeleteFromDB(); - unit->AddObjectToRemoveList(); + if (TempSummon* summon = creature->ToTempSummon()) + summon->UnSummon(); + else + { + // Delete the creature + creature->CombatStop(); + creature->DeleteFromDB(); + creature->AddObjectToRemoveList(); + } handler->SendSysMessage(LANG_COMMAND_DELCREATMESSAGE); @@ -701,15 +785,22 @@ public: uint32 mechanicImmuneMask = cInfo->MechanicImmuneMask; uint32 displayid = target->GetDisplayId(); uint32 nativeid = target->GetNativeDisplayId(); - uint32 Entry = target->GetEntry(); + uint32 entry = target->GetEntry(); + + int64 curRespawnDelay = target->GetRespawnCompatibilityMode() ? target->GetRespawnTimeEx() - time(nullptr) : target->GetMap()->GetCreatureRespawnTime(target->GetSpawnId()) - time(nullptr); - int64 curRespawnDelay = target->GetRespawnTimeEx()-time(nullptr); if (curRespawnDelay < 0) curRespawnDelay = 0; std::string curRespawnDelayStr = secsToTimeString(uint64(curRespawnDelay), true); std::string defRespawnDelayStr = secsToTimeString(target->GetRespawnDelay(), true); - handler->PSendSysMessage(LANG_NPCINFO_CHAR, target->GetSpawnId(), target->GetGUID().GetCounter(), faction, npcflags, Entry, displayid, nativeid); + handler->PSendSysMessage(LANG_NPCINFO_CHAR, target->GetName().c_str(), target->GetSpawnId(), target->GetGUID().GetCounter(), entry, faction, npcflags, displayid, nativeid); + if (target->GetCreatureData() && target->GetCreatureData()->spawnGroupData->groupId) + { + SpawnGroupTemplateData const* const groupData = target->GetCreatureData()->spawnGroupData; + handler->PSendSysMessage(LANG_SPAWNINFO_GROUP_ID, groupData->name.c_str(), groupData->groupId, groupData->flags, target->GetMap()->IsSpawnGroupActive(groupData->groupId)); + } + handler->PSendSysMessage(LANG_SPAWNINFO_COMPATIBILITY_MODE, target->GetRespawnCompatibilityMode()); handler->PSendSysMessage(LANG_NPCINFO_LEVEL, target->getLevel()); handler->PSendSysMessage(LANG_NPCINFO_EQUIPMENT, target->GetCurrentEquipmentId(), target->GetOriginalEquipmentId()); handler->PSendSysMessage(LANG_NPCINFO_HEALTH, target->GetCreateHealth(), target->GetMaxHealth(), target->GetHealth()); @@ -785,7 +876,7 @@ public: if (!creatureTemplate) continue; - handler->PSendSysMessage(LANG_CREATURE_LIST_CHAT, guid, guid, creatureTemplate->Name.c_str(), x, y, z, mapId); + handler->PSendSysMessage(LANG_CREATURE_LIST_CHAT, guid, guid, creatureTemplate->Name.c_str(), x, y, z, mapId, "", ""); ++count; } @@ -803,8 +894,13 @@ public: ObjectGuid::LowType lowguid = 0; Creature* creature = handler->getSelectedCreature(); + Player const* player = handler->GetSession()->GetPlayer(); + if (!player) + return false; - if (!creature) + if (creature) + lowguid = creature->GetSpawnId(); + else { // number or [name] Shift-click form |color|Hcreature:creature_guid|h[name]|h|r char* cId = handler->extractKeyFromLink((char*)args, "Hcreature"); @@ -812,63 +908,46 @@ public: return false; lowguid = atoul(cId); - - // Attempting creature load from DB data - CreatureData const* data = sObjectMgr->GetCreatureData(lowguid); - if (!data) - { - handler->PSendSysMessage(LANG_COMMAND_CREATGUIDNOTFOUND, lowguid); - handler->SetSentErrorMessage(true); - return false; - } - - uint32 map_id = data->mapid; - - if (handler->GetSession()->GetPlayer()->GetMapId() != map_id) - { - handler->PSendSysMessage(LANG_COMMAND_CREATUREATSAMEMAP, lowguid); - handler->SetSentErrorMessage(true); - return false; - } } - else + + // Attempting creature load from DB data + CreatureData const* data = sObjectMgr->GetCreatureData(lowguid); + if (!data) { - lowguid = creature->GetSpawnId(); + handler->PSendSysMessage(LANG_COMMAND_CREATGUIDNOTFOUND, lowguid); + handler->SetSentErrorMessage(true); + return false; } - float x = handler->GetSession()->GetPlayer()->GetPositionX(); - float y = handler->GetSession()->GetPlayer()->GetPositionY(); - float z = handler->GetSession()->GetPlayer()->GetPositionZ(); - float o = handler->GetSession()->GetPlayer()->GetOrientation(); - - if (creature) + if (player->GetMapId() != data->spawnPoint.GetMapId()) { - if (CreatureData const* data = sObjectMgr->GetCreatureData(creature->GetSpawnId())) - { - const_cast(data)->posX = x; - const_cast(data)->posY = y; - const_cast(data)->posZ = z; - const_cast(data)->orientation = o; - } - creature->SetPosition(x, y, z, o); - creature->GetMotionMaster()->Initialize(); - if (creature->IsAlive()) // dead creature will reset movement generator at respawn - { - creature->setDeathState(JUST_DIED); - creature->Respawn(); - } + handler->PSendSysMessage(LANG_COMMAND_CREATUREATSAMEMAP, lowguid); + handler->SetSentErrorMessage(true); + return false; } + // update position in memory + const_cast(data)->spawnPoint.Relocate(*player); + PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_CREATURE_POSITION); - stmt->setFloat(0, x); - stmt->setFloat(1, y); - stmt->setFloat(2, z); - stmt->setFloat(3, o); + stmt->setFloat(0, player->GetPositionX()); + stmt->setFloat(1, player->GetPositionY()); + stmt->setFloat(2, player->GetPositionZ()); + stmt->setFloat(3, player->GetOrientation()); stmt->setUInt32(4, lowguid); - WorldDatabase.Execute(stmt); + // respawn selected creature at the new location + if (creature) + { + if (creature->IsAlive()) + creature->setDeathState(JUST_DIED); + creature->Respawn(true); + if (!creature->GetRespawnCompatibilityMode()) + creature->AddObjectToRemoveList(); + } + handler->PSendSysMessage(LANG_COMMAND_CREATUREMOVED); return true; } diff --git a/src/server/scripts/Commands/cs_wp.cpp b/src/server/scripts/Commands/cs_wp.cpp index 66ffb1371ea..702a293e057 100644 --- a/src/server/scripts/Commands/cs_wp.cpp +++ b/src/server/scripts/Commands/cs_wp.cpp @@ -667,7 +667,7 @@ public: // re-create Creature* wpCreature = new Creature(); - if (!wpCreature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMask(), VISUAL_WAYPOINT, chr->GetPositionX(), chr->GetPositionY(), chr->GetPositionZ(), chr->GetOrientation())) + if (!wpCreature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMask(), VISUAL_WAYPOINT, *chr)) { handler->PSendSysMessage(LANG_WAYPOINT_VP_NOTCREATED, VISUAL_WAYPOINT); delete wpCreature; @@ -678,8 +678,7 @@ public: wpCreature->SaveToDB(map->GetId(), (1 << map->GetSpawnMode()), chr->GetPhaseMask()); // To call _LoadGoods(); _LoadQuests(); CreateTrainerSpells(); - /// @todo Should we first use "Create" then use "LoadFromDB"? - if (!wpCreature->LoadCreatureFromDB(wpCreature->GetSpawnId(), map)) + if (!wpCreature->LoadFromDB(wpCreature->GetSpawnId(), map, true, true)) { handler->PSendSysMessage(LANG_WAYPOINT_VP_NOTCREATED, VISUAL_WAYPOINT); delete wpCreature; @@ -881,7 +880,7 @@ public: float o = chr->GetOrientation(); Creature* wpCreature = new Creature(); - if (!wpCreature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMask(), id, x, y, z, o)) + if (!wpCreature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMask(), id, { x, y, z, o })) { handler->PSendSysMessage(LANG_WAYPOINT_VP_NOTCREATED, id); delete wpCreature; @@ -899,7 +898,7 @@ public: wpCreature->SaveToDB(map->GetId(), (1 << map->GetSpawnMode()), chr->GetPhaseMask()); // To call _LoadGoods(); _LoadQuests(); CreateTrainerSpells(); - if (!wpCreature->LoadCreatureFromDB(wpCreature->GetSpawnId(), map)) + if (!wpCreature->LoadFromDB(wpCreature->GetSpawnId(), map, true, true)) { handler->PSendSysMessage(LANG_WAYPOINT_VP_NOTCREATED, id); delete wpCreature; @@ -945,7 +944,7 @@ public: Map* map = chr->GetMap(); Creature* creature = new Creature(); - if (!creature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMask(), id, x, y, z, o)) + if (!creature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMask(), id, { x, y, z, o })) { handler->PSendSysMessage(LANG_WAYPOINT_VP_NOTCREATED, id); delete creature; @@ -955,7 +954,7 @@ public: PhasingHandler::InheritPhaseShift(creature, chr); creature->SaveToDB(map->GetId(), (1 << map->GetSpawnMode()), chr->GetPhaseMask()); - if (!creature->LoadCreatureFromDB(creature->GetSpawnId(), map)) + if (!creature->LoadFromDB(creature->GetSpawnId(), map, true, true)) { handler->PSendSysMessage(LANG_WAYPOINT_VP_NOTCREATED, id); delete creature; @@ -996,7 +995,7 @@ public: Map* map = chr->GetMap(); Creature* creature = new Creature(); - if (!creature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMask(), id, x, y, z, o)) + if (!creature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMask(), id, { x, y, z, o })) { handler->PSendSysMessage(LANG_WAYPOINT_NOTCREATED, id); delete creature; @@ -1006,7 +1005,7 @@ public: PhasingHandler::InheritPhaseShift(creature, chr); creature->SaveToDB(map->GetId(), (1 << map->GetSpawnMode()), chr->GetPhaseMask()); - if (!creature->LoadCreatureFromDB(creature->GetSpawnId(), map)) + if (!creature->LoadFromDB(creature->GetSpawnId(), map, true, true)) { handler->PSendSysMessage(LANG_WAYPOINT_NOTCREATED, id); delete creature; diff --git a/src/server/scripts/EasternKingdoms/AlteracValley/alterac_valley.cpp b/src/server/scripts/EasternKingdoms/AlteracValley/alterac_valley.cpp index a17ba9bc37d..2831164a47d 100644 --- a/src/server/scripts/EasternKingdoms/AlteracValley/alterac_valley.cpp +++ b/src/server/scripts/EasternKingdoms/AlteracValley/alterac_valley.cpp @@ -107,7 +107,7 @@ class npc_av_marshal_or_warmaster : public CreatureScript events.ScheduleEvent(EVENT_CHECK_RESET, 5000); } - void JustRespawned() override + void JustAppeared() override { Reset(); } diff --git a/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp b/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp index 6dccb51dce6..3371da03d6e 100644 --- a/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp +++ b/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp @@ -71,7 +71,7 @@ public: events.ScheduleEvent(EVENT_RANDOM_YELL, urand(20 * IN_MILLISECONDS, 30 * IN_MILLISECONDS)); //20 to 30 seconds } - void JustRespawned() override + void JustAppeared() override { Reset(); Talk(SAY_RESPAWN); diff --git a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_felmyst.cpp b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_felmyst.cpp index af7b50bf9f6..e01aa753503 100644 --- a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_felmyst.cpp +++ b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_felmyst.cpp @@ -186,7 +186,7 @@ public: Talk(YELL_KILL); } - void JustRespawned() override + void JustAppeared() override { Talk(YELL_BIRTH); } diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp index cdb7b99372b..fa774c0b4f9 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp @@ -278,31 +278,24 @@ public: _anubar.push_back(guid); } - void Initialize() + void InitializeAI() override { + BossAI::InitializeAI(); me->SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS, 9.0f); me->SetFloatValue(UNIT_FIELD_COMBATREACH, 9.0f); _enteredCombat = false; _doorsWebbed = false; _lastPlayerCombatState = false; SetStep(0); + } + + void JustAppeared() override + { + BossAI::JustAppeared(); SetCombatMovement(true); SummonCrusherPack(SUMMON_GROUP_CRUSHER_1); } - void InitializeAI() override - { - BossAI::InitializeAI(); - if (me->IsAlive()) - Initialize(); - } - - void JustRespawned() override - { - BossAI::JustRespawned(); - Initialize(); - } - void UpdateAI(uint32 diff) override { if (!UpdateVictim()) diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp index 00184fc36c2..ba54dff4847 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp @@ -157,15 +157,9 @@ class boss_krik_thir : public CreatureScript me->SetReactState(REACT_PASSIVE); } - void InitializeAI() override + void JustAppeared() override { - BossAI::InitializeAI(); - SummonAdds(); - } - - void JustRespawned() override - { - BossAI::JustRespawned(); + BossAI::JustAppeared(); SummonAdds(); } diff --git a/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_halion.cpp b/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_halion.cpp index ede7e912b44..7a60a86b4e4 100644 --- a/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_halion.cpp +++ b/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_halion.cpp @@ -569,7 +569,7 @@ class npc_halion_controller : public CreatureScript _twilightDamageTaken = 0; } - void JustRespawned() override + void JustAppeared() override { if (_instance->GetGuidData(DATA_HALION) || _instance->GetBossState(DATA_GENERAL_ZARITHRIAN) != DONE) return; diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp index 7f8d38feac4..b634771e494 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp @@ -442,7 +442,7 @@ struct BloodPrincesBossAI : public BossAI { _spawnHealth = 1; if (!me->isDead()) - JustRespawned(); + JustAppeared(); } void Reset() override @@ -490,7 +490,7 @@ struct BloodPrincesBossAI : public BossAI } } - void JustRespawned() override + void JustAppeared() override { if (Creature* controller = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_BLOOD_PRINCES_CONTROL))) if (controller->AI()->GetData(DATA_INTRO) != DATA_INTRO_DONE) diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp index 7d02971daf2..0b4ccf64c6c 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp @@ -687,9 +687,9 @@ class npc_spinestalker : public CreatureScript } } - void JustRespawned() override + void JustAppeared() override { - ScriptedAI::JustRespawned(); + ScriptedAI::JustAppeared(); _instance->SetData(DATA_SINDRAGOSA_FROSTWYRMS, me->GetSpawnId()); // this cannot be in Reset because reset also happens on evade } @@ -826,9 +826,9 @@ class npc_rimefang : public CreatureScript } } - void JustRespawned() override + void JustAppeared() override { - ScriptedAI::JustRespawned(); + ScriptedAI::JustAppeared(); _instance->SetData(DATA_SINDRAGOSA_FROSTWYRMS, me->GetSpawnId()); // this cannot be in Reset because reset also happens on evade } @@ -994,9 +994,9 @@ class npc_sindragosa_trash : public CreatureScript Initialize(); } - void JustRespawned() override + void JustAppeared() override { - ScriptedAI::JustRespawned(); + ScriptedAI::JustAppeared(); // Increase add count if (me->GetEntry() == NPC_FROSTWING_WHELP) diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp index 4bc49f7f74a..04545affd63 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp @@ -261,7 +261,7 @@ class ValithriaDespawner : public BasicEvent creature->SetRespawnDelay(10); if (CreatureData const* data = creature->GetCreatureData()) - creature->SetPosition(data->posX, data->posY, data->posZ, data->orientation); + creature->UpdatePosition(data->spawnPoint); creature->DespawnOrUnsummon(); creature->SetCorpseDelay(corpseDelay); diff --git a/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp b/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp index f11077011d3..a928eada90f 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp @@ -354,7 +354,7 @@ class FrostwingGauntletRespawner creature->SetRespawnDelay(2); if (CreatureData const* data = creature->GetCreatureData()) - creature->SetPosition(data->posX, data->posY, data->posZ, data->orientation); + creature->UpdatePosition(data->spawnPoint); creature->DespawnOrUnsummon(); creature->SetCorpseDelay(corpseDelay); diff --git a/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp b/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp index 766bd158886..411c93d49ba 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp @@ -395,13 +395,13 @@ class instance_icecrown_citadel : public InstanceMapScript break; case NPC_ZAFOD_BOOMBOX: if (GameObjectTemplate const* go = sObjectMgr->GetGameObjectTemplate(GO_THE_SKYBREAKER_A)) - if ((TeamInInstance == ALLIANCE && data->mapid == go->moTransport.mapID) || - (TeamInInstance == HORDE && data->mapid != go->moTransport.mapID)) + if ((TeamInInstance == ALLIANCE && data->spawnPoint.GetMapId() == go->moTransport.mapID) || + (TeamInInstance == HORDE && data->spawnPoint.GetMapId() != go->moTransport.mapID)) return entry; return 0; case NPC_IGB_MURADIN_BRONZEBEARD: - if ((TeamInInstance == ALLIANCE && data->posX > 10.0f) || - (TeamInInstance == HORDE && data->posX < 10.0f)) + if ((TeamInInstance == ALLIANCE && data->spawnPoint.GetPositionX() > 10.0f) || + (TeamInInstance == HORDE && data->spawnPoint.GetPositionX() < 10.0f)) return entry; return 0; default: diff --git a/src/server/scripts/Northrend/Naxxramas/boss_anubrekhan.cpp b/src/server/scripts/Northrend/Naxxramas/boss_anubrekhan.cpp index a73512a9bd9..5b68353cd09 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_anubrekhan.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_anubrekhan.cpp @@ -91,7 +91,7 @@ public: void InitializeAI() override { - if (!me->isDead()) + if (!me->isDead() && instance->GetBossState(BOSS_ANUBREKHAN) != DONE) { Reset(); SummonGuards(); diff --git a/src/server/scripts/Northrend/Naxxramas/boss_faerlina.cpp b/src/server/scripts/Northrend/Naxxramas/boss_faerlina.cpp index ca76190ad27..a5724679fb5 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_faerlina.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_faerlina.cpp @@ -84,7 +84,7 @@ class boss_faerlina : public CreatureScript void InitializeAI() override { - if (!me->isDead()) + if (!me->isDead() && instance->GetBossState(BOSS_FAERLINA) != DONE) { Reset(); SummonAdds(); diff --git a/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp b/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp index 9170fbe4600..27d952cd7c5 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp @@ -270,14 +270,9 @@ struct boss_four_horsemen_baseAI : public BossAI for (Horseman boss : horsemen) { if (Creature* cBoss = getHorsemanHandle(boss)) - { - cBoss->DespawnOrUnsummon(0); - cBoss->SetRespawnTime(15); - } + cBoss->DespawnOrUnsummon(0, Seconds(15)); else - { TC_LOG_WARN("scripts", "FourHorsemenAI: Encounter resetting but horseman with id %u is not present", uint32(boss)); - } } } diff --git a/src/server/scripts/Northrend/Naxxramas/boss_razuvious.cpp b/src/server/scripts/Northrend/Naxxramas/boss_razuvious.cpp index 4737b4a2676..2a85deba57a 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_razuvious.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_razuvious.cpp @@ -74,7 +74,7 @@ public: void InitializeAI() override { - if (!me->isDead()) + if (!me->isDead() && instance->GetBossState(BOSS_RAZUVIOUS) != DONE) { Reset(); SummonAdds(); diff --git a/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp b/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp index 6a55b557b41..08591ba57c3 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp @@ -31,15 +31,12 @@ enum Phases PHASE_NOT_ENGAGED = 1, PHASE_PETS, PHASE_TRANSITION, - PHASE_THADDIUS, - PHASE_RESETTING + PHASE_THADDIUS }; enum AIActions { - ACTION_RESET_ENCOUNTER_TIMER = -1, // sent from instance AI ACTION_BEGIN_RESET_ENCOUNTER = 0, // sent from thaddius to pets to trigger despawn and encounter reset - ACTION_RESET_ENCOUNTER, // sent from thaddius to pets to trigger respawn and full reset ACTION_FEUGEN_DIED, // sent from respective pet to thaddius to indicate death ACTION_STALAGG_DIED, // ^ ACTION_FEUGEN_RESET, // pet to thaddius @@ -176,9 +173,6 @@ public: { events.SetPhase(PHASE_NOT_ENGAGED); SetCombatMovement(false); - - // initialize everything properly, and ensure that the coils are loaded by the time we initialize - BeginResetEncounter(true); } } @@ -209,9 +203,9 @@ public: return false; } - void JustRespawned() override + void JustAppeared() override { - if (events.IsInPhase(PHASE_RESETTING)) + if (instance->GetBossState(BOSS_THADDIUS) != DONE) ResetEncounter(); } @@ -237,22 +231,13 @@ public: { switch (action) { - case ACTION_RESET_ENCOUNTER_TIMER: - if (events.IsInPhase(PHASE_RESETTING)) - ResetEncounter(); - break; case ACTION_FEUGEN_RESET: case ACTION_STALAGG_RESET: - if (!events.IsInPhase(PHASE_NOT_ENGAGED) && !events.IsInPhase(PHASE_RESETTING)) + if (!events.IsInPhase(PHASE_NOT_ENGAGED)) BeginResetEncounter(); break; case ACTION_FEUGEN_AGGRO: case ACTION_STALAGG_AGGRO: - if (events.IsInPhase(PHASE_RESETTING)) - { - BeginResetEncounter(); - return; - } if (!events.IsInPhase(PHASE_NOT_ENGAGED)) return; events.SetPhase(PHASE_PETS); @@ -325,28 +310,24 @@ public: events.ScheduleEvent(EVENT_TRANSITION_3, Seconds(14), 0, PHASE_TRANSITION); } - void BeginResetEncounter(bool initial = false) + void BeginResetEncounter() { if (instance->GetBossState(BOSS_THADDIUS) == DONE) return; - if (events.IsInPhase(PHASE_RESETTING)) - return; // remove polarity shift debuffs on reset instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_POSITIVE_CHARGE_APPLY); instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_NEGATIVE_CHARGE_APPLY); - me->DespawnOrUnsummon(); - me->SetRespawnTime(initial ? 5 : 30); + me->DespawnOrUnsummon(0, Seconds(30)); me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_STUNNED); - events.SetPhase(PHASE_RESETTING); + me->setActive(false); if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN))) feugen->AI()->DoAction(ACTION_BEGIN_RESET_ENCOUNTER); if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG))) stalagg->AI()->DoAction(ACTION_BEGIN_RESET_ENCOUNTER); - me->setActive(false); me->SetFarVisible(false); } @@ -359,10 +340,9 @@ public: events.SetPhase(PHASE_NOT_ENGAGED); me->SetReactState(REACT_PASSIVE); - if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN))) - feugen->AI()->DoAction(ACTION_RESET_ENCOUNTER); - if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG))) - stalagg->AI()->DoAction(ACTION_RESET_ENCOUNTER); + // @todo these guys should really be moved to a summon group - this is merely a hack to make them work in dynamic_spawning + instance->instance->RemoveRespawnTime(SPAWN_TYPE_CREATURE, 130958, true); // Stalagg + instance->instance->RemoveRespawnTime(SPAWN_TYPE_CREATURE, 130959, true); // Feugen } void UpdateAI(uint32 diff) override @@ -535,7 +515,7 @@ public: { if (GameObject* coil = myCoilGO()) coil->SetGoState(GO_STATE_READY); - me->DespawnOrUnsummon(); + me->DespawnOrUnsummon(0, Hours(24 * 7)); // will be force respawned by thaddius me->setActive(false); me->SetFarVisible(false); } @@ -553,9 +533,6 @@ public: case ACTION_BEGIN_RESET_ENCOUNTER: BeginResetEncounter(); break; - case ACTION_RESET_ENCOUNTER: - ResetEncounter(); - break; case ACTION_STALAGG_REVIVING_FX: break; case ACTION_STALAGG_REVIVED: @@ -580,7 +557,7 @@ public: break; case ACTION_TRANSITION: me->KillSelf(); // true death - me->DespawnOrUnsummon(); + me->DespawnOrUnsummon(0, Hours(24 * 7)); if (Creature* coil = myCoil()) { @@ -804,17 +781,11 @@ public: { if (GameObject* coil = myCoilGO()) coil->SetGoState(GO_STATE_READY); - me->DespawnOrUnsummon(); + me->DespawnOrUnsummon(0, Hours(24 * 7)); // will be force respawned by thaddius me->setActive(false); me->SetFarVisible(false); } - void ResetEncounter() - { - me->Respawn(true); - Initialize(); - } - void DoAction(int32 action) override { switch (action) @@ -822,9 +793,6 @@ public: case ACTION_BEGIN_RESET_ENCOUNTER: BeginResetEncounter(); break; - case ACTION_RESET_ENCOUNTER: - ResetEncounter(); - break; case ACTION_FEUGEN_REVIVING_FX: break; case ACTION_FEUGEN_REVIVED: @@ -851,7 +819,7 @@ public: break; case ACTION_TRANSITION: me->KillSelf(); // true death this time around - me->DespawnOrUnsummon(); + me->DespawnOrUnsummon(0, Hours(24 * 7)); if (Creature* coil = myCoil()) { diff --git a/src/server/scripts/Northrend/zone_storm_peaks.cpp b/src/server/scripts/Northrend/zone_storm_peaks.cpp index 85b82e330e3..1623c24a89a 100644 --- a/src/server/scripts/Northrend/zone_storm_peaks.cpp +++ b/src/server/scripts/Northrend/zone_storm_peaks.cpp @@ -210,7 +210,7 @@ public: me->CastSpell(me, SPELL_ICE_PRISON, true); } - void JustRespawned() override + void JustAppeared() override { Reset(); } diff --git a/src/server/scripts/Northrend/zone_zuldrak.cpp b/src/server/scripts/Northrend/zone_zuldrak.cpp index c5cc6d0ba72..b6337a7ce3a 100644 --- a/src/server/scripts/Northrend/zone_zuldrak.cpp +++ b/src/server/scripts/Northrend/zone_zuldrak.cpp @@ -926,7 +926,7 @@ public: me->CastSpell(me, STORM_VISUAL, true); } - void JustRespawned() override + void JustAppeared() override { Reset(); } diff --git a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp index 64f1f000d6b..206452819ac 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp +++ b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp @@ -733,7 +733,7 @@ public: instance->SetGuidData(DATA_LEOTHERAS_EVENT_STARTER, who->GetGUID()); } - void JustRespawned() override + void JustAppeared() override { AddedBanish = false; Reset(); diff --git a/src/server/scripts/Outland/boss_doomlord_kazzak.cpp b/src/server/scripts/Outland/boss_doomlord_kazzak.cpp index e77767cab06..989f4e86387 100644 --- a/src/server/scripts/Outland/boss_doomlord_kazzak.cpp +++ b/src/server/scripts/Outland/boss_doomlord_kazzak.cpp @@ -83,7 +83,7 @@ class boss_doomlord_kazzak : public CreatureScript _events.ScheduleEvent(EVENT_BERSERK, 180000); } - void JustRespawned() override + void JustAppeared() override { Talk(SAY_INTRO); } diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 4816ad56f97..cc9dc4c922e 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -16,6 +16,7 @@ # WARDEN SETTINGS # PLAYER INTERACTION # CREATURE SETTINGS +# SPAWN/RESPAWN SETTINGS # CHAT SETTINGS # GAME MASTER SETTINGS # VISIBILITY AND DISTANCES @@ -1711,6 +1712,122 @@ MonsterSight = 50.000000 # ################################################################################################### +################################################################################################### +# SPAWN/RESPAWN SETTINGS +# +# Respawn.MinCheckIntervalMS +# Description: Minimum time that needs to pass between respawn checks for any given map. +# Default: 5000 - 5 seconds + +Respawn.MinCheckIntervalMS = 5000 + +# +# Respawn.GuidWarnLevel +# Description: The point at which the highest guid for creatures or gameobjects in any map must reach +# before the warning logic is enabled. A restart will then be queued at the next quiet time +# The maximum guid per map is 16,777,216. So, it must be less than this value. +# Default: 12000000 - 12 million + +Respawn.GuidWarnLevel = 12000000 + +# +# Respawn.WarningMessage +# Description: This message will be periodically shown (Frequency specified by Respawn.WarningFrequency) to +# all users of the server, once the Respawn.GuidWarnLevel has been passed, and a restart scheduled. +# It's used to warn users that there will be an out of schedule server restart soon. +# Default: "There will be an unscheduled server restart at 03:00 server time. The server will be available again shortly after." + +Respawn.WarningMessage = "There will be an unscheduled server restart at 03:00. The server will be available again shortly after." + +# +# Respawn.WarningFrequency +# Description: The frequency (in seconds) that the warning message will be sent to users after a quiet time restart is triggered. +# The message will repeat each time this many seconds passed until the server is restarted. +# If set to 0, no warnings will be sent. +# Default: 1800 - (30 minutes) + +Respawn.WarningFrequency = 1800 + +# +# Respawn.GuidAlertLevel +# Description: The point at which the highest guid for creatures or gameobjects in any map must reach +# before the alert logic is enabled. A restart will then be triggered for 30 mins from that +# point. The maximum guid per map is 16,777,216. So, it must be less than this value. +# Default: 16000000 - 16 million + +Respawn.GuidAlertLevel = 16000000 + +# +# Respawn.AlertRestartReason +# Description: The shutdown reason given when the alert level is reached. The server will use a fixed time of +# 5 minutes and the reason for shutdown will be this message +# Default: "Urgent Maintenance" + +Respawn.AlertRestartReason = "Urgent Maintenance" + +# +# Respawn.RestartQuietTime +# Description: The hour at which the server will be restarted after the Respawn.GuidWarnLevel +# threshold has been reached. This can be between 0 and 23. 20 will be 8pm server time +# Default: 3 - 3am + +Respawn.RestartQuietTime = 3 + +# +# Respawn.DynamicMode +# Description: Select which mode (if any) should be used to adjust respawn of creatures. +# This will only affect creatures that have dynamic spawn rate scaling enabled in +# the spawn group table (by default, gathering nodes and quest targets with respawn time <30min +# 1 - Use number of players in zone +# Default: 0 - No dynamic respawn function + +Respawn.DynamicMode = 0 + +# +# Respawn.DynamicEscortNPC +# Description: This switch controls the dynamic respawn system for escort NPCs not in instancable maps (base maps only). +# This will cause the respawn timer to begin when an escort event begins, and potentially +# allow multiple instances of the NPC to be alive at the same time (when combined with Respawn.DynamicMode > 0) +# 1 - Enabled +# Default: 0 - Disabled + +Respawn.DynamicEscortNPC = 0 + +# +# Respawn.DynamicRateCreature +# Description: The rate at which the respawn time is adjusted for high player counts in a zone (for creatures). +# Up to this number of players, the respawn rate is unchanged. +# At double this number in players, you get twice as many respawns, at three times this number, three times the respawns, and so forth. +# Default: 10 + +Respawn.DynamicRateCreature = 10 + +# +# Respawn.DynamicMinimumCreature +# Description: The minimum respawn time for a creature under dynamic scaling. +# Default: 10 - (10 seconds) + +Respawn.DynamicMinimumCreature = 10 + +# +# Respawn.DynamicRateGameObject +# Description: The rate at which the respawn time is adjusted for high player counts in a zone (for gameobjects). +# Up to this number of players, the respawn rate is unchanged. +# At double this number in players, you get twice as many respawns, at three times this number, three times the respawns, and so forth. +# Default: 10 + +Respawn.DynamicRateGameObject = 10 + +# +# Respawn.DynamicMinimumGameObject +# Description: The minimum respawn time for a gameobject under dynamic scaling. +# Default: 10 - (10 seconds) + +Respawn.DynamicMinimumGameObject = 10 + +# +################################################################################################### + ################################################################################################### # CHAT SETTINGS #