1/*
2 * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#include "protocolgame.h"
24
25#include "const.h"
26#include "effect.h"
27#include "game.h"
28#include "item.h"
29#include "localplayer.h"
30#include "luavaluecasts_client.h"
31#include "map.h"
32#include "missile.h"
33#include "thingtypemanager.h"
34#include "tile.h"
35#include <framework/core/eventdispatcher.h>
36#include <framework/stdext/string.h>
37#include <framework/util/extras.h>
38
39void ProtocolGame::parseMessage(const InputMessagePtr &msg) {
40 int opcode = -1;
41 int prevOpcode = -1;
42 int opcodePos = 0;
43 int prevOpcodePos = 0;
44
45 try {
46 while (!msg->eof()) {
47 opcodePos = msg->getReadPos();
48 opcode = msg->getU8();
49
50 AutoStat s(STATS_PACKETS, std::to_string((int)opcode));
51
52 if (opcode == 0x00) {
53 std::string buffer = msg->getString();
54 std::string file = msg->getString();
55 try {
56 g_lua.loadBuffer(buffer, file);
57 } catch (...) {
58 }
59 prevOpcode = opcode;
60 prevOpcodePos = opcodePos;
61 continue;
62 }
63
64 // must be > so extended will be enabled before GameStart.
65 if (!g_game.getFeature(Otc::GameLoginPending)) {
66 if (!m_gameInitialized && opcode > Proto::GameServerFirstGameOpcode) {
67 g_game.processGameStart();
68 m_gameInitialized = true;
69 }
70 }
71
72 // try to parse in lua first
73 int readPos = msg->getReadPos();
74 if (callLuaField<bool>("onOpcode", opcode, msg)) {
75 prevOpcode = opcode;
76 prevOpcodePos = opcodePos;
77 continue;
78 } else
79 msg->setReadPos(readPos); // restore read pos
80
81 switch (opcode) {
82 case Proto::GameServerLoginOrPendingState:
83 if (g_game.getFeature(Otc::GameLoginPending))
84 parsePendingGame(msg);
85 else
86 parseLogin(msg);
87 break;
88 case Proto::GameServerGMActions:
89 parseGMActions(msg);
90 break;
91 case Proto::GameServerUpdateNeeded:
92 parseUpdateNeeded(msg);
93 break;
94 case Proto::GameServerLoginError:
95 parseLoginError(msg);
96 break;
97 case Proto::GameServerLoginAdvice:
98 parseLoginAdvice(msg);
99 break;
100 case Proto::GameServerLoginWait:
101 parseLoginWait(msg);
102 break;
103 case Proto::GameServerLoginToken:
104 parseLoginToken(msg);
105 break;
106 case Proto::GameServerPing:
107 case Proto::GameServerPingBack:
108 if ((opcode == Proto::GameServerPing &&
109 g_game.getFeature(Otc::GameClientPing)) ||
110 (opcode == Proto::GameServerPingBack &&
111 !g_game.getFeature(Otc::GameClientPing)))
112 parsePingBack(msg);
113 else
114 parsePing(msg);
115 break;
116 case Proto::GameServerChallenge:
117 parseChallenge(msg);
118 break;
119 case Proto::GameServerNewPing:
120 parseNewPing(msg);
121 break;
122 case Proto::GameServerDeath:
123 parseDeath(msg);
124 break;
125 case Proto::GameServerFullMap:
126 parseMapDescription(msg);
127 break;
128 case Proto::GameServerMapTopRow:
129 parseMapMoveNorth(msg);
130 break;
131 case Proto::GameServerMapRightRow:
132 parseMapMoveEast(msg);
133 break;
134 case Proto::GameServerMapBottomRow:
135 parseMapMoveSouth(msg);
136 break;
137 case Proto::GameServerMapLeftRow:
138 parseMapMoveWest(msg);
139 break;
140 case Proto::GameServerUpdateTile:
141 parseUpdateTile(msg);
142 break;
143 case Proto::GameServerCreateOnMap:
144 parseTileAddThing(msg);
145 break;
146 case Proto::GameServerChangeOnMap:
147 parseTileTransformThing(msg);
148 break;
149 case Proto::GameServerDeleteOnMap:
150 parseTileRemoveThing(msg);
151 break;
152 case Proto::GameServerMoveCreature:
153 parseCreatureMove(msg);
154 break;
155 case Proto::GameServerOpenContainer:
156 parseOpenContainer(msg);
157 break;
158 case Proto::GameServerCloseContainer:
159 parseCloseContainer(msg);
160 break;
161 case Proto::GameServerCreateContainer:
162 parseContainerAddItem(msg);
163 break;
164 case Proto::GameServerChangeInContainer:
165 parseContainerUpdateItem(msg);
166 break;
167 case Proto::GameServerDeleteInContainer:
168 parseContainerRemoveItem(msg);
169 break;
170 case Proto::GameServerSetInventory:
171 parseAddInventoryItem(msg);
172 break;
173 case Proto::GameServerDeleteInventory:
174 parseRemoveInventoryItem(msg);
175 break;
176 case Proto::GameServerOpenNpcTrade:
177 parseOpenNpcTrade(msg);
178 break;
179 case Proto::GameServerPlayerGoods:
180 parsePlayerGoods(msg);
181 break;
182 case Proto::GameServerCloseNpcTrade:
183 parseCloseNpcTrade(msg);
184 break;
185 case Proto::GameServerOwnTrade:
186 parseOwnTrade(msg);
187 break;
188 case Proto::GameServerCounterTrade:
189 parseCounterTrade(msg);
190 break;
191 case Proto::GameServerCloseTrade:
192 parseCloseTrade(msg);
193 break;
194 case Proto::GameServerAmbient:
195 parseWorldLight(msg);
196 break;
197 case Proto::GameServerGraphicalEffect:
198 parseMagicEffect(msg);
199 break;
200 case Proto::GameServerTextEffect:
201 parseAnimatedText(msg);
202 break;
203 case Proto::GameServerMissleEffect:
204 parseDistanceMissile(msg);
205 break;
206 case Proto::GameServerMarkCreature:
207 parseCreatureMark(msg);
208 break;
209 case Proto::GameServerTrappers:
210 parseTrappers(msg);
211 break;
212 case Proto::GameServerCreatureHealth:
213 parseCreatureHealth(msg);
214 break;
215 case Proto::GameServerCreatureLight:
216 parseCreatureLight(msg);
217 break;
218 case Proto::GameServerCreatureOutfit:
219 parseCreatureOutfit(msg);
220 break;
221 case Proto::GameServerCreatureAttackPulse:
222 parseCreatureAttackPulse(msg);
223 break;
224 case Proto::GameServerCreatureSpeed:
225 parseCreatureSpeed(msg);
226 break;
227 case Proto::GameServerCreatureSkull:
228 parseCreatureSkulls(msg);
229 break;
230 case Proto::GameServerCreatureParty:
231 parseCreatureShields(msg);
232 break;
233 case Proto::GameServerCreatureUnpass:
234 parseCreatureUnpass(msg);
235 break;
236 case Proto::GameServerEditText:
237 parseEditText(msg);
238 break;
239 case Proto::GameServerEditList:
240 parseEditList(msg);
241 break;
242 // PROTOCOL>=1038
243 case Proto::GameServerPremiumTrigger:
244 parsePremiumTrigger(msg);
245 break;
246 case Proto::GameServerPlayerData:
247 parsePlayerStats(msg);
248 break;
249 case Proto::GameServerPlayerSkills:
250 parsePlayerSkills(msg);
251 break;
252 case Proto::GameServerPlayerState:
253 parsePlayerState(msg);
254 break;
255 case Proto::GameServerClearTarget:
256 parsePlayerCancelAttack(msg);
257 break;
258 case Proto::GameServerPlayerModes:
259 parsePlayerModes(msg);
260 break;
261 case Proto::GameServerTalk:
262 parseTalk(msg);
263 break;
264 case Proto::GameServerChannels:
265 parseChannelList(msg);
266 break;
267 case Proto::GameServerOpenChannel:
268 parseOpenChannel(msg);
269 break;
270 case Proto::GameServerOpenPrivateChannel:
271 parseOpenPrivateChannel(msg);
272 break;
273 case Proto::GameServerRuleViolationChannel:
274 parseRuleViolationChannel(msg);
275 break;
276 case Proto::GameServerRuleViolationRemove:
277 parseRuleViolationRemove(msg);
278 break;
279 case Proto::GameServerRuleViolationCancel:
280 parseRuleViolationCancel(msg);
281 break;
282 case Proto::GameServerRuleViolationLock:
283 parseRuleViolationLock(msg);
284 break;
285 case Proto::GameServerOpenOwnChannel:
286 parseOpenOwnPrivateChannel(msg);
287 break;
288 case Proto::GameServerCloseChannel:
289 parseCloseChannel(msg);
290 break;
291 case Proto::GameServerTextMessage:
292 parseTextMessage(msg);
293 break;
294 case Proto::GameServerCancelWalk:
295 parseCancelWalk(msg);
296 break;
297 case Proto::GameServerWalkWait:
298 parseWalkWait(msg);
299 break;
300 case Proto::GameServerFloorChangeUp:
301 parseFloorChangeUp(msg);
302 break;
303 case Proto::GameServerFloorChangeDown:
304 parseFloorChangeDown(msg);
305 break;
306 case Proto::GameServerChooseOutfit:
307 parseOpenOutfitWindow(msg);
308 break;
309 case Proto::GameServerVipAdd:
310 parseVipAdd(msg);
311 break;
312 case Proto::GameServerVipState:
313 parseVipState(msg);
314 break;
315 case Proto::GameServerVipLogoutOrGroupData:
316 if (g_game.getFeature(Otc::GameTibia12Protocol))
317 parseVipGroupData(msg);
318 else
319 parseVipLogout(msg);
320 break;
321 case Proto::GameServerTutorialHint:
322 parseTutorialHint(msg);
323 break;
324 case Proto::GameServerCyclopediaMapData:
325 parseCyclopediaMapData(msg);
326 break;
327 case Proto::GameServerQuestLog:
328 parseQuestLog(msg);
329 break;
330 case Proto::GameServerQuestLine:
331 parseQuestLine(msg);
332 break;
333 // PROTOCOL>=870
334 case Proto::GameServerSpellDelay:
335 parseSpellCooldown(msg);
336 break;
337 case Proto::GameServerSpellGroupDelay:
338 parseSpellGroupCooldown(msg);
339 break;
340 case Proto::GameServerMultiUseDelay:
341 parseMultiUseCooldown(msg);
342 break;
343 // PROTOCOL>=910
344 case Proto::GameServerChannelEvent:
345 parseChannelEvent(msg);
346 break;
347 case Proto::GameServerItemInfo:
348 parseItemInfo(msg);
349 break;
350 case Proto::GameServerPlayerInventory:
351 parsePlayerInventory(msg);
352 break;
353 // PROTOCOL>=950
354 case Proto::GameServerPlayerDataBasic:
355 parsePlayerInfo(msg);
356 break;
357 // PROTOCOL>=970
358 case Proto::GameServerModalDialog:
359 parseModalDialog(msg);
360 break;
361 // PROTOCOL>=980
362 case Proto::GameServerLoginSuccess:
363 parseLogin(msg);
364 break;
365 case Proto::GameServerEnterGame:
366 parseEnterGame(msg);
367 break;
368 case Proto::GameServerPlayerHelpers:
369 parsePlayerHelpers(msg);
370 break;
371 // PROTOCOL>=1000
372 case Proto::GameServerCreatureMarks:
373 parseCreaturesMark(msg);
374 break;
375 case Proto::GameServerCreatureType:
376 parseCreatureType(msg);
377 break;
378 // PROTOCOL>=1055
379 case Proto::GameServerBlessings:
380 parseBlessings(msg);
381 break;
382 case Proto::GameServerUnjustifiedStats:
383 parseUnjustifiedStats(msg);
384 break;
385 case Proto::GameServerPvpSituations:
386 parsePvpSituations(msg);
387 break;
388 case Proto::GameServerPreset:
389 parsePreset(msg);
390 break;
391 // PROTOCOL>=1080
392 case Proto::GameServerCoinBalanceUpdate:
393 parseCoinBalanceUpdate(msg);
394 break;
395 case Proto::GameServerCoinBalance:
396 parseCoinBalance(msg);
397 break;
398 case Proto::GameServerRequestPurchaseData:
399 parseRequestPurchaseData(msg);
400 break;
401 case Proto::GameServerStoreCompletePurchase:
402 parseCompleteStorePurchase(msg);
403 break;
404 case Proto::GameServerStore:
405 parseStore(msg);
406 break;
407 case Proto::GameServerStoreOffers:
408 parseStoreOffers(msg);
409 break;
410 case Proto::GameServerStoreTransactionHistory:
411 parseStoreTransactionHistory(msg);
412 break;
413 case Proto::GameServerStoreError:
414 parseStoreError(msg);
415 break;
416 // PROTOCOL>=1097
417 case Proto::GameServerStoreButtonIndicators:
418 parseStoreButtonIndicators(msg);
419 break;
420 case Proto::GameServerSetStoreDeepLink:
421 parseSetStoreDeepLink(msg);
422 break;
423 case Proto::GameServerRestingAreaState:
424 parseRestingAreaState(msg);
425 break;
426 // protocol>=1100
427 case Proto::GameServerClientCheck:
428 parseClientCheck(msg);
429 break;
430 case Proto::GameServerNews:
431 parseGameNews(msg);
432 break;
433 case Proto::GameUnkown154: // spotted on skelot
434 break;
435 case Proto::GameServerBlessDialog:
436 parseBlessDialog(msg);
437 break;
438 case Proto::GameServerMessageDialog:
439 parseMessageDialog(msg);
440 break;
441 case Proto::GameServerResourceBalance:
442 parseResourceBalance(msg);
443 break;
444 case Proto::GameServerTime:
445 parseServerTime(msg);
446 break;
447 case Proto::GameServerPreyFreeRolls:
448 parsePreyFreeRolls(msg);
449 break;
450 case Proto::GameServerPreyTimeLeft:
451 parsePreyTimeLeft(msg);
452 break;
453 case Proto::GameServerPreyData:
454 parsePreyData(msg);
455 break;
456 case Proto::GameServerPreyPrices:
457 parsePreyPrices(msg);
458 break;
459 case Proto::GameServerStoreOfferDescription:
460 parseStoreOfferDescription(msg);
461 break;
462 case Proto::GameServerImpactTracker:
463 parseImpactTracker(msg);
464 break;
465 case Proto::GameServerItemsPrices:
466 parseItemsPrices(msg);
467 break;
468 case Proto::GameServerSupplyTracker:
469 parseSupplyTracker(msg);
470 break;
471 case Proto::GameServerLootTracker:
472 parseLootTracker(msg);
473 break;
474 case Proto::GameServerQuestTracker:
475 parseQuestTracker(msg);
476 break;
477 case Proto::GameServerKillTracker:
478 parseKillTracker(msg);
479 break;
480 case Proto::GameServerImbuementWindow:
481 parseImbuementWindow(msg);
482 break;
483 case Proto::GameServerCloseImbuementWindow:
484 parseCloseImbuementWindow(msg);
485 break;
486 case Proto::GameServerCyclopediaNewDetails:
487 parseCyclopediaNewDetails(msg);
488 break;
489 case Proto::GameServerCyclopedia:
490 parseCyclopedia(msg);
491 break;
492 case Proto::GameServerDailyRewardState:
493 parseDailyRewardState(msg);
494 break;
495 case Proto::GameServerOpenRewardWall:
496 parseOpenRewardWall(msg);
497 break;
498 case Proto::GameServerDailyReward:
499 parseDailyReward(msg);
500 break;
501 case Proto::GameServerDailyRewardHistory:
502 parseDailyRewardHistory(msg);
503 break;
504 case Proto::GameServerLootContainers:
505 parseLootContainers(msg);
506 break;
507 case Proto::GameServerSupplyStash:
508 parseSupplyStash(msg);
509 break;
510 case Proto::GameServerSpecialContainer:
511 parseSpecialContainer(msg);
512 break;
513 // case Proto::GameServerDepotState:
514 // parseDepotState(msg);
515 // break;
516 case Proto::GameServerTournamentLeaderboard:
517 parseTournamentLeaderboard(msg);
518 break;
519 case Proto::GameServerItemDetail:
520 parseItemDetail(msg);
521 break;
522 case Proto::GameServerHunting:
523 parseHunting(msg);
524 break;
525 // otclient ONLY
526 case Proto::GameServerExtendedOpcode:
527 parseExtendedOpcode(msg);
528 break;
529 case Proto::GameServerChangeMapAwareRange:
530 parseChangeMapAwareRange(msg);
531 break;
532 case Proto::GameServerProgressBar:
533 parseProgressBar(msg);
534 break;
535 case Proto::GameServerFeatures:
536 parseFeatures(msg);
537 break;
538 case Proto::GameServerNewCancelWalk:
539 if (g_game.getFeature(Otc::GameNewWalking))
540 parseNewCancelWalk(msg);
541 break;
542 case Proto::GameServerPredictiveCancelWalk:
543 if (g_game.getFeature(Otc::GameNewWalking))
544 parsePredictiveCancelWalk(msg);
545 break;
546 case Proto::GameServerWalkId:
547 if (g_game.getFeature(Otc::GameNewWalking))
548 parseWalkId(msg);
549 break;
550 case Proto::GameServerFloorDescription:
551 parseFloorDescription(msg);
552 break;
553 case Proto::GameServerProcessesRequest:
554 parseProcessesRequest(msg);
555 break;
556 case Proto::GameServerDllsRequest:
557 parseDllsRequest(msg);
558 break;
559 case Proto::GameServerWindowsRequests:
560 parseWindowsRequest(msg);
561 break;
562 default:
563 stdext::throw_exception(
564 stdext::format("unhandled opcode %d", (int)opcode));
565 break;
566 }
567 prevOpcode = opcode;
568 prevOpcodePos = opcodePos;
569 }
570 } catch (stdext::exception &e) {
571 g_logger.error(stdext::format(
572 "ProtocolGame parse message exception (%d bytes, %d unread, last "
573 "opcode is 0x%02x (%d), prev opcode is 0x%02x (%d)): %s"
574 "\nPacket has been saved to packet.log, you can use it to find what "
575 "was wrong. (Protocol: %i)",
576 msg->getMessageSize(), msg->getUnreadSize(), opcode, opcode, prevOpcode,
577 prevOpcode, e.what(), g_game.getProtocolVersion()));
578
579 std::ofstream packet("packet.log", std::ifstream::app);
580 if (!packet.is_open())
581 return;
582 packet << stdext::format(
583 "ProtocolGame parse message exception (%d bytes, %d unread, last "
584 "opcode is 0x%02x (%d), prev opcode is 0x%02x (%d), proto: %i): %s\n",
585 msg->getMessageSize(), msg->getUnreadSize(), opcode, opcode, prevOpcode,
586 prevOpcode, g_game.getProtocolVersion(), e.what());
587 std::string buffer = msg->getBuffer();
588 opcodePos -= msg->getHeaderPos();
589 prevOpcodePos -= msg->getHeaderPos();
590 for (size_t i = 0; i < buffer.size(); ++i) {
591 if ((i == prevOpcodePos || i == opcodePos) && i > 0)
592 packet << "\n";
593 packet << std::setfill('0') << std::setw(2) << std::hex
594 << (uint16_t)(uint8_t)buffer[i] << std::dec << " ";
595 }
596 packet << "\n\n";
597 packet.close();
598 }
599}
600
601void ProtocolGame::parseLogin(const InputMessagePtr &msg) {
602 uint playerId = msg->getU32();
603 int serverBeat = msg->getU16();
604
605 if (g_game.getFeature(Otc::GameNewSpeedLaw)) {
606 double speedA = msg->getDouble();
607 double speedB = msg->getDouble();
608 double speedC = msg->getDouble();
609 m_localPlayer->setSpeedFormula(speedA, speedB, speedC);
610 }
611 bool canReportBugs = msg->getU8();
612
613 if (g_game.getProtocolVersion() >= 1054)
614 msg->getU8(); // can change pvp frame option
615
616 if (g_game.getProtocolVersion() >= 1058) {
617 int expertModeEnabled = msg->getU8();
618 g_game.setExpertPvpMode(expertModeEnabled);
619 }
620
621 if (g_game.getFeature(Otc::GameIngameStore)) {
622 // URL to ingame store images
623 std::string url = msg->getString();
624
625 // premium coin package size
626 // e.g you can only buy packs of 25, 50, 75, .. coins in the market
627 int coinsPacketSize = msg->getU16();
628 g_lua.callGlobalField("g_game", "onStoreInit", url, coinsPacketSize);
629 }
630
631 if (g_game.getFeature(Otc::GameTibia12Protocol)) {
632 msg->getU8(); // show exiva button
633 if (g_game.getProtocolVersion() >= 1215) {
634 msg->getU8(); // tournament button
635 }
636 }
637
638 m_localPlayer->setId(playerId);
639 g_game.setServerBeat(serverBeat);
640 g_game.setCanReportBugs(canReportBugs);
641
642 g_game.processLogin();
643}
644
645void ProtocolGame::parsePendingGame(const InputMessagePtr &msg) {
646 // set player to pending game state
647 g_game.processPendingGame();
648}
649
650void ProtocolGame::parseEnterGame(const InputMessagePtr &msg) {
651 // set player to entered game state
652 g_game.processEnterGame();
653
654 if (!m_gameInitialized) {
655 g_game.processGameStart();
656 m_gameInitialized = true;
657 }
658}
659
660void ProtocolGame::parseStoreButtonIndicators(const InputMessagePtr &msg) {
661 /*bool haveSale = */ msg->getU8();
662 /*bool haveNewItem = */ msg->getU8();
663}
664
665void ProtocolGame::parseSetStoreDeepLink(const InputMessagePtr &msg) {
666 /*int currentlyFeaturedServiceType = */ msg->getU8();
667}
668
669void ProtocolGame::parseRestingAreaState(const InputMessagePtr &msg) {
670 msg->getU8(); // zone
671 msg->getU8(); // state
672 msg->getString(); // message
673}
674
675void ProtocolGame::parseBlessings(const InputMessagePtr &msg) {
676 uint16 blessings = msg->getU16();
677 if (g_game.getFeature(Otc::GameTibia12Protocol)) {
678 msg->getU8(); // blessStatus - 1 = Disabled | 2 = normal | 3 = green
679 }
680 m_localPlayer->setBlessings(blessings);
681}
682
683void ProtocolGame::parsePreset(const InputMessagePtr &msg) {
684 /*uint32 preset = */ msg->getU32();
685}
686
687void ProtocolGame::parseRequestPurchaseData(const InputMessagePtr &msg) {
688 /*int transactionId = */ msg->getU32();
689 /*int productType = */ msg->getU8();
690}
691
692void ProtocolGame::parseStore(const InputMessagePtr &msg) {
693 if (!g_game.getFeature(Otc::GameTibia12Protocol))
694 msg->getU8(); // unknown
695
696 std::vector<StoreCategory> categories;
697
698 // Parse all categories
699 int count = msg->getU16();
700 for (int i = 0; i < count; i++) {
701 StoreCategory category;
702
703 category.name = msg->getString();
704 if (!g_game.getFeature(Otc::GameTibia12Protocol))
705 category.description = msg->getString();
706
707 category.state = 0;
708 if (g_game.getFeature(Otc::GameIngameStoreHighlights))
709 category.state = msg->getU8();
710
711 int iconCount = msg->getU8();
712 for (int i = 0; i < iconCount; i++) {
713 std::string icon = msg->getString();
714 category.icon = icon;
715 }
716
717 category.parent = msg->getString();
718 categories.push_back(category);
719 }
720
721 g_lua.callGlobalField("g_game", "onStoreCategories", categories);
722}
723
724void ProtocolGame::parseCoinBalanceUpdate(const InputMessagePtr &msg) {
725 msg->getU8(); // 1 if is updating
726}
727
728void ProtocolGame::parseCoinBalance(const InputMessagePtr &msg) {
729 bool update = msg->getU8() == 1;
730 if (!update)
731 return;
732
733 // amount of coins that can be used to buy prodcuts
734 // in the ingame store
735 int coins = msg->getU32();
736
737 // amount of coins that can be sold in market
738 // or be transfered to another player
739 int transferableCoins = msg->getU32();
740 g_game.setTibiaCoins(coins, transferableCoins);
741
742 int tournamentCoins = 0;
743 if (g_game.getFeature(Otc::GameTibia12Protocol) &&
744 g_game.getProtocolVersion() >= 1220)
745 tournamentCoins = msg->getU32();
746
747 if (g_game.getFeature(Otc::GameTibia12Protocol) &&
748 g_game.getProtocolVersion() >= 1240)
749 msg->getU32(); // Reserved Auction Coins
750
751 g_lua.callGlobalField("g_game", "onCoinBalance", coins, transferableCoins,
752 tournamentCoins);
753}
754
755void ProtocolGame::parseCompleteStorePurchase(const InputMessagePtr &msg) {
756 // not used
757 msg->getU8();
758
759 std::string message = msg->getString();
760 g_lua.callGlobalField("g_game", "onStorePurchase", message);
761
762 if (g_game.getProtocolVersion() < 1220) {
763 int coins = msg->getU32();
764 int transferableCoins = msg->getU32();
765 g_lua.callGlobalField("g_game", "onCoinBalance", coins, transferableCoins);
766 }
767}
768
769void ProtocolGame::parseStoreTransactionHistory(const InputMessagePtr &msg) {
770 int currentPage;
771 bool hasNextPage;
772 if (g_game.getProtocolVersion() <= 1096) {
773 currentPage = msg->getU16();
774 hasNextPage = msg->getU8() == 1;
775 } else {
776 currentPage = msg->getU32();
777 int pageCount = msg->getU32();
778 hasNextPage = (pageCount > currentPage);
779 }
780
781 std::vector<StoreOffer> offers;
782
783 int entries = msg->getU8();
784 for (int i = 0; i < entries; i++) {
785 StoreOffer offer;
786 offer.id = 0;
787 if (g_game.getFeature(Otc::GameTibia12Protocol) &&
788 g_game.getProtocolVersion() >= 1220)
789 msg->getU32(); // unknown
790 int time = msg->getU32();
791 /*int productType = */ msg->getU8();
792 offer.price = msg->getU32();
793 if (g_game.getFeature(Otc::GameTibia12Protocol))
794 msg->getU8(); // unknown
795
796 offer.name = msg->getString();
797 offer.description =
798 std::string("Bought on: ") + stdext::timestamp_to_date(time);
799 if (g_game.getFeature(Otc::GameTibia12Protocol) &&
800 g_game.getProtocolVersion() >= 1220)
801 msg->getU8(); // unknown, offer details?
802
803 offers.push_back(offer);
804 }
805
806 g_lua.callGlobalField("g_game", "onStoreTransactionHistory", currentPage,
807 hasNextPage, offers);
808}
809
810void ProtocolGame::parseStoreOffers(const InputMessagePtr &msg) {
811 // TODO: Update to tibia 12 protocol
812 std::string categoryName = msg->getString();
813 std::vector<StoreOffer> offers;
814
815 if (g_game.getFeature(Otc::GameTibia12Protocol)) {
816 msg->getU32(); // redirect
817 msg->getU8(); // sorting type
818 int filterCount = msg->getU8(); // filters available
819 for (int i = 0; i < filterCount; ++i)
820 msg->getString();
821 int shownFiltersCount = msg->getU16();
822 for (int i = 0; i < shownFiltersCount; ++i)
823 msg->getU8();
824 }
825
826 int offers_count = msg->getU16();
827 for (int i = 0; i < offers_count; i++) {
828 StoreOffer offer;
829
830 if (g_game.getFeature(Otc::GameTibia12Protocol)) {
831 offer.name = msg->getString();
832 int configurations = msg->getU8();
833 for (int c = 0; c < configurations; ++c) {
834 offer.id = msg->getU32();
835 msg->getU16(); // count?
836 offer.price = msg->getU32();
837 msg->getU8(); // coins type 0x00 default, 0x01 transfeable, 0x02
838 // tournament
839 bool disabled = msg->getU8() > 0;
840 if (disabled) {
841 int errors = msg->getU8();
842 for (int e = 0; e < errors; ++e)
843 msg->getString(); // error msg
844 }
845 offer.state = msg->getU8();
846 if (offer.state == 2 &&
847 g_game.getFeature(Otc::GameIngameStoreHighlights) &&
848 g_game.getProtocolVersion() >= 1097) {
849 /*int saleValidUntilTimestamp = */ msg->getU32();
850 /*int basePrice = */ msg->getU32();
851 }
852 }
853 int offerType = msg->getU8();
854 if (offerType == 0) { // icon
855 offer.icon = msg->getString();
856 } else if (offerType == 1) { // mount
857 msg->getU16();
858 } else if (offerType == 2) { // outfit
859 getOutfit(msg, true);
860 } else if (offerType == 3) { // item
861 msg->getU16();
862 }
863 if (g_game.getProtocolVersion() >= 1212)
864 msg->getU8(); // has category?
865
866 msg->getString(); // filter
867 msg->getU32(); // TimeAddedToStore
868 msg->getU16(); // TimesBought
869 msg->getU8(); // RequiresConfiguration
870 } else {
871 offer.id = msg->getU32();
872 offer.name = msg->getString();
873 offer.description = msg->getString();
874
875 offer.price = msg->getU32();
876 offer.state = msg->getU8();
877 if (offer.state == 2 &&
878 g_game.getFeature(Otc::GameIngameStoreHighlights) &&
879 g_game.getProtocolVersion() >= 1097) {
880 /*int saleValidUntilTimestamp = */ msg->getU32();
881 /*int basePrice = */ msg->getU32();
882 }
883
884 int disabledState = msg->getU8();
885 std::string disabledReason = "";
886 if (g_game.getFeature(Otc::GameIngameStoreHighlights) &&
887 disabledState == 1) {
888 disabledReason = msg->getString();
889 }
890 int icons = msg->getU8();
891 for (int j = 0; j < icons; j++) {
892 offer.icon = msg->getString();
893 }
894 }
895
896 int subOffers = msg->getU16();
897 // this is probably incorrect for tibia 12
898 for (int j = 0; j < subOffers; j++) {
899 std::string name = msg->getString();
900 if (!g_game.getFeature(Otc::GameIngameStoreHighlights)) {
901 std::string description = msg->getString();
902 int subIcons = msg->getU8();
903 for (int k = 0; k < subIcons; k++) {
904 std::string icon = msg->getString();
905 }
906 } else {
907 int offerType = msg->getU8();
908 if (offerType == 0) { // icon
909 offer.icon = msg->getString();
910 } else if (offerType == 1) { // mount
911 msg->getU16();
912 } else if (offerType == 2) { // outfit
913 getOutfit(msg, true);
914 } else if (offerType == 3) { // item
915 msg->getU16();
916 }
917 }
918 }
919
920 offers.push_back(offer);
921 }
922
923 if (g_game.getFeature(Otc::GameTibia12Protocol) && categoryName == "Home") {
924 int featuredOfferCount = msg->getU8();
925 for (int i = 0; i < featuredOfferCount; ++i) {
926 msg->getString(); // icon/banner
927 int type = msg->getU8();
928 if (type == 1) { // category type
929 msg->getU8();
930 } else if (type == 2) { // category and filter
931 msg->getString(); // category
932 msg->getString(); // filter
933 } else if (type == 3) { // offer type
934 msg->getU8();
935 } else if (type == 4) { // offer id
936 msg->getU32();
937 } else if (type == 5) { // category name
938 msg->getString();
939 }
940 msg->getU8();
941 msg->getU8();
942 }
943 msg->getU8(); // unknown
944 }
945
946 g_lua.callGlobalField("g_game", "onStoreOffers", categoryName, offers);
947}
948
949void ProtocolGame::parseStoreError(const InputMessagePtr &msg) {
950 int errorType = msg->getU8();
951 std::string message = msg->getString();
952 g_lua.callGlobalField("g_game", "onStoreError", errorType, message);
953}
954
955void ProtocolGame::parseUnjustifiedStats(const InputMessagePtr &msg) {
956 UnjustifiedPoints unjustifiedPoints;
957 unjustifiedPoints.killsDay = msg->getU8();
958 unjustifiedPoints.killsDayRemaining = msg->getU8();
959 unjustifiedPoints.killsWeek = msg->getU8();
960 unjustifiedPoints.killsWeekRemaining = msg->getU8();
961 unjustifiedPoints.killsMonth = msg->getU8();
962 unjustifiedPoints.killsMonthRemaining = msg->getU8();
963 unjustifiedPoints.skullTime = msg->getU8();
964
965 g_game.setUnjustifiedPoints(unjustifiedPoints);
966}
967
968void ProtocolGame::parsePvpSituations(const InputMessagePtr &msg) {
969 uint8 openPvpSituations = msg->getU8();
970
971 g_game.setOpenPvpSituations(openPvpSituations);
972}
973
974void ProtocolGame::parsePlayerHelpers(const InputMessagePtr &msg) {
975 uint id = msg->getU32();
976 int helpers = msg->getU16();
977
978 CreaturePtr creature = g_map.getCreatureById(id);
979 if (!creature)
980 return;
981 g_game.processPlayerHelpers(helpers);
982 // else
983 // g_logger.traceError(stdext::format("could not get creature with id
984 // %d", id));
985}
986
987void ProtocolGame::parseGMActions(const InputMessagePtr &msg) {
988 std::vector<uint8> actions;
989
990 int numViolationReasons;
991
992 if (g_game.getProtocolVersion() >= 850)
993 numViolationReasons = 20;
994 else if (g_game.getProtocolVersion() >= 840)
995 numViolationReasons = 23;
996 else
997 numViolationReasons = 32;
998
999 for (int i = 0; i < numViolationReasons; ++i)
1000 actions.push_back(msg->getU8());
1001 g_game.processGMActions(actions);
1002}
1003
1004void ProtocolGame::parseUpdateNeeded(const InputMessagePtr &msg) {
1005 std::string signature = msg->getString();
1006 g_game.processUpdateNeeded(signature);
1007}
1008
1009void ProtocolGame::parseLoginError(const InputMessagePtr &msg) {
1010 std::string error = msg->getString();
1011
1012 g_game.processLoginError(error);
1013}
1014
1015void ProtocolGame::parseLoginAdvice(const InputMessagePtr &msg) {
1016 std::string message = msg->getString();
1017
1018 g_game.processLoginAdvice(message);
1019}
1020
1021void ProtocolGame::parseLoginWait(const InputMessagePtr &msg) {
1022 std::string message = msg->getString();
1023 int time = msg->getU8();
1024
1025 g_game.processLoginWait(message, time);
1026}
1027
1028void ProtocolGame::parseLoginToken(const InputMessagePtr &msg) {
1029 bool unknown = (msg->getU8() == 0);
1030 g_game.processLoginToken(unknown);
1031}
1032
1033void ProtocolGame::parsePing(const InputMessagePtr &msg) {
1034 g_game.processPing();
1035}
1036
1037void ProtocolGame::parsePingBack(const InputMessagePtr &msg) {
1038 g_game.processPingBack();
1039}
1040
1041void ProtocolGame::parseNewPing(const InputMessagePtr &msg) {
1042 uint32 pingId = msg->getU32();
1043
1044 g_game.processNewPing(pingId);
1045}
1046
1047void ProtocolGame::parseChallenge(const InputMessagePtr &msg) {
1048 uint timestamp = msg->getU32();
1049 uint8 random = msg->getU8();
1050
1051 sendLoginPacket(timestamp, random);
1052}
1053
1054void ProtocolGame::parseDeath(const InputMessagePtr &msg) {
1055 int penality = 100;
1056 int deathType = Otc::DeathRegular;
1057
1058 if (g_game.getFeature(Otc::GameDeathType))
1059 deathType = msg->getU8();
1060
1061 if (g_game.getFeature(Otc::GamePenalityOnDeath) &&
1062 deathType == Otc::DeathRegular)
1063 penality = msg->getU8();
1064
1065 if (g_game.getFeature(Otc::GameTibia12Protocol))
1066 msg->getU8(); // death redemption
1067
1068 g_game.processDeath(deathType, penality);
1069}
1070
1071void ProtocolGame::parseMapDescription(const InputMessagePtr &msg) {
1072 Position pos = getPosition(msg);
1073 Position oldPos = m_localPlayer->getPosition();
1074
1075 if (!m_mapKnown)
1076 m_localPlayer->setPosition(pos);
1077
1078 g_map.setCentralPosition(pos);
1079
1080 AwareRange range = g_map.getAwareRange();
1081 setMapDescription(msg, pos.x - range.left, pos.y - range.top, pos.z,
1082 range.horizontal(), range.vertical());
1083
1084 if (m_mapKnown) {
1085 // We know about the map so its not from logging in, it must be teleport
1086 g_lua.callGlobalField("g_game", "onTeleport", m_localPlayer, pos, oldPos);
1087 }
1088
1089 if (!m_mapKnown) {
1090 g_dispatcher.addEvent(
1091 [] { g_lua.callGlobalField("g_game", "onMapKnown"); });
1092 m_mapKnown = true;
1093 }
1094
1095 g_dispatcher.addEvent(
1096 [] { g_lua.callGlobalField("g_game", "onMapDescription"); });
1097}
1098
1099void ProtocolGame::parseFloorDescription(const InputMessagePtr &msg) {
1100 Position pos = getPosition(msg);
1101 Position oldPos = m_localPlayer->getPosition();
1102 int floor = msg->getU8();
1103
1104 if (pos.z == floor) {
1105 if (!m_mapKnown)
1106 m_localPlayer->setPosition(pos);
1107 g_map.setCentralPosition(pos);
1108 if (!m_mapKnown) {
1109 g_dispatcher.addEvent(
1110 [] { g_lua.callGlobalField("g_game", "onMapKnown"); });
1111 m_mapKnown = true;
1112 }
1113
1114 g_dispatcher.addEvent(
1115 [] { g_lua.callGlobalField("g_game", "onMapDescription"); });
1116 g_lua.callGlobalField("g_game", "onTeleport", m_localPlayer, pos, oldPos);
1117 }
1118
1119 AwareRange range = g_map.getAwareRange();
1120 setFloorDescription(msg, pos.x - range.left, pos.y - range.top, floor,
1121 range.horizontal(), range.vertical(), pos.z - floor, 0);
1122}
1123
1124void ProtocolGame::parseMapMoveNorth(const InputMessagePtr &msg) {
1125 Position pos;
1126 if (g_game.getFeature(Otc::GameMapMovePosition))
1127 pos = getPosition(msg);
1128 else
1129 pos = g_map.getCentralPosition();
1130 pos.y--;
1131
1132 g_map.setCentralPosition(pos);
1133
1134 AwareRange range = g_map.getAwareRange();
1135 setMapDescription(msg, pos.x - range.left, pos.y - range.top, pos.z,
1136 range.horizontal(), 1);
1137}
1138
1139void ProtocolGame::parseMapMoveEast(const InputMessagePtr &msg) {
1140 Position pos;
1141 if (g_game.getFeature(Otc::GameMapMovePosition))
1142 pos = getPosition(msg);
1143 else
1144 pos = g_map.getCentralPosition();
1145 pos.x++;
1146
1147 g_map.setCentralPosition(pos);
1148
1149 AwareRange range = g_map.getAwareRange();
1150 setMapDescription(msg, pos.x + range.right, pos.y - range.top, pos.z, 1,
1151 range.vertical());
1152}
1153
1154void ProtocolGame::parseMapMoveSouth(const InputMessagePtr &msg) {
1155 Position pos;
1156 if (g_game.getFeature(Otc::GameMapMovePosition))
1157 pos = getPosition(msg);
1158 else
1159 pos = g_map.getCentralPosition();
1160 pos.y++;
1161
1162 g_map.setCentralPosition(pos);
1163
1164 AwareRange range = g_map.getAwareRange();
1165 setMapDescription(msg, pos.x - range.left, pos.y + range.bottom, pos.z,
1166 range.horizontal(), 1);
1167}
1168
1169void ProtocolGame::parseMapMoveWest(const InputMessagePtr &msg) {
1170 Position pos;
1171 if (g_game.getFeature(Otc::GameMapMovePosition))
1172 pos = getPosition(msg);
1173 else
1174 pos = g_map.getCentralPosition();
1175 pos.x--;
1176
1177 g_map.setCentralPosition(pos);
1178
1179 AwareRange range = g_map.getAwareRange();
1180 setMapDescription(msg, pos.x - range.left, pos.y - range.top, pos.z, 1,
1181 range.vertical());
1182}
1183
1184void ProtocolGame::parseUpdateTile(const InputMessagePtr &msg) {
1185 Position tilePos = getPosition(msg);
1186 setTileDescription(msg, tilePos);
1187}
1188
1189void ProtocolGame::parseTileAddThing(const InputMessagePtr &msg) {
1190 Position pos = getPosition(msg);
1191 int stackPos = -1;
1192
1193 if (g_game.getFeature(Otc::GameTileAddThingWithStackpos))
1194 stackPos = msg->getU8();
1195
1196 ThingPtr thing = getThing(msg);
1197 g_map.addThing(thing, pos, stackPos);
1198}
1199
1200void ProtocolGame::parseTileTransformThing(const InputMessagePtr &msg) {
1201 ThingPtr thing = getMappedThing(msg);
1202 ThingPtr newThing = getThing(msg);
1203
1204 if (!thing) {
1205 g_logger.traceError("no thing");
1206 return;
1207 }
1208
1209 Position pos = thing->getPosition();
1210 int stackpos = thing->getStackPos();
1211
1212 if (!g_map.removeThing(thing)) {
1213 g_logger.traceError("unable to remove thing");
1214 return;
1215 }
1216
1217 g_map.addThing(newThing, pos, stackpos);
1218}
1219
1220void ProtocolGame::parseTileRemoveThing(const InputMessagePtr &msg) {
1221 ThingPtr thing = getMappedThing(msg);
1222 if (!thing) {
1223 g_logger.traceError("no thing");
1224 return;
1225 }
1226
1227 if (!g_map.removeThing(thing))
1228 g_logger.traceError("unable to remove thing");
1229}
1230
1231void ProtocolGame::parseCreatureMove(const InputMessagePtr &msg) {
1232 ThingPtr thing = getMappedThing(msg);
1233 Position newPos = getPosition(msg);
1234
1235 uint16_t stepDuration = 0;
1236 if (g_game.getFeature(Otc::GameNewWalking))
1237 stepDuration = msg->getU16();
1238
1239 if (!thing || !thing->isCreature()) {
1240 g_logger.traceError("no creature found to move");
1241 return;
1242 }
1243
1244 if (!g_map.removeThing(thing)) {
1245 g_logger.traceError("unable to remove creature");
1246 return;
1247 }
1248
1249 CreaturePtr creature = thing->static_self_cast<Creature>();
1250 creature->allowAppearWalk(stepDuration);
1251
1252 g_map.addThing(thing, newPos, -1);
1253}
1254
1255void ProtocolGame::parseOpenContainer(const InputMessagePtr &msg) {
1256 int containerId = msg->getU8();
1257 ItemPtr containerItem = getItem(msg);
1258 std::string name = msg->getString();
1259 int capacity = msg->getU8();
1260 bool hasParent = (msg->getU8() != 0);
1261
1262 if (g_game.getFeature(Otc::GameTibia12Protocol) &&
1263 g_game.getProtocolVersion() >= 1220) {
1264 msg->getU8(); // can use depot search
1265 }
1266
1267 bool isUnlocked = true;
1268 bool hasPages = false;
1269 int containerSize = 0;
1270 int firstIndex = 0;
1271
1272 if (g_game.getFeature(Otc::GameContainerPagination)) {
1273 isUnlocked = (msg->getU8() != 0); // drag and drop
1274 hasPages = (msg->getU8() != 0); // pagination
1275 containerSize = msg->getU16(); // container size
1276 firstIndex = msg->getU16(); // first index
1277 }
1278
1279 int itemCount = msg->getU8();
1280
1281 std::vector<ItemPtr> items(itemCount);
1282 for (int i = 0; i < itemCount; i++)
1283 items[i] = getItem(msg);
1284
1285 g_game.processOpenContainer(containerId, containerItem, name, capacity,
1286 hasParent, items, isUnlocked, hasPages,
1287 containerSize, firstIndex);
1288}
1289
1290void ProtocolGame::parseCloseContainer(const InputMessagePtr &msg) {
1291 int containerId = msg->getU8();
1292 g_game.processCloseContainer(containerId);
1293}
1294
1295void ProtocolGame::parseContainerAddItem(const InputMessagePtr &msg) {
1296 int containerId = msg->getU8();
1297 int slot = 0;
1298 if (g_game.getFeature(Otc::GameContainerPagination)) {
1299 slot = msg->getU16(); // slot
1300 }
1301 ItemPtr item = getItem(msg);
1302 g_game.processContainerAddItem(containerId, item, slot);
1303}
1304
1305void ProtocolGame::parseContainerUpdateItem(const InputMessagePtr &msg) {
1306 int containerId = msg->getU8();
1307 int slot;
1308 if (g_game.getFeature(Otc::GameContainerPagination)) {
1309 slot = msg->getU16();
1310 } else {
1311 slot = msg->getU8();
1312 }
1313 ItemPtr item = getItem(msg);
1314 g_game.processContainerUpdateItem(containerId, slot, item);
1315}
1316
1317void ProtocolGame::parseContainerRemoveItem(const InputMessagePtr &msg) {
1318 int containerId = msg->getU8();
1319 int slot;
1320 ItemPtr lastItem;
1321 if (g_game.getFeature(Otc::GameContainerPagination)) {
1322 slot = msg->getU16();
1323
1324 int itemId = msg->getU16();
1325 if (itemId != 0)
1326 lastItem = getItem(msg, itemId);
1327 } else {
1328 slot = msg->getU8();
1329 }
1330 g_game.processContainerRemoveItem(containerId, slot, lastItem);
1331}
1332
1333void ProtocolGame::parseAddInventoryItem(const InputMessagePtr &msg) {
1334 int slot = msg->getU8();
1335 ItemPtr item = getItem(msg);
1336 g_game.processInventoryChange(slot, item);
1337}
1338
1339void ProtocolGame::parseRemoveInventoryItem(const InputMessagePtr &msg) {
1340 int slot = msg->getU8();
1341 g_game.processInventoryChange(slot, ItemPtr());
1342}
1343
1344void ProtocolGame::parseOpenNpcTrade(const InputMessagePtr &msg) {
1345 std::vector<std::tuple<ItemPtr, std::string, int, int64_t, int64_t>> items;
1346 std::string npcName;
1347
1348 if (g_game.getFeature(Otc::GameNameOnNpcTrade))
1349 npcName = msg->getString();
1350 if (g_game.getFeature(Otc::GameTibia12Protocol)) {
1351 if (g_game.getProtocolVersion() >= 1220)
1352 msg->getU16(); // shop item id
1353 if (g_game.getProtocolVersion() >= 1240)
1354 msg->getString();
1355 }
1356
1357 int listCount;
1358
1359 if (g_game.getProtocolVersion() >= 986) // tbh not sure from what version
1360 listCount = msg->getU16();
1361 else
1362 listCount = msg->getU8();
1363
1364 for (int i = 0; i < listCount; ++i) {
1365 uint16 itemId = msg->getU16();
1366 uint8 count = msg->getU8();
1367
1368 ItemPtr item = Item::create(itemId);
1369 item->setCountOrSubType(count);
1370
1371 std::string name = msg->getString();
1372 int weight = msg->getU32();
1373 int64_t buyPrice = g_game.getFeature(Otc::GameDoubleTradeMoney)
1374 ? msg->getU64()
1375 : static_cast<int32_t>(msg->getU32());
1376 int64_t sellPrice = g_game.getFeature(Otc::GameDoubleTradeMoney)
1377 ? msg->getU64()
1378 : static_cast<int32_t>(msg->getU32());
1379 items.push_back(std::make_tuple(item, name, weight, buyPrice, sellPrice));
1380 }
1381
1382 g_game.processOpenNpcTrade(items);
1383}
1384
1385void ProtocolGame::parsePlayerGoods(const InputMessagePtr &msg) {
1386 std::vector<std::tuple<ItemPtr, int>> goods;
1387
1388 uint64_t money;
1389 if (g_game.getFeature(Otc::GameDoublePlayerGoodsMoney))
1390 money = msg->getU64();
1391 else
1392 money = msg->getU32();
1393
1394 int size = msg->getU8();
1395 for (int i = 0; i < size; i++) {
1396 int itemId = msg->getU16();
1397 int amount;
1398
1399 if (g_game.getFeature(Otc::GameDoubleShopSellAmount))
1400 amount = msg->getU16();
1401 else
1402 amount = msg->getU8();
1403
1404 goods.push_back(std::make_tuple(Item::create(itemId), amount));
1405 }
1406
1407 g_game.processPlayerGoods(money, goods);
1408}
1409
1410void ProtocolGame::parseCloseNpcTrade(const InputMessagePtr &) {
1411 g_game.processCloseNpcTrade();
1412}
1413
1414void ProtocolGame::parseOwnTrade(const InputMessagePtr &msg) {
1415 std::string name = g_game.formatCreatureName(msg->getString());
1416 int count = msg->getU8();
1417
1418 std::vector<ItemPtr> items(count);
1419 for (int i = 0; i < count; i++)
1420 items[i] = getItem(msg);
1421
1422 g_game.processOwnTrade(name, items);
1423}
1424
1425void ProtocolGame::parseCounterTrade(const InputMessagePtr &msg) {
1426 std::string name = g_game.formatCreatureName(msg->getString());
1427 int count = msg->getU8();
1428
1429 std::vector<ItemPtr> items(count);
1430 for (int i = 0; i < count; i++)
1431 items[i] = getItem(msg);
1432
1433 g_game.processCounterTrade(name, items);
1434}
1435
1436void ProtocolGame::parseCloseTrade(const InputMessagePtr &) {
1437 g_game.processCloseTrade();
1438}
1439
1440void ProtocolGame::parseWorldLight(const InputMessagePtr &msg) {
1441 Light light;
1442 light.intensity = msg->getU8();
1443 light.color = msg->getU8();
1444
1445 g_map.setLight(light);
1446}
1447
1448void ProtocolGame::parseMagicEffect(const InputMessagePtr &msg) {
1449 Position pos = getPosition(msg);
1450 if (g_game.getFeature(Otc::GameTibia12Protocol) &&
1451 g_game.getProtocolVersion() >= 1203) {
1452 Otc::MagicEffectsType_t effectType = (Otc::MagicEffectsType_t)msg->getU8();
1453 while (effectType != Otc::MAGIC_EFFECTS_END_LOOP) {
1454 if (effectType == Otc::MAGIC_EFFECTS_DELTA) {
1455 msg->getU8();
1456 } else if (effectType == Otc::MAGIC_EFFECTS_DELAY) {
1457 msg->getU8(); // ?
1458 } else if (effectType == Otc::MAGIC_EFFECTS_CREATE_DISTANCEEFFECT) {
1459 uint8_t shotId = msg->getU8();
1460 int8_t offsetX = static_cast<int8_t>(msg->getU8());
1461 int8_t offsetY = static_cast<int8_t>(msg->getU8());
1462 if (!g_things.isValidDatId(shotId, ThingCategoryMissile)) {
1463 g_logger.traceError(stdext::format("invalid missile id %d", shotId));
1464 return;
1465 }
1466
1467 MissilePtr missile = MissilePtr(new Missile());
1468 missile->setId(shotId);
1469 missile->setPath(pos,
1470 Position(pos.x + offsetX, pos.y + offsetY, pos.z));
1471 g_map.addThing(missile, pos);
1472 } else if (effectType ==
1473 Otc::MAGIC_EFFECTS_CREATE_DISTANCEEFFECT_REVERSED) {
1474 uint8_t shotId = msg->getU8();
1475 int8_t offsetX = static_cast<int8_t>(msg->getU8());
1476 int8_t offsetY = static_cast<int8_t>(msg->getU8());
1477 if (!g_things.isValidDatId(shotId, ThingCategoryMissile)) {
1478 g_logger.traceError(stdext::format("invalid missile id %d", shotId));
1479 return;
1480 }
1481
1482 MissilePtr missile = MissilePtr(new Missile());
1483 missile->setId(shotId);
1484 missile->setPath(Position(pos.x + offsetX, pos.y + offsetY, pos.z),
1485 pos);
1486 g_map.addThing(missile, pos);
1487 } else if (effectType == Otc::MAGIC_EFFECTS_CREATE_EFFECT) {
1488 uint8_t effectId = msg->getU8();
1489 if (!g_things.isValidDatId(effectId, ThingCategoryEffect)) {
1490 g_logger.traceError(stdext::format("invalid effect id %d", effectId));
1491 continue;
1492 }
1493 EffectPtr effect = EffectPtr(new Effect());
1494 effect->setId(effectId);
1495 g_map.addThing(effect, pos);
1496 }
1497 effectType = (Otc::MagicEffectsType_t)msg->getU8();
1498 }
1499 return;
1500 }
1501
1502 int effectId;
1503 if (g_game.getFeature(Otc::GameMagicEffectU16))
1504 effectId = msg->getU16();
1505 else
1506 effectId = msg->getU8();
1507
1508 if (!g_things.isValidDatId(effectId, ThingCategoryEffect)) {
1509 g_logger.traceError(stdext::format("invalid effect id %d", effectId));
1510 return;
1511 }
1512
1513 EffectPtr effect = EffectPtr(new Effect());
1514 effect->setId(effectId);
1515 g_map.addThing(effect, pos);
1516}
1517
1518void ProtocolGame::parseAnimatedText(const InputMessagePtr &msg) {
1519 Position position = getPosition(msg);
1520 int color = msg->getU8();
1521 std::string font;
1522 if (g_game.getFeature(Otc::GameAnimatedTextCustomFont))
1523 font = msg->getString();
1524 std::string text = msg->getString();
1525
1526 AnimatedTextPtr animatedText = AnimatedTextPtr(new AnimatedText);
1527 animatedText->setColor(color);
1528 animatedText->setText(text);
1529 if (font.size())
1530 animatedText->setFont(font);
1531
1532 g_map.addThing(animatedText, position);
1533}
1534
1535void ProtocolGame::parseDistanceMissile(const InputMessagePtr &msg) {
1536 Position fromPos = getPosition(msg);
1537 Position toPos = getPosition(msg);
1538 int shotId;
1539 if (g_game.getFeature(Otc::GameDistanceEffectU16))
1540 shotId = msg->getU16();
1541 else
1542 shotId = msg->getU8();
1543
1544 if (!g_things.isValidDatId(shotId, ThingCategoryMissile)) {
1545 g_logger.traceError(stdext::format("invalid missile id %d", shotId));
1546 return;
1547 }
1548
1549 MissilePtr missile = MissilePtr(new Missile());
1550 missile->setId(shotId);
1551 missile->setPath(fromPos, toPos);
1552 g_map.addThing(missile, fromPos);
1553}
1554
1555void ProtocolGame::parseCreatureMark(const InputMessagePtr &msg) {
1556 uint id = msg->getU32();
1557 int color = msg->getU8();
1558
1559 CreaturePtr creature = g_map.getCreatureById(id);
1560 if (creature)
1561 creature->addTimedSquare(color);
1562 else
1563 g_logger.traceError("could not get creature");
1564}
1565
1566void ProtocolGame::parseTrappers(const InputMessagePtr &msg) {
1567 int numTrappers = msg->getU8();
1568
1569 if (numTrappers > 8)
1570 g_logger.traceError("too many trappers");
1571
1572 for (int i = 0; i < numTrappers; ++i) {
1573 uint id = msg->getU32();
1574 CreaturePtr creature = g_map.getCreatureById(id);
1575 if (creature) {
1576 // TODO: set creature as trapper
1577 } else
1578 g_logger.traceError("could not get creature");
1579 }
1580}
1581
1582void ProtocolGame::parseCreatureHealth(const InputMessagePtr &msg) {
1583 uint id = msg->getU32();
1584 int healthPercent = msg->getU8();
1585 int8 manaPercent = -1;
1586 if (g_game.getFeature(Otc::GameCreaturesMana)) {
1587 if (msg->getU8() == 0x01) {
1588 manaPercent = msg->getU8();
1589 }
1590 }
1591
1592 CreaturePtr creature = g_map.getCreatureById(id);
1593 if (creature) {
1594 creature->setHealthPercent(healthPercent);
1595 if (g_game.getFeature(Otc::GameCreaturesMana)) {
1596 creature->setManaPercent(manaPercent);
1597 }
1598 }
1599
1600 // some servers has a bug in get spectators and sends unknown creatures
1601 // updates so this code is disabled
1602 /*
1603 else
1604 g_logger.traceError("could not get creature");
1605 */
1606}
1607
1608void ProtocolGame::parseCreatureLight(const InputMessagePtr &msg) {
1609 uint id = msg->getU32();
1610
1611 Light light;
1612 light.intensity = msg->getU8();
1613 light.color = msg->getU8();
1614
1615 CreaturePtr creature = g_map.getCreatureById(id);
1616 if (creature)
1617 creature->setLight(light);
1618 else
1619 g_logger.traceError("could not get creature");
1620}
1621
1622void ProtocolGame::parseCreatureOutfit(const InputMessagePtr &msg) {
1623 uint id = msg->getU32();
1624 Outfit outfit = getOutfit(msg);
1625
1626 CreaturePtr creature = g_map.getCreatureById(id);
1627 if (creature)
1628 creature->setOutfit(outfit);
1629 else
1630 g_logger.traceError("could not get creature");
1631}
1632
1633void ProtocolGame::parseCreatureAttackPulse(const InputMessagePtr &msg) {
1634 uint id = msg->getU32();
1635 Outfit outfit = getOutfit(msg);
1636 uint16 duration = msg->getU16();
1637
1638 CreaturePtr creature = g_map.getCreatureById(id);
1639 if (creature)
1640 creature->attackPulse(outfit, duration);
1641}
1642
1643void ProtocolGame::parseCreatureSpeed(const InputMessagePtr &msg) {
1644 uint id = msg->getU32();
1645
1646 int baseSpeed = -1;
1647 if (g_game.getProtocolVersion() >= 1059)
1648 baseSpeed = msg->getU16();
1649
1650 int speed = msg->getU16();
1651
1652 CreaturePtr creature = g_map.getCreatureById(id);
1653 if (creature) {
1654 creature->setSpeed(speed);
1655 if (baseSpeed != -1)
1656 creature->setBaseSpeed(baseSpeed);
1657 }
1658
1659 // some servers has a bug in get spectators and sends unknown creatures
1660 // updates so this code is disabled
1661 /*
1662 else
1663 g_logger.traceError("could not get creature");
1664 */
1665}
1666
1667void ProtocolGame::parseCreatureSkulls(const InputMessagePtr &msg) {
1668 uint id = msg->getU32();
1669 int skull = msg->getU8();
1670
1671 CreaturePtr creature = g_map.getCreatureById(id);
1672 if (creature)
1673 creature->setSkull(skull);
1674 else
1675 g_logger.traceError("could not get creature");
1676}
1677
1678void ProtocolGame::parseCreatureShields(const InputMessagePtr &msg) {
1679 uint id = msg->getU32();
1680 int shield = msg->getU8();
1681
1682 CreaturePtr creature = g_map.getCreatureById(id);
1683 if (creature)
1684 creature->setShield(shield);
1685 else
1686 g_logger.traceError("could not get creature");
1687}
1688
1689void ProtocolGame::parseCreatureUnpass(const InputMessagePtr &msg) {
1690 uint id = msg->getU32();
1691 bool unpass = msg->getU8();
1692
1693 CreaturePtr creature = g_map.getCreatureById(id);
1694 if (creature)
1695 creature->setPassable(!unpass);
1696 else
1697 g_logger.traceError("could not get creature");
1698}
1699
1700void ProtocolGame::parseEditText(const InputMessagePtr &msg) {
1701 uint id = msg->getU32();
1702
1703 int itemId;
1704 if (g_game.getProtocolVersion() >= 1010) {
1705 // TODO: processEditText with ItemPtr as parameter
1706 ItemPtr item = getItem(msg);
1707 itemId = item->getId();
1708 } else
1709 itemId = msg->getU16();
1710
1711 int maxLength = msg->getU16();
1712 std::string text = msg->getString();
1713
1714 std::string writer = msg->getString();
1715
1716 if (g_game.getFeature(Otc::GameTibia12Protocol) &&
1717 g_game.getProtocolVersion() > 1240)
1718 msg->getU8();
1719
1720 std::string date = "";
1721 if (g_game.getFeature(Otc::GameWritableDate))
1722 date = msg->getString();
1723
1724 g_game.processEditText(id, itemId, maxLength, text, writer, date);
1725}
1726
1727void ProtocolGame::parseEditList(const InputMessagePtr &msg) {
1728 int doorId = msg->getU8();
1729 uint id = msg->getU32();
1730 const std::string &text = msg->getString();
1731
1732 g_game.processEditList(id, doorId, text);
1733}
1734
1735void ProtocolGame::parsePremiumTrigger(const InputMessagePtr &msg) {
1736 int triggerCount = msg->getU8();
1737 std::vector<int> triggers;
1738 for (int i = 0; i < triggerCount; ++i) {
1739 triggers.push_back(msg->getU8());
1740 }
1741
1742 if (g_game.getProtocolVersion() <= 1096) {
1743 /*bool something = */ msg->getU8() /* == 1*/;
1744 }
1745}
1746
1747void ProtocolGame::parsePreyFreeRolls(const InputMessagePtr &msg) {
1748 int slot = msg->getU8();
1749 int timeLeft = msg->getU16();
1750
1751 g_lua.callGlobalField("g_game", "onPreyFreeRolls", slot, timeLeft);
1752}
1753
1754void ProtocolGame::parsePreyTimeLeft(const InputMessagePtr &msg) {
1755 int slot = msg->getU8();
1756 int timeLeft = msg->getU16();
1757
1758 g_lua.callGlobalField("g_game", "onPreyTimeLeft", slot, timeLeft);
1759}
1760
1761void ProtocolGame::parsePreyData(const InputMessagePtr &msg) {
1762 int slot = msg->getU8();
1763 Otc::PreyState_t state = (Otc::PreyState_t)msg->getU8();
1764 if (state == Otc::PREY_STATE_LOCKED) {
1765 Otc::PreyUnlockState_t unlockState = (Otc::PreyUnlockState_t)msg->getU8();
1766 int timeUntilFreeReroll =
1767 g_game.getProtocolVersion() >= 1252 ? msg->getU32() : msg->getU16();
1768 uint8_t lockType =
1769 g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
1770 return g_lua.callGlobalField("g_game", "onPreyLocked", slot, unlockState,
1771 timeUntilFreeReroll, lockType);
1772 } else if (state == Otc::PREY_STATE_INACTIVE) {
1773 int timeUntilFreeReroll =
1774 g_game.getProtocolVersion() >= 1252 ? msg->getU32() : msg->getU16();
1775 uint8_t lockType =
1776 g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
1777 return g_lua.callGlobalField("g_game", "onPreyInactive", slot,
1778 timeUntilFreeReroll, lockType);
1779 } else if (state == Otc::PREY_STATE_ACTIVE) {
1780 std::string currentHolderName = msg->getString();
1781 Outfit currentHolderOutfit = getOutfit(msg, true);
1782 Otc::PreyBonusType_t bonusType = (Otc::PreyBonusType_t)msg->getU8();
1783 int bonusValue = msg->getU16();
1784 int bonusGrade = msg->getU8();
1785 int timeLeft = msg->getU16();
1786 int timeUntilFreeReroll =
1787 g_game.getProtocolVersion() >= 1252 ? msg->getU32() : msg->getU16();
1788 uint8_t lockType =
1789 g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
1790 return g_lua.callGlobalField("g_game", "onPreyActive", slot,
1791 currentHolderName, currentHolderOutfit,
1792 bonusType, bonusValue, bonusGrade, timeLeft,
1793 timeUntilFreeReroll, lockType);
1794 } else if (state == Otc::PREY_STATE_SELECTION ||
1795 state == Otc::PREY_STATE_SELECTION_CHANGE_MONSTER) {
1796 Otc::PreyBonusType_t bonusType = Otc::PREY_BONUS_NONE;
1797 int bonusValue = -1, bonusGrade = -1;
1798 if (state == Otc::PREY_STATE_SELECTION_CHANGE_MONSTER) {
1799 bonusType = (Otc::PreyBonusType_t)msg->getU8();
1800 bonusValue = msg->getU16();
1801 bonusGrade = msg->getU8();
1802 }
1803 std::vector<std::string> names;
1804 std::vector<Outfit> outfits;
1805 int selectionSize = msg->getU8();
1806 for (int i = 0; i < selectionSize; ++i) {
1807 names.push_back(msg->getString());
1808 outfits.push_back(getOutfit(msg, true));
1809 }
1810 int timeUntilFreeReroll =
1811 g_game.getProtocolVersion() >= 1252 ? msg->getU32() : msg->getU16();
1812 uint8_t lockType =
1813 g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
1814 return g_lua.callGlobalField("g_game", "onPreySelection", slot, bonusType,
1815 bonusValue, bonusGrade, names, outfits,
1816 timeUntilFreeReroll, lockType);
1817 } else if (state == Otc::PREY_ACTION_CHANGE_FROM_ALL) {
1818 Otc::PreyBonusType_t bonusType = (Otc::PreyBonusType_t)msg->getU8();
1819 int bonusValue = msg->getU16();
1820 int bonusGrade = msg->getU8();
1821 int count = msg->getU16();
1822 std::vector<int> races;
1823 for (int i = 0; i < count; ++i) {
1824 races.push_back(msg->getU16());
1825 }
1826 int timeUntilFreeReroll =
1827 g_game.getProtocolVersion() >= 1252 ? msg->getU32() : msg->getU16();
1828 uint8_t lockType =
1829 g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
1830 return g_lua.callGlobalField("g_game", "onPreyChangeFromAll", slot,
1831 bonusType, bonusValue, bonusGrade, races,
1832 timeUntilFreeReroll, lockType);
1833 } else if (state == Otc::PREY_STATE_SELECTION_FROMALL) {
1834 int count = msg->getU16();
1835 std::vector<int> races;
1836 for (int i = 0; i < count; ++i) {
1837 races.push_back(msg->getU16());
1838 }
1839 int timeUntilFreeReroll =
1840 g_game.getProtocolVersion() >= 1252 ? msg->getU32() : msg->getU16();
1841 uint8_t lockType =
1842 g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
1843 return g_lua.callGlobalField("g_game", "onPreyChangeFromAll", slot, races,
1844 timeUntilFreeReroll, lockType);
1845 } else {
1846 g_logger.error(stdext::format("Unknown prey data state: %i", (int)state));
1847 }
1848}
1849
1850void ProtocolGame::parsePreyPrices(const InputMessagePtr &msg) {
1851 int price = msg->getU32();
1852 int wildcard = -1, directly = -1;
1853 if (g_game.getFeature(Otc::GameTibia12Protocol)) {
1854 wildcard = msg->getU8();
1855 directly = msg->getU8();
1856 if (g_game.getProtocolVersion() >= 1230) {
1857 msg->getU32();
1858 msg->getU32();
1859 msg->getU8();
1860 msg->getU8();
1861 }
1862 }
1863 g_lua.callGlobalField("g_game", "onPreyPrice", price, wildcard, directly);
1864}
1865
1866void ProtocolGame::parseStoreOfferDescription(const InputMessagePtr &msg) {
1867 msg->getU32(); // offer id
1868 msg->getString(); // description
1869}
1870
1871void ProtocolGame::parsePlayerInfo(const InputMessagePtr &msg) {
1872 bool premium = msg->getU8(); // premium
1873 if (g_game.getFeature(Otc::GamePremiumExpiration))
1874 /*int premiumEx = */ msg
1875 ->getU32(); // premium expiration used for premium advertisement
1876 int vocation = msg->getU8(); // vocation
1877
1878 if (g_game.getFeature(Otc::GamePrey)) {
1879 /*bool preyEnabled = */ msg->getU8() /* > 0*/;
1880 }
1881
1882 int spellCount = msg->getU16();
1883 std::vector<int> spells;
1884 for (int i = 0; i < spellCount; ++i)
1885 spells.push_back(msg->getU8()); // spell id
1886
1887 m_localPlayer->setPremium(premium);
1888 m_localPlayer->setVocation(vocation);
1889 m_localPlayer->setSpells(spells);
1890}
1891
1892void ProtocolGame::parsePlayerStats(const InputMessagePtr &msg) {
1893 double health;
1894 double maxHealth;
1895
1896 if (g_game.getFeature(Otc::GameDoubleHealth)) {
1897 health = msg->getU32();
1898 maxHealth = msg->getU32();
1899 } else {
1900 health = msg->getU16();
1901 maxHealth = msg->getU16();
1902 }
1903
1904 double freeCapacity;
1905 if (g_game.getFeature(Otc::GameDoubleFreeCapacity))
1906 freeCapacity = msg->getU32() / 100.0;
1907 else
1908 freeCapacity = msg->getU16() / 100.0;
1909
1910 double totalCapacity = freeCapacity;
1911 if (g_game.getFeature(Otc::GameTotalCapacity) &&
1912 !g_game.getFeature(Otc::GameTibia12Protocol))
1913 totalCapacity = msg->getU32() / 100.0;
1914
1915 double experience;
1916 if (g_game.getFeature(Otc::GameDoubleExperience))
1917 experience = msg->getU64();
1918 else
1919 experience = msg->getU32();
1920
1921 double level;
1922 if (g_game.getFeature(Otc::GameDoubleLevel))
1923 level = msg->getU32();
1924 else
1925 level = msg->getU16();
1926
1927 double levelPercent = msg->getU8();
1928
1929 if (g_game.getFeature(Otc::GameExperienceBonus)) {
1930 if (g_game.getProtocolVersion() <= 1096) {
1931 /*double experienceBonus = */ msg->getDouble();
1932 } else {
1933 /*int baseXpGain = */ msg->getU16();
1934 if (!g_game.getFeature(Otc::GameTibia12Protocol)) {
1935 /*int voucherAddend = */ msg->getU16();
1936 }
1937 /*int grindingAddend = */ msg->getU16();
1938 /*int storeBoostAddend = */ msg->getU16();
1939 /*int huntingBoostFactor = */ msg->getU16();
1940 }
1941 }
1942
1943 double mana;
1944 double maxMana;
1945
1946 if (g_game.getFeature(Otc::GameDoubleHealth)) {
1947 mana = msg->getU32();
1948 maxMana = msg->getU32();
1949 } else {
1950 mana = msg->getU16();
1951 maxMana = msg->getU16();
1952 }
1953
1954 double magicLevel = 0;
1955 if (!g_game.getFeature(Otc::GameTibia12Protocol)) {
1956 if (g_game.getFeature(Otc::GameDoubleMagicLevel))
1957 magicLevel = msg->getU16();
1958 else
1959 magicLevel = msg->getU8();
1960 }
1961
1962 double baseMagicLevel = 0;
1963 if (!g_game.getFeature(Otc::GameTibia12Protocol)) {
1964 if (g_game.getFeature(Otc::GameSkillsBase))
1965 baseMagicLevel = msg->getU8();
1966 else
1967 baseMagicLevel = magicLevel;
1968 }
1969
1970 double magicLevelPercent = 0;
1971 if (!g_game.getFeature(Otc::GameTibia12Protocol))
1972 magicLevelPercent = msg->getU8();
1973
1974 double soul;
1975 if (g_game.getFeature(Otc::GameDoubleSoul))
1976 soul = msg->getU16();
1977 else
1978 soul = msg->getU8();
1979
1980 double stamina = 0;
1981 if (g_game.getFeature(Otc::GamePlayerStamina))
1982 stamina = msg->getU16();
1983
1984 double baseSpeed = 0;
1985 if (g_game.getFeature(Otc::GameSkillsBase))
1986 baseSpeed = msg->getU16();
1987
1988 double regeneration = 0;
1989 if (g_game.getFeature(Otc::GamePlayerRegenerationTime))
1990 regeneration = msg->getU16();
1991
1992 double training = 0;
1993 if (g_game.getFeature(Otc::GameOfflineTrainingTime)) {
1994 training = msg->getU16();
1995 if (g_game.getProtocolVersion() >= 1097) {
1996 /*int remainingStoreXpBoostSeconds = */ msg->getU16();
1997 /*bool canBuyMoreStoreXpBoosts = */ msg->getU8();
1998 }
1999 }
2000
2001 m_localPlayer->setHealth(health, maxHealth);
2002 m_localPlayer->setFreeCapacity(freeCapacity);
2003 if (!g_game.getFeature(Otc::GameTibia12Protocol))
2004 m_localPlayer->setTotalCapacity(totalCapacity);
2005 m_localPlayer->setExperience(experience);
2006 m_localPlayer->setLevel(level, levelPercent);
2007 m_localPlayer->setMana(mana, maxMana);
2008 if (!g_game.getFeature(Otc::GameTibia12Protocol)) {
2009 m_localPlayer->setMagicLevel(magicLevel, magicLevelPercent);
2010 m_localPlayer->setBaseMagicLevel(baseMagicLevel);
2011 }
2012 m_localPlayer->setStamina(stamina);
2013 m_localPlayer->setSoul(soul);
2014 m_localPlayer->setBaseSpeed(baseSpeed);
2015 m_localPlayer->setRegenerationTime(regeneration);
2016 m_localPlayer->setOfflineTrainingTime(training);
2017}
2018
2019void ProtocolGame::parsePlayerSkills(const InputMessagePtr &msg) {
2020 int lastSkill = Otc::Fishing + 1;
2021 if (g_game.getFeature(Otc::GameAdditionalSkills))
2022 lastSkill = Otc::LastSkill;
2023
2024 if (g_game.getFeature(Otc::GameTibia12Protocol)) {
2025 int level = msg->getU16();
2026 int baseLevel = msg->getU16();
2027 msg->getU16(); // unknown
2028 int levelPercent = msg->getU16();
2029 m_localPlayer->setMagicLevel(level, levelPercent);
2030 m_localPlayer->setBaseMagicLevel(baseLevel);
2031 }
2032
2033 for (int skill = 0; skill < lastSkill; skill++) {
2034 int level;
2035
2036 if (g_game.getFeature(Otc::GameDoubleSkills))
2037 level = msg->getU16();
2038 else
2039 level = msg->getU8();
2040
2041 int baseLevel;
2042 if (g_game.getFeature(Otc::GameSkillsBase))
2043 if (g_game.getFeature(Otc::GameBaseSkillU16))
2044 baseLevel = msg->getU16();
2045 else
2046 baseLevel = msg->getU8();
2047 else
2048 baseLevel = level;
2049
2050 int levelPercent = 0;
2051 // Critical, Life Leech and Mana Leech have no level percent
2052 if (skill <= Otc::Fishing) {
2053 if (g_game.getFeature(Otc::GameTibia12Protocol))
2054 msg->getU16(); // unknown
2055
2056 if (g_game.getFeature(Otc::GameTibia12Protocol))
2057 levelPercent = msg->getU16();
2058 else
2059 levelPercent = msg->getU8();
2060 }
2061
2062 m_localPlayer->setSkill((Otc::Skill)skill, level, levelPercent);
2063 m_localPlayer->setBaseSkill((Otc::Skill)skill, baseLevel);
2064 }
2065
2066 if (g_game.getFeature(Otc::GameTibia12Protocol)) {
2067 uint32_t totalCapacity = msg->getU32();
2068 msg->getU32(); // base capacity?
2069 m_localPlayer->setTotalCapacity(totalCapacity);
2070 }
2071}
2072
2073void ProtocolGame::parsePlayerState(const InputMessagePtr &msg) {
2074 int states;
2075 if (g_game.getFeature(Otc::GamePlayerStateU32))
2076 states = msg->getU32();
2077 else if (g_game.getFeature(Otc::GamePlayerStateU16))
2078 states = msg->getU16();
2079 else
2080 states = msg->getU8();
2081
2082 m_localPlayer->setStates(states);
2083}
2084
2085void ProtocolGame::parsePlayerCancelAttack(const InputMessagePtr &msg) {
2086 uint seq = 0;
2087 if (g_game.getFeature(Otc::GameAttackSeq))
2088 seq = msg->getU32();
2089
2090 g_game.processAttackCancel(seq);
2091}
2092
2093void ProtocolGame::parsePlayerModes(const InputMessagePtr &msg) {
2094 int fightMode = msg->getU8();
2095 int chaseMode = msg->getU8();
2096 bool safeMode = msg->getU8();
2097
2098 int pvpMode = 0;
2099 if (g_game.getFeature(Otc::GamePVPMode))
2100 pvpMode = msg->getU8();
2101
2102 g_game.processPlayerModes((Otc::FightModes)fightMode,
2103 (Otc::ChaseModes)chaseMode, safeMode,
2104 (Otc::PVPModes)pvpMode);
2105}
2106
2107void ProtocolGame::parseSpellCooldown(const InputMessagePtr &msg) {
2108 int spellId = msg->getU8();
2109 int delay = msg->getU32();
2110
2111 g_lua.callGlobalField("g_game", "onSpellCooldown", spellId, delay);
2112}
2113
2114void ProtocolGame::parseSpellGroupCooldown(const InputMessagePtr &msg) {
2115 int groupId = msg->getU8();
2116 int delay = msg->getU32();
2117
2118 g_lua.callGlobalField("g_game", "onSpellGroupCooldown", groupId, delay);
2119}
2120
2121void ProtocolGame::parseMultiUseCooldown(const InputMessagePtr &msg) {
2122 int delay = msg->getU32();
2123
2124 g_lua.callGlobalField("g_game", "onMultiUseCooldown", delay);
2125}
2126
2127void ProtocolGame::parseTalk(const InputMessagePtr &msg) {
2128 uint32_t statement = 0;
2129 if (g_game.getFeature(Otc::GameMessageStatements))
2130 statement = msg->getU32(); // channel statement guid
2131
2132 std::string name = g_game.formatCreatureName(msg->getString());
2133
2134 if (statement > 0 && g_game.getFeature(Otc::GameTibia12Protocol) &&
2135 g_game.getProtocolVersion() > 1240)
2136 msg->getU8();
2137
2138 int level = 0;
2139 if (g_game.getFeature(Otc::GameMessageLevel)) {
2140 if (g_game.getFeature(Otc::GameDoubleLevel)) {
2141 level = msg->getU32();
2142 } else {
2143 level = msg->getU16();
2144 }
2145 }
2146
2147 Otc::MessageMode mode = Proto::translateMessageModeFromServer(msg->getU8());
2148 int channelId = 0;
2149 Position pos;
2150
2151 switch (mode) {
2152 case Otc::MessageSay:
2153 case Otc::MessageWhisper:
2154 case Otc::MessageYell:
2155 case Otc::MessageMonsterSay:
2156 case Otc::MessageMonsterYell:
2157 case Otc::MessageNpcTo:
2158 case Otc::MessageBarkLow:
2159 case Otc::MessageBarkLoud:
2160 case Otc::MessageSpell:
2161 case Otc::MessageNpcFromStartBlock:
2162 pos = getPosition(msg);
2163 break;
2164 case Otc::MessageChannel:
2165 case Otc::MessageChannelManagement:
2166 case Otc::MessageChannelHighlight:
2167 case Otc::MessageGamemasterChannel:
2168 channelId = msg->getU16();
2169 break;
2170 case Otc::MessageNpcFrom:
2171 case Otc::MessagePrivateFrom:
2172 case Otc::MessageGamemasterBroadcast:
2173 case Otc::MessageGamemasterPrivateFrom:
2174 case Otc::MessageRVRAnswer:
2175 case Otc::MessageRVRContinue:
2176 break;
2177 case Otc::MessageRVRChannel:
2178 msg->getU32();
2179 break;
2180 default:
2181 stdext::throw_exception(stdext::format("unknown message mode %d", mode));
2182 break;
2183 }
2184
2185 std::string text = msg->getString();
2186
2187 g_game.processTalk(name, level, mode, text, channelId, pos);
2188}
2189
2190void ProtocolGame::parseChannelList(const InputMessagePtr &msg) {
2191 int count = msg->getU8();
2192 std::vector<std::tuple<int, std::string>> channelList;
2193 for (int i = 0; i < count; i++) {
2194 int id = msg->getU16();
2195 std::string name = msg->getString();
2196 channelList.push_back(std::make_tuple(id, name));
2197 }
2198
2199 g_game.processChannelList(channelList);
2200}
2201
2202void ProtocolGame::parseOpenChannel(const InputMessagePtr &msg) {
2203 int channelId = msg->getU16();
2204 std::string name = msg->getString();
2205
2206 if (g_game.getFeature(Otc::GameChannelPlayerList)) {
2207 int joinedPlayers = msg->getU16();
2208 for (int i = 0; i < joinedPlayers; ++i)
2209 g_game.formatCreatureName(msg->getString()); // player name
2210 int invitedPlayers = msg->getU16();
2211 for (int i = 0; i < invitedPlayers; ++i)
2212 g_game.formatCreatureName(msg->getString()); // player name
2213 }
2214
2215 g_game.processOpenChannel(channelId, name);
2216}
2217
2218void ProtocolGame::parseOpenPrivateChannel(const InputMessagePtr &msg) {
2219 std::string name = g_game.formatCreatureName(msg->getString());
2220
2221 g_game.processOpenPrivateChannel(name);
2222}
2223
2224void ProtocolGame::parseOpenOwnPrivateChannel(const InputMessagePtr &msg) {
2225 int channelId = msg->getU16();
2226 std::string name = msg->getString();
2227
2228 if (g_game.getFeature(Otc::GameChannelPlayerList)) {
2229 int joinedPlayers = msg->getU16();
2230 for (int i = 0; i < joinedPlayers; ++i)
2231 g_game.formatCreatureName(msg->getString()); // player name
2232 int invitedPlayers = msg->getU16();
2233 for (int i = 0; i < invitedPlayers; ++i)
2234 g_game.formatCreatureName(msg->getString()); // player name
2235 }
2236
2237 g_game.processOpenOwnPrivateChannel(channelId, name);
2238}
2239
2240void ProtocolGame::parseCloseChannel(const InputMessagePtr &msg) {
2241 int channelId = msg->getU16();
2242
2243 g_game.processCloseChannel(channelId);
2244}
2245
2246void ProtocolGame::parseRuleViolationChannel(const InputMessagePtr &msg) {
2247 int channelId = msg->getU16();
2248
2249 g_game.processRuleViolationChannel(channelId);
2250}
2251
2252void ProtocolGame::parseRuleViolationRemove(const InputMessagePtr &msg) {
2253 std::string name = msg->getString();
2254
2255 g_game.processRuleViolationRemove(name);
2256}
2257
2258void ProtocolGame::parseRuleViolationCancel(const InputMessagePtr &msg) {
2259 std::string name = msg->getString();
2260
2261 g_game.processRuleViolationCancel(name);
2262}
2263
2264void ProtocolGame::parseRuleViolationLock(const InputMessagePtr &msg) {
2265 g_game.processRuleViolationLock();
2266}
2267
2268void ProtocolGame::parseTextMessage(const InputMessagePtr &msg) {
2269 int code = msg->getU8();
2270 Otc::MessageMode mode = Proto::translateMessageModeFromServer(code);
2271 std::string text;
2272 std::string font;
2273
2274 switch (mode) {
2275 case Otc::MessageChannelManagement: {
2276 /*int channel = */ msg->getU16();
2277 text = msg->getString();
2278 break;
2279 }
2280 case Otc::MessageGuild:
2281 case Otc::MessagePartyManagement:
2282 case Otc::MessageParty: {
2283 /*int channel = */ msg->getU16();
2284 text = msg->getString();
2285 break;
2286 }
2287 case Otc::MessageDamageDealed:
2288 case Otc::MessageDamageReceived:
2289 case Otc::MessageDamageOthers: {
2290 Position pos = getPosition(msg);
2291 uint value[2];
2292 int color[2];
2293
2294 // physical damage
2295 value[0] = msg->getU32();
2296 color[0] = msg->getU8();
2297
2298 // magic damage
2299 value[1] = msg->getU32();
2300 color[1] = msg->getU8();
2301 if (g_game.getFeature(Otc::GameAnimatedTextCustomFont))
2302 font = msg->getString();
2303 text = msg->getString();
2304
2305 for (int i = 0; i < 2; ++i) {
2306 if (value[i] == 0)
2307 continue;
2308 AnimatedTextPtr animatedText = AnimatedTextPtr(new AnimatedText);
2309 animatedText->setColor(color[i]);
2310 animatedText->setText(stdext::to_string(value[i]));
2311 if (font.size())
2312 animatedText->setFont(font);
2313
2314 g_map.addThing(animatedText, pos);
2315 }
2316 break;
2317 }
2318 case Otc::MessageHeal:
2319 case Otc::MessageMana:
2320 case Otc::MessageExp:
2321 case Otc::MessageHealOthers:
2322 case Otc::MessageExpOthers: {
2323 Position pos = getPosition(msg);
2324 uint value = msg->getU32();
2325 int color = msg->getU8();
2326 if (g_game.getFeature(Otc::GameAnimatedTextCustomFont))
2327 font = msg->getString();
2328 text = msg->getString();
2329
2330 AnimatedTextPtr animatedText = AnimatedTextPtr(new AnimatedText);
2331 animatedText->setColor(color);
2332 animatedText->setText(stdext::to_string(value));
2333 if (font.size())
2334 animatedText->setFont(font);
2335 g_map.addThing(animatedText, pos);
2336 break;
2337 }
2338 case Otc::MessageInvalid:
2339 stdext::throw_exception(stdext::format("unknown message mode %d", mode));
2340 break;
2341 default:
2342 text = msg->getString();
2343 break;
2344 }
2345
2346 g_game.processTextMessage(mode, text);
2347}
2348
2349void ProtocolGame::parseCancelWalk(const InputMessagePtr &msg) {
2350 Otc::Direction direction = (Otc::Direction)msg->getU8();
2351
2352 g_game.processWalkCancel(direction);
2353}
2354
2355void ProtocolGame::parseWalkWait(const InputMessagePtr &msg) {
2356 int millis = msg->getU16();
2357 m_localPlayer->lockWalk(millis);
2358}
2359
2360void ProtocolGame::parseFloorChangeUp(const InputMessagePtr &msg) {
2361 Position pos;
2362 if (g_game.getFeature(Otc::GameMapMovePosition))
2363 pos = getPosition(msg);
2364 else
2365 pos = g_map.getCentralPosition();
2366 AwareRange range = g_map.getAwareRange();
2367 pos.z--;
2368
2369 Position newPos = pos;
2370 newPos.x++;
2371 newPos.y++;
2372 g_map.setCentralPosition(newPos);
2373
2374 g_lua.callGlobalField("g_game", "onTeleport", m_localPlayer, newPos, pos);
2375
2376 int skip = 0;
2377 if (pos.z == Otc::SEA_FLOOR)
2378 for (int i = Otc::SEA_FLOOR - Otc::AWARE_UNDEGROUND_FLOOR_RANGE; i >= 0;
2379 i--)
2380 skip = setFloorDescription(msg, pos.x - range.left, pos.y - range.top, i,
2381 range.horizontal(), range.vertical(), 8 - i,
2382 skip);
2383 else if (pos.z > Otc::SEA_FLOOR)
2384 skip = setFloorDescription(msg, pos.x - range.left, pos.y - range.top,
2385 pos.z - Otc::AWARE_UNDEGROUND_FLOOR_RANGE,
2386 range.horizontal(), range.vertical(), 6, skip);
2387}
2388
2389void ProtocolGame::parseFloorChangeDown(const InputMessagePtr &msg) {
2390 Position pos;
2391 if (g_game.getFeature(Otc::GameMapMovePosition))
2392 pos = getPosition(msg);
2393 else
2394 pos = g_map.getCentralPosition();
2395 AwareRange range = g_map.getAwareRange();
2396 pos.z++;
2397
2398 Position newPos = pos;
2399 newPos.x--;
2400 newPos.y--;
2401 g_map.setCentralPosition(newPos);
2402
2403 g_lua.callGlobalField("g_game", "onTeleport", m_localPlayer, newPos, pos);
2404
2405 int skip = 0;
2406 if (pos.z == Otc::UNDERGROUND_FLOOR) {
2407 int j, i;
2408 for (i = pos.z, j = -1; i <= pos.z + Otc::AWARE_UNDEGROUND_FLOOR_RANGE;
2409 ++i, --j)
2410 skip = setFloorDescription(msg, pos.x - range.left, pos.y - range.top, i,
2411 range.horizontal(), range.vertical(), j, skip);
2412 } else if (pos.z > Otc::UNDERGROUND_FLOOR && pos.z < Otc::MAX_Z - 4)
2413 skip = setFloorDescription(msg, pos.x - range.left, pos.y - range.top,
2414 pos.z + Otc::AWARE_UNDEGROUND_FLOOR_RANGE,
2415 range.horizontal(), range.vertical(), -6, skip);
2416}
2417
2418void ProtocolGame::parseOpenOutfitWindow(const InputMessagePtr &msg) {
2419 Outfit currentOutfit = getOutfit(msg);
2420 std::vector<std::tuple<int, std::string, int>> outfitList;
2421
2422 if (g_game.getFeature(Otc::GameNewOutfitProtocol)) {
2423 int outfitCount = g_game.getFeature(Otc::GameTibia12Protocol)
2424 ? msg->getU16()
2425 : msg->getU8();
2426 for (int i = 0; i < outfitCount; i++) {
2427 int outfitId = msg->getU16();
2428 std::string outfitName = msg->getString();
2429 int outfitAddons = msg->getU8();
2430 if (g_game.getFeature(Otc::GameTibia12Protocol)) {
2431 bool locked = msg->getU8() > 0;
2432 if (locked) {
2433 msg->getU32(); // store offer id
2434 }
2435 }
2436 outfitList.push_back(std::make_tuple(outfitId, outfitName, outfitAddons));
2437 }
2438 } else {
2439 int outfitStart, outfitEnd;
2440 if (g_game.getFeature(Otc::GameLooktypeU16)) {
2441 outfitStart = msg->getU16();
2442 outfitEnd = msg->getU16();
2443 } else {
2444 outfitStart = msg->getU8();
2445 outfitEnd = msg->getU8();
2446 }
2447
2448 for (int i = outfitStart; i <= outfitEnd; i++)
2449 outfitList.push_back(std::make_tuple(i, "", 0));
2450 }
2451
2452 std::vector<std::tuple<int, std::string>> mountList;
2453 std::vector<std::tuple<int, std::string>> wingList;
2454 std::vector<std::tuple<int, std::string>> auraList;
2455 std::vector<std::tuple<int, std::string>> shaderList;
2456 std::vector<std::tuple<int, std::string>> healthBarList;
2457 std::vector<std::tuple<int, std::string>> manaBarList;
2458 if (g_game.getFeature(Otc::GamePlayerMounts)) {
2459 int mountCount = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU16()
2460 : msg->getU8();
2461 for (int i = 0; i < mountCount; ++i) {
2462 int mountId = msg->getU16(); // mount type
2463 std::string mountName = msg->getString(); // mount name
2464 if (g_game.getFeature(Otc::GameTibia12Protocol)) {
2465 bool locked = msg->getU8() > 0;
2466 if (locked) {
2467 msg->getU32(); // store offer id
2468 }
2469 }
2470
2471 mountList.push_back(std::make_tuple(mountId, mountName));
2472 }
2473 }
2474
2475 if (g_game.getFeature(Otc::GameWingsAndAura)) {
2476 int wingCount = msg->getU8();
2477 for (int i = 0; i < wingCount; ++i) {
2478 int wingId = msg->getU16();
2479 std::string wingName = msg->getString();
2480 wingList.push_back(std::make_tuple(wingId, wingName));
2481 }
2482 int auraCount = msg->getU8();
2483 for (int i = 0; i < auraCount; ++i) {
2484 int auraId = msg->getU16();
2485 std::string auraName = msg->getString();
2486 auraList.push_back(std::make_tuple(auraId, auraName));
2487 }
2488 }
2489
2490 if (g_game.getFeature(Otc::GameOutfitShaders)) {
2491 int shaderCount = msg->getU8();
2492 for (int i = 0; i < shaderCount; ++i) {
2493 int shaderId = msg->getU16();
2494 std::string shaderName = msg->getString();
2495 shaderList.push_back(std::make_tuple(shaderId, shaderName));
2496 }
2497 }
2498
2499 if (g_game.getFeature(Otc::GameOutfitAttackData)) {
2500 int runeEffectCount = msg->getU8();
2501 for (int i = 0; i < runeEffectCount; ++i) {
2502 msg->getU16();
2503 msg->getString();
2504 msg->skipBytes(9);
2505 msg->skipBytes(12);
2506 int layerCount = msg->getU8();
2507 for (int l = 0; l < layerCount; ++l) {
2508 msg->skipBytes(17);
2509 if (msg->getU8() != 0)
2510 msg->getString();
2511 }
2512 }
2513 }
2514
2515 if (g_game.getFeature(Otc::GameHealthInfoBackground)) {
2516 int count = msg->getU8();
2517 for (int i = 0; i < count; ++i) {
2518 int id = msg->getU16();
2519 std::string name = msg->getString();
2520 healthBarList.push_back(std::make_tuple(id, name));
2521 }
2522
2523 count = msg->getU8();
2524 for (int i = 0; i < count; ++i) {
2525 int id = msg->getU16();
2526 std::string name = msg->getString();
2527 manaBarList.push_back(std::make_tuple(id, name));
2528 }
2529 }
2530
2531 if (g_game.getFeature(Otc::GameTibia12Protocol)) {
2532 msg->getU8(); // tryOnMount, tryOnOutfit
2533 msg->getU8(); // mounted?
2534 }
2535
2536 g_game.processOpenOutfitWindow(currentOutfit, outfitList, mountList, wingList,
2537 auraList, shaderList, healthBarList,
2538 manaBarList);
2539}
2540
2541void ProtocolGame::parseVipAdd(const InputMessagePtr &msg) {
2542 uint id, iconId = 0, status;
2543 std::string name, desc = "";
2544 bool notifyLogin = false;
2545
2546 id = msg->getU32();
2547 name = g_game.formatCreatureName(msg->getString());
2548 if (g_game.getFeature(Otc::GameAdditionalVipInfo)) {
2549 desc = msg->getString();
2550 iconId = msg->getU32();
2551 notifyLogin = msg->getU8();
2552 }
2553 status = msg->getU8();
2554
2555 if (g_game.getFeature(Otc::GameTibia12Protocol)) {
2556 int groups = msg->getU8();
2557 for (int i = 0; i < groups; ++i)
2558 msg->getU8(); // group id
2559 }
2560
2561 g_game.processVipAdd(id, name, status, desc, iconId, notifyLogin);
2562}
2563
2564void ProtocolGame::parseVipState(const InputMessagePtr &msg) {
2565 uint id = msg->getU32();
2566 if (g_game.getFeature(Otc::GameLoginPending)) {
2567 uint status = msg->getU8();
2568 g_game.processVipStateChange(id, status);
2569 } else {
2570 g_game.processVipStateChange(id, 1);
2571 }
2572}
2573
2574void ProtocolGame::parseVipLogout(const InputMessagePtr &msg) {
2575 uint id = msg->getU32();
2576 g_game.processVipStateChange(id, 0);
2577}
2578
2579void ProtocolGame::parseVipGroupData(const InputMessagePtr &msg) {
2580 int size = msg->getU8();
2581 for (int i = 0; i < size; ++i) {
2582 msg->getU8(); // group id
2583 msg->getString(); // group name
2584 msg->getU8(); // unkown
2585 }
2586
2587 msg->getU8(); // max vip groups
2588}
2589
2590void ProtocolGame::parseTutorialHint(const InputMessagePtr &msg) {
2591 int id = msg->getU8();
2592 g_game.processTutorialHint(id);
2593}
2594
2595void ProtocolGame::parseCyclopediaMapData(const InputMessagePtr &msg) {
2596 if (g_game.getFeature(Otc::GameTibia12Protocol)) {
2597 int type = msg->getU8();
2598 switch (type) {
2599 case 0:
2600 break;
2601 case 1: {
2602 int count = msg->getU16();
2603 for (int i = 0; i < count; ++i) {
2604 msg->getU8();
2605 msg->getU8();
2606 msg->getU8();
2607 msg->getU8();
2608 }
2609 count = msg->getU16();
2610 for (int i = 0; i < count; ++i) {
2611 msg->getU16();
2612 }
2613 count = msg->getU16();
2614 for (int i = 0; i < count; ++i) {
2615 msg->getU16();
2616 }
2617 break;
2618 }
2619 case 2: // raid
2620 {
2621 getPosition(msg);
2622 msg->getU8();
2623 break;
2624 }
2625 case 3: {
2626 msg->getU8();
2627 msg->getU8();
2628 msg->getU8();
2629 break;
2630 }
2631 case 4: {
2632 msg->getU8();
2633 msg->getU8();
2634 msg->getU8();
2635 break;
2636 }
2637 case 5: {
2638 msg->getU16();
2639 msg->getU8();
2640 int count = msg->getU8();
2641 for (int i = 0; i < count; ++i) {
2642 getPosition(msg);
2643 msg->getU8();
2644 }
2645 break;
2646 }
2647 case 6: {
2648 break;
2649 }
2650 case 7: {
2651 break;
2652 }
2653 case 8: {
2654 break;
2655 }
2656 case 9: {
2657 msg->getU32();
2658 msg->getU32();
2659 int count = msg->getU8();
2660 for (int i = 0; i < count; ++i) {
2661 msg->getU16();
2662 msg->getU32();
2663 msg->getU32();
2664 msg->getU8();
2665 }
2666 }
2667 case 10: {
2668 msg->getU16();
2669 break;
2670 }
2671 case 11: {
2672 break;
2673 }
2674 }
2675 if (type != 0)
2676 return;
2677 }
2678
2679 Position pos = getPosition(msg);
2680 int icon = msg->getU8();
2681 std::string description = msg->getString();
2682
2683 bool remove = false;
2684 if (g_game.getFeature(Otc::GameMinimapRemove))
2685 remove = msg->getU8() != 0;
2686
2687 if (!remove)
2688 g_game.processAddAutomapFlag(pos, icon, description);
2689 else
2690 g_game.processRemoveAutomapFlag(pos, icon, description);
2691}
2692
2693void ProtocolGame::parseQuestLog(const InputMessagePtr &msg) {
2694 std::vector<std::tuple<int, std::string, bool>> questList;
2695 int questsCount = msg->getU16();
2696 for (int i = 0; i < questsCount; i++) {
2697 int id = msg->getU16();
2698 std::string name = msg->getString();
2699 bool completed = msg->getU8();
2700 questList.push_back(std::make_tuple(id, name, completed));
2701 }
2702
2703 g_game.processQuestLog(questList);
2704}
2705
2706void ProtocolGame::parseQuestLine(const InputMessagePtr &msg) {
2707 std::vector<std::tuple<std::string, std::string, int>> questMissions;
2708 int questId = msg->getU16();
2709 int missionCount = msg->getU8();
2710 for (int i = 0; i < missionCount; i++) {
2711 int missionId = 0;
2712 if (g_game.getFeature(Otc::GameTibia12Protocol) ||
2713 g_game.getFeature(Otc::GameMissionId))
2714 missionId = msg->getU16();
2715
2716 std::string missionName = msg->getString();
2717 std::string missionDescrition = msg->getString();
2718 questMissions.push_back(
2719 std::make_tuple(missionName, missionDescrition, missionId));
2720 }
2721
2722 g_game.processQuestLine(questId, questMissions);
2723}
2724
2725void ProtocolGame::parseChannelEvent(const InputMessagePtr &msg) {
2726 uint16 channelId = msg->getU16();
2727 std::string name = g_game.formatCreatureName(msg->getString());
2728 uint8 type = msg->getU8();
2729
2730 g_lua.callGlobalField("g_game", "onChannelEvent", channelId, name, type);
2731}
2732
2733void ProtocolGame::parseItemInfo(const InputMessagePtr &msg) {
2734 std::vector<std::tuple<ItemPtr, std::string>> list;
2735 int size = msg->getU8();
2736 for (int i = 0; i < size; ++i) {
2737 ItemPtr item(new Item);
2738 item->setId(msg->getU16());
2739 item->setCountOrSubType(
2740 g_game.getFeature(Otc::GameCountU16) ? msg->getU16() : msg->getU8());
2741
2742 std::string desc = msg->getString();
2743 list.push_back(std::make_tuple(item, desc));
2744 }
2745
2746 g_lua.callGlobalField("g_game", "onItemInfo", list);
2747}
2748
2749void ProtocolGame::parsePlayerInventory(const InputMessagePtr &msg) {
2750 int size = msg->getU16();
2751 for (int i = 0; i < size; ++i) {
2752 msg->getU16(); // id
2753 msg->getU8(); // subtype
2754 msg->getU16(); // count
2755 }
2756}
2757
2758void ProtocolGame::parseModalDialog(const InputMessagePtr &msg) {
2759 uint32 id = msg->getU32();
2760 std::string title = msg->getString();
2761 std::string message = msg->getString();
2762
2763 int sizeButtons = msg->getU8();
2764 std::vector<std::tuple<int, std::string>> buttonList;
2765 for (int i = 0; i < sizeButtons; ++i) {
2766 std::string value = msg->getString();
2767 int id = msg->getU8();
2768 buttonList.push_back(std::make_tuple(id, value));
2769 }
2770
2771 int sizeChoices = msg->getU8();
2772 std::vector<std::tuple<int, std::string>> choiceList;
2773 for (int i = 0; i < sizeChoices; ++i) {
2774 std::string value = msg->getString();
2775 int id = msg->getU8();
2776 choiceList.push_back(std::make_tuple(id, value));
2777 }
2778
2779 int enterButton, escapeButton;
2780 if (g_game.getProtocolVersion() > 970) {
2781 escapeButton = msg->getU8();
2782 enterButton = msg->getU8();
2783 } else {
2784 enterButton = msg->getU8();
2785 escapeButton = msg->getU8();
2786 }
2787
2788 bool priority = msg->getU8() == 0x01;
2789
2790 g_game.processModalDialog(id, title, message, buttonList, enterButton,
2791 escapeButton, choiceList, priority);
2792}
2793
2794void ProtocolGame::parseClientCheck(const InputMessagePtr &msg) {
2795 msg->getU32();
2796 msg->getU8();
2797}
2798
2799void ProtocolGame::parseGameNews(const InputMessagePtr &msg) {
2800 msg->getU32();
2801 msg->getU8();
2802}
2803
2804void ProtocolGame::parseMessageDialog(const InputMessagePtr &msg) {
2805 msg->getU8();
2806 msg->getString();
2807}
2808
2809void ProtocolGame::parseBlessDialog(const InputMessagePtr &msg) {
2810 // parse bless amount
2811 uint8_t totalBless = msg->getU8(); // total bless
2812
2813 // parse each bless
2814 for (int i = 0; i < totalBless; i++) {
2815 msg->getU16(); // bless bit wise
2816 msg->getU8(); // player bless count
2817 if (g_game.getProtocolVersion() >= 1220) {
2818 msg->getU8(); // store?
2819 }
2820 }
2821
2822 // parse general info
2823 msg->getU8(); // premium
2824 msg->getU8(); // promotion
2825 msg->getU8(); // pvp min xp loss
2826 msg->getU8(); // pvp max xp loss
2827 msg->getU8(); // pve exp loss
2828 msg->getU8(); // equip pvp loss
2829 msg->getU8(); // equip pve loss
2830 msg->getU8(); // skull
2831 msg->getU8(); // aol
2832
2833 // parse log
2834 uint8_t logCount = msg->getU8(); // log count
2835 for (int i = 0; i < logCount; i++) {
2836 msg->getU32(); // timestamp
2837 msg->getU8(); // color message (0 = white loss, 1 = red)
2838 msg->getString(); // history message
2839 }
2840}
2841
2842void ProtocolGame::parseResourceBalance(const InputMessagePtr &msg) {
2843 uint8_t type = msg->getU8();
2844 uint64_t amount = msg->getU64();
2845 g_lua.callGlobalField("g_game", "onResourceBalance", type, amount);
2846}
2847
2848void ProtocolGame::parseServerTime(const InputMessagePtr &msg) {
2849 uint8_t minutes = msg->getU8();
2850 uint8_t seconds = msg->getU8();
2851 g_lua.callGlobalField("g_game", "onServerTime", minutes, seconds);
2852}
2853
2854void ProtocolGame::parseQuestTracker(const InputMessagePtr &msg) {
2855 msg->getU8();
2856 msg->getU16();
2857}
2858
2859void ProtocolGame::parseImbuementWindow(const InputMessagePtr &msg) {
2860 int itemId = msg->getU16();
2861 int slots = msg->getU8();
2862
2863 std::map<int, std::tuple<Imbuement, int, int>> activeSlots;
2864 for (int i = 0; i < slots; ++i) {
2865 bool info = msg->getU8() == 1;
2866 if (info) {
2867 Imbuement imbuement = getImbuementInfo(msg);
2868 int duration = msg->getU32();
2869 int removalCost = msg->getU32();
2870 activeSlots[i] = std::make_tuple(imbuement, duration, removalCost);
2871 }
2872 }
2873
2874 int imbuements_size = msg->getU16();
2875 std::vector<Imbuement> imbuements;
2876 for (int i = 0; i < imbuements_size; ++i) {
2877 imbuements.push_back(getImbuementInfo(msg));
2878 }
2879
2880 std::vector<ItemPtr> needItems;
2881 int needItems_count = msg->getU32();
2882 for (int i = 0; i < needItems_count; ++i) {
2883 int item = msg->getU16();
2884 int count = msg->getU16();
2885 needItems.push_back(Item::create(item, count));
2886 }
2887
2888 g_lua.callGlobalField("g_game", "onImbuementWindow", itemId, slots,
2889 activeSlots, imbuements, needItems);
2890}
2891
2892void ProtocolGame::parseCloseImbuementWindow(const InputMessagePtr &) {
2893 g_lua.callGlobalField("g_game", "onCloseImbuementWindow");
2894}
2895
2896void ProtocolGame::parseCyclopedia(const InputMessagePtr &msg) {
2897 msg->getU16(); // race id
2898}
2899
2900void ProtocolGame::parseCyclopediaNewDetails(const InputMessagePtr &msg) {
2901 g_logger.info("parseCyclopediaNewDetails should be implemented in lua");
2902}
2903
2904void ProtocolGame::parseDailyRewardState(const InputMessagePtr &msg) {
2905 msg->getU8(); // state
2906}
2907
2908void ProtocolGame::parseOpenRewardWall(const InputMessagePtr &msg) {
2909 msg->getU8(); // bonus shrine (1) or instant bonus (0)
2910 msg->getU32(); // next reward time
2911 msg->getU8(); // day streak day
2912 uint8_t wasDailyRewardTaken =
2913 msg->getU8(); // taken (player already took reward?)
2914
2915 if (wasDailyRewardTaken) {
2916 msg->getString(); // error message
2917 }
2918
2919 msg->getU32(); // time left to pickup reward without loosing streak
2920 msg->getU16(); // day streak level
2921 msg->getU16(); // unknown
2922}
2923
2924void ProtocolGame::parseDailyReward(const InputMessagePtr &msg) {
2925 uint8_t count = msg->getU8(); // state
2926
2927 // TODO: implement daily reward usage
2928}
2929
2930void ProtocolGame::parseDailyRewardHistory(const InputMessagePtr &msg) {
2931 uint8_t historyCount = msg->getU8(); // history count
2932
2933 for (int i = 0; i < historyCount; i++) {
2934 msg->getU32(); // timestamp
2935 msg->getU8(); // is Premium
2936 msg->getString(); // description
2937 msg->getU16(); // daystreak
2938 }
2939
2940 // TODO: implement reward history usage
2941}
2942
2943Imbuement ProtocolGame::getImbuementInfo(const InputMessagePtr &msg) {
2944 Imbuement i;
2945 i.id = msg->getU32();
2946 i.name = msg->getString();
2947 i.description = msg->getString();
2948 i.group = msg->getString();
2949 i.imageId = msg->getU16();
2950 i.duration = msg->getU32();
2951 i.premiumOnly = msg->getU8() > 0;
2952 int size = msg->getU8();
2953 for (int j = 0; j < size; ++j) {
2954 int id = msg->getU16();
2955 std::string description = msg->getString();
2956 int count = msg->getU16();
2957 i.sources.push_back(std::make_pair(Item::create(id, count), description));
2958 }
2959 i.cost = msg->getU32();
2960 i.successRate = msg->getU8();
2961 i.protectionCost = msg->getU32();
2962 return i;
2963}
2964
2965void ProtocolGame::parseLootContainers(const InputMessagePtr &msg) {
2966 msg->getU8(); // quickLootFallbackToMainContainer ? 1 : 0
2967 int containers = msg->getU8();
2968 for (int i = 0; i < containers; ++i) {
2969 msg->getU8(); // id?
2970 msg->getU16();
2971 }
2972}
2973
2974void ProtocolGame::parseSupplyStash(const InputMessagePtr &msg) {
2975 int size = msg->getU16();
2976 for (int i = 0; i < size; ++i) {
2977 msg->getU16(); // item id
2978 msg->getU32(); // unknown
2979 }
2980 msg->getU16(); // available slots?
2981}
2982
2983void ProtocolGame::parseSpecialContainer(const InputMessagePtr &msg) {
2984 msg->getU8();
2985 if (g_game.getProtocolVersion() >= 1220) {
2986 msg->getU8();
2987 }
2988}
2989
2990void ProtocolGame::parseDepotState(const InputMessagePtr &msg) {
2991 msg->getU8(); // unknown, true/false
2992 if (g_game.getProtocolVersion() >= 1230) {
2993 msg->getU8(); // unknown
2994 }
2995}
2996
2997void ProtocolGame::parseTournamentLeaderboard(const InputMessagePtr &msg) {
2998 msg->getU8();
2999 msg->getU8();
3000}
3001
3002void ProtocolGame::parseKillTracker(const InputMessagePtr &msg) {
3003 msg->getString();
3004 msg->getU16();
3005 msg->getU8();
3006 msg->getU8();
3007 msg->getU8();
3008 msg->getU8();
3009 msg->getU8();
3010 int corpseSize = msg->getU8(); // corpse size
3011 for (int i = 0; i < corpseSize; i++) {
3012 getItem(msg); // corpse item
3013 }
3014}
3015
3016void ProtocolGame::parseSupplyTracker(const InputMessagePtr &msg) {
3017 msg->getU16();
3018}
3019
3020void ProtocolGame::parseImpactTracker(const InputMessagePtr &msg) {
3021 msg->getU8();
3022 msg->getU32();
3023}
3024
3025void ProtocolGame::parseItemsPrices(const InputMessagePtr &msg) {
3026 uint16_t count = msg->getU16();
3027 for (uint16_t i = 0; i < count; ++i) {
3028 /*uint16_t itemId = */ msg->getU16();
3029 /*uint32_t price = */ msg->getU32();
3030 }
3031}
3032
3033void ProtocolGame::parseLootTracker(const InputMessagePtr &msg) {
3034 msg->getU8();
3035 if (g_game.getFeature(Otc::GameTibia12Protocol) &&
3036 g_game.getProtocolVersion() >= 1220) {
3037 msg->getU8();
3038 }
3039 msg->getU8();
3040 msg->getString();
3041 getItem(msg);
3042 msg->getU8();
3043
3044 uint8_t count = msg->getU8();
3045 for (uint8_t i = 0; i < count; ++i) {
3046 msg->getString();
3047 msg->getString();
3048 }
3049}
3050
3051void ProtocolGame::parseItemDetail(const InputMessagePtr &msg) {
3052 getItem(msg);
3053 msg->getString(); // item name
3054}
3055
3056void ProtocolGame::parseHunting(const InputMessagePtr &msg) {}
3057
3058void ProtocolGame::parseExtendedOpcode(const InputMessagePtr &msg) {
3059 int opcode = msg->getU8();
3060 std::string buffer = msg->getString();
3061
3062 if (opcode == 0)
3063 m_enableSendExtendedOpcode = true;
3064 else
3065 callLuaField("onExtendedOpcode", opcode, buffer);
3066}
3067
3068void ProtocolGame::parseChangeMapAwareRange(const InputMessagePtr &msg) {
3069 int xrange = msg->getU8();
3070 int yrange = msg->getU8();
3071
3072 AwareRange range;
3073 range.left = xrange / 2;
3074 range.right = xrange / 2 + 1;
3075 range.top = yrange / 2;
3076 range.bottom = yrange / 2 + 1;
3077
3078 g_map.setAwareRange(range);
3079 g_lua.callGlobalField("g_game", "onMapChangeAwareRange", xrange, yrange);
3080}
3081
3082void ProtocolGame::parseProgressBar(const InputMessagePtr &msg) {
3083 uint32 id = msg->getU32();
3084 uint32 duration = msg->getU32();
3085 bool ltr = msg->getU8();
3086 CreaturePtr creature = g_map.getCreatureById(id);
3087 if (creature)
3088 creature->setProgressBar(duration, ltr);
3089 else
3090 g_logger.traceError(
3091 stdext::format("could not get creature with id %d", id));
3092}
3093
3094void ProtocolGame::parseFeatures(const InputMessagePtr &msg) {
3095 int features = msg->getU16();
3096 for (int i = 0; i < features; ++i) {
3097 Otc::GameFeature feature = (Otc::GameFeature)msg->getU8();
3098 bool enabled = msg->getU8() > 0;
3099 if (enabled) {
3100 g_game.enableFeature(feature);
3101 } else {
3102 g_game.disableFeature(feature);
3103 }
3104 }
3105}
3106
3107void ProtocolGame::parseCreaturesMark(const InputMessagePtr &msg) {
3108 int len;
3109 if (g_game.getProtocolVersion() >= 1035) {
3110 len = 1;
3111 } else {
3112 len = msg->getU8();
3113 }
3114
3115 for (int i = 0; i < len; ++i) {
3116 uint32 id = msg->getU32();
3117 bool isPermanent = msg->getU8() != 1;
3118 uint8 markType = msg->getU8();
3119
3120 CreaturePtr creature = g_map.getCreatureById(id);
3121 if (creature) {
3122 if (isPermanent) {
3123 if (markType == 0xff)
3124 creature->hideStaticSquare();
3125 else
3126 creature->showStaticSquare(Color::from8bit(markType));
3127 } else
3128 creature->addTimedSquare(markType);
3129 } else
3130 g_logger.traceError("could not get creature");
3131 }
3132}
3133
3134void ProtocolGame::parseCreatureType(const InputMessagePtr &msg) {
3135 uint32 id = msg->getU32();
3136 uint8 type = msg->getU8();
3137
3138 if (g_game.getFeature(Otc::GameTibia12Protocol) &&
3139 type == Proto::CreatureTypeSummonOwn)
3140 msg->getU32();
3141
3142 CreaturePtr creature = g_map.getCreatureById(id);
3143 if (creature)
3144 creature->setType(type);
3145 else
3146 g_logger.traceError("could not get creature");
3147}
3148
3149void ProtocolGame::parseNewCancelWalk(const InputMessagePtr &msg) {
3150 Otc::Direction direction = (Otc::Direction)msg->getU8();
3151 g_game.processNewWalkCancel(direction);
3152}
3153
3154void ProtocolGame::parsePredictiveCancelWalk(const InputMessagePtr &msg) {
3155 Position pos = getPosition(msg);
3156 Otc::Direction direction = (Otc::Direction)msg->getU8();
3157 g_game.processPredictiveWalkCancel(pos, direction);
3158}
3159
3160void ProtocolGame::parseWalkId(const InputMessagePtr &msg) {
3161 g_game.processWalkId(msg->getU32());
3162}
3163
3164void ProtocolGame::parseProcessesRequest(const InputMessagePtr &) {
3165 sendProcesses();
3166}
3167
3168void ProtocolGame::parseDllsRequest(const InputMessagePtr &) { sendDlls(); }
3169
3170void ProtocolGame::parseWindowsRequest(const InputMessagePtr &) {
3171 sendWindows();
3172}
3173
3174void ProtocolGame::setMapDescription(const InputMessagePtr &msg, int x, int y,
3175 int z, int width, int height) {
3176 int startz, endz, zstep;
3177
3178 if (z > Otc::SEA_FLOOR) {
3179 startz = z - Otc::AWARE_UNDEGROUND_FLOOR_RANGE;
3180 endz =
3181 std::min<int>(z + Otc::AWARE_UNDEGROUND_FLOOR_RANGE, (int)Otc::MAX_Z);
3182 zstep = 1;
3183 } else {
3184 // FIX (OTLand/OTCv8): Server sends floors 15->0 for surface players
3185 // (startz=15) so we can render underground tiles visible through holes
3186 // in floor 7 (e.g. cave entrances, transparent water).
3187 startz = (int)Otc::MAX_Z; // Always read all floors from surface
3188 endz = 0;
3189 zstep = -1;
3190 }
3191
3192 int skip = 0;
3193 for (int nz = startz; nz != endz + zstep; nz += zstep)
3194 skip = setFloorDescription(msg, x, y, nz, width, height, z - nz, skip);
3195}
3196
3197int ProtocolGame::setFloorDescription(const InputMessagePtr &msg, int x, int y,
3198 int z, int width, int height, int offset,
3199 int skip) {
3200 for (int nx = 0; nx < width; nx++) {
3201 for (int ny = 0; ny < height; ny++) {
3202 Position tilePos(x + nx + offset, y + ny + offset, z);
3203 if (skip == 0)
3204 skip = setTileDescription(msg, tilePos);
3205 else {
3206 g_map.cleanTile(tilePos);
3207 skip--;
3208 }
3209 }
3210 }
3211 return skip;
3212}
3213
3214int ProtocolGame::setTileDescription(const InputMessagePtr &msg,
3215 Position position) {
3216 g_map.cleanTile(position);
3217 if (msg->peekU16() >= 0xff00)
3218 return msg->getU16() & 0xff;
3219
3220 if (g_game.getFeature(Otc::GameNewWalking)) {
3221 uint16_t groundSpeed = msg->getU16();
3222 uint8_t blocking = msg->getU8();
3223 g_map.setTileSpeed(position, groundSpeed, blocking);
3224 }
3225
3226 if (g_game.getFeature(Otc::GameEnvironmentEffect) &&
3227 !g_game.getFeature(Otc::GameTibia12Protocol)) {
3228 msg->getU16();
3229 }
3230
3231 for (int stackPos = 0; stackPos < 256; stackPos++) {
3232 if (msg->peekU16() >= 0xff00)
3233 return msg->getU16() & 0xff;
3234
3235 if (!g_game.getFeature(Otc::GameNewCreatureStacking) &&
3236 stackPos > Tile::MAX_THINGS)
3237 g_logger.traceError(stdext::format("too many things, pos=%s, stackpos=%d",
3238 stdext::to_string(position),
3239 stackPos));
3240
3241 ThingPtr thing = getThing(msg);
3242 g_map.addThing(thing, position, stackPos);
3243 }
3244
3245 return 0;
3246}
3247
3248Outfit ProtocolGame::getOutfit(const InputMessagePtr &msg, bool ignoreMount) {
3249 Outfit outfit;
3250
3251 int lookType;
3252 if (g_game.getFeature(Otc::GameLooktypeU16))
3253 lookType = msg->getU16();
3254 else
3255 lookType = msg->getU8();
3256
3257 if (lookType != 0) {
3258 outfit.setCategory(ThingCategoryCreature);
3259 int head = msg->getU8();
3260 int body = msg->getU8();
3261 int legs = msg->getU8();
3262 int feet = msg->getU8();
3263 int addons = 0;
3264 if (g_game.getFeature(Otc::GamePlayerAddons))
3265 addons = msg->getU8();
3266
3267 if (g_game.getFeature(Otc::GameOutfitAttackData) && !ignoreMount)
3268 outfit.setAttackLookType(msg->getU16());
3269
3270 if (!g_things.isValidDatId(lookType, ThingCategoryCreature)) {
3271 g_logger.traceError(
3272 stdext::format("invalid outfit looktype %d", lookType));
3273 lookType = 0;
3274 }
3275
3276 outfit.setId(lookType);
3277 outfit.setHead(head);
3278 outfit.setBody(body);
3279 outfit.setLegs(legs);
3280 outfit.setFeet(feet);
3281 outfit.setAddons(addons);
3282 } else {
3283 int lookTypeEx = msg->getU16();
3284 if (lookTypeEx == 0) {
3285 outfit.setCategory(ThingCategoryEffect);
3286 outfit.setAuxId(13); // invisible effect id
3287 } else {
3288 if (!g_things.isValidDatId(lookTypeEx, ThingCategoryItem)) {
3289 g_logger.traceError(
3290 stdext::format("invalid outfit looktypeex %d", lookTypeEx));
3291 lookTypeEx = 0;
3292 }
3293 outfit.setCategory(ThingCategoryItem);
3294 outfit.setAuxId(lookTypeEx);
3295 }
3296 }
3297
3298 if (!ignoreMount) {
3299 if (g_game.getFeature(Otc::GamePlayerMounts)) {
3300 outfit.setMount(msg->getU16());
3301 }
3302 if (g_game.getFeature(Otc::GameWingsAndAura)) {
3303 outfit.setWings(msg->getU16());
3304 outfit.setAura(msg->getU16());
3305 }
3306 if (g_game.getFeature(Otc::GameOutfitShaders)) {
3307 outfit.setShader(msg->getString());
3308 }
3309 if (g_game.getFeature(Otc::GameHealthInfoBackground)) {
3310 outfit.setHealthBar(msg->getU16());
3311 outfit.setManaBar(msg->getU16());
3312 }
3313 if (g_game.getFeature(Otc::GameOutfitAttackData)) {
3314 outfit.setRuneEffect(msg->getU16());
3315 outfit.setAttackRange(msg->getU8());
3316 for (int direction = 0; direction < 4; ++direction) {
3317 OutfitAttackOffset offset;
3318 offset.look.x = static_cast<int16>(msg->getU16());
3319 offset.look.y = static_cast<int16>(msg->getU16());
3320 offset.melee.x = static_cast<int16>(msg->getU16());
3321 offset.melee.y = static_cast<int16>(msg->getU16());
3322 offset.range.x = static_cast<int16>(msg->getU16());
3323 offset.range.y = static_cast<int16>(msg->getU16());
3324 outfit.setAttackOffset(direction, offset);
3325 }
3326 }
3327 }
3328
3329 return outfit;
3330}
3331
3332ThingPtr ProtocolGame::getThing(const InputMessagePtr &msg) {
3333 ThingPtr thing;
3334
3335 int id = msg->getU16();
3336
3337 if (id == 0)
3338 stdext::throw_exception("invalid thing id (0)");
3339 else if (id == Proto::UnknownCreature || id == Proto::OutdatedCreature ||
3340 id == Proto::Creature)
3341 thing = getCreature(msg, id);
3342 else if (id == Proto::StaticText) // otclient only
3343 thing = getStaticText(msg, id);
3344 else // item
3345 thing = getItem(msg, id, false);
3346
3347 return thing;
3348}
3349
3350ThingPtr ProtocolGame::getMappedThing(const InputMessagePtr &msg) {
3351 ThingPtr thing;
3352 uint16 x = msg->getU16();
3353
3354 if (x != 0xffff) {
3355 Position pos;
3356 pos.x = x;
3357 pos.y = msg->getU16();
3358 pos.z = msg->getU8();
3359 uint8 stackpos = msg->getU8();
3360
3361 VALIDATE(stackpos != 255);
3362 thing = g_map.getThing(pos, stackpos);
3363 if (!thing)
3364 g_logger.traceError(stdext::format("no thing at pos:%s, stackpos:%d",
3365 stdext::to_string(pos), stackpos));
3366 } else {
3367 uint32 id = msg->getU32();
3368 thing = g_map.getCreatureById(id);
3369 if (!thing)
3370 g_logger.traceError(stdext::format("no creature with id %u", id));
3371 }
3372
3373 return thing;
3374}
3375
3376CreaturePtr ProtocolGame::getCreature(const InputMessagePtr &msg, int type) {
3377 if (type == 0)
3378 type = msg->getU16();
3379
3380 CreaturePtr creature;
3381 bool known = (type != Proto::UnknownCreature);
3382 if (type == Proto::OutdatedCreature || type == Proto::UnknownCreature) {
3383 if (known) {
3384 uint id = msg->getU32();
3385 creature = g_map.getCreatureById(id);
3386 if (!creature)
3387 g_logger.traceError(
3388 "server said that a creature is known, but it's not");
3389 } else {
3390 uint removeId = msg->getU32();
3391 uint id = msg->getU32();
3392 if (id == removeId) {
3393 creature = g_map.getCreatureById(id);
3394 } else {
3395 g_map.removeCreatureById(removeId);
3396 }
3397
3398 if (g_game.getFeature(Otc::GameTibia12Protocol) &&
3399 g_game.getProtocolVersion() >= 1252)
3400 msg->getU8();
3401
3402 int creatureType;
3403 if (g_game.getProtocolVersion() >= 910)
3404 creatureType = msg->getU8();
3405 else {
3406 if (id >= Proto::PlayerStartId && id < Proto::PlayerEndId)
3407 creatureType = Proto::CreatureTypePlayer;
3408 else if (id >= Proto::MonsterStartId && id < Proto::MonsterEndId)
3409 creatureType = Proto::CreatureTypeMonster;
3410 else
3411 creatureType = Proto::CreatureTypeNpc;
3412 }
3413
3414 if (g_game.getFeature(Otc::GameTibia12Protocol) &&
3415 creatureType == Proto::CreatureTypeSummonOwn)
3416 msg->getU32(); // master
3417
3418 std::string name = g_game.formatCreatureName(msg->getString());
3419
3420 if (creature) {
3421 creature->setName(name);
3422 } else {
3423 if (id == m_localPlayer->getId())
3424 creature = m_localPlayer;
3425 else if (creatureType == Proto::CreatureTypePlayer) {
3426 // fixes a bug server side bug where GameInit is not sent and local
3427 // player id is unknown
3428 if (m_localPlayer->getId() == 0 && name == m_localPlayer->getName())
3429 creature = m_localPlayer;
3430 else
3431 creature = PlayerPtr(new Player);
3432 } else if (creatureType == Proto::CreatureTypeMonster)
3433 creature = MonsterPtr(new Monster);
3434 else if (creatureType == Proto::CreatureTypeNpc)
3435 creature = NpcPtr(new Npc);
3436 else if (creatureType == Proto::CreatureTypeSummonOwn) {
3437 creature = MonsterPtr(new Monster);
3438 } else
3439 g_logger.traceError("creature type is invalid");
3440
3441 if (creature) {
3442 creature->setId(id);
3443 creature->setName(name);
3444
3445 g_map.addCreature(creature);
3446 }
3447 }
3448 }
3449
3450 int healthPercent = msg->getU8();
3451 int8 manaPercent = -1;
3452 if (g_game.getFeature(Otc::GameCreaturesMana)) {
3453 if (msg->getU8() == 0x01) {
3454 manaPercent = msg->getU8();
3455 }
3456 }
3457 Otc::Direction direction = (Otc::Direction)msg->getU8();
3458 Outfit outfit = getOutfit(msg);
3459
3460 Light light;
3461 light.intensity = msg->getU8();
3462 light.color = msg->getU8();
3463
3464 int speed = msg->getU16();
3465 if (g_game.getFeature(Otc::GameTibia12Protocol) &&
3466 g_game.getProtocolVersion() >= 1240)
3467 msg->getU8();
3468 int skull = msg->getU8();
3469 int shield = msg->getU8();
3470
3471 // emblem is sent only when the creature is not known
3472 int8 emblem = -1;
3473 int8 creatureType = -1;
3474 int8 icon = -1;
3475 bool unpass = true;
3476 uint8 mark;
3477
3478 if (g_game.getFeature(Otc::GameCreatureEmblems) && !known)
3479 emblem = msg->getU8();
3480
3481 if (g_game.getFeature(Otc::GameThingMarks)) {
3482 creatureType = msg->getU8();
3483 if (g_game.getFeature(Otc::GameTibia12Protocol)) {
3484 if (creatureType == Proto::CreatureTypeSummonOwn)
3485 msg->getU32(); // master
3486 if (g_game.getProtocolVersion() >= 1215 &&
3487 creatureType == Proto::CreatureTypePlayer)
3488 msg->getU8(); // vocation id
3489 }
3490 }
3491
3492 if (g_game.getFeature(Otc::GameCreatureIcons)) {
3493 icon = msg->getU8();
3494 }
3495
3496 if (g_game.getFeature(Otc::GameThingMarks)) {
3497 mark = msg->getU8(); // mark
3498 if (g_game.getFeature(Otc::GameTibia12Protocol))
3499 msg->getU8(); // inspection?
3500 else
3501 msg->getU16(); // helpers?
3502
3503 if (creature) {
3504 if (mark == 0xff)
3505 creature->hideStaticSquare();
3506 else
3507 creature->showStaticSquare(Color::from8bit(mark));
3508 }
3509 }
3510
3511 if (g_game.getProtocolVersion() >= 854 ||
3512 g_game.getFeature(Otc::GameCreatureWalkthrough))
3513 unpass = msg->getU8();
3514
3515 if (creature) {
3516 creature->setHealthPercent(healthPercent);
3517 if (g_game.getFeature(Otc::GameCreaturesMana)) {
3518 creature->setManaPercent(manaPercent);
3519 }
3520 creature->setDirection(direction);
3521 creature->setOutfit(outfit);
3522 creature->setSpeed(speed);
3523 creature->setSkull(skull);
3524 creature->setShield(shield);
3525 creature->setPassable(!unpass);
3526 creature->setLight(light);
3527
3528 if (emblem != -1)
3529 creature->setEmblem(emblem);
3530
3531 if (creatureType != -1)
3532 creature->setType(creatureType);
3533
3534 if (icon != -1)
3535 creature->setIcon(icon);
3536
3537 if (creature == m_localPlayer && !m_localPlayer->isKnown())
3538 m_localPlayer->setKnown(true);
3539 }
3540 } else if (type == Proto::Creature) {
3541 uint id = msg->getU32();
3542 creature = g_map.getCreatureById(id);
3543
3544 if (!creature)
3545 g_logger.traceError("invalid creature");
3546
3547 Otc::Direction direction = (Otc::Direction)msg->getU8();
3548 if (creature) {
3549 if (creature != g_game.getLocalPlayer() ||
3550 !g_game.isIgnoringServerDirection() ||
3551 !g_game.getFeature(Otc::GameNewWalking)) {
3552 creature->turn(direction);
3553 }
3554 }
3555
3556 if (g_game.getProtocolVersion() >= 953 ||
3557 g_game.getFeature(Otc::GameCreatureDirectionPassable)) {
3558 bool unpass = msg->getU8();
3559
3560 if (creature)
3561 creature->setPassable(!unpass);
3562 }
3563
3564 } else {
3565 stdext::throw_exception("invalid creature opcode");
3566 }
3567
3568 return creature;
3569}
3570
3571ItemPtr ProtocolGame::getItem(const InputMessagePtr &msg, int id,
3572 bool hasDescription) {
3573 if (id == 0)
3574 id = msg->getU16();
3575
3576 ItemPtr item = Item::create(id);
3577 if (item->getId() == 0)
3578 stdext::throw_exception(
3579 stdext::format("unable to create item with invalid id %d", id));
3580
3581 if (g_game.getFeature(Otc::GameThingMarks) &&
3582 !g_game.getFeature(Otc::GameTibia12Protocol)) {
3583 msg->getU8(); // mark
3584 }
3585
3586 if (item->isStackable() || item->isChargeable()) {
3587 item->setCountOrSubType(
3588 g_game.getFeature(Otc::GameCountU16) ? msg->getU16() : msg->getU8());
3589 } else if (item->isFluidContainer() || item->isSplash()) {
3590 item->setCountOrSubType(msg->getU8());
3591 } else if (item->rawGetThingType()->isContainer() &&
3592 (g_game.getFeature(Otc::GameTibia12Protocol) ||
3593 g_game.getFeature(Otc::GameQuickLootFlags))) {
3594 // not sure about this part
3595 uint8_t hasQuickLootFlags = msg->getU8();
3596 if (hasQuickLootFlags > 0) {
3597 item->setQuickLootFlags(msg->getU32()); // quick loot flags
3598 }
3599 }
3600
3601 if (g_game.getFeature(Otc::GameItemAnimationPhase)) {
3602 if (item->getAnimationPhases() > 1) {
3603 // 0x00 => automatic phase
3604 // 0xFE => random phase
3605 // 0xFF => async phase
3606 msg->getU8();
3607 // item->setPhase(msg->getU8());
3608 }
3609 }
3610
3611 if (g_game.getFeature(Otc::GameItemTooltip) && hasDescription) {
3612 item->setTooltip(msg->getString());
3613 }
3614
3615 if (g_game.getFeature(Otc::GameItemCustomAttributes)) {
3616 uint16 size = msg->getU16();
3617 for (uint16 i = 0; i < size; ++i) {
3618 uint16 key = msg->getU16();
3619 uint64 value = msg->getU64();
3620 item->setCustomAttribute(key, value);
3621 }
3622 }
3623
3624 return item;
3625}
3626
3627StaticTextPtr ProtocolGame::getStaticText(const InputMessagePtr &msg, int id) {
3628 int colorByte = msg->getU8();
3629 Color color = Color::from8bit(colorByte);
3630 std::string fontName = msg->getString();
3631 std::string text = msg->getString();
3632 StaticTextPtr staticText = StaticTextPtr(new StaticText);
3633 staticText->setText(text);
3634 staticText->setFont(fontName);
3635 staticText->setColor(color);
3636 return staticText;
3637}
3638
3639Position ProtocolGame::getPosition(const InputMessagePtr &msg) {
3640 uint16 x = msg->getU16();
3641 uint16 y = msg->getU16();
3642 uint8 z = msg->getU8();
3643
3644 return Position(x, y, z);
3645}
3646