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