2 * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #include "WorldSocket.h" // must be first to make ACE happy with ACE includes in it
25 #include "Database/DatabaseEnv.h"
28 #include "WorldPacket.h"
29 #include "WorldSession.h"
31 #include "ObjectMgr.h"
35 #include "BattleGroundMgr.h"
36 #include "MapManager.h"
37 #include "SocialMgr.h"
38 #include "zlib/zlib.h"
40 /// WorldSession constructor
41 WorldSession::WorldSession(uint32 id
, WorldSocket
*sock
, AccountTypes sec
, uint8 expansion
, time_t mute_time
, LocaleConstant locale
) :
42 LookingForGroup_auto_join(false), LookingForGroup_auto_add(false), m_muteTime(mute_time
),
43 _player(NULL
), m_Socket(sock
),_security(sec
), _accountId(id
), m_expansion(expansion
),
44 m_sessionDbcLocale(sWorld
.GetAvailableDbcLocale(locale
)), m_sessionDbLocaleIndex(sObjectMgr
.GetIndexForLocale(locale
)),
45 _logoutTime(0), m_inQueue(false), m_playerLoading(false), m_playerLogout(false), m_playerRecentlyLogout(false), m_playerSave(false),
46 m_latency(0), m_TutorialsChanged(false)
50 m_Address
= sock
->GetRemoteAddress ();
51 sock
->AddReference ();
55 /// WorldSession destructor
56 WorldSession::~WorldSession()
58 ///- unload player if not unloaded
62 /// - If have unclosed socket, close it
65 m_Socket
->CloseSocket ();
66 m_Socket
->RemoveReference ();
70 ///- empty incoming packet queue
72 while(_recvQueue
.next(packet
))
76 void WorldSession::SizeError(WorldPacket
const& packet
, uint32 size
) const
78 sLog
.outError("Client (account %u) send packet %s (%u) with size " SIZEFMTD
" but expected %u (attempt crash server?), skipped",
79 GetAccountId(),LookupOpcodeName(packet
.GetOpcode()),packet
.GetOpcode(),packet
.size(),size
);
82 /// Get the player name
83 char const* WorldSession::GetPlayerName() const
85 return GetPlayer() ? GetPlayer()->GetName() : "<none>";
88 /// Send a packet to the client
89 void WorldSession::SendPacket(WorldPacket
const* packet
)
96 // Code for network use statistic
97 static uint64 sendPacketCount
= 0;
98 static uint64 sendPacketBytes
= 0;
100 static time_t firstTime
= time(NULL
);
101 static time_t lastTime
= firstTime
; // next 60 secs start time
103 static uint64 sendLastPacketCount
= 0;
104 static uint64 sendLastPacketBytes
= 0;
106 time_t cur_time
= time(NULL
);
108 if((cur_time
- lastTime
) < 60)
111 sendPacketBytes
+=packet
->size();
113 sendLastPacketCount
+=1;
114 sendLastPacketBytes
+=packet
->size();
118 uint64 minTime
= uint64(cur_time
- lastTime
);
119 uint64 fullTime
= uint64(lastTime
- firstTime
);
120 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
));
121 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
);
124 sendLastPacketCount
= 1;
125 sendLastPacketBytes
= packet
->wpos(); // wpos is real written size
128 #endif // !MANGOS_DEBUG
130 if (m_Socket
->SendPacket (*packet
) == -1)
131 m_Socket
->CloseSocket ();
134 /// Add an incoming packet to the queue
135 void WorldSession::QueuePacket(WorldPacket
* new_packet
)
137 _recvQueue
.add(new_packet
);
140 /// Logging helper for unexpected opcodes
141 void WorldSession::LogUnexpectedOpcode(WorldPacket
* packet
, const char *reason
)
143 sLog
.outError( "SESSION: received unexpected opcode %s (0x%.4X) %s",
144 LookupOpcodeName(packet
->GetOpcode()),
149 /// Logging helper for unexpected opcodes
150 void WorldSession::LogUnprocessedTail(WorldPacket
*packet
)
152 sLog
.outError( "SESSION: opcode %s (0x%.4X) have unprocessed tail data (read stop at " SIZEFMTD
" from " SIZEFMTD
")",
153 LookupOpcodeName(packet
->GetOpcode()),
155 packet
->rpos(),packet
->wpos());
158 /// Update the WorldSession (triggered by World update)
159 bool WorldSession::Update(uint32
/*diff*/)
161 ///- Retrieve packets from the receive queue and call the appropriate handlers
162 /// not proccess packets if socket already closed
164 while (_recvQueue
.next(packet
) && m_Socket
&& !m_Socket
->IsClosed ())
167 sLog.outError( "MOEP: %s (0x%.4X)",
168 LookupOpcodeName(packet->GetOpcode()),
169 packet->GetOpcode());
172 OpcodeHandler
& opHandle
= opcodeTable
[packet
->GetOpcode()];
175 switch (opHandle
.status
)
177 case STATUS_LOGGEDIN
:
180 // skip STATUS_LOGGEDIN opcode unexpected errors if player logout sometime ago - this can be network lag delayed packets
181 if(!m_playerRecentlyLogout
)
182 LogUnexpectedOpcode(packet
, "the player has not logged in yet");
184 else if(_player
->IsInWorld())
186 (this->*opHandle
.handler
)(*packet
);
187 if (sLog
.IsOutDebug() && packet
->rpos() < packet
->wpos())
188 LogUnprocessedTail(packet
);
190 // lag can cause STATUS_LOGGEDIN opcodes to arrive after the player started a transfer
192 case STATUS_LOGGEDIN_OR_RECENTLY_LOGGEDOUT
:
193 if(!_player
&& !m_playerRecentlyLogout
)
195 LogUnexpectedOpcode(packet
, "the player has not logged in yet and not recently logout");
199 // not expected _player or must checked in packet hanlder
200 (this->*opHandle
.handler
)(*packet
);
201 if (sLog
.IsOutDebug() && packet
->rpos() < packet
->wpos())
202 LogUnprocessedTail(packet
);
205 case STATUS_TRANSFER
:
207 LogUnexpectedOpcode(packet
, "the player has not logged in yet");
208 else if(_player
->IsInWorld())
209 LogUnexpectedOpcode(packet
, "the player is still in world");
212 (this->*opHandle
.handler
)(*packet
);
213 if (sLog
.IsOutDebug() && packet
->rpos() < packet
->wpos())
214 LogUnprocessedTail(packet
);
218 // prevent cheating with skip queue wait
221 LogUnexpectedOpcode(packet
, "the player not pass queue yet");
225 // single from authed time opcodes send in to after logout time
226 // and before other STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT opcodes.
227 if (packet
->GetOpcode() != CMSG_SET_ACTIVE_VOICE_CHANNEL
)
228 m_playerRecentlyLogout
= false;
230 (this->*opHandle
.handler
)(*packet
);
231 if (sLog
.IsOutDebug() && packet
->rpos() < packet
->wpos())
232 LogUnprocessedTail(packet
);
235 sLog
.outError( "SESSION: received not allowed opcode %s (0x%.4X)",
236 LookupOpcodeName(packet
->GetOpcode()),
237 packet
->GetOpcode());
239 case STATUS_UNHANDLED
:
240 sLog
.outDebug("SESSION: received not handled opcode %s (0x%.4X)",
241 LookupOpcodeName(packet
->GetOpcode()),
242 packet
->GetOpcode());
245 sLog
.outError("SESSION: received wrong-status-req opcode %s (0x%.4X)",
246 LookupOpcodeName(packet
->GetOpcode()),
247 packet
->GetOpcode());
251 catch(ByteBufferException
&)
253 sLog
.outError("WorldSession::Update ByteBufferException occured while parsing a packet (opcode: %u) from client %s, accountid=%i. Skipped packet.",
254 packet
->GetOpcode(), GetRemoteAddress().c_str(), GetAccountId());
255 if(sLog
.IsOutDebug())
257 sLog
.outDebug("Dumping error causing packet:");
265 ///- Cleanup socket pointer if need
266 if (m_Socket
&& m_Socket
->IsClosed ())
268 m_Socket
->RemoveReference ();
272 ///- If necessary, log the player out
273 time_t currTime
= time(NULL
);
274 if (!m_Socket
|| (ShouldLogOut(currTime
) && !m_playerLoading
))
278 return false; //Will remove this session from the world session map
283 /// %Log the player out
284 void WorldSession::LogoutPlayer(bool Save
)
286 // finish pending transfers before starting the logout
287 while(_player
&& _player
->IsBeingTeleportedFar())
288 HandleMoveWorldportAckOpcode();
290 m_playerLogout
= true;
295 if (uint64 lguid
= GetPlayer()->GetLootGUID())
296 DoLootRelease(lguid
);
298 ///- If the player just died before logging out, make him appear as a ghost
299 //FIXME: logout must be delayed in case lost connection with client in time of combat
300 if (_player
->GetDeathTimer())
302 _player
->getHostileRefManager().deleteReferences();
303 _player
->BuildPlayerRepop();
304 _player
->RepopAtGraveyard();
306 else if (!_player
->getAttackers().empty())
308 _player
->CombatStop();
309 _player
->getHostileRefManager().setOnlineOfflineState(false);
310 _player
->RemoveAllAurasOnDeath();
312 // build set of player who attack _player or who have pet attacking of _player
313 std::set
<Player
*> aset
;
314 for(Unit::AttackerSet::const_iterator itr
= _player
->getAttackers().begin(); itr
!= _player
->getAttackers().end(); ++itr
)
316 Unit
* owner
= (*itr
)->GetOwner(); // including player controlled case
319 if(owner
->GetTypeId()==TYPEID_PLAYER
)
320 aset
.insert((Player
*)owner
);
323 if((*itr
)->GetTypeId()==TYPEID_PLAYER
)
324 aset
.insert((Player
*)(*itr
));
327 _player
->SetPvPDeath(!aset
.empty());
328 _player
->KillPlayer();
329 _player
->BuildPlayerRepop();
330 _player
->RepopAtGraveyard();
332 // give honor to all attackers from set like group case
333 for(std::set
<Player
*>::const_iterator itr
= aset
.begin(); itr
!= aset
.end(); ++itr
)
334 (*itr
)->RewardHonor(_player
,aset
.size());
336 // give bg rewards and update counters like kill by first from attackers
337 // this can't be called for all attackers.
339 if(BattleGround
*bg
= _player
->GetBattleGround())
340 bg
->HandleKillPlayer(_player
,*aset
.begin());
342 else if(_player
->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION
))
344 // this will kill character by SPELL_AURA_SPIRIT_OF_REDEMPTION
345 _player
->RemoveSpellsCausingAura(SPELL_AURA_MOD_SHAPESHIFT
);
346 //_player->SetDeathPvP(*); set at SPELL_AURA_SPIRIT_OF_REDEMPTION apply time
347 _player
->KillPlayer();
348 _player
->BuildPlayerRepop();
349 _player
->RepopAtGraveyard();
351 //drop a flag if player is carrying it
352 if(BattleGround
*bg
= _player
->GetBattleGround())
353 bg
->EventPlayerLoggedOut(_player
);
355 ///- Teleport to home if the player is in an invalid instance
356 if(!_player
->m_InstanceValid
&& !_player
->isGameMaster())
358 _player
->TeleportToHomebind();
359 //this is a bad place to call for far teleport because we need player to be in world for successful logout
360 //maybe we should implement delayed far teleport logout?
363 // FG: finish pending transfers after starting the logout
364 // this should fix players beeing able to logout and login back with full hp at death position
365 while(_player
->IsBeingTeleportedFar())
366 HandleMoveWorldportAckOpcode();
368 for (int i
=0; i
< PLAYER_MAX_BATTLEGROUND_QUEUES
; ++i
)
370 if(BattleGroundQueueTypeId bgQueueTypeId
= _player
->GetBattleGroundQueueTypeId(i
))
372 _player
->RemoveBattleGroundQueueId(bgQueueTypeId
);
373 sBattleGroundMgr
.m_BattleGroundQueues
[ bgQueueTypeId
].RemovePlayer(_player
->GetGUID(), true);
377 ///- Reset the online field in the account table
378 // 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
379 // No SQL injection as AccountID is uint32
380 loginDatabase
.PExecute("UPDATE account SET active_realm_id = 0 WHERE id = '%u'", GetAccountId());
382 ///- If the player is in a guild, update the guild roster and broadcast a logout message to other guild members
383 Guild
*guild
= sObjectMgr
.GetGuildById(_player
->GetGuildId());
386 guild
->SetMemberStats(_player
->GetGUID());
387 guild
->UpdateLogoutTime(_player
->GetGUID());
389 guild
->BroadcastEvent(GE_SIGNED_OFF
, _player
->GetGUID(), 1, _player
->GetName(), "", "");
393 _player
->RemovePet(NULL
, PET_SAVE_AS_CURRENT
, true);
395 ///- empty buyback items and save the player in the database
396 // some save parts only correctly work in case player present in map/player_lists (pets, etc)
400 for(int j
= BUYBACK_SLOT_START
; j
< BUYBACK_SLOT_END
; ++j
)
402 eslot
= j
- BUYBACK_SLOT_START
;
403 _player
->SetUInt64Value(PLAYER_FIELD_VENDORBUYBACK_SLOT_1
+ (eslot
* 2), 0);
404 _player
->SetUInt32Value(PLAYER_FIELD_BUYBACK_PRICE_1
+ eslot
, 0);
405 _player
->SetUInt32Value(PLAYER_FIELD_BUYBACK_TIMESTAMP_1
+ eslot
, 0);
410 ///- Leave all channels before player delete...
411 _player
->CleanupChannels();
413 ///- If the player is in a group (or invited), remove him. If the group if then only 1 person, disband the group.
414 _player
->UninviteFromGroup();
416 // remove player from the group if he is:
417 // a) in group; b) not in raid group; c) logging out normally (not being kicked or disconnected)
418 if(_player
->GetGroup() && !_player
->GetGroup()->isRaidGroup() && m_Socket
)
419 _player
->RemoveFromGroup();
421 ///- Send update to group
422 if(_player
->GetGroup())
423 _player
->GetGroup()->SendUpdate();
425 ///- Broadcast a logout message to the player's friends
426 sSocialMgr
.SendFriendStatus(_player
, FRIEND_OFFLINE
, _player
->GetGUIDLow(), true);
427 sSocialMgr
.RemovePlayerSocial (_player
->GetGUIDLow ());
429 ///- Remove the player from the world
430 // the player may not be in the world when logging out
431 // e.g if he got disconnected during a transfer to another map
432 // calls to GetMap in this case may cause crashes
433 Map
* _map
= _player
->GetMap();
434 _map
->Remove(_player
, true);
435 SetPlayer(NULL
); // deleted in Remove call
437 ///- Send the 'logout complete' packet to the client
438 WorldPacket
data( SMSG_LOGOUT_COMPLETE
, 0 );
441 ///- Since each account can only have one online character at any given time, ensure all characters for active account are marked as offline
442 //No SQL injection as AccountId is uint32
443 CharacterDatabase
.PExecute("UPDATE characters SET online = 0 WHERE account = '%u'",
445 sLog
.outDebug( "SESSION: Sent SMSG_LOGOUT_COMPLETE Message" );
448 m_playerLogout
= false;
449 m_playerSave
= false;
450 m_playerRecentlyLogout
= true;
454 /// Kick a player out of the World
455 void WorldSession::KickPlayer()
458 m_Socket
->CloseSocket ();
461 /// Cancel channeling handler
463 void WorldSession::SendAreaTriggerMessage(const char* Text
, ...)
470 vsnprintf( szStr
, 1024, Text
, ap
);
473 uint32 length
= strlen(szStr
)+1;
474 WorldPacket
data(SMSG_AREA_TRIGGER_MESSAGE
, 4+length
);
480 void WorldSession::SendNotification(const char *format
,...)
487 va_start(ap
, format
);
488 vsnprintf( szStr
, 1024, format
, ap
);
491 WorldPacket
data(SMSG_NOTIFICATION
, (strlen(szStr
)+1));
497 void WorldSession::SendNotification(int32 string_id
,...)
499 char const* format
= GetMangosString(string_id
);
505 va_start(ap
, string_id
);
506 vsnprintf( szStr
, 1024, format
, ap
);
509 WorldPacket
data(SMSG_NOTIFICATION
, (strlen(szStr
)+1));
515 void WorldSession::SendSetPhaseShift(uint32 PhaseShift
)
517 WorldPacket
data(SMSG_SET_PHASE_SHIFT
, 4);
518 data
<< uint32(PhaseShift
);
522 const char * WorldSession::GetMangosString( int32 entry
) const
524 return sObjectMgr
.GetMangosString(entry
,GetSessionDbLocaleIndex());
527 void WorldSession::Handle_NULL( WorldPacket
& recvPacket
)
529 sLog
.outError( "SESSION: received unhandled opcode %s (0x%.4X)",
530 LookupOpcodeName(recvPacket
.GetOpcode()),
531 recvPacket
.GetOpcode());
534 void WorldSession::Handle_EarlyProccess( WorldPacket
& recvPacket
)
536 sLog
.outError( "SESSION: received opcode %s (0x%.4X) that must be processed in WorldSocket::OnRead",
537 LookupOpcodeName(recvPacket
.GetOpcode()),
538 recvPacket
.GetOpcode());
541 void WorldSession::Handle_ServerSide( WorldPacket
& recvPacket
)
543 sLog
.outError( "SESSION: received server-side opcode %s (0x%.4X)",
544 LookupOpcodeName(recvPacket
.GetOpcode()),
545 recvPacket
.GetOpcode());
548 void WorldSession::Handle_Deprecated( WorldPacket
& recvPacket
)
550 sLog
.outError( "SESSION: received deprecated opcode %s (0x%.4X)",
551 LookupOpcodeName(recvPacket
.GetOpcode()),
552 recvPacket
.GetOpcode());
555 void WorldSession::SendAuthWaitQue(uint32 position
)
559 WorldPacket
packet( SMSG_AUTH_RESPONSE
, 1 );
560 packet
<< uint8( AUTH_OK
);
565 WorldPacket
packet( SMSG_AUTH_RESPONSE
, 1+4+1 );
566 packet
<< uint8(AUTH_WAIT_QUEUE
);
567 packet
<< uint32(position
);
568 packet
<< uint8(0); // unk 3.3.0
573 void WorldSession::LoadGlobalAccountData()
576 CharacterDatabase
.PQuery("SELECT type, time, data FROM account_data WHERE account='%u'", GetAccountId()),
581 void WorldSession::LoadAccountData(QueryResult
* result
, uint32 mask
)
583 for (uint32 i
= 0; i
< NUM_ACCOUNT_DATA_TYPES
; ++i
)
585 m_accountData
[i
] = AccountData();
592 Field
*fields
= result
->Fetch();
594 uint32 type
= fields
[0].GetUInt32();
595 if (type
>= NUM_ACCOUNT_DATA_TYPES
)
597 sLog
.outError("Table `%s` have invalid account data type (%u), ignore.",
598 mask
== GLOBAL_CACHE_MASK
? "account_data" : "character_account_data", type
);
602 if ((mask
& (1 << type
))==0)
604 sLog
.outError("Table `%s` have non appropriate for table account data type (%u), ignore.",
605 mask
== GLOBAL_CACHE_MASK
? "account_data" : "character_account_data", type
);
609 m_accountData
[type
].Time
= fields
[1].GetUInt32();
610 m_accountData
[type
].Data
= fields
[2].GetCppString();
612 } while (result
->NextRow());
617 void WorldSession::SetAccountData(AccountDataType type
, time_t time_
, std::string data
)
619 if ((1 << type
) & GLOBAL_CACHE_MASK
)
621 uint32 acc
= GetAccountId();
623 CharacterDatabase
.BeginTransaction ();
624 CharacterDatabase
.PExecute("DELETE FROM account_data WHERE account='%u' AND type='%u'", acc
, type
);
625 std::string safe_data
= data
;
626 CharacterDatabase
.escape_string(safe_data
);
627 CharacterDatabase
.PExecute("INSERT INTO account_data VALUES ('%u','%u','%u','%s')", acc
, type
, (uint32
)time_
, safe_data
.c_str());
628 CharacterDatabase
.CommitTransaction ();
632 // _player can be NULL and packet received after logout but m_GUID still store correct guid
636 CharacterDatabase
.BeginTransaction ();
637 CharacterDatabase
.PExecute("DELETE FROM character_account_data WHERE guid='%u' AND type='%u'", m_GUIDLow
, type
);
638 std::string safe_data
= data
;
639 CharacterDatabase
.escape_string(safe_data
);
640 CharacterDatabase
.PExecute("INSERT INTO character_account_data VALUES ('%u','%u','%u','%s')", m_GUIDLow
, type
, (uint32
)time_
, safe_data
.c_str());
641 CharacterDatabase
.CommitTransaction ();
644 m_accountData
[type
].Time
= time_
;
645 m_accountData
[type
].Data
= data
;
648 void WorldSession::SendAccountDataTimes(uint32 mask
)
650 WorldPacket
data( SMSG_ACCOUNT_DATA_TIMES
, 4+1+4+8*4 ); // changed in WotLK
651 data
<< uint32(time(NULL
)); // unix time of something
653 data
<< uint32(mask
); // type mask
654 for(uint32 i
= 0; i
< NUM_ACCOUNT_DATA_TYPES
; ++i
)
656 data
<< uint32(GetAccountData(AccountDataType(i
))->Time
);// also unix time
660 void WorldSession::LoadTutorialsData()
662 for ( int aX
= 0 ; aX
< 8 ; ++aX
)
663 m_Tutorials
[ aX
] = 0;
665 QueryResult
*result
= CharacterDatabase
.PQuery("SELECT tut0,tut1,tut2,tut3,tut4,tut5,tut6,tut7 FROM character_tutorial WHERE account = '%u'", GetAccountId());
671 Field
*fields
= result
->Fetch();
673 for (int iI
= 0; iI
< 8; ++iI
)
674 m_Tutorials
[iI
] = fields
[iI
].GetUInt32();
676 while( result
->NextRow() );
681 m_TutorialsChanged
= false;
684 void WorldSession::SendTutorialsData()
686 WorldPacket
data(SMSG_TUTORIAL_FLAGS
, 4*8);
687 for(uint32 i
= 0; i
< 8; ++i
)
688 data
<< m_Tutorials
[i
];
692 void WorldSession::SaveTutorialsData()
694 if(!m_TutorialsChanged
)
698 // it's better than rebuilding indexes multiple times
699 QueryResult
*result
= CharacterDatabase
.PQuery("SELECT count(*) AS r FROM character_tutorial WHERE account = '%u'", GetAccountId());
702 Rows
= result
->Fetch()[0].GetUInt32();
708 CharacterDatabase
.PExecute("UPDATE character_tutorial SET tut0='%u', tut1='%u', tut2='%u', tut3='%u', tut4='%u', tut5='%u', tut6='%u', tut7='%u' WHERE account = '%u'",
709 m_Tutorials
[0], m_Tutorials
[1], m_Tutorials
[2], m_Tutorials
[3], m_Tutorials
[4], m_Tutorials
[5], m_Tutorials
[6], m_Tutorials
[7], GetAccountId());
713 CharacterDatabase
.PExecute("INSERT INTO character_tutorial (account,tut0,tut1,tut2,tut3,tut4,tut5,tut6,tut7) VALUES ('%u', '%u', '%u', '%u', '%u', '%u', '%u', '%u', '%u')", GetAccountId(), m_Tutorials
[0], m_Tutorials
[1], m_Tutorials
[2], m_Tutorials
[3], m_Tutorials
[4], m_Tutorials
[5], m_Tutorials
[6], m_Tutorials
[7]);
716 m_TutorialsChanged
= false;
719 void WorldSession::ReadAddonsInfo(WorldPacket
&data
)
721 if (data
.rpos() + 4 > data
.size())
731 sLog
.outError("WorldSession::ReadAddonsInfo addon info too big, size %u", size
);
737 uint32 pos
= data
.rpos();
739 ByteBuffer addonInfo
;
740 addonInfo
.resize(size
);
742 if (uncompress(const_cast<uint8
*>(addonInfo
.contents()), &uSize
, const_cast<uint8
*>(data
.contents() + pos
), data
.size() - pos
) == Z_OK
)
745 addonInfo
>> addonsCount
; // addons count
747 for(uint32 i
= 0; i
< addonsCount
; ++i
)
749 std::string addonName
;
753 // check next addon data format correctness
754 if(addonInfo
.rpos()+1 > addonInfo
.size())
757 addonInfo
>> addonName
;
759 addonInfo
>> enabled
>> crc
>> unk1
;
761 sLog
.outDebug("ADDON: Name: %s, Enabled: 0x%x, CRC: 0x%x, Unknown2: 0x%x", addonName
.c_str(), enabled
, crc
, unk1
);
763 m_addonsList
.push_back(AddonInfo(addonName
, enabled
, crc
));
769 if(addonInfo
.rpos() != addonInfo
.size())
770 sLog
.outDebug("packet under read!");
773 sLog
.outError("Addon packet uncompress error!");
776 void WorldSession::SendAddonsInfo()
778 unsigned char tdata
[256] =
780 0xC3, 0x5B, 0x50, 0x84, 0xB9, 0x3E, 0x32, 0x42, 0x8C, 0xD0, 0xC7, 0x48, 0xFA, 0x0E, 0x5D, 0x54,
781 0x5A, 0xA3, 0x0E, 0x14, 0xBA, 0x9E, 0x0D, 0xB9, 0x5D, 0x8B, 0xEE, 0xB6, 0x84, 0x93, 0x45, 0x75,
782 0xFF, 0x31, 0xFE, 0x2F, 0x64, 0x3F, 0x3D, 0x6D, 0x07, 0xD9, 0x44, 0x9B, 0x40, 0x85, 0x59, 0x34,
783 0x4E, 0x10, 0xE1, 0xE7, 0x43, 0x69, 0xEF, 0x7C, 0x16, 0xFC, 0xB4, 0xED, 0x1B, 0x95, 0x28, 0xA8,
784 0x23, 0x76, 0x51, 0x31, 0x57, 0x30, 0x2B, 0x79, 0x08, 0x50, 0x10, 0x1C, 0x4A, 0x1A, 0x2C, 0xC8,
785 0x8B, 0x8F, 0x05, 0x2D, 0x22, 0x3D, 0xDB, 0x5A, 0x24, 0x7A, 0x0F, 0x13, 0x50, 0x37, 0x8F, 0x5A,
786 0xCC, 0x9E, 0x04, 0x44, 0x0E, 0x87, 0x01, 0xD4, 0xA3, 0x15, 0x94, 0x16, 0x34, 0xC6, 0xC2, 0xC3,
787 0xFB, 0x49, 0xFE, 0xE1, 0xF9, 0xDA, 0x8C, 0x50, 0x3C, 0xBE, 0x2C, 0xBB, 0x57, 0xED, 0x46, 0xB9,
788 0xAD, 0x8B, 0xC6, 0xDF, 0x0E, 0xD6, 0x0F, 0xBE, 0x80, 0xB3, 0x8B, 0x1E, 0x77, 0xCF, 0xAD, 0x22,
789 0xCF, 0xB7, 0x4B, 0xCF, 0xFB, 0xF0, 0x6B, 0x11, 0x45, 0x2D, 0x7A, 0x81, 0x18, 0xF2, 0x92, 0x7E,
790 0x98, 0x56, 0x5D, 0x5E, 0x69, 0x72, 0x0A, 0x0D, 0x03, 0x0A, 0x85, 0xA2, 0x85, 0x9C, 0xCB, 0xFB,
791 0x56, 0x6E, 0x8F, 0x44, 0xBB, 0x8F, 0x02, 0x22, 0x68, 0x63, 0x97, 0xBC, 0x85, 0xBA, 0xA8, 0xF7,
792 0xB5, 0x40, 0x68, 0x3C, 0x77, 0x86, 0x6F, 0x4B, 0xD7, 0x88, 0xCA, 0x8A, 0xD7, 0xCE, 0x36, 0xF0,
793 0x45, 0x6E, 0xD5, 0x64, 0x79, 0x0F, 0x17, 0xFC, 0x64, 0xDD, 0x10, 0x6F, 0xF3, 0xF5, 0xE0, 0xA6,
794 0xC3, 0xFB, 0x1B, 0x8C, 0x29, 0xEF, 0x8E, 0xE5, 0x34, 0xCB, 0xD1, 0x2A, 0xCE, 0x79, 0xC3, 0x9A,
795 0x0D, 0x36, 0xEA, 0x01, 0xE0, 0xAA, 0x91, 0x20, 0x54, 0xF0, 0x72, 0xD8, 0x1E, 0xC7, 0x89, 0xD2
798 WorldPacket
data(SMSG_ADDON_INFO
, 4);
800 for(AddonsList::iterator itr
= m_addonsList
.begin(); itr
!= m_addonsList
.end(); ++itr
)
802 uint8 state
= 2; // 2 is sent here
803 data
<< uint8(state
);
805 uint8 unk1
= 1; // 1 is sent here
809 uint8 unk2
= (itr
->CRC
!= 0x4c1c776d); // If addon is Standard addon CRC
810 data
<< uint8(unk2
); // if 1, than add addon public signature
811 if (unk2
) // if CRC is wrong, add public key (client need it)
812 data
.append(tdata
, sizeof(tdata
));
817 uint8 unk3
= 0; // 0 is sent here
818 data
<< uint8(unk3
); // use <Addon>\<Addon>.url file or not
821 // String, 256 (null terminated?)
826 m_addonsList
.clear();
829 data
<< uint32(count
); // BannedAddons count
830 /*for(uint32 i = 0; i < count; ++i)
842 void WorldSession::SetPlayer( Player
*plr
)
846 // set m_GUID that can be used while player loggined and later until m_playerRecentlyLogout not reset
848 m_GUIDLow
= _player
->GetGUIDLow();