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