· 8 years ago · Mar 13, 2018, 08:30 PM
1/**
2* The Forgotten Server - a free and open-source MMORPG server emulator
3* Copyright (C) 2017 Mark Samman <mark.samman@gmail.com>
4*
5* This program is free software; you can redistribute it and/or modify
6* it under the terms of the GNU General Public License as published by
7* the Free Software Foundation; either version 2 of the License, or
8* (at your option) any later version.
9*
10* This program is distributed in the hope that it will be useful,
11* but WITHOUT ANY WARRANTY; without even the implied warranty of
12* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13* GNU General Public License for more details.
14*
15* You should have received a copy of the GNU General Public License along
16* with this program; if not, write to the Free Software Foundation, Inc.,
17* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18*/
19
20#include "otpch.h"
21
22#include <boost/range/adaptor/reversed.hpp>
23
24#include "protocolgame.h"
25
26#include "outputmessage.h"
27
28#include "player.h"
29
30#include "configmanager.h"
31#include "actions.h"
32#include "game.h"
33#include "iologindata.h"
34#include "iomarket.h"
35#include "waitlist.h"
36#include "ban.h"
37#include "scheduler.h"
38#include "databasetasks.h"
39#include "modules.h"
40
41extern Game g_game;
42extern ConfigManager g_config;
43extern Actions actions;
44extern CreatureEvents* g_creatureEvents;
45extern Chat* g_chat;
46extern Modules* g_modules;
47
48ProtocolGame::LiveCastsMap ProtocolGame::liveCasts;
49
50void ProtocolGame::release()
51{
52 //dispatcher thread
53 stopLiveCast();
54 if (player && player->client == shared_from_this()) {
55 if (player->getTile() && (player->getTile()->hasFlag(TILESTATE_PROTECTIONZONE) || !player->hasCondition(CONDITION_INFIGHT))) {
56 logout(true, true);
57 }
58
59 player->client.reset();
60 player->decrementReferenceCounter();
61 player = nullptr;
62 }
63
64 OutputMessagePool::getInstance().removeProtocolFromAutosend(shared_from_this());
65 Protocol::release();
66}
67
68void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingSystem_t operatingSystem)
69{
70 //dispatcher thread
71 Player* foundPlayer = g_game.getPlayerByName(name);
72 if (!foundPlayer || g_config.getBoolean(ConfigManager::ALLOW_CLONES)) {
73 player = new Player(getThis());
74 player->setName(name);
75
76 player->incrementReferenceCounter();
77 player->setID();
78
79 if (!IOLoginData::preloadPlayer(player, name)) {
80 disconnectClient("Your character could not be loaded.");
81 return;
82 }
83
84 if (IOBan::isPlayerNamelocked(player->getGUID())) {
85 disconnectClient("Your character has been namelocked.");
86 return;
87 }
88
89 if (g_game.getGameState() == GAME_STATE_CLOSING && !player->hasFlag(PlayerFlag_CanAlwaysLogin)) {
90 disconnectClient("The game is just going down.\nPlease try again later.");
91 return;
92 }
93
94 if (g_game.getGameState() == GAME_STATE_CLOSED && !player->hasFlag(PlayerFlag_CanAlwaysLogin)) {
95 disconnectClient("Server is currently closed.\nPlease try again later.");
96 return;
97 }
98
99 if (g_config.getBoolean(ConfigManager::ONE_PLAYER_ON_ACCOUNT) && player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER && g_game.getPlayerByAccount(player->getAccount())) {
100 disconnectClient("You may only login with one character\nof your account at the same time.");
101 return;
102 }
103
104 if (!player->hasFlag(PlayerFlag_CannotBeBanned)) {
105 BanInfo banInfo;
106 if (IOBan::isAccountBanned(accountId, banInfo)) {
107 if (banInfo.reason.empty()) {
108 banInfo.reason = "(none)";
109 }
110
111 std::ostringstream ss;
112 if (banInfo.expiresAt > 0) {
113 ss << "Your account has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason;
114 }
115 else {
116 ss << "Your account has been permanently banned by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason;
117 }
118 disconnectClient(ss.str());
119 return;
120 }
121 }
122
123 WaitingList& waitingList = WaitingList::getInstance();
124 if (!waitingList.clientLogin(player)) {
125 uint32_t currentSlot = waitingList.getClientSlot(player);
126 uint32_t retryTime = WaitingList::getTime(currentSlot);
127 std::ostringstream ss;
128
129 ss << "Too many players online.\nYou are at place "
130 << currentSlot << " on the waiting list.";
131
132 auto output = OutputMessagePool::getOutputMessage();
133 output->addByte(0x16);
134 output->addString(ss.str());
135 output->addByte(retryTime);
136 send(output);
137 disconnect();
138 return;
139 }
140
141 if (!IOLoginData::loadPlayerById(player, player->getGUID())) {
142 disconnectClient("Your character could not be loaded.");
143 return;
144 }
145
146 // Prey System
147 IOLoginData::loadPlayerPreyById(player, player->getGUID());
148
149 player->setOperatingSystem(operatingSystem);
150
151 if (!g_game.placeCreature(player, player->getLoginPosition())) {
152 if (!g_game.placeCreature(player, player->getTemplePosition(), false, true)) {
153 disconnectClient("Temple position is wrong. Contact the administrator.");
154 return;
155 }
156 }
157
158 if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) {
159 player->registerCreatureEvent("ExtendedOpcode");
160 }
161
162 player->lastIP = player->getIP();
163 player->lastLoginSaved = std::max<time_t>(time(nullptr), player->lastLoginSaved + 1);
164 acceptPackets = true;
165 }
166 else {
167 if (eventConnect != 0 || !g_config.getBoolean(ConfigManager::REPLACE_KICK_ON_LOGIN)) {
168 //Already trying to connect
169 disconnectClient("You are already logged in.");
170 return;
171 }
172
173 if (foundPlayer->client) {
174 foundPlayer->disconnect();
175 foundPlayer->isConnecting = true;
176
177 eventConnect = g_scheduler.addEvent(createSchedulerTask(1000, std::bind(&ProtocolGame::connect, getThis(), foundPlayer->getID(), operatingSystem)));
178 }
179 else {
180 connect(foundPlayer->getID(), operatingSystem);
181 }
182 }
183 OutputMessagePool::getInstance().addProtocolToAutosend(shared_from_this());
184}
185
186void ProtocolGame::connect(uint32_t playerId, OperatingSystem_t operatingSystem)
187{
188 eventConnect = 0;
189
190 Player* foundPlayer = g_game.getPlayerByID(playerId);
191 if (!foundPlayer || foundPlayer->client) {
192 disconnectClient("You are already logged in.");
193 return;
194 }
195
196 if (isConnectionExpired()) {
197 //ProtocolGame::release() has been called at this point and the Connection object
198 //no longer exists, so we return to prevent leakage of the Player.
199 return;
200 }
201
202 player = foundPlayer;
203 player->incrementReferenceCounter();
204
205 g_chat->removeUserFromAllChannels(*player);
206 player->clearModalWindows();
207 player->setOperatingSystem(operatingSystem);
208 player->isConnecting = false;
209
210 player->client = getThis();
211 sendAddCreature(player, player->getPosition(), 0, false);
212 player->lastIP = player->getIP();
213 player->lastLoginSaved = std::max<time_t>(time(nullptr), player->lastLoginSaved + 1);
214 acceptPackets = true;
215}
216
217void ProtocolGame::logout(bool displayEffect, bool forced)
218{
219 //dispatcher thread
220 if (!player) {
221 return;
222 }
223
224 if (!player->isRemoved()) {
225 if (!forced) {
226 if (!player->isAccessPlayer()) {
227 if (player->getTile()->hasFlag(TILESTATE_NOLOGOUT)) {
228 player->sendCancelMessage(RETURNVALUE_YOUCANNOTLOGOUTHERE);
229 return;
230 }
231
232 if (!player->getTile()->hasFlag(TILESTATE_PROTECTIONZONE) && player->hasCondition(CONDITION_INFIGHT)) {
233 player->sendCancelMessage(RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT);
234 return;
235 }
236 }
237
238 //scripting event - onLogout
239 if (!g_creatureEvents->playerLogout(player)) {
240 //Let the script handle the error message
241 return;
242 }
243 }
244
245 if (displayEffect && player->getHealth() > 0) {
246 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
247 }
248 }
249
250 stopLiveCast();
251 disconnect();
252
253 g_game.removeCreature(player);
254}
255
256bool ProtocolGame::startLiveCast(const std::string& password /*= ""*/)
257{
258 auto connection = getConnection();
259 if (!g_config.getBoolean(ConfigManager::ENABLE_LIVE_CASTING) || isLiveCaster() || !player || player->isRemoved() || !connection || liveCasts.size() >= getMaxLiveCastCount()) {
260 return false;
261 }
262
263 {
264 std::lock_guard<decltype(liveCastLock)> lock{ liveCastLock };
265 //DO NOT do any send operations here
266 liveCastName = player->getName();
267 liveCastPassword = password;
268 isCaster.store(true, std::memory_order_relaxed);
269 }
270
271 liveCasts.insert(std::make_pair(player, getThis()));
272
273 registerLiveCast();
274 //Send a "dummy" channel
275 sendChannel(CHANNEL_CAST, LIVE_CAST_CHAT_NAME, nullptr, nullptr);
276 return true;
277}
278
279bool ProtocolGame::stopLiveCast()
280{
281 //dispatcher
282 if (!isLiveCaster()) {
283 return false;
284 }
285
286 CastSpectatorVec spectators;
287
288 {
289 std::lock_guard<decltype(liveCastLock)> lock{ liveCastLock };
290 //DO NOT do any send operations here
291 std::swap(this->spectators, spectators);
292 isCaster.store(false, std::memory_order_relaxed);
293 }
294
295 liveCasts.erase(player);
296 for (auto& spectator : spectators) {
297 spectator->onLiveCastStop();
298 }
299 unregisterLiveCast();
300
301 return true;
302}
303
304void ProtocolGame::clearLiveCastInfo()
305{
306 static std::once_flag flag;
307 std::call_once(flag, []() {
308 assert(g_game.getGameState() == GAME_STATE_INIT);
309 std::ostringstream query;
310 query << "TRUNCATE TABLE `live_casts`;";
311 g_databaseTasks.addTask(query.str());
312 });
313}
314
315void ProtocolGame::registerLiveCast()
316{
317 std::ostringstream query;
318 query << "INSERT into `live_casts` (`player_id`, `cast_name`, `password`) VALUES (" << player->getGUID() << ", '"
319 << getLiveCastName() << "', " << isPasswordProtected() << ");";
320 g_databaseTasks.addTask(query.str());
321}
322
323void ProtocolGame::unregisterLiveCast()
324{
325 std::ostringstream query;
326 query << "DELETE FROM `live_casts` WHERE `player_id`=" << player->getGUID() << ";";
327 g_databaseTasks.addTask(query.str());
328}
329
330void ProtocolGame::updateLiveCastInfo()
331{
332 std::ostringstream query;
333 query << "UPDATE `live_casts` SET `cast_name`='" << getLiveCastName() << "', `password`="
334 << isPasswordProtected() << ", `spectators`=" << getSpectatorCount()
335 << " WHERE `player_id`=" << player->getGUID() << ";";
336 g_databaseTasks.addTask(query.str());
337}
338
339void ProtocolGame::addSpectator(ProtocolSpectator_ptr spectatorClient)
340{
341 std::lock_guard<decltype(liveCastLock)> lock(liveCastLock);
342 //DO NOT do any send operations here
343 spectators.emplace_back(spectatorClient);
344 updateLiveCastInfo();
345}
346
347void ProtocolGame::removeSpectator(ProtocolSpectator_ptr spectatorClient)
348{
349 std::lock_guard<decltype(liveCastLock)> lock(liveCastLock);
350 //DO NOT do any send operations here
351 auto it = std::find(spectators.begin(), spectators.end(), spectatorClient);
352 if (it != spectators.end()) {
353 spectators.erase(it);
354 updateLiveCastInfo();
355 }
356}
357
358void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg)
359{
360 if (g_game.getGameState() == GAME_STATE_SHUTDOWN) {
361 disconnect();
362 return;
363 }
364
365 OperatingSystem_t operatingSystem = static_cast<OperatingSystem_t>(msg.get<uint16_t>());
366 version = msg.get<uint16_t>();
367 if (version >= 1111) {
368 enableCompact();
369 }
370
371 msg.skipBytes(7); // U32 client version, U8 client type, U16 dat revision
372
373 if (!Protocol::RSA_decrypt(msg)) {
374 disconnect();
375 return;
376 }
377
378 uint32_t key[4];
379 key[0] = msg.get<uint32_t>();
380 key[1] = msg.get<uint32_t>();
381 key[2] = msg.get<uint32_t>();
382 key[3] = msg.get<uint32_t>();
383 enableXTEAEncryption();
384 setXTEAKey(key);
385
386 if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) {
387 NetworkMessage opcodeMessage;
388 opcodeMessage.addByte(0x32);
389 opcodeMessage.addByte(0x00);
390 opcodeMessage.add<uint16_t>(0x00);
391 writeToOutputBuffer(opcodeMessage);
392 }
393
394 msg.skipBytes(1); // gamemaster flag
395
396 std::string sessionKey = msg.getString();
397 size_t pos = sessionKey.find('\n');
398 if (pos == std::string::npos) {
399 disconnectClient("You must enter your account name.");
400 return;
401 }
402
403 std::string accountName = sessionKey.substr(0, pos);
404 if (accountName.empty()) {
405 disconnectClient("You must enter your account name.");
406 return;
407 }
408
409 std::string password = sessionKey.substr(pos + 1);
410
411 std::string characterName = msg.getString();
412
413 uint32_t timeStamp = msg.get<uint32_t>();
414 uint8_t randNumber = msg.getByte();
415 if (challengeTimestamp != timeStamp || challengeRandom != randNumber) {
416 disconnect();
417 return;
418 }
419
420 if (version < g_config.getNumber(ConfigManager::VERSION_MIN) || version > g_config.getNumber(ConfigManager::VERSION_MAX)) {
421 std::ostringstream ss;
422 ss << "Only clients with protocol " << g_config.getString(ConfigManager::VERSION_STR) << " allowed!";
423 disconnectClient(ss.str());
424 return;
425 }
426
427 if (g_game.getGameState() == GAME_STATE_STARTUP) {
428 disconnectClient("Gameworld is starting up. Please wait.");
429 return;
430 }
431
432 if (g_game.getGameState() == GAME_STATE_MAINTAIN) {
433 disconnectClient("Gameworld is under maintenance. Please re-connect in a while.");
434 return;
435 }
436
437 BanInfo banInfo;
438 if (IOBan::isIpBanned(getIP(), banInfo)) {
439 if (banInfo.reason.empty()) {
440 banInfo.reason = "(none)";
441 }
442
443 std::ostringstream ss;
444 ss << "Your IP has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason;
445 disconnectClient(ss.str());
446 return;
447 }
448
449 uint32_t accountId = IOLoginData::gameworldAuthentication(accountName, password, characterName);
450 if (accountId == 0) {
451 disconnectClient("Account name or password is not correct.");
452 return;
453 }
454
455 g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::login, getThis(), characterName, accountId, operatingSystem)));
456}
457
458void ProtocolGame::disconnectClient(const std::string& message) const
459{
460 auto output = OutputMessagePool::getOutputMessage();
461 output->addByte(0x14);
462 output->addString(message);
463 send(output);
464 disconnect();
465}
466
467void ProtocolGame::writeToOutputBuffer(const NetworkMessage& msg, bool broadcast /*= true*/)
468{
469 if (!broadcast && isLiveCaster()) {
470 //We're casting and we need to send a packet that's not supposed to be broadcast so we need a new messasge.
471 //This shouldn't impact performance by a huge amount as most packets can be broadcast.
472 auto out = OutputMessagePool::getOutputMessage();
473 out->append(msg);
474 send(std::move(out));
475 }
476 else {
477 auto out = getOutputBuffer(msg.getLength());
478 if (isLiveCaster()) {
479 out->setBroadcastMsg(true);
480 }
481 out->append(msg);
482 }
483}
484
485void ProtocolGame::parsePacket(NetworkMessage& msg)
486{
487 if (!acceptPackets || g_game.getGameState() == GAME_STATE_SHUTDOWN || msg.getLength() <= 0) {
488 return;
489 }
490
491 uint8_t recvbyte = msg.getByte();
492
493 //a dead player can not perform actions
494 if (!player || player->isRemoved() || player->getHealth() <= 0) {
495 auto this_ptr = getThis();
496 g_dispatcher.addTask(createTask([this_ptr]() {
497 this_ptr->stopLiveCast();
498 }));
499 if (recvbyte == 0x0F) {
500 // we need to make the player pointer != null in this part, game.cpp release is the first step
501 // login(player->getName(), player->getAccount(), player->operatingSystem);
502 disconnect();
503 return;
504 }
505
506 if (recvbyte != 0x14) {
507 return;
508 }
509 }
510
511 g_dispatcher.addTask(createTask(std::bind(&Modules::executeOnRecvbyte, g_modules, player, msg, recvbyte)));
512
513 switch (recvbyte) {
514 case 0x14: g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::logout, getThis(), true, false))); break;
515 case 0x1D: addGameTask(&Game::playerReceivePingBack, player->getID()); break;
516 case 0x1E: addGameTask(&Game::playerReceivePing, player->getID()); break;
517 case 0x32: parseExtendedOpcode(msg); break; //otclient extended opcode
518 case 0x64: parseAutoWalk(msg); break;
519 case 0x65: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTH); break;
520 case 0x66: addGameTask(&Game::playerMove, player->getID(), DIRECTION_EAST); break;
521 case 0x67: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTH); break;
522 case 0x68: addGameTask(&Game::playerMove, player->getID(), DIRECTION_WEST); break;
523 case 0x69: addGameTask(&Game::playerStopAutoWalk, player->getID()); break;
524 case 0x6A: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTHEAST); break;
525 case 0x6B: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTHEAST); break;
526 case 0x6C: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTHWEST); break;
527 case 0x6D: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTHWEST); break;
528 case 0x6F: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_NORTH); break;
529 case 0x70: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_EAST); break;
530 case 0x71: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_SOUTH); break;
531 case 0x72: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_WEST); break;
532 case 0x78: parseThrow(msg); break;
533 case 0x79: parseLookInShop(msg); break;
534 case 0x7A: parsePlayerPurchase(msg); break;
535 case 0x7B: parsePlayerSale(msg); break;
536 case 0x7C: addGameTask(&Game::playerCloseShop, player->getID()); break;
537 case 0x7D: parseRequestTrade(msg); break;
538 case 0x7E: parseLookInTrade(msg); break;
539 case 0x7F: addGameTask(&Game::playerAcceptTrade, player->getID()); break;
540 case 0x80: addGameTask(&Game::playerCloseTrade, player->getID()); break;
541 case 0x82: parseUseItem(msg); break;
542 case 0x83: parseUseItemEx(msg); break;
543 case 0x84: parseUseWithCreature(msg); break;
544 case 0x85: parseRotateItem(msg); break;
545 case 0x87: parseCloseContainer(msg); break;
546 case 0x88: parseUpArrowContainer(msg); break;
547 case 0x89: parseTextWindow(msg); break;
548 case 0x8A: parseHouseWindow(msg); break;
549 case 0x8B: parseWrapableItem(msg); break;
550 case 0x8C: parseLookAt(msg); break;
551 case 0x8D: parseLookInBattleList(msg); break;
552 case 0x8E: /* join aggression */ break;
553 case 0x96: parseSay(msg); break;
554 case 0x97: addGameTask(&Game::playerRequestChannels, player->getID()); break;
555 case 0x98: parseOpenChannel(msg); break;
556 case 0x99: parseCloseChannel(msg); break;
557 case 0x9A: parseOpenPrivateChannel(msg); break;
558 case 0x9E: addGameTask(&Game::playerCloseNpcChannel, player->getID()); break;
559 case 0xA0: parseFightModes(msg); break;
560 case 0xA1: parseAttack(msg); break;
561 case 0xA2: parseFollow(msg); break;
562 case 0xA3: parseInviteToParty(msg); break;
563 case 0xA4: parseJoinParty(msg); break;
564 case 0xA5: parseRevokePartyInvite(msg); break;
565 case 0xA6: parsePassPartyLeadership(msg); break;
566 case 0xA7: addGameTask(&Game::playerLeaveParty, player->getID()); break;
567 case 0xA8: parseEnableSharedPartyExperience(msg); break;
568 case 0xAA: addGameTask(&Game::playerCreatePrivateChannel, player->getID()); break;
569 case 0xAB: parseChannelInvite(msg); break;
570 case 0xAC: parseChannelExclude(msg); break;
571 case 0xBE: addGameTask(&Game::playerCancelAttackAndFollow, player->getID()); break;
572 case 0xC9: /* update tile */ break;
573 case 0xCA: parseUpdateContainer(msg); break;
574 case 0xCB: parseBrowseField(msg); break;
575 case 0xCC: parseSeekInContainer(msg); break;
576 case 0xD2: addGameTask(&Game::playerRequestOutfit, player->getID()); break;
577 case 0xD3: parseSetOutfit(msg); break;
578 case 0xD4: parseToggleMount(msg); break;
579 case 0xDC: parseAddVip(msg); break;
580 case 0xDD: parseRemoveVip(msg); break;
581 case 0xDE: parseEditVip(msg); break;
582 case 0xE6: parseBugReport(msg); break;
583 case 0xE7: /* thank you */ break;
584 case 0xE8: parseDebugAssert(msg); break;
585 case 0xEF: if (!g_config.getBoolean(ConfigManager::STOREMODULES)) { parseCoinTransfer(msg); } break; /* premium coins transfer */
586 case 0xF0: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerShowQuestLog, player->getID()); break;
587 case 0xF1: parseQuestLine(msg); break;
588 case 0xF2: parseRuleViolationReport(msg); break;
589 case 0xF3: /* get object info */ break;
590 case 0xF4: parseMarketLeave(); break;
591 case 0xF5: parseMarketBrowse(msg); break;
592 case 0xF6: parseMarketCreateOffer(msg); break;
593 case 0xF7: parseMarketCancelOffer(msg); break;
594 case 0xF8: parseMarketAcceptOffer(msg); break;
595 case 0xF9: parseModalWindowAnswer(msg); break;
596 case 0xFA: if (!g_config.getBoolean(ConfigManager::STOREMODULES)) { parseStoreOpen(msg); } break;
597 case 0xFB: if (!g_config.getBoolean(ConfigManager::STOREMODULES)) { parseStoreRequestOffers(msg); } break;
598 case 0xFC: if (!g_config.getBoolean(ConfigManager::STOREMODULES)) { parseStoreBuyOffer(msg); } break;
599 // case 0xFD: parseStoreOpenTransactionHistory(msg); break;
600 // case 0xFE: parseStoreRequestTransactionHistory(msg); break;
601
602 //case 0x77 Equip Hotkey.
603 //case 0xDF, 0xE0, 0xE1, 0xFB, 0xFC, 0xFD, 0xFE Premium Shop.
604
605 default:
606 // std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << static_cast<uint16_t>(recvbyte) << std::dec << "!" << std::endl;
607 break;
608 }
609
610 if (msg.isOverrun()) {
611 disconnect();
612 }
613}
614
615// Parse methods
616void ProtocolGame::parseChannelInvite(NetworkMessage& msg)
617{
618 const std::string name = msg.getString();
619 addGameTask(&Game::playerChannelInvite, player->getID(), name);
620}
621
622void ProtocolGame::parseChannelExclude(NetworkMessage& msg)
623{
624 const std::string name = msg.getString();
625 addGameTask(&Game::playerChannelExclude, player->getID(), name);
626}
627
628void ProtocolGame::parseOpenChannel(NetworkMessage& msg)
629{
630 uint16_t channelId = msg.get<uint16_t>();
631 addGameTask(&Game::playerOpenChannel, player->getID(), channelId);
632}
633
634void ProtocolGame::parseCloseChannel(NetworkMessage& msg)
635{
636 uint16_t channelId = msg.get<uint16_t>();
637 addGameTask(&Game::playerCloseChannel, player->getID(), channelId);
638}
639
640void ProtocolGame::parseOpenPrivateChannel(NetworkMessage& msg)
641{
642 const std::string receiver = msg.getString();
643 addGameTask(&Game::playerOpenPrivateChannel, player->getID(), receiver);
644}
645
646void ProtocolGame::parseAutoWalk(NetworkMessage& msg)
647{
648 uint8_t numdirs = msg.getByte();
649 if (numdirs == 0 || (msg.getBufferPosition() + numdirs) != (msg.getLength() + 8)) {
650 return;
651 }
652
653 msg.skipBytes(numdirs);
654
655 std::forward_list<Direction> path;
656 for (uint8_t i = 0; i < numdirs; ++i) {
657 uint8_t rawdir = msg.getPreviousByte();
658 switch (rawdir) {
659 case 1: path.push_front(DIRECTION_EAST); break;
660 case 2: path.push_front(DIRECTION_NORTHEAST); break;
661 case 3: path.push_front(DIRECTION_NORTH); break;
662 case 4: path.push_front(DIRECTION_NORTHWEST); break;
663 case 5: path.push_front(DIRECTION_WEST); break;
664 case 6: path.push_front(DIRECTION_SOUTHWEST); break;
665 case 7: path.push_front(DIRECTION_SOUTH); break;
666 case 8: path.push_front(DIRECTION_SOUTHEAST); break;
667 default: break;
668 }
669 }
670
671 if (path.empty()) {
672 return;
673 }
674
675 addGameTask(&Game::playerAutoWalk, player->getID(), path);
676}
677
678void ProtocolGame::parseSetOutfit(NetworkMessage& msg)
679{
680 Outfit_t newOutfit;
681 newOutfit.lookType = msg.get<uint16_t>();
682 newOutfit.lookHead = msg.getByte();
683 newOutfit.lookBody = msg.getByte();
684 newOutfit.lookLegs = msg.getByte();
685 newOutfit.lookFeet = msg.getByte();
686 newOutfit.lookAddons = msg.getByte();
687 newOutfit.lookMount = msg.get<uint16_t>();
688 addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit);
689}
690
691void ProtocolGame::parseToggleMount(NetworkMessage& msg)
692{
693 bool mount = msg.getByte() != 0;
694 addGameTask(&Game::playerToggleMount, player->getID(), mount);
695}
696
697void ProtocolGame::parseUseItem(NetworkMessage& msg)
698{
699 Position pos = msg.getPosition();
700 uint16_t spriteId = msg.get<uint16_t>();
701 uint8_t stackpos = msg.getByte();
702 uint8_t index = msg.getByte();
703 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseItem, player->getID(), pos, stackpos, index, spriteId);
704}
705
706void ProtocolGame::parseUseItemEx(NetworkMessage& msg)
707{
708 Position fromPos = msg.getPosition();
709 uint16_t fromSpriteId = msg.get<uint16_t>();
710 uint8_t fromStackPos = msg.getByte();
711 Position toPos = msg.getPosition();
712 uint16_t toSpriteId = msg.get<uint16_t>();
713 uint8_t toStackPos = msg.getByte();
714 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseItemEx, player->getID(), fromPos, fromStackPos, fromSpriteId, toPos, toStackPos, toSpriteId);
715}
716
717void ProtocolGame::parseUseWithCreature(NetworkMessage& msg)
718{
719 Position fromPos = msg.getPosition();
720 uint16_t spriteId = msg.get<uint16_t>();
721 uint8_t fromStackPos = msg.getByte();
722 uint32_t creatureId = msg.get<uint32_t>();
723 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseWithCreature, player->getID(), fromPos, fromStackPos, creatureId, spriteId);
724}
725
726void ProtocolGame::parseCloseContainer(NetworkMessage& msg)
727{
728 uint8_t cid = msg.getByte();
729 addGameTask(&Game::playerCloseContainer, player->getID(), cid);
730}
731
732void ProtocolGame::parseUpArrowContainer(NetworkMessage& msg)
733{
734 uint8_t cid = msg.getByte();
735 addGameTask(&Game::playerMoveUpContainer, player->getID(), cid);
736}
737
738void ProtocolGame::parseUpdateContainer(NetworkMessage& msg)
739{
740 uint8_t cid = msg.getByte();
741 addGameTask(&Game::playerUpdateContainer, player->getID(), cid);
742}
743
744void ProtocolGame::parseThrow(NetworkMessage& msg)
745{
746 Position fromPos = msg.getPosition();
747 uint16_t spriteId = msg.get<uint16_t>();
748 uint8_t fromStackpos = msg.getByte();
749 Position toPos = msg.getPosition();
750 uint8_t count = msg.getByte();
751
752 if (toPos != fromPos) {
753 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerMoveThing, player->getID(), fromPos, spriteId, fromStackpos, toPos, count);
754 }
755}
756
757void ProtocolGame::parseLookAt(NetworkMessage& msg)
758{
759 Position pos = msg.getPosition();
760 msg.skipBytes(2); // spriteId
761 uint8_t stackpos = msg.getByte();
762 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookAt, player->getID(), pos, stackpos);
763}
764
765void ProtocolGame::parseLookInBattleList(NetworkMessage& msg)
766{
767 uint32_t creatureId = msg.get<uint32_t>();
768 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInBattleList, player->getID(), creatureId);
769}
770
771void ProtocolGame::parseSay(NetworkMessage& msg)
772{
773 std::string receiver;
774 uint16_t channelId;
775
776 SpeakClasses type = static_cast<SpeakClasses>(msg.getByte());
777 switch (type) {
778 case TALKTYPE_PRIVATE_TO:
779 case TALKTYPE_PRIVATE_RED_TO:
780 receiver = msg.getString();
781 channelId = 0;
782 break;
783
784 case TALKTYPE_CHANNEL_Y:
785 case TALKTYPE_CHANNEL_R1:
786 channelId = msg.get<uint16_t>();
787 break;
788
789 default:
790 channelId = 0;
791 break;
792 }
793
794 const std::string text = msg.getString();
795 if (text.length() > 255) {
796 return;
797 }
798
799 addGameTask(&Game::playerSay, player->getID(), channelId, type, receiver, text);
800}
801
802void ProtocolGame::parseFightModes(NetworkMessage& msg)
803{
804 uint8_t rawFightMode = msg.getByte(); // 1 - offensive, 2 - balanced, 3 - defensive
805 uint8_t rawChaseMode = msg.getByte(); // 0 - stand while fightning, 1 - chase opponent
806 uint8_t rawSecureMode = msg.getByte(); // 0 - can't attack unmarked, 1 - can attack unmarked
807 // uint8_t rawPvpMode = msg.getByte(); // pvp mode introduced in 10.0
808
809 fightMode_t fightMode;
810 if (rawFightMode == 1) {
811 fightMode = FIGHTMODE_ATTACK;
812 }
813 else if (rawFightMode == 2) {
814 fightMode = FIGHTMODE_BALANCED;
815 }
816 else {
817 fightMode = FIGHTMODE_DEFENSE;
818 }
819
820 addGameTask(&Game::playerSetFightModes, player->getID(), fightMode, rawChaseMode != 0, rawSecureMode != 0);
821}
822
823void ProtocolGame::parseAttack(NetworkMessage& msg)
824{
825 uint32_t creatureId = msg.get<uint32_t>();
826 // msg.get<uint32_t>(); creatureId (same as above)
827 addGameTask(&Game::playerSetAttackedCreature, player->getID(), creatureId);
828}
829
830void ProtocolGame::parseFollow(NetworkMessage& msg)
831{
832 uint32_t creatureId = msg.get<uint32_t>();
833 // msg.get<uint32_t>(); creatureId (same as above)
834 addGameTask(&Game::playerFollowCreature, player->getID(), creatureId);
835}
836
837void ProtocolGame::parseTextWindow(NetworkMessage& msg)
838{
839 uint32_t windowTextId = msg.get<uint32_t>();
840 const std::string newText = msg.getString();
841 addGameTask(&Game::playerWriteItem, player->getID(), windowTextId, newText);
842}
843
844void ProtocolGame::parseHouseWindow(NetworkMessage& msg)
845{
846 uint8_t doorId = msg.getByte();
847 uint32_t id = msg.get<uint32_t>();
848 const std::string text = msg.getString();
849 addGameTask(&Game::playerUpdateHouseWindow, player->getID(), doorId, id, text);
850}
851
852void ProtocolGame::parseLookInShop(NetworkMessage& msg)
853{
854 uint16_t id = msg.get<uint16_t>();
855 uint8_t count = msg.getByte();
856 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInShop, player->getID(), id, count);
857}
858
859void ProtocolGame::parsePlayerPurchase(NetworkMessage& msg)
860{
861 uint16_t id = msg.get<uint16_t>();
862 uint8_t count = msg.getByte();
863 uint8_t amount = msg.getByte();
864 bool ignoreCap = msg.getByte() != 0;
865 bool inBackpacks = msg.getByte() != 0;
866 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerPurchaseItem, player->getID(), id, count, amount, ignoreCap, inBackpacks);
867}
868
869void ProtocolGame::parsePlayerSale(NetworkMessage& msg)
870{
871 uint16_t id = msg.get<uint16_t>();
872 uint8_t count = msg.getByte();
873 uint8_t amount = msg.getByte();
874 bool ignoreEquipped = msg.getByte() != 0;
875 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerSellItem, player->getID(), id, count, amount, ignoreEquipped);
876}
877
878void ProtocolGame::parseRequestTrade(NetworkMessage& msg)
879{
880 Position pos = msg.getPosition();
881 uint16_t spriteId = msg.get<uint16_t>();
882 uint8_t stackpos = msg.getByte();
883 uint32_t playerId = msg.get<uint32_t>();
884 addGameTask(&Game::playerRequestTrade, player->getID(), pos, stackpos, playerId, spriteId);
885}
886
887void ProtocolGame::parseLookInTrade(NetworkMessage& msg)
888{
889 bool counterOffer = (msg.getByte() == 0x01);
890 uint8_t index = msg.getByte();
891 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInTrade, player->getID(), counterOffer, index);
892}
893
894void ProtocolGame::parseAddVip(NetworkMessage& msg)
895{
896 const std::string name = msg.getString();
897 addGameTask(&Game::playerRequestAddVip, player->getID(), name);
898}
899
900void ProtocolGame::parseRemoveVip(NetworkMessage& msg)
901{
902 uint32_t guid = msg.get<uint32_t>();
903 addGameTask(&Game::playerRequestRemoveVip, player->getID(), guid);
904}
905
906void ProtocolGame::parseEditVip(NetworkMessage& msg)
907{
908 uint32_t guid = msg.get<uint32_t>();
909 const std::string description = msg.getString();
910 uint32_t icon = std::min<uint32_t>(10, msg.get<uint32_t>()); // 10 is max icon in 9.63
911 bool notify = msg.getByte() != 0;
912 addGameTask(&Game::playerRequestEditVip, player->getID(), guid, description, icon, notify);
913}
914
915void ProtocolGame::parseRotateItem(NetworkMessage& msg)
916{
917 Position pos = msg.getPosition();
918 uint16_t spriteId = msg.get<uint16_t>();
919 uint8_t stackpos = msg.getByte();
920 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerRotateItem, player->getID(), pos, stackpos, spriteId);
921}
922
923void ProtocolGame::parseWrapableItem(NetworkMessage& msg)
924{
925 Position pos = msg.getPosition();
926 uint16_t spriteId = msg.get<uint16_t>();
927 uint8_t stackpos = msg.getByte();
928 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerWrapableItem, player->getID(), pos, stackpos, spriteId);
929}
930
931void ProtocolGame::parseRuleViolationReport(NetworkMessage &msg)
932{
933 uint8_t reportType = msg.getByte();
934 uint8_t reportReason = msg.getByte();
935 const std::string& targetName = msg.getString();
936 const std::string& comment = msg.getString();
937 std::string translation;
938 if (reportType == REPORT_TYPE_NAME) {
939 translation = msg.getString();
940 }
941 else if (reportType == REPORT_TYPE_STATEMENT) {
942 translation = msg.getString();
943 msg.get<uint32_t>(); // statement id, used to get whatever player have said, we don't log that.
944 }
945
946 addGameTask(&Game::playerReportRuleViolationReport, player->getID(), targetName, reportType, reportReason, comment, translation);
947}
948
949void ProtocolGame::parseBugReport(NetworkMessage& msg)
950{
951 uint8_t category = msg.getByte();
952 std::string message = msg.getString();
953
954 Position position;
955 if (category == BUG_CATEGORY_MAP) {
956 position = msg.getPosition();
957 }
958
959 addGameTask(&Game::playerReportBug, player->getID(), message, position, category);
960}
961
962void ProtocolGame::parseDebugAssert(NetworkMessage& msg)
963{
964 if (debugAssertSent) {
965 return;
966 }
967
968 debugAssertSent = true;
969
970 std::string assertLine = msg.getString();
971 std::string date = msg.getString();
972 std::string description = msg.getString();
973 std::string comment = msg.getString();
974 addGameTask(&Game::playerDebugAssert, player->getID(), assertLine, date, description, comment);
975}
976
977void ProtocolGame::parseInviteToParty(NetworkMessage& msg)
978{
979 uint32_t targetId = msg.get<uint32_t>();
980 addGameTask(&Game::playerInviteToParty, player->getID(), targetId);
981}
982
983void ProtocolGame::parseJoinParty(NetworkMessage& msg)
984{
985 uint32_t targetId = msg.get<uint32_t>();
986 addGameTask(&Game::playerJoinParty, player->getID(), targetId);
987}
988
989void ProtocolGame::parseRevokePartyInvite(NetworkMessage& msg)
990{
991 uint32_t targetId = msg.get<uint32_t>();
992 addGameTask(&Game::playerRevokePartyInvitation, player->getID(), targetId);
993}
994
995void ProtocolGame::parsePassPartyLeadership(NetworkMessage& msg)
996{
997 uint32_t targetId = msg.get<uint32_t>();
998 addGameTask(&Game::playerPassPartyLeadership, player->getID(), targetId);
999}
1000
1001void ProtocolGame::parseEnableSharedPartyExperience(NetworkMessage& msg)
1002{
1003 bool sharedExpActive = msg.getByte() == 1;
1004 addGameTask(&Game::playerEnableSharedPartyExperience, player->getID(), sharedExpActive);
1005}
1006
1007void ProtocolGame::parseQuestLine(NetworkMessage& msg)
1008{
1009 uint16_t questId = msg.get<uint16_t>();
1010 addGameTask(&Game::playerShowQuestLine, player->getID(), questId);
1011}
1012
1013void ProtocolGame::parseMarketLeave()
1014{
1015 addGameTask(&Game::playerLeaveMarket, player->getID());
1016}
1017
1018void ProtocolGame::parseMarketBrowse(NetworkMessage& msg)
1019{
1020 uint16_t browseId = msg.get<uint16_t>();
1021
1022 if (browseId == MARKETREQUEST_OWN_OFFERS) {
1023 addGameTask(&Game::playerBrowseMarketOwnOffers, player->getID());
1024 }
1025 else if (browseId == MARKETREQUEST_OWN_HISTORY) {
1026 addGameTask(&Game::playerBrowseMarketOwnHistory, player->getID());
1027 }
1028 else {
1029 addGameTask(&Game::playerBrowseMarket, player->getID(), browseId);
1030 }
1031}
1032
1033void ProtocolGame::parseStoreOpen(NetworkMessage &msg) {
1034 uint8_t serviceType = msg.getByte();
1035 addGameTaskTimed(600, &Game::playerStoreOpen, player->getID(), serviceType);
1036}
1037
1038void ProtocolGame::parseStoreRequestOffers(NetworkMessage &message) {
1039 //StoreService_t serviceType = SERVICE_STANDARD;
1040 message.getByte(); // discard service type byte // version >= 1092
1041
1042 std::string categoryName = message.getString();
1043 const int16_t index = g_game.gameStore.getCategoryIndexByName(categoryName);
1044
1045 if (index >= 0) {
1046 addGameTaskTimed(350, &Game::playerShowStoreCategoryOffers, player->getID(),
1047 g_game.gameStore.getCategoryOffers().at(index));
1048 }
1049 else {
1050 std::cout << "[Warning - ProtocolGame::parseStoreRequestOffers] requested category: \"" << categoryName << "\" doesn't exists" << std::endl;
1051 }
1052}
1053
1054void ProtocolGame::parseStoreBuyOffer(NetworkMessage &message) {
1055 uint32_t offerId = message.get<uint32_t>();
1056 uint8_t productType = message.getByte(); //used only in return of a namechange offer request
1057 std::string additionalInfo;
1058 if (productType == ADDITIONALINFO) {
1059 additionalInfo = message.getString();
1060 }
1061 addGameTaskTimed(350, &Game::playerBuyStoreOffer, player->getID(), offerId, productType, additionalInfo);
1062}
1063
1064void ProtocolGame::parseStoreOpenTransactionHistory(NetworkMessage& msg)
1065{
1066 uint8_t entriesPerPage = msg.getByte();
1067 if (entriesPerPage>0 && entriesPerPage != GameStore::HISTORY_ENTRIES_PER_PAGE) {
1068 GameStore::HISTORY_ENTRIES_PER_PAGE = entriesPerPage;
1069 }
1070
1071 addGameTaskTimed(2000, &Game::playerStoreTransactionHistory, player->getID(), 1);
1072}
1073
1074void ProtocolGame::parseStoreRequestTransactionHistory(NetworkMessage& msg)
1075{
1076 uint32_t pageNumber = msg.get<uint32_t>();
1077 addGameTaskTimed(2000, &Game::playerStoreTransactionHistory, player->getID(), pageNumber);
1078}
1079
1080void ProtocolGame::parseCoinTransfer(NetworkMessage& msg)
1081{
1082 std::string receiverName = msg.getString();
1083 uint32_t amount = msg.get<uint32_t>();
1084
1085 addGameTaskTimed(350, &Game::playerCoinTransfer, player->getID(), receiverName, amount);
1086}
1087
1088void ProtocolGame::parseMarketCreateOffer(NetworkMessage& msg)
1089{
1090 uint8_t type = msg.getByte();
1091 uint16_t spriteId = msg.get<uint16_t>();
1092 uint16_t amount = msg.get<uint16_t>();
1093 uint32_t price = msg.get<uint32_t>();
1094 bool anonymous = (msg.getByte() != 0);
1095 addGameTask(&Game::playerCreateMarketOffer, player->getID(), type, spriteId, amount, price, anonymous);
1096}
1097
1098void ProtocolGame::parseMarketCancelOffer(NetworkMessage& msg)
1099{
1100 uint32_t timestamp = msg.get<uint32_t>();
1101 uint16_t counter = msg.get<uint16_t>();
1102 addGameTask(&Game::playerCancelMarketOffer, player->getID(), timestamp, counter);
1103}
1104
1105void ProtocolGame::parseMarketAcceptOffer(NetworkMessage& msg)
1106{
1107 uint32_t timestamp = msg.get<uint32_t>();
1108 uint16_t counter = msg.get<uint16_t>();
1109 uint16_t amount = msg.get<uint16_t>();
1110 addGameTask(&Game::playerAcceptMarketOffer, player->getID(), timestamp, counter, amount);
1111}
1112
1113void ProtocolGame::parseModalWindowAnswer(NetworkMessage& msg)
1114{
1115 uint32_t id = msg.get<uint32_t>();
1116 uint8_t button = msg.getByte();
1117 uint8_t choice = msg.getByte();
1118 addGameTask(&Game::playerAnswerModalWindow, player->getID(), id, button, choice);
1119}
1120
1121void ProtocolGame::parseBrowseField(NetworkMessage& msg)
1122{
1123 const Position& pos = msg.getPosition();
1124 addGameTask(&Game::playerBrowseField, player->getID(), pos);
1125}
1126
1127void ProtocolGame::parseSeekInContainer(NetworkMessage& msg)
1128{
1129 uint8_t containerId = msg.getByte();
1130 uint16_t index = msg.get<uint16_t>();
1131 addGameTask(&Game::playerSeekInContainer, player->getID(), containerId, index);
1132}
1133
1134// Send methods
1135void ProtocolGame::sendOpenPrivateChannel(const std::string& receiver)
1136{
1137 NetworkMessage msg;
1138 msg.addByte(0xAD);
1139 msg.addString(receiver);
1140 writeToOutputBuffer(msg);
1141}
1142
1143void ProtocolGame::sendChannelEvent(uint16_t channelId, const std::string& playerName, ChannelEvent_t channelEvent)
1144{
1145 NetworkMessage msg;
1146 msg.addByte(0xF3);
1147 msg.add<uint16_t>(channelId);
1148 msg.addString(playerName);
1149 msg.addByte(channelEvent);
1150 writeToOutputBuffer(msg);
1151}
1152
1153void ProtocolGame::sendCreatureOutfit(const Creature* creature, const Outfit_t& outfit)
1154{
1155 if (!canSee(creature)) {
1156 return;
1157 }
1158
1159 NetworkMessage msg;
1160 msg.addByte(0x8E);
1161 msg.add<uint32_t>(creature->getID());
1162 AddOutfit(msg, outfit);
1163 writeToOutputBuffer(msg);
1164}
1165
1166void ProtocolGame::sendCreatureWalkthrough(const Creature* creature, bool walkthrough)
1167{
1168 if (!canSee(creature)) {
1169 return;
1170 }
1171
1172 NetworkMessage msg;
1173 msg.addByte(0x92);
1174 msg.add<uint32_t>(creature->getID());
1175 msg.addByte(walkthrough ? 0x00 : 0x01);
1176 writeToOutputBuffer(msg);
1177}
1178
1179void ProtocolGame::sendCreatureShield(const Creature* creature)
1180{
1181 if (!canSee(creature)) {
1182 return;
1183 }
1184
1185 NetworkMessage msg;
1186 msg.addByte(0x91);
1187 msg.add<uint32_t>(creature->getID());
1188 msg.addByte(player->getPartyShield(creature->getPlayer()));
1189 writeToOutputBuffer(msg);
1190}
1191
1192void ProtocolGame::sendCreatureSkull(const Creature* creature)
1193{
1194 if (g_game.getWorldType() != WORLD_TYPE_PVP) {
1195 return;
1196 }
1197
1198 if (!canSee(creature)) {
1199 return;
1200 }
1201
1202 NetworkMessage msg;
1203 msg.addByte(0x90);
1204 msg.add<uint32_t>(creature->getID());
1205 msg.addByte(player->getSkullClient(creature));
1206 writeToOutputBuffer(msg);
1207}
1208
1209void ProtocolGame::sendCreatureType(const Creature* creature, uint8_t creatureType)
1210{
1211 NetworkMessage msg;
1212 msg.addByte(0x95);
1213 msg.add<uint32_t>(creature->getID());
1214 msg.addByte(creatureType);
1215
1216 if (player->getOperatingSystem() == CLIENTOS_WINDOWS && player->getProtocolVersion() >= 1120) {
1217 msg.addByte(creatureType); // type or any byte idk
1218 }
1219
1220 writeToOutputBuffer(msg);
1221}
1222
1223void ProtocolGame::sendCreatureHelpers(uint32_t creatureId, uint16_t helpers)
1224{
1225 NetworkMessage msg;
1226 msg.addByte(0x94);
1227 msg.add<uint32_t>(creatureId);
1228 msg.add<uint16_t>(helpers);
1229 writeToOutputBuffer(msg);
1230}
1231
1232void ProtocolGame::sendCreatureSquare(const Creature* creature, SquareColor_t color)
1233{
1234 if (!canSee(creature)) {
1235 return;
1236 }
1237
1238 NetworkMessage msg;
1239 msg.addByte(0x93);
1240 msg.add<uint32_t>(creature->getID());
1241 msg.addByte(0x01);
1242 msg.addByte(color);
1243 writeToOutputBuffer(msg);
1244}
1245
1246void ProtocolGame::sendTutorial(uint8_t tutorialId)
1247{
1248 NetworkMessage msg;
1249 msg.addByte(0xDC);
1250 msg.addByte(tutorialId);
1251 writeToOutputBuffer(msg);
1252}
1253
1254void ProtocolGame::sendAddMarker(const Position& pos, uint8_t markType, const std::string& desc)
1255{
1256 NetworkMessage msg;
1257 msg.addByte(0xDD);
1258 msg.addPosition(pos);
1259 msg.addByte(markType);
1260 msg.addString(desc);
1261 writeToOutputBuffer(msg);
1262}
1263
1264void ProtocolGame::sendReLoginWindow(uint8_t unfairFightReduction)
1265{
1266 NetworkMessage msg;
1267 msg.addByte(0x28);
1268 msg.addByte(0x00);
1269 msg.addByte(unfairFightReduction);
1270 if (version >= 1120) {
1271 msg.addByte(0x00); //Use death redemption
1272 }
1273 writeToOutputBuffer(msg);
1274}
1275
1276void ProtocolGame::sendTextMessage(const TextMessage& message)
1277{
1278 NetworkMessage msg;
1279 msg.addByte(0xB4);
1280 msg.addByte(message.type);
1281 switch (message.type) {
1282 case MESSAGE_DAMAGE_DEALT:
1283 case MESSAGE_DAMAGE_RECEIVED:
1284 case MESSAGE_DAMAGE_OTHERS: {
1285 msg.addPosition(message.position);
1286 msg.add<uint32_t>(message.primary.value);
1287 msg.addByte(message.primary.color);
1288 msg.add<uint32_t>(message.secondary.value);
1289 msg.addByte(message.secondary.color);
1290 break;
1291 }
1292 case MESSAGE_HEALED:
1293 case MESSAGE_HEALED_OTHERS:
1294 case MESSAGE_EXPERIENCE:
1295 case MESSAGE_EXPERIENCE_OTHERS: {
1296 msg.addPosition(message.position);
1297 msg.add<uint32_t>(message.primary.value);
1298 msg.addByte(message.primary.color);
1299 break;
1300 }
1301 case MESSAGE_GUILD:
1302 case MESSAGE_PARTY_MANAGEMENT:
1303 case MESSAGE_PARTY:
1304 msg.add<uint16_t>(message.channelId);
1305 break;
1306 default: {
1307 break;
1308 }
1309 }
1310 msg.addString(message.text);
1311 writeToOutputBuffer(msg);
1312}
1313
1314void ProtocolGame::sendClosePrivate(uint16_t channelId)
1315{
1316 NetworkMessage msg;
1317 msg.addByte(0xB3);
1318 msg.add<uint16_t>(channelId);
1319 writeToOutputBuffer(msg);
1320}
1321
1322void ProtocolGame::sendCreatePrivateChannel(uint16_t channelId, const std::string& channelName)
1323{
1324 NetworkMessage msg;
1325 msg.addByte(0xB2);
1326 msg.add<uint16_t>(channelId);
1327 msg.addString(channelName);
1328 msg.add<uint16_t>(0x01);
1329 msg.addString(player->getName());
1330 msg.add<uint16_t>(0x00);
1331 writeToOutputBuffer(msg);
1332}
1333
1334void ProtocolGame::sendChannelsDialog()
1335{
1336 NetworkMessage msg;
1337 msg.addByte(0xAB);
1338
1339 const ChannelList& list = g_chat->getChannelList(*player);
1340 msg.addByte(list.size());
1341 for (ChatChannel* channel : list) {
1342 msg.add<uint16_t>(channel->getId());
1343 msg.addString(channel->getName());
1344 }
1345
1346 writeToOutputBuffer(msg);
1347}
1348
1349void ProtocolGame::sendIcons(uint16_t icons)
1350{
1351 NetworkMessage msg;
1352 msg.addByte(0xA2);
1353 if (version >= 1140) // TODO: verify compatibility of the new icon range ( 16-31 )
1354 msg.add<uint32_t>(icons);
1355 else
1356 msg.add<uint16_t>(icons);
1357 writeToOutputBuffer(msg);
1358}
1359
1360void ProtocolGame::sendShop(Npc* npc, const ShopInfoList& itemList)
1361{
1362 NetworkMessage msg;
1363 msg.addByte(0x7A);
1364 msg.addString(npc->getName());
1365
1366 uint16_t itemsToSend = std::min<size_t>(itemList.size(), std::numeric_limits<uint16_t>::max());
1367 msg.add<uint16_t>(itemsToSend);
1368
1369 uint16_t i = 0;
1370 for (auto it = itemList.begin(); i < itemsToSend; ++it, ++i) {
1371 AddShopItem(msg, *it);
1372 }
1373
1374 writeToOutputBuffer(msg);
1375}
1376
1377void ProtocolGame::sendCloseShop()
1378{
1379 NetworkMessage msg;
1380 msg.addByte(0x7C);
1381 writeToOutputBuffer(msg);
1382}
1383
1384void ProtocolGame::sendClientCheck()
1385{
1386 NetworkMessage msg;
1387 msg.addByte(0x63);
1388 msg.add<uint32_t>(1);
1389 msg.addByte(1);
1390 writeToOutputBuffer(msg);
1391}
1392
1393void ProtocolGame::sendGameNews()
1394{
1395 NetworkMessage msg;
1396 msg.addByte(0x98);
1397 msg.add<uint32_t>(1); // unknown
1398 msg.addByte(1); //(0 = open | 1 = highlight)
1399 writeToOutputBuffer(msg);
1400}
1401
1402void ProtocolGame::sendResourceBalance(uint64_t money, uint64_t bank)
1403{
1404 NetworkMessage msg;
1405 msg.addByte(0xEE);
1406 msg.addByte(0x00);
1407 msg.add<uint64_t>(bank);
1408 msg.addByte(0xEE);
1409 msg.addByte(0x01);
1410 msg.add<uint64_t>(money);
1411 writeToOutputBuffer(msg);
1412}
1413
1414void ProtocolGame::sendSaleItemList(const std::list<ShopInfo>& shop)
1415{
1416 if (player->getProtocolVersion() >= 1100) {
1417 sendResourceBalance(player->getMoney(), player->getBankBalance());
1418 }
1419
1420 NetworkMessage msg;
1421 msg.addByte(0x7B);
1422 msg.add<uint64_t>(player->getMoney() + player->getBankBalance());
1423
1424 std::map<uint16_t, uint32_t> saleMap;
1425
1426 if (shop.size() <= 5) {
1427 // For very small shops it's not worth it to create the complete map
1428 for (const ShopInfo& shopInfo : shop) {
1429 if (shopInfo.sellPrice == 0) {
1430 continue;
1431 }
1432
1433 int8_t subtype = -1;
1434
1435 const ItemType& itemType = Item::items[shopInfo.itemId];
1436 if (itemType.hasSubType() && !itemType.stackable) {
1437 subtype = (shopInfo.subType == 0 ? -1 : shopInfo.subType);
1438 }
1439
1440 uint32_t count = player->getItemTypeCount(shopInfo.itemId, subtype);
1441 if (count > 0) {
1442 saleMap[shopInfo.itemId] = count;
1443 }
1444 }
1445 }
1446 else {
1447 // Large shop, it's better to get a cached map of all item counts and use it
1448 // We need a temporary map since the finished map should only contain items
1449 // available in the shop
1450 std::map<uint32_t, uint32_t> tempSaleMap;
1451 player->getAllItemTypeCount(tempSaleMap);
1452
1453 // We must still check manually for the special items that require subtype matches
1454 // (That is, fluids such as potions etc., actually these items are very few since
1455 // health potions now use their own ID)
1456 for (const ShopInfo& shopInfo : shop) {
1457 if (shopInfo.sellPrice == 0) {
1458 continue;
1459 }
1460
1461 int8_t subtype = -1;
1462
1463 const ItemType& itemType = Item::items[shopInfo.itemId];
1464 if (itemType.hasSubType() && !itemType.stackable) {
1465 subtype = (shopInfo.subType == 0 ? -1 : shopInfo.subType);
1466 }
1467
1468 if (subtype != -1) {
1469 uint32_t count;
1470 if (!itemType.isFluidContainer() && !itemType.isSplash()) {
1471 count = player->getItemTypeCount(shopInfo.itemId, subtype); // This shop item requires extra checks
1472 }
1473 else {
1474 count = subtype;
1475 }
1476
1477 if (count > 0) {
1478 saleMap[shopInfo.itemId] = count;
1479 }
1480 }
1481 else {
1482 std::map<uint32_t, uint32_t>::const_iterator findIt = tempSaleMap.find(shopInfo.itemId);
1483 if (findIt != tempSaleMap.end() && findIt->second > 0) {
1484 saleMap[shopInfo.itemId] = findIt->second;
1485 }
1486 }
1487 }
1488 }
1489
1490 uint8_t itemsToSend = std::min<size_t>(saleMap.size(), std::numeric_limits<uint8_t>::max());
1491 msg.addByte(itemsToSend);
1492
1493 uint8_t i = 0;
1494 for (std::map<uint16_t, uint32_t>::const_iterator it = saleMap.begin(); i < itemsToSend; ++it, ++i) {
1495 msg.addItemId(it->first);
1496 msg.addByte(std::min<uint32_t>(it->second, std::numeric_limits<uint8_t>::max()));
1497 }
1498
1499 writeToOutputBuffer(msg);
1500}
1501
1502void ProtocolGame::sendMarketEnter(uint32_t depotId)
1503{
1504 NetworkMessage msg;
1505 msg.addByte(0xF6);
1506
1507 msg.add<uint64_t>(player->getBankBalance());
1508 msg.addByte(std::min<uint32_t>(IOMarket::getPlayerOfferCount(player->getGUID()), std::numeric_limits<uint8_t>::max()));
1509
1510 DepotLocker* depotLocker = player->getDepotLocker(depotId);
1511 if (!depotLocker) {
1512 msg.add<uint16_t>(0x00);
1513 writeToOutputBuffer(msg);
1514 return;
1515 }
1516
1517 player->setInMarket(true);
1518
1519 std::map<uint16_t, uint32_t> depotItems;
1520 std::forward_list<Container*> containerList{ depotLocker };
1521
1522 do {
1523 Container* container = containerList.front();
1524 containerList.pop_front();
1525
1526 for (Item* item : container->getItemList()) {
1527 Container* c = item->getContainer();
1528 if (c && !c->empty()) {
1529 containerList.push_front(c);
1530 continue;
1531 }
1532
1533 const ItemType& itemType = Item::items[item->getID()];
1534 if (itemType.wareId == 0) {
1535 continue;
1536 }
1537
1538 if (c && (!itemType.isContainer() || c->capacity() != itemType.maxItems)) {
1539 continue;
1540 }
1541
1542 if (!item->hasMarketAttributes()) {
1543 continue;
1544 }
1545
1546 depotItems[itemType.wareId] += Item::countByType(item, -1);
1547 }
1548 } while (!containerList.empty());
1549
1550 uint16_t itemsToSend = std::min<size_t>(depotItems.size(), std::numeric_limits<uint16_t>::max());
1551 msg.add<uint16_t>(itemsToSend);
1552
1553 uint16_t i = 0;
1554 for (std::map<uint16_t, uint32_t>::const_iterator it = depotItems.begin(); i < itemsToSend; ++it, ++i) {
1555 msg.add<uint16_t>(it->first);
1556 msg.add<uint16_t>(std::min<uint32_t>(0xFFFF, it->second));
1557 }
1558
1559 writeToOutputBuffer(msg);
1560
1561 updateCoinBalance();
1562 sendResourceBalance(player->getMoney(), player->getBankBalance());
1563}
1564
1565void ProtocolGame::updateCoinBalance()
1566{
1567 NetworkMessage msg;
1568 msg.addByte(0xF2);
1569 msg.addByte(0x00);
1570
1571 writeToOutputBuffer(msg);
1572
1573 g_dispatcher.addTask(
1574 createTask(std::bind([](ProtocolGame_ptr client) {
1575 client->sendCoinBalance();
1576 }, getThis()))
1577 );
1578}
1579
1580void ProtocolGame::sendCoinBalance()
1581{
1582 Database& db = Database::getInstance();
1583
1584 std::ostringstream query;
1585
1586 query << "SELECT `coins` FROM `accounts` WHERE `id`=" + std::to_string(player->getAccount());
1587 DBResult_ptr result = db.storeQuery(query.str());
1588 if (!result) {
1589 return;
1590 }
1591
1592 NetworkMessage msg;
1593 msg.addByte(0xF2);
1594 msg.addByte(0x01);
1595
1596 msg.addByte(0xDF);
1597 msg.addByte(0x01);
1598
1599 msg.add<uint32_t>(result->getNumber<uint32_t>("coins")); //total coins
1600 msg.add<uint32_t>(result->getNumber<uint32_t>("coins")); //transferable coins
1601
1602 writeToOutputBuffer(msg);
1603}
1604
1605void ProtocolGame::sendMarketLeave()
1606{
1607 NetworkMessage msg;
1608 msg.addByte(0xF7);
1609 writeToOutputBuffer(msg);
1610}
1611
1612void ProtocolGame::sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers)
1613{
1614 NetworkMessage msg;
1615
1616 msg.addByte(0xF9);
1617 msg.addItemId(itemId);
1618
1619 msg.add<uint32_t>(buyOffers.size());
1620 for (const MarketOffer& offer : buyOffers) {
1621 msg.add<uint32_t>(offer.timestamp);
1622 msg.add<uint16_t>(offer.counter);
1623 msg.add<uint16_t>(offer.amount);
1624 msg.add<uint32_t>(offer.price);
1625 msg.addString(offer.playerName);
1626 }
1627
1628 msg.add<uint32_t>(sellOffers.size());
1629 for (const MarketOffer& offer : sellOffers) {
1630 msg.add<uint32_t>(offer.timestamp);
1631 msg.add<uint16_t>(offer.counter);
1632 msg.add<uint16_t>(offer.amount);
1633 msg.add<uint32_t>(offer.price);
1634 msg.addString(offer.playerName);
1635 }
1636
1637 writeToOutputBuffer(msg);
1638}
1639
1640void ProtocolGame::sendMarketAcceptOffer(const MarketOfferEx& offer)
1641{
1642 NetworkMessage msg;
1643 msg.addByte(0xF9);
1644 msg.addItemId(offer.itemId);
1645
1646 if (offer.type == MARKETACTION_BUY) {
1647 msg.add<uint32_t>(0x01);
1648 msg.add<uint32_t>(offer.timestamp);
1649 msg.add<uint16_t>(offer.counter);
1650 msg.add<uint16_t>(offer.amount);
1651 msg.add<uint32_t>(offer.price);
1652 msg.addString(offer.playerName);
1653 msg.add<uint32_t>(0x00);
1654 }
1655 else {
1656 msg.add<uint32_t>(0x00);
1657 msg.add<uint32_t>(0x01);
1658 msg.add<uint32_t>(offer.timestamp);
1659 msg.add<uint16_t>(offer.counter);
1660 msg.add<uint16_t>(offer.amount);
1661 msg.add<uint32_t>(offer.price);
1662 msg.addString(offer.playerName);
1663 }
1664
1665 writeToOutputBuffer(msg);
1666}
1667
1668void ProtocolGame::sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers)
1669{
1670 NetworkMessage msg;
1671 msg.addByte(0xF9);
1672 msg.add<uint16_t>(MARKETREQUEST_OWN_OFFERS);
1673
1674 msg.add<uint32_t>(buyOffers.size());
1675 for (const MarketOffer& offer : buyOffers) {
1676 msg.add<uint32_t>(offer.timestamp);
1677 msg.add<uint16_t>(offer.counter);
1678 msg.addItemId(offer.itemId);
1679 msg.add<uint16_t>(offer.amount);
1680 msg.add<uint32_t>(offer.price);
1681 }
1682
1683 msg.add<uint32_t>(sellOffers.size());
1684 for (const MarketOffer& offer : sellOffers) {
1685 msg.add<uint32_t>(offer.timestamp);
1686 msg.add<uint16_t>(offer.counter);
1687 msg.addItemId(offer.itemId);
1688 msg.add<uint16_t>(offer.amount);
1689 msg.add<uint32_t>(offer.price);
1690 }
1691
1692 writeToOutputBuffer(msg);
1693}
1694
1695void ProtocolGame::sendMarketCancelOffer(const MarketOfferEx& offer)
1696{
1697 NetworkMessage msg;
1698 msg.addByte(0xF9);
1699 msg.add<uint16_t>(MARKETREQUEST_OWN_OFFERS);
1700
1701 if (offer.type == MARKETACTION_BUY) {
1702 msg.add<uint32_t>(0x01);
1703 msg.add<uint32_t>(offer.timestamp);
1704 msg.add<uint16_t>(offer.counter);
1705 msg.addItemId(offer.itemId);
1706 msg.add<uint16_t>(offer.amount);
1707 msg.add<uint32_t>(offer.price);
1708 msg.add<uint32_t>(0x00);
1709 }
1710 else {
1711 msg.add<uint32_t>(0x00);
1712 msg.add<uint32_t>(0x01);
1713 msg.add<uint32_t>(offer.timestamp);
1714 msg.add<uint16_t>(offer.counter);
1715 msg.addItemId(offer.itemId);
1716 msg.add<uint16_t>(offer.amount);
1717 msg.add<uint32_t>(offer.price);
1718 }
1719
1720 writeToOutputBuffer(msg);
1721}
1722
1723void ProtocolGame::sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers)
1724{
1725 uint32_t i = 0;
1726 std::map<uint32_t, uint16_t> counterMap;
1727 uint32_t buyOffersToSend = std::min<uint32_t>(buyOffers.size(), 810 + std::max<int32_t>(0, 810 - sellOffers.size()));
1728 uint32_t sellOffersToSend = std::min<uint32_t>(sellOffers.size(), 810 + std::max<int32_t>(0, 810 - buyOffers.size()));
1729
1730 NetworkMessage msg;
1731 msg.addByte(0xF9);
1732 msg.add<uint16_t>(MARKETREQUEST_OWN_HISTORY);
1733
1734 msg.add<uint32_t>(buyOffersToSend);
1735 for (auto it = buyOffers.begin(); i < buyOffersToSend; ++it, ++i) {
1736 msg.add<uint32_t>(it->timestamp);
1737 msg.add<uint16_t>(counterMap[it->timestamp]++);
1738 msg.addItemId(it->itemId);
1739 msg.add<uint16_t>(it->amount);
1740 msg.add<uint32_t>(it->price);
1741 msg.addByte(it->state);
1742 }
1743
1744 counterMap.clear();
1745 i = 0;
1746
1747 msg.add<uint32_t>(sellOffersToSend);
1748 for (auto it = sellOffers.begin(); i < sellOffersToSend; ++it, ++i) {
1749 msg.add<uint32_t>(it->timestamp);
1750 msg.add<uint16_t>(counterMap[it->timestamp]++);
1751 msg.addItemId(it->itemId);
1752 msg.add<uint16_t>(it->amount);
1753 msg.add<uint32_t>(it->price);
1754 msg.addByte(it->state);
1755 }
1756
1757 writeToOutputBuffer(msg);
1758}
1759
1760void ProtocolGame::sendMarketDetail(uint16_t itemId)
1761{
1762 NetworkMessage msg;
1763 msg.addByte(0xF8);
1764 msg.addItemId(itemId);
1765
1766 const ItemType& it = Item::items[itemId];
1767 if (it.armor != 0) {
1768 msg.addString(std::to_string(it.armor));
1769 }
1770 else {
1771 msg.add<uint16_t>(0x00);
1772 }
1773
1774 if (it.attack != 0) {
1775 // TODO: chance to hit, range
1776 // example:
1777 // "attack +x, chance to hit +y%, z fields"
1778 if (it.abilities && it.abilities->elementType != COMBAT_NONE && it.abilities->elementDamage != 0) {
1779 std::ostringstream ss;
1780 ss << it.attack << " physical +" << it.abilities->elementDamage << ' ' << getCombatName(it.abilities->elementType);
1781 msg.addString(ss.str());
1782 }
1783 else {
1784 msg.addString(std::to_string(it.attack));
1785 }
1786 }
1787 else {
1788 msg.add<uint16_t>(0x00);
1789 }
1790
1791 if (it.isContainer()) {
1792 msg.addString(std::to_string(it.maxItems));
1793 }
1794 else {
1795 msg.add<uint16_t>(0x00);
1796 }
1797
1798 if (it.defense != 0) {
1799 if (it.extraDefense != 0) {
1800 std::ostringstream ss;
1801 ss << it.defense << ' ' << std::showpos << it.extraDefense << std::noshowpos;
1802 msg.addString(ss.str());
1803 }
1804 else {
1805 msg.addString(std::to_string(it.defense));
1806 }
1807 }
1808 else {
1809 msg.add<uint16_t>(0x00);
1810 }
1811
1812 if (!it.description.empty()) {
1813 const std::string& descr = it.description;
1814 if (descr.back() == '.') {
1815 msg.addString(std::string(descr, 0, descr.length() - 1));
1816 }
1817 else {
1818 msg.addString(descr);
1819 }
1820 }
1821 else {
1822 msg.add<uint16_t>(0x00);
1823 }
1824
1825 if (it.decayTime != 0) {
1826 std::ostringstream ss;
1827 ss << it.decayTime << " seconds";
1828 msg.addString(ss.str());
1829 }
1830 else {
1831 msg.add<uint16_t>(0x00);
1832 }
1833
1834 if (it.abilities) {
1835 std::ostringstream ss;
1836 bool separator = false;
1837
1838 for (size_t i = 0; i < COMBAT_COUNT; ++i) {
1839 if (it.abilities->absorbPercent[i] == 0) {
1840 continue;
1841 }
1842
1843 if (separator) {
1844 ss << ", ";
1845 }
1846 else {
1847 separator = true;
1848 }
1849
1850 ss << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << '%';
1851 }
1852
1853 msg.addString(ss.str());
1854 }
1855 else {
1856 msg.add<uint16_t>(0x00);
1857 }
1858
1859 if (it.minReqLevel != 0) {
1860 msg.addString(std::to_string(it.minReqLevel));
1861 }
1862 else {
1863 msg.add<uint16_t>(0x00);
1864 }
1865
1866 if (it.minReqMagicLevel != 0) {
1867 msg.addString(std::to_string(it.minReqMagicLevel));
1868 }
1869 else {
1870 msg.add<uint16_t>(0x00);
1871 }
1872
1873 msg.addString(it.vocationString);
1874
1875 msg.addString(it.runeSpellName);
1876
1877 if (it.abilities) {
1878 std::ostringstream ss;
1879 bool separator = false;
1880
1881 for (uint8_t i = SKILL_FIRST; i <= SKILL_FISHING; i++) {
1882 if (!it.abilities->skills[i]) {
1883 continue;
1884 }
1885
1886 if (separator) {
1887 ss << ", ";
1888 }
1889 else {
1890 separator = true;
1891 }
1892
1893 ss << getSkillName(i) << ' ' << std::showpos << it.abilities->skills[i] << std::noshowpos;
1894 }
1895
1896 for (uint8_t i = SKILL_CRITICAL_HIT_CHANCE; i <= SKILL_LAST; i++) {
1897 if (!it.abilities->skills[i]) {
1898 continue;
1899 }
1900
1901 if (separator) {
1902 ss << ", ";
1903 }
1904 else {
1905 separator = true;
1906 }
1907
1908 ss << getSkillName(i) << ' ' << std::showpos << it.abilities->skills[i] << std::noshowpos << '%';
1909 }
1910
1911 if (it.abilities->stats[STAT_MAGICPOINTS] != 0) {
1912 if (separator) {
1913 ss << ", ";
1914 }
1915 else {
1916 separator = true;
1917 }
1918
1919 ss << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos;
1920 }
1921
1922 if (it.abilities->speed != 0) {
1923 if (separator) {
1924 ss << ", ";
1925 }
1926
1927 ss << "speed " << std::showpos << (it.abilities->speed >> 1) << std::noshowpos;
1928 }
1929
1930 msg.addString(ss.str());
1931 }
1932 else {
1933 msg.add<uint16_t>(0x00);
1934 }
1935
1936 if (it.charges != 0) {
1937 msg.addString(std::to_string(it.charges));
1938 }
1939 else {
1940 msg.add<uint16_t>(0x00);
1941 }
1942
1943 std::string weaponName = getWeaponName(it.weaponType);
1944
1945 if (it.slotPosition & SLOTP_TWO_HAND) {
1946 if (!weaponName.empty()) {
1947 weaponName += ", two-handed";
1948 }
1949 else {
1950 weaponName = "two-handed";
1951 }
1952 }
1953
1954 msg.addString(weaponName);
1955
1956 if (it.weight != 0) {
1957 std::ostringstream ss;
1958 if (it.weight < 10) {
1959 ss << "0.0" << it.weight;
1960 }
1961 else if (it.weight < 100) {
1962 ss << "0." << it.weight;
1963 }
1964 else {
1965 std::string weightString = std::to_string(it.weight);
1966 weightString.insert(weightString.end() - 2, '.');
1967 ss << weightString;
1968 }
1969 ss << " oz";
1970 msg.addString(ss.str());
1971 }
1972 else {
1973 msg.add<uint16_t>(0x00);
1974 }
1975
1976 if (version > 1099) {
1977 msg.add<uint16_t>(0x00); // imbuement detail
1978 }
1979
1980 MarketStatistics* statistics = IOMarket::getInstance().getPurchaseStatistics(itemId);
1981 if (statistics) {
1982 msg.addByte(0x01);
1983 msg.add<uint32_t>(statistics->numTransactions);
1984 msg.add<uint32_t>(std::min<uint64_t>(std::numeric_limits<uint32_t>::max(), statistics->totalPrice));
1985 msg.add<uint32_t>(statistics->highestPrice);
1986 msg.add<uint32_t>(statistics->lowestPrice);
1987 }
1988 else {
1989 msg.addByte(0x00);
1990 }
1991
1992 statistics = IOMarket::getInstance().getSaleStatistics(itemId);
1993 if (statistics) {
1994 msg.addByte(0x01);
1995 msg.add<uint32_t>(statistics->numTransactions);
1996 msg.add<uint32_t>(std::min<uint64_t>(std::numeric_limits<uint32_t>::max(), statistics->totalPrice));
1997 msg.add<uint32_t>(statistics->highestPrice);
1998 msg.add<uint32_t>(statistics->lowestPrice);
1999 }
2000 else {
2001 msg.addByte(0x00);
2002 }
2003
2004 writeToOutputBuffer(msg);
2005}
2006
2007void ProtocolGame::sendQuestTracker()
2008{
2009 NetworkMessage msg;
2010 msg.addByte(0xD0); // byte quest tracker
2011 msg.addByte(1); // send quests of quest log ??
2012 msg.add<uint16_t>(1); // unknown
2013 writeToOutputBuffer(msg);
2014}
2015
2016void ProtocolGame::sendQuestLog()
2017{
2018 NetworkMessage msg;
2019 msg.addByte(0xF0);
2020 msg.add<uint16_t>(g_game.quests.getQuestsCount(player));
2021
2022 for (const Quest& quest : g_game.quests.getQuests()) {
2023 if (quest.isStarted(player)) {
2024 msg.add<uint16_t>(quest.getID());
2025 msg.addString(quest.getName());
2026 msg.addByte(quest.isCompleted(player));
2027 }
2028 }
2029
2030 writeToOutputBuffer(msg);
2031}
2032
2033void ProtocolGame::sendQuestLine(const Quest* quest)
2034{
2035 NetworkMessage msg;
2036 msg.addByte(0xF1);
2037 msg.add<uint16_t>(quest->getID());
2038 msg.addByte(quest->getMissionsCount(player));
2039
2040 for (const Mission& mission : quest->getMissions()) {
2041 if (mission.isStarted(player)) {
2042 if (player->getProtocolVersion() >= 1120) {
2043 msg.add<uint16_t>(quest->getID());
2044 }
2045 msg.addString(mission.getName(player));
2046 msg.addString(mission.getDescription(player));
2047 }
2048 }
2049
2050 if (player->operatingSystem == CLIENTOS_NEW_WINDOWS) {
2051 sendQuestTracker();
2052 }
2053
2054 writeToOutputBuffer(msg);
2055}
2056
2057void ProtocolGame::sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack)
2058{
2059 NetworkMessage msg;
2060
2061 if (ack) {
2062 msg.addByte(0x7D);
2063 }
2064 else {
2065 msg.addByte(0x7E);
2066 }
2067
2068 msg.addString(traderName);
2069
2070 if (const Container* tradeContainer = item->getContainer()) {
2071 std::list<const Container*> listContainer{ tradeContainer };
2072 std::list<const Item*> itemList{ tradeContainer };
2073 while (!listContainer.empty()) {
2074 const Container* container = listContainer.front();
2075 listContainer.pop_front();
2076
2077 for (Item* containerItem : container->getItemList()) {
2078 Container* tmpContainer = containerItem->getContainer();
2079 if (tmpContainer) {
2080 listContainer.push_back(tmpContainer);
2081 }
2082 itemList.push_back(containerItem);
2083 }
2084 }
2085
2086 msg.addByte(itemList.size());
2087 for (const Item* listItem : itemList) {
2088 msg.addItem(listItem);
2089 }
2090 }
2091 else {
2092 msg.addByte(0x01);
2093 msg.addItem(item);
2094 }
2095 writeToOutputBuffer(msg);
2096}
2097
2098void ProtocolGame::sendCloseTrade()
2099{
2100 NetworkMessage msg;
2101 msg.addByte(0x7F);
2102 writeToOutputBuffer(msg);
2103}
2104
2105void ProtocolGame::sendCloseContainer(uint8_t cid)
2106{
2107 NetworkMessage msg;
2108 msg.addByte(0x6F);
2109 msg.addByte(cid);
2110 writeToOutputBuffer(msg);
2111}
2112
2113void ProtocolGame::sendCreatureTurn(const Creature* creature, uint32_t stackPos)
2114{
2115 if (!canSee(creature)) {
2116 return;
2117 }
2118
2119 NetworkMessage msg;
2120 msg.addByte(0x6B);
2121 msg.addPosition(creature->getPosition());
2122 msg.addByte(stackPos);
2123 msg.add<uint16_t>(0x63);
2124 msg.add<uint32_t>(creature->getID());
2125 msg.addByte(creature->getDirection());
2126 msg.addByte(player->canWalkthroughEx(creature) ? 0x00 : 0x01);
2127 writeToOutputBuffer(msg);
2128}
2129
2130void ProtocolGame::sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, const Position* pos/* = nullptr*/)
2131{
2132 NetworkMessage msg;
2133 msg.addByte(0xAA);
2134
2135 static uint32_t statementId = 0;
2136 msg.add<uint32_t>(++statementId);
2137
2138 msg.addString(creature->getName());
2139
2140 //Add level only for players
2141 if (const Player* speaker = creature->getPlayer()) {
2142 msg.add<uint16_t>(speaker->getLevel());
2143 }
2144 else {
2145 msg.add<uint16_t>(0x00);
2146 }
2147
2148 msg.addByte(type);
2149 if (pos) {
2150 msg.addPosition(*pos);
2151 }
2152 else {
2153 msg.addPosition(creature->getPosition());
2154 }
2155
2156 msg.addString(text);
2157 writeToOutputBuffer(msg);
2158}
2159
2160void ProtocolGame::sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId)
2161{
2162 NetworkMessage msg;
2163 msg.addByte(0xAA);
2164
2165 static uint32_t statementId = 0;
2166 msg.add<uint32_t>(++statementId);
2167 if (!creature) {
2168 msg.add<uint32_t>(0x00);
2169 }
2170 else if (type == TALKTYPE_CHANNEL_R2) {
2171 msg.add<uint32_t>(0x00);
2172 type = TALKTYPE_CHANNEL_R1;
2173 }
2174 else {
2175 msg.addString(creature->getName());
2176 //Add level only for players
2177 if (const Player* speaker = creature->getPlayer()) {
2178 msg.add<uint16_t>(speaker->getLevel());
2179 }
2180 else {
2181 msg.add<uint16_t>(0x00);
2182 }
2183 }
2184
2185 msg.addByte(type);
2186 msg.add<uint16_t>(channelId);
2187 msg.addString(text);
2188 writeToOutputBuffer(msg);
2189}
2190
2191void ProtocolGame::sendPrivateMessage(const Player* speaker, SpeakClasses type, const std::string& text)
2192{
2193 NetworkMessage msg;
2194 msg.addByte(0xAA);
2195 static uint32_t statementId = 0;
2196 msg.add<uint32_t>(++statementId);
2197 if (speaker) {
2198 msg.addString(speaker->getName());
2199 msg.add<uint16_t>(speaker->getLevel());
2200 }
2201 else {
2202 msg.add<uint32_t>(0x00);
2203 }
2204 msg.addByte(type);
2205 msg.addString(text);
2206 writeToOutputBuffer(msg);
2207}
2208
2209void ProtocolGame::sendCancelTarget()
2210{
2211 NetworkMessage msg;
2212 msg.addByte(0xA3);
2213 msg.add<uint32_t>(0x00);
2214 writeToOutputBuffer(msg);
2215}
2216
2217void ProtocolGame::sendChangeSpeed(const Creature* creature, uint32_t speed)
2218{
2219 NetworkMessage msg;
2220 msg.addByte(0x8F);
2221 msg.add<uint32_t>(creature->getID());
2222 msg.add<uint16_t>(creature->getBaseSpeed() / 2);
2223 msg.add<uint16_t>(speed / 2);
2224 writeToOutputBuffer(msg);
2225}
2226
2227void ProtocolGame::sendDistanceShoot(const Position& from, const Position& to, uint8_t type)
2228{
2229 NetworkMessage msg;
2230 msg.addByte(0x85);
2231 msg.addPosition(from);
2232 msg.addPosition(to);
2233 msg.addByte(type);
2234 writeToOutputBuffer(msg);
2235}
2236
2237void ProtocolGame::sendCreatureHealth(const Creature* creature)
2238{
2239 NetworkMessage msg;
2240 msg.addByte(0x8C);
2241 msg.add<uint32_t>(creature->getID());
2242
2243 if (creature->isHealthHidden()) {
2244 msg.addByte(0x00);
2245 }
2246 else {
2247 msg.addByte(std::ceil((static_cast<double>(creature->getHealth()) / std::max<int32_t>(creature->getMaxHealth(), 1)) * 100));
2248 }
2249 writeToOutputBuffer(msg);
2250}
2251
2252void ProtocolGame::sendFYIBox(const std::string& message)
2253{
2254 NetworkMessage msg;
2255 msg.addByte(0x15);
2256 msg.addString(message);
2257 writeToOutputBuffer(msg);
2258}
2259
2260//tile
2261void ProtocolGame::sendAddTileItem(const Position& pos, uint32_t stackpos, const Item* item)
2262{
2263 if (!canSee(pos)) {
2264 return;
2265 }
2266
2267 NetworkMessage msg;
2268 msg.addByte(0x6A);
2269 msg.addPosition(pos);
2270 msg.addByte(stackpos);
2271 msg.addItem(item);
2272 writeToOutputBuffer(msg);
2273}
2274
2275void ProtocolGame::sendUpdateTileItem(const Position& pos, uint32_t stackpos, const Item* item)
2276{
2277 if (!canSee(pos)) {
2278 return;
2279 }
2280
2281 NetworkMessage msg;
2282 msg.addByte(0x6B);
2283 msg.addPosition(pos);
2284 msg.addByte(stackpos);
2285 msg.addItem(item);
2286 writeToOutputBuffer(msg);
2287}
2288
2289void ProtocolGame::sendRemoveTileThing(const Position& pos, uint32_t stackpos)
2290{
2291 if (!canSee(pos)) {
2292 return;
2293 }
2294
2295 NetworkMessage msg;
2296 RemoveTileThing(msg, pos, stackpos);
2297 writeToOutputBuffer(msg);
2298}
2299
2300void ProtocolGame::sendFightModes()
2301{
2302 NetworkMessage msg;
2303 msg.addByte(0xA7);
2304 msg.addByte(player->fightMode);
2305 msg.addByte(player->chaseMode);
2306 msg.addByte(player->secureMode);
2307 msg.addByte(PVP_MODE_DOVE);
2308 writeToOutputBuffer(msg);
2309}
2310
2311void ProtocolGame::sendMoveCreature(const Creature* creature, const Position& newPos, int32_t newStackPos, const Position& oldPos, int32_t oldStackPos, bool teleport)
2312{
2313 if (creature == player) {
2314 if (oldStackPos >= 10) {
2315 sendMapDescription(newPos);
2316 }
2317 else if (teleport) {
2318 NetworkMessage msg;
2319 RemoveTileThing(msg, oldPos, oldStackPos);
2320 writeToOutputBuffer(msg);
2321 sendMapDescription(newPos);
2322 }
2323 else {
2324 NetworkMessage msg;
2325 if (oldPos.z == 7 && newPos.z >= 8) {
2326 RemoveTileThing(msg, oldPos, oldStackPos);
2327 }
2328 else {
2329 msg.addByte(0x6D);
2330 msg.addPosition(oldPos);
2331 msg.addByte(oldStackPos);
2332 msg.addPosition(newPos);
2333 }
2334
2335 if (newPos.z > oldPos.z) {
2336 MoveDownCreature(msg, creature, newPos, oldPos);
2337 }
2338 else if (newPos.z < oldPos.z) {
2339 MoveUpCreature(msg, creature, newPos, oldPos);
2340 }
2341
2342 if (oldPos.y > newPos.y) { // north, for old x
2343 msg.addByte(0x65);
2344 GetMapDescription(oldPos.x - Map::maxClientViewportX, newPos.y - Map::maxClientViewportY, newPos.z, (Map::maxClientViewportX+1)*2, 1, msg);
2345 } else if (oldPos.y < newPos.y) { // south, for old x
2346 msg.addByte(0x67);
2347 GetMapDescription(oldPos.x - Map::maxClientViewportX, newPos.y + (Map::maxClientViewportY+1), newPos.z, (Map::maxClientViewportX+1)*2, 1, msg);
2348 }
2349 if (oldPos.x < newPos.x) { // east, [with new y]
2350 msg.addByte(0x66);
2351 GetMapDescription(newPos.x + (Map::maxClientViewportX+1), newPos.y - Map::maxClientViewportY, newPos.z, 1, (Map::maxClientViewportY+1)*2, msg);
2352 } else if (oldPos.x > newPos.x) { // west, [with new y]
2353 msg.addByte(0x68);
2354 GetMapDescription(newPos.x - Map::maxClientViewportX, newPos.y - Map::maxClientViewportY, newPos.z, 1, (Map::maxClientViewportY+1)*2, msg);
2355 }
2356 writeToOutputBuffer(msg);
2357 }
2358 }
2359 else if (canSee(oldPos) && canSee(creature->getPosition())) {
2360 if (teleport || (oldPos.z == 7 && newPos.z >= 8) || oldStackPos >= 10) {
2361 sendRemoveTileThing(oldPos, oldStackPos);
2362 sendAddCreature(creature, newPos, newStackPos, false);
2363 }
2364 else {
2365 NetworkMessage msg;
2366 msg.addByte(0x6D);
2367 msg.addPosition(oldPos);
2368 msg.addByte(oldStackPos);
2369 msg.addPosition(creature->getPosition());
2370 writeToOutputBuffer(msg);
2371 }
2372 }
2373 else if (canSee(oldPos)) {
2374 sendRemoveTileThing(oldPos, oldStackPos);
2375 }
2376 else if (canSee(creature->getPosition())) {
2377 sendAddCreature(creature, newPos, newStackPos, false);
2378 }
2379}
2380
2381void ProtocolGame::sendAddContainerItem(uint8_t cid, uint16_t slot, const Item* item)
2382{
2383 NetworkMessage msg;
2384 msg.addByte(0x70);
2385 msg.addByte(cid);
2386 msg.add<uint16_t>(slot);
2387 msg.addItem(item);
2388 writeToOutputBuffer(msg);
2389}
2390
2391void ProtocolGame::sendUpdateContainerItem(uint8_t cid, uint16_t slot, const Item* item)
2392{
2393 NetworkMessage msg;
2394 msg.addByte(0x71);
2395 msg.addByte(cid);
2396 msg.add<uint16_t>(slot);
2397 msg.addItem(item);
2398 writeToOutputBuffer(msg);
2399}
2400
2401void ProtocolGame::sendRemoveContainerItem(uint8_t cid, uint16_t slot, const Item* lastItem)
2402{
2403 NetworkMessage msg;
2404 msg.addByte(0x72);
2405 msg.addByte(cid);
2406 msg.add<uint16_t>(slot);
2407 if (lastItem) {
2408 msg.addItem(lastItem);
2409 }
2410 else {
2411 msg.add<uint16_t>(0x00);
2412 }
2413 writeToOutputBuffer(msg);
2414}
2415
2416void ProtocolGame::sendTextWindow(uint32_t windowTextId, Item* item, uint16_t maxlen, bool canWrite)
2417{
2418 NetworkMessage msg;
2419 msg.addByte(0x96);
2420 msg.add<uint32_t>(windowTextId);
2421 msg.addItem(item);
2422
2423 if (canWrite) {
2424 msg.add<uint16_t>(maxlen);
2425 msg.addString(item->getText());
2426 }
2427 else {
2428 const std::string& text = item->getText();
2429 msg.add<uint16_t>(text.size());
2430 msg.addString(text);
2431 }
2432
2433 const std::string& writer = item->getWriter();
2434 if (!writer.empty()) {
2435 msg.addString(writer);
2436 }
2437 else {
2438 msg.add<uint16_t>(0x00);
2439 }
2440
2441 time_t writtenDate = item->getDate();
2442 if (writtenDate != 0) {
2443 msg.addString(formatDateShort(writtenDate));
2444 }
2445 else {
2446 msg.add<uint16_t>(0x00);
2447 }
2448
2449 writeToOutputBuffer(msg);
2450}
2451
2452void ProtocolGame::sendTextWindow(uint32_t windowTextId, uint32_t itemId, const std::string& text)
2453{
2454 NetworkMessage msg;
2455 msg.addByte(0x96);
2456 msg.add<uint32_t>(windowTextId);
2457 msg.addItem(itemId, 1);
2458 msg.add<uint16_t>(text.size());
2459 msg.addString(text);
2460 msg.add<uint16_t>(0x00);
2461 msg.add<uint16_t>(0x00);
2462 writeToOutputBuffer(msg);
2463}
2464
2465void ProtocolGame::sendHouseWindow(uint32_t windowTextId, const std::string& text)
2466{
2467 NetworkMessage msg;
2468 msg.addByte(0x97);
2469 msg.addByte(0x00);
2470 msg.add<uint32_t>(windowTextId);
2471 msg.addString(text);
2472 writeToOutputBuffer(msg);
2473}
2474
2475void ProtocolGame::sendOutfitWindow()
2476{
2477 NetworkMessage msg;
2478 msg.addByte(0xC8);
2479
2480 Outfit_t currentOutfit = player->getDefaultOutfit();
2481 Mount* currentMount = g_game.mounts.getMountByID(player->getCurrentMount());
2482 if (currentMount) {
2483 currentOutfit.lookMount = currentMount->clientId;
2484 }
2485
2486 AddOutfit(msg, currentOutfit);
2487
2488 std::vector<ProtocolOutfit> protocolOutfits;
2489 if (player->isAccessPlayer()) {
2490 static const std::string gamemasterOutfitName = "Game Master";
2491 protocolOutfits.emplace_back(gamemasterOutfitName, 75, 0);
2492
2493 static const std::string gmCustomerSupport = "Customer Support";
2494 protocolOutfits.emplace_back(gmCustomerSupport, 266, 0);
2495
2496 static const std::string communityManager = "Community Manager";
2497 protocolOutfits.emplace_back(communityManager, 302, 0);
2498 }
2499
2500 const auto& outfits = Outfits::getInstance().getOutfits(player->getSex());
2501 protocolOutfits.reserve(outfits.size());
2502 for (const Outfit& outfit : outfits) {
2503 uint8_t addons;
2504 if (!player->getOutfitAddons(outfit, addons)) {
2505 continue;
2506 }
2507
2508 protocolOutfits.emplace_back(outfit.name, outfit.lookType, addons);
2509 if (protocolOutfits.size() == 150) { // Game client doesn't allow more than 100 outfits
2510 break;
2511 }
2512 }
2513
2514 msg.addByte(protocolOutfits.size());
2515 for (const ProtocolOutfit& outfit : protocolOutfits) {
2516 msg.add<uint16_t>(outfit.lookType);
2517 msg.addString(outfit.name);
2518 msg.addByte(outfit.addons);
2519 }
2520
2521 std::vector<const Mount*> mounts;
2522 for (const Mount& mount : g_game.mounts.getMounts()) {
2523 if (player->hasMount(&mount)) {
2524 mounts.push_back(&mount);
2525 }
2526 }
2527
2528 msg.addByte(mounts.size());
2529 for (const Mount* mount : mounts) {
2530 msg.add<uint16_t>(mount->clientId);
2531 msg.addString(mount->name);
2532 }
2533
2534 writeToOutputBuffer(msg);
2535}
2536
2537void ProtocolGame::sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus)
2538{
2539 NetworkMessage msg;
2540 msg.addByte(0xD3);
2541 msg.add<uint32_t>(guid);
2542 msg.addByte(newStatus);
2543 writeToOutputBuffer(msg);
2544}
2545
2546void ProtocolGame::sendSpellCooldown(uint8_t spellId, uint32_t time)
2547{
2548 NetworkMessage msg;
2549 msg.addByte(0xA4);
2550 if (player->getProtocolVersion() < 1120 && spellId >= 170) {
2551 spellId = 150;
2552 }
2553 msg.addByte(spellId);
2554 msg.add<uint32_t>(time);
2555 writeToOutputBuffer(msg);
2556}
2557
2558void ProtocolGame::sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time)
2559{
2560 NetworkMessage msg;
2561 msg.addByte(0xA5);
2562 msg.addByte(groupId);
2563 msg.add<uint32_t>(time);
2564 writeToOutputBuffer(msg);
2565}
2566
2567void ProtocolGame::sendCoinBalanceUpdating(bool updating)
2568{
2569 //by jlcvp
2570 NetworkMessage msg;
2571 msg.addByte(0xF2);
2572 msg.addByte(0x00);
2573 writeToOutputBuffer(msg);
2574
2575 if (updating) {
2576 sendUpdatedCoinBalance();
2577 }
2578}
2579
2580void ProtocolGame::sendUpdatedCoinBalance()
2581{
2582 NetworkMessage msg;
2583 msg.addByte(0xF2); //balanceupdating
2584 msg.addByte(0x01); //this is not the end
2585
2586 msg.addByte(0xDF); //coinBalance opcode
2587 msg.addByte(0x01); //as follows
2588
2589 uint32_t playerCoinBalance = IOAccount::getCoinBalance(player->getAccount());
2590
2591 msg.add<uint32_t>(playerCoinBalance);
2592 msg.add<uint32_t>(playerCoinBalance); //I don't know why this duplicated entry is needed but... better keep it there
2593
2594 writeToOutputBuffer(msg);
2595}
2596
2597void ProtocolGame::sendOpenStore(uint8_t)
2598{
2599 NetworkMessage msg;
2600
2601 msg.addByte(0xFB); //open store
2602 msg.addByte(0x00);
2603
2604 //add categories
2605 uint16_t categoriesCount = g_game.gameStore.getCategoryOffers().size();
2606
2607 msg.add<uint16_t>(categoriesCount);
2608
2609 for (StoreCategory* category : g_game.gameStore.getCategoryOffers())
2610 {
2611 msg.addString(category->name);
2612 msg.addString(category->description);
2613
2614 uint8_t stateByte;
2615 switch (category->state) {
2616 case NORMAL:
2617 stateByte = 0;
2618 break;
2619 case NEW:
2620 stateByte = 1;
2621 break;
2622 case SALE:
2623 stateByte = 2;
2624 break;
2625 case LIMITED_TIME:
2626 stateByte = 3;
2627 break;
2628 default:
2629 stateByte = 0;
2630 break;
2631 }
2632 msg.addByte(stateByte);
2633
2634 msg.addByte((uint8_t)category->icons.size());
2635 for (std::string iconStr : category->icons) {
2636 msg.addString(iconStr);
2637 }
2638 msg.addString(""); //TODO: parentCategory
2639 }
2640
2641 writeToOutputBuffer(msg);
2642 sendCoinBalanceUpdating(true);
2643 addGameTaskTimed(350, &Game::playerShowStoreCategoryOffers, player->getID(), g_game.gameStore.getCategoryOffers().at(0));
2644}
2645
2646void ProtocolGame::sendStoreCategoryOffers(StoreCategory* category)
2647{
2648 NetworkMessage msg;
2649 msg.addByte(0xFC); //StoreOffers
2650 msg.addString(category->name);
2651 msg.add<uint16_t>(category->offers.size());
2652
2653 for (BaseOffer* offer : category->offers) {
2654 msg.add<uint32_t>(offer->id);
2655 std::stringstream offername;
2656 if (offer->type == Offer_t::ITEM || offer->type == Offer_t::STACKABLE_ITEM) {
2657 if (((ItemOffer*)offer)->count > 1) {
2658 offername << ((ItemOffer*)offer)->count << "x ";
2659 }
2660 }
2661 offername << offer->name;
2662
2663 msg.addString(offername.str());
2664 msg.addString(offer->description);
2665
2666 msg.add<uint32_t>(offer->price);
2667 msg.addByte((uint8_t)offer->state);
2668
2669 //outfits
2670 uint8_t disabled = 0;
2671 std::stringstream disabledReason;
2672
2673 disabledReason << "";
2674
2675 if (offer->type == OUTFIT || offer->type == OUTFIT_ADDON) {
2676 OutfitOffer* outfitOffer = (OutfitOffer*)offer;
2677
2678 uint16_t looktype = (player->getSex() == PLAYERSEX_MALE) ? outfitOffer->maleLookType : outfitOffer->femaleLookType;
2679 uint8_t addons = outfitOffer->addonNumber;
2680
2681 if (player->canWear(looktype, addons)) { //player can wear the offer already
2682 disabled = 1;
2683 if (addons == 0) { //addons == 0 //oufit-only offer and player already has it
2684 disabledReason << "You already have this outfit.";
2685 }
2686 else {
2687 disabledReason << "You already have this outfit/addon.";
2688 }
2689 }
2690 else {
2691 if (outfitOffer->type == OUTFIT_ADDON && !player->canWear(looktype, 0)) { //addon offer and player doesnt have the base outfit
2692 disabled = 1;
2693 disabledReason << "You don't have the outfit, you can't buy the addon.";
2694 }
2695 }
2696 }
2697 else if (offer->type == MOUNT) {
2698 MountOffer* mountOffer = (MountOffer*)offer;
2699 Mount* m = g_game.mounts.getMountByID(mountOffer->mountId);
2700 if (player->hasMount(m)) {
2701 disabled = 1;
2702 disabledReason << "You already have this mount.";
2703 }
2704 }
2705 else if (offer->type == PROMOTION) {
2706 if (player->isPromoted() || !player->isPremium()) { //TODO: add support to multiple promotion levels
2707 disabled = 1;
2708 disabledReason << "You can't get this promotion";
2709 }
2710 }
2711
2712 msg.addByte(disabled);
2713
2714 if (disabled) {
2715 msg.addString(disabledReason.str());
2716 }
2717
2718 //add icons
2719 msg.addByte((uint8_t)offer->icons.size());
2720
2721 for (std::string iconName : offer->icons) {
2722 msg.addString(iconName);
2723 }
2724
2725 msg.add<uint16_t>(0);
2726 //TODO: add support to suboffers
2727 }
2728
2729 writeToOutputBuffer(msg);
2730}
2731
2732void ProtocolGame::sendStoreError(GameStoreError_t error, const std::string& message)
2733{
2734 NetworkMessage msg;
2735
2736 msg.addByte(0xE0); //storeError
2737 msg.addByte(error);
2738 msg.addString(message);
2739
2740 writeToOutputBuffer(msg);
2741}
2742
2743void ProtocolGame::sendStorePurchaseSuccessful(const std::string& message, const uint32_t coinBalance)
2744{
2745 NetworkMessage msg;
2746
2747 msg.addByte(0xFE); //CompletePurchase
2748 msg.addByte(0x00);
2749
2750 msg.addString(message);
2751 msg.add<uint32_t>(coinBalance); //dont know why the client needs it duplicated. But ok...
2752 msg.add<uint32_t>(coinBalance);
2753
2754 writeToOutputBuffer(msg);
2755}
2756
2757void ProtocolGame::sendStoreRequestAdditionalInfo(uint32_t offerId, ClientOffer_t clientOfferType)
2758{
2759 NetworkMessage msg;
2760
2761 msg.addByte(0xE1); //RequestPurchaseData
2762 msg.add<uint32_t>(offerId);
2763 msg.addByte(clientOfferType);
2764
2765 writeToOutputBuffer(msg);
2766}
2767
2768void ProtocolGame::sendStoreTrasactionHistory(HistoryStoreOfferList &list, uint32_t page, uint8_t entriesPerPage)
2769{
2770 NetworkMessage msg;
2771 uint32_t isLastPage = (list.size() <= entriesPerPage) ? 0x01 : 0x00;
2772
2773 //TODO: Support multiple pages
2774 isLastPage = 0x01; //FIXME
2775 page = 0x00;
2776 ////////////////////////
2777
2778 msg.addByte(0xFD); //BrowseTransactionHistory
2779 msg.add<uint32_t>(page); //which page
2780 msg.add<uint32_t>(isLastPage); //is the last page? /
2781 msg.addByte((uint8_t)list.size()); //how many elements follows
2782
2783 for (HistoryStoreOffer offer : list) {
2784 msg.add<uint32_t>(offer.time);
2785 msg.addByte(offer.mode);
2786 msg.add<uint32_t>(offer.amount); //FIXME: investigate why it doesn't send the price properly
2787 msg.addString(offer.description);
2788 }
2789
2790 writeToOutputBuffer(msg);
2791}
2792
2793
2794void ProtocolGame::sendModalWindow(const ModalWindow& modalWindow)
2795{
2796 NetworkMessage msg;
2797 msg.addByte(0xFA);
2798
2799 msg.add<uint32_t>(modalWindow.id);
2800 msg.addString(modalWindow.title);
2801 msg.addString(modalWindow.message);
2802
2803 msg.addByte(modalWindow.buttons.size());
2804 for (const auto& it : modalWindow.buttons) {
2805 msg.addString(it.first);
2806 msg.addByte(it.second);
2807 }
2808
2809 msg.addByte(modalWindow.choices.size());
2810 for (const auto& it : modalWindow.choices) {
2811 msg.addString(it.first);
2812 msg.addByte(it.second);
2813 }
2814
2815 msg.addByte(modalWindow.defaultEscapeButton);
2816 msg.addByte(modalWindow.defaultEnterButton);
2817 msg.addByte(modalWindow.priority ? 0x01 : 0x00);
2818
2819 writeToOutputBuffer(msg);
2820}
2821
2822////////////// Add common messages
2823void ProtocolGame::MoveUpCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos)
2824{
2825 if (creature != player) {
2826 return;
2827 }
2828
2829 //floor change up
2830 msg.addByte(0xBE);
2831
2832 //going to surface
2833if (newPos.z == 7) {
2834 int32_t skip = -1;
2835 GetFloorDescription(msg, oldPos.x - Map::maxClientViewportX, oldPos.y - Map::maxClientViewportY, 5, (Map::maxClientViewportX+1)*2, (Map::maxClientViewportY+1)*2, 3, skip); //(floor 7 and 6 already set)
2836 GetFloorDescription(msg, oldPos.x - Map::maxClientViewportX, oldPos.y - Map::maxClientViewportY, 4, (Map::maxClientViewportX+1)*2, (Map::maxClientViewportY+1)*2, 4, skip);
2837 GetFloorDescription(msg, oldPos.x - Map::maxClientViewportX, oldPos.y - Map::maxClientViewportY, 3, (Map::maxClientViewportX+1)*2, (Map::maxClientViewportY+1)*2, 5, skip);
2838 GetFloorDescription(msg, oldPos.x - Map::maxClientViewportX, oldPos.y - Map::maxClientViewportY, 2, (Map::maxClientViewportX+1)*2, (Map::maxClientViewportY+1)*2, 6, skip);
2839 GetFloorDescription(msg, oldPos.x - Map::maxClientViewportX, oldPos.y - Map::maxClientViewportY, 1, (Map::maxClientViewportX+1)*2, (Map::maxClientViewportY+1)*2, 7, skip);
2840 GetFloorDescription(msg, oldPos.x - Map::maxClientViewportX, oldPos.y - Map::maxClientViewportY, 0, (Map::maxClientViewportX+1)*2, (Map::maxClientViewportY+1)*2, 8, skip);
2841
2842 if (skip >= 0) {
2843 msg.addByte(skip);
2844 msg.addByte(0xFF);
2845 }
2846 }
2847 //underground, going one floor up (still underground)
2848else if (newPos.z > 7) {
2849 int32_t skip = -1;
2850 GetFloorDescription(msg, oldPos.x - Map::maxClientViewportX, oldPos.y - Map::maxClientViewportY, oldPos.getZ() - 3, (Map::maxClientViewportX+1)*2, (Map::maxClientViewportY+1)*2, 3, skip);
2851
2852 if (skip >= 0) {
2853 msg.addByte(skip);
2854 msg.addByte(0xFF);
2855 }
2856 }
2857
2858//moving up a floor up makes us out of sync
2859//west
2860msg.addByte(0x68);
2861GetMapDescription(oldPos.x - Map::maxClientViewportX, oldPos.y - (Map::maxClientViewportY-1), newPos.z, 1, (Map::maxClientViewportY+1)*2, msg);
2862//north
2863msg.addByte(0x65);
2864GetMapDescription(oldPos.x - Map::maxClientViewportX, oldPos.y - Map::maxClientViewportY, newPos.z, (Map::maxClientViewportX+1)*2, 1, msg);
2865
2866}
2867
2868void ProtocolGame::MoveDownCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos)
2869{
2870 if (creature != player) {
2871 return;
2872 }
2873
2874 //floor change down
2875 msg.addByte(0xBF);
2876
2877//going from surface to underground
2878if (newPos.z == 8) {
2879 int32_t skip = -1;
2880 GetFloorDescription(msg, oldPos.x - Map::maxClientViewportX, oldPos.y - Map::maxClientViewportY, newPos.z, (Map::maxClientViewportX+1)*2, (Map::maxClientViewportY+1)*2, -1, skip);
2881 GetFloorDescription(msg, oldPos.x - Map::maxClientViewportX, oldPos.y - Map::maxClientViewportY, newPos.z + 1, (Map::maxClientViewportX+1)*2, (Map::maxClientViewportY+1)*2, -2, skip);
2882 GetFloorDescription(msg, oldPos.x - Map::maxClientViewportX, oldPos.y - Map::maxClientViewportY, newPos.z + 2, (Map::maxClientViewportX+1)*2, (Map::maxClientViewportY+1)*2, -3, skip);
2883
2884 if (skip >= 0) {
2885 msg.addByte(skip);
2886 msg.addByte(0xFF);
2887 }
2888 }
2889//going further down
2890else if (newPos.z > oldPos.z && newPos.z > 8 && newPos.z < 14) {
2891 int32_t skip = -1;
2892 GetFloorDescription(msg, oldPos.x - Map::maxClientViewportX, oldPos.y - Map::maxClientViewportY, newPos.z + 2, (Map::maxClientViewportX+1)*2, (Map::maxClientViewportY+1)*2, -3, skip);
2893
2894 if (skip >= 0) {
2895 msg.addByte(skip);
2896 msg.addByte(0xFF);
2897 }
2898 }
2899
2900 //moving down a floor makes us out of sync
2901 //east
2902 msg.addByte(0x66);
2903 GetMapDescription(oldPos.x + (Map::maxClientViewportX+1), oldPos.y - (Map::maxClientViewportY+1), newPos.z, 1, (Map::maxClientViewportY+1)*2, msg);
2904
2905 //south
2906 msg.addByte(0x67);
2907 GetMapDescription(oldPos.x - Map::maxClientViewportX, newPos.y + (Map::maxClientViewportY+1), newPos.z, (Map::maxClientViewportX+1)*2, 1, msg);
2908
2909}
2910
2911void ProtocolGame::AddShopItem(NetworkMessage& msg, const ShopInfo& item)
2912{
2913 const ItemType& it = Item::items[item.itemId];
2914 msg.add<uint16_t>(it.clientId);
2915
2916 if (it.isSplash() || it.isFluidContainer()) {
2917 msg.addByte(serverFluidToClient(item.subType));
2918 }
2919 else {
2920 msg.addByte(0x00);
2921 }
2922
2923 msg.addString(item.realName);
2924 msg.add<uint32_t>(it.weight);
2925 msg.add<uint32_t>(item.buyPrice == 4294967295 ? 0 : item.buyPrice);
2926 msg.add<uint32_t>(item.sellPrice == 4294967295 ? 0 : item.sellPrice);
2927}
2928
2929void ProtocolGame::parseExtendedOpcode(NetworkMessage& msg)
2930{
2931 uint8_t opcode = msg.getByte();
2932 const std::string& buffer = msg.getString();
2933
2934 // process additional opcodes via lua script event
2935 addGameTask(&Game::parsePlayerExtendedOpcode, player->getID(), opcode, buffer);
2936}