Skip to content

Instantly share code, notes, and snippets.

@devovh
Forked from FALL1N1/gist:28e4a91aa1c85345aeed
Created July 10, 2014 08:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save devovh/b67f3ba96f25d561231e to your computer and use it in GitHub Desktop.
Save devovh/b67f3ba96f25d561231e to your computer and use it in GitHub Desktop.
From 2c514cb968fd65709f44e7c272d4570065f9be2b Mon Sep 17 00:00:00 2001
From: EmiliyanKurtseliyanski <emiliyan9604bg@gmail.com>
Date: Fri, 4 Apr 2014 00:43:08 -0700
Subject: [PATCH] Implement Arena Spectator
---
sql/Razer/Custom/rbac_permissions_Spectate.sql | 12 +
.../Custom/rbac_role_permissions_Spectate.sql | 12 +
src/server/game/Accounts/RBAC.h | 6 +
src/server/game/Battlegrounds/Battleground.cpp | 34 +-
src/server/game/Battlegrounds/Battleground.h | 9 +
src/server/game/Battlegrounds/BattlegroundMgr.h | 13 +-
src/server/game/Battlegrounds/SpectatorAddon.cpp | 219 ++++++++
src/server/game/Battlegrounds/SpectatorAddon.h | 95 ++++
src/server/game/Entities/GameObject/GameObject.cpp | 11 +
src/server/game/Entities/Player/Player.cpp | 170 +++++-
src/server/game/Entities/Player/Player.h | 16 +-
src/server/game/Entities/Unit/Unit.cpp | 95 +++-
src/server/game/Handlers/ChatHandler.cpp | 6 +
src/server/game/Maps/Map.cpp | 7 +
src/server/game/Miscellaneous/Language.h | 3 +
src/server/game/Scripting/ScriptLoader.cpp | 4 +-
src/server/game/Spells/Spell.cpp | 14 +
src/server/scripts/Commands/cs_gm.cpp | 2 +
src/server/scripts/Custom/CMakeLists.txt | 1 +
src/server/scripts/Custom/arena_spectator.cpp | 611 +++++++++++++++++++++
20 files changed, 1315 insertions(+), 25 deletions(-)
create mode 100644 sql/Razer/Custom/rbac_permissions_Spectate.sql
create mode 100644 sql/Razer/Custom/rbac_role_permissions_Spectate.sql
create mode 100644 src/server/game/Battlegrounds/SpectatorAddon.cpp
create mode 100644 src/server/game/Battlegrounds/SpectatorAddon.h
create mode 100644 src/server/scripts/Custom/arena_spectator.cpp
diff --git a/sql/Razer/Custom/rbac_permissions_Spectate.sql b/sql/Razer/Custom/rbac_permissions_Spectate.sql
new file mode 100644
index 0000000..a71947f
--- /dev/null
+++ b/sql/Razer/Custom/rbac_permissions_Spectate.sql
@@ -0,0 +1,12 @@
+/*
+SQLyog Ultimate v11.11 (64 bit)
+MySQL - 5.6.12-log
+*********************************************************************
+*/
+/*!40101 SET NAMES utf8 */;
+
+insert into `rbac_permissions` (`id`, `name`) values('1000','spectate');
+insert into `rbac_permissions` (`id`, `name`) values('1001','spectate player');
+insert into `rbac_permissions` (`id`, `name`) values('1002','spectate view');
+insert into `rbac_permissions` (`id`, `name`) values('1003','spectate reset');
+insert into `rbac_permissions` (`id`, `name`) values('1004','spectate leave');
diff --git a/sql/Razer/Custom/rbac_role_permissions_Spectate.sql b/sql/Razer/Custom/rbac_role_permissions_Spectate.sql
new file mode 100644
index 0000000..8af12a8
--- /dev/null
+++ b/sql/Razer/Custom/rbac_role_permissions_Spectate.sql
@@ -0,0 +1,12 @@
+/*
+SQLyog Ultimate v11.11 (64 bit)
+MySQL - 5.6.12-log
+*********************************************************************
+*/
+/*!40101 SET NAMES utf8 */;
+
+insert into `rbac_role_permissions` (`roleId`, `permissionId`, `command`) values('1','1000','spectate');
+insert into `rbac_role_permissions` (`roleId`, `permissionId`, `command`) values('1','1001','spectate player');
+insert into `rbac_role_permissions` (`roleId`, `permissionId`, `command`) values('1','1002','spectate view');
+insert into `rbac_role_permissions` (`roleId`, `permissionId`, `command`) values('1','1003','spectate reset');
+insert into `rbac_role_permissions` (`roleId`, `permissionId`, `command`) values('1','1004','spectate leave');
diff --git a/src/server/game/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h
index dc5a276..d28e0b5 100644
--- a/src/server/game/Accounts/RBAC.h
+++ b/src/server/game/Accounts/RBAC.h
@@ -683,6 +683,12 @@ enum RBACPermissions
RBAC_PERM_COMMAND_MAILBOX = 777,
// custom permissions 1000+
+ RBAC_PERM_COMMAND_SPECTATE = 1000,
+ RBAC_PERM_COMMAND_SPECTATE_PLAYER = 1001,
+ RBAC_PERM_COMMAND_SPECTATE_VIEW = 1002,
+ RBAC_PERM_COMMAND_SPECTATE_RESET = 1003,
+ RBAC_PERM_COMMAND_SPECTATE_LEAVE = 1004,
+
RBAC_PERM_MAX
};
diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp
index c4aa3ef..908ff30 100644
--- a/src/server/game/Battlegrounds/Battleground.cpp
+++ b/src/server/game/Battlegrounds/Battleground.cpp
@@ -1252,14 +1252,23 @@ void Battleground::EventPlayerLoggedOut(Player* player)
m_Players[guid].OfflineRemoveTime = sWorld->GetGameTime() + MAX_OFFLINE_TIME;
if (GetStatus() == STATUS_IN_PROGRESS)
{
- // drop flag and handle other cleanups
- RemovePlayer(player, guid, GetPlayerTeam(guid));
-
- // 1 player is logging out, if it is the last, then end arena!
- if (isArena())
- if (GetAlivePlayersCountByTeam(player->GetBGTeam()) <= 1 && GetPlayersCountByTeam(GetOtherTeam(player->GetBGTeam())))
- EndBattleground(GetOtherTeam(player->GetBGTeam()));
- }
+ if (!player->IsSpectator())
+ {
+ // drop flag and handle other cleanups
+ RemovePlayer(player, guid, GetPlayerTeam(guid));
+ // 1 player is logging out, if it is the last, then end arena!
+ if (isArena())
+ if (GetAlivePlayersCountByTeam(player->GetTeam()) <= 1 && GetPlayersCountByTeam(GetOtherTeam(player->GetTeam())))
+ EndBattleground(GetOtherTeam(player->GetTeam()));
+ }
+ }
+ if (!player->IsSpectator())
+ player->LeaveBattleground();
+ else
+ {
+ player->TeleportToBGEntryPoint();
+ RemoveSpectator(player->GetGUID());
+ }
}
// This method should be called only once ... it adds pointer to queue
@@ -1967,3 +1976,12 @@ bool Battleground::CheckAchievementCriteriaMeet(uint32 criteriaId, Player const*
TC_LOG_ERROR("bg.battleground", "Battleground::CheckAchievementCriteriaMeet: No implementation for criteria %u", criteriaId);
return false;
}
+
+void Battleground::SendSpectateAddonsMsg(SpectatorAddonMsg msg)
+{
+ if (!HaveSpectators())
+ return;
+
+ for (SpectatorList::iterator itr = m_Spectators.begin(); itr != m_Spectators.end(); ++itr)
+ msg.SendPacket(*itr);
+}
\ No newline at end of file
diff --git a/src/server/game/Battlegrounds/Battleground.h b/src/server/game/Battlegrounds/Battleground.h
index 5e2aa4b..dd85358 100644
--- a/src/server/game/Battlegrounds/Battleground.h
+++ b/src/server/game/Battlegrounds/Battleground.h
@@ -22,6 +22,7 @@
#include "Common.h"
#include "SharedDefines.h"
#include "DBCEnums.h"
+#include "SpectatorAddon.h"
class Creature;
class GameObject;
@@ -359,6 +360,12 @@ class Battleground
bool HasFreeSlots() const;
uint32 GetFreeSlotsForTeam(uint32 Team) const;
+ typedef std::set<uint32> SpectatorList;
+ void AddSpectator(uint32 playerId) { m_Spectators.insert(playerId); }
+ void RemoveSpectator(uint32 playerId) { m_Spectators.erase(playerId); }
+ bool HaveSpectators() { return (m_Spectators.size() > 0); }
+ void SendSpectateAddonsMsg(SpectatorAddonMsg msg);
+
bool isArena() const { return m_IsArena; }
bool isBattleground() const { return !m_IsArena; }
bool isRated() const { return m_IsRated; }
@@ -645,6 +652,8 @@ class Battleground
// Raid Group
Group* m_BgRaids[BG_TEAMS_COUNT]; // 0 - alliance, 1 - horde
+ SpectatorList m_Spectators;
+
// Players count by team
uint32 m_PlayersCount[BG_TEAMS_COUNT];
diff --git a/src/server/game/Battlegrounds/BattlegroundMgr.h b/src/server/game/Battlegrounds/BattlegroundMgr.h
index 4280362..7564b50 100644
--- a/src/server/game/Battlegrounds/BattlegroundMgr.h
+++ b/src/server/game/Battlegrounds/BattlegroundMgr.h
@@ -89,6 +89,7 @@ class BattlegroundMgr
Battleground* GetBattleground(uint32 InstanceID, BattlegroundTypeId bgTypeId);
Battleground* GetBattlegroundTemplate(BattlegroundTypeId bgTypeId);
Battleground* CreateNewBattleground(BattlegroundTypeId bgTypeId, PvPDifficultyEntry const* bracketEntry, uint8 arenaType, bool isRated);
+ BattlegroundContainer GetBattlegroundsByType(BattlegroundTypeId bgTypeId) { return m_Battlegrounds[bgTypeId]; }
void AddBattleground(Battleground* bg);
void RemoveBattleground(BattlegroundTypeId bgTypeId, uint32 instanceId);
@@ -113,6 +114,7 @@ class BattlegroundMgr
bool isArenaTesting() const { return m_ArenaTesting; }
bool isTesting() const { return m_Testing; }
+ bool IsArenaType(BattlegroundTypeId bgTypeId);
static BattlegroundQueueTypeId BGQueueTypeId(BattlegroundTypeId bgTypeId, uint8 arenaType);
static BattlegroundTypeId BGTemplateId(BattlegroundQueueTypeId bgQueueTypeId);
@@ -121,6 +123,14 @@ class BattlegroundMgr
static HolidayIds BGTypeToWeekendHolidayId(BattlegroundTypeId bgTypeId);
static BattlegroundTypeId WeekendHolidayIdToBGType(HolidayIds holiday);
static bool IsBGWeekend(BattlegroundTypeId bgTypeId);
+ BattlegroundData* GetAllBattlegroundsWithTypeId(BattlegroundTypeId bgTypeId)
+ {
+ BattlegroundDataContainer::iterator it = bgDataStore.find(bgTypeId);
+ if (it == bgDataStore.end())
+ return NULL;
+
+ return &it->second;
+ }
uint32 GetMaxRatingDifference() const;
uint32 GetRatingDiscardTimer() const;
@@ -138,12 +148,11 @@ class BattlegroundMgr
private:
bool CreateBattleground(CreateBattlegroundData& data);
uint32 CreateClientVisibleInstanceId(BattlegroundTypeId bgTypeId, BattlegroundBracketId bracket_id);
- static bool IsArenaType(BattlegroundTypeId bgTypeId);
BattlegroundTypeId GetRandomBG(BattlegroundTypeId id);
typedef std::map<BattlegroundTypeId, BattlegroundData> BattlegroundDataContainer;
BattlegroundDataContainer bgDataStore;
-
+ BattlegroundContainer m_Battlegrounds[MAX_BATTLEGROUND_TYPE_ID];
BattlegroundQueue m_BattlegroundQueues[MAX_BATTLEGROUND_QUEUE_TYPES];
typedef std::map<BattlegroundTypeId, uint8> BattlegroundSelectionWeightMap; // TypeId and its selectionWeight
diff --git a/src/server/game/Battlegrounds/SpectatorAddon.cpp b/src/server/game/Battlegrounds/SpectatorAddon.cpp
new file mode 100644
index 0000000..b00b8a4
--- /dev/null
+++ b/src/server/game/Battlegrounds/SpectatorAddon.cpp
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2008-2012 TrinityCore <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "Player.h"
+#include "Item.h"
+#include "SpellInfo.h"
+
+SpectatorAddonMsg::SpectatorAddonMsg()
+{
+ for (uint8 i = 0; i < SPECTATOR_PREFIX_COUNT; ++i)
+ prefixFlags[i] = false;
+
+ player = "";
+ target = "";
+ isAlive = false;
+ pClass = CLASS_WARRIOR;
+ maxHP = 0;
+ maxPower = 0;
+ currHP = 0;
+ currPower = 0;
+ powerType = POWER_MANA;
+ spellId = 0;
+ castTime = 0;
+ team = ALLIANCE;
+}
+
+bool SpectatorAddonMsg::CanSandAura(uint32 auraID)
+{
+ const SpellInfo *spell = sSpellMgr->GetSpellInfo(auraID);
+ if (!spell)
+ return false;
+
+ if (spell->SpellIconID == 1)
+ return false;
+
+ return true;
+}
+
+void SpectatorAddonMsg::CreateAura(uint32 _caster, uint32 _spellId, bool _isDebuff, uint8 _type, int32 _duration, int32 _expire, uint16 _stack, bool _isRemove)
+{
+ if (!CanSandAura(_spellId))
+ return;
+
+ aCaster = _caster;
+ aSpellId = _spellId;
+ aIsDebuff = _isDebuff;
+ aType = _type;
+ aDuration = _duration;
+ aExpire = _expire;
+ aStack = _stack;
+ aRemove = _isRemove;
+ EnableFlag(SPECTATOR_PREFIX_AURA);
+}
+
+std::string SpectatorAddonMsg::GetMsgData()
+{
+ std::string addonData = "";
+
+ if (!isFilledIn(SPECTATOR_PREFIX_PLAYER))
+ {
+ TC_LOG_ERROR("bg.battleground", "SPECTATOR ADDON: player is not filled in.");
+ return addonData;
+ }
+
+ std::string msg = "";
+ for (uint8 i = 0; i < SPECTATOR_PREFIX_COUNT; ++i)
+ if (isFilledIn(i))
+ {
+ switch (i)
+ {
+ case SPECTATOR_PREFIX_PLAYER:
+ msg += player + ";";
+ break;
+ case SPECTATOR_PREFIX_TARGET:
+ msg += "TRG=" + target + ";";
+ break;
+ case SPECTATOR_PREFIX_TEAM:
+ {
+ char buffer[20];
+ sprintf(buffer, "TEM=%i;", (uint16)team);
+ msg += buffer;
+ break;
+ }
+ case SPECTATOR_PREFIX_STATUS:
+ {
+ char buffer[20];
+ sprintf(buffer, "STA=%d;", isAlive);
+ msg += buffer;
+ break;
+ }
+ case SPECTATOR_PREFIX_CLASS:
+ {
+ char buffer[20];
+ sprintf(buffer, "CLA=%i;", (int)pClass);
+ msg += buffer;
+ break;
+ }
+ case SPECTATOR_PREFIX_MAXHP:
+ {
+ char buffer[30];
+ sprintf(buffer, "MHP=%i;", maxHP);
+ msg += buffer;
+ break;
+ }
+ case SPECTATOR_PREFIX_CURHP:
+ {
+ char buffer[30];
+ sprintf(buffer, "CHP=%i;", currHP);
+ msg += buffer;
+ break;
+ }
+ case SPECTATOR_PREFIX_MAXPOWER:
+ {
+ char buffer[30];
+ sprintf(buffer, "MPW=%i;", maxPower);
+ msg += buffer;
+ break;
+ }
+ case SPECTATOR_PREFIX_CURPOWER:
+ {
+ char buffer[30];
+ sprintf(buffer, "CPW=%i;", currPower);
+ msg += buffer;
+ break;
+ }
+ case SPECTATOR_PREFIX_POWERTYPE:
+ {
+ char buffer[20];
+ sprintf(buffer, "PWT=%i;", (uint8)powerType);
+ msg += buffer;
+ break;
+ }
+ case SPECTATOR_PREFIX_SPELL:
+ {
+ char buffer[80];
+ sprintf(buffer, "SPE=%i,%i;", spellId, castTime);
+ msg += buffer;
+ break;
+ }
+ case SPECTATOR_PREFIX_AURA:
+ {
+ char buffer[300];
+ sprintf(buffer, "AUR=%i,%i,%i,%i,%i,%i,%i,0x%X;", aRemove, aStack,
+ aExpire, aDuration,
+ aSpellId, aType,
+ aIsDebuff, aCaster);
+ msg += buffer;
+ break;
+ }
+ }
+ }
+
+ if (msg != "")
+ addonData = "ARENASPEC " + msg;
+
+ return addonData;
+}
+
+bool SpectatorAddonMsg::SendPacket(uint32 receiver)
+{
+ std::string addonData = GetMsgData();
+ if (addonData == "")
+ return false;
+
+ Player* rPlayer = ObjectAccessor::FindPlayer(receiver);
+ if (!rPlayer)
+ return false;
+
+ WorldPacket data(SMSG_MESSAGECHAT, 200);
+ data << uint8(CHAT_MSG_WHISPER);
+ data << uint32(LANG_ADDON);
+ data << uint64(0);
+ data << uint32(LANG_ADDON); //language 2.1.0 ?
+ data << uint64(0);
+ data << uint32(addonData.length() + 1);
+ data << addonData;
+ data << uint8(CHAT_TAG_NONE);
+ rPlayer->GetSession()->SendPacket(&data);
+
+ return true;
+}
+
+bool SpectatorAddonMsg::SendPacket(SpectatorAddonMsg msg, uint32 receiver)
+{
+ std::string addonData = msg.GetMsgData();
+ if (addonData == "")
+ return false;
+
+ Player* rPlayer = ObjectAccessor::FindPlayer(receiver);
+ if (!rPlayer)
+ return false;
+
+ WorldPacket data(SMSG_MESSAGECHAT, 200);
+ data << uint8(CHAT_MSG_WHISPER);
+ data << uint32(LANG_ADDON);
+ data << uint64(0);
+ data << uint32(LANG_ADDON); //language 2.1.0 ?
+ data << uint64(0);
+ data << uint32(addonData.length() + 1);
+ data << addonData;
+ data << uint8(CHAT_TAG_NONE);
+ rPlayer->GetSession()->SendPacket(&data);
+
+ return true;
+}
\ No newline at end of file
diff --git a/src/server/game/Battlegrounds/SpectatorAddon.h b/src/server/game/Battlegrounds/SpectatorAddon.h
new file mode 100644
index 0000000..9228ecc
--- /dev/null
+++ b/src/server/game/Battlegrounds/SpectatorAddon.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2008-2012 TrinityCore <http://www.trinitycore.org/>
+ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define SPECTATOR_ADDON_SPELL_INTERUPTED 99999 // specific addons
+#define SPECTATOR_ADDON_SPELL_CANCELED 99998 // numbers =\
+
+enum SpectatorPrefix {
+ SPECTATOR_PREFIX_PLAYER,
+ SPECTATOR_PREFIX_STATUS,
+ SPECTATOR_PREFIX_MAXHP,
+ SPECTATOR_PREFIX_CURHP,
+ SPECTATOR_PREFIX_MAXPOWER,
+ SPECTATOR_PREFIX_CURPOWER,
+ SPECTATOR_PREFIX_POWERTYPE,
+ SPECTATOR_PREFIX_TARGET,
+ SPECTATOR_PREFIX_CLASS,
+ SPECTATOR_PREFIX_TEAM,
+ SPECTATOR_PREFIX_SPELL,
+ SPECTATOR_PREFIX_AURA,
+ SPECTATOR_PREFIX_COUNT // must be at the end of list
+};
+
+class SpectatorAddonMsg {
+ public:
+ SpectatorAddonMsg();
+
+ void SetPlayer(std::string _player) { player = _player; EnableFlag(SPECTATOR_PREFIX_PLAYER); }
+ void SetStatus(bool _isAlive) { isAlive = _isAlive; EnableFlag(SPECTATOR_PREFIX_STATUS); }
+ void SetClass(uint8 _class) { pClass = _class; EnableFlag(SPECTATOR_PREFIX_CLASS); }
+ void SetTarget(std::string _target) { target = _target; EnableFlag(SPECTATOR_PREFIX_TARGET); }
+ void SetTeam(uint32 _team) { team = _team; EnableFlag(SPECTATOR_PREFIX_TEAM); }
+
+ void SetMaxHP(uint16 hp) { maxHP = hp; EnableFlag(SPECTATOR_PREFIX_MAXHP); }
+ void SetCurrentHP(uint16 hp) { currHP = hp; EnableFlag(SPECTATOR_PREFIX_CURHP); }
+ void SetMaxPower(uint16 power) { maxPower = power; EnableFlag(SPECTATOR_PREFIX_MAXPOWER); }
+ void SetCurrentPower(uint16 power) { currPower = power; EnableFlag(SPECTATOR_PREFIX_CURPOWER); }
+ void SetPowerType(Powers power) { powerType = power; EnableFlag(SPECTATOR_PREFIX_POWERTYPE); }
+
+ void CastSpell(uint32 _spellId, uint32 _castTime) { spellId = _spellId; castTime = _castTime; EnableFlag(SPECTATOR_PREFIX_SPELL); }
+ void CreateAura(uint32 _caster, uint32 _spellId, bool _isDebuff, uint8 _type, int32 _duration, int32 _expire, uint16 _stack, bool _isRemove);
+
+ static bool SendPacket(SpectatorAddonMsg msg, uint32 receiver);
+ bool SendPacket(uint32 receiver);
+
+ std::string GetMsgData();
+
+ bool isFilledIn(uint8 prefix) { return prefixFlags[prefix]; }
+
+ static bool CanSandAura(uint32 auraID);
+ private:
+
+ void EnableFlag(uint8 prefix) { prefixFlags[prefix] = true; }
+ std::string player;
+ bool isAlive;
+ std::string target;
+ uint8 pClass;
+
+ uint16 maxHP;
+ uint16 maxPower;
+ uint16 currHP;
+ uint16 currPower;
+ Powers powerType;
+
+ uint32 spellId;
+ uint32 castTime;
+
+ uint32 team;
+
+ // aura data
+ uint32 aCaster;
+ uint32 aSpellId;
+ bool aIsDebuff;
+ uint8 aType;
+ int32 aDuration;
+ int32 aExpire;
+ uint16 aStack;
+ bool aRemove;
+
+ bool prefixFlags[SPECTATOR_PREFIX_COUNT];
+};
\ No newline at end of file
diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp
index 67f7b1a..9627716 100644
--- a/src/server/game/Entities/GameObject/GameObject.cpp
+++ b/src/server/game/Entities/GameObject/GameObject.cpp
@@ -513,6 +513,11 @@ void GameObject::Update(uint32 diff)
if (ok)
{
+
+ if (Player *tmpPlayer = ok->ToPlayer())
+ if (tmpPlayer->IsSpectator())
+ return;
+
// some traps do not have spell but should be triggered
if (goInfo->trap.spellId)
CastSpell(ok, goInfo->trap.spellId);
@@ -1720,6 +1725,12 @@ void GameObject::Use(Unit* user)
void GameObject::CastSpell(Unit* target, uint32 spellId)
{
+
+ if (target)
+ if (Player *tmpPlayer = target->ToPlayer())
+ if (tmpPlayer->IsSpectator())
+ return;
+
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
return;
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index e8bb798..d85ff78 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -891,7 +891,9 @@ Player::Player(WorldSession* session): Unit(true)
m_MonthlyQuestChanged = false;
m_SeasonalQuestChanged = false;
-
+ spectatorFlag = false;
+ spectateCanceled = false;
+ spectateFrom = NULL;
SetPendingBind(0, 0);
_activeCheats = CHEAT_NONE;
@@ -1869,7 +1871,14 @@ void Player::setDeathState(DeathState s)
TC_LOG_ERROR("entities.player", "setDeathState: attempt to kill a dead player %s(%d)", GetName().c_str(), GetGUIDLow());
return;
}
-
+ // send spectate addon message
+ if (HaveSpectators())
+ {
+ SpectatorAddonMsg msg;
+ msg.SetPlayer(GetName());
+ msg.SetStatus(false);
+ SendSpectatorAddonMsgToBG(msg);
+ }
// drunken state is cleared on death
SetDrunkValue(0);
// lost combo points at any target (targeted combo points clear in Unit::setDeathState)
@@ -1914,6 +1923,20 @@ void Player::InnEnter(time_t time, uint32 mapid, float x, float y, float z)
time_inn_enter = time;
}
+void Player::SetSelection(uint64 guid)
+{
+ uint32 m_curSelection = guid;
+ SetUInt64Value(UNIT_FIELD_TARGET, guid);
+ if (Player *target = ObjectAccessor::FindPlayer(guid))
+ if (HaveSpectators())
+ {
+ SpectatorAddonMsg msg;
+ msg.SetPlayer(GetName());
+ msg.SetTarget(target->GetName());
+ SendSpectatorAddonMsgToBG(msg);
+ }
+}
+
bool Player::BuildEnumData(PreparedQueryResult result, WorldPacket* data)
{
// 0 1 2 3 4 5 6 7
@@ -2350,7 +2373,17 @@ bool Player::TeleportToBGEntryPoint()
ScheduleDelayedOperation(DELAYED_BG_MOUNT_RESTORE);
ScheduleDelayedOperation(DELAYED_BG_TAXI_RESTORE);
ScheduleDelayedOperation(DELAYED_BG_GROUP_RESTORE);
- return TeleportTo(m_bgData.joinPos);
+ Battleground *oldBg = GetBattleground();
+ bool result = TeleportTo(m_bgData.joinPos);
+
+ if (IsSpectator() && result)
+ {
+ SetSpectate(false);
+ if (oldBg)
+ oldBg->RemoveSpectator(GetGUID());
+ }
+
+ return result;
}
void Player::ProcessDelayedOperations()
@@ -2799,6 +2832,93 @@ void Player::SetInWater(bool apply)
getHostileRefManager().updateThreatTables();
}
+void Player::SetSpectate(bool on)
+{
+ if (on)
+ {
+ SetSpeed(MOVE_RUN, 5.0);
+ spectatorFlag = true;
+
+ m_ExtraFlags |= PLAYER_EXTRA_GM_ON;
+ setFaction(35);
+
+ if (Pet* pet = GetPet())
+ {
+ RemovePet(pet, PET_SAVE_AS_CURRENT);
+ }
+ UnsummonPetTemporaryIfAny();
+
+ RemoveByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP);
+ ResetContestedPvP();
+
+ getHostileRefManager().setOnlineOfflineState(false);
+ CombatStopWithPets();
+
+ m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_ADMINISTRATOR);
+ }
+ else
+ {
+ uint32 newPhase = 0;
+ AuraEffectList const& phases = GetAuraEffectsByType(SPELL_AURA_PHASE);
+ if (!phases.empty())
+ for (AuraEffectList::const_iterator itr = phases.begin(); itr != phases.end(); ++itr)
+ newPhase |= (*itr)->GetMiscValue();
+
+ if (!newPhase)
+ newPhase = PHASEMASK_NORMAL;
+
+ SetPhaseMask(newPhase, false);
+
+ m_ExtraFlags &= ~ PLAYER_EXTRA_GM_ON;
+ setFactionForRace(getRace());
+ RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_GM);
+ RemoveFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_ALLOW_CHEAT_SPELLS);
+
+ if (spectateFrom)
+ SetViewpoint(spectateFrom, false);
+
+ // restore FFA PvP Server state
+ if (sWorld->IsFFAPvPRealm())
+ SetByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP);
+
+ // restore FFA PvP area state, remove not allowed for GM mounts
+ UpdateArea(m_areaUpdateId);
+
+ getHostileRefManager().setOnlineOfflineState(true);
+ m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER);
+ spectateCanceled = false;
+ spectatorFlag = false;
+ RestoreDisplayId();
+ UpdateSpeed(MOVE_RUN, true);
+ }
+ UpdateObjectVisibility();
+}
+
+bool Player::HaveSpectators()
+{
+ if (IsSpectator())
+ return false;
+
+ if (Battleground *bg = GetBattleground())
+ if (bg->isArena())
+ {
+ if (bg->GetStatus() != STATUS_IN_PROGRESS)
+ return false;
+
+ return bg->HaveSpectators();
+ }
+
+ return false;
+}
+
+void Player::SendSpectatorAddonMsgToBG(SpectatorAddonMsg msg)
+{
+ if (!HaveSpectators())
+ return;
+
+ GetBattleground()->SendSpectateAddonsMsg(msg);
+}
+
void Player::SetGameMaster(bool on)
{
if (on)
@@ -23196,7 +23316,28 @@ void Player::SendAurasForTarget(Unit* target)
AuraApplication * auraApp = itr->second;
auraApp->BuildUpdatePacket(data, false);
}
-
+ if (Player *stream = target->ToPlayer())
+ if (stream->HaveSpectators() && IsSpectator())
+ {
+ for (Unit::VisibleAuraMap::const_iterator itr = visibleAuras->begin(); itr != visibleAuras->end(); ++itr)
+ {
+ AuraApplication * auraApp = itr->second;
+ auraApp->BuildUpdatePacket(data, false);
+ if (Aura* aura = auraApp->GetBase())
+ {
+ SpectatorAddonMsg msg;
+ uint64 casterID = 0;
+ if (aura->GetCaster())
+ casterID = (aura->GetCaster()->ToPlayer()) ? aura->GetCaster()->GetGUID() : 0;
+ msg.SetPlayer(stream->GetName());
+ msg.CreateAura(casterID, aura->GetSpellInfo()->Id,
+ aura->GetSpellInfo()->IsPositive(), aura->GetSpellInfo()->Dispel,
+ aura->GetDuration(), aura->GetMaxDuration(),
+ aura->GetStackAmount(), false);
+ msg.SendPacket(GetGUID());
+ }
+ }
+ }
GetSession()->SendPacket(&data);
}
@@ -24329,6 +24470,15 @@ void Player::SetViewpoint(WorldObject* target, bool apply)
{
if (apply)
{
+ if (target->ToPlayer() == this)
+ return;
+
+ //remove Viewpoint if already have
+ if (IsSpectator() && spectateFrom)
+ {
+ SetViewpoint(spectateFrom, false);
+ spectateFrom = NULL;
+ }
TC_LOG_DEBUG("maps", "Player::CreateViewpoint: Player %s create seer %u (TypeId: %u).", GetName().c_str(), target->GetEntry(), target->GetTypeId());
if (!AddUInt64Value(PLAYER_FARSIGHT, target->GetGUID()))
@@ -24341,10 +24491,17 @@ void Player::SetViewpoint(WorldObject* target, bool apply)
UpdateVisibilityOf(target);
if (target->isType(TYPEMASK_UNIT) && !GetVehicle())
- ((Unit*)target)->AddPlayerToVision(this);
+ {
+ if (IsSpectator())
+ spectateFrom = (Unit*)target;
+
+ ((Unit*)target)->AddPlayerToVision(this);
+ }
}
else
{
+ if (IsSpectator() && !spectateFrom)
+ return;
TC_LOG_DEBUG("maps", "Player::CreateViewpoint: Player %s remove seer", GetName().c_str());
if (!RemoveUInt64Value(PLAYER_FARSIGHT, target->GetGUID()))
@@ -24355,7 +24512,8 @@ void Player::SetViewpoint(WorldObject* target, bool apply)
if (target->isType(TYPEMASK_UNIT) && !GetVehicle())
((Unit*)target)->RemovePlayerFromVision(this);
-
+ if (IsSpectator())
+ spectateFrom = NULL;
//must immediately set seer back otherwise may crash
m_seer = this;
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index 2eb740f..49e7b6e 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -28,6 +28,7 @@
#include "QuestDef.h"
#include "SpellMgr.h"
#include "Unit.h"
+#include "Battleground.h"
#include <string>
#include <vector>
@@ -1128,7 +1129,7 @@ class Player : public Unit, public GridObject<Player>
// mount_id can be used in scripting calls
bool isAcceptWhispers() const { return m_ExtraFlags & PLAYER_EXTRA_ACCEPT_WHISPERS; }
void SetAcceptWhispers(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_ACCEPT_WHISPERS; else m_ExtraFlags &= ~PLAYER_EXTRA_ACCEPT_WHISPERS; }
- bool IsGameMaster() const { return m_ExtraFlags & PLAYER_EXTRA_GM_ON; }
+ bool IsGameMaster() const { return ((m_ExtraFlags & PLAYER_EXTRA_GM_ON)); }
void SetGameMaster(bool on);
bool isGMChat() const { return m_ExtraFlags & PLAYER_EXTRA_GM_CHAT; }
void SetGMChat(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_GM_CHAT; else m_ExtraFlags &= ~PLAYER_EXTRA_GM_CHAT; }
@@ -1139,6 +1140,13 @@ class Player : public Unit, public GridObject<Player>
bool Has310Flyer(bool checkAllSpells, uint32 excludeSpellId = 0);
void SetHas310Flyer(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_HAS_310_FLYER; else m_ExtraFlags &= ~PLAYER_EXTRA_HAS_310_FLYER; }
void SetPvPDeath(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_PVP_DEATH; else m_ExtraFlags &= ~PLAYER_EXTRA_PVP_DEATH; }
+ bool HaveSpectators();
+ void SendSpectatorAddonMsgToBG(SpectatorAddonMsg msg);
+ bool isSpectateCanceled() { return spectateCanceled; }
+ void CancelSpectate() { spectateCanceled = true; }
+ Unit* getSpectateFrom() { return spectateFrom; }
+ bool IsSpectator() const { return spectatorFlag; }
+ void SetSpectate(bool on);
void GiveXP(uint32 xp, Unit* victim, float group_rate=1.0f);
void GiveLevel(uint8 level);
@@ -1512,7 +1520,7 @@ class Player : public Unit, public GridObject<Player>
Player* GetSelectedPlayer() const;
void SetTarget(uint64 /*guid*/) OVERRIDE { } /// Used for serverside target changes, does not apply to players
- void SetSelection(uint64 guid) { SetUInt64Value(UNIT_FIELD_TARGET, guid); }
+ void SetSelection(uint64 guid);
uint8 GetComboPoints() const { return m_comboPoints; }
uint64 GetComboTarget() const { return m_comboTarget; }
@@ -2623,7 +2631,9 @@ class Player : public Unit, public GridObject<Player>
InstanceTimeMap _instanceResetTimes;
uint32 _pendingBindId;
uint32 _pendingBindTimer;
-
+ bool spectatorFlag;
+ bool spectateCanceled;
+ Unit *spectateFrom;
uint32 _activeCheats;
};
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index 98072e1..0d5eb06 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -293,6 +293,19 @@ Unit::~Unit()
m_currentSpells[i] = NULL;
}
+ // remove view point for spectator
+ if (!m_sharedVision.empty())
+ {
+ for (SharedVisionList::iterator itr = m_sharedVision.begin(); itr != m_sharedVision.end(); ++itr)
+ if ((*itr)->IsSpectator() && (*itr)->getSpectateFrom())
+ {
+ (*itr)->SetViewpoint((*itr)->getSpectateFrom(), false);
+ if (m_sharedVision.empty())
+ break;
+ --itr;
+ }
+ }
+
_DeleteRemovedAuras();
delete i_motionMaster;
@@ -497,14 +510,50 @@ AuraApplication * Unit::GetVisibleAura(uint8 slot) const
void Unit::SetVisibleAura(uint8 slot, AuraApplication * aur)
{
- m_visibleAuras[slot]=aur;
- UpdateAuraForGroup(slot);
+ if (Aura* aura = aur->GetBase())
+ if (Player *player = ToPlayer())
+ if (player->HaveSpectators() && slot < MAX_AURAS)
+ {
+ SpectatorAddonMsg msg;
+ uint64 casterID = 0;
+ if (aura->GetCaster())
+ casterID = (aura->GetCaster()->ToPlayer()) ? aura->GetCaster()->GetGUID() : 0;
+ msg.SetPlayer(player->GetName());
+ msg.CreateAura(casterID, aura->GetSpellInfo()->Id,
+ aura->GetSpellInfo()->IsPositive(), aura->GetSpellInfo()->Dispel,
+ aura->GetDuration(), aura->GetMaxDuration(),
+ aura->GetStackAmount(), false);
+ player->SendSpectatorAddonMsgToBG(msg);
+ }
+
+ m_visibleAuras[slot] = aur;
+ UpdateAuraForGroup(slot);
}
void Unit::RemoveVisibleAura(uint8 slot)
{
- m_visibleAuras.erase(slot);
- UpdateAuraForGroup(slot);
+ AuraApplication *aurApp = GetVisibleAura(slot);
+ if (aurApp && slot < MAX_AURAS)
+ {
+ if (Aura* aura = aurApp->GetBase())
+ if (Player *player = ToPlayer())
+ if (player->HaveSpectators())
+ {
+ SpectatorAddonMsg msg;
+ uint64 casterID = 0;
+ if (aura->GetCaster())
+ casterID = (aura->GetCaster()->ToPlayer()) ? aura->GetCaster()->GetGUID() : 0;
+ msg.SetPlayer(player->GetName());
+ msg.CreateAura(casterID, aura->GetSpellInfo()->Id,
+ aurApp->IsPositive(), aura->GetSpellInfo()->Dispel,
+ aura->GetDuration(), aura->GetMaxDuration(),
+ aura->GetStackAmount(), true);
+ player->SendSpectatorAddonMsgToBG(msg);
+ }
+ }
+
+ m_visibleAuras.erase(slot);
+ UpdateAuraForGroup(slot);
}
void Unit::UpdateInterruptMask()
@@ -13294,6 +13343,15 @@ void Unit::SetHealth(uint32 val)
// group update
if (Player* player = ToPlayer())
{
+
+ if (player->HaveSpectators())
+ {
+ SpectatorAddonMsg msg;
+ msg.SetPlayer(player->GetName());
+ msg.SetCurrentHP(val);
+ player->SendSpectatorAddonMsgToBG(msg);
+ }
+
if (player->GetGroup())
player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_CUR_HP);
}
@@ -13319,6 +13377,15 @@ void Unit::SetMaxHealth(uint32 val)
// group update
if (GetTypeId() == TYPEID_PLAYER)
{
+
+ if (ToPlayer()->HaveSpectators())
+ {
+ SpectatorAddonMsg msg;
+ msg.SetPlayer(ToPlayer()->GetName());
+ msg.SetMaxHP(val);
+ ToPlayer()->SendSpectatorAddonMsgToBG(msg);
+ }
+
if (ToPlayer()->GetGroup())
ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_MAX_HP);
}
@@ -13356,6 +13423,16 @@ void Unit::SetPower(Powers power, uint32 val)
// group update
if (Player* player = ToPlayer())
{
+
+ if (player->HaveSpectators())
+ {
+ SpectatorAddonMsg msg;
+ msg.SetPlayer(player->GetName());
+ msg.SetCurrentPower(val);
+ msg.SetPowerType(power);
+ player->SendSpectatorAddonMsgToBG(msg);
+ }
+
if (player->GetGroup())
player->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_CUR_POWER);
}
@@ -13382,6 +13459,16 @@ void Unit::SetMaxPower(Powers power, uint32 val)
// group update
if (GetTypeId() == TYPEID_PLAYER)
{
+
+ if (ToPlayer()->HaveSpectators())
+ {
+ SpectatorAddonMsg msg;
+ msg.SetPlayer(ToPlayer()->GetName());
+ msg.SetMaxPower(val);
+ msg.SetPowerType(power);
+ ToPlayer()->SendSpectatorAddonMsgToBG(msg);
+ }
+
if (ToPlayer()->GetGroup())
ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_MAX_POWER);
}
diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp
index fbc7856..7f4c0bc 100644
--- a/src/server/game/Handlers/ChatHandler.cpp
+++ b/src/server/game/Handlers/ChatHandler.cpp
@@ -561,6 +561,12 @@ void WorldSession::HandleTextEmoteOpcode(WorldPacket& recvData)
recvData >> emoteNum;
recvData >> guid;
+ if (GetPlayer()->IsSpectator())
+ {
+ SendNotification(LANG_SPEC_CAN_NOT_CHAT);
+ return;
+ }
+
sScriptMgr->OnPlayerTextEmote(GetPlayer(), text_emote, emoteNum, guid);
EmotesTextEntry const* em = sEmotesTextStore.LookupEntry(text_emote);
diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp
index 6865764..3c8a102 100644
--- a/src/server/game/Maps/Map.cpp
+++ b/src/server/game/Maps/Map.cpp
@@ -3254,6 +3254,13 @@ bool BattlegroundMap::AddPlayerToMap(Player* player)
void BattlegroundMap::RemovePlayerFromMap(Player* player, bool remove)
{
+ if (player && player->IsSpectator() && !player->isSpectateCanceled())
+ {
+ if (GetBG())
+ GetBG()->RemoveSpectator(player->GetGUID());
+ player->SetSpectate(false);
+ }
+
TC_LOG_INFO("maps", "MAP: Removing player '%s' from bg '%u' of map '%s' before relocating to another map", player->GetName().c_str(), GetInstanceId(), GetMapName());
Map::RemovePlayerFromMap(player, remove);
}
diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h
index 3102e2c..0742db1 100644
--- a/src/server/game/Miscellaneous/Language.h
+++ b/src/server/game/Miscellaneous/Language.h
@@ -844,6 +844,9 @@ enum TrinityStrings
LANG_RBAC_EMAIL_REQUIRED = 881,
// Room for in-game strings 882-999 not used
+ // Arena Spectator (Emote Disable)
+ LANG_SPEC_CAN_NOT_CHAT = 900,
+
// Level 4 (CLI only commands)
LANG_COMMAND_EXIT = 1000,
LANG_ACCOUNT_DELETED = 1001,
diff --git a/src/server/game/Scripting/ScriptLoader.cpp b/src/server/game/Scripting/ScriptLoader.cpp
index 7365d59..7bf5dbd 100644
--- a/src/server/game/Scripting/ScriptLoader.cpp
+++ b/src/server/game/Scripting/ScriptLoader.cpp
@@ -1436,13 +1436,13 @@ void AddBattlegroundScripts()
#ifdef SCRIPTS
/* This is where custom scripts' loading functions should be declared. */
-
+void AddSC_arena_spectator_script();
#endif
void AddCustomScripts()
{
#ifdef SCRIPTS
/* This is where custom scripts should be added. */
-
+AddSC_arena_spectator_script();
#endif
}
diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp
index 124c1c2..b54f0ef 100644
--- a/src/server/game/Spells/Spell.cpp
+++ b/src/server/game/Spells/Spell.cpp
@@ -2920,6 +2920,16 @@ void Spell::prepare(SpellCastTargets const* targets, AuraEffect const* triggered
TC_LOG_DEBUG("spells", "Spell::prepare: spell id %u source %u caster %d customCastFlags %u mask %u", m_spellInfo->Id, m_caster->GetEntry(), m_originalCaster ? m_originalCaster->GetEntry() : -1, _triggeredCastFlags, m_targets.GetTargetMask());
+ if (GetCaster() && GetSpellInfo())
+ if (Player *tmpPlayer = GetCaster()->ToPlayer())
+ if (tmpPlayer->HaveSpectators())
+ {
+ SpectatorAddonMsg msg;
+ msg.SetPlayer(tmpPlayer->GetName());
+ msg.CastSpell(GetSpellInfo()->Id, GetSpellInfo()->CastTimeEntry->CastTime);
+ tmpPlayer->SendSpectatorAddonMsgToBG(msg);
+ }
+
//Containers for channeled spells have to be set
/// @todoApply this to all cast spells if needed
// Why check duration? 29350: channelled triggers channelled
@@ -4629,6 +4639,10 @@ SpellCastResult Spell::CheckCast(bool strict)
return SPELL_FAILED_ONLY_INDOORS;
}
+ if (Player *tmpPlayer = m_caster->ToPlayer())
+ if (tmpPlayer->IsSpectator())
+ return SPELL_FAILED_SPELL_UNAVAILABLE;
+
// only check at first call, Stealth auras are already removed at second call
// for now, ignore triggered spells
if (strict && !(_triggeredCastFlags & TRIGGERED_IGNORE_SHAPESHIFT))
diff --git a/src/server/scripts/Commands/cs_gm.cpp b/src/server/scripts/Commands/cs_gm.cpp
index 498ee68..3dc89a9 100644
--- a/src/server/scripts/Commands/cs_gm.cpp
+++ b/src/server/scripts/Commands/cs_gm.cpp
@@ -133,6 +133,8 @@ public:
itrSec <= AccountTypes(sWorld->getIntConfig(CONFIG_GM_LEVEL_IN_GM_LIST)))) &&
(!handler->GetSession() || itr->second->IsVisibleGloballyFor(handler->GetSession()->GetPlayer())))
{
+ if (itr->second->IsSpectator())
+ continue; // don't show spectators, they're not really gms
if (first)
{
first = false;
diff --git a/src/server/scripts/Custom/CMakeLists.txt b/src/server/scripts/Custom/CMakeLists.txt
index 78db719..6659783 100644
--- a/src/server/scripts/Custom/CMakeLists.txt
+++ b/src/server/scripts/Custom/CMakeLists.txt
@@ -10,6 +10,7 @@
set(scripts_STAT_SRCS
${scripts_STAT_SRCS}
+ Custom/arena_spectator.cpp
)
message(" -> Prepared: Custom")
diff --git a/src/server/scripts/Custom/arena_spectator.cpp b/src/server/scripts/Custom/arena_spectator.cpp
new file mode 100644
index 0000000..64cfb86
--- /dev/null
+++ b/src/server/scripts/Custom/arena_spectator.cpp
@@ -0,0 +1,611 @@
+/*
+* Copyright (C) 2008-2012 TrinityCore <http://www.trinitycore.org/>
+* Copyright (C) 2006-2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at your
+* option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but WITHOUT
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+* more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* ScriptData
+Name: Arena Spectator
+%Complete: 100
+Comment: Script allow spectate arena games
+Category: Custom Script
+EndScriptData */
+
+#include "ScriptPCH.h"
+#include "Chat.h"
+#include "ArenaTeamMgr.h"
+#include "BattlegroundMgr.h"
+#include "WorldSession.h"
+#include "Player.h"
+#include "ArenaTeam.h"
+#include "Battleground.h"
+#include "BattlegroundMgr.h"
+#include "CreatureTextMgr.h"
+#include "Config.h"
+
+int8 UsingGossip;
+
+class arena_spectator_commands : public CommandScript
+{
+public:
+ arena_spectator_commands() : CommandScript("arena_spectator_commands") { }
+
+ static bool HandleSpectateCommand(ChatHandler* handler, char const* args)
+ {
+ Player* target;
+ uint64 target_guid;
+ std::string target_name;
+ if (!handler->extractPlayerTarget((char*)args, &target, &target_guid, &target_name))
+ return false;
+
+ Player* player = handler->GetSession()->GetPlayer();
+ if (target == player || target_guid == player->GetGUID())
+ {
+ handler->PSendSysMessage("You can't spectate yourself.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ if (player->IsInCombat())
+ {
+ handler->PSendSysMessage("You are in combat.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ if (!target)
+ {
+ handler->PSendSysMessage("Target is not online or does not exist.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ if (player->GetPet())
+ {
+ handler->PSendSysMessage("You must hide your pet.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ if (player->GetMap()->IsBattlegroundOrArena() && !player->IsSpectator())
+ {
+ handler->PSendSysMessage("You are already in a battleground or arena.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ Map* cMap = target->GetMap();
+ if (!cMap->IsBattleArena())
+ {
+ handler->PSendSysMessage("Player is not in an Arena match.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ if (player->GetMap()->IsBattleground())
+ {
+ handler->PSendSysMessage("You can't do that while in a battleground.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ if (target->HasAura(32728) || target->HasAura(32727))
+ {
+ handler->PSendSysMessage("You can't do that. The Arena match didn't start yet.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ if (target->IsSpectator())
+ {
+ handler->PSendSysMessage("You can't do that. Your target is a spectator.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ if (player->IsMounted())
+ {
+ handler->PSendSysMessage("Cannot Spectate while mounted.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+
+ // all's well, set bg id
+ // when porting out from the bg, it will be reset to 0
+ player->SetBattlegroundId(target->GetBattlegroundId(), target->GetBattlegroundTypeId());
+ // remember current position as entry point for return at bg end teleportation
+ if (!player->GetMap()->IsBattlegroundOrArena())
+ player->SetBattlegroundEntryPoint();
+
+ // stop flight if need
+ if (player->IsInFlight())
+ {
+ player->GetMotionMaster()->MovementExpired();
+ player->CleanupAfterTaxiFlight();
+ }
+ // save only in non-flight case
+ else
+ player->SaveRecallPosition();
+
+ // search for two teams
+ Battleground *bGround = target->GetBattleground();
+ if (bGround->isRated())
+ {
+ uint32 slot = bGround->GetArenaType() - 2;
+ if (bGround->GetArenaType() > 3)
+ slot = 2;
+ uint32 firstTeamID = target->GetArenaTeamId(slot);
+ uint32 secondTeamID = 0;
+ Player *firstTeamMember = target;
+ Player *secondTeamMember = NULL;
+ for (Battleground::BattlegroundPlayerMap::const_iterator itr = bGround->GetPlayers().begin(); itr != bGround->GetPlayers().end(); ++itr)
+ if (Player* tmpPlayer = ObjectAccessor::FindPlayer(itr->first))
+ {
+ if (tmpPlayer->IsSpectator())
+ continue;
+
+ uint32 tmpID = tmpPlayer->GetArenaTeamId(slot);
+ if (tmpID != firstTeamID && tmpID > 0)
+ {
+ secondTeamID = tmpID;
+ secondTeamMember = tmpPlayer;
+ break;
+ }
+ }
+
+ if (firstTeamID > 0 && secondTeamID > 0 && secondTeamMember)
+ {
+ ArenaTeam *firstTeam = sArenaTeamMgr->GetArenaTeamById(firstTeamID);
+ ArenaTeam *secondTeam = sArenaTeamMgr->GetArenaTeamById(secondTeamID);
+ if (firstTeam && secondTeam)
+ {
+ handler->PSendSysMessage("You entered a Rated Arena.");
+ handler->PSendSysMessage("Teams:");
+ handler->PSendSysMessage("|cFFffffff%s|r vs |cFFffffff%s|r", firstTeam->GetName().c_str(), secondTeam->GetName().c_str());
+ handler->PSendSysMessage("|cFFffffff%u(%u)|r -- |cFFffffff%u(%u)|r", firstTeam->GetRating(), firstTeam->GetAverageMMR(firstTeamMember->GetGroup()),
+ secondTeam->GetRating(), secondTeam->GetAverageMMR(secondTeamMember->GetGroup()));
+ }
+ }
+ }
+
+ // to point to see at target with same orientation
+ float x, y, z;
+ target->GetContactPoint(player, x, y, z);
+
+ player->TeleportTo(target->GetMapId(), x, y, z, player->GetAngle(target), TELE_TO_GM_MODE);
+ player->SetPhaseMask(target->GetPhaseMask(), true);
+ player->SetSpectate(true);
+ target->GetBattleground()->AddSpectator(player->GetGUID());
+
+ return true;
+ }
+
+ static bool HandleSpectateCancelCommand(ChatHandler* handler, const char* /*args*/)
+ {
+ Player* player = handler->GetSession()->GetPlayer();
+
+ if (!player->IsSpectator() || player->isSpectateCanceled())
+ {
+ handler->PSendSysMessage("You are not a spectator.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ // RemoveSpectator shouldn't be needed to be done explicitly as it'll be done in the CancelSpectate func
+ //player->GetBattleground()->RemoveSpectator(player->GetGUID());
+ player->CancelSpectate();
+ player->TeleportToBGEntryPoint();
+
+ return true;
+ }
+
+ static bool HandleSpectateFromCommand(ChatHandler* handler, const char *args)
+ {
+ Player* target;
+ uint64 target_guid;
+ std::string target_name;
+ if (!handler->extractPlayerTarget((char*)args, &target, &target_guid, &target_name))
+ return false;
+
+ Player* player = handler->GetSession()->GetPlayer();
+
+ if (!target)
+ {
+ handler->PSendSysMessage("Player is not online or does not exist.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ if (!player->IsSpectator())
+ {
+ handler->PSendSysMessage("You are not a spectator, spectate someone first.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ if (target->IsSpectator() && target != player)
+ {
+ handler->PSendSysMessage("You can't do that. Your target is a spectator.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ if (player->GetMap() != target->GetMap())
+ {
+ handler->PSendSysMessage("You can't do that. Your target might be in a different arena match.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ if (target && target->HasAuraType(SPELL_AURA_MOD_STEALTH))
+ {
+ handler->PSendSysMessage("You can't target stealthed players.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ // check for arena preperation
+ // if exists than battle didn`t begin
+ if (target && target->HasAura(32728) || target->HasAura(32727))
+ {
+ handler->PSendSysMessage("You can't do that. The Arena match didn't start yet.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ if (target == player && player->getSpectateFrom())
+ player->SetViewpoint(player->getSpectateFrom(), false);
+ else
+ player->SetViewpoint(target, true);
+ return true;
+ }
+
+ // addon data
+ static bool HandleSpectateResetCommand(ChatHandler* handler, const char *args)
+ {
+ Player* player = handler->GetSession()->GetPlayer();
+
+ if (!player->IsSpectator())
+ {
+ handler->PSendSysMessage("You are not a spectator.");
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
+
+ Battleground *bGround = player->GetBattleground();
+ if (!bGround)
+ return false;
+
+ if (bGround->GetStatus() != STATUS_IN_PROGRESS)
+ return true;
+
+ for (Battleground::BattlegroundPlayerMap::const_iterator itr = bGround->GetPlayers().begin(); itr != bGround->GetPlayers().end(); ++itr)
+ if (Player* tmpPlayer = ObjectAccessor::FindPlayer(itr->first))
+ {
+ if (tmpPlayer->IsSpectator())
+ continue;
+
+ uint32 tmpID = bGround->GetPlayerTeam(tmpPlayer->GetGUID());
+
+ // generate addon massage
+ std::string pName = tmpPlayer->GetName();
+ std::string tName = "";
+
+ if (Player *target = tmpPlayer->GetSelectedPlayer())
+ tName = target->GetName();
+
+ SpectatorAddonMsg msg;
+ msg.SetPlayer(pName);
+ if (tName != "")
+ msg.SetTarget(tName);
+ msg.SetStatus(tmpPlayer->IsAlive());
+ msg.SetClass(tmpPlayer->getClass());
+ msg.SetCurrentHP(tmpPlayer->GetHealth());
+ msg.SetMaxHP(tmpPlayer->GetMaxHealth());
+ Powers powerType = tmpPlayer->getPowerType();
+ msg.SetMaxPower(tmpPlayer->GetMaxPower(powerType));
+ msg.SetCurrentPower(tmpPlayer->GetPower(powerType));
+ msg.SetPowerType(powerType);
+ msg.SetTeam(tmpID);
+ msg.SendPacket(player->GetGUID());
+ }
+
+ return true;
+ }
+
+ ChatCommand* GetCommands() const OVERRIDE
+ {
+ static ChatCommand spectateCommandTable[] =
+ {
+ { "player", rbac::RBAC_PERM_COMMAND_SPECTATE_PLAYER, true, &HandleSpectateCommand, "", NULL },
+ { "view", rbac::RBAC_PERM_COMMAND_SPECTATE_VIEW, true, &HandleSpectateFromCommand, "", NULL },
+ { "reset", rbac::RBAC_PERM_COMMAND_SPECTATE_RESET, true, &HandleSpectateResetCommand, "", NULL },
+ { "leave", rbac::RBAC_PERM_COMMAND_SPECTATE_LEAVE, true, &HandleSpectateCancelCommand, "", NULL },
+ { NULL, 0, false, NULL, "", NULL }
+ };
+
+ static ChatCommand commandTable[] =
+ {
+ { "spectate", rbac::RBAC_PERM_COMMAND_SPECTATE, false, NULL, "", spectateCommandTable },
+ { NULL, 0, false, NULL, "", NULL }
+ };
+ return commandTable;
+ }
+};
+
+enum NpcSpectatorAtions {
+ // will be used for scrolling
+ NPC_SPECTATOR_ACTION_2V2_GAMES = 1000,
+ NPC_SPECTATOR_ACTION_3V3_GAMES = 2000,
+ NPC_SPECTATOR_ACTION_SPECIFIC = 500,
+
+ // NPC_SPECTATOR_ACTION_SELECTED_PLAYER + player.Guid()
+ NPC_SPECTATOR_ACTION_SELECTED_PLAYER = 3000
+};
+
+const uint8 GamesOnPage = 15;
+
+class npc_arena_spectator : public CreatureScript
+{
+public:
+ npc_arena_spectator() : CreatureScript("npc_arena_spectator") { }
+
+ bool OnGossipHello(Player* pPlayer, Creature* pCreature)
+ {
+ pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "|TInterface\\icons\\Achievement_Arena_2v2_7:35:35:-30:0|tGames: 2v2", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_2V2_GAMES);
+ pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "|TInterface\\icons\\Achievement_Arena_3v3_7:35:35:-30:0|tGames: 3v3", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_3V3_GAMES);
+ pPlayer->ADD_GOSSIP_ITEM_EXTENDED(GOSSIP_ICON_CHAT, "|TInterface\\icons\\Spell_Holy_DevineAegis:35:35:-30:0|tSpectate Specific Player.", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_SPECIFIC, "", 0, true);
+ pPlayer->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, pCreature->GetGUID());
+ return true;
+ }
+
+ bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 action)
+ {
+ player->PlayerTalkClass->ClearMenus();
+ if (action == NPC_SPECTATOR_ACTION_SPECIFIC)
+ {
+
+ }
+ if (action = NPC_SPECTATOR_ACTION_2V2_GAMES)
+ {
+ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Refresh", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_2V2_GAMES);
+ ShowPage(player, action - NPC_SPECTATOR_ACTION_2V2_GAMES, false);
+ player->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, creature->GetGUID());
+ }
+ else if (action = NPC_SPECTATOR_ACTION_3V3_GAMES)
+ {
+ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Refresh", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_3V3_GAMES);
+ ShowPage(player, action - NPC_SPECTATOR_ACTION_3V3_GAMES, true);
+ player->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, creature->GetGUID());
+ }
+ else
+ {
+
+ uint64 guid = action - NPC_SPECTATOR_ACTION_SELECTED_PLAYER;
+ if (Player* target = ObjectAccessor::FindPlayer(guid))
+ {
+ ChatHandler handler(player->GetSession());
+ char const* pTarget = target->GetName().c_str();
+ arena_spectator_commands::HandleSpectateCommand(&handler, pTarget);
+ }
+ }
+ return true;
+ }
+
+ std::string GetClassNameById(uint8 id)
+ {
+ std::string sClass = "";
+ switch (id)
+ {
+ case CLASS_WARRIOR: sClass = "Warrior "; break;
+ case CLASS_PALADIN: sClass = "Paladin "; break;
+ case CLASS_HUNTER: sClass = "Hunter "; break;
+ case CLASS_ROGUE: sClass = "Rogue "; break;
+ case CLASS_PRIEST: sClass = "Priest "; break;
+ case CLASS_DEATH_KNIGHT: sClass = "DKnight "; break;
+ case CLASS_SHAMAN: sClass = "Shaman "; break;
+ case CLASS_MAGE: sClass = "Mage "; break;
+ case CLASS_WARLOCK: sClass = "Warlock "; break;
+ case CLASS_DRUID: sClass = "Druid "; break;
+ }
+ return sClass;
+ }
+
+ std::string GetGamesStringData(Battleground* team, uint16 mmr, uint16 mmrTwo)
+ {
+ std::string teamsMember[BG_TEAMS_COUNT];
+ uint32 firstTeamId = 0;
+ for (Battleground::BattlegroundPlayerMap::const_iterator itr = team->GetPlayers().begin(); itr != team->GetPlayers().end(); ++itr)
+ if (Player* player = ObjectAccessor::FindPlayer(itr->first))
+ {
+ if (player->IsSpectator())
+ continue;
+
+ if (player->IsGameMaster())
+ continue;
+
+ uint32 team = itr->second.Team;
+ if (!firstTeamId)
+ firstTeamId = team;
+
+ teamsMember[firstTeamId == team] += GetClassNameById(player->getClass());
+ }
+
+ std::string data = teamsMember[0] + "(";
+ std::stringstream sstwo;
+ std::stringstream ss;
+ ss << mmr;
+ sstwo << mmrTwo;
+ data += ss.str();
+ data += ") - ";
+ data += teamsMember[1] + "(" + sstwo.str();
+ data += ")";
+ return data;
+ }
+
+ uint64 GetFirstPlayerGuid(Battleground* team)
+ {
+ for (Battleground::BattlegroundPlayerMap::const_iterator itr = team->GetPlayers().begin(); itr != team->GetPlayers().end(); ++itr)
+ if (Player* player = ObjectAccessor::FindPlayer(itr->first))
+ return itr->first;
+ return 0;
+ }
+
+ void ShowPage(Player* player, uint16 page, bool IsTop)
+ {
+ uint32 firstTeamId = 0;
+ uint16 TypeTwo = 0;
+ uint16 TypeThree = 0;
+ uint16 mmr = 0;
+ uint16 mmrTwo = 0;
+ bool haveNextPage = false;
+ for (uint8 i = 0; i <= MAX_BATTLEGROUND_TYPE_ID; ++i)
+ {
+ if (!sBattlegroundMgr->IsArenaType(BattlegroundTypeId(i)))
+ continue;
+
+ BattlegroundData* arenas = sBattlegroundMgr->GetAllBattlegroundsWithTypeId(BattlegroundTypeId(i));
+
+ if (!arenas || arenas->m_Battlegrounds.empty())
+ continue;
+
+ for (BattlegroundContainer::const_iterator itr = arenas->m_Battlegrounds.begin(); itr != arenas->m_Battlegrounds.end(); ++itr)
+ {
+ Battleground* arena = itr->second;
+ Player* target = ObjectAccessor::FindPlayer(GetFirstPlayerGuid(arena));
+ if (!target)
+ continue;
+
+ if (target->HasAura(32728) || target->HasAura(32727))
+ continue;
+
+ if (!arena->GetPlayersSize())
+ continue;
+
+ if (!arena->isRated())
+ continue;
+
+ if (arena->GetArenaType() == ARENA_TYPE_2v2)
+ {
+ mmr = arena->GetArenaMatchmakerRatingByIndex(0);
+ firstTeamId = target->GetArenaTeamId(0);
+ Battleground::BattlegroundPlayerMap::const_iterator citr = arena->GetPlayers().begin();
+ for (; citr != arena->GetPlayers().end(); ++citr)
+ if (Player* plrs = sObjectAccessor->FindPlayer(citr->first))
+ if (plrs->GetArenaTeamId(0) != firstTeamId)
+ mmrTwo = arena->GetArenaMatchmakerRating(citr->second.Team);
+ }
+ else
+ {
+ mmr = arena->GetArenaMatchmakerRatingByIndex(1);
+ firstTeamId = target->GetArenaTeamId(1);
+ Battleground::BattlegroundPlayerMap::const_iterator citr = arena->GetPlayers().begin();
+ for (; citr != arena->GetPlayers().end(); ++citr)
+ if (Player* plrs = sObjectAccessor->FindPlayer(citr->first))
+ if (plrs->GetArenaTeamId(1) != firstTeamId)
+ mmrTwo = arena->GetArenaMatchmakerRating(citr->second.Team);
+ }
+
+ if (IsTop && arena->GetArenaType() == ARENA_TYPE_3v3)
+ {
+ TypeThree++;
+ if (TypeThree > (page + 1) * GamesOnPage)
+ {
+ haveNextPage = true;
+ break;
+ }
+
+ if (TypeThree >= page * GamesOnPage)
+ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_BATTLE, GetGamesStringData(arena, mmr, mmrTwo), GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_SELECTED_PLAYER + GetFirstPlayerGuid(arena));
+ }
+ else if (!IsTop && arena->GetArenaType() == ARENA_TYPE_2v2)
+ {
+ TypeTwo++;
+ if (TypeTwo > (page + 1) * GamesOnPage)
+ {
+ haveNextPage = true;
+ break;
+ }
+
+ if (TypeTwo >= page * GamesOnPage)
+ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_BATTLE, GetGamesStringData(arena, mmr, mmrTwo), GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_SELECTED_PLAYER + GetFirstPlayerGuid(arena));
+ }
+ }
+ }
+
+ if (page > 0)
+ {
+ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Prev..", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_2V2_GAMES + page - 1);
+ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Prev..", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_3V3_GAMES + page - 1);
+ }
+
+ if (haveNextPage)
+ {
+ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Next..", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_2V2_GAMES + page + 1);
+ player->ADD_GOSSIP_ITEM(GOSSIP_ICON_DOT, "Next..", GOSSIP_SENDER_MAIN, NPC_SPECTATOR_ACTION_3V3_GAMES + page + 1);
+ }
+ }
+
+ bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, const char* code)
+ {
+ if (!player)
+ return true;
+
+ player->PlayerTalkClass->ClearMenus();
+ player->CLOSE_GOSSIP_MENU();
+ if (sender == GOSSIP_SENDER_MAIN)
+ {
+ switch (action)
+ {
+ case NPC_SPECTATOR_ACTION_SPECIFIC: // choosing a player
+
+ const char* plrName = code;
+
+ char playerName[50];
+ strcpy(playerName, plrName);
+
+ for (int i = 0; i < 13; i++)
+ {
+ if (playerName[i] == NULL)
+ break;
+ if (i == 0 && playerName[i] > 96)
+ playerName[0] -= 32;
+ else if (playerName[i] < 97)
+ playerName[i] += 32;
+ }
+
+ if (Player* target = sObjectAccessor->FindPlayerByName(playerName))
+ {
+ ChatHandler handler(player->GetSession());
+ char const* pTarget = target->GetName().c_str();
+ arena_spectator_commands::HandleSpectateCommand(&handler, pTarget);
+ }
+ ChatHandler(player->GetSession()).PSendSysMessage("Player is not online or does not exist.");
+ return true;
+ }
+ }
+
+ return false;
+ }
+};
+
+
+
+void AddSC_arena_spectator_script()
+{
+ new arena_spectator_commands();
+ new npc_arena_spectator();
+}
\ No newline at end of file
--
1.8.3.msysgit.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment