aboutsummaryrefslogtreecommitdiff
path: root/src/game/WorldSession.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/game/WorldSession.cpp')
-rw-r--r--src/game/WorldSession.cpp463
1 files changed, 463 insertions, 0 deletions
diff --git a/src/game/WorldSession.cpp b/src/game/WorldSession.cpp
new file mode 100644
index 00000000000..eaba12b4a10
--- /dev/null
+++ b/src/game/WorldSession.cpp
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2005-2008 MaNGOS <http://www.mangosproject.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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/** \file
+ \ingroup u2w
+*/
+
+#include "Common.h"
+#include "Database/DatabaseEnv.h"
+#include "Log.h"
+#include "Opcodes.h"
+#include "WorldSocket.h"
+#include "WorldPacket.h"
+#include "WorldSession.h"
+#include "Player.h"
+#include "ObjectMgr.h"
+#include "Group.h"
+#include "Guild.h"
+#include "World.h"
+#include "MapManager.h"
+#include "ObjectAccessor.h"
+#include "BattleGroundMgr.h"
+#include "Language.h" // for CMSG_CANCEL_MOUNT_AURA handler
+#include "Chat.h"
+#include "SocialMgr.h"
+
+/// WorldSession constructor
+WorldSession::WorldSession(uint32 id, WorldSocket *sock, uint32 sec, bool tbc, time_t mute_time, LocaleConstant locale) :
+LookingForGroup_auto_join(false), LookingForGroup_auto_add(false), m_muteTime(mute_time),
+_player(NULL), _socket(sock),_security(sec), _accountId(id), m_isTBC(tbc),
+m_sessionDbcLocale(sWorld.GetAvailableDbcLocale(locale)), m_sessionDbLocaleIndex(objmgr.GetIndexForLocale(locale)),
+_logoutTime(0), m_playerLoading(false), m_playerLogout(false), m_playerRecentlyLogout(false), m_latency(0)
+{
+}
+
+/// WorldSession destructor
+WorldSession::~WorldSession()
+{
+ ///- unload player if not unloaded
+ if(_player)
+ LogoutPlayer(true);
+
+ /// - If have unclosed socket, close it
+ if(_socket)
+ _socket->CloseSocket();
+
+ _socket = NULL;
+
+ ///- empty incoming packet queue
+ while(!_recvQueue.empty())
+ {
+ WorldPacket *packet = _recvQueue.next();
+ delete packet;
+ }
+}
+
+void WorldSession::SizeError(WorldPacket const& packet, uint32 size) const
+{
+ sLog.outError("Client (account %u) send packet %s (%u) with size %u but expected %u (attempt crash server?), skipped",
+ GetAccountId(),LookupOpcodeName(packet.GetOpcode()),packet.GetOpcode(),packet.size(),size);
+}
+
+/// Get the player name
+char const* WorldSession::GetPlayerName() const
+{
+ return GetPlayer() ? GetPlayer()->GetName() : "<none>";
+}
+
+/// Set the WorldSocket associated with this session
+void WorldSession::SetSocket(WorldSocket *sock)
+{
+ _socket = sock;
+}
+
+/// Send a packet to the client
+void WorldSession::SendPacket(WorldPacket const* packet)
+{
+ if (!_socket)
+ return;
+ #ifdef MANGOS_DEBUG
+ // Code for network use statistic
+ static uint64 sendPacketCount = 0;
+ static uint64 sendPacketBytes = 0;
+
+ static time_t firstTime = time(NULL);
+ static time_t lastTime = firstTime; // next 60 secs start time
+
+ static uint64 sendLastPacketCount = 0;
+ static uint64 sendLastPacketBytes = 0;
+
+ time_t cur_time = time(NULL);
+
+ if((cur_time - lastTime) < 60)
+ {
+ sendPacketCount+=1;
+ sendPacketBytes+=packet->size();
+
+ sendLastPacketCount+=1;
+ sendLastPacketBytes+=packet->size();
+ }
+ else
+ {
+ uint64 minTime = uint64(cur_time - lastTime);
+ uint64 fullTime = uint64(lastTime - firstTime);
+ sLog.outDetail("Send all time packets count: " I64FMTD " bytes: " I64FMTD " avr.count/sec: %f avr.bytes/sec: %f time: %u",sendPacketCount,sendPacketBytes,float(sendPacketCount)/fullTime,float(sendPacketBytes)/fullTime,uint32(fullTime));
+ sLog.outDetail("Send last min packets count: " I64FMTD " bytes: " I64FMTD " avr.count/sec: %f avr.bytes/sec: %f",sendLastPacketCount,sendLastPacketBytes,float(sendLastPacketCount)/minTime,float(sendLastPacketBytes)/minTime);
+
+ lastTime = cur_time;
+ sendLastPacketCount = 1;
+ sendLastPacketBytes = packet->wpos(); // wpos is real written size
+ }
+ #endif // !MANGOS_DEBUG
+
+ _socket->SendPacket(packet);
+}
+
+/// Add an incoming packet to the queue
+void WorldSession::QueuePacket(WorldPacket& packet)
+{
+ WorldPacket *pck = new WorldPacket(packet);
+ _recvQueue.add(pck);
+}
+
+/// Logging helper for unexpected opcodes
+void WorldSession::logUnexpectedOpcode(WorldPacket* packet, const char *reason)
+{
+ sLog.outError( "SESSION: received unexpected opcode %s (0x%.4X) %s",
+ LookupOpcodeName(packet->GetOpcode()),
+ packet->GetOpcode(),
+ reason);
+}
+
+/// Update the WorldSession (triggered by World update)
+bool WorldSession::Update(uint32 /*diff*/)
+{
+ WorldPacket *packet;
+
+ ///- Retrieve packets from the receive queue and call the appropriate handlers
+ /// \todo Is there a way to consolidate the OpcondeHandlerTable and the g_worldOpcodeNames to only maintain 1 list?
+ /// answer : there is a way, but this is better, because it would use redundant RAM
+ while (!_recvQueue.empty())
+ {
+ packet = _recvQueue.next();
+
+ /*#if 1
+ sLog.outError( "MOEP: %s (0x%.4X)",
+ LookupOpcodeName(packet->GetOpcode()),
+ packet->GetOpcode());
+ #endif*/
+
+ if(packet->GetOpcode() >= NUM_MSG_TYPES)
+ {
+ sLog.outError( "SESSION: received non-existed opcode %s (0x%.4X)",
+ LookupOpcodeName(packet->GetOpcode()),
+ packet->GetOpcode());
+ }
+ else
+ {
+ OpcodeHandler& opHandle = opcodeTable[packet->GetOpcode()];
+ switch (opHandle.status)
+ {
+ case STATUS_LOGGEDIN:
+ if(!_player)
+ {
+ // skip STATUS_LOGGEDIN opcode unexpected errors if player logout sometime ago - this can be network lag delayed packets
+ if(!m_playerRecentlyLogout)
+ logUnexpectedOpcode(packet, "the player has not logged in yet");
+ }
+ else if(_player->IsInWorld())
+ (this->*opHandle.handler)(*packet);
+ // lag can cause STATUS_LOGGEDIN opcodes to arrive after the player started a transfer
+ break;
+ case STATUS_TRANSFER_PENDING:
+ if(!_player)
+ logUnexpectedOpcode(packet, "the player has not logged in yet");
+ else if(_player->IsInWorld())
+ logUnexpectedOpcode(packet, "the player is still in world");
+ else
+ (this->*opHandle.handler)(*packet);
+ break;
+ case STATUS_AUTHED:
+ m_playerRecentlyLogout = false;
+ (this->*opHandle.handler)(*packet);
+ break;
+ case STATUS_NEVER:
+ sLog.outError( "SESSION: received not allowed opcode %s (0x%.4X)",
+ LookupOpcodeName(packet->GetOpcode()),
+ packet->GetOpcode());
+ break;
+ }
+ }
+
+ delete packet;
+ }
+
+ ///- If necessary, log the player out
+ time_t currTime = time(NULL);
+ if (!_socket || (ShouldLogOut(currTime) && !m_playerLoading))
+ LogoutPlayer(true);
+
+ if (!_socket)
+ return false; //Will remove this session from the world session map
+
+ return true;
+}
+
+/// %Log the player out
+void WorldSession::LogoutPlayer(bool Save)
+{
+ // finish pending transfers before starting the logout
+ while(_player && _player->IsBeingTeleported())
+ HandleMoveWorldportAckOpcode();
+
+ m_playerLogout = true;
+
+ if (_player)
+ {
+ ///- If the player just died before logging out, make him appear as a ghost
+ //FIXME: logout must be delayed in case lost connection with client in time of combat
+ if (_player->GetDeathTimer())
+ {
+ _player->getHostilRefManager().deleteReferences();
+ _player->BuildPlayerRepop();
+ _player->RepopAtGraveyard();
+ }
+ else if (!_player->getAttackers().empty())
+ {
+ _player->CombatStop();
+ _player->getHostilRefManager().setOnlineOfflineState(false);
+ _player->RemoveAllAurasOnDeath();
+
+ // build set of player who attack _player or who have pet attacking of _player
+ std::set<Player*> aset;
+ for(Unit::AttackerSet::const_iterator itr = _player->getAttackers().begin(); itr != _player->getAttackers().end(); ++itr)
+ {
+ Unit* owner = (*itr)->GetOwner(); // including player controlled case
+ if(owner)
+ {
+ if(owner->GetTypeId()==TYPEID_PLAYER)
+ aset.insert((Player*)owner);
+ }
+ else
+ if((*itr)->GetTypeId()==TYPEID_PLAYER)
+ aset.insert((Player*)(*itr));
+ }
+
+ _player->SetPvPDeath(!aset.empty());
+ _player->KillPlayer();
+ _player->BuildPlayerRepop();
+ _player->RepopAtGraveyard();
+
+ // give honor to all attackers from set like group case
+ for(std::set<Player*>::const_iterator itr = aset.begin(); itr != aset.end(); ++itr)
+ (*itr)->RewardHonor(_player,aset.size());
+
+ // give bg rewards and update counters like kill by first from attackers
+ // this can't be called for all attackers.
+ if(!aset.empty())
+ if(BattleGround *bg = _player->GetBattleGround())
+ bg->HandleKillPlayer(_player,*aset.begin());
+ }
+ else if(_player->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION))
+ {
+ // this will kill character by SPELL_AURA_SPIRIT_OF_REDEMPTION
+ _player->RemoveSpellsCausingAura(SPELL_AURA_MOD_SHAPESHIFT);
+ //_player->SetDeathPvP(*); set at SPELL_AURA_SPIRIT_OF_REDEMPTION apply time
+ _player->KillPlayer();
+ _player->BuildPlayerRepop();
+ _player->RepopAtGraveyard();
+ }
+
+ ///- Remove player from battleground (teleport to entrance)
+ if(_player->InBattleGround())
+ _player->LeaveBattleground();
+
+ for (int i=0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; i++)
+ {
+ if(int32 bgTypeId = _player->GetBattleGroundQueueId(i))
+ {
+ _player->RemoveBattleGroundQueueId(bgTypeId);
+ sBattleGroundMgr.m_BattleGroundQueues[ bgTypeId ].RemovePlayer(_player->GetGUID(), true);
+ }
+ }
+
+ ///- Reset the online field in the account table
+ // no point resetting online in character table here as Player::SaveToDB() will set it to 1 since player has not been removed from world at this stage
+ //No SQL injection as AccountID is uint32
+ loginDatabase.PExecute("UPDATE account SET online = 0 WHERE id = '%u'", GetAccountId());
+
+ ///- If the player is in a guild, update the guild roster and broadcast a logout message to other guild members
+ Guild *guild = objmgr.GetGuildById(_player->GetGuildId());
+ if(guild)
+ {
+ guild->LoadPlayerStatsByGuid(_player->GetGUID());
+ guild->UpdateLogoutTime(_player->GetGUID());
+
+ WorldPacket data(SMSG_GUILD_EVENT, (1+1+12+8)); // name limited to 12 in character table.
+ data<<(uint8)GE_SIGNED_OFF;
+ data<<(uint8)1;
+ data<<_player->GetName();
+ data<<_player->GetGUID();
+ guild->BroadcastPacket(&data);
+ }
+
+ ///- Remove pet
+ _player->RemovePet(NULL,PET_SAVE_AS_CURRENT, true);
+
+ ///- empty buyback items and save the player in the database
+ // some save parts only correctly work in case player present in map/player_lists (pets, etc)
+ if(Save)
+ {
+ uint32 eslot;
+ for(int j = BUYBACK_SLOT_START; j < BUYBACK_SLOT_END; j++)
+ {
+ eslot = j - BUYBACK_SLOT_START;
+ _player->SetUInt64Value(PLAYER_FIELD_VENDORBUYBACK_SLOT_1+eslot*2,0);
+ _player->SetUInt32Value(PLAYER_FIELD_BUYBACK_PRICE_1+eslot,0);
+ _player->SetUInt32Value(PLAYER_FIELD_BUYBACK_TIMESTAMP_1+eslot,0);
+ }
+ _player->SaveToDB();
+ }
+
+ ///- Leave all channels before player delete...
+ _player->CleanupChannels();
+
+ ///- If the player is in a group (or invited), remove him. If the group if then only 1 person, disband the group.
+ _player->UninviteFromGroup();
+
+ // remove player from the group if he is:
+ // a) in group; b) not in raid group; c) logging out normally (not being kicked or disconnected)
+ if(_player->GetGroup() && !_player->GetGroup()->isRaidGroup() && _socket)
+ _player->RemoveFromGroup();
+
+ ///- Remove the player from the world
+ // the player may not be in the world when logging out
+ // e.g if he got disconnected during a transfer to another map
+ // calls to GetMap in this case may cause crashes
+ if(_player->IsInWorld()) MapManager::Instance().GetMap(_player->GetMapId(), _player)->Remove(_player, false);
+ // RemoveFromWorld does cleanup that requires the player to be in the accessor
+ ObjectAccessor::Instance().RemoveObject(_player);
+
+ ///- Send update to group
+ if(_player->GetGroup())
+ _player->GetGroup()->SendUpdate();
+
+ ///- Broadcast a logout message to the player's friends
+ sSocialMgr.SendFriendStatus(_player, FRIEND_OFFLINE, _player->GetGUIDLow(), "", true);
+
+ ///- Delete the player object
+ _player->CleanupsBeforeDelete(); // do some cleanup before deleting to prevent crash at crossreferences to already deleted data
+
+ delete _player;
+ _player = NULL;
+
+ ///- Send the 'logout complete' packet to the client
+ WorldPacket data( SMSG_LOGOUT_COMPLETE, 0 );
+ SendPacket( &data );
+
+ ///- Since each account can only have one online character at any given time, ensure all characters for active account are marked as offline
+ //No SQL injection as AccountId is uint32
+ CharacterDatabase.PExecute("UPDATE characters SET online = 0 WHERE account = '%u'", GetAccountId());
+ sLog.outDebug( "SESSION: Sent SMSG_LOGOUT_COMPLETE Message" );
+ }
+
+ m_playerLogout = false;
+ m_playerRecentlyLogout = true;
+ LogoutRequest(0);
+}
+
+/// Kick a player out of the World
+void WorldSession::KickPlayer()
+{
+ if(!_socket)
+ return;
+
+ // player will be logout and session will removed in next update tick
+ _socket->CloseSocket();
+ _socket = NULL;
+}
+
+/// Cancel channeling handler
+
+void WorldSession::SendAreaTriggerMessage(const char* Text, ...)
+{
+ va_list ap;
+ char szStr [1024];
+ szStr[0] = '\0';
+
+ va_start(ap, Text);
+ vsnprintf( szStr, 1024, Text, ap );
+ va_end(ap);
+
+ uint32 length = strlen(szStr)+1;
+ WorldPacket data(SMSG_AREA_TRIGGER_MESSAGE, 4+length);
+ data << length;
+ data << szStr;
+ SendPacket(&data);
+}
+
+void WorldSession::SendNotification(const char *format,...)
+{
+ if(format)
+ {
+ va_list ap;
+ char szStr [1024];
+ szStr[0] = '\0';
+ va_start(ap, format);
+ vsnprintf( szStr, 1024, format, ap );
+ va_end(ap);
+
+ WorldPacket data(SMSG_NOTIFICATION, (strlen(szStr)+1));
+ data << szStr;
+ SendPacket(&data);
+ }
+}
+
+const char * WorldSession::GetMangosString( int32 entry )
+{
+ return objmgr.GetMangosString(entry,GetSessionDbLocaleIndex());
+}
+
+void WorldSession::Handle_NULL( WorldPacket& recvPacket )
+{
+ sLog.outError( "SESSION: received unhandled opcode %s (0x%.4X)",
+ LookupOpcodeName(recvPacket.GetOpcode()),
+ recvPacket.GetOpcode());
+}
+
+void WorldSession::Handle_EarlyProccess( WorldPacket& recvPacket )
+{
+ sLog.outError( "SESSION: received opcode %s (0x%.4X) that must be proccessed in WorldSocket::OnRead",
+ LookupOpcodeName(recvPacket.GetOpcode()),
+ recvPacket.GetOpcode());
+}
+
+void WorldSession::Handle_ServerSide( WorldPacket& recvPacket )
+{
+ sLog.outError( "SESSION: received sever-side opcode %s (0x%.4X)",
+ LookupOpcodeName(recvPacket.GetOpcode()),
+ recvPacket.GetOpcode());
+}
+
+void WorldSession::Handle_Depricated( WorldPacket& recvPacket )
+{
+ sLog.outError( "SESSION: received depricated opcode %s (0x%.4X)",
+ LookupOpcodeName(recvPacket.GetOpcode()),
+ recvPacket.GetOpcode());
+}