#include "mod_instance_release.h" #include "Battleground.h" #include "Config.h" #include "Map.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "Opcodes.h" #include "ScriptMgr.h" #include "WorldPacket.h" // SPELL_RESURRECTION_VISUAL (24171, "Resurrection Impact Visual") comes from // Battleground.h's BattlegroundSpells enum - it's the same purely-cosmetic // spell the battleground spirit-heal system casts on a player right after // resurrecting them. namespace { // A generic, purely-cosmetic "teleport in" flash reused across unrelated // content in this codebase (Sunwell Kil'jaeden, Arcatraz, the Test Flight // quest) - no gameplay effect, safe to reuse here for the arrival visual. constexpr uint32 SPELL_INSTANCE_RELEASE_ARRIVAL_VISUAL = 35517; // A same-map TeleportTo() relocates the player server-side immediately, but // the client doesn't visually snap to the new position until it acks the // teleport (MSG_MOVE_TELEPORT_ACK). Casting a visual spell synchronously // right after TeleportTo() sends it while the client is still mid-transition, // so it would appear to play at the old (death) spot instead of the entrance. // Deferring a beat lets the client catch up first. class DelayedArrivalVisualEvent : public BasicEvent { public: explicit DelayedArrivalVisualEvent(ObjectGuid playerGuid) : _playerGuid(playerGuid) { } bool Execute(uint64 /*time*/, uint32 /*diff*/) override { Player* player = ObjectAccessor::FindConnectedPlayer(_playerGuid); if (player && player->IsInWorld() && player->IsAlive()) player->CastSpell(player, SPELL_INSTANCE_RELEASE_ARRIVAL_VISUAL, true); return true; } private: ObjectGuid _playerGuid; }; } // namespace void InstanceRelease::LoadConfig() { _enabled = sConfigMgr->GetOption("InstanceRelease.Enable", true); _includeDungeons = sConfigMgr->GetOption("InstanceRelease.IncludeDungeons", true); _includeRaids = sConfigMgr->GetOption("InstanceRelease.IncludeRaids", true); _autoResurrect = sConfigMgr->GetOption("InstanceRelease.AutoResurrect", true); _resurrectVisual = sConfigMgr->GetOption("InstanceRelease.ResurrectionVisual", true); _arrivalVisual = sConfigMgr->GetOption("InstanceRelease.ArrivalVisual", true); } bool InstanceRelease::TryReleaseAtEntrance(Player* player) const { if (!_enabled) return false; Map* map = player->GetMap(); if (!map || !map->IsDungeon()) return false; if (map->IsRaid() ? !_includeRaids : !_includeDungeons) return false; AreaTriggerTeleport const* entrance = sObjectMgr->GetMapEntranceTrigger(player->GetMapId()); if (!entrance) return false; player->TeleportTo(entrance->target_mapId, entrance->target_X, entrance->target_Y, entrance->target_Z, entrance->target_Orientation); if (_autoResurrect && player->isDead()) { player->ResurrectPlayer(1.0f); player->SpawnCorpseBones(); if (_resurrectVisual) player->CastSpell(player, SPELL_RESURRECTION_VISUAL, true); if (_arrivalVisual) player->m_Events.AddEvent(new DelayedArrivalVisualEvent(player->GetGUID()), player->m_Events.CalculateTime(250)); } else if (player->isDead()) { WorldPacket data(SMSG_DEATH_RELEASE_LOC, 4 * 4); data << entrance->target_mapId; data << entrance->target_X; data << entrance->target_Y; data << entrance->target_Z; player->SendDirectMessage(&data); } return true; } class InstanceRelease_Player : public PlayerScript { public: InstanceRelease_Player() : PlayerScript("InstanceRelease_Player") { } bool OnPlayerCanRepopAtGraveyard(Player* player) override { return !sInstanceRelease->TryReleaseAtEntrance(player); } }; class InstanceRelease_World : public WorldScript { public: InstanceRelease_World() : WorldScript("InstanceRelease_World") { } void OnBeforeConfigLoad(bool /*reload*/) override { sInstanceRelease->LoadConfig(); } }; void AddSC_mod_instance_release() { new InstanceRelease_Player(); new InstanceRelease_World(); }