1/**
2 * The Forgotten Server - a free and open-source MMORPG server emulator
3 * Copyright (C) 2019 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 "actions.h"
31#include "ban.h"
32#include "configmanager.h"
33#include "game.h"
34#include "iologindata.h"
35#include "iomarket.h"
36#include "scheduler.h"
37#include "waitlist.h"
38
39extern ConfigManager g_config;
40extern Actions actions;
41extern CreatureEvents *g_creatureEvents;
42extern Chat *g_chat;
43
44void ProtocolGame::release() {
45 // dispatcher thread
46 if (player && player->client == shared_from_this()) {
47 player->client.reset();
48 player->decrementReferenceCounter();
49 player = nullptr;
50 }
51
52 OutputMessagePool::getInstance().removeProtocolFromAutosend(
53 shared_from_this());
54 Protocol::release();
55}
56
57void ProtocolGame::login(const std::string &name, uint32_t accountId,
58 OperatingSystem_t operatingSystem) {
59 // OTCv8 features and extended opcodes
60 if (otclientV8 || operatingSystem >= CLIENTOS_OTCLIENT_LINUX) {
61 if (otclientV8)
62 sendFeatures();
63 NetworkMessage opcodeMessage;
64 opcodeMessage.addByte(0x32);
65 opcodeMessage.addByte(0x00);
66 opcodeMessage.add<uint16_t>(0x00);
67 writeToOutputBuffer(opcodeMessage);
68 }
69
70 // dispatcher thread
71 Player *foundPlayer = g_game.getPlayerByName(name);
72 if (!foundPlayer || g_config.getBoolean(ConfigManager::ALLOW_CLONES)) {
73 player = new Player(getThis());
74 player->setName(name);
75
76 player->incrementReferenceCounter();
77 player->setID();
78
79 if (!IOLoginData::preloadPlayer(player, name)) {
80 disconnectClient("Your character could not be loaded.");
81 return;
82 }
83
84 if (IOBan::isPlayerNamelocked(player->getGUID())) {
85 disconnectClient("Your character has been namelocked.");
86 return;
87 }
88
89 if (g_game.getGameState() == GAME_STATE_CLOSING &&
90 !player->hasFlag(PlayerFlag_CanAlwaysLogin)) {
91 disconnectClient("The game is just going down.\nPlease try again later.");
92 return;
93 }
94
95 if (g_game.getGameState() == GAME_STATE_CLOSED &&
96 !player->hasFlag(PlayerFlag_CanAlwaysLogin)) {
97 disconnectClient("Server is currently closed.\nPlease try again later.");
98 return;
99 }
100
101 if (g_config.getBoolean(ConfigManager::ONE_PLAYER_ON_ACCOUNT) &&
102 player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER &&
103 g_game.getPlayerByAccount(player->getAccount())) {
104 disconnectClient("You may only login with one character\nof your account "
105 "at the same time.");
106 return;
107 }
108
109 if (!player->hasFlag(PlayerFlag_CannotBeBanned)) {
110 BanInfo banInfo;
111 if (IOBan::isAccountBanned(accountId, banInfo)) {
112 if (banInfo.reason.empty()) {
113 banInfo.reason = "(none)";
114 }
115
116 std::ostringstream ss;
117 if (banInfo.expiresAt > 0) {
118 ss << "Your account has been banned until "
119 << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy
120 << ".\n\nReason specified:\n"
121 << banInfo.reason;
122 } else {
123 ss << "Your account has been permanently banned by "
124 << banInfo.bannedBy << ".\n\nReason specified:\n"
125 << banInfo.reason;
126 }
127 disconnectClient(ss.str());
128 return;
129 }
130 }
131
132 std::size_t currentSlot = WaitingList::getInstance().clientLogin(player);
133 if (currentSlot > 0) {
134 uint8_t retryTime = WaitingList::getTime(currentSlot);
135 std::ostringstream ss;
136
137 ss << "Too many players online.\nYou are at place " << currentSlot
138 << " on the waiting list.";
139
140 auto output = OutputMessagePool::getOutputMessage();
141 output->addByte(0x16);
142 output->addString(ss.str());
143 output->addByte(retryTime);
144 send(output);
145 disconnect();
146 return;
147 }
148
149 if (!IOLoginData::loadPlayerById(player, player->getGUID())) {
150 disconnectClient("Your character could not be loaded.");
151 return;
152 }
153
154 player->setOperatingSystem(operatingSystem);
155
156 if (!g_game.placeCreature(player, player->getLoginPosition())) {
157 if (!g_game.placeCreature(player, player->getTemplePosition(), false,
158 true)) {
159 disconnectClient(
160 "Temple position is wrong. Contact the administrator.");
161 return;
162 }
163 }
164
165 if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) {
166 player->registerCreatureEvent("ExtendedOpcode");
167 }
168
169 player->lastIP = player->getIP();
170 player->lastLoginSaved =
171 std::max<time_t>(time(nullptr), player->lastLoginSaved + 1);
172 acceptPackets = true;
173 } else {
174 if (eventConnect != 0 ||
175 !g_config.getBoolean(ConfigManager::REPLACE_KICK_ON_LOGIN)) {
176 // Already trying to connect
177 disconnectClient("You are already logged in.");
178 return;
179 }
180
181 if (foundPlayer->client) {
182 foundPlayer->disconnect();
183 foundPlayer->isConnecting = true;
184
185 eventConnect = g_scheduler.addEvent(createSchedulerTask(
186 1000, std::bind(&ProtocolGame::connect, getThis(),
187 foundPlayer->getID(), operatingSystem)));
188 } else {
189 connect(foundPlayer->getID(), operatingSystem);
190 }
191 }
192 OutputMessagePool::getInstance().addProtocolToAutosend(shared_from_this());
193}
194
195void ProtocolGame::connect(uint32_t playerId,
196 OperatingSystem_t operatingSystem) {
197 eventConnect = 0;
198
199 Player *foundPlayer = g_game.getPlayerByID(playerId);
200 if (!foundPlayer || foundPlayer->client) {
201 disconnectClient("You are already logged in.");
202 return;
203 }
204
205 if (isConnectionExpired()) {
206 // ProtocolGame::release() has been called at this point and the Connection
207 // object no longer exists, so we return to prevent leakage of the Player.
208 return;
209 }
210
211 player = foundPlayer;
212 player->incrementReferenceCounter();
213
214 g_chat->removeUserFromAllChannels(*player);
215 player->clearModalWindows();
216 player->setOperatingSystem(operatingSystem);
217 player->isConnecting = false;
218
219 player->client = getThis();
220 sendAddCreature(player, player->getPosition(), 0, false);
221 player->lastIP = player->getIP();
222 player->lastLoginSaved =
223 std::max<time_t>(time(nullptr), player->lastLoginSaved + 1);
224 acceptPackets = true;
225}
226
227void ProtocolGame::logout(bool displayEffect, bool forced) {
228 // dispatcher thread
229 if (!player) {
230 return;
231 }
232
233 if (!player->isRemoved()) {
234 if (!forced) {
235 if (!player->isAccessPlayer()) {
236 if (player->getTile()->hasFlag(TILESTATE_NOLOGOUT)) {
237 player->sendCancelMessage(RETURNVALUE_YOUCANNOTLOGOUTHERE);
238 return;
239 }
240
241 if (!player->getTile()->hasFlag(TILESTATE_PROTECTIONZONE) &&
242 player->hasCondition(CONDITION_INFIGHT)) {
243 player->sendCancelMessage(RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT);
244 return;
245 }
246 }
247
248 // scripting event - onLogout
249 if (!g_creatureEvents->playerLogout(player)) {
250 // Let the script handle the error message
251 return;
252 }
253 }
254
255 if (displayEffect && player->getHealth() > 0) {
256 g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
257 }
258 }
259
260 disconnect();
261
262 g_game.removeCreature(player);
263}
264
265void ProtocolGame::onRecvFirstMessage(NetworkMessage &msg) {
266 if (g_game.getGameState() == GAME_STATE_SHUTDOWN) {
267 disconnect();
268 return;
269 }
270
271 OperatingSystem_t operatingSystem =
272 static_cast<OperatingSystem_t>(msg.get<uint16_t>());
273 version = msg.get<uint16_t>();
274
275 msg.skipBytes(7); // U32 client version, U8 client type, U16 dat revision
276
277 if (!Protocol::RSA_decrypt(msg)) {
278 disconnect();
279 return;
280 }
281
282 xtea::key key;
283 key[0] = msg.get<uint32_t>();
284 key[1] = msg.get<uint32_t>();
285 key[2] = msg.get<uint32_t>();
286 key[3] = msg.get<uint32_t>();
287 enableXTEAEncryption();
288 setXTEAKey(std::move(key));
289
290 msg.skipBytes(1); // gamemaster flag
291
292 std::string sessionKey = msg.getString();
293
294 auto sessionArgs = explodeString(sessionKey, "\n", 4);
295 if (sessionArgs.size() != 4) {
296 disconnect();
297 return;
298 }
299
300 std::string &accountName = sessionArgs[0];
301 std::string &password = sessionArgs[1];
302 std::string &token = sessionArgs[2];
303 uint32_t tokenTime = 0;
304 try {
305 tokenTime = std::stoul(sessionArgs[3]);
306 } catch (const std::invalid_argument &) {
307 disconnectClient("Malformed token packet.");
308 return;
309 } catch (const std::out_of_range &) {
310 disconnectClient("Token time is too long.");
311 return;
312 }
313
314 if (accountName.empty()) {
315 disconnectClient("You must enter your account name.");
316 return;
317 }
318
319 std::string characterName = msg.getString();
320
321 uint32_t timeStamp = msg.get<uint32_t>();
322 uint8_t randNumber = msg.getByte();
323 if (challengeTimestamp != timeStamp || challengeRandom != randNumber) {
324 disconnect();
325 return;
326 }
327
328 // OTCv8 version detection
329 uint16_t otcV8StringLength = msg.get<uint16_t>();
330 if (otcV8StringLength == 5 && msg.getString(5) == "OTCv8") {
331 otclientV8 = msg.get<uint16_t>(); // 253, 260, 261, ...
332 }
333
334 if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) {
335 std::ostringstream ss;
336 ss << "Only clients with protocol " << CLIENT_VERSION_STR << " allowed!";
337 disconnectClient(ss.str());
338 return;
339 }
340
341 if (g_game.getGameState() == GAME_STATE_STARTUP) {
342 disconnectClient("Gameworld is starting up. Please wait.");
343 return;
344 }
345
346 if (g_game.getGameState() == GAME_STATE_MAINTAIN) {
347 disconnectClient(
348 "Gameworld is under maintenance. Please re-connect in a while.");
349 return;
350 }
351
352 BanInfo banInfo;
353 if (IOBan::isIpBanned(getIP(), banInfo)) {
354 if (banInfo.reason.empty()) {
355 banInfo.reason = "(none)";
356 }
357
358 std::ostringstream ss;
359 ss << "Your IP has been banned until " << formatDateShort(banInfo.expiresAt)
360 << " by " << banInfo.bannedBy << ".\n\nReason specified:\n"
361 << banInfo.reason;
362 disconnectClient(ss.str());
363 return;
364 }
365
366 uint32_t accountId = IOLoginData::gameworldAuthentication(
367 accountName, password, characterName, token, tokenTime);
368 if (accountId == 0) {
369 disconnectClient("Account name or password is not correct.");
370 return;
371 }
372
373 g_dispatcher.addTask(
374 createTask(std::bind(&ProtocolGame::login, getThis(), characterName,
375 accountId, operatingSystem)));
376}
377
378void ProtocolGame::onConnect() {
379 auto output = OutputMessagePool::getOutputMessage();
380 static std::random_device rd;
381 static std::ranlux24 generator(rd());
382 static std::uniform_int_distribution<uint16_t> randNumber(0x00, 0xFF);
383
384 // Skip checksum
385 output->skipBytes(sizeof(uint32_t));
386
387 // Packet length & type
388 output->add<uint16_t>(0x0006);
389 output->addByte(0x1F);
390
391 // Add timestamp & random number
392 challengeTimestamp = static_cast<uint32_t>(time(nullptr));
393 output->add<uint32_t>(challengeTimestamp);
394
395 challengeRandom = randNumber(generator);
396 output->addByte(challengeRandom);
397
398 // Go back and write checksum
399 output->skipBytes(-12);
400 output->add<uint32_t>(
401 adlerChecksum(output->getOutputBuffer() + sizeof(uint32_t), 8));
402
403 send(output);
404}
405
406void ProtocolGame::disconnectClient(const std::string &message) const {
407 auto output = OutputMessagePool::getOutputMessage();
408 output->addByte(0x14);
409 output->addString(message);
410 send(output);
411 disconnect();
412}
413
414void ProtocolGame::writeToOutputBuffer(const NetworkMessage &msg) {
415 auto out = getOutputBuffer(msg.getLength());
416 out->append(msg);
417}
418
419void ProtocolGame::parsePacket(NetworkMessage &msg) {
420 if (!acceptPackets || g_game.getGameState() == GAME_STATE_SHUTDOWN ||
421 msg.getLength() <= 0) {
422 return;
423 }
424
425 uint8_t recvbyte = msg.getByte();
426
427 if (!player) {
428 if (recvbyte == 0x0F) {
429 disconnect();
430 }
431
432 return;
433 }
434
435 // a dead player can not performs actions
436 if (player->isRemoved() || player->getHealth() <= 0) {
437 if (recvbyte == 0x0F) {
438 disconnect();
439 return;
440 }
441
442 if (recvbyte != 0x14) {
443 return;
444 }
445 }
446
447 switch (recvbyte) {
448 case 0x14:
449 g_dispatcher.addTask(
450 createTask(std::bind(&ProtocolGame::logout, getThis(), true, false)));
451 break;
452 case 0x1D:
453 addGameTask(&Game::playerReceivePingBack, player->getID());
454 break;
455 case 0x1E:
456 addGameTask(&Game::playerReceivePing, player->getID());
457 break;
458 case 0x32:
459 parseExtendedOpcode(msg);
460 break; // otclient extended opcode
461 case 0x40:
462 parseNewPing(msg);
463 break;
464 case 0x42:
465 parseChangeAwareRange(msg);
466 break;
467 case 0x45:
468 parseNewWalking(msg);
469 break;
470 case 0x64:
471 parseAutoWalk(msg);
472 break;
473 case 0x65:
474 addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTH);
475 break;
476 case 0x66:
477 addGameTask(&Game::playerMove, player->getID(), DIRECTION_EAST);
478 break;
479 case 0x67:
480 addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTH);
481 break;
482 case 0x68:
483 addGameTask(&Game::playerMove, player->getID(), DIRECTION_WEST);
484 break;
485 case 0x69:
486 addGameTask(&Game::playerStopAutoWalk, player->getID());
487 break;
488 case 0x6A:
489 addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTHEAST);
490 break;
491 case 0x6B:
492 addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTHEAST);
493 break;
494 case 0x6C:
495 addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTHWEST);
496 break;
497 case 0x6D:
498 addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTHWEST);
499 break;
500 case 0x6F:
501 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn,
502 player->getID(), DIRECTION_NORTH);
503 break;
504 case 0x70:
505 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn,
506 player->getID(), DIRECTION_EAST);
507 break;
508 case 0x71:
509 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn,
510 player->getID(), DIRECTION_SOUTH);
511 break;
512 case 0x72:
513 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn,
514 player->getID(), DIRECTION_WEST);
515 break;
516 case 0x77:
517 parseEquipObject(msg);
518 break;
519 case 0x78:
520 parseThrow(msg);
521 break;
522 case 0x79:
523 parseLookInShop(msg);
524 break;
525 case 0x7A:
526 parsePlayerPurchase(msg);
527 break;
528 case 0x7B:
529 parsePlayerSale(msg);
530 break;
531 case 0x7C:
532 addGameTask(&Game::playerCloseShop, player->getID());
533 break;
534 case 0x7D:
535 parseRequestTrade(msg);
536 break;
537 case 0x7E:
538 parseLookInTrade(msg);
539 break;
540 case 0x7F:
541 addGameTask(&Game::playerAcceptTrade, player->getID());
542 break;
543 case 0x80:
544 addGameTask(&Game::playerCloseTrade, player->getID());
545 break;
546 case 0x82:
547 parseUseItem(msg);
548 break;
549 case 0x83:
550 parseUseItemEx(msg);
551 break;
552 case 0x84:
553 parseUseWithCreature(msg);
554 break;
555 case 0x85:
556 parseRotateItem(msg);
557 break;
558 case 0x87:
559 parseCloseContainer(msg);
560 break;
561 case 0x88:
562 parseUpArrowContainer(msg);
563 break;
564 case 0x89:
565 parseTextWindow(msg);
566 break;
567 case 0x8A:
568 parseHouseWindow(msg);
569 break;
570 case 0x8B:
571 parseWrapItem(msg);
572 break;
573 case 0x8C:
574 parseLookAt(msg);
575 break;
576 case 0x8D:
577 parseLookInBattleList(msg);
578 break;
579 case 0x8E: /* join aggression */
580 break;
581 case 0x96:
582 parseSay(msg);
583 break;
584 case 0x97:
585 addGameTask(&Game::playerRequestChannels, player->getID());
586 break;
587 case 0x98:
588 parseOpenChannel(msg);
589 break;
590 case 0x99:
591 parseCloseChannel(msg);
592 break;
593 case 0x9A:
594 parseOpenPrivateChannel(msg);
595 break;
596 case 0x9E:
597 addGameTask(&Game::playerCloseNpcChannel, player->getID());
598 break;
599 case 0xA0:
600 parseFightModes(msg);
601 break;
602 case 0xA1:
603 parseAttack(msg);
604 break;
605 case 0xA2:
606 parseFollow(msg);
607 break;
608 case 0xA3:
609 parseInviteToParty(msg);
610 break;
611 case 0xA4:
612 parseJoinParty(msg);
613 break;
614 case 0xA5:
615 parseRevokePartyInvite(msg);
616 break;
617 case 0xA6:
618 parsePassPartyLeadership(msg);
619 break;
620 case 0xA7:
621 addGameTask(&Game::playerLeaveParty, player->getID());
622 break;
623 case 0xA8:
624 parseEnableSharedPartyExperience(msg);
625 break;
626 case 0xAA:
627 addGameTask(&Game::playerCreatePrivateChannel, player->getID());
628 break;
629 case 0xAB:
630 parseChannelInvite(msg);
631 break;
632 case 0xAC:
633 parseChannelExclude(msg);
634 break;
635 case 0xBE:
636 addGameTask(&Game::playerCancelAttackAndFollow, player->getID());
637 break;
638 case 0xC9: /* update tile */
639 break;
640 case 0xCA:
641 parseUpdateContainer(msg);
642 break;
643 case 0xCB:
644 parseBrowseField(msg);
645 break;
646 case 0xCC:
647 parseSeekInContainer(msg);
648 break;
649 case 0xD2:
650 addGameTask(&Game::playerRequestOutfit, player->getID());
651 break;
652 case 0xD3:
653 parseSetOutfit(msg);
654 break;
655 case 0xD4:
656 parseToggleMount(msg);
657 break;
658 case 0xDC:
659 parseAddVip(msg);
660 break;
661 case 0xDD:
662 parseRemoveVip(msg);
663 break;
664 case 0xDE:
665 parseEditVip(msg);
666 break;
667 case 0xE6:
668 parseBugReport(msg);
669 break;
670 case 0xE7: /* thank you */
671 break;
672 case 0xE8:
673 parseDebugAssert(msg);
674 break;
675 case 0xF0:
676 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerShowQuestLog,
677 player->getID());
678 break;
679 case 0xF1:
680 parseQuestLine(msg);
681 break;
682 case 0xF2:
683 parseRuleViolationReport(msg);
684 break;
685 case 0xF3: /* get object info */
686 break;
687 case 0xF4:
688 parseMarketLeave();
689 break;
690 case 0xF5:
691 parseMarketBrowse(msg);
692 break;
693 case 0xF6:
694 parseMarketCreateOffer(msg);
695 break;
696 case 0xF7:
697 parseMarketCancelOffer(msg);
698 break;
699 case 0xF8:
700 parseMarketAcceptOffer(msg);
701 break;
702 case 0xF9:
703 parseModalWindowAnswer(msg);
704 break;
705
706 default:
707 // std::cout << "Player: " << player->getName() << " sent an unknown packet
708 // header: 0x" << std::hex << static_cast<uint16_t>(recvbyte) << std::dec <<
709 // "!" << std::endl;
710 break;
711 }
712
713 if (msg.isOverrun()) {
714 disconnect();
715 }
716}
717
718void ProtocolGame::GetTileDescription(const Tile *tile, NetworkMessage &msg) {
719 Item *ground = tile->getGround();
720 if (otclientV8) { // OTCLIENTV8 new walking
721 uint16_t groundSpeed = 150;
722 // I've noticed that sometimes ground speed was incorrect in otclient,
723 // so server just sends 100% correct ground speed
724 bool isBlocking = false;
725 // if isBlocking is true, next prewalking from this tile will be impossible
726 // till server confirmation/rejection, used for teleport or stairs
727 if (ground) {
728 groundSpeed = (uint16_t)Item::items[ground->getID()].speed;
729 if (groundSpeed == 0) {
730 groundSpeed = 150;
731 }
732 // you can uncomment to make walk animation slower when going into stairs,
733 // looks cool floor change speed is the same, only animation is slower
734 // if(tile->hasFlag(TILESTATE_FLOORCHANGE))
735 // groundSpeed *= 3;
736 if (tile->hasFlag(TILESTATE_FLOORCHANGE) ||
737 tile->hasFlag(TILESTATE_TELEPORT))
738 isBlocking = true;
739 }
740 msg.add<uint16_t>(groundSpeed);
741 msg.add<uint8_t>(isBlocking ? 1 : 0);
742 } else {
743 msg.add<uint16_t>(0x00); // environmental effects
744 }
745
746 int32_t count;
747 if (ground) {
748 msg.addItem(ground);
749 count = 1;
750 } else {
751 count = 0;
752 }
753
754 const TileItemVector *items = tile->getItemList();
755 if (items) {
756 for (auto it = items->getBeginTopItem(), end = items->getEndTopItem();
757 it != end; ++it) {
758 msg.addItem(*it);
759
760 count++;
761 if (count == 9 && tile->getPosition() == player->getPosition() &&
762 !otclientV8) {
763 break;
764 } else if (count == 10) {
765 return;
766 }
767 }
768 }
769
770 const CreatureVector *creatures = tile->getCreatures();
771 if (creatures) {
772 bool playerAdded = false;
773 for (const Creature *creature : boost::adaptors::reverse(*creatures)) {
774 if (!player->canSeeCreature(creature)) {
775 continue;
776 }
777
778 if (tile->getPosition() == player->getPosition() && count == 9 &&
779 !playerAdded && !otclientV8) {
780 creature = player;
781 }
782
783 if (creature->getID() == player->getID()) {
784 playerAdded = true;
785 }
786
787 bool known;
788 uint32_t removedKnown;
789 checkCreatureAsKnown(creature->getID(), known, removedKnown);
790 AddCreature(msg, creature, known, removedKnown);
791
792 if (++count == 10) {
793 return;
794 }
795 }
796 }
797
798 if (items) {
799 for (auto it = items->getBeginDownItem(), end = items->getEndDownItem();
800 it != end; ++it) {
801 msg.addItem(*it);
802
803 if (++count == 10) {
804 return;
805 }
806 }
807 }
808}
809
810void ProtocolGame::GetMapDescription(int32_t x, int32_t y, int32_t z,
811 int32_t width, int32_t height,
812 NetworkMessage &msg) {
813 int32_t skip = -1;
814 int32_t startz, endz, zstep;
815
816 if (z > 7) {
817 startz = z - 5;
818 endz = std::min<int32_t>(MAP_MAX_LAYERS - 1, z + 5);
819 zstep = 1;
820 } else {
821 // FIX: send ALL floors (15->0) for surface players so they can see
822 // the entire underground map through holes, cave entrances, etc.
823 startz = MAP_MAX_LAYERS - 1; // = 15
824 endz = 0;
825 zstep = -1;
826 }
827
828 for (int32_t nz = startz; nz != endz + zstep; nz += zstep) {
829 GetFloorDescription(msg, x, y, nz, width, height, z - nz, skip);
830 }
831
832 if (skip >= 0) {
833 msg.addByte(skip);
834 msg.addByte(0xFF);
835 }
836}
837
838void ProtocolGame::GetFloorDescription(NetworkMessage &msg, int32_t x,
839 int32_t y, int32_t z, int32_t width,
840 int32_t height, int32_t offset,
841 int32_t &skip) {
842 for (int32_t nx = 0; nx < width; nx++) {
843 for (int32_t ny = 0; ny < height; ny++) {
844 Tile *tile = g_game.map.getTile(x + nx + offset, y + ny + offset, z);
845 if (tile) {
846 if (skip >= 0) {
847 msg.addByte(skip);
848 msg.addByte(0xFF);
849 }
850
851 skip = 0;
852 GetTileDescription(tile, msg);
853 } else if (skip == 0xFE) {
854 msg.addByte(0xFF);
855 msg.addByte(0xFF);
856 skip = -1;
857 } else {
858 ++skip;
859 }
860 }
861 }
862}
863
864void ProtocolGame::checkCreatureAsKnown(uint32_t id, bool &known,
865 uint32_t &removedKnown) {
866 auto result = knownCreatureSet.insert(id);
867 if (!result.second) {
868 known = true;
869 return;
870 }
871
872 known = false;
873
874 if (knownCreatureSet.size() > 1300) {
875 // Look for a creature to remove
876 for (auto it = knownCreatureSet.begin(), end = knownCreatureSet.end();
877 it != end; ++it) {
878 Creature *creature = g_game.getCreatureByID(*it);
879 if (!canSee(creature)) {
880 removedKnown = *it;
881 knownCreatureSet.erase(it);
882 return;
883 }
884 }
885
886 // Bad situation. Let's just remove anyone.
887 auto it = knownCreatureSet.begin();
888 if (*it == id) {
889 ++it;
890 }
891
892 removedKnown = *it;
893 knownCreatureSet.erase(it);
894 } else {
895 removedKnown = 0;
896 }
897}
898
899bool ProtocolGame::canSee(const Creature *c) const {
900 if (!c || !player || c->isRemoved()) {
901 return false;
902 }
903
904 if (!player->canSeeCreature(c)) {
905 return false;
906 }
907
908 return canSee(c->getPosition());
909}
910
911bool ProtocolGame::canSee(const Position &pos) const {
912 return canSee(pos.x, pos.y, pos.z);
913}
914
915bool ProtocolGame::canSee(int32_t x, int32_t y, int32_t z) const {
916 if (!player) {
917 return false;
918 }
919
920 const Position &myPos = player->getPosition();
921 if (myPos.z <= 7) {
922 // we are on ground level or above (7 -> 0)
923 // FIX: surface players see ALL floors 0-15 (no restriction).
924 // z is already capped at MAP_MAX_LAYERS-1 by position validation,
925 // so no additional check needed here.
926 } else {
927 // we are underground (8 -> 15)
928 // view is +/- 5 from the floor we stand on
929 if (std::abs(myPos.getZ() - z) > 5) {
930 return false;
931 }
932 }
933
934 // negative offset means that the action taken place is on a lower floor than
935 // ourself
936 int32_t offsetz = myPos.getZ() - z;
937 if ((x >= myPos.getX() - awareRange.left() + offsetz) &&
938 (x <= myPos.getX() + awareRange.right() + offsetz) &&
939 (y >= myPos.getY() - awareRange.top() + offsetz) &&
940 (y <= myPos.getY() + awareRange.bottom() + offsetz)) {
941 return true;
942 }
943 return false;
944}
945
946// Parse methods
947void ProtocolGame::parseChannelInvite(NetworkMessage &msg) {
948 const std::string name = msg.getString();
949 addGameTask(&Game::playerChannelInvite, player->getID(), name);
950}
951
952void ProtocolGame::parseChannelExclude(NetworkMessage &msg) {
953 const std::string name = msg.getString();
954 addGameTask(&Game::playerChannelExclude, player->getID(), name);
955}
956
957void ProtocolGame::parseOpenChannel(NetworkMessage &msg) {
958 uint16_t channelId = msg.get<uint16_t>();
959 addGameTask(&Game::playerOpenChannel, player->getID(), channelId);
960}
961
962void ProtocolGame::parseCloseChannel(NetworkMessage &msg) {
963 uint16_t channelId = msg.get<uint16_t>();
964 addGameTask(&Game::playerCloseChannel, player->getID(), channelId);
965}
966
967void ProtocolGame::parseOpenPrivateChannel(NetworkMessage &msg) {
968 const std::string receiver = msg.getString();
969 addGameTask(&Game::playerOpenPrivateChannel, player->getID(), receiver);
970}
971
972void ProtocolGame::parseAutoWalk(NetworkMessage &msg) {
973 uint8_t numdirs = msg.getByte();
974 if (numdirs == 0 ||
975 (msg.getBufferPosition() + numdirs) != (msg.getLength() + 8)) {
976 return;
977 }
978
979 msg.skipBytes(numdirs);
980
981 std::list<Direction> path;
982 for (uint8_t i = 0; i < numdirs; ++i) {
983 uint8_t rawdir = msg.getPreviousByte();
984 switch (rawdir) {
985 case 1:
986 path.push_front(DIRECTION_EAST);
987 break;
988 case 2:
989 path.push_front(DIRECTION_NORTHEAST);
990 break;
991 case 3:
992 path.push_front(DIRECTION_NORTH);
993 break;
994 case 4:
995 path.push_front(DIRECTION_NORTHWEST);
996 break;
997 case 5:
998 path.push_front(DIRECTION_WEST);
999 break;
1000 case 6:
1001 path.push_front(DIRECTION_SOUTHWEST);
1002 break;
1003 case 7:
1004 path.push_front(DIRECTION_SOUTH);
1005 break;
1006 case 8:
1007 path.push_front(DIRECTION_SOUTHEAST);
1008 break;
1009 default:
1010 break;
1011 }
1012 }
1013
1014 if (path.empty()) {
1015 return;
1016 }
1017
1018 addGameTask(&Game::playerAutoWalk, player->getID(), path);
1019}
1020
1021void ProtocolGame::parseSetOutfit(NetworkMessage &msg) {
1022 Outfit_t newOutfit;
1023 newOutfit.lookType = msg.get<uint16_t>();
1024 newOutfit.lookHead = msg.getByte();
1025 newOutfit.lookBody = msg.getByte();
1026 newOutfit.lookLegs = msg.getByte();
1027 newOutfit.lookFeet = msg.getByte();
1028 newOutfit.lookAddons = msg.getByte();
1029 newOutfit.lookMount = msg.get<uint16_t>();
1030 newOutfit.lookWings = otclientV8 ? msg.get<uint16_t>() : 0;
1031 newOutfit.lookAura = otclientV8 ? msg.get<uint16_t>() : 0;
1032 std::string shaderName = otclientV8 ? msg.getString() : "";
1033 Shader *shader = g_game.shaders.getShaderByName(shaderName);
1034 newOutfit.lookShader = shader ? shader->id : 0;
1035 newOutfit.lookRuneEffect = otclientV8 ? msg.get<uint16_t>() : 0;
1036 addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit);
1037}
1038
1039void ProtocolGame::parseToggleMount(NetworkMessage &msg) {
1040 int mount = msg.get<int8_t>();
1041 int wings = -1, aura = -1, shader = -1, runeeffect = -1;
1042 if (otclientV8 >= 254) {
1043 wings = msg.get<int8_t>();
1044 aura = msg.get<int8_t>();
1045 shader = msg.get<int8_t>();
1046 runeeffect = msg.get<int8_t>();
1047 }
1048 addGameTask(&Game::playerToggleOutfitExtension, player->getID(), mount, wings,
1049 aura, shader, runeeffect);
1050}
1051
1052void ProtocolGame::parseUseItem(NetworkMessage &msg) {
1053 Position pos = msg.getPosition();
1054 uint16_t spriteId = msg.get<uint16_t>();
1055 uint8_t stackpos = msg.getByte();
1056 uint8_t index = msg.getByte();
1057 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseItem,
1058 player->getID(), pos, stackpos, index, spriteId);
1059}
1060
1061void ProtocolGame::parseUseItemEx(NetworkMessage &msg) {
1062 Position fromPos = msg.getPosition();
1063 uint16_t fromSpriteId = msg.get<uint16_t>();
1064 uint8_t fromStackPos = msg.getByte();
1065 Position toPos = msg.getPosition();
1066 uint16_t toSpriteId = msg.get<uint16_t>();
1067 uint8_t toStackPos = msg.getByte();
1068 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseItemEx,
1069 player->getID(), fromPos, fromStackPos, fromSpriteId, toPos,
1070 toStackPos, toSpriteId);
1071}
1072
1073void ProtocolGame::parseUseWithCreature(NetworkMessage &msg) {
1074 Position fromPos = msg.getPosition();
1075 uint16_t spriteId = msg.get<uint16_t>();
1076 uint8_t fromStackPos = msg.getByte();
1077 uint32_t creatureId = msg.get<uint32_t>();
1078 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseWithCreature,
1079 player->getID(), fromPos, fromStackPos, creatureId,
1080 spriteId);
1081}
1082
1083void ProtocolGame::parseCloseContainer(NetworkMessage &msg) {
1084 uint8_t cid = msg.getByte();
1085 addGameTask(&Game::playerCloseContainer, player->getID(), cid);
1086}
1087
1088void ProtocolGame::parseUpArrowContainer(NetworkMessage &msg) {
1089 uint8_t cid = msg.getByte();
1090 addGameTask(&Game::playerMoveUpContainer, player->getID(), cid);
1091}
1092
1093void ProtocolGame::parseUpdateContainer(NetworkMessage &msg) {
1094 uint8_t cid = msg.getByte();
1095 addGameTask(&Game::playerUpdateContainer, player->getID(), cid);
1096}
1097
1098void ProtocolGame::parseThrow(NetworkMessage &msg) {
1099 Position fromPos = msg.getPosition();
1100 uint16_t spriteId = msg.get<uint16_t>();
1101 uint8_t fromStackpos = msg.getByte();
1102 Position toPos = msg.getPosition();
1103 uint8_t count = msg.getByte();
1104
1105 if (toPos != fromPos) {
1106 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerMoveThing,
1107 player->getID(), fromPos, spriteId, fromStackpos, toPos,
1108 count);
1109 }
1110}
1111
1112void ProtocolGame::parseLookAt(NetworkMessage &msg) {
1113 Position pos = msg.getPosition();
1114 msg.skipBytes(2); // spriteId
1115 uint8_t stackpos = msg.getByte();
1116 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookAt,
1117 player->getID(), pos, stackpos);
1118}
1119
1120void ProtocolGame::parseLookInBattleList(NetworkMessage &msg) {
1121 uint32_t creatureId = msg.get<uint32_t>();
1122 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInBattleList,
1123 player->getID(), creatureId);
1124}
1125
1126void ProtocolGame::parseSay(NetworkMessage &msg) {
1127 std::string receiver;
1128 uint16_t channelId;
1129
1130 SpeakClasses type = static_cast<SpeakClasses>(msg.getByte());
1131 switch (type) {
1132 case TALKTYPE_PRIVATE_TO:
1133 case TALKTYPE_PRIVATE_RED_TO:
1134 receiver = msg.getString();
1135 channelId = 0;
1136 break;
1137
1138 case TALKTYPE_CHANNEL_Y:
1139 case TALKTYPE_CHANNEL_R1:
1140 channelId = msg.get<uint16_t>();
1141 break;
1142
1143 default:
1144 channelId = 0;
1145 break;
1146 }
1147
1148 const std::string text = msg.getString();
1149 if (text.length() > 255) {
1150 return;
1151 }
1152
1153 addGameTask(&Game::playerSay, player->getID(), channelId, type, receiver,
1154 text);
1155}
1156
1157void ProtocolGame::parseFightModes(NetworkMessage &msg) {
1158 uint8_t rawFightMode =
1159 msg.getByte(); // 1 - offensive, 2 - balanced, 3 - defensive
1160 uint8_t rawChaseMode =
1161 msg.getByte(); // 0 - stand while fightning, 1 - chase opponent
1162 uint8_t rawSecureMode =
1163 msg.getByte(); // 0 - can't attack unmarked, 1 - can attack unmarked
1164 // uint8_t rawPvpMode = msg.getByte(); // pvp mode introduced in 10.0
1165
1166 fightMode_t fightMode;
1167 if (rawFightMode == 1) {
1168 fightMode = FIGHTMODE_ATTACK;
1169 } else if (rawFightMode == 2) {
1170 fightMode = FIGHTMODE_BALANCED;
1171 } else {
1172 fightMode = FIGHTMODE_DEFENSE;
1173 }
1174
1175 addGameTask(&Game::playerSetFightModes, player->getID(), fightMode,
1176 rawChaseMode != 0, rawSecureMode != 0);
1177}
1178
1179void ProtocolGame::parseAttack(NetworkMessage &msg) {
1180 uint32_t creatureId = msg.get<uint32_t>();
1181 // msg.get<uint32_t>(); creatureId (same as above)
1182 addGameTask(&Game::playerSetAttackedCreature, player->getID(), creatureId);
1183}
1184
1185void ProtocolGame::parseFollow(NetworkMessage &msg) {
1186 uint32_t creatureId = msg.get<uint32_t>();
1187 // msg.get<uint32_t>(); creatureId (same as above)
1188 addGameTask(&Game::playerFollowCreature, player->getID(), creatureId);
1189}
1190
1191void ProtocolGame::parseEquipObject(NetworkMessage &msg) {
1192 uint16_t spriteId = msg.get<uint16_t>();
1193 // msg.get<uint8_t>();
1194
1195 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerEquipItem,
1196 player->getID(), spriteId);
1197}
1198
1199void ProtocolGame::parseTextWindow(NetworkMessage &msg) {
1200 uint32_t windowTextId = msg.get<uint32_t>();
1201 const std::string newText = msg.getString();
1202 addGameTask(&Game::playerWriteItem, player->getID(), windowTextId, newText);
1203}
1204
1205void ProtocolGame::parseHouseWindow(NetworkMessage &msg) {
1206 uint8_t doorId = msg.getByte();
1207 uint32_t id = msg.get<uint32_t>();
1208 const std::string text = msg.getString();
1209 addGameTask(&Game::playerUpdateHouseWindow, player->getID(), doorId, id,
1210 text);
1211}
1212
1213void ProtocolGame::parseWrapItem(NetworkMessage &msg) {
1214 Position pos = msg.getPosition();
1215 uint16_t spriteId = msg.get<uint16_t>();
1216 uint8_t stackpos = msg.getByte();
1217 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerWrapItem,
1218 player->getID(), pos, stackpos, spriteId);
1219}
1220
1221void ProtocolGame::parseLookInShop(NetworkMessage &msg) {
1222 uint16_t id = msg.get<uint16_t>();
1223 uint8_t count = msg.getByte();
1224 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInShop,
1225 player->getID(), id, count);
1226}
1227
1228void ProtocolGame::parsePlayerPurchase(NetworkMessage &msg) {
1229 uint16_t id = msg.get<uint16_t>();
1230 uint8_t count = msg.getByte();
1231 uint8_t amount = msg.getByte();
1232 bool ignoreCap = msg.getByte() != 0;
1233 bool inBackpacks = msg.getByte() != 0;
1234 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerPurchaseItem,
1235 player->getID(), id, count, amount, ignoreCap, inBackpacks);
1236}
1237
1238void ProtocolGame::parsePlayerSale(NetworkMessage &msg) {
1239 uint16_t id = msg.get<uint16_t>();
1240 uint8_t count = msg.getByte();
1241 uint8_t amount = msg.getByte();
1242 bool ignoreEquipped = msg.getByte() != 0;
1243 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerSellItem,
1244 player->getID(), id, count, amount, ignoreEquipped);
1245}
1246
1247void ProtocolGame::parseRequestTrade(NetworkMessage &msg) {
1248 Position pos = msg.getPosition();
1249 uint16_t spriteId = msg.get<uint16_t>();
1250 uint8_t stackpos = msg.getByte();
1251 uint32_t playerId = msg.get<uint32_t>();
1252 addGameTask(&Game::playerRequestTrade, player->getID(), pos, stackpos,
1253 playerId, spriteId);
1254}
1255
1256void ProtocolGame::parseLookInTrade(NetworkMessage &msg) {
1257 bool counterOffer = (msg.getByte() == 0x01);
1258 uint8_t index = msg.getByte();
1259 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInTrade,
1260 player->getID(), counterOffer, index);
1261}
1262
1263void ProtocolGame::parseAddVip(NetworkMessage &msg) {
1264 const std::string name = msg.getString();
1265 addGameTask(&Game::playerRequestAddVip, player->getID(), name);
1266}
1267
1268void ProtocolGame::parseRemoveVip(NetworkMessage &msg) {
1269 uint32_t guid = msg.get<uint32_t>();
1270 addGameTask(&Game::playerRequestRemoveVip, player->getID(), guid);
1271}
1272
1273void ProtocolGame::parseEditVip(NetworkMessage &msg) {
1274 uint32_t guid = msg.get<uint32_t>();
1275 const std::string description = msg.getString();
1276 uint32_t icon =
1277 std::min<uint32_t>(10, msg.get<uint32_t>()); // 10 is max icon in 9.63
1278 bool notify = msg.getByte() != 0;
1279 addGameTask(&Game::playerRequestEditVip, player->getID(), guid, description,
1280 icon, notify);
1281}
1282
1283void ProtocolGame::parseRotateItem(NetworkMessage &msg) {
1284 Position pos = msg.getPosition();
1285 uint16_t spriteId = msg.get<uint16_t>();
1286 uint8_t stackpos = msg.getByte();
1287 addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerRotateItem,
1288 player->getID(), pos, stackpos, spriteId);
1289}
1290
1291void ProtocolGame::parseRuleViolationReport(NetworkMessage &msg) {
1292 uint8_t reportType = msg.getByte();
1293 uint8_t reportReason = msg.getByte();
1294 const std::string &targetName = msg.getString();
1295 const std::string &comment = msg.getString();
1296 std::string translation;
1297 if (reportType == REPORT_TYPE_NAME) {
1298 translation = msg.getString();
1299 } else if (reportType == REPORT_TYPE_STATEMENT) {
1300 translation = msg.getString();
1301 msg.get<uint32_t>(); // statement id, used to get whatever player have said,
1302 // we don't log that.
1303 }
1304
1305 addGameTask(&Game::playerReportRuleViolation, player->getID(), targetName,
1306 reportType, reportReason, comment, translation);
1307}
1308
1309void ProtocolGame::parseBugReport(NetworkMessage &msg) {
1310 uint8_t category = msg.getByte();
1311 std::string message = msg.getString();
1312
1313 Position position;
1314 if (category == BUG_CATEGORY_MAP) {
1315 position = msg.getPosition();
1316 }
1317
1318 addGameTask(&Game::playerReportBug, player->getID(), message, position,
1319 category);
1320}
1321
1322void ProtocolGame::parseDebugAssert(NetworkMessage &msg) {
1323 if (debugAssertSent) {
1324 return;
1325 }
1326
1327 debugAssertSent = true;
1328
1329 std::string assertLine = msg.getString();
1330 std::string date = msg.getString();
1331 std::string description = msg.getString();
1332 std::string comment = msg.getString();
1333 addGameTask(&Game::playerDebugAssert, player->getID(), assertLine, date,
1334 description, comment);
1335}
1336
1337void ProtocolGame::parseInviteToParty(NetworkMessage &msg) {
1338 uint32_t targetId = msg.get<uint32_t>();
1339 addGameTask(&Game::playerInviteToParty, player->getID(), targetId);
1340}
1341
1342void ProtocolGame::parseJoinParty(NetworkMessage &msg) {
1343 uint32_t targetId = msg.get<uint32_t>();
1344 addGameTask(&Game::playerJoinParty, player->getID(), targetId);
1345}
1346
1347void ProtocolGame::parseRevokePartyInvite(NetworkMessage &msg) {
1348 uint32_t targetId = msg.get<uint32_t>();
1349 addGameTask(&Game::playerRevokePartyInvitation, player->getID(), targetId);
1350}
1351
1352void ProtocolGame::parsePassPartyLeadership(NetworkMessage &msg) {
1353 uint32_t targetId = msg.get<uint32_t>();
1354 addGameTask(&Game::playerPassPartyLeadership, player->getID(), targetId);
1355}
1356
1357void ProtocolGame::parseEnableSharedPartyExperience(NetworkMessage &msg) {
1358 bool sharedExpActive = msg.getByte() == 1;
1359 addGameTask(&Game::playerEnableSharedPartyExperience, player->getID(),
1360 sharedExpActive);
1361}
1362
1363void ProtocolGame::parseQuestLine(NetworkMessage &msg) {
1364 uint16_t questId = msg.get<uint16_t>();
1365 addGameTask(&Game::playerShowQuestLine, player->getID(), questId);
1366}
1367
1368void ProtocolGame::parseMarketLeave() {
1369 addGameTask(&Game::playerLeaveMarket, player->getID());
1370}
1371
1372void ProtocolGame::parseMarketBrowse(NetworkMessage &msg) {
1373 uint16_t browseId = msg.get<uint16_t>();
1374
1375 if (browseId == MARKETREQUEST_OWN_OFFERS) {
1376 addGameTask(&Game::playerBrowseMarketOwnOffers, player->getID());
1377 } else if (browseId == MARKETREQUEST_OWN_HISTORY) {
1378 addGameTask(&Game::playerBrowseMarketOwnHistory, player->getID());
1379 } else {
1380 addGameTask(&Game::playerBrowseMarket, player->getID(), browseId);
1381 }
1382}
1383
1384void ProtocolGame::parseMarketCreateOffer(NetworkMessage &msg) {
1385 uint8_t type = msg.getByte();
1386 uint16_t spriteId = msg.get<uint16_t>();
1387 uint16_t amount = msg.get<uint16_t>();
1388 uint32_t price = msg.get<uint32_t>();
1389 bool anonymous = (msg.getByte() != 0);
1390 addGameTask(&Game::playerCreateMarketOffer, player->getID(), type, spriteId,
1391 amount, price, anonymous);
1392}
1393
1394void ProtocolGame::parseMarketCancelOffer(NetworkMessage &msg) {
1395 uint32_t timestamp = msg.get<uint32_t>();
1396 uint16_t counter = msg.get<uint16_t>();
1397 addGameTask(&Game::playerCancelMarketOffer, player->getID(), timestamp,
1398 counter);
1399}
1400
1401void ProtocolGame::parseMarketAcceptOffer(NetworkMessage &msg) {
1402 uint32_t timestamp = msg.get<uint32_t>();
1403 uint16_t counter = msg.get<uint16_t>();
1404 uint16_t amount = msg.get<uint16_t>();
1405 addGameTask(&Game::playerAcceptMarketOffer, player->getID(), timestamp,
1406 counter, amount);
1407}
1408
1409void ProtocolGame::parseModalWindowAnswer(NetworkMessage &msg) {
1410 uint32_t id = msg.get<uint32_t>();
1411 uint8_t button = msg.getByte();
1412 uint8_t choice = msg.getByte();
1413 addGameTask(&Game::playerAnswerModalWindow, player->getID(), id, button,
1414 choice);
1415}
1416
1417void ProtocolGame::parseBrowseField(NetworkMessage &msg) {
1418 const Position &pos = msg.getPosition();
1419 addGameTask(&Game::playerBrowseField, player->getID(), pos);
1420}
1421
1422void ProtocolGame::parseSeekInContainer(NetworkMessage &msg) {
1423 uint8_t containerId = msg.getByte();
1424 uint16_t index = msg.get<uint16_t>();
1425 addGameTask(&Game::playerSeekInContainer, player->getID(), containerId,
1426 index);
1427}
1428
1429// Send methods
1430void ProtocolGame::sendOpenPrivateChannel(const std::string &receiver) {
1431 NetworkMessage msg;
1432 msg.addByte(0xAD);
1433 msg.addString(receiver);
1434 writeToOutputBuffer(msg);
1435}
1436
1437void ProtocolGame::sendChannelEvent(uint16_t channelId,
1438 const std::string &playerName,
1439 ChannelEvent_t channelEvent) {
1440 NetworkMessage msg;
1441 msg.addByte(0xF3);
1442 msg.add<uint16_t>(channelId);
1443 msg.addString(playerName);
1444 msg.addByte(channelEvent);
1445 writeToOutputBuffer(msg);
1446}
1447
1448void ProtocolGame::sendCreatureOutfit(const Creature *creature,
1449 const Outfit_t &outfit) {
1450 if (!canSee(creature)) {
1451 return;
1452 }
1453
1454 Outfit_t finalOutfit = outfit;
1455 const Player *player = creature->getPlayer();
1456 if (player) {
1457 const Outfit *outfitDef = Outfits::getInstance().getOutfitByLookType(player->getSex(), finalOutfit.lookType);
1458 if (outfitDef) {
1459 finalOutfit.attackRange = outfitDef->attackRange;
1460 for (int d = 0; d < OUTFIT_DIRECTIONS; ++d) {
1461 finalOutfit.offsets[d] = outfitDef->offsets[d];
1462 }
1463 }
1464 }
1465
1466 NetworkMessage msg;
1467 msg.addByte(0x8E);
1468 msg.add<uint32_t>(creature->getID());
1469 AddOutfit(msg, finalOutfit, creature->getAttackOutfitLookType());
1470 writeToOutputBuffer(msg);
1471}
1472
1473void ProtocolGame::sendCreatureAttackPulse(const Creature *creature,
1474 const Outfit_t &outfit,
1475 uint32_t duration) {
1476 if (!canSee(creature)) {
1477 return;
1478 }
1479
1480 Outfit_t finalOutfit = outfit;
1481 const Player *player = creature->getPlayer();
1482 if (player) {
1483 const Outfit *outfitDef = Outfits::getInstance().getOutfitByLookType(player->getSex(), creature->getCurrentOutfit().lookType);
1484 if (outfitDef) {
1485 finalOutfit.attackRange = outfitDef->attackRange;
1486 for (int d = 0; d < OUTFIT_DIRECTIONS; ++d) {
1487 finalOutfit.offsets[d] = outfitDef->offsets[d];
1488 }
1489 }
1490 }
1491
1492 NetworkMessage msg;
1493 msg.addByte(0x5E);
1494 msg.add<uint32_t>(creature->getID());
1495 AddOutfit(msg, finalOutfit, creature->getAttackOutfitLookType());
1496 msg.add<uint16_t>(duration);
1497 writeToOutputBuffer(msg);
1498}
1499
1500void ProtocolGame::sendCreatureLight(const Creature *creature) {
1501 if (!canSee(creature)) {
1502 return;
1503 }
1504
1505 NetworkMessage msg;
1506 AddCreatureLight(msg, creature);
1507 writeToOutputBuffer(msg);
1508}
1509
1510void ProtocolGame::sendWorldLight(LightInfo lightInfo) {
1511 NetworkMessage msg;
1512 AddWorldLight(msg, lightInfo);
1513 writeToOutputBuffer(msg);
1514}
1515
1516void ProtocolGame::sendCreatureWalkthrough(const Creature *creature,
1517 bool walkthrough) {
1518 if (!canSee(creature)) {
1519 return;
1520 }
1521
1522 NetworkMessage msg;
1523 msg.addByte(0x92);
1524 msg.add<uint32_t>(creature->getID());
1525 msg.addByte(walkthrough ? 0x00 : 0x01);
1526 writeToOutputBuffer(msg);
1527}
1528
1529void ProtocolGame::sendCreatureShield(const Creature *creature) {
1530 if (!canSee(creature)) {
1531 return;
1532 }
1533
1534 NetworkMessage msg;
1535 msg.addByte(0x91);
1536 msg.add<uint32_t>(creature->getID());
1537 msg.addByte(player->getPartyShield(creature->getPlayer()));
1538 writeToOutputBuffer(msg);
1539}
1540
1541void ProtocolGame::sendCreatureSkull(const Creature *creature) {
1542 if (g_game.getWorldType() != WORLD_TYPE_PVP) {
1543 return;
1544 }
1545
1546 if (!canSee(creature)) {
1547 return;
1548 }
1549
1550 NetworkMessage msg;
1551 msg.addByte(0x90);
1552 msg.add<uint32_t>(creature->getID());
1553 msg.addByte(player->getSkullClient(creature));
1554 writeToOutputBuffer(msg);
1555}
1556
1557void ProtocolGame::sendCreatureType(uint32_t creatureId, uint8_t creatureType) {
1558 NetworkMessage msg;
1559 msg.addByte(0x95);
1560 msg.add<uint32_t>(creatureId);
1561 msg.addByte(creatureType);
1562 writeToOutputBuffer(msg);
1563}
1564
1565void ProtocolGame::sendCreatureHelpers(uint32_t creatureId, uint16_t helpers) {
1566 NetworkMessage msg;
1567 msg.addByte(0x94);
1568 msg.add<uint32_t>(creatureId);
1569 msg.add<uint16_t>(helpers);
1570 writeToOutputBuffer(msg);
1571}
1572
1573void ProtocolGame::sendCreatureSquare(const Creature *creature,
1574 SquareColor_t color) {
1575 if (!canSee(creature)) {
1576 return;
1577 }
1578
1579 NetworkMessage msg;
1580 msg.addByte(0x93);
1581 msg.add<uint32_t>(creature->getID());
1582 msg.addByte(0x01);
1583 msg.addByte(color);
1584 writeToOutputBuffer(msg);
1585}
1586
1587void ProtocolGame::sendTutorial(uint8_t tutorialId) {
1588 NetworkMessage msg;
1589 msg.addByte(0xDC);
1590 msg.addByte(tutorialId);
1591 writeToOutputBuffer(msg);
1592}
1593
1594void ProtocolGame::sendAddMarker(const Position &pos, uint8_t markType,
1595 const std::string &desc) {
1596 NetworkMessage msg;
1597 msg.addByte(0xDD);
1598 msg.addPosition(pos);
1599 msg.addByte(markType);
1600 msg.addString(desc);
1601 writeToOutputBuffer(msg);
1602}
1603
1604void ProtocolGame::sendReLoginWindow(uint8_t unfairFightReduction) {
1605 NetworkMessage msg;
1606 msg.addByte(0x28);
1607 msg.addByte(0x00);
1608 msg.addByte(unfairFightReduction);
1609 writeToOutputBuffer(msg);
1610}
1611
1612void ProtocolGame::sendStats() {
1613 NetworkMessage msg;
1614 AddPlayerStats(msg);
1615 writeToOutputBuffer(msg);
1616}
1617
1618void ProtocolGame::sendBasicData() {
1619 NetworkMessage msg;
1620 msg.addByte(0x9F);
1621 if (player->isPremium()) {
1622 msg.addByte(1);
1623 msg.add<uint32_t>(time(nullptr) + (player->premiumDays * 86400));
1624 } else {
1625 msg.addByte(0);
1626 msg.add<uint32_t>(0);
1627 }
1628 msg.addByte(player->getVocation()->getClientId());
1629 msg.add<uint16_t>(0xFF); // number of known spells
1630 for (uint8_t spellId = 0x00; spellId < 0xFF; spellId++) {
1631 msg.addByte(spellId);
1632 }
1633 writeToOutputBuffer(msg);
1634}
1635
1636void ProtocolGame::sendTextMessage(const TextMessage &message) {
1637 NetworkMessage msg;
1638 msg.addByte(0xB4);
1639 msg.addByte(message.type);
1640 switch (message.type) {
1641 case MESSAGE_DAMAGE_DEALT:
1642 case MESSAGE_DAMAGE_RECEIVED:
1643 case MESSAGE_DAMAGE_OTHERS: {
1644 msg.addPosition(message.position);
1645 msg.add<uint32_t>(message.primary.value);
1646 msg.addByte(message.primary.color);
1647 msg.add<uint32_t>(message.secondary.value);
1648 msg.addByte(message.secondary.color);
1649 break;
1650 }
1651 case MESSAGE_HEALED:
1652 case MESSAGE_HEALED_OTHERS:
1653 case MESSAGE_EXPERIENCE:
1654 case MESSAGE_EXPERIENCE_OTHERS: {
1655 msg.addPosition(message.position);
1656 msg.add<uint32_t>(message.primary.value);
1657 msg.addByte(message.primary.color);
1658 break;
1659 }
1660 case MESSAGE_GUILD:
1661 case MESSAGE_PARTY_MANAGEMENT:
1662 case MESSAGE_PARTY:
1663 msg.add<uint16_t>(message.channelId);
1664 break;
1665 default: {
1666 break;
1667 }
1668 }
1669 msg.addString(message.text);
1670 writeToOutputBuffer(msg);
1671}
1672
1673void ProtocolGame::sendClosePrivate(uint16_t channelId) {
1674 NetworkMessage msg;
1675 msg.addByte(0xB3);
1676 msg.add<uint16_t>(channelId);
1677 writeToOutputBuffer(msg);
1678}
1679
1680void ProtocolGame::sendCreatePrivateChannel(uint16_t channelId,
1681 const std::string &channelName) {
1682 NetworkMessage msg;
1683 msg.addByte(0xB2);
1684 msg.add<uint16_t>(channelId);
1685 msg.addString(channelName);
1686 msg.add<uint16_t>(0x01);
1687 msg.addString(player->getName());
1688 msg.add<uint16_t>(0x00);
1689 writeToOutputBuffer(msg);
1690}
1691
1692void ProtocolGame::sendChannelsDialog() {
1693 NetworkMessage msg;
1694 msg.addByte(0xAB);
1695
1696 const ChannelList &list = g_chat->getChannelList(*player);
1697 msg.addByte(list.size());
1698 for (ChatChannel *channel : list) {
1699 msg.add<uint16_t>(channel->getId());
1700 msg.addString(channel->getName());
1701 }
1702
1703 writeToOutputBuffer(msg);
1704}
1705
1706void ProtocolGame::sendChannel(uint16_t channelId,
1707 const std::string &channelName,
1708 const UsersMap *channelUsers,
1709 const InvitedMap *invitedUsers) {
1710 NetworkMessage msg;
1711 msg.addByte(0xAC);
1712
1713 msg.add<uint16_t>(channelId);
1714 msg.addString(channelName);
1715
1716 if (channelUsers) {
1717 msg.add<uint16_t>(channelUsers->size());
1718 for (const auto &it : *channelUsers) {
1719 msg.addString(it.second->getName());
1720 }
1721 } else {
1722 msg.add<uint16_t>(0x00);
1723 }
1724
1725 if (invitedUsers) {
1726 msg.add<uint16_t>(invitedUsers->size());
1727 for (const auto &it : *invitedUsers) {
1728 msg.addString(it.second->getName());
1729 }
1730 } else {
1731 msg.add<uint16_t>(0x00);
1732 }
1733 writeToOutputBuffer(msg);
1734}
1735
1736void ProtocolGame::sendChannelMessage(const std::string &author,
1737 const std::string &text,
1738 SpeakClasses type, uint16_t channel) {
1739 NetworkMessage msg;
1740 msg.addByte(0xAA);
1741 msg.add<uint32_t>(0x00);
1742 msg.addString(author);
1743 msg.add<uint16_t>(0x00);
1744 msg.addByte(type);
1745 msg.add<uint16_t>(channel);
1746 msg.addString(text);
1747 writeToOutputBuffer(msg);
1748}
1749
1750void ProtocolGame::sendIcons(uint16_t icons) {
1751 NetworkMessage msg;
1752 msg.addByte(0xA2);
1753 msg.add<uint16_t>(icons);
1754 writeToOutputBuffer(msg);
1755}
1756
1757void ProtocolGame::sendContainer(uint8_t cid, const Container *container,
1758 bool hasParent, uint16_t firstIndex) {
1759 NetworkMessage msg;
1760 msg.addByte(0x6E);
1761
1762 msg.addByte(cid);
1763
1764 if (container->getID() == ITEM_BROWSEFIELD) {
1765 msg.addItem(ITEM_BAG, 1, otclientV8);
1766 msg.addString("Browse Field");
1767 } else {
1768 msg.addItem(container, otclientV8);
1769 msg.addString(container->getName());
1770 }
1771
1772 msg.addByte(container->capacity());
1773
1774 msg.addByte(hasParent ? 0x01 : 0x00);
1775
1776 msg.addByte(container->isUnlocked() ? 0x01 : 0x00); // Drag and drop
1777 msg.addByte(container->hasPagination() ? 0x01 : 0x00); // Pagination
1778
1779 uint32_t containerSize = container->size();
1780 msg.add<uint16_t>(containerSize);
1781 msg.add<uint16_t>(firstIndex);
1782 if (firstIndex < containerSize) {
1783 uint8_t itemsToSend = std::min<uint32_t>(
1784 std::min<uint32_t>(container->capacity(), containerSize - firstIndex),
1785 std::numeric_limits<uint8_t>::max());
1786
1787 msg.addByte(itemsToSend);
1788 for (auto it = container->getItemList().begin() + firstIndex,
1789 end = it + itemsToSend;
1790 it != end; ++it) {
1791 msg.addItem(*it, otclientV8);
1792 }
1793 } else {
1794 msg.addByte(0x00);
1795 }
1796 writeToOutputBuffer(msg);
1797}
1798
1799void ProtocolGame::sendShop(Npc *npc, const ShopInfoList &itemList) {
1800 NetworkMessage msg;
1801 msg.addByte(0x7A);
1802 msg.addString(npc->getName());
1803
1804 uint16_t itemsToSend =
1805 std::min<size_t>(itemList.size(), std::numeric_limits<uint16_t>::max());
1806 msg.add<uint16_t>(itemsToSend);
1807
1808 uint16_t i = 0;
1809 for (auto it = itemList.begin(); i < itemsToSend; ++it, ++i) {
1810 AddShopItem(msg, *it);
1811 }
1812
1813 writeToOutputBuffer(msg);
1814}
1815
1816void ProtocolGame::sendCloseShop() {
1817 NetworkMessage msg;
1818 msg.addByte(0x7C);
1819 writeToOutputBuffer(msg);
1820}
1821
1822void ProtocolGame::sendSaleItemList(const std::list<ShopInfo> &shop) {
1823 NetworkMessage msg;
1824 msg.addByte(0x7B);
1825 msg.add<uint64_t>(player->getMoney() + player->getBankBalance());
1826
1827 std::map<uint16_t, uint32_t> saleMap;
1828
1829 if (shop.size() <= 5) {
1830 // For very small shops it's not worth it to create the complete map
1831 for (const ShopInfo &shopInfo : shop) {
1832 if (shopInfo.sellPrice == 0) {
1833 continue;
1834 }
1835
1836 int8_t subtype = -1;
1837
1838 const ItemType &itemType = Item::items[shopInfo.itemId];
1839 if (itemType.hasSubType() && !itemType.stackable) {
1840 subtype = (shopInfo.subType == 0 ? -1 : shopInfo.subType);
1841 }
1842
1843 uint32_t count = player->getItemTypeCount(shopInfo.itemId, subtype);
1844 if (count > 0) {
1845 saleMap[shopInfo.itemId] = count;
1846 }
1847 }
1848 } else {
1849 // Large shop, it's better to get a cached map of all item counts and use it
1850 // We need a temporary map since the finished map should only contain items
1851 // available in the shop
1852 std::map<uint32_t, uint32_t> tempSaleMap;
1853 player->getAllItemTypeCount(tempSaleMap);
1854
1855 // We must still check manually for the special items that require subtype
1856 // matches (That is, fluids such as potions etc., actually these items are
1857 // very few since health potions now use their own ID)
1858 for (const ShopInfo &shopInfo : shop) {
1859 if (shopInfo.sellPrice == 0) {
1860 continue;
1861 }
1862
1863 int8_t subtype = -1;
1864
1865 const ItemType &itemType = Item::items[shopInfo.itemId];
1866 if (itemType.hasSubType() && !itemType.stackable) {
1867 subtype = (shopInfo.subType == 0 ? -1 : shopInfo.subType);
1868 }
1869
1870 if (subtype != -1) {
1871 uint32_t count;
1872 if (!itemType.isFluidContainer() && !itemType.isSplash()) {
1873 count = player->getItemTypeCount(
1874 shopInfo.itemId, subtype); // This shop item requires extra checks
1875 } else {
1876 count = subtype;
1877 }
1878
1879 if (count > 0) {
1880 saleMap[shopInfo.itemId] = count;
1881 }
1882 } else {
1883 std::map<uint32_t, uint32_t>::const_iterator findIt =
1884 tempSaleMap.find(shopInfo.itemId);
1885 if (findIt != tempSaleMap.end() && findIt->second > 0) {
1886 saleMap[shopInfo.itemId] = findIt->second;
1887 }
1888 }
1889 }
1890 }
1891
1892 uint8_t itemsToSend =
1893 std::min<size_t>(saleMap.size(), std::numeric_limits<uint8_t>::max());
1894 msg.addByte(itemsToSend);
1895
1896 uint8_t i = 0;
1897 for (std::map<uint16_t, uint32_t>::const_iterator it = saleMap.begin();
1898 i < itemsToSend; ++it, ++i) {
1899 msg.addItemId(it->first);
1900 msg.addByte(
1901 std::min<uint32_t>(it->second, std::numeric_limits<uint8_t>::max()));
1902 }
1903
1904 writeToOutputBuffer(msg);
1905}
1906
1907void ProtocolGame::sendMarketEnter(uint32_t depotId) {
1908 NetworkMessage msg;
1909 msg.addByte(0xF6);
1910
1911 msg.add<uint64_t>(player->getBankBalance());
1912 msg.addByte(
1913 std::min<uint32_t>(IOMarket::getPlayerOfferCount(player->getGUID()),
1914 std::numeric_limits<uint8_t>::max()));
1915
1916 DepotChest *depotChest = player->getDepotChest(depotId, false);
1917 if (!depotChest) {
1918 msg.add<uint16_t>(0x00);
1919 writeToOutputBuffer(msg);
1920 return;
1921 }
1922
1923 player->setInMarket(true);
1924
1925 std::map<uint16_t, uint32_t> depotItems;
1926 std::forward_list<Container *> containerList{depotChest, player->getInbox()};
1927
1928 do {
1929 Container *container = containerList.front();
1930 containerList.pop_front();
1931
1932 for (Item *item : container->getItemList()) {
1933 Container *c = item->getContainer();
1934 if (c && !c->empty()) {
1935 containerList.push_front(c);
1936 continue;
1937 }
1938
1939 const ItemType &itemType = Item::items[item->getID()];
1940 if (itemType.wareId == 0) {
1941 continue;
1942 }
1943
1944 if (c &&
1945 (!itemType.isContainer() || c->capacity() != itemType.maxItems)) {
1946 continue;
1947 }
1948
1949 if (!item->hasMarketAttributes()) {
1950 continue;
1951 }
1952
1953 depotItems[itemType.wareId] += Item::countByType(item, -1);
1954 }
1955 } while (!containerList.empty());
1956
1957 uint16_t itemsToSend =
1958 std::min<size_t>(depotItems.size(), std::numeric_limits<uint16_t>::max());
1959 msg.add<uint16_t>(itemsToSend);
1960
1961 uint16_t i = 0;
1962 for (std::map<uint16_t, uint32_t>::const_iterator it = depotItems.begin();
1963 i < itemsToSend; ++it, ++i) {
1964 msg.add<uint16_t>(it->first);
1965 msg.add<uint16_t>(std::min<uint32_t>(0xFFFF, it->second));
1966 }
1967
1968 writeToOutputBuffer(msg);
1969}
1970
1971void ProtocolGame::sendMarketLeave() {
1972 NetworkMessage msg;
1973 msg.addByte(0xF7);
1974 writeToOutputBuffer(msg);
1975}
1976
1977void ProtocolGame::sendMarketBrowseItem(uint16_t itemId,
1978 const MarketOfferList &buyOffers,
1979 const MarketOfferList &sellOffers) {
1980 NetworkMessage msg;
1981
1982 msg.addByte(0xF9);
1983 msg.addItemId(itemId);
1984
1985 msg.add<uint32_t>(buyOffers.size());
1986 for (const MarketOffer &offer : buyOffers) {
1987 msg.add<uint32_t>(offer.timestamp);
1988 msg.add<uint16_t>(offer.counter);
1989 msg.add<uint16_t>(offer.amount);
1990 msg.add<uint32_t>(offer.price);
1991 msg.addString(offer.playerName);
1992 }
1993
1994 msg.add<uint32_t>(sellOffers.size());
1995 for (const MarketOffer &offer : sellOffers) {
1996 msg.add<uint32_t>(offer.timestamp);
1997 msg.add<uint16_t>(offer.counter);
1998 msg.add<uint16_t>(offer.amount);
1999 msg.add<uint32_t>(offer.price);
2000 msg.addString(offer.playerName);
2001 }
2002
2003 writeToOutputBuffer(msg);
2004}
2005
2006void ProtocolGame::sendMarketAcceptOffer(const MarketOfferEx &offer) {
2007 NetworkMessage msg;
2008 msg.addByte(0xF9);
2009 msg.addItemId(offer.itemId);
2010
2011 if (offer.type == MARKETACTION_BUY) {
2012 msg.add<uint32_t>(0x01);
2013 msg.add<uint32_t>(offer.timestamp);
2014 msg.add<uint16_t>(offer.counter);
2015 msg.add<uint16_t>(offer.amount);
2016 msg.add<uint32_t>(offer.price);
2017 msg.addString(offer.playerName);
2018 msg.add<uint32_t>(0x00);
2019 } else {
2020 msg.add<uint32_t>(0x00);
2021 msg.add<uint32_t>(0x01);
2022 msg.add<uint32_t>(offer.timestamp);
2023 msg.add<uint16_t>(offer.counter);
2024 msg.add<uint16_t>(offer.amount);
2025 msg.add<uint32_t>(offer.price);
2026 msg.addString(offer.playerName);
2027 }
2028
2029 writeToOutputBuffer(msg);
2030}
2031
2032void ProtocolGame::sendMarketBrowseOwnOffers(
2033 const MarketOfferList &buyOffers, const MarketOfferList &sellOffers) {
2034 NetworkMessage msg;
2035 msg.addByte(0xF9);
2036 msg.add<uint16_t>(MARKETREQUEST_OWN_OFFERS);
2037
2038 msg.add<uint32_t>(buyOffers.size());
2039 for (const MarketOffer &offer : buyOffers) {
2040 msg.add<uint32_t>(offer.timestamp);
2041 msg.add<uint16_t>(offer.counter);
2042 msg.addItemId(offer.itemId);
2043 msg.add<uint16_t>(offer.amount);
2044 msg.add<uint32_t>(offer.price);
2045 }
2046
2047 msg.add<uint32_t>(sellOffers.size());
2048 for (const MarketOffer &offer : sellOffers) {
2049 msg.add<uint32_t>(offer.timestamp);
2050 msg.add<uint16_t>(offer.counter);
2051 msg.addItemId(offer.itemId);
2052 msg.add<uint16_t>(offer.amount);
2053 msg.add<uint32_t>(offer.price);
2054 }
2055
2056 writeToOutputBuffer(msg);
2057}
2058
2059void ProtocolGame::sendMarketCancelOffer(const MarketOfferEx &offer) {
2060 NetworkMessage msg;
2061 msg.addByte(0xF9);
2062 msg.add<uint16_t>(MARKETREQUEST_OWN_OFFERS);
2063
2064 if (offer.type == MARKETACTION_BUY) {
2065 msg.add<uint32_t>(0x01);
2066 msg.add<uint32_t>(offer.timestamp);
2067 msg.add<uint16_t>(offer.counter);
2068 msg.addItemId(offer.itemId);
2069 msg.add<uint16_t>(offer.amount);
2070 msg.add<uint32_t>(offer.price);
2071 msg.add<uint32_t>(0x00);
2072 } else {
2073 msg.add<uint32_t>(0x00);
2074 msg.add<uint32_t>(0x01);
2075 msg.add<uint32_t>(offer.timestamp);
2076 msg.add<uint16_t>(offer.counter);
2077 msg.addItemId(offer.itemId);
2078 msg.add<uint16_t>(offer.amount);
2079 msg.add<uint32_t>(offer.price);
2080 }
2081
2082 writeToOutputBuffer(msg);
2083}
2084
2085void ProtocolGame::sendMarketBrowseOwnHistory(
2086 const HistoryMarketOfferList &buyOffers,
2087 const HistoryMarketOfferList &sellOffers) {
2088 uint32_t i = 0;
2089 std::map<uint32_t, uint16_t> counterMap;
2090 uint32_t buyOffersToSend = std::min<uint32_t>(
2091 buyOffers.size(), 810 + std::max<int32_t>(0, 810 - sellOffers.size()));
2092 uint32_t sellOffersToSend = std::min<uint32_t>(
2093 sellOffers.size(), 810 + std::max<int32_t>(0, 810 - buyOffers.size()));
2094
2095 NetworkMessage msg;
2096 msg.addByte(0xF9);
2097 msg.add<uint16_t>(MARKETREQUEST_OWN_HISTORY);
2098
2099 msg.add<uint32_t>(buyOffersToSend);
2100 for (auto it = buyOffers.begin(); i < buyOffersToSend; ++it, ++i) {
2101 msg.add<uint32_t>(it->timestamp);
2102 msg.add<uint16_t>(counterMap[it->timestamp]++);
2103 msg.addItemId(it->itemId);
2104 msg.add<uint16_t>(it->amount);
2105 msg.add<uint32_t>(it->price);
2106 msg.addByte(it->state);
2107 }
2108
2109 counterMap.clear();
2110 i = 0;
2111
2112 msg.add<uint32_t>(sellOffersToSend);
2113 for (auto it = sellOffers.begin(); i < sellOffersToSend; ++it, ++i) {
2114 msg.add<uint32_t>(it->timestamp);
2115 msg.add<uint16_t>(counterMap[it->timestamp]++);
2116 msg.addItemId(it->itemId);
2117 msg.add<uint16_t>(it->amount);
2118 msg.add<uint32_t>(it->price);
2119 msg.addByte(it->state);
2120 }
2121
2122 writeToOutputBuffer(msg);
2123}
2124
2125void ProtocolGame::sendMarketDetail(uint16_t itemId) {
2126 NetworkMessage msg;
2127 msg.addByte(0xF8);
2128 msg.addItemId(itemId);
2129
2130 const ItemType &it = Item::items[itemId];
2131 if (it.armor != 0) {
2132 msg.addString(std::to_string(it.armor));
2133 } else {
2134 msg.add<uint16_t>(0x00);
2135 }
2136
2137 if (it.attack != 0) {
2138 // TODO: chance to hit, range
2139 // example:
2140 // "attack +x, chance to hit +y%, z fields"
2141 if (it.abilities && it.abilities->elementType != COMBAT_NONE &&
2142 it.abilities->elementDamage != 0) {
2143 std::ostringstream ss;
2144 ss << it.attack << " physical +" << it.abilities->elementDamage << ' '
2145 << getCombatName(it.abilities->elementType);
2146 msg.addString(ss.str());
2147 } else {
2148 msg.addString(std::to_string(it.attack));
2149 }
2150 } else {
2151 msg.add<uint16_t>(0x00);
2152 }
2153
2154 if (it.isContainer()) {
2155 msg.addString(std::to_string(it.maxItems));
2156 } else {
2157 msg.add<uint16_t>(0x00);
2158 }
2159
2160 if (it.defense != 0) {
2161 if (it.extraDefense != 0) {
2162 std::ostringstream ss;
2163 ss << it.defense << ' ' << std::showpos << it.extraDefense
2164 << std::noshowpos;
2165 msg.addString(ss.str());
2166 } else {
2167 msg.addString(std::to_string(it.defense));
2168 }
2169 } else {
2170 msg.add<uint16_t>(0x00);
2171 }
2172
2173 if (!it.description.empty()) {
2174 const std::string &descr = it.description;
2175 if (descr.back() == '.') {
2176 msg.addString(std::string(descr, 0, descr.length() - 1));
2177 } else {
2178 msg.addString(descr);
2179 }
2180 } else {
2181 msg.add<uint16_t>(0x00);
2182 }
2183
2184 if (it.decayTime != 0) {
2185 std::ostringstream ss;
2186 ss << it.decayTime << " seconds";
2187 msg.addString(ss.str());
2188 } else {
2189 msg.add<uint16_t>(0x00);
2190 }
2191
2192 if (it.abilities) {
2193 std::ostringstream ss;
2194 bool separator = false;
2195
2196 for (size_t i = 0; i < COMBAT_COUNT; ++i) {
2197 if (it.abilities->absorbPercent[i] == 0) {
2198 continue;
2199 }
2200
2201 if (separator) {
2202 ss << ", ";
2203 } else {
2204 separator = true;
2205 }
2206
2207 ss << getCombatName(indexToCombatType(i)) << ' ' << std::showpos
2208 << it.abilities->absorbPercent[i] << std::noshowpos << '%';
2209 }
2210
2211 msg.addString(ss.str());
2212 } else {
2213 msg.add<uint16_t>(0x00);
2214 }
2215
2216 if (it.minReqLevel != 0) {
2217 msg.addString(std::to_string(it.minReqLevel));
2218 } else {
2219 msg.add<uint16_t>(0x00);
2220 }
2221
2222 if (it.minReqMagicLevel != 0) {
2223 msg.addString(std::to_string(it.minReqMagicLevel));
2224 } else {
2225 msg.add<uint16_t>(0x00);
2226 }
2227
2228 msg.addString(it.vocationString);
2229
2230 msg.addString(it.runeSpellName);
2231
2232 if (it.abilities) {
2233 std::ostringstream ss;
2234 bool separator = false;
2235
2236 for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; i++) {
2237 if (!it.abilities->skills[i]) {
2238 continue;
2239 }
2240
2241 if (separator) {
2242 ss << ", ";
2243 } else {
2244 separator = true;
2245 }
2246
2247 ss << getSkillName(i) << ' ' << std::showpos << it.abilities->skills[i]
2248 << std::noshowpos;
2249 }
2250
2251 if (it.abilities->stats[STAT_MAGICPOINTS] != 0) {
2252 if (separator) {
2253 ss << ", ";
2254 } else {
2255 separator = true;
2256 }
2257
2258 ss << "magic level " << std::showpos
2259 << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos;
2260 }
2261
2262 if (it.abilities->speed != 0) {
2263 if (separator) {
2264 ss << ", ";
2265 }
2266
2267 ss << "speed " << std::showpos << (it.abilities->speed >> 1)
2268 << std::noshowpos;
2269 }
2270
2271 msg.addString(ss.str());
2272 } else {
2273 msg.add<uint16_t>(0x00);
2274 }
2275
2276 if (it.charges != 0) {
2277 msg.addString(std::to_string(it.charges));
2278 } else {
2279 msg.add<uint16_t>(0x00);
2280 }
2281
2282 std::string weaponName = getWeaponName(it.weaponType);
2283
2284 if (it.slotPosition & SLOTP_TWO_HAND) {
2285 if (!weaponName.empty()) {
2286 weaponName += ", two-handed";
2287 } else {
2288 weaponName = "two-handed";
2289 }
2290 }
2291
2292 msg.addString(weaponName);
2293
2294 if (it.weight != 0) {
2295 std::ostringstream ss;
2296 if (it.weight < 10) {
2297 ss << "0.0" << it.weight;
2298 } else if (it.weight < 100) {
2299 ss << "0." << it.weight;
2300 } else {
2301 std::string weightString = std::to_string(it.weight);
2302 weightString.insert(weightString.end() - 2, '.');
2303 ss << weightString;
2304 }
2305 ss << " oz";
2306 msg.addString(ss.str());
2307 } else {
2308 msg.add<uint16_t>(0x00);
2309 }
2310
2311 MarketStatistics *statistics =
2312 IOMarket::getInstance().getPurchaseStatistics(itemId);
2313 if (statistics) {
2314 msg.addByte(0x01);
2315 msg.add<uint32_t>(statistics->numTransactions);
2316 msg.add<uint32_t>(std::min<uint64_t>(std::numeric_limits<uint32_t>::max(),
2317 statistics->totalPrice));
2318 msg.add<uint32_t>(statistics->highestPrice);
2319 msg.add<uint32_t>(statistics->lowestPrice);
2320 } else {
2321 msg.addByte(0x00);
2322 }
2323
2324 statistics = IOMarket::getInstance().getSaleStatistics(itemId);
2325 if (statistics) {
2326 msg.addByte(0x01);
2327 msg.add<uint32_t>(statistics->numTransactions);
2328 msg.add<uint32_t>(std::min<uint64_t>(std::numeric_limits<uint32_t>::max(),
2329 statistics->totalPrice));
2330 msg.add<uint32_t>(statistics->highestPrice);
2331 msg.add<uint32_t>(statistics->lowestPrice);
2332 } else {
2333 msg.addByte(0x00);
2334 }
2335
2336 writeToOutputBuffer(msg);
2337}
2338
2339void ProtocolGame::sendQuestLog() {
2340 NetworkMessage msg;
2341 msg.addByte(0xF0);
2342 msg.add<uint16_t>(g_game.quests.getQuestsCount(player));
2343
2344 for (const Quest &quest : g_game.quests.getQuests()) {
2345 if (quest.isStarted(player)) {
2346 msg.add<uint16_t>(quest.getID());
2347 msg.addString(quest.getName());
2348 msg.addByte(quest.isCompleted(player));
2349 }
2350 }
2351
2352 writeToOutputBuffer(msg);
2353}
2354
2355void ProtocolGame::sendQuestLine(const Quest *quest) {
2356 NetworkMessage msg;
2357 msg.addByte(0xF1);
2358 msg.add<uint16_t>(quest->getID());
2359 msg.addByte(quest->getMissionsCount(player));
2360
2361 for (const Mission &mission : quest->getMissions()) {
2362 if (mission.isStarted(player)) {
2363 msg.addString(mission.getName(player));
2364 msg.addString(mission.getDescription(player));
2365 }
2366 }
2367
2368 writeToOutputBuffer(msg);
2369}
2370
2371void ProtocolGame::sendTradeItemRequest(const std::string &traderName,
2372 const Item *item, bool ack) {
2373 NetworkMessage msg;
2374
2375 if (ack) {
2376 msg.addByte(0x7D);
2377 } else {
2378 msg.addByte(0x7E);
2379 }
2380
2381 msg.addString(traderName);
2382
2383 if (const Container *tradeContainer = item->getContainer()) {
2384 std::list<const Container *> listContainer{tradeContainer};
2385 std::list<const Item *> itemList{tradeContainer};
2386 while (!listContainer.empty()) {
2387 const Container *container = listContainer.front();
2388 listContainer.pop_front();
2389
2390 for (Item *containerItem : container->getItemList()) {
2391 Container *tmpContainer = containerItem->getContainer();
2392 if (tmpContainer) {
2393 listContainer.push_back(tmpContainer);
2394 }
2395 itemList.push_back(containerItem);
2396 }
2397 }
2398
2399 msg.addByte(itemList.size());
2400 for (const Item *listItem : itemList) {
2401 msg.addItem(listItem, otclientV8);
2402 }
2403 } else {
2404 msg.addByte(0x01);
2405 msg.addItem(item, otclientV8);
2406 }
2407 writeToOutputBuffer(msg);
2408}
2409
2410void ProtocolGame::sendCloseTrade() {
2411 NetworkMessage msg;
2412 msg.addByte(0x7F);
2413 writeToOutputBuffer(msg);
2414}
2415
2416void ProtocolGame::sendCloseContainer(uint8_t cid) {
2417 NetworkMessage msg;
2418 msg.addByte(0x6F);
2419 msg.addByte(cid);
2420 writeToOutputBuffer(msg);
2421}
2422
2423void ProtocolGame::sendCreatureTurn(const Creature *creature,
2424 uint32_t stackPos) {
2425 if (!canSee(creature)) {
2426 return;
2427 }
2428
2429 NetworkMessage msg;
2430 msg.addByte(0x6B);
2431 msg.addPosition(creature->getPosition());
2432 msg.addByte(stackPos);
2433 msg.add<uint16_t>(0x63);
2434 msg.add<uint32_t>(creature->getID());
2435 msg.addByte(creature->getDirection());
2436 msg.addByte(player->canWalkthroughEx(creature) ? 0x00 : 0x01);
2437 writeToOutputBuffer(msg);
2438}
2439
2440void ProtocolGame::sendCreatureSay(const Creature *creature, SpeakClasses type,
2441 const std::string &text,
2442 const Position *pos /* = nullptr*/) {
2443 NetworkMessage msg;
2444 msg.addByte(0xAA);
2445
2446 static uint32_t statementId = 0;
2447 msg.add<uint32_t>(++statementId);
2448
2449 msg.addString(creature->getName());
2450
2451 // Add level only for players
2452 if (const Player *speaker = creature->getPlayer()) {
2453 msg.add<uint16_t>(speaker->getLevel());
2454 } else {
2455 msg.add<uint16_t>(0x00);
2456 }
2457
2458 msg.addByte(type);
2459 if (pos) {
2460 msg.addPosition(*pos);
2461 } else {
2462 msg.addPosition(creature->getPosition());
2463 }
2464
2465 msg.addString(text);
2466 writeToOutputBuffer(msg);
2467}
2468
2469void ProtocolGame::sendToChannel(const Creature *creature, SpeakClasses type,
2470 const std::string &text, uint16_t channelId) {
2471 NetworkMessage msg;
2472 msg.addByte(0xAA);
2473
2474 static uint32_t statementId = 0;
2475 msg.add<uint32_t>(++statementId);
2476 if (!creature) {
2477 msg.add<uint32_t>(0x00);
2478 } else if (type == TALKTYPE_CHANNEL_R2) {
2479 msg.add<uint32_t>(0x00);
2480 type = TALKTYPE_CHANNEL_R1;
2481 } else {
2482 msg.addString(creature->getName());
2483 // Add level only for players
2484 if (const Player *speaker = creature->getPlayer()) {
2485 msg.add<uint16_t>(speaker->getLevel());
2486 } else {
2487 msg.add<uint16_t>(0x00);
2488 }
2489 }
2490
2491 msg.addByte(type);
2492 msg.add<uint16_t>(channelId);
2493 msg.addString(text);
2494 writeToOutputBuffer(msg);
2495}
2496
2497void ProtocolGame::sendPrivateMessage(const Player *speaker, SpeakClasses type,
2498 const std::string &text) {
2499 NetworkMessage msg;
2500 msg.addByte(0xAA);
2501 static uint32_t statementId = 0;
2502 msg.add<uint32_t>(++statementId);
2503 if (speaker) {
2504 msg.addString(speaker->getName());
2505 msg.add<uint16_t>(speaker->getLevel());
2506 } else {
2507 msg.add<uint32_t>(0x00);
2508 }
2509 msg.addByte(type);
2510 msg.addString(text);
2511 writeToOutputBuffer(msg);
2512}
2513
2514void ProtocolGame::sendCancelTarget() {
2515 NetworkMessage msg;
2516 msg.addByte(0xA3);
2517 msg.add<uint32_t>(0x00);
2518 writeToOutputBuffer(msg);
2519}
2520
2521void ProtocolGame::sendChangeSpeed(const Creature *creature, uint32_t speed) {
2522 NetworkMessage msg;
2523 msg.addByte(0x8F);
2524 msg.add<uint32_t>(creature->getID());
2525 msg.add<uint16_t>(creature->getBaseSpeed() / 2);
2526 msg.add<uint16_t>(speed / 2);
2527 writeToOutputBuffer(msg);
2528}
2529
2530void ProtocolGame::sendCancelWalk() {
2531 NetworkMessage msg;
2532 msg.addByte(0xB5);
2533 msg.addByte(player->getDirection());
2534 writeToOutputBuffer(msg);
2535}
2536
2537void ProtocolGame::sendSkills() {
2538 NetworkMessage msg;
2539 AddPlayerSkills(msg);
2540 writeToOutputBuffer(msg);
2541}
2542
2543void ProtocolGame::sendPing() {
2544 NetworkMessage msg;
2545 msg.addByte(0x1D);
2546 writeToOutputBuffer(msg);
2547}
2548
2549void ProtocolGame::sendPingBack() {
2550 NetworkMessage msg;
2551 msg.addByte(0x1E);
2552 writeToOutputBuffer(msg);
2553}
2554
2555void ProtocolGame::sendDistanceShoot(const Position &from, const Position &to,
2556 uint8_t type) {
2557 NetworkMessage msg;
2558 msg.addByte(0x85);
2559 msg.addPosition(from);
2560 msg.addPosition(to);
2561 msg.addByte(type);
2562 writeToOutputBuffer(msg);
2563}
2564
2565void ProtocolGame::sendMagicEffect(const Position &pos, uint16_t type) {
2566 if (!canSee(pos)) {
2567 return;
2568 }
2569
2570 NetworkMessage msg;
2571 msg.addByte(0x83);
2572 msg.addPosition(pos);
2573 msg.add<uint16_t>(type);
2574 writeToOutputBuffer(msg);
2575}
2576
2577void ProtocolGame::sendCreatureHealth(const Creature *creature) {
2578 NetworkMessage msg;
2579 msg.addByte(0x8C);
2580 msg.add<uint32_t>(creature->getID());
2581
2582 if (creature->isHealthHidden()) {
2583 msg.addByte(0x00);
2584 } else {
2585 msg.addByte(std::ceil((static_cast<double>(creature->getHealth()) /
2586 std::max<int32_t>(creature->getMaxHealth(), 1)) *
2587 100));
2588 }
2589 writeToOutputBuffer(msg);
2590}
2591
2592void ProtocolGame::sendFYIBox(const std::string &message) {
2593 NetworkMessage msg;
2594 msg.addByte(0x15);
2595 msg.addString(message);
2596 writeToOutputBuffer(msg);
2597}
2598
2599// tile
2600void ProtocolGame::sendMapDescription(const Position &pos) {
2601 if (otclientV8) {
2602 int32_t startz, endz, zstep;
2603
2604 if (pos.z > 7) {
2605 startz = pos.z - 5;
2606 endz = std::min<int32_t>(MAP_MAX_LAYERS - 1, pos.z + 5);
2607 zstep = 1;
2608 } else {
2609 // FIX: send ALL floors (15->0) for surface players.
2610 startz = MAP_MAX_LAYERS - 1; // = 15
2611 endz = 0;
2612 zstep = -1;
2613 }
2614
2615 for (int32_t nz = startz; nz != endz + zstep; nz += zstep) {
2616 sendFloorDescription(pos, nz);
2617 }
2618 } else {
2619 NetworkMessage msg;
2620 msg.addByte(0x64);
2621 msg.addPosition(player->getPosition());
2622 GetMapDescription(pos.x - awareRange.left(), pos.y - awareRange.top(),
2623 pos.z, awareRange.horizontal(), awareRange.vertical(),
2624 msg);
2625 writeToOutputBuffer(msg);
2626 }
2627}
2628
2629void ProtocolGame::sendFloorDescription(const Position &pos, int floor) {
2630 // When map view range is big, let's say 30x20 all floors may not fit in
2631 // single packets So we split one packet with every floor to few packets with
2632 // single floor
2633 NetworkMessage msg;
2634 msg.addByte(0x4B);
2635 msg.addPosition(player->getPosition());
2636 msg.addByte(floor);
2637 int32_t skip = -1;
2638 GetFloorDescription(msg, pos.x - awareRange.left(), pos.y - awareRange.top(),
2639 floor, awareRange.horizontal(), awareRange.vertical(),
2640 pos.z - floor, skip);
2641 if (skip >= 0) {
2642 msg.addByte(skip);
2643 msg.addByte(0xFF);
2644 }
2645 writeToOutputBuffer(msg);
2646}
2647
2648void ProtocolGame::sendAddTileItem(const Position &pos, uint32_t stackpos,
2649 const Item *item) {
2650 if (!canSee(pos)) {
2651 return;
2652 }
2653
2654 NetworkMessage msg;
2655 msg.addByte(0x6A);
2656 msg.addPosition(pos);
2657 msg.addByte(stackpos);
2658 msg.addItem(item);
2659 writeToOutputBuffer(msg);
2660
2661 checkPredictiveWalking(pos);
2662}
2663
2664void ProtocolGame::sendUpdateTileItem(const Position &pos, uint32_t stackpos,
2665 const Item *item) {
2666 if (!canSee(pos)) {
2667 return;
2668 }
2669
2670 NetworkMessage msg;
2671 msg.addByte(0x6B);
2672 msg.addPosition(pos);
2673 msg.addByte(stackpos);
2674 msg.addItem(item);
2675 writeToOutputBuffer(msg);
2676
2677 checkPredictiveWalking(pos);
2678}
2679
2680void ProtocolGame::sendRemoveTileThing(const Position &pos, uint32_t stackpos) {
2681 if (!canSee(pos)) {
2682 return;
2683 }
2684
2685 NetworkMessage msg;
2686 RemoveTileThing(msg, pos, stackpos);
2687 writeToOutputBuffer(msg);
2688}
2689
2690void ProtocolGame::sendUpdateTile(const Tile *tile, const Position &pos) {
2691 if (!canSee(pos)) {
2692 return;
2693 }
2694
2695 NetworkMessage msg;
2696 msg.addByte(0x69);
2697 msg.addPosition(pos);
2698
2699 if (tile) {
2700 GetTileDescription(tile, msg);
2701 msg.addByte(0x00);
2702 msg.addByte(0xFF);
2703 } else {
2704 msg.addByte(0x01);
2705 msg.addByte(0xFF);
2706 }
2707
2708 writeToOutputBuffer(msg);
2709}
2710
2711void ProtocolGame::sendPendingStateEntered() {
2712 NetworkMessage msg;
2713 msg.addByte(0x0A);
2714 writeToOutputBuffer(msg);
2715}
2716
2717void ProtocolGame::sendEnterWorld() {
2718 NetworkMessage msg;
2719 msg.addByte(0x0F);
2720 writeToOutputBuffer(msg);
2721}
2722
2723void ProtocolGame::sendFightModes() {
2724 NetworkMessage msg;
2725 msg.addByte(0xA7);
2726 msg.addByte(player->fightMode);
2727 msg.addByte(player->chaseMode);
2728 msg.addByte(player->secureMode);
2729 msg.addByte(PVP_MODE_DOVE);
2730 writeToOutputBuffer(msg);
2731}
2732
2733void ProtocolGame::sendAddCreature(const Creature *creature,
2734 const Position &pos, int32_t stackpos,
2735 bool isLogin) {
2736 if (!canSee(pos)) {
2737 return;
2738 }
2739
2740 if (creature != player) {
2741 if (stackpos != -1) {
2742 NetworkMessage msg;
2743 msg.addByte(0x6A);
2744 msg.addPosition(pos);
2745 msg.addByte(stackpos);
2746
2747 bool known;
2748 uint32_t removedKnown;
2749 checkCreatureAsKnown(creature->getID(), known, removedKnown);
2750 AddCreature(msg, creature, known, removedKnown);
2751 writeToOutputBuffer(msg);
2752 }
2753
2754 if (isLogin) {
2755 sendMagicEffect(pos, CONST_ME_TELEPORT);
2756 }
2757
2758 checkPredictiveWalking(pos);
2759 return;
2760 }
2761
2762 NetworkMessage msg;
2763 msg.addByte(0x17);
2764
2765 msg.add<uint32_t>(player->getID());
2766 msg.add<uint16_t>(0x32); // beat duration (50)
2767
2768 msg.addDouble(Creature::speedA, 3);
2769 msg.addDouble(Creature::speedB, 3);
2770 msg.addDouble(Creature::speedC, 3);
2771
2772 // can report bugs?
2773 if (player->getAccountType() >= ACCOUNT_TYPE_TUTOR) {
2774 msg.addByte(0x01);
2775 } else {
2776 msg.addByte(0x00);
2777 }
2778
2779 msg.addByte(0x00); // can change pvp framing option
2780 msg.addByte(0x00); // expert mode button enabled
2781
2782 msg.add<uint16_t>(0x00); // URL (string) to ingame store images
2783 msg.add<uint16_t>(25); // premium coin package size
2784
2785 writeToOutputBuffer(msg);
2786
2787 sendPendingStateEntered();
2788 sendEnterWorld();
2789 sendMapDescription(pos);
2790
2791 if (isLogin) {
2792 sendMagicEffect(pos, CONST_ME_TELEPORT);
2793 }
2794
2795 for (int i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) {
2796 sendInventoryItem(static_cast<slots_t>(i),
2797 player->getInventoryItem(static_cast<slots_t>(i)));
2798 }
2799
2800 sendStats();
2801 sendSkills();
2802
2803 // gameworld light-settings
2804 sendWorldLight(g_game.getWorldLightInfo());
2805
2806 // player light level
2807 sendCreatureLight(creature);
2808
2809 sendVIPEntries();
2810
2811 sendBasicData();
2812 player->sendIcons();
2813}
2814
2815void ProtocolGame::sendMoveCreature(const Creature *creature,
2816 const Position &newPos, int32_t newStackPos,
2817 const Position &oldPos, int32_t oldStackPos,
2818 bool teleport) {
2819 if (creature == player) {
2820 if (oldStackPos >= 10) {
2821 sendMapDescription(newPos);
2822 } else if (teleport) {
2823 NetworkMessage msg;
2824 RemoveTileThing(msg, oldPos, oldStackPos);
2825 writeToOutputBuffer(msg);
2826 sendMapDescription(newPos);
2827 } else {
2828 NetworkMessage msg;
2829 if (oldPos.z == 7 && newPos.z >= 8) {
2830 RemoveTileThing(msg, oldPos, oldStackPos);
2831 } else {
2832 msg.addByte(0x6D);
2833 msg.addPosition(oldPos);
2834 msg.addByte(oldStackPos);
2835 msg.addPosition(newPos);
2836 if (otclientV8) {
2837 msg.add<uint16_t>(creature->getStepDuration(true));
2838 }
2839 }
2840
2841 if (newPos.z > oldPos.z) {
2842 MoveDownCreature(msg, creature, newPos, oldPos);
2843 } else if (newPos.z < oldPos.z) {
2844 MoveUpCreature(msg, creature, newPos, oldPos);
2845 }
2846
2847 if (oldPos.y > newPos.y) { // north, for old x
2848 msg.addByte(0x65);
2849 GetMapDescription(oldPos.x - awareRange.left(),
2850 newPos.y - awareRange.top(), newPos.z,
2851 awareRange.horizontal(), 1, msg);
2852 } else if (oldPos.y < newPos.y) { // south, for old x
2853 msg.addByte(0x67);
2854 GetMapDescription(oldPos.x - awareRange.left(),
2855 newPos.y + awareRange.bottom(), newPos.z,
2856 awareRange.horizontal(), 1, msg);
2857 }
2858
2859 if (oldPos.x < newPos.x) { // east, [with new y]
2860 msg.addByte(0x66);
2861 GetMapDescription(newPos.x + awareRange.right(),
2862 newPos.y - awareRange.top(), newPos.z, 1,
2863 awareRange.vertical(), msg);
2864 } else if (oldPos.x > newPos.x) { // west, [with new y]
2865 msg.addByte(0x68);
2866 GetMapDescription(newPos.x - awareRange.left(),
2867 newPos.y - awareRange.top(), newPos.z, 1,
2868 awareRange.vertical(), msg);
2869 }
2870 writeToOutputBuffer(msg);
2871 }
2872 } else if (canSee(oldPos) && canSee(creature->getPosition())) {
2873 if (teleport || (oldPos.z == 7 && newPos.z >= 8) || oldStackPos >= 10) {
2874 sendRemoveTileThing(oldPos, oldStackPos);
2875 sendAddCreature(creature, newPos, newStackPos, false);
2876 } else {
2877 NetworkMessage msg;
2878 msg.addByte(0x6D);
2879 msg.addPosition(oldPos);
2880 msg.addByte(oldStackPos);
2881 msg.addPosition(creature->getPosition());
2882 if (otclientV8) {
2883 msg.add<uint16_t>(creature->getStepDuration(true));
2884 }
2885 writeToOutputBuffer(msg);
2886 checkPredictiveWalking(oldPos);
2887 checkPredictiveWalking(newPos);
2888 }
2889 } else if (canSee(oldPos)) {
2890 sendRemoveTileThing(oldPos, oldStackPos);
2891 } else if (canSee(creature->getPosition())) {
2892 sendAddCreature(creature, newPos, newStackPos, false);
2893 }
2894}
2895
2896void ProtocolGame::sendInventoryItem(slots_t slot, const Item *item) {
2897 NetworkMessage msg;
2898 if (item) {
2899 msg.addByte(0x78);
2900 msg.addByte(slot);
2901 msg.addItem(item, otclientV8);
2902 } else {
2903 msg.addByte(0x79);
2904 msg.addByte(slot);
2905 }
2906 writeToOutputBuffer(msg);
2907}
2908
2909void ProtocolGame::sendItems() {
2910 NetworkMessage msg;
2911 msg.addByte(0xF5);
2912
2913 const std::vector<uint16_t> &inventory = Item::items.getInventory();
2914 msg.add<uint16_t>(inventory.size() + 11);
2915 for (uint16_t i = 1; i <= 11; i++) {
2916 msg.add<uint16_t>(i);
2917 msg.addByte(0); // always 0
2918 msg.add<uint16_t>(1); // always 1
2919 }
2920
2921 for (auto clientId : inventory) {
2922 msg.add<uint16_t>(clientId);
2923 msg.addByte(0); // always 0
2924 msg.add<uint16_t>(1);
2925 }
2926
2927 writeToOutputBuffer(msg);
2928}
2929
2930void ProtocolGame::sendAddContainerItem(uint8_t cid, uint16_t slot,
2931 const Item *item) {
2932 NetworkMessage msg;
2933 msg.addByte(0x70);
2934 msg.addByte(cid);
2935 msg.add<uint16_t>(slot);
2936 msg.addItem(item, otclientV8);
2937 writeToOutputBuffer(msg);
2938}
2939
2940void ProtocolGame::sendUpdateContainerItem(uint8_t cid, uint16_t slot,
2941 const Item *item) {
2942 NetworkMessage msg;
2943 msg.addByte(0x71);
2944 msg.addByte(cid);
2945 msg.add<uint16_t>(slot);
2946 msg.addItem(item, otclientV8);
2947 writeToOutputBuffer(msg);
2948}
2949
2950void ProtocolGame::sendRemoveContainerItem(uint8_t cid, uint16_t slot,
2951 const Item *lastItem) {
2952 NetworkMessage msg;
2953 msg.addByte(0x72);
2954 msg.addByte(cid);
2955 msg.add<uint16_t>(slot);
2956 if (lastItem) {
2957 msg.addItem(lastItem, otclientV8);
2958 } else {
2959 msg.add<uint16_t>(0x00);
2960 }
2961 writeToOutputBuffer(msg);
2962}
2963
2964void ProtocolGame::sendTextWindow(uint32_t windowTextId, Item *item,
2965 uint16_t maxlen, bool canWrite) {
2966 NetworkMessage msg;
2967 msg.addByte(0x96);
2968 msg.add<uint32_t>(windowTextId);
2969 msg.addItem(item, otclientV8);
2970
2971 if (canWrite) {
2972 msg.add<uint16_t>(maxlen);
2973 msg.addString(item->getText());
2974 } else {
2975 const std::string &text = item->getText();
2976 msg.add<uint16_t>(text.size());
2977 msg.addString(text);
2978 }
2979
2980 const std::string &writer = item->getWriter();
2981 if (!writer.empty()) {
2982 msg.addString(writer);
2983 } else {
2984 msg.add<uint16_t>(0x00);
2985 }
2986
2987 time_t writtenDate = item->getDate();
2988 if (writtenDate != 0) {
2989 msg.addString(formatDateShort(writtenDate));
2990 } else {
2991 msg.add<uint16_t>(0x00);
2992 }
2993
2994 writeToOutputBuffer(msg);
2995}
2996
2997void ProtocolGame::sendTextWindow(uint32_t windowTextId, uint32_t itemId,
2998 const std::string &text) {
2999 NetworkMessage msg;
3000 msg.addByte(0x96);
3001 msg.add<uint32_t>(windowTextId);
3002 msg.addItem(itemId, 1, otclientV8);
3003 msg.add<uint16_t>(text.size());
3004 msg.addString(text);
3005 msg.add<uint16_t>(0x00);
3006 msg.add<uint16_t>(0x00);
3007 writeToOutputBuffer(msg);
3008}
3009
3010void ProtocolGame::sendHouseWindow(uint32_t windowTextId,
3011 const std::string &text) {
3012 NetworkMessage msg;
3013 msg.addByte(0x97);
3014 msg.addByte(0x00);
3015 msg.add<uint32_t>(windowTextId);
3016 msg.addString(text);
3017 writeToOutputBuffer(msg);
3018}
3019
3020void ProtocolGame::sendOutfitWindow() {
3021 NetworkMessage msg;
3022 msg.addByte(0xC8);
3023
3024 Outfit_t currentOutfit = player->getDefaultOutfit();
3025 Mount *currentMount = g_game.mounts.getMountByID(player->getCurrentMount());
3026 if (currentMount) {
3027 currentOutfit.lookMount = currentMount->clientId;
3028 }
3029
3030 const Outfit *outfitDef = Outfits::getInstance().getOutfitByLookType(player->getSex(), currentOutfit.lookType);
3031 if (outfitDef) {
3032 currentOutfit.attackRange = outfitDef->attackRange;
3033 for (int d = 0; d < OUTFIT_DIRECTIONS; ++d) {
3034 currentOutfit.offsets[d] = outfitDef->offsets[d];
3035 }
3036 }
3037
3038 AddOutfit(msg, currentOutfit, player->getAttackOutfitLookType());
3039
3040 std::vector<ProtocolOutfit> protocolOutfits;
3041 if (player->isAccessPlayer()) {
3042 static const std::string gamemasterOutfitName = "Gamemaster";
3043 protocolOutfits.emplace_back(gamemasterOutfitName, 75, 0);
3044 }
3045
3046 const auto &outfits = Outfits::getInstance().getOutfits(player->getSex());
3047 protocolOutfits.reserve(outfits.size());
3048 for (const Outfit &outfit : outfits) {
3049 uint8_t addons;
3050 if (!player->getOutfitAddons(outfit, addons)) {
3051 continue;
3052 }
3053
3054 protocolOutfits.emplace_back(outfit.name, outfit.lookType, addons);
3055 if (protocolOutfits.size() ==
3056 500) { // Game client doesn't allow more than 500 outfits
3057 break;
3058 }
3059 }
3060
3061 msg.addByte(protocolOutfits.size());
3062 for (const ProtocolOutfit &outfit : protocolOutfits) {
3063 msg.add<uint16_t>(outfit.lookType);
3064 msg.addString(outfit.name);
3065 msg.addByte(outfit.addons);
3066 }
3067
3068 std::vector<const Mount *> mounts;
3069 for (const Mount &mount : g_game.mounts.getMounts()) {
3070 if (player->hasMount(&mount)) {
3071 mounts.push_back(&mount);
3072 }
3073 }
3074
3075 msg.addByte(mounts.size());
3076 for (const Mount *mount : mounts) {
3077 msg.add<uint16_t>(mount->clientId);
3078 msg.addString(mount->name);
3079 }
3080
3081 if (otclientV8) {
3082 std::vector<const Wing *> wings;
3083 for (const Wing &wing : g_game.wings.getWings()) {
3084 if (player->hasWing(&wing)) {
3085 wings.push_back(&wing);
3086 }
3087 }
3088
3089 msg.addByte(wings.size());
3090 for (const Wing *wing : wings) {
3091 msg.add<uint16_t>(wing->clientId);
3092 msg.addString(wing->name);
3093 }
3094
3095 std::vector<const Aura *> auras;
3096 for (const Aura &aura : g_game.auras.getAuras()) {
3097 if (player->hasAura(&aura)) {
3098 auras.push_back(&aura);
3099 }
3100 }
3101
3102 msg.addByte(auras.size());
3103 for (const Aura *aura : auras) {
3104 msg.add<uint16_t>(aura->clientId);
3105 msg.addString(aura->name);
3106 }
3107
3108 std::vector<const Shader *> shaders;
3109 for (const Shader &shader : g_game.shaders.getShaders()) {
3110 if (player->hasShader(&shader)) {
3111 shaders.push_back(&shader);
3112 }
3113 }
3114
3115 msg.addByte(shaders.size());
3116 for (const Shader *shader : shaders) {
3117 msg.add<uint16_t>(shader->id);
3118 msg.addString(shader->name);
3119 }
3120
3121 std::vector<const RuneEffect *> runeEffectList;
3122 for (const RuneEffect &re : g_game.runeEffects.getRuneEffects()) {
3123 if (player->hasRuneEffect(&re)) {
3124 runeEffectList.push_back(&re);
3125 }
3126 }
3127
3128 msg.addByte(runeEffectList.size());
3129 for (const RuneEffect *re : runeEffectList) {
3130 msg.add<uint16_t>(re->id);
3131 msg.addString(re->name);
3132 msg.addByte(static_cast<uint8_t>(std::min(255, static_cast<int>(re->opacity * 255))));
3133 msg.addByte(static_cast<uint8_t>(std::min(255, static_cast<int>(re->speed * 100))));
3134 msg.addByte(static_cast<uint8_t>(std::min(255, static_cast<int>(re->scale * 100))));
3135 // color RGB
3136 uint8_t cr = 0xFF, cg = 0xFF, cb = 0xFF;
3137 if (re->color.size() >= 7 && re->color[0] == '#') {
3138 cr = static_cast<uint8_t>(std::stoul(re->color.substr(1, 2), nullptr, 16));
3139 cg = static_cast<uint8_t>(std::stoul(re->color.substr(3, 2), nullptr, 16));
3140 cb = static_cast<uint8_t>(std::stoul(re->color.substr(5, 2), nullptr, 16));
3141 }
3142 msg.addByte(cr);
3143 msg.addByte(cg);
3144 msg.addByte(cb);
3145 msg.addByte(static_cast<uint8_t>(std::min(255, static_cast<int>(re->flicker * 255))));
3146 msg.addByte(static_cast<uint8_t>(std::min(255, static_cast<int>(re->colorShift * 255))));
3147 msg.addByte(re->layer == "front" ? 1 : 0);
3148 msg.add<int16_t>(re->bonusLoot);
3149 msg.add<int16_t>(re->bonusDamage);
3150 msg.add<int16_t>(re->bonusSpeed);
3151 msg.add<int16_t>(re->bonusXp);
3152 msg.add<uint16_t>(static_cast<uint16_t>(std::min<int32_t>(65535, re->damageInterval)));
3153 msg.add<uint16_t>(static_cast<uint16_t>(std::min<int32_t>(65535, re->damageDelay)));
3154
3155 // Multi-layer data
3156 uint8_t layerCount = static_cast<uint8_t>(std::min<size_t>(255, re->layers.size()));
3157 msg.addByte(layerCount);
3158 for (const RuneEffectLayer &rl : re->layers) {
3159 msg.add<uint16_t>(rl.effectId);
3160 msg.add<int16_t>(static_cast<int16_t>(std::max(-32768, std::min(32767, static_cast<int>(rl.offsetX * 10)))));
3161 msg.add<int16_t>(static_cast<int16_t>(std::max(-32768, std::min(32767, static_cast<int>(rl.offsetY * 10)))));
3162 msg.addByte(static_cast<uint8_t>(std::min(255, static_cast<int>(rl.speed * 100))));
3163 msg.add<uint16_t>(static_cast<uint16_t>(std::min(65535, static_cast<int>(rl.scale * 100))));
3164 msg.addByte(static_cast<uint8_t>(std::min(255, static_cast<int>(rl.opacity * 255))));
3165 uint8_t lcr = 0xFF, lcg = 0xFF, lcb = 0xFF;
3166 if (rl.color.size() >= 7 && rl.color[0] == '#') {
3167 lcr = static_cast<uint8_t>(std::stoul(rl.color.substr(1, 2), nullptr, 16));
3168 lcg = static_cast<uint8_t>(std::stoul(rl.color.substr(3, 2), nullptr, 16));
3169 lcb = static_cast<uint8_t>(std::stoul(rl.color.substr(5, 2), nullptr, 16));
3170 }
3171 msg.addByte(lcr);
3172 msg.addByte(lcg);
3173 msg.addByte(lcb);
3174 msg.addByte(static_cast<uint8_t>(std::min(255, static_cast<int>(rl.flicker * 255))));
3175 msg.addByte(static_cast<uint8_t>(std::min(255, static_cast<int>(rl.colorShift * 255))));
3176 msg.addByte(static_cast<uint8_t>(std::min(255, static_cast<int>(rl.drawOrder))));
3177 msg.addByte(rl.behind ? 1 : 0);
3178 bool hasTexture = !rl.texture.empty();
3179 msg.addByte(hasTexture ? 1 : 0);
3180 if (hasTexture) {
3181 msg.addString(rl.texture);
3182 }
3183 }
3184 }
3185 }
3186
3187 writeToOutputBuffer(msg);
3188}
3189
3190void ProtocolGame::sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus) {
3191 NetworkMessage msg;
3192 msg.addByte(0xD3);
3193 msg.add<uint32_t>(guid);
3194 msg.addByte(newStatus);
3195 writeToOutputBuffer(msg);
3196}
3197
3198void ProtocolGame::sendVIP(uint32_t guid, const std::string &name,
3199 const std::string &description, uint32_t icon,
3200 bool notify, VipStatus_t status) {
3201 NetworkMessage msg;
3202 msg.addByte(0xD2);
3203 msg.add<uint32_t>(guid);
3204 msg.addString(name);
3205 msg.addString(description);
3206 msg.add<uint32_t>(std::min<uint32_t>(10, icon));
3207 msg.addByte(notify ? 0x01 : 0x00);
3208 msg.addByte(status);
3209 writeToOutputBuffer(msg);
3210}
3211
3212void ProtocolGame::sendVIPEntries() {
3213 const std::forward_list<VIPEntry> &vipEntries =
3214 IOLoginData::getVIPEntries(player->getAccount());
3215
3216 for (const VIPEntry &entry : vipEntries) {
3217 VipStatus_t vipStatus = VIPSTATUS_ONLINE;
3218
3219 Player *vipPlayer = g_game.getPlayerByGUID(entry.guid);
3220
3221 if (!vipPlayer || vipPlayer->isInGhostMode() || player->isAccessPlayer()) {
3222 vipStatus = VIPSTATUS_OFFLINE;
3223 }
3224
3225 sendVIP(entry.guid, entry.name, entry.description, entry.icon, entry.notify,
3226 vipStatus);
3227 }
3228}
3229
3230void ProtocolGame::sendSpellCooldown(uint8_t spellId, uint32_t time) {
3231 NetworkMessage msg;
3232 msg.addByte(0xA4);
3233 msg.addByte(spellId);
3234 msg.add<uint32_t>(time);
3235 writeToOutputBuffer(msg);
3236}
3237
3238void ProtocolGame::sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time) {
3239 NetworkMessage msg;
3240 msg.addByte(0xA5);
3241 msg.addByte(groupId);
3242 msg.add<uint32_t>(time);
3243 writeToOutputBuffer(msg);
3244}
3245
3246void ProtocolGame::sendModalWindow(const ModalWindow &modalWindow) {
3247 NetworkMessage msg;
3248 msg.addByte(0xFA);
3249
3250 msg.add<uint32_t>(modalWindow.id);
3251 msg.addString(modalWindow.title);
3252 msg.addString(modalWindow.message);
3253
3254 msg.addByte(modalWindow.buttons.size());
3255 for (const auto &it : modalWindow.buttons) {
3256 msg.addString(it.first);
3257 msg.addByte(it.second);
3258 }
3259
3260 msg.addByte(modalWindow.choices.size());
3261 for (const auto &it : modalWindow.choices) {
3262 msg.addString(it.first);
3263 msg.addByte(it.second);
3264 }
3265
3266 msg.addByte(modalWindow.defaultEscapeButton);
3267 msg.addByte(modalWindow.defaultEnterButton);
3268 msg.addByte(modalWindow.priority ? 0x01 : 0x00);
3269
3270 writeToOutputBuffer(msg);
3271}
3272
3273////////////// Add common messages
3274void ProtocolGame::AddCreature(NetworkMessage &msg, const Creature *creature,
3275 bool known, uint32_t remove) {
3276 CreatureType_t creatureType = creature->getType();
3277
3278 const Player *otherPlayer = creature->getPlayer();
3279
3280 if (known) {
3281 msg.add<uint16_t>(0x62);
3282 msg.add<uint32_t>(creature->getID());
3283 } else {
3284 msg.add<uint16_t>(0x61);
3285 msg.add<uint32_t>(remove);
3286 msg.add<uint32_t>(creature->getID());
3287 msg.addByte(creatureType);
3288 msg.addString(creature->getName());
3289 }
3290
3291 if (creature->isHealthHidden()) {
3292 msg.addByte(0x00);
3293 } else {
3294 msg.addByte(std::ceil((static_cast<double>(creature->getHealth()) /
3295 std::max<int32_t>(creature->getMaxHealth(), 1)) *
3296 100));
3297 }
3298
3299 msg.addByte(creature->getDirection());
3300
3301 if (!creature->isInGhostMode() && !creature->isInvisible()) {
3302 Outfit_t creatureOutfit = creature->getCurrentOutfit();
3303 const Player *creaturePlayer = creature->getPlayer();
3304 if (creaturePlayer) {
3305 const Outfit *outfitDef = Outfits::getInstance().getOutfitByLookType(creaturePlayer->getSex(), creatureOutfit.lookType);
3306 if (outfitDef) {
3307 creatureOutfit.attackRange = outfitDef->attackRange;
3308 for (int d = 0; d < OUTFIT_DIRECTIONS; ++d) {
3309 creatureOutfit.offsets[d] = outfitDef->offsets[d];
3310 }
3311 }
3312 }
3313 AddOutfit(msg, creatureOutfit,
3314 creature->getAttackOutfitLookType());
3315 } else {
3316 static Outfit_t outfit;
3317 AddOutfit(msg, outfit, creature->getAttackOutfitLookType());
3318 }
3319
3320 LightInfo lightInfo = creature->getCreatureLight();
3321 msg.addByte(lightInfo.level);
3322 msg.addByte(lightInfo.color);
3323
3324 msg.add<uint16_t>(creature->getStepSpeed() / 2);
3325
3326 msg.addByte(player->getSkullClient(creature));
3327 msg.addByte(player->getPartyShield(otherPlayer));
3328
3329 if (!known) {
3330 msg.addByte(player->getGuildEmblem(otherPlayer));
3331 }
3332
3333 if (creatureType == CREATURETYPE_MONSTER) {
3334 const Creature *master = creature->getMaster();
3335 if (master) {
3336 const Player *masterPlayer = master->getPlayer();
3337 if (masterPlayer) {
3338 if (masterPlayer == player) {
3339 creatureType = CREATURETYPE_SUMMON_OWN;
3340 } else {
3341 creatureType = CREATURETYPE_SUMMON_OTHERS;
3342 }
3343 }
3344 }
3345 }
3346
3347 msg.addByte(creatureType); // Type (for summons)
3348 msg.addByte(creature->getSpeechBubble());
3349 msg.addByte(0xFF); // MARK_UNMARKED
3350
3351 if (otherPlayer) {
3352 msg.add<uint16_t>(otherPlayer->getHelpers());
3353 } else {
3354 msg.add<uint16_t>(0x00);
3355 }
3356
3357 msg.addByte(player->canWalkthroughEx(creature) ? 0x00 : 0x01);
3358}
3359
3360void ProtocolGame::AddPlayerStats(NetworkMessage &msg) {
3361 msg.addByte(0xA0);
3362
3363 msg.add<uint16_t>(std::min<int32_t>(player->getHealth(),
3364 std::numeric_limits<uint16_t>::max()));
3365 msg.add<uint16_t>(std::min<int32_t>(player->getMaxHealth(),
3366 std::numeric_limits<uint16_t>::max()));
3367
3368 msg.add<uint32_t>(player->getFreeCapacity());
3369 msg.add<uint32_t>(player->getCapacity());
3370
3371 msg.add<uint64_t>(player->getExperience());
3372
3373 msg.add<uint16_t>(player->getLevel());
3374 msg.addByte(player->getLevelPercent());
3375
3376 msg.add<uint16_t>(100); // base xp gain rate
3377 msg.add<uint16_t>(0); // xp voucher
3378 msg.add<uint16_t>(0); // low level bonus
3379 msg.add<uint16_t>(0); // xp boost
3380 msg.add<uint16_t>(100); // stamina multiplier (100 = x1.0)
3381
3382 msg.add<uint16_t>(std::min<int32_t>(player->getMana(),
3383 std::numeric_limits<uint16_t>::max()));
3384 msg.add<uint16_t>(std::min<int32_t>(player->getMaxMana(),
3385 std::numeric_limits<uint16_t>::max()));
3386
3387 msg.addByte(std::min<uint32_t>(player->getMagicLevel(),
3388 std::numeric_limits<uint8_t>::max()));
3389 msg.addByte(std::min<uint32_t>(player->getBaseMagicLevel(),
3390 std::numeric_limits<uint8_t>::max()));
3391 msg.addByte(player->getMagicLevelPercent());
3392
3393 msg.addByte(player->getSoul());
3394
3395 msg.add<uint16_t>(player->getStaminaMinutes());
3396
3397 msg.add<uint16_t>(player->getBaseSpeed() / 2);
3398
3399 Condition *condition = player->getCondition(CONDITION_REGENERATION);
3400 msg.add<uint16_t>(condition ? condition->getTicks() / 1000 : 0x00);
3401
3402 msg.add<uint16_t>(player->getOfflineTrainingTime() / 60 / 1000);
3403
3404 msg.add<uint16_t>(0); // xp boost time (seconds)
3405 msg.addByte(0); // enables exp boost in the store
3406}
3407
3408void ProtocolGame::AddPlayerSkills(NetworkMessage &msg) {
3409 msg.addByte(0xA1);
3410
3411 for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) {
3412 msg.add<uint16_t>(std::min<int32_t>(player->getSkillLevel(i),
3413 std::numeric_limits<uint16_t>::max()));
3414 msg.add<uint16_t>(player->getBaseSkill(i));
3415 msg.addByte(player->getSkillPercent(i));
3416 }
3417
3418 for (uint8_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) {
3419 msg.add<uint16_t>(std::min<int32_t>(100, player->varSpecialSkills[i]));
3420 msg.add<uint16_t>(0);
3421 }
3422}
3423
3424void ProtocolGame::AddOutfit(NetworkMessage &msg, const Outfit_t &outfit,
3425 uint16_t lookAttack) {
3426 msg.add<uint16_t>(outfit.lookType);
3427
3428 if (outfit.lookType != 0) {
3429 msg.addByte(outfit.lookHead);
3430 msg.addByte(outfit.lookBody);
3431 msg.addByte(outfit.lookLegs);
3432 msg.addByte(outfit.lookFeet);
3433 msg.addByte(outfit.lookAddons);
3434 msg.add<uint16_t>(lookAttack);
3435 } else {
3436 msg.addItemId(outfit.lookTypeEx);
3437 }
3438
3439 msg.add<uint16_t>(outfit.lookMount);
3440 if (otclientV8) {
3441 msg.add<uint16_t>(outfit.lookWings);
3442 msg.add<uint16_t>(outfit.lookAura);
3443 Shader *shader = g_game.shaders.getShaderByID(outfit.lookShader);
3444 msg.addString(shader ? shader->name : "");
3445 msg.add<uint16_t>(outfit.lookRuneEffect);
3446 msg.addByte(outfit.attackRange);
3447 for (int d = 0; d < OUTFIT_DIRECTIONS; ++d) {
3448 msg.add<int16_t>(outfit.offsets[d].lookX);
3449 msg.add<int16_t>(outfit.offsets[d].lookY);
3450 msg.add<int16_t>(outfit.offsets[d].meleeX);
3451 msg.add<int16_t>(outfit.offsets[d].meleeY);
3452 msg.add<int16_t>(outfit.offsets[d].rangeX);
3453 msg.add<int16_t>(outfit.offsets[d].rangeY);
3454 }
3455 }
3456}
3457
3458void ProtocolGame::AddWorldLight(NetworkMessage &msg, LightInfo lightInfo) {
3459 msg.addByte(0x82);
3460 msg.addByte(lightInfo.level);
3461 msg.addByte(lightInfo.color);
3462}
3463
3464void ProtocolGame::AddCreatureLight(NetworkMessage &msg,
3465 const Creature *creature) {
3466 LightInfo lightInfo = creature->getCreatureLight();
3467
3468 msg.addByte(0x8D);
3469 msg.add<uint32_t>(creature->getID());
3470 msg.addByte(lightInfo.level);
3471 msg.addByte(lightInfo.color);
3472}
3473
3474// tile
3475void ProtocolGame::RemoveTileThing(NetworkMessage &msg, const Position &pos,
3476 uint32_t stackpos) {
3477 if (stackpos >= 10) {
3478 return;
3479 }
3480
3481 msg.addByte(0x6C);
3482 msg.addPosition(pos);
3483 msg.addByte(stackpos);
3484}
3485
3486void ProtocolGame::MoveUpCreature(NetworkMessage &msg, const Creature *creature,
3487 const Position &newPos,
3488 const Position &oldPos) {
3489 if (creature != player) {
3490 return;
3491 }
3492
3493 // floor change up
3494 msg.addByte(0xBE);
3495
3496 // going to surface
3497 if (newPos.z == 7) {
3498 int32_t skip = -1;
3499 if (otclientV8) {
3500 for (int z = 5; z >= 0; --z) {
3501 sendFloorDescription(oldPos, z);
3502 }
3503 } else {
3504 GetFloorDescription(msg, oldPos.x - awareRange.left(),
3505 oldPos.y - awareRange.top(), 5,
3506 awareRange.horizontal(), awareRange.vertical(), 3,
3507 skip); //(floor 7 and 6 already set)
3508 GetFloorDescription(
3509 msg, oldPos.x - awareRange.left(), oldPos.y - awareRange.top(), 4,
3510 awareRange.horizontal(), awareRange.vertical(), 4, skip);
3511 GetFloorDescription(
3512 msg, oldPos.x - awareRange.left(), oldPos.y - awareRange.top(), 3,
3513 awareRange.horizontal(), awareRange.vertical(), 5, skip);
3514 GetFloorDescription(
3515 msg, oldPos.x - awareRange.left(), oldPos.y - awareRange.top(), 2,
3516 awareRange.horizontal(), awareRange.vertical(), 6, skip);
3517 GetFloorDescription(
3518 msg, oldPos.x - awareRange.left(), oldPos.y - awareRange.top(), 1,
3519 awareRange.horizontal(), awareRange.vertical(), 7, skip);
3520 GetFloorDescription(
3521 msg, oldPos.x - awareRange.left(), oldPos.y - awareRange.top(), 0,
3522 awareRange.horizontal(), awareRange.vertical(), 8, skip);
3523 if (skip >= 0) {
3524 msg.addByte(skip);
3525 msg.addByte(0xFF);
3526 }
3527 }
3528 }
3529 // underground, going one floor up (still underground)
3530 else if (newPos.z > 7) {
3531 int32_t skip = -1;
3532 GetFloorDescription(msg, oldPos.x - awareRange.left(),
3533 oldPos.y - awareRange.top(), oldPos.getZ() - 6,
3534 awareRange.horizontal(), awareRange.vertical(), 6,
3535 skip);
3536
3537 if (skip >= 0) {
3538 msg.addByte(skip);
3539 msg.addByte(0xFF);
3540 }
3541 }
3542
3543 // moving up a floor up makes us out of sync
3544 // west
3545 msg.addByte(0x68);
3546 GetMapDescription(oldPos.x - awareRange.left(),
3547 oldPos.y - (awareRange.top() - 1), newPos.z, 1,
3548 awareRange.vertical(), msg);
3549
3550 // north
3551 msg.addByte(0x65);
3552 GetMapDescription(oldPos.x - awareRange.left(), oldPos.y - awareRange.top(),
3553 newPos.z, awareRange.horizontal(), 1, msg);
3554}
3555
3556void ProtocolGame::MoveDownCreature(NetworkMessage &msg,
3557 const Creature *creature,
3558 const Position &newPos,
3559 const Position &oldPos) {
3560 if (creature != player) {
3561 return;
3562 }
3563
3564 // floor change down
3565 msg.addByte(0xBF);
3566
3567 // going from surface to underground
3568 if (newPos.z == 8) {
3569 int32_t skip = -1;
3570
3571 if (otclientV8) {
3572 for (int z = 0; z < 3; ++z) {
3573 sendFloorDescription(oldPos, newPos.z + z);
3574 }
3575 } else {
3576 GetFloorDescription(
3577 msg, oldPos.x - awareRange.left(), oldPos.y - awareRange.top(),
3578 newPos.z, awareRange.horizontal(), awareRange.vertical(), -1, skip);
3579 GetFloorDescription(msg, oldPos.x - awareRange.left(),
3580 oldPos.y - awareRange.top(), newPos.z + 1,
3581 awareRange.horizontal(), awareRange.vertical(), -2,
3582 skip);
3583 GetFloorDescription(msg, oldPos.x - awareRange.left(),
3584 oldPos.y - awareRange.top(), newPos.z + 2,
3585 awareRange.horizontal(), awareRange.vertical(), -3,
3586 skip);
3587 if (skip >= 0) {
3588 msg.addByte(skip);
3589 msg.addByte(0xFF);
3590 }
3591 }
3592 }
3593 // going further down
3594 else if (newPos.z > oldPos.z && newPos.z > 8 && newPos.z < 11) {
3595 int32_t skip = -1;
3596 GetFloorDescription(
3597 msg, oldPos.x - awareRange.left(), oldPos.y - awareRange.top(),
3598 newPos.z + 5, awareRange.horizontal(), awareRange.vertical(), -6, skip);
3599
3600 if (skip >= 0) {
3601 msg.addByte(skip);
3602 msg.addByte(0xFF);
3603 }
3604 }
3605
3606 // moving down a floor makes us out of sync
3607 // east
3608 msg.addByte(0x66);
3609 GetMapDescription(newPos.x + awareRange.right(), newPos.y - awareRange.top(),
3610 newPos.z, 1, awareRange.vertical(), msg);
3611
3612 // south
3613 msg.addByte(0x67);
3614 GetMapDescription(newPos.x - awareRange.left(), newPos.y - awareRange.top(),
3615 newPos.z, 1, awareRange.vertical(), msg);
3616}
3617
3618void ProtocolGame::AddShopItem(NetworkMessage &msg, const ShopInfo &item) {
3619 const ItemType &it = Item::items[item.itemId];
3620 msg.add<uint16_t>(it.clientId);
3621
3622 if (it.isSplash() || it.isFluidContainer()) {
3623 msg.addByte(serverFluidToClient(item.subType));
3624 } else {
3625 msg.addByte(0x00);
3626 }
3627
3628 msg.addString(item.realName);
3629 msg.add<uint32_t>(it.weight);
3630 msg.add<uint32_t>(item.buyPrice);
3631 msg.add<uint32_t>(item.sellPrice);
3632}
3633
3634void ProtocolGame::parseExtendedOpcode(NetworkMessage &msg) {
3635 uint8_t opcode = msg.getByte();
3636 const std::string &buffer = msg.getString();
3637
3638 // process additional opcodes via lua script event
3639 addGameTask(&Game::parsePlayerExtendedOpcode, player->getID(), opcode,
3640 buffer);
3641}
3642
3643// OTCv8
3644void ProtocolGame::sendFeatures() {
3645 if (!otclientV8)
3646 return;
3647
3648 std::map<GameFeature, bool> features;
3649 // place for non-standard OTCv8 features
3650 features[GameExtendedOpcode] = true;
3651 features[GameChangeMapAwareRange] = true;
3652 features[GameNewWalking] = true;
3653 features[GameEnvironmentEffect] =
3654 false; // disable it, useless 2 byte with every tile
3655 features[GameExtendedClientPing] = true;
3656 features[GameItemTooltip] = true; // fully available from version 2.6
3657 features[GameWingsAndAura] = true;
3658 features[GameOutfitShaders] = true;
3659
3660 // packet compression
3661 // we don't send feature, because feature assumes all packets are compressed
3662 // if adler32 is enabled then compression can be detected automaticly, just
3663 // adlre32 must be 0
3664 if (g_config.getBoolean(ConfigManager::PACKET_COMPRESSION)) {
3665 enableCompression();
3666 }
3667
3668 if (features.empty())
3669 return;
3670
3671 auto msg = getOutputBuffer(1024);
3672 msg->addByte(0x43);
3673 msg->add<uint16_t>(features.size());
3674 for (auto &feature : features) {
3675 msg->addByte((uint8_t)feature.first);
3676 msg->addByte(feature.second ? 1 : 0);
3677 }
3678
3679 send(std::move(getCurrentBuffer())); // send this packet immediately
3680}
3681
3682void ProtocolGame::parseChangeAwareRange(NetworkMessage &msg) {
3683 uint8_t width = msg.get<uint8_t>();
3684 uint8_t height = msg.get<uint8_t>();
3685
3686 g_dispatcher.addTask(createTask(
3687 std::bind(&ProtocolGame::updateAwareRange, getThis(), width, height)));
3688}
3689
3690void ProtocolGame::updateAwareRange(int width, int height) {
3691 if (!otclientV8)
3692 return;
3693
3694 // If you want to change max awareRange, edit maxViewportX, maxViewportY,
3695 // maxClientViewportX, maxClientViewportY in map.h
3696 awareRange.width =
3697 std::min(Map::maxViewportX * 2 - 1,
3698 std::min(Map::maxClientViewportX * 2 + 1, std::max(15, width)));
3699 awareRange.height =
3700 std::min(Map::maxViewportY * 2 - 1,
3701 std::min(Map::maxClientViewportY * 2 + 1, std::max(11, height)));
3702 // numbers must be odd
3703 if (awareRange.width % 2 != 1)
3704 awareRange.width -= 1;
3705 if (awareRange.height % 2 != 1)
3706 awareRange.height -= 1;
3707
3708 sendAwareRange();
3709 sendMapDescription(player->getPosition()); // refresh map
3710}
3711
3712void ProtocolGame::sendAwareRange() {
3713 if (!otclientV8)
3714 return;
3715
3716 NetworkMessage msg;
3717 msg.addByte(0x42);
3718 msg.add<uint8_t>(awareRange.width);
3719 msg.add<uint8_t>(awareRange.height);
3720 writeToOutputBuffer(msg);
3721}
3722
3723void ProtocolGame::parseNewWalking(NetworkMessage &msg) {
3724 uint32_t playerWalkId = msg.get<uint32_t>();
3725 int32_t predictiveWalkId =
3726 msg.get<int32_t>(); // extension for proxy system, currently not used
3727 Position playerPosition = msg.getPosition(); // local player position before
3728 // moving, including prewalk
3729 uint8_t flags = msg.getByte(); // 0x01 - prewalk, 0x02 - autowalk
3730
3731 uint16_t numdirs = msg.get<uint16_t>();
3732 if (numdirs == 0 || numdirs > 4096) {
3733 return;
3734 }
3735
3736 std::list<Direction> path;
3737 for (uint16_t i = 0; i < numdirs; ++i) {
3738 uint8_t rawdir = msg.getByte();
3739 switch (rawdir) {
3740 case 1:
3741 path.push_back(DIRECTION_EAST);
3742 break;
3743 case 2:
3744 path.push_back(DIRECTION_NORTHEAST);
3745 break;
3746 case 3:
3747 path.push_back(DIRECTION_NORTH);
3748 break;
3749 case 4:
3750 path.push_back(DIRECTION_NORTHWEST);
3751 break;
3752 case 5:
3753 path.push_back(DIRECTION_WEST);
3754 break;
3755 case 6:
3756 path.push_back(DIRECTION_SOUTHWEST);
3757 break;
3758 case 7:
3759 path.push_back(DIRECTION_SOUTH);
3760 break;
3761 case 8:
3762 path.push_back(DIRECTION_SOUTHEAST);
3763 break;
3764 default:
3765 break;
3766 }
3767 }
3768
3769 uint32_t playerId = player->getID();
3770
3771 auto self(getThis());
3772 g_dispatcher.addTask(createTask([self, playerWalkId, predictiveWalkId,
3773 playerId, playerPosition, flags, path] {
3774 bool preWalk = flags & 0x01;
3775 Position destination = getNextPosition(*(path.begin()), playerPosition);
3776 if (preWalk && predictiveWalkId < self->walkMatrix.get(destination)) {
3777 self->walkId += 1;
3778 self->sendWalkId();
3779 return;
3780 }
3781
3782 if (playerWalkId < self->walkId) {
3783 // this walk has been sent before player received previous newCancelWalk,
3784 // so it's invalid, ignore it
3785 return;
3786 }
3787
3788 g_game.playerNewWalk(playerId, playerPosition, flags, path);
3789 }));
3790}
3791
3792void ProtocolGame::checkPredictiveWalking(const Position &pos) {
3793 if (!otclientV8)
3794 return;
3795
3796 if (walkMatrix.inRange(pos) && !g_game.map.canWalkTo(*player, pos)) {
3797 int newValue = walkMatrix.update(pos);
3798 sendPredictiveCancel(pos, newValue);
3799 }
3800}
3801
3802void ProtocolGame::sendPredictiveCancel(const Position &pos, int value) {
3803 (void)value;
3804
3805 if (!otclientV8)
3806 return;
3807
3808 NetworkMessage msg;
3809 msg.addByte(0x46);
3810 msg.addPosition(pos);
3811 msg.addByte(player->getDirection());
3812 writeToOutputBuffer(msg);
3813}
3814
3815void ProtocolGame::sendWalkId() {
3816 if (!otclientV8)
3817 return;
3818
3819 NetworkMessage msg;
3820 msg.addByte(0x47);
3821 msg.add<uint32_t>(walkId);
3822 writeToOutputBuffer(msg);
3823}
3824
3825void ProtocolGame::sendNewCancelWalk() {
3826 if (!otclientV8)
3827 return;
3828
3829 NetworkMessage msg;
3830 msg.addByte(0x45);
3831 msg.addByte(player->getDirection());
3832 writeToOutputBuffer(msg);
3833 walkId += 1;
3834 sendWalkId();
3835}
3836
3837void ProtocolGame::parseNewPing(NetworkMessage &msg) {
3838 uint32_t pingId = msg.get<uint32_t>();
3839 uint16_t localPing = msg.get<uint16_t>();
3840 uint16_t fps = msg.get<uint16_t>();
3841
3842 addGameTask(&Game::playerReceiveNewPing, player->getID(), localPing, fps);
3843 g_dispatcher.addTask(
3844 createTask(std::bind(&ProtocolGame::sendNewPing, getThis(), pingId)));
3845}
3846
3847void ProtocolGame::sendNewPing(uint32_t pingId) {
3848 if (!otclientV8)
3849 return;
3850
3851 NetworkMessage msg;
3852 msg.addByte(0x40);
3853 msg.add<uint32_t>(pingId);
3854 writeToOutputBuffer(msg);
3855}
3856