diff --git a/sql/updates/world/3.3.5/9999_99_99_99_world_DYNSPAWN.sql b/sql/updates/world/3.3.5/9999_99_99_99_world_DYNSPAWN.sql new file mode 100644 index 0000000000000..dde858acfc3d3 --- /dev/null +++ b/sql/updates/world/3.3.5/9999_99_99_99_world_DYNSPAWN.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/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp index c64e7a9c2fcb3..654074d594bfd 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 b68f53bb17339..f7784ad256fc9 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 bd7a6efab22d1..c355eaacb5851 100644 --- a/src/server/game/AI/CreatureAI.h +++ b/src/server/game/AI/CreatureAI.h @@ -187,6 +187,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 e9e252bcf3572..afa7e789b7a93 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp @@ -478,7 +478,7 @@ void BossAI::_Reset() events.Reset(); summons.DespawnAll(); scheduler.CancelAll(); - if (instance) + if (instance && instance->GetBossState(_bossId) != DONE) instance->SetBossState(_bossId, NOT_STARTED); } @@ -564,12 +564,12 @@ bool BossAI::CanAIAttack(Unit const* target) const return CheckBoundary(target); } -void BossAI::_DespawnAtEvade(uint32 delayToRespawn /*= 30*/, Creature* who /*= nullptr*/) +void BossAI::_DespawnAtEvade(Seconds delayToRespawn, Creature* who) { - if (delayToRespawn < 2) + if (delayToRespawn < Seconds(2)) { - TC_LOG_ERROR("scripts", "_DespawnAtEvade called with delay of %u seconds, defaulting to 2.", delayToRespawn); - delayToRespawn = 2; + TC_LOG_ERROR("scripts", "_DespawnAtEvade called with delay of %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 6fc85b6a81a13..9f2e49c1ffb6e 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.h +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.h @@ -366,8 +366,8 @@ class TC_GAME_API BossAI : public ScriptedAI void _EnterCombat(); void _JustDied(); void _JustReachedHome(); - void _DespawnAtEvade(uint32 delayToRespawn = 30, Creature* who = nullptr); - void _DespawnAtEvade(Seconds const& time, Creature* who = nullptr) { _DespawnAtEvade(uint32(time.count()), who); } + void _DespawnAtEvade(Seconds delayToRespawn, Creature* who = nullptr); + void _DespawnAtEvade(uint32 delayToRespawn = 30, Creature* who = nullptr) { _DespawnAtEvade(Seconds(delayToRespawn), who); } void TeleportCheaters(); diff --git a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp index bcf060e2ad6d0..5f5330c3c8dc9 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp @@ -239,13 +239,17 @@ void npc_escortAI::UpdateAI(uint32 diff) return; } - if (m_bCanInstantRespawn) + if (m_bCanInstantRespawn && !sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC)) { me->setDeathState(JUST_DIED); me->Respawn(); } else + { + if (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC)) + me->GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, me->GetSpawnId(), true); me->DespawnOrUnsummon(); + } return; } @@ -280,11 +284,17 @@ void npc_escortAI::UpdateAI(uint32 diff) { TC_LOG_DEBUG("scripts", "EscortAI failed because player/group was to far away or not found"); - if (m_bCanInstantRespawn) + bool isEscort = false; + if (CreatureData const* cdata = me->GetCreatureData()) + isEscort = (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC) && (cdata->spawnGroupData->flags & SPAWNGROUP_FLAG_ESCORTQUESTNPC)); + + if (m_bCanInstantRespawn && !isEscort) { me->setDeathState(JUST_DIED); me->Respawn(); } + else if (m_bCanInstantRespawn && isEscort) + me->GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, me->GetSpawnId(), true); else me->DespawnOrUnsummon(); @@ -424,6 +434,22 @@ void npc_escortAI::SetRun(bool on) /// @todo get rid of this many variables passed in function. void npc_escortAI::Start(bool isActiveAttacker /* = true*/, bool run /* = false */, ObjectGuid playerGUID /* = 0 */, Quest const* quest /* = nullptr */, bool instantRespawn /* = false */, bool canLoopPath /* = false */, bool resetWaypoints /* = true */) { + // Queue respawn from the point it starts + if (Map* map = me->GetMap()) + { + if (CreatureData const* cdata = me->GetCreatureData()) + { + if (SpawnGroupTemplateData const* groupdata = cdata->spawnGroupData) + { + if (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC) && (groupdata->flags & SPAWNGROUP_FLAG_ESCORTQUESTNPC) && !map->GetCreatureRespawnTime(me->GetSpawnId())) + { + me->SetRespawnTime(me->GetRespawnDelay()); + me->SaveRespawnTime(); + } + } + } + } + if (me->GetVictim()) { TC_LOG_ERROR("scripts.escortai", "TSCR ERROR: EscortAI (script: %s, creature entry: %u) attempts to Start while in combat", me->GetScriptName().c_str(), me->GetEntry()); @@ -567,3 +593,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 754d96dced9ff..7d6f210f0344e 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h +++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h @@ -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/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index 03ad4df397a86..79f3b7ce652e6 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -2052,6 +2052,92 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (IsCreature(target)) target->ToCreature()->SetCorpseDelay(e.action.corpseDelay.timer); } + + break; + } + case SMART_ACTION_SPAWN_SPAWNGROUP: + { + if (e.action.groupSpawn.minDelay == 0 && e.action.groupSpawn.maxDelay == 0) + { + bool const ignoreRespawn = ((e.action.groupSpawn.spawnflags & SMARTAI_SPAWN_FLAGS::SMARTAI_SPAWN_FLAG_IGNORE_RESPAWN) != 0); + bool const force = ((e.action.groupSpawn.spawnflags & SMARTAI_SPAWN_FLAGS::SMARTAI_SPAWN_FLAG_FORCE_SPAWN) != 0); + + // Instant spawn + sObjectMgr->SpawnGroupSpawn(e.action.groupSpawn.groupId, GetBaseObject()->GetMap(), ignoreRespawn, force); + } + else + { + // Delayed spawn (use values from parameter to schedule event to call us back + SmartEvent ne = SmartEvent(); + ne.type = (SMART_EVENT)SMART_EVENT_UPDATE; + ne.event_chance = 100; + + ne.minMaxRepeat.min = e.action.groupSpawn.minDelay; + ne.minMaxRepeat.max = e.action.groupSpawn.maxDelay; + ne.minMaxRepeat.repeatMin = 0; + ne.minMaxRepeat.repeatMax = 0; + + ne.event_flags = 0; + ne.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; + + SmartAction ac = SmartAction(); + ac.type = (SMART_ACTION)SMART_ACTION_SPAWN_SPAWNGROUP; + ac.groupSpawn.groupId = e.action.groupSpawn.groupId; + ac.groupSpawn.minDelay = 0; + ac.groupSpawn.maxDelay = 0; + ac.groupSpawn.spawnflags = e.action.groupSpawn.spawnflags; + ac.timeEvent.id = e.action.timeEvent.id; + + SmartScriptHolder ev = SmartScriptHolder(); + ev.event = ne; + ev.event_id = e.event_id; + ev.target = e.target; + ev.action = ac; + InitTimer(ev); + mStoredEvents.push_back(ev); + } + break; + } + case SMART_ACTION_DESPAWN_SPAWNGROUP: + { + if (e.action.groupSpawn.minDelay == 0 && e.action.groupSpawn.maxDelay == 0) + { + bool const deleteRespawnTimes = ((e.action.groupSpawn.spawnflags & SMARTAI_SPAWN_FLAGS::SMARTAI_SPAWN_FLAG_NOSAVE_RESPAWN) != 0); + + // Instant spawn + sObjectMgr->SpawnGroupDespawn(e.action.groupSpawn.groupId, GetBaseObject()->GetMap(), deleteRespawnTimes); + } + else + { + // Delayed spawn (use values from parameter to schedule event to call us back + SmartEvent ne = SmartEvent(); + ne.type = (SMART_EVENT)SMART_EVENT_UPDATE; + ne.event_chance = 100; + + ne.minMaxRepeat.min = e.action.groupSpawn.minDelay; + ne.minMaxRepeat.max = e.action.groupSpawn.maxDelay; + ne.minMaxRepeat.repeatMin = 0; + ne.minMaxRepeat.repeatMax = 0; + + ne.event_flags = 0; + ne.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; + + SmartAction ac = SmartAction(); + ac.type = (SMART_ACTION)SMART_ACTION_DESPAWN_SPAWNGROUP; + ac.groupSpawn.groupId = e.action.groupSpawn.groupId; + ac.groupSpawn.minDelay = 0; + ac.groupSpawn.maxDelay = 0; + ac.groupSpawn.spawnflags = e.action.groupSpawn.spawnflags; + ac.timeEvent.id = e.action.timeEvent.id; + + SmartScriptHolder ev = SmartScriptHolder(); + ev.event = ne; + ev.event_id = e.event_id; + ev.target = e.target; + ev.action = ac; + InitTimer(ev); + mStoredEvents.push_back(ev); + } break; } case SMART_ACTION_DISABLE_EVADE: diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp index 31282a934ac78..3555575b2b2cd 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))); @@ -932,7 +932,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; @@ -1508,6 +1508,8 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) case SMART_ACTION_TRIGGER_RANDOM_TIMED_EVENT: case SMART_ACTION_SET_COUNTER: case SMART_ACTION_REMOVE_ALL_GAMEOBJECTS: + case SMART_ACTION_SPAWN_SPAWNGROUP: + case SMART_ACTION_DESPAWN_SPAWNGROUP: break; default: TC_LOG_ERROR("sql.sql", "SmartAIMgr: Not handled action_type(%u), event_type(%u), Entry %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 7a1433bc7fb59..f2ba244aaae81 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.h +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.h @@ -589,8 +589,12 @@ enum SMART_ACTION SMART_ACTION_REMOVE_ALL_GAMEOBJECTS = 126, SMART_ACTION_STOP_MOTION = 127, // stopMoving, movementExpired SMART_ACTION_PLAY_ANIMKIT = 128, // don't use on 3.3.5a + SMART_ACTION_SCENE_PLAY = 129, // don't use on 3.3.5a + SMART_ACTION_SCENE_CANCEL = 130, // don't use on 3.3.5a + SMART_ACTION_SPAWN_SPAWNGROUP = 131, // Group ID, min secs, max secs, spawnflags + SMART_ACTION_DESPAWN_SPAWNGROUP = 132, // Group ID, min secs, max secs, spawnflags - SMART_ACTION_END = 129 + SMART_ACTION_END = 133 }; struct SmartAction @@ -1094,6 +1098,13 @@ struct SmartAction { uint32 disable; } disableEvade; + struct + { + uint32 groupId; + uint32 minDelay; + uint32 maxDelay; + uint32 spawnflags; + } groupSpawn; struct { @@ -1138,6 +1149,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/Battlefield/Battlefield.cpp b/src/server/game/Battlefield/Battlefield.cpp index 27e83185e27d4..92b78216cb00b 100644 --- a/src/server/game/Battlefield/Battlefield.cpp +++ b/src/server/game/Battlefield/Battlefield.cpp @@ -776,18 +776,15 @@ 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 41591c419e98b..e184c22354edd 100644 --- a/src/server/game/Battlegrounds/Battleground.cpp +++ b/src/server/game/Battlegrounds/Battleground.cpp @@ -1515,7 +1515,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/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp index 146d2cb3bdee1..ba45699804255 100644 --- a/src/server/game/Conditions/ConditionMgr.cpp +++ b/src/server/game/Conditions/ConditionMgr.cpp @@ -2011,7 +2011,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 cbfab8004f6da..bcca31ed94e2b 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -239,7 +239,7 @@ m_lootRecipient(), m_lootRecipientGroup(0), _pickpocketLootRestore(0), m_corpseR 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_respawnCompatibilityMode(false), m_focusSpell(nullptr), m_focusDelay(0), m_shouldReacquireTarget(false), m_suppressedOrientation(0.0f), _lastDamagedTime(0) { m_regenTimer = CREATURE_REGEN_INTERVAL; @@ -333,43 +333,60 @@ void Creature::RemoveCorpse(bool setSpawnTime, bool destroyForNearbyPlayers) if (getDeathState() != CORPSE) return; - m_corpseRemoveTime = time(nullptr); - setDeathState(DEAD); - RemoveAllAuras(); - loot.clear(); - uint32 respawnDelay = m_respawnDelay; - if (IsAIEnabled) - AI()->CorpseRemoved(respawnDelay); + if (m_respawnCompatibilityMode) + { + m_corpseRemoveTime = time(nullptr); + setDeathState(DEAD); + RemoveAllAuras(); + loot.clear(); + uint32 respawnDelay = m_respawnDelay; + if (IsAIEnabled) + AI()->CorpseRemoved(respawnDelay); - if (destroyForNearbyPlayers) - DestroyForNearbyPlayers(); + if (destroyForNearbyPlayers) + DestroyForNearbyPlayers(); - // Should get removed later, just keep "compatibility" with scripts - if (setSpawnTime) - m_respawnTime = std::max(time(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); + float x, y, z, o; + GetRespawnPosition(x, y, z, &o); - // We were spawned on transport, calculate real position - if (IsSpawnedOnTransport()) - { - Position& pos = m_movementInfo.transport.pos; - pos.m_positionX = x; - pos.m_positionY = y; - pos.m_positionZ = z; - pos.SetOrientation(o); + // We were spawned on transport, calculate real position + if (IsSpawnedOnTransport()) + { + Position& pos = m_movementInfo.transport.pos; + pos.m_positionX = x; + pos.m_positionY = y; + pos.m_positionZ = z; + pos.SetOrientation(o); + + if (TransportBase* transport = GetDirectTransport()) + transport->CalculatePassengerPosition(x, y, z, &o); + } - if (TransportBase* transport = GetDirectTransport()) - transport->CalculatePassengerPosition(x, y, z, &o); + SetHomePosition(x, y, z, o); + GetMap()->CreatureRelocation(this, x, y, z, o); } + else + { + // In case this is called directly and normal respawn timer not set + // Since this timer will be longer than the already present time it + // will be ignored if the correct place added a respawn timer + if (setSpawnTime) + { + uint32 respawnDelay = m_respawnDelay; + m_respawnTime = std::max(time(NULL) + respawnDelay, m_respawnTime); - SetHomePosition(x, y, z, o); - GetMap()->CreatureRelocation(this, x, y, z, o); + SaveRespawnTime(0, false); + } + AddObjectToRemoveList(); + } } /** @@ -579,30 +596,34 @@ 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: { 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 + if (!sScriptMgr->CanSpawn(GetSpawnId(), GetEntry(), GetCreatureData(), GetMap())) + { + m_respawnTime = now + urand(4,7); + break; // Will be rechecked on next Update call after delay expires + } ObjectGuid dbtableHighGuid(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 Respawn(); else // the master is dead { ObjectGuid targetGuid = sObjectMgr->GetLinkedRespawnGuid(dbtableHighGuid); - if (targetGuid == dbtableHighGuid) // if linking self, never respawn (check delayed to next day) - SetRespawnTime(DAY); + if (targetGuid == dbtableHighGuid) // if linking self, never respawn + SetRespawnTime(WEEK); else { // else copy time from master and add a little - time_t baseRespawnTime = std::max(linkedRespawntime, now); + time_t baseRespawnTime = std::max(linkedRespawnTime, now); time_t const offset = urand(5, MINUTE); // linked guid can be a boss, uses std::numeric_limits::max to never respawn in that instance @@ -947,12 +968,16 @@ 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); SetPhaseMask(phaseMask, false); + // Set if this creature can handle dynamic spawns + if (!dynamic) + SetRespawnCompatibilityMode(); + CreatureTemplate const* cinfo = sObjectMgr->GetCreatureTemplate(entry); if (!cinfo) { @@ -962,13 +987,13 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, u //! 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; } UpdatePositionData(); @@ -1005,10 +1030,8 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, u //! 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(); @@ -1188,27 +1211,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 data.spawndist = GetDefaultMovementType() == IDLE_MOTION_TYPE ? 0.0f : m_respawnradius; @@ -1222,6 +1235,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(); // update in DB SQLTransaction trans = WorldDatabase.BeginTransaction(); @@ -1426,7 +1441,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) { @@ -1467,31 +1482,41 @@ bool Creature::LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool ad } m_spawnId = spawnId; + + m_respawnCompatibilityMode = ((data->spawnGroupData->flags & SPAWNGROUP_FLAG_COMPATIBILITY_MODE) != 0); m_creatureData = data; m_respawnradius = data->spawndist; m_respawnDelay = data->spawntimesecs; - if (!Create(map->GenerateLowGuid(), map, data->phaseMask, data->id, data->posX, data->posY, data->posZ, data->orientation, data)) + + // Is the creature script objecting to us spawning? If yes, delay by a little bit (then re-check in ::Update) + if (!m_respawnCompatibilityMode && !m_respawnTime && !sScriptMgr->CanSpawn(spawnId, data->id, data, map)) + { + SaveRespawnTime(urand(4,7)); + return false; + } + + if (!Create(map->GenerateLowGuid(), 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; + // Is the creature script objecting to us spawning? If yes, delay by a little bit (then re-check in ::Update) + if (m_respawnCompatibilityMode && !m_respawnTime && !sScriptMgr->CanSpawn(spawnId, GetEntry(), GetCreatureData(), map)) + m_respawnTime = time(nullptr)+urand(4,7); if (m_respawnTime) // respawn on Update { m_deathState = DEAD; if (CanFly()) { - float tz = map->GetHeight(GetPhaseMask(), 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(GetPhaseMask(), data->spawnPoint, true, MAX_FALL_DISTANCE); + if (data->spawnPoint.GetPositionZ() - tz > 0.1f && Trinity::IsValidMapCoord(tz)) + Relocate(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY(), tz); } } @@ -1587,7 +1612,7 @@ void Creature::DeleteFromDB() return; } - GetMap()->RemoveCreatureRespawnTime(m_spawnId); + GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, m_spawnId); sObjectMgr->DeleteCreatureData(m_spawnId); SQLTransaction trans = WorldDatabase.BeginTransaction(); @@ -1596,6 +1621,11 @@ void Creature::DeleteFromDB() 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); @@ -1735,14 +1765,31 @@ void Creature::setDeathState(DeathState s) if (s == JUST_DIED) { m_corpseRemoveTime = time(nullptr) + m_corpseDelay; - if (IsDungeonBoss() && !m_respawnDelay) - m_respawnTime = std::numeric_limits::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 @@ -1810,8 +1857,6 @@ void Creature::setDeathState(DeathState s) void Creature::Respawn(bool force) { - DestroyForNearbyPlayers(); - if (force) { if (IsAlive()) @@ -1820,51 +1865,62 @@ void Creature::Respawn(bool force) setDeathState(CORPSE); } - RemoveCorpse(false, false); - - if (getDeathState() == DEAD) + if (m_respawnCompatibilityMode) { - if (m_spawnId) - GetMap()->RemoveCreatureRespawnTime(m_spawnId); + DestroyForNearbyPlayers(); + RemoveCorpse(false, false); - TC_LOG_DEBUG("entities.unit", "Respawning creature %s (%s)", - GetName().c_str(), GetGUID().ToString().c_str()); - m_respawnTime = 0; - ResetPickPocketRefillTimer(); - loot.clear(); + if (getDeathState() == DEAD) + { + if (m_spawnId) + GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, m_spawnId); - if (m_originalEntry != GetEntry()) - UpdateEntry(m_originalEntry); + TC_LOG_DEBUG("entities.unit", "Respawning creature %s (%s)", GetName().c_str(), GetGUID().ToString().c_str()); + m_respawnTime = 0; + ResetPickPocketRefillTimer(); + loot.clear(); - SelectLevel(); + if (m_originalEntry != GetEntry()) + UpdateEntry(m_originalEntry); - setDeathState(JUST_RESPAWNED); + SelectLevel(); - uint32 displayID = GetNativeDisplayId(); - if (sObjectMgr->GetCreatureModelRandomGender(&displayID)) - { - SetDisplayId(displayID); - SetNativeDisplayId(displayID); - } + setDeathState(JUST_RESPAWNED); - GetMotionMaster()->InitDefault(); - //Re-initialize reactstate that could be altered by movementgenerators - InitializeReactState(); + uint32 displayID = GetNativeDisplayId(); + if (sObjectMgr->GetCreatureModelRandomGender(&displayID)) + { + SetDisplayId(displayID); + SetNativeDisplayId(displayID); + } - //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 - } + GetMotionMaster()->InitDefault(); + //Re-initialize reactstate that could be altered by movementgenerators + InitializeReactState(); + + //Call AI respawn virtual function//Call AI respawn virtual function + if (IsAIEnabled) + { + //reset the AI to be sure no dirty or uninitialized values will be used till next tick + AI()->Reset(); + m_TriggerJustRespawned = true;//delay event to next tick so all creatures are created on the map before processing + } - uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool(GetSpawnId()) : 0; - if (poolid) - sPoolMgr->UpdatePool(poolid, GetSpawnId()); + uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool(GetSpawnId()) : 0; + if (poolid) + sPoolMgr->UpdatePool(poolid, GetSpawnId()); + } + UpdateObjectVisibility(); } + else + { + if (m_spawnId) + GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, m_spawnId, true); + } + + TC_LOG_DEBUG("entities.unit", "Respawning creature %s (%s)", + GetName().c_str(), GetGUID().ToString().c_str()); - UpdateObjectVisibility(); } void Creature::ForcedDespawn(uint32 timeMSToDespawn, Seconds const& forceRespawnTimer) @@ -1875,30 +1931,48 @@ void Creature::ForcedDespawn(uint32 timeMSToDespawn, Seconds const& forceRespawn return; } - uint32 corpseDelay = GetCorpseDelay(); - uint32 respawnDelay = GetRespawnDelay(); + if (m_respawnCompatibilityMode) + { + uint32 corpseDelay = GetCorpseDelay(); + uint32 respawnDelay = GetRespawnDelay(); + + // do it before killing creature + DestroyForNearbyPlayers(); + + bool overrideRespawnTime = false; + if (IsAlive()) + { + if (forceRespawnTimer > Seconds::zero()) + { + SetCorpseDelay(0); + SetRespawnDelay(forceRespawnTimer.count()); + overrideRespawnTime = true; + } + + setDeathState(JUST_DIED); + } - // do it before killing creature - DestroyForNearbyPlayers(); + // Skip corpse decay time + RemoveCorpse(!overrideRespawnTime, false); - bool overrideRespawnTime = true; - if (IsAlive()) + SetCorpseDelay(corpseDelay); + SetRespawnDelay(respawnDelay); + } + else { if (forceRespawnTimer > Seconds::zero()) + SaveRespawnTime(forceRespawnTimer.count()); + else { - SetCorpseDelay(0); - SetRespawnDelay(forceRespawnTimer.count()); - overrideRespawnTime = false; + uint32 respawnDelay = m_respawnDelay; + if (uint32 scalingMode = sWorld->getIntConfig(CONFIG_RESPAWN_DYNAMICMODE)) + GetMap()->ApplyDynamicModeRespawnScaling(this, m_spawnId, respawnDelay, scalingMode); + m_respawnTime = time(NULL) + respawnDelay; + SaveRespawnTime(); } - setDeathState(JUST_DIED); + AddObjectToRemoveList(); } - - // Skip corpse decay time - RemoveCorpse(overrideRespawnTime, false); - - SetCorpseDelay(corpseDelay); - SetRespawnDelay(respawnDelay); } void Creature::DespawnOrUnsummon(uint32 msTimeToDespawn /*= 0*/, Seconds const& forceRespawnTimer /*= 0*/) @@ -2245,12 +2319,19 @@ bool Creature::_IsTargetAcceptable(Unit const* target) const return false; } -void Creature::SaveRespawnTime() +void Creature::SaveRespawnTime(uint32 forceDelay, bool savetodb) { if (IsSummon() || !m_spawnId || (m_creatureData && !m_creatureData->dbData)) return; - GetMap()->SaveCreatureRespawnTime(m_spawnId, m_respawnTime); + if (m_respawnCompatibilityMode) + { + GetMap()->SaveRespawnTimeDB(SPAWN_TYPE_CREATURE, m_spawnId, m_respawnTime); + return; + } + + uint32 thisRespawnTime = forceDelay ? time(NULL) + forceDelay : m_respawnTime; + GetMap()->SaveRespawnTime(SPAWN_TYPE_CREATURE, m_spawnId, GetEntry(), thisRespawnTime, GetMap()->GetZoneId(GetHomePosition()), Trinity::ComputeGridCoord(GetHomePosition().GetPositionX(), GetHomePosition().GetPositionY()).GetId(), m_creatureData->dbData && savetodb); } // this should not be called by petAI or @@ -2462,33 +2543,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) { - // for npcs on transport, this will return transport offset - 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; } - - // changed this from current position to home position, fixes world summons with infinite duration (wg npcs for example) - Position homePos = GetHomePosition(); - x = homePos.GetPositionX(); - y = homePos.GetPositionY(); - z = homePos.GetPositionZ(); - if (ori) - *ori = homePos.GetOrientation(); - - if (dist) - *dist = 0; } void Creature::AllLootRemovedFromCorpse() @@ -2539,7 +2613,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; @@ -3082,3 +3156,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 cc12efbd7ebb0..8de86af1dff65 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(); @@ -83,7 +83,7 @@ class TC_GAME_API Creature : public Unit, public GridObject, public Ma void Update(uint32 time) override; // overwrited Unit::Update void GetRespawnPosition(float &x, float &y, float &z, float* ori = nullptr, float* dist = nullptr) const; - bool IsSpawnedOnTransport() const { return m_creatureData && m_creatureData->mapid != GetMapId(); } + bool IsSpawnedOnTransport() const { return m_creatureData && m_creatureData->spawnPoint.GetMapId() != GetMapId(); } void SetCorpseDelay(uint32 delay) { m_corpseDelay = delay; } uint32 GetCorpseDelay() const { return m_corpseDelay; } @@ -176,8 +176,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); @@ -239,7 +238,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; } @@ -313,6 +312,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; @@ -336,6 +339,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; @@ -404,6 +408,7 @@ class TC_GAME_API Creature : public Unit, public GridObject, public Ma //Formation var CreatureGroup* m_formation; bool m_TriggerJustRespawned; + bool m_respawnCompatibilityMode; /* Spell focus system */ Spell const* m_focusSpell; // Locks the target during spell cast for proper facing diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index 754963f9812b7..de169c9713664 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 @@ -235,33 +236,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), 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; - 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/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index 6ae82ea3cebe3..3717b16280ef3 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -101,7 +101,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; @@ -240,7 +240,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); @@ -253,6 +253,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(); + SetPhaseMask(phaseMask, false); UpdatePositionData(); @@ -376,6 +380,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(); @@ -487,83 +494,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; - } + ObjectGuid dbtableHighGuid(HighGuid::GameObject, GetEntry(), m_spawnId); + time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid); + if (linkedRespawntime) // Can't respawn, the master is dead + { + ObjectGuid targetGuid = sObjectMgr->GetLinkedRespawnGuid(dbtableHighGuid); + if (targetGuid == dbtableHighGuid) // if linking self, never respawn + SetRespawnTime(WEEK); + else + m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime) + urand(5, MINUTE); // else copy time from master and add a little + SaveRespawnTime(); // also save to DB immediately + return; + } - m_respawnTime = 0; - m_SkillupList.clear(); - m_usetimes = 0; + m_respawnTime = 0; + m_SkillupList.clear(); + m_usetimes = 0; - // If nearby linked trap exists, respawn it - if (GameObject* linkedTrap = GetLinkedTrap()) - linkedTrap->SetLootState(GO_READY); + // If nearby linked trap exists, respawn it + if (GameObject* linkedTrap = GetLinkedTrap()) + linkedTrap->SetLootState(GO_READY); - switch (GetGoType()) - { - case GAMEOBJECT_TYPE_FISHINGNODE: // can't fish now + switch (GetGoType()) { - Unit* caster = GetOwner(); - if (caster && caster->GetTypeId() == TYPEID_PLAYER) + case GAMEOBJECT_TYPE_FISHINGNODE: // can't fish now { - caster->ToPlayer()->RemoveGameObject(this, false); - - WorldPacket data(SMSG_FISH_ESCAPED, 0); - caster->ToPlayer()->SendDirectMessage(&data); + 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; } - // 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; } - 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; - } + // Despawn timer + if (!m_spawnedByDefault) + { + // Can be despawned or destroyed + SetLootState(GO_JUST_DEACTIVATED); + return; + } - // Call AI Reset (required for example in SmartAI to clear one time events) - if (AI()) - AI()->Reset(); + // Call AI Reset (required for example in SmartAI to clear one time events) + if (AI()) + AI()->Reset(); - // Respawn timer - uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool(GetSpawnId()) : 0; - if (poolid) - sPoolMgr->UpdatePool(poolid, GetSpawnId()); - else - GetMap()->AddToMap(this); + // 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(); @@ -751,6 +765,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; @@ -758,12 +773,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; } @@ -849,7 +880,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!"); @@ -869,22 +900,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(); @@ -917,9 +948,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) { @@ -930,14 +961,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; if (data->spawntimesecs >= 0) @@ -959,7 +990,7 @@ bool GameObject::LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, boo if (m_respawnTime && m_respawnTime <= time(nullptr)) { m_respawnTime = 0; - GetMap()->RemoveGORespawnTime(m_spawnId); + GetMap()->RemoveRespawnTime(SPAWN_TYPE_GAMEOBJECT, m_spawnId); } } } @@ -980,20 +1011,25 @@ bool GameObject::LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, boo void GameObject::DeleteFromDB() { - GetMap()->RemoveGORespawnTime(m_spawnId); - sObjectMgr->DeleteGOData(m_spawnId); + GetMap()->RemoveRespawnTime(SPAWN_TYPE_GAMEOBJECT, m_spawnId); + sObjectMgr->DeleteGameObjectData(m_spawnId); - PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT); + SQLTransaction trans = WorldDatabase.BeginTransaction(); + PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT); stmt->setUInt32(0, m_spawnId); + trans->Append(stmt); - 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); - - WorldDatabase.Execute(stmt); + trans->Append(stmt); + + WorldDatabase.CommitTransaction(trans); } /*********************************************************/ @@ -1056,10 +1092,19 @@ Unit* GameObject::GetOwner() const return ObjectAccessor::GetUnit(*this, GetOwnerGUID()); } -void GameObject::SaveRespawnTime() +void GameObject::SaveRespawnTime(uint32 forceDelay, bool savetodb) { - if (m_goData && m_goData->dbData && m_respawnTime > time(nullptr) && m_spawnedByDefault) - GetMap()->SaveGORespawnTime(m_spawnId, m_respawnTime); + if (m_goData && m_respawnTime > time(nullptr) && m_spawnedByDefault) + { + if (m_respawnCompatibilityMode) + { + GetMap()->SaveRespawnTimeDB(SPAWN_TYPE_GAMEOBJECT, m_spawnId, m_respawnTime); + return; + } + + uint32 thisRespawnTime = forceDelay ? time(nullptr) + forceDelay : m_respawnTime; + GetMap()->SaveRespawnTime(SPAWN_TYPE_GAMEOBJECT, m_spawnId, GetEntry(), thisRespawnTime, GetZoneId(), Trinity::ComputeGridCoord(GetPositionX(), GetPositionY()).GetId(), m_goData->dbData ? savetodb : false); + } } bool GameObject::IsNeverVisible() const @@ -1124,7 +1169,7 @@ void GameObject::Respawn() if (m_spawnedByDefault && m_respawnTime > 0) { m_respawnTime = time(nullptr); - GetMap()->RemoveGORespawnTime(m_spawnId); + GetMap()->RemoveRespawnTime(SPAWN_TYPE_GAMEOBJECT, m_spawnId, true); } } @@ -1228,7 +1273,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; } @@ -1239,10 +1284,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; @@ -1972,8 +2017,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; @@ -2411,24 +2456,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 diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h index 3080bd94860ae..784af8fda2717 100644 --- a/src/server/game/Entities/GameObject/GameObject.h +++ b/src/server/game/Entities/GameObject/GameObject.h @@ -89,11 +89,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; @@ -113,8 +113,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) @@ -212,7 +211,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; @@ -264,6 +263,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; } + uint32 GetScriptId() const; GameObjectAI* AI() const { return m_AI; } @@ -344,5 +347,6 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject return IsInRange(obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ(), dist2compare); } 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 e2d2f27e5b547..6e1a07786f983 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 @@ -593,26 +594,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), 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; - 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 508a89a86c813..d7dc720f55fc0 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -1908,7 +1908,7 @@ TempSummon* Map::SummonCreature(uint32 entry, Position const& pos, SummonPropert break; } - if (!summon->Create(GenerateLowGuid(), this, phase, entry, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), nullptr, vehId)) + if (!summon->Create(GenerateLowGuid(), this, phase, entry, pos, nullptr, vehId)) { delete summon; return nullptr; diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index ca1b547f68bf5..2e0bef23916cc 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -342,7 +342,7 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation void SendObjectDeSpawnAnim(ObjectGuid guid); - virtual void SaveRespawnTime() { } + virtual void SaveRespawnTime(uint32 /*forceDelay*/ = 0, bool /*saveToDB*/ = true) { } void AddObjectToRemoveList(); float GetGridActivationRange() const; diff --git a/src/server/game/Entities/Object/ObjectGuid.cpp b/src/server/game/Entities/Object/ObjectGuid.cpp index 3a26dfd0d7f8f..71837474ca95f 100644 --- a/src/server/game/Entities/Object/ObjectGuid.cpp +++ b/src/server/game/Entities/Object/ObjectGuid.cpp @@ -98,6 +98,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 75410aa4f2883..d1e34d3a38cd8 100644 --- a/src/server/game/Entities/Object/ObjectGuid.h +++ b/src/server/game/Entities/Object/ObjectGuid.h @@ -287,6 +287,7 @@ class TC_GAME_API ObjectGuidGeneratorBase protected: static void HandleCounterOverflow(HighGuid high); + static void CheckGuidTrigger(ObjectGuid::LowType guid); ObjectGuid::LowType _nextGuid; }; @@ -300,6 +301,10 @@ class TC_GAME_API 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 98cb82df2e146..11867c6fed6e0 100644 --- a/src/server/game/Entities/Object/Position.h +++ b/src/server/game/Entities/Object/Position.h @@ -207,15 +207,9 @@ struct TC_GAME_API Position 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(Position const& center, float xradius, float yradius, float zradius) const; @@ -245,15 +239,12 @@ class WorldLocation : public Position WorldLocation(WorldLocation const& loc) : Position(loc), m_mapId(loc.GetMapId()) { } - void WorldRelocate(WorldLocation const& loc) - { - 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) + void WorldRelocate(WorldLocation const& loc) { m_mapId = loc.GetMapId(); Relocate(loc); } + void WorldRelocate(WorldLocation const* loc) { m_mapId = loc->GetMapId(); Relocate(loc); } + void WorldRelocate(uint32 mapId, Position const& pos) { m_mapId = mapId; Relocate(pos); } + void WorldRelocate(uint32 mapId = MAPID_INVALID, float x = 0.f, float y = 0.f, float z = 0.f, float o = 0.f) { - m_mapId = _mapId; + m_mapId = mapId; Relocate(x, y, z, o); } diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 9d9323e8d3fdf..c445f53190c84 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -7006,6 +7006,8 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea) guild->UpdateMemberData(this, GUILD_MEMBER_DATA_ZONEID, newZone); } + GetMap()->UpdatePlayerZoneStats(m_zoneUpdateId, newZone); + // group update if (GetGroup()) SetGroupUpdateFlag(GROUP_UPDATE_FULL); diff --git a/src/server/game/Entities/Transport/Transport.cpp b/src/server/game/Entities/Transport/Transport.cpp index 6d36095ee7ddb..2b9b82b392bf0 100644 --- a/src/server/game/Entities/Transport/Transport.cpp +++ b/src/server/game/Entities/Transport/Transport.cpp @@ -302,16 +302,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->AddUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT); @@ -349,7 +347,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; @@ -357,10 +355,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(); @@ -468,7 +464,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, phase, entry, x, y, z, o, nullptr, vehId)) + if (!summon->Create(map->GenerateLowGuid(), map, phase, 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 fc00ce5ee9e33..307f5674b9cb6 100644 --- a/src/server/game/Events/GameEventMgr.cpp +++ b/src/server/game/Events/GameEventMgr.cpp @@ -436,7 +436,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); @@ -1140,7 +1140,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) { @@ -1203,13 +1203,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; } } @@ -1225,19 +1225,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 { @@ -1280,7 +1280,7 @@ void GameEventMgr::GameEventUnspawn(int16 event_id) { sObjectMgr->RemoveCreatureFromGrid(*itr, data); - sMapMgr->DoForAllMapsWithMapId(data->mapid, [&itr](Map* map) + sMapMgr->DoForAllMapsWithMapId(data->spawnPoint.GetMapId(), [&itr](Map* map) { auto creatureBounds = map->GetCreatureBySpawnIdStore().equal_range(*itr); for (auto itr2 = creatureBounds.first; itr2 != creatureBounds.second;) @@ -1306,11 +1306,11 @@ void GameEventMgr::GameEventUnspawn(int16 event_id) if (event_id >0 && hasGameObjectActiveEventExcept(*itr, event_id)) continue; // Remove the gameobject from grid - if (GameObjectData const* data = sObjectMgr->GetGOData(*itr)) + if (GameObjectData const* data = sObjectMgr->GetGameObjectData(*itr)) { sObjectMgr->RemoveGameobjectFromGrid(*itr, data); - sMapMgr->DoForAllMapsWithMapId(data->mapid, [&itr](Map* map) + sMapMgr->DoForAllMapsWithMapId(data->spawnPoint.GetMapId(), [&itr](Map* map) { auto gameobjectBounds = map->GetGameObjectBySpawnIdStore().equal_range(*itr); for (auto itr2 = gameobjectBounds.first; itr2 != gameobjectBounds.second;) @@ -1344,7 +1344,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); diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index c6cb3c06b781e..451cd84721363 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -1123,7 +1123,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); @@ -1461,8 +1461,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; @@ -1490,7 +1490,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); @@ -1498,8 +1498,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; @@ -1519,7 +1519,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); @@ -1527,7 +1527,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); @@ -1535,8 +1535,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; @@ -1556,7 +1556,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); @@ -1572,8 +1572,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; @@ -1626,8 +1626,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; @@ -1741,8 +1741,8 @@ void ObjectMgr::LoadCreatures() { uint32 oldMSTime = getMSTime(); - // 0 1 2 3 4 5 6 7 8 9 10 - QueryResult result = WorldDatabase.Query("SELECT creature.guid, id, map, modelid, equipment_id, position_x, position_y, position_z, orientation, spawntimesecs, spawndist, " + // 0 1 2 3 4 5 6 7 8 9 10 + QueryResult result = WorldDatabase.Query("SELECT creature.guid, id, map, position_x, position_y, position_z, orientation, modelid, equipment_id, spawntimesecs, spawndist, " // 11 12 13 14 15 16 17 18 19 20 21 "currentwaypoint, curhealth, curmana, MovementType, spawnMask, phaseMask, eventEntry, pool_entry, creature.npcflag, creature.unit_flags, creature.dynamicflags, " // 22 @@ -1782,14 +1782,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(); @@ -1803,18 +1800,19 @@ void ObjectMgr::LoadCreatures() data.npcflag = fields[19].GetUInt32(); data.unit_flags = fields[20].GetUInt32(); data.dynamicflags = fields[21].GetUInt32(); - data.ScriptId = GetScriptId(fields[22].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) @@ -1879,17 +1877,11 @@ void ObjectMgr::LoadCreatures() data.phaseMask = 1; } - 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 (sWorld->getBoolConfig(CONFIG_CALCULATE_CREATURE_ZONE_AREA_DATA)) { uint32 zoneId = 0; uint32 areaId = 0; - sMapMgr->GetZoneAndAreaId(zoneId, areaId, data.mapid, data.posX, data.posY, data.posZ); + sMapMgr->GetZoneAndAreaId(zoneId, areaId, data.spawnPoint); PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_CREATURE_ZONE_AREA_DATA); @@ -1916,8 +1908,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); } } @@ -1930,14 +1922,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) @@ -1947,41 +1939,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); + GameObjectData& data = NewOrExistGameObjectData(spawnId); + data.spawnId = spawnId; data.id = entry; - data.mapid = mapId; - - pos.GetPosition(data.posX, data.posY, data.posZ, data.orientation); - + data.spawnPoint.WorldRelocate(mapId,pos); data.rotation = rot; data.spawntimesecs = spawntimedelay; data.animprogress = 100; data.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; + data.spawnGroupData = GetLegacySpawnGroup(); - AddGameobjectToGrid(guid, &data); + AddGameobjectToGrid(spawnId, &data); // Spawn if necessary (loaded grids only) // We use spawn coords to spawn - if (!map->Instanceable() && map->IsGridLoaded(data.posX, data.posY)) + if (!map->Instanceable() && map->IsGridLoaded(data.spawnPoint)) { GameObject* go = 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; } @@ -1997,15 +1988,13 @@ 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; @@ -2018,14 +2007,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; @@ -2033,10 +2023,10 @@ ObjectGuid::LowType ObjectMgr::AddCreatureData(uint32 entry, uint32 mapId, Posit } } - return guid; + return spawnId; } -void ObjectMgr::LoadGameobjects() +void ObjectMgr::LoadGameObjects() { uint32 oldMSTime = getMSTime(); @@ -2100,22 +2090,20 @@ void ObjectMgr::LoadGameobjects() GameObjectData& data = _gameObjectDataStore[guid]; + data.spawnId = guid; data.id = entry; - data.mapid = fields[2].GetUInt16(); - data.posX = fields[3].GetFloat(); - data.posY = fields[4].GetFloat(); - data.posZ = fields[5].GetFloat(); - data.orientation = fields[6].GetFloat(); + data.spawnPoint.WorldRelocate(fields[2].GetUInt16(), fields[3].GetFloat(), fields[4].GetFloat(), fields[5].GetFloat(), fields[6].GetFloat()); data.rotation.x = fields[7].GetFloat(); data.rotation.y = fields[8].GetFloat(); data.rotation.z = fields[9].GetFloat(); data.rotation.w = fields[10].GetFloat(); data.spawntimesecs = fields[11].GetInt32(); + data.spawnGroupData = &_spawnGroupDataStore[0]; - MapEntry const* mapEntry = sMapStore.LookupEntry(data.mapid); + MapEntry const* mapEntry = sMapStore.LookupEntry(data.spawnPoint.GetMapId()); if (!mapEntry) { - TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %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; } @@ -2133,24 +2121,18 @@ void ObjectMgr::LoadGameobjects() TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) with invalid `state` (%u) value, skip", guid, data.id, go_state); 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(); uint32 PoolId = fields[17].GetUInt32(); - data.ScriptId = GetScriptId(fields[18].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[18].GetString()); if (data.rotation.x < -1.0f || data.rotation.x > 1.0f) { @@ -2176,7 +2158,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; @@ -2192,7 +2174,7 @@ void ObjectMgr::LoadGameobjects() { uint32 zoneId = 0; uint32 areaId = 0; - sMapMgr->GetZoneAndAreaId(zoneId, areaId, data.mapid, data.posX, data.posY, data.posZ); + sMapMgr->GetZoneAndAreaId(zoneId, areaId, data.spawnPoint); PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_GAMEOBJECT_ZONE_AREA_DATA); @@ -2211,6 +2193,153 @@ void ObjectMgr::LoadGameobjects() TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " gameobjects in %u ms", _gameObjectDataStore.size(), GetMSTimeDiffToNow(oldMSTime)); } +void ObjectMgr::LoadSpawnGroupTemplates() +{ + uint32 oldMSTime = getMSTime(); + + // 0 1 2 + QueryResult result = WorldDatabase.Query("SELECT groupId, groupName, groupFlags FROM spawn_group_template"); + + if (result) + { + do + { + Field* fields = result->Fetch(); + uint32 groupId = fields[0].GetUInt32(); + SpawnGroupTemplateData& group = _spawnGroupDataStore[groupId]; + group.groupId = groupId; + group.name = fields[1].GetString(); + group.mapId = SPAWNGROUP_MAP_UNSET; + uint32 flags = fields[2].GetUInt32(); + if (flags & ~SPAWNGROUP_FLAGS_ALL) + { + flags &= SPAWNGROUP_FLAGS_ALL; + TC_LOG_ERROR("server.loading", "Invalid spawn group flag %u on group ID %u (%s), reduced to valid flag %u.", flags, groupId, group.name.c_str(), uint32(group.flags)); + } + if (flags & SPAWNGROUP_FLAG_SYSTEM && flags & SPAWNGROUP_FLAG_MANUAL_SPAWN) + { + flags &= ~SPAWNGROUP_FLAG_MANUAL_SPAWN; + TC_LOG_ERROR("server.loading", "System spawn group %u (%s) has invalid manual spawn flag. Ignored.", groupId, group.name.c_str()); + } + group.flags = SpawnGroupFlags(flags); + group.isActive = !(group.flags & SPAWNGROUP_FLAG_MANUAL_SPAWN); + } while (result->NextRow()); + } + + if (_spawnGroupDataStore.find(0) == _spawnGroupDataStore.end()) + { + TC_LOG_ERROR("server.loading", "Default spawn group (index 0) is missing from DB! Manually inserted."); + SpawnGroupTemplateData& data = _spawnGroupDataStore[0]; + data.groupId = 0; + data.name = "Default Group"; + data.mapId = 0; + data.flags = SPAWNGROUP_FLAG_SYSTEM; + data.isActive = true; + } + if (_spawnGroupDataStore.find(1) == _spawnGroupDataStore.end()) + { + TC_LOG_ERROR("server.loading", "Default legacy spawn group (index 1) is missing from DB! Manually inserted."); + SpawnGroupTemplateData&data = _spawnGroupDataStore[1]; + data.groupId = 1; + data.name = "Legacy Group"; + data.mapId = 0; + data.flags = SpawnGroupFlags(SPAWNGROUP_FLAG_SYSTEM | SPAWNGROUP_FLAG_COMPATIBILITY_MODE); + data.isActive = true; + } + + if (result) + TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " spawn group templates in %u ms", _spawnGroupDataStore.size(), GetMSTimeDiffToNow(oldMSTime)); + else + TC_LOG_ERROR("server.loading", ">> Loaded 0 spawn group templates. DB table `spawn_group_template` is empty."); + + return; +} + +void ObjectMgr::LoadSpawnGroups() +{ + uint32 oldMSTime = getMSTime(); + + // 0 1 2 + QueryResult result = WorldDatabase.Query("SELECT groupId, spawnType, spawnId FROM spawn_group"); + + if (!result) + { + TC_LOG_ERROR("server.loading", ">> Loaded 0 spawn group members. DB table `spawn_group` is empty."); + return; + } + + uint32 numMembers = 0; + do + { + Field* fields = result->Fetch(); + uint32 groupId = fields[0].GetUInt32(); + SpawnObjectType spawnType; + { + uint32 type = fields[1].GetUInt8(); + if (type >= SPAWN_TYPE_MAX) + { + TC_LOG_ERROR("server.loading", "Spawn data with invalid type %u listed for spawn group %u. Skipped.", type, groupId); + continue; + } + spawnType = SpawnObjectType(type); + } + ObjectGuid::LowType spawnId = fields[2].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::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; @@ -2218,8 +2347,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); } } @@ -2232,8 +2361,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); } } @@ -4881,7 +5010,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", @@ -4931,7 +5060,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", @@ -6586,12 +6715,142 @@ uint32 ObjectMgr::GenerateGameObjectSpawnId() { if (_gameObjectSpawnId >= uint32(0xFFFFFF)) { - TC_LOG_ERROR("misc", "Creature spawn id overflow!! Can't continue, shutting down server. Search on forum for TCE00007 for more info. "); + TC_LOG_ERROR("misc", "GameObject spawn id overflow!! Can't continue, shutting down server. Search on forum for TCE00007 for more info. "); World::StopNow(ERROR_EXIT_CODE); } return _gameObjectSpawnId++; } +bool ObjectMgr::SpawnGroupSpawn(uint32 groupId, Map* map, bool ignoreRespawn, bool force, std::vector* spawnedObjects) +{ + auto itr = _spawnGroupDataStore.find(groupId); + if (itr == _spawnGroupDataStore.end() || itr->second.flags & SPAWNGROUP_FLAG_SYSTEM) + { + TC_LOG_ERROR("maps", "Tried to despawn non-existing (or system) spawn group %u. Blocked.", groupId); + return false; + } + + if (!map) + { + TC_LOG_ERROR("maps", "Tried to despawn creature group %u, but no map was supplied. Blocked.", groupId); + return false; + } + + if (itr->second.mapId != map->GetId()) + { + TC_LOG_ERROR("maps", "Tried to despawn creature group %u, but supplied map is %u, creature group has map %u. Blocked.", groupId, map->GetId(), itr->second.mapId); + return false; + } + + for (auto& pair : GetSpawnDataForGroup(groupId)) + { + SpawnData const* data = pair.second; + ASSERT(itr->second.mapId == data->spawnPoint.GetMapId()); + // Check if there's already an instance spawned + if (!force) + if (WorldObject* obj = map->GetWorldObjectBySpawnId(data->type, data->spawnId)) + if ((data->type != SPAWN_TYPE_CREATURE) || obj->ToCreature()->IsAlive()) + continue; + + time_t respawnTime = map->GetRespawnTime(data->type, data->spawnId); + if (respawnTime && respawnTime > time(NULL)) + { + if (!force && !ignoreRespawn) + continue; + + // we need to remove the respawn time, otherwise we'd end up double spawning + map->RemoveRespawnTime(data->type, data->spawnId, false); + } + + // don't spawn if the grid isn't loaded (will be handled in grid loader) + if (!map->IsGridLoaded(data->spawnPoint)) + continue; + + // Everything OK, now do the actual (re)spawn + switch (data->type) + { + case SPAWN_TYPE_CREATURE: + { + Creature* creature = new Creature(); + if (!creature->LoadFromDB(data->spawnId, map, true, force)) + delete creature; + else if (spawnedObjects) + spawnedObjects->push_back(creature); + break; + } + case SPAWN_TYPE_GAMEOBJECT: + { + GameObject* gameobject = new GameObject(); + if (!gameobject->LoadFromDB(data->spawnId, map, true)) + delete gameobject; + else if (spawnedObjects) + spawnedObjects->push_back(gameobject); + break; + } + default: + ASSERT(false, "Invalid spawn type %u with spawnId %u", uint32(data->type), data->spawnId); + return false; + } + } + itr->second.isActive = true; // start processing respawns for the group + return true; +} + +bool ObjectMgr::SpawnGroupDespawn(uint32 groupId, Map* map, bool deleteRespawnTimes) +{ + auto itr = _spawnGroupDataStore.find(groupId); + if (itr == _spawnGroupDataStore.end() || itr->second.flags & SPAWNGROUP_FLAG_SYSTEM) + { + TC_LOG_ERROR("maps", "Tried to despawn non-existing (or system) spawn group %u. Blocked.", groupId); + return false; + } + + if (!map) + { + TC_LOG_ERROR("maps", "Tried to despawn creature group %u, but no map was supplied. Blocked.", groupId); + return false; + } + + if (itr->second.mapId != map->GetId()) + { + TC_LOG_ERROR("maps", "Tried to despawn creature group %u, but supplied map is %u, creature group has map %u. Blocked.", groupId, map->GetId(), itr->second.mapId); + return false; + } + + std::vector toUnload; // unload after iterating, otherwise iterator invalidation + for (auto const& pair : GetSpawnDataForGroup(groupId)) + { + SpawnData const* data = pair.second; + if (deleteRespawnTimes) + map->RemoveRespawnTime(data->type, data->spawnId); + switch (data->type) + { + case SPAWN_TYPE_CREATURE: + { + auto bounds = map->GetCreatureBySpawnIdStore().equal_range(data->spawnId); + for (auto it = bounds.first; it != bounds.second; ++it) + toUnload.emplace_back(it->second); + break; + } + case SPAWN_TYPE_GAMEOBJECT: + { + auto bounds = map->GetGameObjectBySpawnIdStore().equal_range(data->spawnId); + for (auto it = bounds.first; it != bounds.second; ++it) + toUnload.emplace_back(it->second); + break; + } + default: + ASSERT(false, "Invalid spawn type %u in spawn data with spawnId %u.", uint32(data->type), data->spawnId); + return false; + } + } + // now do the actual despawning + for (WorldObject* obj : toUnload) + obj->AddObjectToRemoveList(); + itr->second.isActive = false; // stop processing respawns for the group, too + return true; +} + void ObjectMgr::LoadGameObjectLocales() { uint32 oldMSTime = getMSTime(); @@ -7497,17 +7756,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 95e7019dacfa6..7c28b21ebeafe 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -23,8 +23,10 @@ #include "ConditionMgr.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" @@ -38,6 +40,7 @@ class Item; class Unit; class Vehicle; +class Map; struct AccessRequirement; struct DeclinedName; struct DungeonEncounterEntry; @@ -529,6 +532,8 @@ 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::map> TempSummonDataContainer; typedef std::unordered_map CreatureLocaleContainer; typedef std::unordered_map GameObjectLocaleContainer; @@ -803,6 +808,7 @@ SkillRangeType GetSkillRangeType(SkillRaceClassInfoEntry const* rcEntry); #define MAX_CHARTER_NAME 24 // max allowed by client name length TC_GAME_API bool normalizePlayerName(std::string& name); +#define SPAWNGROUP_MAP_UNSET 0xFFFFFFFF struct LanguageDesc { @@ -1120,7 +1126,9 @@ class TC_GAME_API ObjectMgr void LoadCreatureModelInfo(); void LoadEquipmentTemplates(); void LoadGameObjectLocales(); - void LoadGameobjects(); + void LoadGameObjects(); + void LoadSpawnGroupTemplates(); + void LoadSpawnGroups(); void LoadItemTemplates(); void LoadItemLocales(); void LoadItemSetNames(); @@ -1202,8 +1210,16 @@ class TC_GAME_API ObjectMgr uint64 GenerateEquipmentSetGuid(); uint32 GenerateMailID(); uint32 GeneratePetNumber(); - uint32 GenerateCreatureSpawnId(); - uint32 GenerateGameObjectSpawnId(); + ObjectGuid::LowType GenerateCreatureSpawnId(); + ObjectGuid::LowType GenerateGameObjectSpawnId(); + + bool SpawnGroupSpawn(uint32 groupId, Map* map, bool ignoreRespawn = false, bool force = false, std::vector* spawnedObjects = nullptr); + bool SpawnGroupDespawn(uint32 groupId, Map* map, bool deleteRespawnTimes = false); + void SetSpawnGroupActive(uint32 groupId, bool state) { auto it = _spawnGroupDataStore.find(groupId); if (it != _spawnGroupDataStore.end()) it->second.isActive = state; } + bool IsSpawnGroupActive(uint32 groupId) const { auto it = _spawnGroupDataStore.find(groupId); return (it != _spawnGroupDataStore.end()) && it->second.isActive; } + SpawnGroupTemplateData const* GetDefaultSpawnGroup() const { return &_spawnGroupDataStore.at(0); } + SpawnGroupTemplateData const* GetLegacySpawnGroup() const { return &_spawnGroupDataStore.at(1); } + Trinity::IteratorPair GetSpawnDataForGroup(uint32 groupId) const { return Trinity::Containers::MapEqualRange(_spawnGroupMapStore, groupId); } MailLevelReward const* GetMailLevelReward(uint32 level, uint32 raceMask) const { @@ -1254,6 +1270,17 @@ class TC_GAME_API ObjectMgr return nullptr; } + SpawnData const* GetSpawnData(SpawnObjectType type, ObjectGuid::LowType guid) + { + if (type == SPAWN_TYPE_CREATURE) + return GetCreatureData(guid); + else if (type == SPAWN_TYPE_GAMEOBJECT) + return GetGameObjectData(guid); + else + ASSERT(false, "Invalid spawn object type %u", uint32(type)); + return nullptr; + } + void OnDeleteSpawnData(SpawnData const* data); CreatureData const* GetCreatureData(ObjectGuid::LowType guid) const { CreatureDataContainer::const_iterator itr = _creatureDataStore.find(guid); @@ -1274,6 +1301,14 @@ class TC_GAME_API ObjectMgr if (itr == _creatureLocaleStore.end()) return nullptr; return &itr->second; } + GameObjectData const* GetGameObjectData(ObjectGuid::LowType guid) const + { + GameObjectDataContainer::const_iterator itr = _gameObjectDataStore.find(guid); + if (itr == _gameObjectDataStore.end()) return nullptr; + return &itr->second; + } + GameObjectData& NewOrExistGameObjectData(ObjectGuid::LowType guid) { return _gameObjectDataStore[guid]; } + void DeleteGameObjectData(ObjectGuid::LowType guid); GameObjectLocale const* GetGameObjectLocale(uint32 entry) const { GameObjectLocaleContainer::const_iterator itr = _gameObjectLocaleStore.find(entry); @@ -1323,15 +1358,6 @@ class TC_GAME_API ObjectMgr return &itr->second; } - GameObjectData const* GetGOData(ObjectGuid::LowType guid) const - { - GameObjectDataContainer::const_iterator itr = _gameObjectDataStore.find(guid); - if (itr == _gameObjectDataStore.end()) return nullptr; - return &itr->second; - } - GameObjectData& NewGOData(ObjectGuid::LowType guid) { return _gameObjectDataStore[guid]; } - void DeleteGOData(ObjectGuid::LowType guid); - TrinityString const* GetTrinityString(uint32 entry) const { TrinityStringContainer::const_iterator itr = _trinityStringStore.find(entry); @@ -1349,7 +1375,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 @@ -1458,8 +1484,8 @@ class TC_GAME_API ObjectMgr std::atomic _mailId; std::atomic _hiPetNumber; - uint32 _creatureSpawnId; - uint32 _gameObjectSpawnId; + ObjectGuid::LowType _creatureSpawnId; + ObjectGuid::LowType _gameObjectSpawnId; // first free low guid for selected guid type template @@ -1579,6 +1605,8 @@ class TC_GAME_API ObjectMgr GameObjectLocaleContainer _gameObjectLocaleStore; GameObjectTemplateContainer _gameObjectTemplateStore; GameObjectTemplateAddonContainer _gameObjectTemplateAddonStore; + SpawnGroupDataContainer _spawnGroupDataStore; + SpawnGroupLinkContainer _spawnGroupMapStore; /// Stores temp summon data grouped by summoner's entry, summoner's type and group id TempSummonDataContainer _tempSummonDataStore; diff --git a/src/server/game/Grids/Notifiers/GridNotifiers.h b/src/server/game/Grids/Notifiers/GridNotifiers.h index 897e43d5e030c..acbe5e641e87b 100644 --- a/src/server/game/Grids/Notifiers/GridNotifiers.h +++ b/src/server/game/Grids/Notifiers/GridNotifiers.h @@ -552,6 +552,11 @@ namespace Trinity : ContainerInserter(container), i_phaseMask(searcher->GetPhaseMask()), i_check(check) { } + template + PlayerListSearcher(uint32 phaseMask, Container& container, Check & check) + : ContainerInserter(container), + i_phaseMask(phaseMask), i_check(check) { } + void Visit(PlayerMapType &m); template void Visit(GridRefManager &) { } @@ -1321,6 +1326,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 653c9d51d11e9..6400e1532a63e 100644 --- a/src/server/game/Grids/ObjectGridLoader.cpp +++ b/src/server/game/Grids/ObjectGridLoader.cpp @@ -27,6 +27,7 @@ #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "World.h" +#include "ScriptMgr.h" void ObjectGridEvacuator::Visit(CreatureMapType &m) { @@ -119,15 +120,47 @@ 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)) + + // Don't spawn at all if there's a respawn time + if ((obj->GetTypeId() == TYPEID_UNIT && !map->GetCreatureRespawnTime(*i_guid)) || (obj->GetTypeId() == TYPEID_GAMEOBJECT && !map->GetGORespawnTime(*i_guid))) { - delete obj; - continue; - } + ObjectGuid::LowType guid = *i_guid; + //TC_LOG_INFO("misc", "DEBUG: LoadHelper from table: %s for (guid: %u) Loading", table, guid); - AddObjectHelper(cell, m, count, map, obj); + 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_MANUAL_SPAWN) && !group->isActive) + continue; + + // If script is blocking spawn, don't spawn but queue for a re-check in a little bit + if (!(group->flags & SPAWNGROUP_FLAG_COMPATIBILITY_MODE) && !sScriptMgr->CanSpawn(guid, cdata->id, cdata, map)) + { + map->SaveRespawnTime(SPAWN_TYPE_CREATURE, guid, cdata->id, time(NULL) + urand(4,7), map->GetZoneId(cdata->spawnPoint), Trinity::ComputeGridCoord(cdata->spawnPoint.GetPositionX(), cdata->spawnPoint.GetPositionY()).GetId(), false); + continue; + } + } + else if (obj->GetTypeId() == TYPEID_GAMEOBJECT) + { + // If gameobject in manual spawn group, don't spawn here, unless group is already active. + GameObjectData const* godata = sObjectMgr->GetGameObjectData(guid); + ASSERT(godata, "Tried to load gameobject with spawnId %u, but no such object exists.", guid); + if ((godata->spawnGroupData->flags & SPAWNGROUP_FLAG_MANUAL_SPAWN) && !godata->spawnGroupData->isActive) + 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/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 0793422f8adbb..2e9974d79d207 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 "ScriptMgr.h" #include "Transport.h" #include "Vehicle.h" @@ -52,6 +54,7 @@ u_map_magic MapLiquidMagic = { {'M','L','I','Q'} }; #define DEFAULT_GRID_EXPIRY 300 #define MAX_GRID_LOAD_TIME 50 #define MAX_CREATURE_ATTACK_RADIUS (45.0f * sWorld->getRate(RATE_CREATURE_AGGRO)) +#define MAP_INVALID_ZONE 0xFFFFFFFF GridState* si_GridStates[MAX_GRID_STATE]; @@ -61,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(); @@ -253,7 +260,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)) { m_parentMap = (_parent ? _parent : this); for (unsigned int idx=0; idx < MAX_NUMBER_OF_GRIDS; ++idx) @@ -266,6 +273,8 @@ i_scriptLock(false), _defaultLight(GetDefaultMapLight(id)) } } + _zonePlayerCountMap.clear(); + //lets initialize visibility distance for map Map::InitVisibilityDistance(); @@ -522,6 +531,29 @@ bool Map::EnsureGridLoaded(Cell const& 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)); @@ -689,6 +721,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(); @@ -885,6 +942,8 @@ void Map::ProcessRelocationNotifies(const uint32 diff) void Map::RemovePlayerFromMap(Player* player, bool remove) { + // Before leaving map, update zone/area for stats + player->UpdateZone(MAP_INVALID_ZONE, 0); sScriptMgr->OnPlayerLeaveMap(this, player); player->getHostileRefManager().deleteReferences(); // multithreading crashfix @@ -2639,10 +2698,7 @@ void Map::GetFullTerrainStatusForPosition(float x, float y, float z, PositionFul 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; @@ -2744,11 +2800,6 @@ bool Map::getObjectHitPos(uint32 phasemask, float x1, float y1, float z1, float return result; } -float Map::GetHeight(uint32 phasemask, float x, float y, float z, bool vmap/*=true*/, float maxSearchDist/*=DEFAULT_HEIGHT_SEARCH*/) const -{ - return std::max(GetHeight(x, y, z, vmap, maxSearchDist), GetGameObjectFloor(phasemask, x, y, z, maxSearchDist)); -} - bool Map::IsInWater(float x, float y, float pZ, LiquidData* data) const { LiquidData liquid_status; @@ -2867,6 +2918,326 @@ void Map::SendObjectUpdates() } } +bool Map::CheckRespawn(RespawnInfo* info) +{ + uint32 poolId = info->spawnId ? sPoolMgr->IsPartOfAPool(info->type, info->spawnId) : 0; + // First, check if there's already an instance of this object that would block the respawn + // Only do this for unpooled spawns + if (!poolId) + { + bool doDelete = false; + switch (info->type) + { + case SPAWN_TYPE_CREATURE: + { + // escort check for creatures only (if the world config boolean is set) + bool isEscort = false; + if (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC) && info->type == SPAWN_TYPE_CREATURE) + if (CreatureData const* cdata = sObjectMgr->GetCreatureData(info->spawnId)) + if (cdata->spawnGroupData->flags & SPAWNGROUP_FLAG_ESCORTQUESTNPC) + isEscort = true; + + auto range = _creatureBySpawnIdStore.equal_range(info->spawnId); + for (auto it = range.first; it != range.second; ++it) + { + Creature* creature = it->second; + if (!creature->IsAlive()) + continue; + // escort NPCs are allowed to respawn as long as all other instances are already escorting + if (isEscort && creature->IsEscortNPC(true)) + continue; + doDelete = true; + break; + } + break; + } + case SPAWN_TYPE_GAMEOBJECT: + // gameobject check is simpler - they cannot be dead or escorting + if (_gameobjectBySpawnIdStore.find(info->spawnId) != _gameobjectBySpawnIdStore.end()) + doDelete = true; + break; + default: + ASSERT(false, "Invalid spawn type %u with spawnId %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 (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; + } + + // if we're a creature, see if the script objects to us spawning + if (info->type == SPAWN_TYPE_CREATURE) + { + if (!sScriptMgr->CanSpawn(info->spawnId, info->entry, sObjectMgr->GetCreatureData(info->spawnId), this)) + { // if a script blocks our respawn, schedule next check in a little bit + info->respawnTime = time(NULL) + urand(4, 7); + return false; + } + } + return true; +} + +void Map::DoRespawn(SpawnObjectType type, ObjectGuid::LowType spawnId, uint32 gridId) +{ + if (!IsGridLoaded(gridId)) // if grid isn't loaded, this will be processed in grid load handler + return; + + switch (type) + { + case SPAWN_TYPE_CREATURE: + { + Creature* obj = new Creature(); + if (!obj->LoadFromDB(spawnId, this, true, true)) + delete obj; + break; + } + case SPAWN_TYPE_GAMEOBJECT: + { + GameObject* obj = new GameObject(); + if (!obj->LoadFromDB(spawnId, this, true)) + delete obj; + break; + } + default: + ASSERT(false, "Invalid spawn type %u (spawnid %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(RespawnVector& 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(RespawnVector& data, RespawnInfoMap const& map, uint32 zoneId) +{ + for (auto const& pair : map) + if (!zoneId || pair.second->zoneId == zoneId) + data.push_back(pair.second); +} +void Map::GetRespawnInfo(RespawnVector& respawnData, SpawnObjectTypeMask types, uint32 zoneId) const +{ + if (types & SPAWN_TYPEMASK_CREATURE) + PushRespawnInfoFrom(respawnData, _creatureRespawnTimesBySpawnId, zoneId); + if (types & SPAWN_TYPEMASK_GAMEOBJECT) + PushRespawnInfoFrom(respawnData, _gameObjectRespawnTimesBySpawnId, zoneId); +} + +RespawnInfo* Map::GetRespawnInfo(SpawnObjectType type, ObjectGuid::LowType spawnId) const +{ + RespawnInfoMap const& map = GetRespawnMapForType(type); + auto it = map.find(spawnId); + if (it == map.end()) + return nullptr; + return it->second; +} + +void Map::DeleteRespawnInfo() // delete everything +{ + for (RespawnInfo* info : _respawnTimes) + delete info; + _respawnTimes.clear(); + _creatureRespawnTimesBySpawnId.clear(); + _gameObjectRespawnTimesBySpawnId.clear(); +} + +void Map::DeleteRespawnInfo(RespawnInfo* info) +{ + // Delete from all relevant containers to ensure consistency + ASSERT(info); + + // spawnid store + size_t const n = GetRespawnMapForType(info->type).erase(info->spawnId); + ASSERT(n == 1, "Respawn stores inconsistent for map %u, spawnid %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(RespawnVector& 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); + SpawnObjectType type; + switch (obj->GetTypeId()) + { + case TYPEID_UNIT: + type = SPAWN_TYPE_CREATURE; + break; + case TYPEID_GAMEOBJECT: + type = SPAWN_TYPE_GAMEOBJECT; + break; + default: + return; + } + + SpawnData const* data = sObjectMgr->GetSpawnData(type, spawnId); + if (!data || !(data->spawnGroupData->flags & SPAWNGROUP_FLAG_DYNAMIC_SPAWN_RATE)) + return; + + auto it = _zonePlayerCountMap.find(obj->GetZoneId()); + if (it == _zonePlayerCountMap.end()) + return; + uint32 const playerCount = it->second; + if (!playerCount) + return; + double const adjustFactor = sWorld->getFloatConfig(type == SPAWN_TYPE_GAMEOBJECT ? CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT : CONFIG_RESPAWN_DYNAMICRATE_CREATURE) / playerCount; + if (adjustFactor >= 1.0) // nothing to do here + return; + uint32 const timeMinimum = sWorld->getIntConfig(type == SPAWN_TYPE_GAMEOBJECT ? CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT : CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE); + if (respawnDelay <= timeMinimum) + return; + + respawnDelay = std::max(ceil(respawnDelay * adjustFactor), timeMinimum); +} + void Map::DelayedUpdate(uint32 t_diff) { for (_transportsUpdateIter = _transports.begin(); _transportsUpdateIter != _transports.end();) @@ -3693,6 +4064,34 @@ Creature* Map::GetCreature(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); @@ -3723,64 +4122,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); - stmt->setUInt64(1, uint64(respawnTime)); - stmt->setUInt16(2, GetId()); - stmt->setUInt32(3, GetInstanceId()); - CharacterDatabase.Execute(stmt); + if (writeDB) + SaveRespawnTimeDB(type, spawnId, ri.respawnTime, dbTrans); // might be different from original respawn time if we didn't replace } -void Map::RemoveCreatureRespawnTime(ObjectGuid::LowType dbGuid) +void Map::SaveRespawnTimeDB(SpawnObjectType type, ObjectGuid::LowType spawnId, time_t respawnTime, SQLTransaction dbTrans) { - _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); + // 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::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() @@ -3796,7 +4168,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(cdata->spawnPoint), Trinity::ComputeGridCoord(cdata->spawnPoint.GetPositionX(), cdata->spawnPoint.GetPositionY()).GetId(), false); + } while (result->NextRow()); } @@ -3811,19 +4185,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(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 dd0e43158c140..d7613f79de5eb 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 @@ -271,7 +273,37 @@ struct ZoneDynamicInfo 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; +typedef std::vector RespawnVector; +struct RespawnInfo +{ + SpawnObjectType type; + ObjectGuid::LowType spawnId; + uint32 entry; + time_t respawnTime; + uint32 gridId; + uint32 zoneId; + RespawnListHandle handle; +}; +inline bool CompareRespawnInfo::operator()(RespawnInfo const* a, RespawnInfo const* b) const +{ + if (a == b) + return false; + if (a->respawnTime != b->respawnTime) + return (a->respawnTime > b->respawnTime); + if (a->spawnId != b->spawnId) + return a->spawnId < b->spawnId; + ASSERT(a->type != b->type, "Duplicate respawn entry for spawnId (%u,%u) found!", a->type, a->spawnId); + return a->type < b->type; +} class TC_GAME_API Map : public GridRefManager { @@ -321,17 +353,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 @@ -350,18 +384,16 @@ class TC_GAME_API Map : public GridRefManager Map const* GetParent() const { return m_parentMap; } - // some calls like isInWater should not use vmaps due to processor power - // can return INVALID_HEIGHT if under z+2 z coord not found height - float GetHeight(float x, float y, float z, bool checkVMap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const; - float GetMinHeight(float x, float y) const; - void GetFullTerrainStatusForPosition(float x, float y, float z, PositionFullTerrainStatus& data, uint8 reqLiquidType = MAP_ALL_LIQUIDS) const; ZLiquidStatus GetLiquidStatus(float x, float y, float z, uint8 ReqLiquidType, LiquidData* data = nullptr) const; bool GetAreaInfo(float x, float y, float z, uint32& mogpflags, int32& adtId, int32& rootId, int32& groupId) const; uint32 GetAreaId(float x, float y, float z, bool *isOutdoors = nullptr) const; + uint32 GetAreaId(Position const& pos) { return GetAreaId(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } uint32 GetZoneId(float x, float y, float z) const; + uint32 GetZoneId(Position const& pos) { return GetZoneId(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } void GetZoneAndAreaId(uint32& zoneid, uint32& areaid, float x, float y, float z) const; + void GetZoneAndAreaId(uint32& zoneid, uint32& areaid, Position const& pos) { GetZoneAndAreaId(zoneid, areaid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } bool IsOutdoors(float x, float y, float z) const; @@ -464,6 +496,9 @@ class TC_GAME_API Map : public GridRefManager Corpse* GetCorpse(ObjectGuid const& guid); Creature* GetCreature(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)); } Transport* GetTransport(ObjectGuid const& guid); DynamicObject* GetDynamicObject(ObjectGuid const& guid); Pet* GetPet(ObjectGuid const& guid); @@ -472,9 +507,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 +541,11 @@ class TC_GAME_API Map : public GridRefManager BattlegroundMap const* ToBattlegroundMap() const { if (IsBattlegroundOrArena()) return reinterpret_cast(this); return nullptr; } float GetWaterOrGroundLevel(uint32 phasemask, float x, float y, float z, float* ground = nullptr, bool swim = false) const; - float GetHeight(uint32 phasemask, float x, float y, float z, bool vmap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const; + float GetMinHeight(float x, float y) const; + float GetHeight(float x, float y, float z, bool checkVMap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const; + float GetHeight(Position const& pos, bool vmap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const { return GetHeight(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), vmap, maxSearchDist); } + float GetHeight(uint32 phasemask, float x, float y, float z, bool vmap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const { return std::max(GetHeight(x, y, z, vmap, maxSearchDist), GetGameObjectFloor(phasemask, x, y, z, maxSearchDist)); } + float GetHeight(uint32 phasemask, Position const& pos, bool vmap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const { return GetHeight(phasemask, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), vmap, maxSearchDist); } bool isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, uint32 phasemask, LineOfSightChecks checks, VMAP::ModelIgnoreFlags ignoreFlags) const; void Balance() { _dynamicTree.balance(); } void RemoveGameObjectModel(GameObjectModel const& model) { _dynamicTree.remove(model); } @@ -522,28 +563,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(); @@ -702,6 +739,59 @@ 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(RespawnVector& respawnData, bool force = false, SQLTransaction dbTrans = nullptr); + void AddRespawnInfo(RespawnInfo& info, bool replace = false); + void DeleteRespawnInfo(); + void DeleteRespawnInfo(RespawnInfo* info); + void DeleteRespawnInfo(RespawnVector& toDelete) + { + for (RespawnInfo* info : toDelete) + DeleteRespawnInfo(info); + toDelete.clear(); + } + void DeleteRespawnInfo(SpawnObjectTypeMask types, uint32 zoneId = 0) + { + RespawnVector v; + GetRespawnInfo(v, types, zoneId); + if (!v.empty()) + DeleteRespawnInfo(v); + } + void DeleteRespawnInfo(SpawnObjectType type, ObjectGuid::LowType spawnId) + { + if (RespawnInfo* info = GetRespawnInfo(type, spawnId)) + DeleteRespawnInfo(info); + } + + public: + void GetRespawnInfo(RespawnVector& respawnData, SpawnObjectTypeMask types, uint32 zoneId = 0) const; + RespawnInfo* GetRespawnInfo(SpawnObjectType type, ObjectGuid::LowType spawnId) const; + void RemoveRespawnTime(RespawnInfo* info, bool doRespawn = false, SQLTransaction dbTrans = nullptr); + void RemoveRespawnTime(RespawnVector& respawnData, bool doRespawn = false, SQLTransaction dbTrans = nullptr); + void RemoveRespawnTime(SpawnObjectTypeMask types = SPAWN_TYPEMASK_ALL, uint32 zoneId = 0, bool doRespawn = false, SQLTransaction dbTrans = nullptr) + { + RespawnVector v; + GetRespawnInfo(v, types, zoneId); + if (!v.empty()) + RemoveRespawnTime(v, doRespawn, dbTrans); + } + void RemoveRespawnTime(SpawnObjectType type, ObjectGuid::LowType spawnId, bool doRespawn = false, SQLTransaction dbTrans = nullptr) + { + if (RespawnInfo* info = GetRespawnInfo(type, spawnId)) + RemoveRespawnTime(info, doRespawn, dbTrans); + } + + private: // Type specific code for add/remove to/from grid template void AddToGrid(T* object, Cell const& cell); @@ -730,8 +820,14 @@ 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; } + + 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 e628a7030aee9..96147c7ded7df 100644 --- a/src/server/game/Maps/MapManager.h +++ b/src/server/game/Maps/MapManager.h @@ -44,16 +44,22 @@ class TC_GAME_API MapManager Map const* m = const_cast(this)->CreateBaseMap(mapid); return m->GetAreaId(x, y, z); } + uint32 GetAreaId(uint32 mapid, Position const& pos) const { return GetAreaId(mapid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } + uint32 GetAreaId(WorldLocation const& loc) const { return GetAreaId(loc.GetMapId(), loc); } uint32 GetZoneId(uint32 mapid, float x, float y, float z) const { Map const* m = const_cast(this)->CreateBaseMap(mapid); return m->GetZoneId(x, y, z); } - void GetZoneAndAreaId(uint32& zoneid, uint32& areaid, uint32 mapid, float x, float y, float z) + uint32 GetZoneId(uint32 mapid, Position const& pos) const { return GetZoneId(mapid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } + uint32 GetZoneId(WorldLocation const& loc) const { return GetZoneId(loc.GetMapId(), loc); } + void GetZoneAndAreaId(uint32& zoneid, uint32& areaid, uint32 mapid, float x, float y, float z) const { Map const* m = const_cast(this)->CreateBaseMap(mapid); m->GetZoneAndAreaId(zoneid, areaid, x, y, z); } + void GetZoneAndAreaId(uint32& zoneid, uint32& areaid, uint32 mapid, Position const& pos) const { GetZoneAndAreaId(zoneid, areaid, mapid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } + void GetZoneAndAreaId(uint32& zoneid, uint32& areaid, WorldLocation const& loc) const { GetZoneAndAreaId(zoneid, areaid, loc.GetMapId(), loc); } void Initialize(void); void Update(uint32); diff --git a/src/server/game/Maps/SpawnData.h b/src/server/game/Maps/SpawnData.h new file mode 100644 index 0000000000000..700311e64c7ff --- /dev/null +++ b/src/server/game/Maps/SpawnData.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2008-2017 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; + bool isActive; +}; + +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; + 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 d96b33fd9683a..400685c1c1bc5 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -1147,7 +1147,32 @@ enum TrinityStrings LANG_COMMAND_MUTEHISTORY_EMPTY = 5060, LANG_COMMAND_MUTEHISTORY_OUTPUT = 5061, - // 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 a2d195bea20bb..b2e962abc0f85 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; @@ -217,7 +217,7 @@ bool OPvPCapturePoint::DelObject(uint32 type) go->SetRespawnTime(0); go->Delete(); } - sObjectMgr->DeleteGOData(spawnId); + sObjectMgr->DeleteGameObjectData(spawnId); m_ObjectTypes[m_Objects[type]] = 0; m_Objects[type] = 0; return true; @@ -225,7 +225,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 9305e226421c6..7ab6c43d41009 100644 --- a/src/server/game/Pools/PoolMgr.cpp +++ b/src/server/game/Pools/PoolMgr.cpp @@ -222,7 +222,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); @@ -230,6 +230,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(); } } @@ -240,11 +243,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); @@ -252,6 +255,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(); } } @@ -379,13 +386,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; @@ -398,18 +405,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; @@ -688,7 +695,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); @@ -1074,6 +1081,21 @@ void PoolMgr::DespawnPool(uint32 pool_id) mPoolQuestGroups[pool_id].DespawnObject(mSpawnedData); } +// 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 6c270e28851e4..c36e7bd29ef3c 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 @@ -118,6 +119,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 9f42ded8cc68e..cfc64b83005a9 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -1566,16 +1566,32 @@ bool ScriptMgr::OnCastItemCombatSpell(Player* player, Unit* victim, SpellInfo co return tmpscript->OnCastItemCombatSpell(player, victim, spellInfo, item); } -bool ScriptMgr::CanSpawn(ObjectGuid::LowType spawnId, uint32 entry, CreatureTemplate const* actTemplate, CreatureData const* cData, Map const* map) +bool ScriptMgr::CanSpawn(ObjectGuid::LowType spawnId, uint32 entry, CreatureData const* cData, Map const* map) { - ASSERT(actTemplate); - + ASSERT(map); CreatureTemplate const* baseTemplate = sObjectMgr->GetCreatureTemplate(entry); - if (!baseTemplate) - baseTemplate = actTemplate; + ASSERT(baseTemplate); + + // find out which template we'd be using + CreatureTemplate const* actTemplate = baseTemplate; + for (uint8 diff = uint8(map->GetSpawnMode()); diff > 0;) + { + if (uint32 diffEntry = baseTemplate->DifficultyEntry[diff - 1]) + if (CreatureTemplate const* diffTemplate = sObjectMgr->GetCreatureTemplate(diffEntry)) + { + actTemplate = diffTemplate; + break; + } + if (diff >= RAID_DIFFICULTY_10MAN_HEROIC && map->IsRaid()) + diff -= 2; + else + diff -= 1; + } + uint32 scriptId = baseTemplate->ScriptID; - if (cData && cData->ScriptId) - scriptId = cData->ScriptId; + if (cData && cData->scriptId) + scriptId = cData->scriptId; + GET_SCRIPT_RET(CreatureScript, scriptId, tmpscript, true); return tmpscript->CanSpawn(spawnId, entry, baseTemplate, actTemplate, cData, map); } diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index dab3b42329cd5..65c5be6be379c 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -907,7 +907,7 @@ class TC_GAME_API ScriptMgr public: /* CreatureScript */ - bool CanSpawn(ObjectGuid::LowType spawnId, uint32 entry, CreatureTemplate const* actTemplate, CreatureData const* cData, Map const* map); + bool CanSpawn(ObjectGuid::LowType spawnId, uint32 entry, CreatureData const* cData, Map const* map); CreatureAI* GetCreatureAI(Creature* creature); public: /* GameObjectScript */ diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index ae5cc87d0974f..9706567ce473c 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -134,6 +134,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(NULL); } /// World destructor @@ -196,6 +201,59 @@ void World::SetClosed(bool val) sScriptMgr->OnOpenStateChange(!val); } +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 { @@ -1192,6 +1250,50 @@ void World::LoadConfigSettings(bool reload) m_int_configs[CONFIG_NO_GRAY_AGGRO_BELOW] = m_int_configs[CONFIG_NO_GRAY_AGGRO_ABOVE]; } + // Respawn Settings + m_int_configs[CONFIG_RESPAWN_MINCHECKINTERVALMS] = sConfigMgr->GetIntDefault("Respawn.MinCheckIntervalMS", 5000); + m_int_configs[CONFIG_RESPAWN_DYNAMICMODE] = sConfigMgr->GetIntDefault("Respawn.DynamicMode", 0); + if (m_int_configs[CONFIG_RESPAWN_DYNAMICMODE] > 1) + { + TC_LOG_ERROR("server.loading", "Invalid value for Respawn.DynamicMode (%u). Set to 0.", m_int_configs[CONFIG_RESPAWN_DYNAMICMODE]); + m_int_configs[CONFIG_RESPAWN_DYNAMICMODE] = 0; + } + m_bool_configs[CONFIG_RESPAWN_DYNAMIC_ESCORTNPC] = sConfigMgr->GetBoolDefault("Respawn.DynamicEscortNPC", true); + m_int_configs[CONFIG_RESPAWN_GUIDWARNLEVEL] = sConfigMgr->GetIntDefault("Respawn.GuidWarnLevel", 12000000); + if (m_int_configs[CONFIG_RESPAWN_GUIDWARNLEVEL] > 16777215) + { + TC_LOG_ERROR("server.loading", "Respawn.GuidWarnLevel (%u) cannot be greater than maximum GUID (16777215). Set to 12000000.", m_int_configs[CONFIG_RESPAWN_GUIDWARNLEVEL]); + m_int_configs[CONFIG_RESPAWN_GUIDWARNLEVEL] = 12000000; + } + m_int_configs[CONFIG_RESPAWN_GUIDALERTLEVEL] = sConfigMgr->GetIntDefault("Respawn.GuidAlertLevel", 16000000); + if (m_int_configs[CONFIG_RESPAWN_GUIDALERTLEVEL] > 16777215) + { + TC_LOG_ERROR("server.loading", "Respawn.GuidWarnLevel (%u) cannot be greater than maximum GUID (16777215). Set to 16000000.", m_int_configs[CONFIG_RESPAWN_GUIDALERTLEVEL]); + m_int_configs[CONFIG_RESPAWN_GUIDALERTLEVEL] = 16000000; + } + m_int_configs[CONFIG_RESPAWN_RESTARTQUIETTIME] = sConfigMgr->GetIntDefault("Respawn.RestartQuietTime", 3); + if (m_int_configs[CONFIG_RESPAWN_RESTARTQUIETTIME] > 23) + { + TC_LOG_ERROR("server.loading", "Respawn.RestartQuietTime (%u) must be an hour, between 0 and 23. Set to 3.", m_int_configs[CONFIG_RESPAWN_RESTARTQUIETTIME]); + m_int_configs[CONFIG_RESPAWN_RESTARTQUIETTIME] = 3; + } + m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_CREATURE] = sConfigMgr->GetFloatDefault("Respawn.DynamicRateCreature", 10.0f); + if (m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_CREATURE] < 0.0f) + { + TC_LOG_ERROR("server.loading", "Respawn.DynamicRateCreature (%f) must be positive. Set to 10.", m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_CREATURE]); + m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_CREATURE] = 10.0f; + } + m_int_configs[CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE] = sConfigMgr->GetIntDefault("Respawn.DynamicMinimumCreature", 10); + m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT] = sConfigMgr->GetFloatDefault("Respawn.DynamicRateGameObject", 10.0f); + if (m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT] < 0.0f) + { + TC_LOG_ERROR("server.loading", "Respawn.DynamicRateGameObject (%f) must be positive. Set to 10.", m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT]); + m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT] = 10.0f; + } + m_int_configs[CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT] = sConfigMgr->GetIntDefault("Respawn.DynamicMinimumGameObject", 10); + _guidWarningMsg = sConfigMgr->GetStringDefault("Respawn.WarningMessage", "There will be an unscheduled server restart at 03:00. The server will be available again shortly after."); + _alertRestartReason = sConfigMgr->GetStringDefault("Respawn.AlertRestartReason", "Urgent Maintenance"); + m_int_configs[CONFIG_RESPAWN_GUIDWARNING_FREQUENCY] = sConfigMgr->GetIntDefault("Respawn.WarningFrequency", 1800); ///- Read the "Data" directory from the config file std::string dataPath = sConfigMgr->GetStringDefault("DataDir", "./"); if (dataPath.empty() || (dataPath.at(dataPath.length()-1) != '/' && dataPath.at(dataPath.length()-1) != '\\')) @@ -1601,6 +1703,9 @@ void World::SetInitialWorldSettings() TC_LOG_INFO("server.loading", "Loading Creature Base Stats..."); sObjectMgr->LoadCreatureClassLevelStats(); + TC_LOG_INFO("server.loading", "Loading Spawn Group Templates..."); + sObjectMgr->LoadSpawnGroupTemplates(); + TC_LOG_INFO("server.loading", "Loading Creature Data..."); sObjectMgr->LoadCreatures(); @@ -1617,10 +1722,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(); @@ -2289,6 +2397,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 5254b32bbdfc8..6c44b1abe1a16 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -202,7 +202,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 @@ -379,13 +378,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, @@ -765,6 +762,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(); @@ -859,7 +860,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 69f7ecb3f9ab7..a7ac9028bc86d 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -972,7 +972,7 @@ class debug_commandscript : public CommandScript 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 6a27060c2ed95..2b8f89da7ac04 100644 --- a/src/server/scripts/Commands/cs_go.cpp +++ b/src/server/scripts/Commands/cs_go.cpp @@ -138,8 +138,6 @@ class go_commandscript : public CommandScript 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); @@ -157,11 +155,7 @@ class go_commandscript : public CommandScript 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; } @@ -272,28 +266,18 @@ class go_commandscript : public CommandScript 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; } @@ -308,7 +292,7 @@ class go_commandscript : public CommandScript else player->SaveRecallPosition(); - player->TeleportTo(mapId, x, y, z, o); + player->TeleportTo(goData->spawnPoint); return true; } diff --git a/src/server/scripts/Commands/cs_gobject.cpp b/src/server/scripts/Commands/cs_gobject.cpp index a315355ff44da..46927a1c8f6db 100644 --- a/src/server/scripts/Commands/cs_gobject.cpp +++ b/src/server/scripts/Commands/cs_gobject.cpp @@ -38,6 +38,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: @@ -57,15 +61,17 @@ class gobject_commandscript : public CommandScript }; 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 = { @@ -169,14 +175,14 @@ class gobject_commandscript : public CommandScript 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; @@ -430,7 +436,7 @@ class gobject_commandscript : public CommandScript object->Delete(); object = new GameObject(); - if (!object->LoadGameObjectFromDB(guidLow, map)) + if (!object->LoadFromDB(guidLow, map, true)) { delete object; return false; @@ -499,7 +505,7 @@ class gobject_commandscript : public CommandScript object->Delete(); object = new GameObject(); - if (!object->LoadGameObjectFromDB(guidLow, map)) + if (!object->LoadFromDB(guidLow, map, true)) { delete object; return false; @@ -578,7 +584,7 @@ class gobject_commandscript : public CommandScript if (!gameObjectInfo) continue; - handler->PSendSysMessage(LANG_GO_LIST_CHAT, guid, entry, guid, gameObjectInfo->name.c_str(), x, y, z, mapId); + handler->PSendSysMessage(LANG_GO_LIST_CHAT, guid, entry, guid, gameObjectInfo->name.c_str(), x, y, z, mapId, "", ""); ++count; } while (result->NextRow()); @@ -611,7 +617,7 @@ class gobject_commandscript : public CommandScript 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; @@ -623,9 +629,16 @@ class gobject_commandscript : public CommandScript 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; @@ -634,10 +647,32 @@ class gobject_commandscript : public CommandScript 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, groupData->isActive); + } + 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 8b502f60e11df..ea57f29a771bc 100644 --- a/src/server/scripts/Commands/cs_list.cpp +++ b/src/server/scripts/Commands/cs_list.cpp @@ -27,7 +27,9 @@ EndScriptData */ #include "Chat.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" @@ -50,6 +52,7 @@ class list_commandscript : public CommandScript { "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, "" }, + { "respawns", rbac::RBAC_PERM_COMMAND_LIST_MAIL, false, &HandleListRespawnsCommand, "" }, }; static std::vector commandTable = { @@ -117,11 +120,40 @@ class list_commandscript : public CommandScript 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 +439,40 @@ class list_commandscript : public CommandScript 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,8 +642,79 @@ class list_commandscript : public CommandScript handler->PSendSysMessage(LANG_LIST_MAIL_NOT_FOUND); return true; } + + static char const* GetZoneName(uint32 zoneId, LocaleConstant locale) + { + AreaTableEntry const* zoneEntry = sAreaTableStore.LookupEntry(zoneId); + return zoneEntry ? zoneEntry->area_name[locale] : ""; + } + static bool HandleListRespawnsCommand(ChatHandler* handler, char const* args) + { + // We need a player + Player const* player = handler->GetSession()->GetPlayer(); + if (!player) + return false; + // And we need a map + Map const* map = player->GetMap(); + if (!map) + return false; + + uint32 range = 0; + if (*args) + range = atoi((char*)args); + + RespawnVector 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(NULL) ? secsToTimeString(uint64(ri->respawnTime - time(NULL)), true) : stringOverdue; + handler->PSendSysMessage("%u | %u | [%02u,%02u] | %s (%u) | %s", ri->spawnId, ri->entry, gridX, gridY, GetZoneName(ri->zoneId, handler->GetSessionDbcLocale()), ri->zoneId, data->spawnGroupData->isActive ? 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(NULL) ? secsToTimeString(uint64(ri->respawnTime - time(NULL)), true) : stringOverdue; + handler->PSendSysMessage("%u | %u | [% 02u, % 02u] | %s (%u) | %s", ri->spawnId, ri->entry, gridX, gridY, GetZoneName(ri->zoneId, handler->GetSessionDbcLocale()), ri->zoneId, data->spawnGroupData->isActive ? respawnTime.c_str() : "inactive"); + } + return true; + } }; + void AddSC_list_commandscript() { new list_commandscript(); diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp index f57d960d08a0c..dbf50d65640f9 100644 --- a/src/server/scripts/Commands/cs_misc.cpp +++ b/src/server/scripts/Commands/cs_misc.cpp @@ -1911,10 +1911,22 @@ class misc_commandscript : public CommandScript 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. + RespawnVector 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()->RemoveRespawnTime(info, true); + } + return true; } diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index dfb9ddb66a6c6..cf63419303df8 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -176,6 +176,86 @@ EnumName const flagsExtra[FLAGS_EXTRA_COUNT] = CREATE_NAMED_ENUM(CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK) }; +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 (!sObjectMgr->SpawnGroupSpawn(groupId, player->GetMap(), 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 (!sObjectMgr->SpawnGroupDespawn(groupId, player->GetMap(), deleteRespawnTimes)) + { + handler->PSendSysMessage(LANG_SPAWNGROUP_BADGROUP, groupId); + handler->SetSentErrorMessage(true); + return false; + } + + return true; +} + class npc_commandscript : public CommandScript { public: @@ -219,21 +299,23 @@ class npc_commandscript : public CommandScript }; 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 = { @@ -257,22 +339,16 @@ class npc_commandscript : public CommandScript 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->GetPhaseMaskForSpawn(); - 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); @@ -283,7 +359,7 @@ class npc_commandscript : public CommandScript } Creature* creature = new Creature(); - if (!creature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMaskForSpawn(), id, x, y, z, o)) + if (!creature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMaskForSpawn(), id, *chr)) { delete creature; return false; @@ -298,7 +374,7 @@ class npc_commandscript : public CommandScript 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; @@ -473,7 +549,7 @@ class npc_commandscript : public CommandScript static bool HandleNpcDeleteCommand(ChatHandler* handler, char const* args) { - Creature* unit = nullptr; + Creature* creature = nullptr; if (*args) { @@ -485,22 +561,27 @@ class npc_commandscript : public CommandScript ObjectGuid::LowType lowguid = atoul(cId); if (!lowguid) return false; - unit = handler->GetCreatureFromPlayerMapByDbGuid(lowguid); + 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); @@ -690,13 +771,20 @@ class npc_commandscript : public CommandScript uint32 nativeid = target->GetNativeDisplayId(); uint32 Entry = target->GetEntry(); - int64 curRespawnDelay = target->GetRespawnTimeEx()-time(nullptr); + int64 curRespawnDelay = target->GetRespawnCompatibilityMode() ? target->GetRespawnTimeEx() - time(nullptr) : target->GetMap()->GetCreatureRespawnTime(target->GetSpawnId()) - 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); + if (target->GetCreatureData() && target->GetCreatureData()->spawnGroupData->groupId) + { + if (SpawnGroupTemplateData const* groupData = target->GetCreatureData()->spawnGroupData) + handler->PSendSysMessage(LANG_SPAWNINFO_GROUP_ID, groupData->name.c_str(), groupData->groupId, groupData->flags, groupData->isActive); + } + 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()); @@ -766,7 +854,7 @@ class npc_commandscript : public CommandScript 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; } @@ -784,6 +872,9 @@ class npc_commandscript : public CommandScript ObjectGuid::LowType lowguid = 0; Creature* creature = handler->getSelectedCreature(); + Player const* player = handler->GetSession()->GetPlayer(); + if (!player) + return false; if (!creature) { @@ -803,9 +894,7 @@ class npc_commandscript : public CommandScript return false; } - uint32 map_id = data->mapid; - - if (handler->GetSession()->GetPlayer()->GetMapId() != map_id) + if (player->GetMapId() != data->spawnPoint.GetMapId()) { handler->PSendSysMessage(LANG_COMMAND_CREATUREATSAMEMAP, lowguid); handler->SetSentErrorMessage(true); @@ -813,25 +902,12 @@ class npc_commandscript : public CommandScript } } else - { lowguid = creature->GetSpawnId(); - } - - 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 (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->UpdatePosition(x, y, z, o); + sObjectMgr->NewOrExistCreatureData(creature->GetSpawnId()).spawnPoint.Relocate(*player); + creature->UpdatePosition(*player); creature->GetMotionMaster()->Initialize(); if (creature->IsAlive()) // dead creature will reset movement generator at respawn { @@ -842,10 +918,10 @@ class npc_commandscript : public CommandScript 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); @@ -1529,13 +1605,13 @@ class npc_commandscript : public CommandScript handler->PSendSysMessage(LANG_COMMAND_NPC_SHOWLOOT_LABEL_2, "Per-player quest items"); _IterateNotNormalLootMap(handler, loot.GetPlayerQuestItems(), loot.quest_items); } - + if (!loot.GetPlayerFFAItems().empty()) { handler->PSendSysMessage(LANG_COMMAND_NPC_SHOWLOOT_LABEL_2, "FFA items per allowed player"); _IterateNotNormalLootMap(handler, loot.GetPlayerFFAItems(), loot.items); } - + if (!loot.GetPlayerNonQuestNonFFAConditionalItems().empty()) { handler->PSendSysMessage(LANG_COMMAND_NPC_SHOWLOOT_LABEL_2, "Per-player conditional items"); diff --git a/src/server/scripts/Commands/cs_wp.cpp b/src/server/scripts/Commands/cs_wp.cpp index efd02314d0e7b..6298a34644f73 100644 --- a/src/server/scripts/Commands/cs_wp.cpp +++ b/src/server/scripts/Commands/cs_wp.cpp @@ -662,7 +662,7 @@ class wp_commandscript : public CommandScript // re-create Creature* wpCreature = new Creature(); - if (!wpCreature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMaskForSpawn(), VISUAL_WAYPOINT, chr->GetPositionX(), chr->GetPositionY(), chr->GetPositionZ(), chr->GetOrientation())) + if (!wpCreature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMaskForSpawn(), VISUAL_WAYPOINT, *chr)) { handler->PSendSysMessage(LANG_WAYPOINT_VP_NOTCREATED, VISUAL_WAYPOINT); delete wpCreature; @@ -671,8 +671,7 @@ class wp_commandscript : public CommandScript wpCreature->SaveToDB(map->GetId(), (1 << map->GetSpawnMode()), chr->GetPhaseMaskForSpawn()); // 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; @@ -874,7 +873,7 @@ class wp_commandscript : public CommandScript float o = chr->GetOrientation(); Creature* wpCreature = new Creature(); - if (!wpCreature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMaskForSpawn(), id, x, y, z, o)) + if (!wpCreature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMaskForSpawn(), id, { x, y, z, o })) { handler->PSendSysMessage(LANG_WAYPOINT_VP_NOTCREATED, id); delete wpCreature; @@ -891,7 +890,7 @@ class wp_commandscript : public CommandScript WorldDatabase.Execute(stmt); // 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; @@ -937,7 +936,7 @@ class wp_commandscript : public CommandScript Map* map = chr->GetMap(); Creature* creature = new Creature(); - if (!creature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMaskForSpawn(), id, x, y, z, o)) + if (!creature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMaskForSpawn(), id, { x, y, z, o })) { handler->PSendSysMessage(LANG_WAYPOINT_VP_NOTCREATED, id); delete creature; @@ -945,7 +944,7 @@ class wp_commandscript : public CommandScript } creature->SaveToDB(map->GetId(), (1 << map->GetSpawnMode()), chr->GetPhaseMaskForSpawn()); - if (!creature->LoadCreatureFromDB(creature->GetSpawnId(), map)) + if (!creature->LoadFromDB(creature->GetSpawnId(), map, true, true)) { handler->PSendSysMessage(LANG_WAYPOINT_VP_NOTCREATED, id); delete creature; @@ -986,7 +985,7 @@ class wp_commandscript : public CommandScript Map* map = chr->GetMap(); Creature* creature = new Creature(); - if (!creature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMaskForSpawn(), id, x, y, z, o)) + if (!creature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMaskForSpawn(), id, { x, y, z, o })) { handler->PSendSysMessage(LANG_WAYPOINT_NOTCREATED, id); delete creature; @@ -994,7 +993,7 @@ class wp_commandscript : public CommandScript } creature->SaveToDB(map->GetId(), (1 << map->GetSpawnMode()), chr->GetPhaseMaskForSpawn()); - 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/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp index 89bc369fd6f45..d2cc08dcc6e08 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp @@ -264,7 +264,7 @@ class ValithriaDespawner : public BasicEvent creature->SetRespawnDelay(10); if (CreatureData const* data = creature->GetCreatureData()) - creature->UpdatePosition(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 ed85ee3fa175f..90d764cd9dbdc 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->UpdatePosition(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 53caf9fda7a4e..c98b086b20366 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp @@ -392,13 +392,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 5454ff7c848f5..cb04d154f8a18 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_anubrekhan.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_anubrekhan.cpp @@ -96,7 +96,7 @@ class boss_anubrekhan : public CreatureScript 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 da3d95a517362..65d70f1e398a8 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_faerlina.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_faerlina.cpp @@ -83,7 +83,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 0fb00d18fd310..51deb5b437557 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp @@ -265,14 +265,9 @@ struct boss_four_horsemen_baseAI : public BossAI for (Horseman boss : horsemen) { if (Creature* cBoss = getHorsemanHandle(boss)) - { - cBoss->DespawnOrUnsummon(); - 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 58da5a92295a8..ac27c44d1de0a 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_razuvious.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_razuvious.cpp @@ -79,7 +79,7 @@ class boss_razuvious : public CreatureScript void InitializeAI() override { - if (!me->isDead()) + if (!me->isDead() && instance->GetBossState(BOSS_RAZUVIOUS) != DONE) { Reset(); SummonAdds(); diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index ba8a10c759dd3..d1ce8ca36b0c1 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 @@ -1713,6 +1714,122 @@ Creature.MovingStopTimeForPlayer = 180000 # ################################################################################################### +################################################################################################### +# 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 = 1 + +# +# 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 #