/src/game/WorldSession.cpp
C++ | 578 lines | 408 code | 81 blank | 89 comment | 70 complexity | 94f411a2428c5acd3db7c81b5ac5983c MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
- /*
- * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
- *
- * Copyright (C) 2008-2009 Trinity <http://www.trinitycore.org/>
- *
- * Copyright (C) 2010 Oregon <http://www.oregoncore.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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
- #include "WorldSocket.h" // must be first to make ACE happy with ACE includes in it
- #include "Common.h"
- #include "Database/DatabaseEnv.h"
- #include "Log.h"
- #include "Opcodes.h"
- #include "WorldPacket.h"
- #include "WorldSession.h"
- #include "Player.h"
- #include "ObjectMgr.h"
- #include "Group.h"
- #include "Guild.h"
- #include "World.h"
- #include "ObjectAccessor.h"
- #include "BattleGroundMgr.h"
- #include "OutdoorPvPMgr.h"
- #include "MapManager.h"
- #include "Chat.h"
- #include "SocialMgr.h"
- #include "ScriptMgr.h"
- // WorldSession constructor
- WorldSession::WorldSession(uint32 id, WorldSocket *sock, uint32 sec, uint8 expansion, time_t mute_time, LocaleConstant locale) :
- LookingForGroup_auto_join(false), LookingForGroup_auto_add(false), m_muteTime(mute_time),
- _player(NULL), m_Socket(sock),_security(sec), _accountId(id), m_expansion(expansion),
- m_sessionDbcLocale(sWorld.GetAvailableDbcLocale(locale)), m_sessionDbLocaleIndex(objmgr.GetIndexForLocale(locale)),
- _logoutTime(0), m_inQueue(false), m_playerLoading(false), m_playerLogout(false), m_playerRecentlyLogout(false), m_playerSave(false),
- m_latency(0), m_timeOutTime(0)
- {
- if (sock)
- {
- m_Address = sock->GetRemoteAddress();
- sock->AddReference();
- ResetTimeOutTime();
- LoginDatabase.PExecute("UPDATE account SET online = 1 WHERE id = %u;", GetAccountId());
- }
- }
- // WorldSession destructor
- WorldSession::~WorldSession()
- {
- // unload player if not unloaded
- if (_player)
- LogoutPlayer(true);
- // If have unclosed socket, close it
- if (m_Socket)
- {
- m_Socket->CloseSocket();
- m_Socket->RemoveReference();
- m_Socket = NULL;
- }
- // empty incoming packet queue
- WorldPacket* packet;
- while (_recvQueue.next(packet))
- delete packet;
- LoginDatabase.PExecute("UPDATE account SET online = 0 WHERE id = %u;", GetAccountId());
- CharacterDatabase.PExecute("UPDATE characters SET online = 0 WHERE account = %u;", GetAccountId());
- }
- void WorldSession::SizeError(WorldPacket const& packet, uint32 size) const
- {
- sLog.outError("Client (account %u) send packet %s (%u) with size " SIZEFMTD " 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>";
- }
- // Send a packet to the client
- void WorldSession::SendPacket(WorldPacket const* packet)
- {
- if (!m_Socket)
- return;
- #ifdef OREGON_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: " UI64FMTD " bytes: " UI64FMTD " 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: " UI64FMTD " bytes: " UI64FMTD " 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 // !OREGON_DEBUG
- if (m_Socket->SendPacket(*packet) == -1)
- m_Socket->CloseSocket();
- }
- // Add an incoming packet to the queue
- void WorldSession::QueuePacket(WorldPacket* new_packet)
- {
- _recvQueue.add(new_packet);
- }
- // 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);
- }
- // Logging helper for unexpected opcodes
- void WorldSession::LogUnprocessedTail(WorldPacket *packet)
- {
- sLog.outError("SESSION: opcode %s (0x%.4X) has unprocessed tail data (read stop at %u from %u)",
- LookupOpcodeName(packet->GetOpcode()),
- packet->GetOpcode(),
- packet->rpos(),packet->wpos());
- }
- // Update the WorldSession (triggered by World update)
- bool WorldSession::Update(uint32 diff)
- {
- /// Update Timeout timer.
- UpdateTimeOutTime(diff);
- ///- Before we process anything:
- /// If necessary, kick the player from the character select screen
- if (IsConnectionIdle())
- m_Socket->CloseSocket();
- // Retrieve packets from the receive queue and call the appropriate handlers
- // not proccess packets if socket already closed
- WorldPacket* packet;
- while (m_Socket && !m_Socket->IsClosed() && _recvQueue.next(packet))
- {
- /*#if 1
- sLog.outError("MOEP: %s (0x%.4X)",
- LookupOpcodeName(packet->GetOpcode()),
- packet->GetOpcode());
- #endif*/
- if (packet->GetOpcode() >= NUM_MSG_TYPES)
- {
- sLog.outError("SESSION: received invalid opcode %s (0x%.4X)",
- LookupOpcodeName(packet->GetOpcode()),
- packet->GetOpcode());
- }
- else
- {
- OpcodeHandler& opHandle = opcodeTable[packet->GetOpcode()];
- try
- {
- 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())
- ExecuteOpcode(opHandle, 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
- ExecuteOpcode(opHandle, packet);
- break;
- case STATUS_AUTHED:
- // prevent cheating with skip queue wait
- if (m_inQueue)
- {
- LogUnexpectedOpcode(packet, "the player not pass queue yet");
- break;
- }
- m_playerRecentlyLogout = false;
- ExecuteOpcode(opHandle, packet);
- break;
- case STATUS_NEVER:
- sLog.outError("SESSION: received not allowed opcode %s (0x%.4X)",
- LookupOpcodeName(packet->GetOpcode()),
- packet->GetOpcode());
- break;
- }
- }
- catch(ByteBufferException &)
- {
- sLog.outError("WorldSession::Update ByteBufferException occured while parsing a packet (opcode: %u) from client %s, accountid=%i. Skipped packet.",
- packet->GetOpcode(), GetRemoteAddress().c_str(), GetAccountId());
- if (sLog.IsOutDebug())
- {
- sLog.outDebug("Dumping error causing packet:");
- packet->hexlike();
- }
- }
- }
- delete packet;
- }
- ///- If necessary, log the player out
- time_t currTime = time(NULL);
- if (ShouldLogOut(currTime) && !m_playerLoading)
- LogoutPlayer(true);
- // Cleanup socket pointer if need
- if (m_Socket && m_Socket->IsClosed())
- {
- m_Socket->RemoveReference();
- m_Socket = NULL;
- }
- if (!m_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->IsBeingTeleportedFar())
- HandleMoveWorldportAckOpcode();
- m_playerLogout = true;
- m_playerSave = Save;
- if (_player)
- {
- if (uint64 lguid = GetPlayer()->GetLootGUID())
- DoLootRelease(lguid);
- // 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->getHostileRefManager().deleteReferences();
- _player->BuildPlayerRepop();
- _player->RepopAtGraveyard();
- }
- else if (!_player->getAttackers().empty())
- {
- _player->CombatStop();
- _player->getHostileRefManager().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(owner->ToPlayer());
- }
- else
- if ((*itr)->GetTypeId() == TYPEID_PLAYER)
- aset.insert((*itr)->ToPlayer());
- }
- _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();
- }
- //drop a flag if player is carrying it
- if (BattleGround *bg = _player->GetBattleGround())
- bg->EventPlayerLoggedOut(_player);
- // Teleport to home if the player is in an invalid instance
- if (!_player->m_InstanceValid && !_player->isGameMaster())
- {
- _player->TeleportToHomebind();
- //this is a bad place to call for far teleport because we need player to be in world for successful logout
- //maybe we should implement delayed far teleport logout?
- }
- // FG: finish pending transfers after starting the logout
- // this should fix players beeing able to logout and login back with full hp at death position
- while (_player->IsBeingTeleportedFar())
- HandleMoveWorldportAckOpcode();
- sOutdoorPvPMgr.HandlePlayerLeaveZone(_player,_player->GetZoneId());
- 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);
- }
- }
- // If the player is in a guild, update the guild roster and broadcast a logout message to other guild members
- if (Guild *guild = objmgr.GetGuildById(_player->GetGuildId()))
- {
- guild->LoadPlayerStatsByGuid(_player->GetGUID());
- guild->UpdateLogoutTime(_player->GetGUID());
- guild->BroadcastEvent(GE_SIGNED_OFF, _player->GetGUID(), _player->GetName());
- }
- // 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() && m_Socket)
- _player->RemoveFromGroup();
- // 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);
- sSocialMgr.RemovePlayerSocial (_player->GetGUIDLow ());
- // 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
- _player->CleanupsBeforeDelete();
- Map* _map = _player->GetMap();
- _map->Remove(_player, true);
- _player = NULL; // deleted in Remove call
- // 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");
- }
- //Hook for OnLogout Event
- sScriptMgr.OnLogout(_player);
- m_playerLogout = false;
- m_playerSave = false;
- m_playerRecentlyLogout = true;
- LogoutRequest(0);
- }
- // Kick a player out of the World
- void WorldSession::KickPlayer()
- {
- if (m_Socket)
- m_Socket->CloseSocket();
- }
- // 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);
- }
- }
- void WorldSession::SendNotification(int32 string_id,...)
- {
- char const* format = GetOregonString(string_id);
- if (format)
- {
- va_list ap;
- char szStr [1024];
- szStr[0] = '\0';
- va_start(ap, string_id);
- vsnprintf(szStr, 1024, format, ap);
- va_end(ap);
- WorldPacket data(SMSG_NOTIFICATION, (strlen(szStr)+1));
- data << szStr;
- SendPacket(&data);
- }
- }
- const char * WorldSession::GetOregonString(int32 entry) const
- {
- return objmgr.GetOregonString(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 processed in WorldSocket::OnRead",
- LookupOpcodeName(recvPacket.GetOpcode()),
- recvPacket.GetOpcode());
- }
- void WorldSession::Handle_ServerSide(WorldPacket& recvPacket)
- {
- sLog.outError("SESSION: received server-side opcode %s (0x%.4X)",
- LookupOpcodeName(recvPacket.GetOpcode()),
- recvPacket.GetOpcode());
- }
- void WorldSession::Handle_Deprecated(WorldPacket& recvPacket)
- {
- sLog.outError("SESSION: received deprecated opcode %s (0x%.4X)",
- LookupOpcodeName(recvPacket.GetOpcode()),
- recvPacket.GetOpcode());
- }
- void WorldSession::SendAuthWaitQue(uint32 position)
- {
- if (position == 0)
- {
- WorldPacket packet(SMSG_AUTH_RESPONSE, 1);
- packet << uint8(AUTH_OK);
- SendPacket(&packet);
- }
- else
- {
- WorldPacket packet(SMSG_AUTH_RESPONSE, 5);
- packet << uint8(AUTH_WAIT_QUEUE);
- packet << uint32(position);
- SendPacket(&packet);
- }
- }
- void WorldSession::ExecuteOpcode(OpcodeHandler const& opHandle, WorldPacket* packet)
- {
- // need prevent do internal far teleports in handlers because some handlers do lot steps
- // or call code that can do far teleports in some conditions unexpectedly for generic way work code
- if (_player)
- _player->SetCanDelayTeleport(true);
- (this->*opHandle.handler)(*packet);
- if (_player)
- {
- // can be not set in fact for login opcode, but this not create porblems.
- _player->SetCanDelayTeleport(false);
- //we should execute delayed teleports only for alive(!) players
- //because we don't want player's ghost teleported from graveyard
- if (_player->IsHasDelayedTeleport())
- _player->TeleportTo(_player->m_teleport_dest, _player->m_teleport_options);
- }
- if (packet->rpos() < packet->wpos())
- LogUnprocessedTail(packet);
- }