#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();
}