root/trunk/src/game/WorldSession.cpp @ 181

Revision 181, 18.1 kB (checked in by yumileroy, 17 years ago)

[svn] Merge from Mangos:
3c7ac5bd3e20c33a22ac57c5c3bac23a0798dc9e 2008-10-23 19:06:27
Some endianess related fixes and cleanups. By VladimirMangos?.

Original author: megamage
Date: 2008-11-06 16:10:28-06:00

Line 
1/*
2 * Copyright (C) 2005-2008 MaNGOS <http://www.mangosproject.org/>
3 *
4 * Copyright (C) 2008 Trinity <http://www.trinitycore.org/>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21/** \file
22    \ingroup u2w
23*/
24
25#include "WorldSocket.h"
26#include "Common.h"
27#include "Database/DatabaseEnv.h"
28#include "Log.h"
29#include "Opcodes.h"
30#include "WorldSocket.h"
31#include "WorldPacket.h"
32#include "WorldSession.h"
33#include "Player.h"
34#include "ObjectMgr.h"
35#include "Group.h"
36#include "Guild.h"
37#include "World.h"
38#include "MapManager.h"
39#include "ObjectAccessor.h"
40#include "BattleGroundMgr.h"
41#include "OutdoorPvPMgr.h"
42#include "Language.h"                                       // for CMSG_CANCEL_MOUNT_AURA handler
43#include "Chat.h"
44#include "SocialMgr.h"
45
46/// WorldSession constructor
47WorldSession::WorldSession(uint32 id, WorldSocket *sock, uint32 sec, uint8 expansion, time_t mute_time, LocaleConstant locale) :
48LookingForGroup_auto_join(false), LookingForGroup_auto_add(false), m_muteTime(mute_time),
49_player(NULL), m_Socket(sock),_security(sec), _accountId(id), m_expansion(expansion),
50m_sessionDbcLocale(sWorld.GetAvailableDbcLocale(locale)), m_sessionDbLocaleIndex(objmgr.GetIndexForLocale(locale)),
51_logoutTime(0), m_playerLoading(false), m_playerLogout(false), m_playerRecentlyLogout(false), m_latency(0)
52{
53    if (sock)
54    {
55        m_Address = sock->GetRemoteAddress ();
56        sock->AddReference ();
57    }
58}
59
60/// WorldSession destructor
61WorldSession::~WorldSession()
62{
63    ///- unload player if not unloaded
64    if (_player)
65        LogoutPlayer (true);
66
67    /// - If have unclosed socket, close it
68    if (m_Socket)
69    {
70        m_Socket->CloseSocket ();
71        m_Socket->RemoveReference ();
72        m_Socket = NULL;
73    }
74
75    ///- empty incoming packet queue
76    while(!_recvQueue.empty())
77    {
78        WorldPacket *packet = _recvQueue.next ();
79        delete packet;
80    }
81}
82
83void WorldSession::SizeError(WorldPacket const& packet, uint32 size) const
84{
85    sLog.outError("Client (account %u) send packet %s (%u) with size %u but expected %u (attempt crash server?), skipped",
86        GetAccountId(),LookupOpcodeName(packet.GetOpcode()),packet.GetOpcode(),packet.size(),size);
87}
88
89/// Get the player name
90char const* WorldSession::GetPlayerName() const
91{
92    return GetPlayer() ? GetPlayer()->GetName() : "<none>";
93}
94
95/// Send a packet to the client
96void WorldSession::SendPacket(WorldPacket const* packet)
97{
98    if (!m_Socket)
99        return;
100
101    #ifdef TRINITY_DEBUG
102
103    // Code for network use statistic
104    static uint64 sendPacketCount = 0;
105    static uint64 sendPacketBytes = 0;
106
107    static time_t firstTime = time(NULL);
108    static time_t lastTime = firstTime;                     // next 60 secs start time
109
110    static uint64 sendLastPacketCount = 0;
111    static uint64 sendLastPacketBytes = 0;
112
113    time_t cur_time = time(NULL);
114
115    if((cur_time - lastTime) < 60)
116    {
117        sendPacketCount+=1;
118        sendPacketBytes+=packet->size();
119
120        sendLastPacketCount+=1;
121        sendLastPacketBytes+=packet->size();
122    }
123    else
124    {
125        uint64 minTime = uint64(cur_time - lastTime);
126        uint64 fullTime = uint64(lastTime - firstTime);
127        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));
128        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);
129
130        lastTime = cur_time;
131        sendLastPacketCount = 1;
132        sendLastPacketBytes = packet->wpos();               // wpos is real written size
133    }
134
135    #endif                                                  // !MANGOS_DEBUG
136
137    if (m_Socket->SendPacket (*packet) == -1)
138        m_Socket->CloseSocket ();
139}
140
141/// Add an incoming packet to the queue
142void WorldSession::QueuePacket(WorldPacket* new_packet)
143{
144    _recvQueue.add(new_packet);
145}
146
147/// Logging helper for unexpected opcodes
148void WorldSession::logUnexpectedOpcode(WorldPacket* packet, const char *reason)
149{
150    sLog.outError( "SESSION: received unexpected opcode %s (0x%.4X) %s",
151        LookupOpcodeName(packet->GetOpcode()),
152        packet->GetOpcode(),
153        reason);
154}
155
156/// Update the WorldSession (triggered by World update)
157bool WorldSession::Update(uint32 /*diff*/)
158{
159    if (m_Socket && m_Socket->IsClosed ())
160    {
161        m_Socket->RemoveReference ();
162        m_Socket = NULL;
163    }
164
165    WorldPacket *packet;
166
167    ///- Retrieve packets from the receive queue and call the appropriate handlers
168    /// \todo Is there a way to consolidate the OpcondeHandlerTable and the g_worldOpcodeNames to only maintain 1 list?
169    /// answer : there is a way, but this is better, because it would use redundant RAM
170    while (!_recvQueue.empty())
171    {
172        packet = _recvQueue.next();
173
174        /*#if 1
175        sLog.outError( "MOEP: %s (0x%.4X)",
176                        LookupOpcodeName(packet->GetOpcode()),
177                        packet->GetOpcode());
178        #endif*/
179
180        if(packet->GetOpcode() >= NUM_MSG_TYPES)
181        {
182            sLog.outError( "SESSION: received non-existed opcode %s (0x%.4X)",
183                LookupOpcodeName(packet->GetOpcode()),
184                packet->GetOpcode());
185        }
186        else
187        {
188            OpcodeHandler& opHandle = opcodeTable[packet->GetOpcode()];
189            switch (opHandle.status)
190            {
191                case STATUS_LOGGEDIN:
192                    if(!_player)
193                    {
194                        // skip STATUS_LOGGEDIN opcode unexpected errors if player logout sometime ago - this can be network lag delayed packets
195                        if(!m_playerRecentlyLogout)
196                            logUnexpectedOpcode(packet, "the player has not logged in yet");
197                    }
198                    else if(_player->IsInWorld())
199                        (this->*opHandle.handler)(*packet);
200                    // lag can cause STATUS_LOGGEDIN opcodes to arrive after the player started a transfer
201                    break;
202                case STATUS_TRANSFER_PENDING:
203                    if(!_player)
204                        logUnexpectedOpcode(packet, "the player has not logged in yet");
205                    else if(_player->IsInWorld())
206                        logUnexpectedOpcode(packet, "the player is still in world");
207                    else
208                        (this->*opHandle.handler)(*packet);
209                    break;
210                case STATUS_AUTHED:
211                    m_playerRecentlyLogout = false;
212                    (this->*opHandle.handler)(*packet);
213                    break;
214                case STATUS_NEVER:
215                    sLog.outError( "SESSION: received not allowed opcode %s (0x%.4X)",
216                        LookupOpcodeName(packet->GetOpcode()),
217                        packet->GetOpcode());
218                    break;
219            }
220        }
221
222        delete packet;
223    }
224
225    ///- If necessary, log the player out
226    time_t currTime = time(NULL);
227    if (!m_Socket || (ShouldLogOut(currTime) && !m_playerLoading))
228        LogoutPlayer(true);
229
230    if (!m_Socket)
231        return false;                                       //Will remove this session from the world session map
232
233    return true;
234}
235
236/// %Log the player out
237void WorldSession::LogoutPlayer(bool Save)
238{
239    // finish pending transfers before starting the logout
240    while(_player && _player->IsBeingTeleported())
241        HandleMoveWorldportAckOpcode();
242
243    m_playerLogout = true;
244
245    if (_player)
246    {
247        if (uint64 lguid = GetPlayer()->GetLootGUID())
248            DoLootRelease(lguid);
249
250        ///- If the player just died before logging out, make him appear as a ghost
251        //FIXME: logout must be delayed in case lost connection with client in time of combat
252        if (_player->GetDeathTimer())
253        {
254            _player->getHostilRefManager().deleteReferences();
255            _player->BuildPlayerRepop();
256            _player->RepopAtGraveyard();
257        }
258        else if (!_player->getAttackers().empty())
259        {
260            _player->CombatStop();
261            _player->getHostilRefManager().setOnlineOfflineState(false);
262            _player->RemoveAllAurasOnDeath();
263
264            // build set of player who attack _player or who have pet attacking of _player
265            std::set<Player*> aset;
266            for(Unit::AttackerSet::const_iterator itr = _player->getAttackers().begin(); itr != _player->getAttackers().end(); ++itr)
267            {
268                Unit* owner = (*itr)->GetOwner();           // including player controlled case
269                if(owner)
270                {
271                    if(owner->GetTypeId()==TYPEID_PLAYER)
272                        aset.insert((Player*)owner);
273                }
274                else
275                if((*itr)->GetTypeId()==TYPEID_PLAYER)
276                    aset.insert((Player*)(*itr));
277            }
278
279            _player->SetPvPDeath(!aset.empty());
280            _player->KillPlayer();
281            _player->BuildPlayerRepop();
282            _player->RepopAtGraveyard();
283
284            // give honor to all attackers from set like group case
285            for(std::set<Player*>::const_iterator itr = aset.begin(); itr != aset.end(); ++itr)
286                (*itr)->RewardHonor(_player,aset.size());
287
288            // give bg rewards and update counters like kill by first from attackers
289            // this can't be called for all attackers.
290            if(!aset.empty())
291                if(BattleGround *bg = _player->GetBattleGround())
292                    bg->HandleKillPlayer(_player,*aset.begin());
293        }
294        else if(_player->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION))
295        {
296            // this will kill character by SPELL_AURA_SPIRIT_OF_REDEMPTION
297            _player->RemoveSpellsCausingAura(SPELL_AURA_MOD_SHAPESHIFT);
298            //_player->SetDeathPvP(*); set at SPELL_AURA_SPIRIT_OF_REDEMPTION apply time
299            _player->KillPlayer();
300            _player->BuildPlayerRepop();
301            _player->RepopAtGraveyard();
302        }
303
304        ///- Remove player from battleground (teleport to entrance)
305        if(_player->InBattleGround())
306            _player->LeaveBattleground();
307
308        sOutdoorPvPMgr.HandlePlayerLeaveZone(_player,_player->GetZoneId());
309
310        for (int i=0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; i++)
311        {
312            if(int32 bgTypeId = _player->GetBattleGroundQueueId(i))
313            {
314                _player->RemoveBattleGroundQueueId(bgTypeId);
315                sBattleGroundMgr.m_BattleGroundQueues[ bgTypeId ].RemovePlayer(_player->GetGUID(), true);
316            }
317        }
318
319        ///- Reset the online field in the account table
320        // 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
321        //No SQL injection as AccountID is uint32
322        loginDatabase.PExecute("UPDATE account SET online = 0 WHERE id = '%u'", GetAccountId());
323
324        ///- If the player is in a guild, update the guild roster and broadcast a logout message to other guild members
325        Guild *guild = objmgr.GetGuildById(_player->GetGuildId());
326        if(guild)
327        {
328            guild->LoadPlayerStatsByGuid(_player->GetGUID());
329            guild->UpdateLogoutTime(_player->GetGUID());
330
331            WorldPacket data(SMSG_GUILD_EVENT, (1+1+12+8)); // name limited to 12 in character table.
332            data<<(uint8)GE_SIGNED_OFF;
333            data<<(uint8)1;
334            data<<_player->GetName();
335            data<<_player->GetGUID();
336            guild->BroadcastPacket(&data);
337        }
338
339        ///- Remove pet
340        _player->RemovePet(NULL,PET_SAVE_AS_CURRENT, true);
341
342        ///- empty buyback items and save the player in the database
343        // some save parts only correctly work in case player present in map/player_lists (pets, etc)
344        if(Save)
345        {
346            uint32 eslot;
347            for(int j = BUYBACK_SLOT_START; j < BUYBACK_SLOT_END; j++)
348            {
349                eslot = j - BUYBACK_SLOT_START;
350                _player->SetUInt64Value(PLAYER_FIELD_VENDORBUYBACK_SLOT_1+eslot*2,0);
351                _player->SetUInt32Value(PLAYER_FIELD_BUYBACK_PRICE_1+eslot,0);
352                _player->SetUInt32Value(PLAYER_FIELD_BUYBACK_TIMESTAMP_1+eslot,0);
353            }
354            _player->SaveToDB();
355        }
356
357        ///- Leave all channels before player delete...
358        _player->CleanupChannels();
359
360        ///- If the player is in a group (or invited), remove him. If the group if then only 1 person, disband the group.
361        _player->UninviteFromGroup();
362
363        // remove player from the group if he is:
364        // a) in group; b) not in raid group; c) logging out normally (not being kicked or disconnected)
365        if(_player->GetGroup() && !_player->GetGroup()->isRaidGroup() && m_Socket)
366            _player->RemoveFromGroup();
367
368        // Unpossess the current possessed unit of player
369        if (_player->isPossessing())
370            _player->RemovePossess(false);
371
372        // Remove any possession of this player on logout
373        _player->UnpossessSelf(false);
374
375        ///- Remove the player from the world
376        // the player may not be in the world when logging out
377        // e.g if he got disconnected during a transfer to another map
378        // calls to GetMap in this case may cause crashes
379        if(_player->IsInWorld()) MapManager::Instance().GetMap(_player->GetMapId(), _player)->Remove(_player, false);
380        // RemoveFromWorld does cleanup that requires the player to be in the accessor
381        ObjectAccessor::Instance().RemoveObject(_player);
382
383        ///- Send update to group
384        if(_player->GetGroup())
385            _player->GetGroup()->SendUpdate();
386
387        ///- Broadcast a logout message to the player's friends
388        sSocialMgr.SendFriendStatus(_player, FRIEND_OFFLINE, _player->GetGUIDLow(), true);
389
390        ///- Delete the player object
391        _player->CleanupsBeforeDelete();                    // do some cleanup before deleting to prevent crash at crossreferences to already deleted data
392
393        sSocialMgr.RemovePlayerSocial (_player->GetGUIDLow ());
394        delete _player;
395        _player = NULL;
396
397        ///- Send the 'logout complete' packet to the client
398        WorldPacket data( SMSG_LOGOUT_COMPLETE, 0 );
399        SendPacket( &data );
400
401        ///- Since each account can only have one online character at any given time, ensure all characters for active account are marked as offline
402        //No SQL injection as AccountId is uint32
403        CharacterDatabase.PExecute("UPDATE characters SET online = 0 WHERE account = '%u'",
404            GetAccountId());
405        sLog.outDebug( "SESSION: Sent SMSG_LOGOUT_COMPLETE Message" );
406    }
407
408    m_playerLogout = false;
409    m_playerRecentlyLogout = true;
410    LogoutRequest(0);
411}
412
413/// Kick a player out of the World
414void WorldSession::KickPlayer()
415{
416    if (m_Socket)
417        m_Socket->CloseSocket ();
418}
419
420/// Cancel channeling handler
421
422void WorldSession::SendAreaTriggerMessage(const char* Text, ...)
423{
424    va_list ap;
425    char szStr [1024];
426    szStr[0] = '\0';
427
428    va_start(ap, Text);
429    vsnprintf( szStr, 1024, Text, ap );
430    va_end(ap);
431
432    uint32 length = strlen(szStr)+1;
433    WorldPacket data(SMSG_AREA_TRIGGER_MESSAGE, 4+length);
434    data << length;
435    data << szStr;
436    SendPacket(&data);
437}
438
439void WorldSession::SendNotification(const char *format,...)
440{
441    if(format)
442    {
443        va_list ap;
444        char szStr [1024];
445        szStr[0] = '\0';
446        va_start(ap, format);
447        vsnprintf( szStr, 1024, format, ap );
448        va_end(ap);
449
450        WorldPacket data(SMSG_NOTIFICATION, (strlen(szStr)+1));
451        data << szStr;
452        SendPacket(&data);
453    }
454}
455
456void WorldSession::SendNotification(int32 string_id,...)
457{
458    char const* format = GetTrinityString(string_id);
459    if(format)
460    {
461        va_list ap;
462        char szStr [1024];
463        szStr[0] = '\0';
464        va_start(ap, string_id);
465        vsnprintf( szStr, 1024, format, ap );
466        va_end(ap);
467
468        WorldPacket data(SMSG_NOTIFICATION, (strlen(szStr)+1));
469        data << szStr;
470        SendPacket(&data);
471    }
472}
473
474const char * WorldSession::GetTrinityString( int32 entry ) const
475{
476    return objmgr.GetTrinityString(entry,GetSessionDbLocaleIndex());
477}
478
479void WorldSession::Handle_NULL( WorldPacket& recvPacket )
480{
481    sLog.outError( "SESSION: received unhandled opcode %s (0x%.4X)",
482        LookupOpcodeName(recvPacket.GetOpcode()),
483        recvPacket.GetOpcode());
484}
485
486void WorldSession::Handle_EarlyProccess( WorldPacket& recvPacket )
487{
488    sLog.outError( "SESSION: received opcode %s (0x%.4X) that must be proccessed in WorldSocket::OnRead",
489        LookupOpcodeName(recvPacket.GetOpcode()),
490        recvPacket.GetOpcode());
491}
492
493void WorldSession::Handle_ServerSide( WorldPacket& recvPacket )
494{
495    sLog.outError( "SESSION: received sever-side opcode %s (0x%.4X)",
496        LookupOpcodeName(recvPacket.GetOpcode()),
497        recvPacket.GetOpcode());
498}
499
500void WorldSession::Handle_Depricated( WorldPacket& recvPacket )
501{
502    sLog.outError( "SESSION: received depricated opcode %s (0x%.4X)",
503        LookupOpcodeName(recvPacket.GetOpcode()),
504        recvPacket.GetOpcode());
505}
506
507void WorldSession::SendAuthWaitQue(uint32 position)
508 {
509     if(position == 0)
510     {
511         WorldPacket packet( SMSG_AUTH_RESPONSE, 1 );
512         packet << uint8( AUTH_OK );
513         SendPacket(&packet);
514     }
515     else
516     {
517         WorldPacket packet( SMSG_AUTH_RESPONSE, 5 );
518         packet << uint8( AUTH_WAIT_QUEUE );
519         packet << uint32 (position);
520         SendPacket(&packet);
521     }
522 }
Note: See TracBrowser for help on using the browser.