1/**
2 * The Forgotten Server - a free and open-source MMORPG server emulator
3 * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "otpch.h"
21
22#include "pugicast.h"
23
24#include "actions.h"
25#include "bed.h"
26#include "configmanager.h"
27#include "creature.h"
28#include "creatureevent.h"
29#include "databasetasks.h"
30#include "events.h"
31#include "game.h"
32#include "globalevent.h"
33#include "iologindata.h"
34#include "iomarket.h"
35#include "items.h"
36#include "monster.h"
37#include "movement.h"
38#include "scheduler.h"
39#include "script.h"
40#include "server.h"
41#include "spells.h"
42#include "talkaction.h"
43#include "weapons.h"
44
45extern ConfigManager g_config;
46extern Actions *g_actions;
47extern Chat *g_chat;
48extern TalkActions *g_talkActions;
49extern Spells *g_spells;
50extern Vocations g_vocations;
51extern GlobalEvents *g_globalEvents;
52extern CreatureEvents *g_creatureEvents;
53extern Events *g_events;
54extern Monsters g_monsters;
55extern MoveEvents *g_moveEvents;
56extern Weapons *g_weapons;
57extern Scripts *g_scripts;
58
59Game::Game() {
60 offlineTrainingWindow.choices.emplace_back("Rifle Combat and Shielding",
61 SKILL_SWORD);
62 offlineTrainingWindow.choices.emplace_back("Pistol Combat and Shielding",
63 SKILL_AXE);
64 offlineTrainingWindow.choices.emplace_back("Machine-Gun Combat and Shielding",
65 SKILL_CLUB);
66 offlineTrainingWindow.choices.emplace_back("Shotgun Combat and Shielding",
67 SKILL_DISTANCE);
68 offlineTrainingWindow.choices.emplace_back("Magic Level and Shielding",
69 SKILL_MAGLEVEL);
70 offlineTrainingWindow.buttons.emplace_back("Okay", 1);
71 offlineTrainingWindow.buttons.emplace_back("Cancel", 0);
72 offlineTrainingWindow.defaultEnterButton = 1;
73 offlineTrainingWindow.defaultEscapeButton = 0;
74 offlineTrainingWindow.priority = true;
75}
76
77Game::~Game() {
78 for (const auto &it : guilds) {
79 delete it.second;
80 }
81}
82
83void Game::start(ServiceManager *manager) {
84 serviceManager = manager;
85
86 g_scheduler.addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL,
87 std::bind(&Game::checkLight, this)));
88 g_scheduler.addEvent(
89 createSchedulerTask(EVENT_CREATURE_THINK_INTERVAL,
90 std::bind(&Game::checkCreatures, this, 0)));
91 g_scheduler.addEvent(createSchedulerTask(EVENT_DECAYINTERVAL,
92 std::bind(&Game::checkDecay, this)));
93}
94
95GameState_t Game::getGameState() const { return gameState; }
96
97void Game::setWorldType(WorldType_t type) { worldType = type; }
98
99void Game::setGameState(GameState_t newState) {
100 if (gameState == GAME_STATE_SHUTDOWN) {
101 return; // this cannot be stopped
102 }
103
104 if (gameState == newState) {
105 return;
106 }
107
108 gameState = newState;
109 switch (newState) {
110 case GAME_STATE_INIT: {
111 loadExperienceStages();
112
113 groups.load();
114 g_chat->load();
115
116 map.spawns.startup();
117
118 raids.loadFromXml();
119 raids.startup();
120
121 quests.loadFromXml();
122 mounts.loadFromXml();
123 auras.loadFromXml();
124 wings.loadFromXml();
125 shaders.loadFromXml();
126 runeEffects.loadFromXml();
127
128 loadMotdNum();
129 loadPlayersRecord();
130
131 g_globalEvents->startup();
132 break;
133 }
134
135 case GAME_STATE_SHUTDOWN: {
136 g_globalEvents->execute(GLOBALEVENT_SHUTDOWN);
137
138 // kick all players that are still online
139 auto it = players.begin();
140 while (it != players.end()) {
141 it->second->kickPlayer(true);
142 it = players.begin();
143 }
144
145 saveMotdNum();
146 saveGameState();
147
148 g_dispatcher.addTask(createTask(std::bind(&Game::shutdown, this)));
149
150 g_scheduler.stop();
151 g_databaseTasks.stop();
152 g_dispatcher.stop();
153 break;
154 }
155
156 case GAME_STATE_CLOSED: {
157 /* kick all players without the CanAlwaysLogin flag */
158 auto it = players.begin();
159 while (it != players.end()) {
160 if (!it->second->hasFlag(PlayerFlag_CanAlwaysLogin)) {
161 it->second->kickPlayer(true);
162 it = players.begin();
163 } else {
164 ++it;
165 }
166 }
167
168 saveGameState();
169 break;
170 }
171
172 default:
173 break;
174 }
175}
176
177void Game::saveGameState() {
178 if (gameState == GAME_STATE_NORMAL) {
179 setGameState(GAME_STATE_MAINTAIN);
180 }
181
182 std::cout << "Saving server..." << std::endl;
183
184 for (const auto &it : players) {
185 it.second->loginPosition = it.second->getPosition();
186 IOLoginData::savePlayer(it.second);
187 }
188
189 Map::save();
190
191 g_databaseTasks.flush();
192
193 if (gameState == GAME_STATE_MAINTAIN) {
194 setGameState(GAME_STATE_NORMAL);
195 }
196}
197
198bool Game::loadMainMap(const std::string &filename) {
199 Monster::despawnRange =
200 g_config.getNumber(ConfigManager::DEFAULT_DESPAWNRANGE);
201 Monster::despawnRadius =
202 g_config.getNumber(ConfigManager::DEFAULT_DESPAWNRADIUS);
203 return map.loadMap("data/world/" + filename + ".otbm", true);
204}
205
206void Game::loadMap(const std::string &path) { map.loadMap(path, false); }
207
208Cylinder *Game::internalGetCylinder(Player *player, const Position &pos) const {
209 if (pos.x != 0xFFFF) {
210 return map.getTile(pos);
211 }
212
213 // container
214 if (pos.y & 0x40) {
215 uint8_t from_cid = pos.y & 0x0F;
216 return player->getContainerByID(from_cid);
217 }
218
219 // inventory
220 return player;
221}
222
223Thing *Game::internalGetThing(Player *player, const Position &pos,
224 int32_t index, uint32_t spriteId,
225 stackPosType_t type) const {
226 if (pos.x != 0xFFFF) {
227 Tile *tile = map.getTile(pos);
228 if (!tile) {
229 return nullptr;
230 }
231
232 Thing *thing;
233 switch (type) {
234 case STACKPOS_LOOK: {
235 return tile->getTopVisibleThing(player);
236 }
237
238 case STACKPOS_MOVE: {
239 Item *item = tile->getTopDownItem();
240 if (item && item->isMoveable()) {
241 thing = item;
242 } else {
243 thing = tile->getTopVisibleCreature(player);
244 }
245 break;
246 }
247
248 case STACKPOS_USEITEM: {
249 thing = tile->getUseItem(index);
250 break;
251 }
252
253 case STACKPOS_TOPDOWN_ITEM: {
254 thing = tile->getTopDownItem();
255 break;
256 }
257
258 case STACKPOS_USETARGET: {
259 thing = tile->getTopVisibleCreature(player);
260 if (!thing) {
261 thing = tile->getUseItem(index);
262 }
263 break;
264 }
265
266 default: {
267 thing = nullptr;
268 break;
269 }
270 }
271
272 if (player && tile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) {
273 // do extra checks here if the thing is accessable
274 if (thing && thing->getItem()) {
275 if (tile->hasProperty(CONST_PROP_ISVERTICAL)) {
276 if (player->getPosition().x + 1 == tile->getPosition().x) {
277 thing = nullptr;
278 }
279 } else { // horizontal
280 if (player->getPosition().y + 1 == tile->getPosition().y) {
281 thing = nullptr;
282 }
283 }
284 }
285 }
286 return thing;
287 }
288
289 // container
290 if (pos.y & 0x40) {
291 uint8_t fromCid = pos.y & 0x0F;
292
293 Container *parentContainer = player->getContainerByID(fromCid);
294 if (!parentContainer) {
295 return nullptr;
296 }
297
298 if (parentContainer->getID() == ITEM_BROWSEFIELD) {
299 Tile *tile = parentContainer->getTile();
300 if (tile && tile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) {
301 if (tile->hasProperty(CONST_PROP_ISVERTICAL)) {
302 if (player->getPosition().x + 1 == tile->getPosition().x) {
303 return nullptr;
304 }
305 } else { // horizontal
306 if (player->getPosition().y + 1 == tile->getPosition().y) {
307 return nullptr;
308 }
309 }
310 }
311 }
312
313 uint8_t slot = pos.z;
314 return parentContainer->getItemByIndex(player->getContainerIndex(fromCid) +
315 slot);
316 } else if (pos.y == 0 && pos.z == 0) {
317 const ItemType &it = Item::items.getItemIdByClientId(spriteId);
318 if (it.id == 0) {
319 return nullptr;
320 }
321
322 int32_t subType;
323 if (it.isFluidContainer() &&
324 index <
325 static_cast<int32_t>(sizeof(reverseFluidMap) / sizeof(uint8_t))) {
326 subType = reverseFluidMap[index];
327 } else {
328 subType = -1;
329 }
330
331 return findItemOfType(player, it.id, true, subType);
332 }
333
334 // inventory
335 slots_t slot = static_cast<slots_t>(pos.y);
336 return player->getInventoryItem(slot);
337}
338
339void Game::internalGetPosition(Item *item, Position &pos, uint8_t &stackpos) {
340 pos.x = 0;
341 pos.y = 0;
342 pos.z = 0;
343 stackpos = 0;
344
345 Cylinder *topParent = item->getTopParent();
346 if (topParent) {
347 if (Player *player = dynamic_cast<Player *>(topParent)) {
348 pos.x = 0xFFFF;
349
350 Container *container = dynamic_cast<Container *>(item->getParent());
351 if (container) {
352 pos.y = static_cast<uint16_t>(0x40) |
353 static_cast<uint16_t>(player->getContainerID(container));
354 pos.z = container->getThingIndex(item);
355 stackpos = pos.z;
356 } else {
357 pos.y = player->getThingIndex(item);
358 stackpos = pos.y;
359 }
360 } else if (Tile *tile = topParent->getTile()) {
361 pos = tile->getPosition();
362 stackpos = tile->getThingIndex(item);
363 }
364 }
365}
366
367Creature *Game::getCreatureByID(uint32_t id) {
368 if (id <= Player::playerAutoID) {
369 return getPlayerByID(id);
370 } else if (id <= Monster::monsterAutoID) {
371 return getMonsterByID(id);
372 } else if (id <= Npc::npcAutoID) {
373 return getNpcByID(id);
374 }
375 return nullptr;
376}
377
378Monster *Game::getMonsterByID(uint32_t id) {
379 if (id == 0) {
380 return nullptr;
381 }
382
383 auto it = monsters.find(id);
384 if (it == monsters.end()) {
385 return nullptr;
386 }
387 return it->second;
388}
389
390Npc *Game::getNpcByID(uint32_t id) {
391 if (id == 0) {
392 return nullptr;
393 }
394
395 auto it = npcs.find(id);
396 if (it == npcs.end()) {
397 return nullptr;
398 }
399 return it->second;
400}
401
402Player *Game::getPlayerByID(uint32_t id) {
403 if (id == 0) {
404 return nullptr;
405 }
406
407 auto it = players.find(id);
408 if (it == players.end()) {
409 return nullptr;
410 }
411 return it->second;
412}
413
414Creature *Game::getCreatureByName(const std::string &s) {
415 if (s.empty()) {
416 return nullptr;
417 }
418
419 const std::string &lowerCaseName = asLowerCaseString(s);
420
421 auto m_it = mappedPlayerNames.find(lowerCaseName);
422 if (m_it != mappedPlayerNames.end()) {
423 return m_it->second;
424 }
425
426 for (const auto &it : npcs) {
427 if (lowerCaseName == asLowerCaseString(it.second->getName())) {
428 return it.second;
429 }
430 }
431
432 for (const auto &it : monsters) {
433 if (lowerCaseName == asLowerCaseString(it.second->getName())) {
434 return it.second;
435 }
436 }
437 return nullptr;
438}
439
440Npc *Game::getNpcByName(const std::string &s) {
441 if (s.empty()) {
442 return nullptr;
443 }
444
445 const char *npcName = s.c_str();
446 for (const auto &it : npcs) {
447 if (strcasecmp(npcName, it.second->getName().c_str()) == 0) {
448 return it.second;
449 }
450 }
451 return nullptr;
452}
453
454Player *Game::getPlayerByName(const std::string &s) {
455 if (s.empty()) {
456 return nullptr;
457 }
458
459 auto it = mappedPlayerNames.find(asLowerCaseString(s));
460 if (it == mappedPlayerNames.end()) {
461 return nullptr;
462 }
463 return it->second;
464}
465
466Player *Game::getPlayerByGUID(const uint32_t &guid) {
467 if (guid == 0) {
468 return nullptr;
469 }
470
471 auto it = mappedPlayerGuids.find(guid);
472 if (it == mappedPlayerGuids.end()) {
473 return nullptr;
474 }
475 return it->second;
476}
477
478ReturnValue Game::getPlayerByNameWildcard(const std::string &s,
479 Player *&player) {
480 size_t strlen = s.length();
481 if (strlen == 0 || strlen > 20) {
482 return RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE;
483 }
484
485 if (s.back() == '~') {
486 const std::string &query = asLowerCaseString(s.substr(0, strlen - 1));
487 std::string result;
488 ReturnValue ret = wildcardTree.findOne(query, result);
489 if (ret != RETURNVALUE_NOERROR) {
490 return ret;
491 }
492
493 player = getPlayerByName(result);
494 } else {
495 player = getPlayerByName(s);
496 }
497
498 if (!player) {
499 return RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE;
500 }
501
502 return RETURNVALUE_NOERROR;
503}
504
505Player *Game::getPlayerByAccount(uint32_t acc) {
506 for (const auto &it : players) {
507 if (it.second->getAccount() == acc) {
508 return it.second;
509 }
510 }
511 return nullptr;
512}
513
514bool Game::internalPlaceCreature(Creature *creature, const Position &pos,
515 bool extendedPos /*=false*/,
516 bool forced /*= false*/) {
517 if (creature->getParent() != nullptr) {
518 return false;
519 }
520
521 if (!map.placeCreature(pos, creature, extendedPos, forced)) {
522 return false;
523 }
524
525 creature->incrementReferenceCounter();
526 creature->setID();
527 creature->addList();
528 return true;
529}
530
531bool Game::placeCreature(Creature *creature, const Position &pos,
532 bool extendedPos /*=false*/, bool forced /*= false*/) {
533 if (!internalPlaceCreature(creature, pos, extendedPos, forced)) {
534 return false;
535 }
536
537 SpectatorVec spectators;
538 map.getSpectators(spectators, creature->getPosition(), true);
539 for (Creature *spectator : spectators) {
540 if (Player *tmpPlayer = spectator->getPlayer()) {
541 tmpPlayer->sendCreatureAppear(creature, creature->getPosition(), true);
542 }
543 }
544
545 for (Creature *spectator : spectators) {
546 spectator->onCreatureAppear(creature, true);
547 }
548
549 creature->getParent()->postAddNotification(creature, nullptr, 0);
550
551 addCreatureCheck(creature);
552 creature->onPlacedCreature();
553 return true;
554}
555
556bool Game::removeCreature(Creature *creature, bool isLogout /* = true*/) {
557 if (creature->isRemoved()) {
558 return false;
559 }
560
561 Tile *tile = creature->getTile();
562
563 std::vector<int32_t> oldStackPosVector;
564
565 SpectatorVec spectators;
566 map.getSpectators(spectators, tile->getPosition(), true);
567 for (Creature *spectator : spectators) {
568 if (Player *player = spectator->getPlayer()) {
569 oldStackPosVector.push_back(
570 player->canSeeCreature(creature)
571 ? tile->getStackposOfCreature(player, creature)
572 : -1);
573 }
574 }
575
576 tile->removeCreature(creature);
577
578 const Position &tilePosition = tile->getPosition();
579
580 // send to client
581 size_t i = 0;
582 for (Creature *spectator : spectators) {
583 if (Player *player = spectator->getPlayer()) {
584 player->sendRemoveTileThing(tilePosition, oldStackPosVector[i++]);
585 }
586 }
587
588 // event method
589 for (Creature *spectator : spectators) {
590 spectator->onRemoveCreature(creature, isLogout);
591 }
592
593 creature->getParent()->postRemoveNotification(creature, nullptr, 0);
594
595 creature->removeList();
596 creature->setRemoved();
597 ReleaseCreature(creature);
598
599 removeCreatureCheck(creature);
600
601 for (Creature *summon : creature->summons) {
602 summon->setSkillLoss(false);
603 removeCreature(summon);
604 }
605 return true;
606}
607
608void Game::playerMoveThing(uint32_t playerId, const Position &fromPos,
609 uint16_t spriteId, uint8_t fromStackPos,
610 const Position &toPos, uint8_t count) {
611 Player *player = getPlayerByID(playerId);
612 if (!player) {
613 return;
614 }
615
616 uint8_t fromIndex = 0;
617 if (fromPos.x == 0xFFFF) {
618 if (fromPos.y & 0x40) {
619 fromIndex = fromPos.z;
620 } else {
621 fromIndex = static_cast<uint8_t>(fromPos.y);
622 }
623 } else {
624 fromIndex = fromStackPos;
625 }
626
627 Thing *thing = internalGetThing(player, fromPos, fromIndex, 0, STACKPOS_MOVE);
628 if (!thing) {
629 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
630 return;
631 }
632
633 if (Creature *movingCreature = thing->getCreature()) {
634 Tile *tile = map.getTile(toPos);
635 if (!tile) {
636 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
637 return;
638 }
639
640 if (Position::areInRange<1, 1, 0>(movingCreature->getPosition(),
641 player->getPosition())) {
642 SchedulerTask *task = createSchedulerTask(
643 1000, std::bind(&Game::playerMoveCreatureByID, this, player->getID(),
644 movingCreature->getID(),
645 movingCreature->getPosition(), tile->getPosition()));
646 player->setNextActionTask(task);
647 } else {
648 playerMoveCreature(player, movingCreature, movingCreature->getPosition(),
649 tile);
650 }
651 } else if (thing->getItem()) {
652 Cylinder *toCylinder = internalGetCylinder(player, toPos);
653 if (!toCylinder) {
654 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
655 return;
656 }
657
658 playerMoveItem(player, fromPos, spriteId, fromStackPos, toPos, count,
659 thing->getItem(), toCylinder);
660 }
661}
662
663void Game::playerMoveCreatureByID(uint32_t playerId, uint32_t movingCreatureId,
664 const Position &movingCreatureOrigPos,
665 const Position &toPos) {
666 Player *player = getPlayerByID(playerId);
667 if (!player) {
668 return;
669 }
670
671 Creature *movingCreature = getCreatureByID(movingCreatureId);
672 if (!movingCreature) {
673 return;
674 }
675
676 Tile *toTile = map.getTile(toPos);
677 if (!toTile) {
678 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
679 return;
680 }
681
682 playerMoveCreature(player, movingCreature, movingCreatureOrigPos, toTile);
683}
684
685void Game::playerMoveCreature(Player *player, Creature *movingCreature,
686 const Position &movingCreatureOrigPos,
687 Tile *toTile) {
688 if (!player->canDoAction()) {
689 uint32_t delay = player->getNextActionTime();
690 SchedulerTask *task = createSchedulerTask(
691 delay, std::bind(&Game::playerMoveCreatureByID, this, player->getID(),
692 movingCreature->getID(), movingCreatureOrigPos,
693 toTile->getPosition()));
694 player->setNextActionTask(task);
695 return;
696 }
697
698 player->setNextActionTask(nullptr);
699
700 if (!Position::areInRange<1, 1, 0>(movingCreatureOrigPos,
701 player->getPosition())) {
702 // need to walk to the creature first before moving it
703 std::list<Direction> listDir;
704 if (player->getPathTo(movingCreatureOrigPos, listDir, 0, 1, true, true)) {
705 g_dispatcher.addTask(createTask(
706 std::bind(&Game::playerAutoWalk, this, player->getID(), listDir)));
707 SchedulerTask *task = createSchedulerTask(
708 1500, std::bind(&Game::playerMoveCreatureByID, this, player->getID(),
709 movingCreature->getID(), movingCreatureOrigPos,
710 toTile->getPosition()));
711 player->setNextWalkActionTask(task);
712 } else {
713 player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
714 }
715 return;
716 }
717
718 if ((!movingCreature->isPushable() &&
719 !player->hasFlag(PlayerFlag_CanPushAllCreatures)) ||
720 (movingCreature->isInGhostMode() && !player->isAccessPlayer())) {
721 player->sendCancelMessage(RETURNVALUE_NOTMOVEABLE);
722 return;
723 }
724
725 // check throw distance
726 const Position &movingCreaturePos = movingCreature->getPosition();
727 const Position &toPos = toTile->getPosition();
728 if ((Position::getDistanceX(movingCreaturePos, toPos) >
729 movingCreature->getThrowRange()) ||
730 (Position::getDistanceY(movingCreaturePos, toPos) >
731 movingCreature->getThrowRange()) ||
732 (Position::getDistanceZ(movingCreaturePos, toPos) * 4 >
733 movingCreature->getThrowRange())) {
734 player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH);
735 return;
736 }
737
738 if (player != movingCreature) {
739 if (toTile->hasFlag(TILESTATE_BLOCKPATH)) {
740 player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
741 return;
742 } else if ((movingCreature->getZone() == ZONE_PROTECTION &&
743 !toTile->hasFlag(TILESTATE_PROTECTIONZONE)) ||
744 (movingCreature->getZone() == ZONE_NOPVP &&
745 !toTile->hasFlag(TILESTATE_NOPVPZONE))) {
746 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
747 return;
748 } else {
749 if (CreatureVector *tileCreatures = toTile->getCreatures()) {
750 for (Creature *tileCreature : *tileCreatures) {
751 if (!tileCreature->isInGhostMode()) {
752 player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
753 return;
754 }
755 }
756 }
757
758 Npc *movingNpc = movingCreature->getNpc();
759 if (movingNpc && !Spawns::isInZone(movingNpc->getMasterPos(),
760 movingNpc->getMasterRadius(), toPos)) {
761 player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
762 return;
763 }
764 }
765 }
766
767 if (!g_events->eventPlayerOnMoveCreature(player, movingCreature,
768 movingCreaturePos, toPos)) {
769 return;
770 }
771
772 ReturnValue ret = internalMoveCreature(*movingCreature, *toTile);
773 if (ret != RETURNVALUE_NOERROR) {
774 player->sendCancelMessage(ret);
775 }
776}
777
778ReturnValue Game::internalMoveCreature(Creature *creature, Direction direction,
779 uint32_t flags /*= 0*/) {
780 creature->setLastPosition(creature->getPosition());
781 const Position ¤tPos = creature->getPosition();
782 Position destPos = getNextPosition(direction, currentPos);
783 Player *player = creature->getPlayer();
784
785 bool diagonalMovement = (direction & DIRECTION_DIAGONAL_MASK) != 0;
786 if (player && !diagonalMovement) {
787 // try go up
788 if (currentPos.z != 8 && creature->getTile()->hasHeight(3)) {
789 Tile *tmpTile =
790 map.getTile(currentPos.x, currentPos.y, currentPos.getZ() - 1);
791 if (tmpTile == nullptr || (tmpTile->getGround() == nullptr &&
792 !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) {
793 tmpTile = map.getTile(destPos.x, destPos.y, destPos.getZ() - 1);
794 if (tmpTile && tmpTile->getGround() &&
795 !tmpTile->hasFlag(TILESTATE_BLOCKSOLID)) {
796 flags |= FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE;
797
798 if (!tmpTile->hasFlag(TILESTATE_FLOORCHANGE)) {
799 player->setDirection(direction);
800 destPos.z--;
801 }
802 }
803 }
804 }
805
806 // try go down
807 if (currentPos.z != 7 && currentPos.z == destPos.z) {
808 Tile *tmpTile = map.getTile(destPos.x, destPos.y, destPos.z);
809 if (tmpTile == nullptr || (tmpTile->getGround() == nullptr &&
810 !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) {
811 tmpTile = map.getTile(destPos.x, destPos.y, destPos.z + 1);
812 if (tmpTile && tmpTile->hasHeight(3)) {
813 flags |= FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE;
814 player->setDirection(direction);
815 destPos.z++;
816 }
817 }
818 }
819 }
820
821 Tile *toTile = map.getTile(destPos);
822 if (!toTile) {
823 return RETURNVALUE_NOTPOSSIBLE;
824 }
825 return internalMoveCreature(*creature, *toTile, flags);
826}
827
828ReturnValue Game::internalMoveCreature(Creature &creature, Tile &toTile,
829 uint32_t flags /*= 0*/) {
830 // check if we can move the creature to the destination
831 ReturnValue ret = toTile.queryAdd(0, creature, 1, flags);
832 if (ret != RETURNVALUE_NOERROR) {
833 return ret;
834 }
835
836 map.moveCreature(creature, toTile);
837 if (creature.getParent() != &toTile) {
838 return RETURNVALUE_NOERROR;
839 }
840
841 int32_t index = 0;
842 Item *toItem = nullptr;
843 Tile *subCylinder = nullptr;
844 Tile *toCylinder = &toTile;
845 Tile *fromCylinder = nullptr;
846 uint32_t n = 0;
847
848 while ((subCylinder = toCylinder->queryDestination(index, creature, &toItem,
849 flags)) != toCylinder) {
850 map.moveCreature(creature, *subCylinder);
851
852 if (creature.getParent() != subCylinder) {
853 // could happen if a script move the creature
854 fromCylinder = nullptr;
855 break;
856 }
857
858 fromCylinder = toCylinder;
859 toCylinder = subCylinder;
860 flags = 0;
861
862 // to prevent infinite loop
863 if (++n >= MAP_MAX_LAYERS) {
864 break;
865 }
866 }
867
868 if (fromCylinder) {
869 const Position &fromPosition = fromCylinder->getPosition();
870 const Position &toPosition = toCylinder->getPosition();
871 if (fromPosition.z != toPosition.z &&
872 (fromPosition.x != toPosition.x || fromPosition.y != toPosition.y)) {
873 Direction dir = getDirectionTo(fromPosition, toPosition);
874 if ((dir & DIRECTION_DIAGONAL_MASK) == 0) {
875 internalCreatureTurn(&creature, dir);
876 }
877 }
878 }
879
880 return RETURNVALUE_NOERROR;
881}
882
883void Game::playerMoveItemByPlayerID(uint32_t playerId, const Position &fromPos,
884 uint16_t spriteId, uint8_t fromStackPos,
885 const Position &toPos, uint8_t count) {
886 Player *player = getPlayerByID(playerId);
887 if (!player) {
888 return;
889 }
890 playerMoveItem(player, fromPos, spriteId, fromStackPos, toPos, count, nullptr,
891 nullptr);
892}
893
894void Game::playerMoveItem(Player *player, const Position &fromPos,
895 uint16_t spriteId, uint8_t fromStackPos,
896 const Position &toPos, uint8_t count, Item *item,
897 Cylinder *toCylinder) {
898 if (!player->canDoAction()) {
899 uint32_t delay = player->getNextActionTime();
900 SchedulerTask *task = createSchedulerTask(
901 delay, std::bind(&Game::playerMoveItemByPlayerID, this, player->getID(),
902 fromPos, spriteId, fromStackPos, toPos, count));
903 player->setNextActionTask(task);
904 return;
905 }
906
907 player->setNextActionTask(nullptr);
908
909 if (item == nullptr) {
910 uint8_t fromIndex = 0;
911 if (fromPos.x == 0xFFFF) {
912 if (fromPos.y & 0x40) {
913 fromIndex = fromPos.z;
914 } else {
915 fromIndex = static_cast<uint8_t>(fromPos.y);
916 }
917 } else {
918 fromIndex = fromStackPos;
919 }
920
921 Thing *thing =
922 internalGetThing(player, fromPos, fromIndex, 0, STACKPOS_MOVE);
923 if (!thing || !thing->getItem()) {
924 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
925 return;
926 }
927
928 item = thing->getItem();
929 }
930
931 if (item->getClientID() != spriteId) {
932 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
933 return;
934 }
935
936 Cylinder *fromCylinder = internalGetCylinder(player, fromPos);
937 if (fromCylinder == nullptr) {
938 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
939 return;
940 }
941
942 if (toCylinder == nullptr) {
943 toCylinder = internalGetCylinder(player, toPos);
944 if (toCylinder == nullptr) {
945 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
946 return;
947 }
948 }
949
950 if (!item->isPushable() || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) {
951 player->sendCancelMessage(RETURNVALUE_NOTMOVEABLE);
952 return;
953 }
954
955 const Position &playerPos = player->getPosition();
956 const Position &mapFromPos = fromCylinder->getTile()->getPosition();
957 if (playerPos.z != mapFromPos.z) {
958 player->sendCancelMessage(playerPos.z > mapFromPos.z
959 ? RETURNVALUE_FIRSTGOUPSTAIRS
960 : RETURNVALUE_FIRSTGODOWNSTAIRS);
961 return;
962 }
963
964 if (!Position::areInRange<1, 1>(playerPos, mapFromPos)) {
965 // need to walk to the item first before using it
966 std::list<Direction> listDir;
967 if (player->getPathTo(item->getPosition(), listDir, 0, 1, true, true)) {
968 g_dispatcher.addTask(createTask(
969 std::bind(&Game::playerAutoWalk, this, player->getID(), listDir)));
970
971 SchedulerTask *task = createSchedulerTask(
972 400, std::bind(&Game::playerMoveItemByPlayerID, this, player->getID(),
973 fromPos, spriteId, fromStackPos, toPos, count));
974 player->setNextWalkActionTask(task);
975 } else {
976 player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
977 }
978 return;
979 }
980
981 const Tile *toCylinderTile = toCylinder->getTile();
982 const Position &mapToPos = toCylinderTile->getPosition();
983
984 // hangable item specific code
985 if (item->isHangable() &&
986 toCylinderTile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) {
987 // destination supports hangable objects so need to move there first
988 bool vertical = toCylinderTile->hasProperty(CONST_PROP_ISVERTICAL);
989 if (vertical) {
990 if (playerPos.x + 1 == mapToPos.x) {
991 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
992 return;
993 }
994 } else { // horizontal
995 if (playerPos.y + 1 == mapToPos.y) {
996 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
997 return;
998 }
999 }
1000
1001 if (!Position::areInRange<1, 1, 0>(playerPos, mapToPos)) {
1002 Position walkPos = mapToPos;
1003 if (vertical) {
1004 walkPos.x++;
1005 } else {
1006 walkPos.y++;
1007 }
1008
1009 Position itemPos = fromPos;
1010 uint8_t itemStackPos = fromStackPos;
1011
1012 if (fromPos.x != 0xFFFF &&
1013 Position::areInRange<1, 1>(mapFromPos, playerPos) &&
1014 !Position::areInRange<1, 1, 0>(mapFromPos, walkPos)) {
1015 // need to pickup the item first
1016 Item *moveItem = nullptr;
1017
1018 ReturnValue ret =
1019 internalMoveItem(fromCylinder, player, INDEX_WHEREEVER, item, count,
1020 &moveItem, 0, player, nullptr, &fromPos, &toPos);
1021 if (ret != RETURNVALUE_NOERROR) {
1022 player->sendCancelMessage(ret);
1023 return;
1024 }
1025
1026 // changing the position since its now in the inventory of the player
1027 internalGetPosition(moveItem, itemPos, itemStackPos);
1028 }
1029
1030 std::list<Direction> listDir;
1031 if (player->getPathTo(walkPos, listDir, 0, 0, true, true)) {
1032 g_dispatcher.addTask(createTask(
1033 std::bind(&Game::playerAutoWalk, this, player->getID(), listDir)));
1034
1035 SchedulerTask *task = createSchedulerTask(
1036 400,
1037 std::bind(&Game::playerMoveItemByPlayerID, this, player->getID(),
1038 itemPos, spriteId, itemStackPos, toPos, count));
1039 player->setNextWalkActionTask(task);
1040 } else {
1041 player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
1042 }
1043 return;
1044 }
1045 }
1046
1047 if ((Position::getDistanceX(playerPos, mapToPos) > item->getThrowRange()) ||
1048 (Position::getDistanceY(playerPos, mapToPos) > item->getThrowRange()) ||
1049 (Position::getDistanceZ(mapFromPos, mapToPos) * 4 >
1050 item->getThrowRange())) {
1051 player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH);
1052 return;
1053 }
1054
1055 if (!canThrowObjectTo(mapFromPos, mapToPos)) {
1056 player->sendCancelMessage(RETURNVALUE_CANNOTTHROW);
1057 return;
1058 }
1059
1060 uint8_t toIndex = 0;
1061 if (toPos.x == 0xFFFF) {
1062 if (toPos.y & 0x40) {
1063 toIndex = toPos.z;
1064 } else {
1065 toIndex = static_cast<uint8_t>(toPos.y);
1066 }
1067 }
1068
1069 ReturnValue ret =
1070 internalMoveItem(fromCylinder, toCylinder, toIndex, item, count, nullptr,
1071 0, player, nullptr, &fromPos, &toPos);
1072 if (ret != RETURNVALUE_NOERROR) {
1073 player->sendCancelMessage(ret);
1074 }
1075}
1076
1077ReturnValue Game::internalMoveItem(Cylinder *fromCylinder, Cylinder *toCylinder,
1078 int32_t index, Item *item, uint32_t count,
1079 Item **_moveItem, uint32_t flags /*= 0*/,
1080 Creature *actor /* = nullptr*/,
1081 Item *tradeItem /* = nullptr*/,
1082 const Position *fromPos /*= nullptr*/,
1083 const Position *toPos /*= nullptr*/) {
1084 Player *actorPlayer = actor ? actor->getPlayer() : nullptr;
1085 if (actorPlayer && fromPos && toPos) {
1086 if (!g_events->eventPlayerOnMoveItem(actorPlayer, item, count, *fromPos,
1087 *toPos, fromCylinder, toCylinder)) {
1088 return RETURNVALUE_NOTPOSSIBLE;
1089 }
1090 }
1091
1092 Tile *fromTile = fromCylinder->getTile();
1093 if (fromTile) {
1094 auto it = browseFields.find(fromTile);
1095 if (it != browseFields.end() && it->second == fromCylinder) {
1096 fromCylinder = fromTile;
1097 }
1098 }
1099
1100 Item *toItem = nullptr;
1101
1102 Cylinder *subCylinder;
1103 int floorN = 0;
1104
1105 while ((subCylinder = toCylinder->queryDestination(index, *item, &toItem,
1106 flags)) != toCylinder) {
1107 toCylinder = subCylinder;
1108 flags = 0;
1109
1110 // to prevent infinite loop
1111 if (++floorN >= MAP_MAX_LAYERS) {
1112 break;
1113 }
1114 }
1115
1116 // destination is the same as the source?
1117 if (item == toItem) {
1118 return RETURNVALUE_NOERROR; // silently ignore move
1119 }
1120
1121 // check if we can add this item
1122 ReturnValue ret = toCylinder->queryAdd(index, *item, count, flags, actor);
1123 if (ret == RETURNVALUE_NEEDEXCHANGE) {
1124 // check if we can add it to source cylinder
1125 ret = fromCylinder->queryAdd(fromCylinder->getThingIndex(item), *toItem,
1126 toItem->getItemCount(), 0);
1127 if (ret == RETURNVALUE_NOERROR) {
1128 // check how much we can move
1129 uint32_t maxExchangeQueryCount = 0;
1130 ReturnValue retExchangeMaxCount = fromCylinder->queryMaxCount(
1131 INDEX_WHEREEVER, *toItem, toItem->getItemCount(),
1132 maxExchangeQueryCount, 0);
1133
1134 if (retExchangeMaxCount != RETURNVALUE_NOERROR &&
1135 maxExchangeQueryCount == 0) {
1136 return retExchangeMaxCount;
1137 }
1138
1139 if (toCylinder->queryRemove(*toItem, toItem->getItemCount(), flags) ==
1140 RETURNVALUE_NOERROR) {
1141 int32_t oldToItemIndex = toCylinder->getThingIndex(toItem);
1142 toCylinder->removeThing(toItem, toItem->getItemCount());
1143 fromCylinder->addThing(toItem);
1144
1145 if (oldToItemIndex != -1) {
1146 toCylinder->postRemoveNotification(toItem, fromCylinder,
1147 oldToItemIndex);
1148 }
1149
1150 int32_t newToItemIndex = fromCylinder->getThingIndex(toItem);
1151 if (newToItemIndex != -1) {
1152 fromCylinder->postAddNotification(toItem, toCylinder, newToItemIndex);
1153 }
1154
1155 ret = toCylinder->queryAdd(index, *item, count, flags);
1156 toItem = nullptr;
1157 }
1158 }
1159 }
1160
1161 if (ret != RETURNVALUE_NOERROR) {
1162 return ret;
1163 }
1164
1165 // check how much we can move
1166 uint32_t maxQueryCount = 0;
1167 ReturnValue retMaxCount =
1168 toCylinder->queryMaxCount(index, *item, count, maxQueryCount, flags);
1169 if (retMaxCount != RETURNVALUE_NOERROR && maxQueryCount == 0) {
1170 return retMaxCount;
1171 }
1172
1173 uint32_t m;
1174 if (item->isStackable()) {
1175 m = std::min<uint32_t>(count, maxQueryCount);
1176 } else {
1177 m = maxQueryCount;
1178 }
1179
1180 Item *moveItem = item;
1181
1182 // check if we can remove this item
1183 ret = fromCylinder->queryRemove(*item, m, flags);
1184 if (ret != RETURNVALUE_NOERROR) {
1185 return ret;
1186 }
1187
1188 if (tradeItem) {
1189 if (toCylinder->getItem() == tradeItem) {
1190 return RETURNVALUE_NOTENOUGHROOM;
1191 }
1192
1193 Cylinder *tmpCylinder = toCylinder->getParent();
1194 while (tmpCylinder) {
1195 if (tmpCylinder->getItem() == tradeItem) {
1196 return RETURNVALUE_NOTENOUGHROOM;
1197 }
1198
1199 tmpCylinder = tmpCylinder->getParent();
1200 }
1201 }
1202
1203 // remove the item
1204 int32_t itemIndex = fromCylinder->getThingIndex(item);
1205 Item *updateItem = nullptr;
1206 fromCylinder->removeThing(item, m);
1207
1208 // update item(s)
1209 if (item->isStackable()) {
1210 uint32_t n;
1211
1212 if (item->equals(toItem)) {
1213 n = std::min<uint32_t>(100 - toItem->getItemCount(), m);
1214 toCylinder->updateThing(toItem, toItem->getID(),
1215 toItem->getItemCount() + n);
1216 updateItem = toItem;
1217 } else {
1218 n = 0;
1219 }
1220
1221 int32_t newCount = m - n;
1222 if (newCount > 0) {
1223 moveItem = item->clone();
1224 moveItem->setItemCount(newCount);
1225 } else {
1226 moveItem = nullptr;
1227 }
1228
1229 if (item->isRemoved()) {
1230 ReleaseItem(item);
1231 }
1232 }
1233
1234 // add item
1235 if (moveItem /*m - n > 0*/) {
1236 toCylinder->addThing(index, moveItem);
1237 }
1238
1239 if (itemIndex != -1) {
1240 fromCylinder->postRemoveNotification(item, toCylinder, itemIndex);
1241 }
1242
1243 if (moveItem) {
1244 int32_t moveItemIndex = toCylinder->getThingIndex(moveItem);
1245 if (moveItemIndex != -1) {
1246 toCylinder->postAddNotification(moveItem, fromCylinder, moveItemIndex);
1247 }
1248 }
1249
1250 if (updateItem) {
1251 int32_t updateItemIndex = toCylinder->getThingIndex(updateItem);
1252 if (updateItemIndex != -1) {
1253 toCylinder->postAddNotification(updateItem, fromCylinder,
1254 updateItemIndex);
1255 }
1256 }
1257
1258 if (_moveItem) {
1259 if (moveItem) {
1260 *_moveItem = moveItem;
1261 } else {
1262 *_moveItem = item;
1263 }
1264 }
1265
1266 // we could not move all, inform the player
1267 if (item->isStackable() && maxQueryCount < count) {
1268 return retMaxCount;
1269 }
1270
1271 if (moveItem && moveItem->getDuration() > 0) {
1272 if (moveItem->getDecaying() != DECAYING_TRUE) {
1273 moveItem->incrementReferenceCounter();
1274 moveItem->setDecaying(DECAYING_TRUE);
1275 toDecayItems.push_front(moveItem);
1276 }
1277 }
1278
1279 if (actorPlayer && fromPos && toPos) {
1280 g_events->eventPlayerOnItemMoved(actorPlayer, item, count, *fromPos, *toPos,
1281 fromCylinder, toCylinder);
1282 }
1283
1284 return ret;
1285}
1286
1287ReturnValue Game::internalAddItem(Cylinder *toCylinder, Item *item,
1288 int32_t index /*= INDEX_WHEREEVER*/,
1289 uint32_t flags /* = 0*/,
1290 bool test /* = false*/) {
1291 uint32_t remainderCount = 0;
1292 return internalAddItem(toCylinder, item, index, flags, test, remainderCount);
1293}
1294
1295ReturnValue Game::internalAddItem(Cylinder *toCylinder, Item *item,
1296 int32_t index, uint32_t flags, bool test,
1297 uint32_t &remainderCount) {
1298 if (toCylinder == nullptr || item == nullptr) {
1299 return RETURNVALUE_NOTPOSSIBLE;
1300 }
1301
1302 Cylinder *destCylinder = toCylinder;
1303 Item *toItem = nullptr;
1304 toCylinder = toCylinder->queryDestination(index, *item, &toItem, flags);
1305
1306 // check if we can add this item
1307 ReturnValue ret =
1308 toCylinder->queryAdd(index, *item, item->getItemCount(), flags);
1309 if (ret != RETURNVALUE_NOERROR) {
1310 return ret;
1311 }
1312
1313 /*
1314 Check if we can move add the whole amount, we do this by checking against the
1315 original cylinder, since the queryDestination can return a cylinder that might
1316 only hold a part of the full amount.
1317 */
1318 uint32_t maxQueryCount = 0;
1319 ret = destCylinder->queryMaxCount(INDEX_WHEREEVER, *item,
1320 item->getItemCount(), maxQueryCount, flags);
1321
1322 if (ret != RETURNVALUE_NOERROR) {
1323 return ret;
1324 }
1325
1326 if (test) {
1327 return RETURNVALUE_NOERROR;
1328 }
1329
1330 if (item->isStackable() && item->equals(toItem)) {
1331 uint32_t m = std::min<uint32_t>(item->getItemCount(), maxQueryCount);
1332 uint32_t n = std::min<uint32_t>(100 - toItem->getItemCount(), m);
1333
1334 toCylinder->updateThing(toItem, toItem->getID(),
1335 toItem->getItemCount() + n);
1336
1337 int32_t count = m - n;
1338 if (count > 0) {
1339 if (item->getItemCount() != count) {
1340 Item *remainderItem = item->clone();
1341 remainderItem->setItemCount(count);
1342 if (internalAddItem(destCylinder, remainderItem, INDEX_WHEREEVER, flags,
1343 false) != RETURNVALUE_NOERROR) {
1344 ReleaseItem(remainderItem);
1345 remainderCount = count;
1346 }
1347 } else {
1348 toCylinder->addThing(index, item);
1349
1350 int32_t itemIndex = toCylinder->getThingIndex(item);
1351 if (itemIndex != -1) {
1352 toCylinder->postAddNotification(item, nullptr, itemIndex);
1353 }
1354 }
1355 } else {
1356 // fully merged with toItem, item will be destroyed
1357 item->onRemoved();
1358 ReleaseItem(item);
1359
1360 int32_t itemIndex = toCylinder->getThingIndex(toItem);
1361 if (itemIndex != -1) {
1362 toCylinder->postAddNotification(toItem, nullptr, itemIndex);
1363 }
1364 }
1365 } else {
1366 toCylinder->addThing(index, item);
1367
1368 int32_t itemIndex = toCylinder->getThingIndex(item);
1369 if (itemIndex != -1) {
1370 toCylinder->postAddNotification(item, nullptr, itemIndex);
1371 }
1372 }
1373
1374 if (item->getDuration() > 0) {
1375 item->incrementReferenceCounter();
1376 item->setDecaying(DECAYING_TRUE);
1377 toDecayItems.push_front(item);
1378 }
1379
1380 return RETURNVALUE_NOERROR;
1381}
1382
1383ReturnValue Game::internalRemoveItem(Item *item, int32_t count /*= -1*/,
1384 bool test /*= false*/,
1385 uint32_t flags /*= 0*/) {
1386 Cylinder *cylinder = item->getParent();
1387 if (cylinder == nullptr) {
1388 return RETURNVALUE_NOTPOSSIBLE;
1389 }
1390
1391 Tile *fromTile = cylinder->getTile();
1392 if (fromTile) {
1393 auto it = browseFields.find(fromTile);
1394 if (it != browseFields.end() && it->second == cylinder) {
1395 cylinder = fromTile;
1396 }
1397 }
1398
1399 if (count == -1) {
1400 count = item->getItemCount();
1401 }
1402
1403 // check if we can remove this item
1404 ReturnValue ret =
1405 cylinder->queryRemove(*item, count, flags | FLAG_IGNORENOTMOVEABLE);
1406 if (ret != RETURNVALUE_NOERROR) {
1407 return ret;
1408 }
1409
1410 if (!item->canRemove()) {
1411 return RETURNVALUE_NOTPOSSIBLE;
1412 }
1413
1414 if (!test) {
1415 int32_t index = cylinder->getThingIndex(item);
1416
1417 // remove the item
1418 cylinder->removeThing(item, count);
1419
1420 if (item->isRemoved()) {
1421 item->onRemoved();
1422 if (item->canDecay()) {
1423 decayItems->remove(item);
1424 }
1425 ReleaseItem(item);
1426 }
1427
1428 cylinder->postRemoveNotification(item, nullptr, index);
1429 }
1430
1431 return RETURNVALUE_NOERROR;
1432}
1433
1434ReturnValue
1435Game::internalPlayerAddItem(Player *player, Item *item,
1436 bool dropOnMap /*= true*/,
1437 slots_t slot /*= CONST_SLOT_WHEREEVER*/) {
1438 uint32_t remainderCount = 0;
1439 ReturnValue ret = internalAddItem(player, item, static_cast<int32_t>(slot), 0,
1440 false, remainderCount);
1441 if (remainderCount != 0) {
1442 Item *remainderItem = Item::CreateItem(item->getID(), remainderCount);
1443 ReturnValue remaindRet = internalAddItem(player->getTile(), remainderItem,
1444 INDEX_WHEREEVER, FLAG_NOLIMIT);
1445 if (remaindRet != RETURNVALUE_NOERROR) {
1446 ReleaseItem(remainderItem);
1447 }
1448 }
1449
1450 if (ret != RETURNVALUE_NOERROR && dropOnMap) {
1451 ret =
1452 internalAddItem(player->getTile(), item, INDEX_WHEREEVER, FLAG_NOLIMIT);
1453 }
1454
1455 return ret;
1456}
1457
1458Item *Game::findItemOfType(Cylinder *cylinder, uint16_t itemId,
1459 bool depthSearch /*= true*/,
1460 int32_t subType /*= -1*/) const {
1461 if (cylinder == nullptr) {
1462 return nullptr;
1463 }
1464
1465 std::vector<Container *> containers;
1466 for (size_t i = cylinder->getFirstIndex(), j = cylinder->getLastIndex();
1467 i < j; ++i) {
1468 Thing *thing = cylinder->getThing(i);
1469 if (!thing) {
1470 continue;
1471 }
1472
1473 Item *item = thing->getItem();
1474 if (!item) {
1475 continue;
1476 }
1477
1478 if (item->getID() == itemId &&
1479 (subType == -1 || subType == item->getSubType())) {
1480 return item;
1481 }
1482
1483 if (depthSearch) {
1484 Container *container = item->getContainer();
1485 if (container) {
1486 containers.push_back(container);
1487 }
1488 }
1489 }
1490
1491 size_t i = 0;
1492 while (i < containers.size()) {
1493 Container *container = containers[i++];
1494 for (Item *item : container->getItemList()) {
1495 if (item->getID() == itemId &&
1496 (subType == -1 || subType == item->getSubType())) {
1497 return item;
1498 }
1499
1500 Container *subContainer = item->getContainer();
1501 if (subContainer) {
1502 containers.push_back(subContainer);
1503 }
1504 }
1505 }
1506 return nullptr;
1507}
1508
1509bool Game::removeMoney(Cylinder *cylinder, uint64_t money,
1510 uint32_t flags /*= 0*/) {
1511 if (cylinder == nullptr) {
1512 return false;
1513 }
1514
1515 if (money == 0) {
1516 return true;
1517 }
1518
1519 std::vector<Container *> containers;
1520
1521 std::multimap<uint32_t, Item *> moneyMap;
1522 uint64_t moneyCount = 0;
1523
1524 for (size_t i = cylinder->getFirstIndex(), j = cylinder->getLastIndex();
1525 i < j; ++i) {
1526 Thing *thing = cylinder->getThing(i);
1527 if (!thing) {
1528 continue;
1529 }
1530
1531 Item *item = thing->getItem();
1532 if (!item) {
1533 continue;
1534 }
1535
1536 Container *container = item->getContainer();
1537 if (container) {
1538 containers.push_back(container);
1539 } else {
1540 const uint32_t worth = item->getWorth();
1541 if (worth != 0) {
1542 moneyCount += worth;
1543 moneyMap.emplace(worth, item);
1544 }
1545 }
1546 }
1547
1548 size_t i = 0;
1549 while (i < containers.size()) {
1550 Container *container = containers[i++];
1551 for (Item *item : container->getItemList()) {
1552 Container *tmpContainer = item->getContainer();
1553 if (tmpContainer) {
1554 containers.push_back(tmpContainer);
1555 } else {
1556 const uint32_t worth = item->getWorth();
1557 if (worth != 0) {
1558 moneyCount += worth;
1559 moneyMap.emplace(worth, item);
1560 }
1561 }
1562 }
1563 }
1564
1565 if (moneyCount < money) {
1566 return false;
1567 }
1568
1569 for (const auto &moneyEntry : moneyMap) {
1570 Item *item = moneyEntry.second;
1571 if (moneyEntry.first < money) {
1572 internalRemoveItem(item);
1573 money -= moneyEntry.first;
1574 } else if (moneyEntry.first > money) {
1575 const uint32_t worth = moneyEntry.first / item->getItemCount();
1576 const uint32_t removeCount =
1577 std::ceil(money / static_cast<double>(worth));
1578
1579 addMoney(cylinder, (worth * removeCount) - money, flags);
1580 internalRemoveItem(item, removeCount);
1581 break;
1582 } else {
1583 internalRemoveItem(item);
1584 break;
1585 }
1586 }
1587 return true;
1588}
1589
1590void Game::addMoney(Cylinder *cylinder, uint64_t money,
1591 uint32_t flags /*= 0*/) {
1592 if (money == 0) {
1593 return;
1594 }
1595
1596 uint32_t crystalCoins = money / 10000;
1597 money -= crystalCoins * 10000;
1598 while (crystalCoins > 0) {
1599 const uint16_t count = std::min<uint32_t>(100, crystalCoins);
1600
1601 Item *remaindItem = Item::CreateItem(ITEM_CRYSTAL_COIN, count);
1602
1603 ReturnValue ret =
1604 internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags);
1605 if (ret != RETURNVALUE_NOERROR) {
1606 internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER,
1607 FLAG_NOLIMIT);
1608 }
1609
1610 crystalCoins -= count;
1611 }
1612
1613 uint16_t platinumCoins = money / 100;
1614 if (platinumCoins != 0) {
1615 Item *remaindItem = Item::CreateItem(ITEM_PLATINUM_COIN, platinumCoins);
1616
1617 ReturnValue ret =
1618 internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags);
1619 if (ret != RETURNVALUE_NOERROR) {
1620 internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER,
1621 FLAG_NOLIMIT);
1622 }
1623
1624 money -= platinumCoins * 100;
1625 }
1626
1627 if (money != 0) {
1628 Item *remaindItem = Item::CreateItem(ITEM_GOLD_COIN, money);
1629
1630 ReturnValue ret =
1631 internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags);
1632 if (ret != RETURNVALUE_NOERROR) {
1633 internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER,
1634 FLAG_NOLIMIT);
1635 }
1636 }
1637}
1638
1639Item *Game::transformItem(Item *item, uint16_t newId,
1640 int32_t newCount /*= -1*/) {
1641 if (item->getID() == newId &&
1642 (newCount == -1 ||
1643 (newCount == item->getSubType() &&
1644 newCount != 0))) { // chargeless item placed on map = infinite
1645 return item;
1646 }
1647
1648 Cylinder *cylinder = item->getParent();
1649 if (cylinder == nullptr) {
1650 return nullptr;
1651 }
1652
1653 Tile *fromTile = cylinder->getTile();
1654 if (fromTile) {
1655 auto it = browseFields.find(fromTile);
1656 if (it != browseFields.end() && it->second == cylinder) {
1657 cylinder = fromTile;
1658 }
1659 }
1660
1661 int32_t itemIndex = cylinder->getThingIndex(item);
1662 if (itemIndex == -1) {
1663 return item;
1664 }
1665
1666 if (!item->canTransform()) {
1667 return item;
1668 }
1669
1670 const ItemType &newType = Item::items[newId];
1671 if (newType.id == 0) {
1672 return item;
1673 }
1674
1675 const ItemType &curType = Item::items[item->getID()];
1676 if (curType.alwaysOnTop != newType.alwaysOnTop) {
1677 // This only occurs when you transform items on tiles from a downItem to a
1678 // topItem (or vice versa) Remove the old, and add the new
1679 cylinder->removeThing(item, item->getItemCount());
1680 cylinder->postRemoveNotification(item, cylinder, itemIndex);
1681
1682 item->setID(newId);
1683 if (newCount != -1) {
1684 item->setSubType(newCount);
1685 }
1686 cylinder->addThing(item);
1687
1688 Cylinder *newParent = item->getParent();
1689 if (newParent == nullptr) {
1690 ReleaseItem(item);
1691 return nullptr;
1692 }
1693
1694 newParent->postAddNotification(item, cylinder,
1695 newParent->getThingIndex(item));
1696 return item;
1697 }
1698
1699 if (curType.type == newType.type) {
1700 // Both items has the same type so we can safely change id/subtype
1701 if (newCount == 0 &&
1702 (item->isStackable() || item->hasAttribute(ITEM_ATTRIBUTE_CHARGES))) {
1703 if (item->isStackable()) {
1704 internalRemoveItem(item);
1705 return nullptr;
1706 } else {
1707 int32_t newItemId = newId;
1708 if (curType.id == newType.id) {
1709 newItemId = item->getDecayTo();
1710 }
1711
1712 if (newItemId < 0) {
1713 internalRemoveItem(item);
1714 return nullptr;
1715 } else if (newItemId != newId) {
1716 // Replacing the the old item with the new while maintaining the old
1717 // position
1718 Item *newItem = Item::CreateItem(newItemId, 1);
1719 if (newItem == nullptr) {
1720 return nullptr;
1721 }
1722
1723 cylinder->replaceThing(itemIndex, newItem);
1724 cylinder->postAddNotification(newItem, cylinder, itemIndex);
1725
1726 item->setParent(nullptr);
1727 cylinder->postRemoveNotification(item, cylinder, itemIndex);
1728 ReleaseItem(item);
1729 return newItem;
1730 } else {
1731 return transformItem(item, newItemId);
1732 }
1733 }
1734 } else {
1735 cylinder->postRemoveNotification(item, cylinder, itemIndex);
1736 uint16_t itemId = item->getID();
1737 int32_t count = item->getSubType();
1738
1739 if (curType.id != newType.id) {
1740 if (newType.group != curType.group) {
1741 item->setDefaultSubtype();
1742 }
1743
1744 itemId = newId;
1745 }
1746
1747 if (newCount != -1 && newType.hasSubType()) {
1748 count = newCount;
1749 }
1750
1751 cylinder->updateThing(item, itemId, count);
1752 cylinder->postAddNotification(item, cylinder, itemIndex);
1753 return item;
1754 }
1755 }
1756
1757 // Replacing the the old item with the new while maintaining the old position
1758 Item *newItem;
1759 if (newCount == -1) {
1760 newItem = Item::CreateItem(newId);
1761 } else {
1762 newItem = Item::CreateItem(newId, newCount);
1763 }
1764
1765 if (newItem == nullptr) {
1766 return nullptr;
1767 }
1768
1769 cylinder->replaceThing(itemIndex, newItem);
1770 cylinder->postAddNotification(newItem, cylinder, itemIndex);
1771
1772 item->setParent(nullptr);
1773 cylinder->postRemoveNotification(item, cylinder, itemIndex);
1774 ReleaseItem(item);
1775
1776 if (newItem->getDuration() > 0) {
1777 if (newItem->getDecaying() != DECAYING_TRUE) {
1778 newItem->incrementReferenceCounter();
1779 newItem->setDecaying(DECAYING_TRUE);
1780 toDecayItems.push_front(newItem);
1781 }
1782 }
1783
1784 return newItem;
1785}
1786
1787ReturnValue Game::internalTeleport(Thing *thing, const Position &newPos,
1788 bool pushMove /* = true*/,
1789 uint32_t flags /*= 0*/) {
1790 if (newPos == thing->getPosition()) {
1791 return RETURNVALUE_NOERROR;
1792 } else if (thing->isRemoved()) {
1793 return RETURNVALUE_NOTPOSSIBLE;
1794 }
1795
1796 Tile *toTile = map.getTile(newPos);
1797 if (!toTile) {
1798 return RETURNVALUE_NOTPOSSIBLE;
1799 }
1800
1801 if (Creature *creature = thing->getCreature()) {
1802 ReturnValue ret = toTile->queryAdd(0, *creature, 1, FLAG_NOLIMIT);
1803 if (ret != RETURNVALUE_NOERROR) {
1804 return ret;
1805 }
1806
1807 map.moveCreature(*creature, *toTile, !pushMove);
1808 return RETURNVALUE_NOERROR;
1809 } else if (Item *item = thing->getItem()) {
1810 return internalMoveItem(item->getParent(), toTile, INDEX_WHEREEVER, item,
1811 item->getItemCount(), nullptr, flags);
1812 }
1813 return RETURNVALUE_NOTPOSSIBLE;
1814}
1815
1816Item *searchForItem(Container *container, uint16_t itemId) {
1817 for (ContainerIterator it = container->iterator(); it.hasNext();
1818 it.advance()) {
1819 if ((*it)->getID() == itemId) {
1820 return *it;
1821 }
1822 }
1823
1824 return nullptr;
1825}
1826
1827slots_t getSlotType(const ItemType &it) {
1828 slots_t slot = CONST_SLOT_RIGHT;
1829 if (it.weaponType != WeaponType_t::WEAPON_SHIELD) {
1830 int32_t slotPosition = it.slotPosition;
1831
1832 if (slotPosition & SLOTP_HEAD) {
1833 slot = CONST_SLOT_HEAD;
1834 } else if (slotPosition & SLOTP_NECKLACE) {
1835 slot = CONST_SLOT_NECKLACE;
1836 } else if (slotPosition & SLOTP_ARMOR) {
1837 slot = CONST_SLOT_ARMOR;
1838 } else if (slotPosition & SLOTP_LEGS) {
1839 slot = CONST_SLOT_LEGS;
1840 } else if (slotPosition & SLOTP_FEET) {
1841 slot = CONST_SLOT_FEET;
1842 } else if (slotPosition & SLOTP_RING) {
1843 slot = CONST_SLOT_RING;
1844 } else if (slotPosition & SLOTP_AMMO) {
1845 slot = CONST_SLOT_AMMO;
1846 } else if (slotPosition & SLOTP_TWO_HAND || slotPosition & SLOTP_LEFT) {
1847 slot = CONST_SLOT_LEFT;
1848 }
1849 }
1850
1851 return slot;
1852}
1853
1854// Implementation of player invoked events
1855void Game::playerEquipItem(uint32_t playerId, uint16_t spriteId) {
1856 Player *player = getPlayerByID(playerId);
1857 if (!player) {
1858 return;
1859 }
1860
1861 Item *item = player->getInventoryItem(CONST_SLOT_BACKPACK);
1862 if (!item) {
1863 return;
1864 }
1865
1866 Container *backpack = item->getContainer();
1867 if (!backpack) {
1868 return;
1869 }
1870
1871 const ItemType &it = Item::items.getItemIdByClientId(spriteId);
1872 slots_t slot = getSlotType(it);
1873
1874 Item *slotItem = player->getInventoryItem(slot);
1875 Item *equipItem = searchForItem(backpack, it.id);
1876 if (slotItem && slotItem->getID() == it.id &&
1877 (!it.stackable || slotItem->getItemCount() == 100 || !equipItem)) {
1878 internalMoveItem(slotItem->getParent(), player, CONST_SLOT_WHEREEVER,
1879 slotItem, slotItem->getItemCount(), nullptr);
1880 } else if (equipItem) {
1881 internalMoveItem(equipItem->getParent(), player, slot, equipItem,
1882 equipItem->getItemCount(), nullptr);
1883 }
1884}
1885
1886void Game::playerMove(uint32_t playerId, Direction direction) {
1887 Player *player = getPlayerByID(playerId);
1888 if (!player) {
1889 return;
1890 }
1891
1892 player->resetIdleTime();
1893 player->setNextWalkActionTask(nullptr);
1894
1895 player->startAutoWalk(std::list<Direction>{direction});
1896}
1897
1898bool Game::playerBroadcastMessage(Player *player,
1899 const std::string &text) const {
1900 if (!player->hasFlag(PlayerFlag_CanBroadcast)) {
1901 return false;
1902 }
1903
1904 std::cout << "> " << player->getName() << " broadcasted: \"" << text << "\"."
1905 << std::endl;
1906
1907 for (const auto &it : players) {
1908 it.second->sendPrivateMessage(player, TALKTYPE_BROADCAST, text);
1909 }
1910
1911 return true;
1912}
1913
1914void Game::playerCreatePrivateChannel(uint32_t playerId) {
1915 Player *player = getPlayerByID(playerId);
1916 if (!player || !player->isPremium()) {
1917 return;
1918 }
1919
1920 ChatChannel *channel = g_chat->createChannel(*player, CHANNEL_PRIVATE);
1921 if (!channel || !channel->addUser(*player)) {
1922 return;
1923 }
1924
1925 player->sendCreatePrivateChannel(channel->getId(), channel->getName());
1926}
1927
1928void Game::playerChannelInvite(uint32_t playerId, const std::string &name) {
1929 Player *player = getPlayerByID(playerId);
1930 if (!player) {
1931 return;
1932 }
1933
1934 PrivateChatChannel *channel = g_chat->getPrivateChannel(*player);
1935 if (!channel) {
1936 return;
1937 }
1938
1939 Player *invitePlayer = getPlayerByName(name);
1940 if (!invitePlayer) {
1941 return;
1942 }
1943
1944 if (player == invitePlayer) {
1945 return;
1946 }
1947
1948 channel->invitePlayer(*player, *invitePlayer);
1949}
1950
1951void Game::playerChannelExclude(uint32_t playerId, const std::string &name) {
1952 Player *player = getPlayerByID(playerId);
1953 if (!player) {
1954 return;
1955 }
1956
1957 PrivateChatChannel *channel = g_chat->getPrivateChannel(*player);
1958 if (!channel) {
1959 return;
1960 }
1961
1962 Player *excludePlayer = getPlayerByName(name);
1963 if (!excludePlayer) {
1964 return;
1965 }
1966
1967 if (player == excludePlayer) {
1968 return;
1969 }
1970
1971 channel->excludePlayer(*player, *excludePlayer);
1972}
1973
1974void Game::playerRequestChannels(uint32_t playerId) {
1975 Player *player = getPlayerByID(playerId);
1976 if (!player) {
1977 return;
1978 }
1979
1980 player->sendChannelsDialog();
1981}
1982
1983void Game::playerOpenChannel(uint32_t playerId, uint16_t channelId) {
1984 Player *player = getPlayerByID(playerId);
1985 if (!player) {
1986 return;
1987 }
1988
1989 ChatChannel *channel = g_chat->addUserToChannel(*player, channelId);
1990 if (!channel) {
1991 return;
1992 }
1993
1994 const InvitedMap *invitedUsers = channel->getInvitedUsers();
1995 const UsersMap *users;
1996 if (!channel->isPublicChannel()) {
1997 users = &channel->getUsers();
1998 } else {
1999 users = nullptr;
2000 }
2001
2002 player->sendChannel(channel->getId(), channel->getName(), users,
2003 invitedUsers);
2004}
2005
2006void Game::playerCloseChannel(uint32_t playerId, uint16_t channelId) {
2007 Player *player = getPlayerByID(playerId);
2008 if (!player) {
2009 return;
2010 }
2011
2012 g_chat->removeUserFromChannel(*player, channelId);
2013}
2014
2015void Game::playerOpenPrivateChannel(uint32_t playerId, std::string &receiver) {
2016 Player *player = getPlayerByID(playerId);
2017 if (!player) {
2018 return;
2019 }
2020
2021 if (!IOLoginData::formatPlayerName(receiver)) {
2022 player->sendCancelMessage("A player with this name does not exist.");
2023 return;
2024 }
2025
2026 if (player->getName() == receiver) {
2027 player->sendCancelMessage(
2028 "You cannot set up a private message channel with yourself.");
2029 return;
2030 }
2031
2032 player->sendOpenPrivateChannel(receiver);
2033}
2034
2035void Game::playerCloseNpcChannel(uint32_t playerId) {
2036 Player *player = getPlayerByID(playerId);
2037 if (!player) {
2038 return;
2039 }
2040
2041 SpectatorVec spectators;
2042 map.getSpectators(spectators, player->getPosition());
2043 for (Creature *spectator : spectators) {
2044 if (Npc *npc = spectator->getNpc()) {
2045 npc->onPlayerCloseChannel(player);
2046 }
2047 }
2048}
2049
2050void Game::playerReceivePing(uint32_t playerId) {
2051 Player *player = getPlayerByID(playerId);
2052 if (!player) {
2053 return;
2054 }
2055
2056 player->receivePing();
2057}
2058
2059void Game::playerReceivePingBack(uint32_t playerId) {
2060 Player *player = getPlayerByID(playerId);
2061 if (!player) {
2062 return;
2063 }
2064
2065 player->sendPingBack();
2066}
2067
2068void Game::playerReceiveNewPing(uint32_t playerId, uint16_t ping,
2069 uint16_t fps) {
2070 Player *player = getPlayerByID(playerId);
2071 if (!player) {
2072 return;
2073 }
2074
2075 player->receivePing();
2076 player->setLocalPing(ping);
2077 player->setFPS(fps);
2078}
2079
2080void Game::playerAutoWalk(uint32_t playerId,
2081 const std::list<Direction> &listDir) {
2082 Player *player = getPlayerByID(playerId);
2083 if (!player) {
2084 return;
2085 }
2086
2087 player->resetIdleTime();
2088 player->setNextWalkTask(nullptr);
2089 player->startAutoWalk(listDir);
2090}
2091
2092void Game::playerNewWalk(uint32_t playerId, Position pos, uint8_t flags,
2093 std::list<Direction> listDir) {
2094 Player *player = getPlayerByID(playerId);
2095 if (!player) {
2096 return;
2097 }
2098
2099 player->resetIdleTime();
2100 player->setNextWalkTask(nullptr);
2101 player->setNextWalkActionTask(nullptr);
2102
2103 // bool withPreWalk = flags & 0x01;
2104 bool autoWalk = flags & 0x04;
2105
2106 if (pos.x != 0 && pos.y != 0 && pos != player->getPosition()) {
2107 auto &dirs = player->getListWalkDir();
2108 Position nextpos = player->getPosition();
2109
2110 int limit = 3;
2111 for (auto &dir : dirs) {
2112 nextpos = getNextPosition(dir, nextpos);
2113 if (--limit == 0)
2114 break;
2115 }
2116
2117 if (!autoWalk) {
2118 // manual walk desync, check if can be fixed
2119 if (limit == 0 || nextpos != pos) {
2120 player->sendNewCancelWalk();
2121 return;
2122 }
2123
2124 for (auto &dir : listDir) {
2125 dirs.push_back(dir);
2126 }
2127 return;
2128 } else {
2129 // auto walk desync, check if can be fixed
2130 if (limit > 0 && nextpos == pos) {
2131 for (auto &dir : listDir) {
2132 dirs.push_back(dir);
2133 }
2134 return;
2135 }
2136
2137 // can't be fixed, so maybe find another way
2138 // WARNING: This loop may use extra cpu but makes autowalk (map click)
2139 // much better
2140 for (int x = 0; x < 3; ++x) {
2141 if (listDir.empty()) {
2142 player->sendNewCancelWalk();
2143 return;
2144 }
2145
2146 for (int i = 0; i < 2; ++i) {
2147 if (listDir.empty())
2148 break;
2149 pos = getNextPosition(listDir.front(), pos);
2150 listDir.pop_front();
2151 }
2152
2153 std::list<Direction> newPath;
2154 if (player->getPathTo(pos, newPath, 0, 0, false, true)) {
2155 newPath.reverse();
2156 for (auto &it : newPath)
2157 listDir.push_front(it);
2158 break;
2159 }
2160 }
2161 }
2162 }
2163
2164 player->startAutoWalk(listDir);
2165}
2166
2167void Game::playerStopAutoWalk(uint32_t playerId) {
2168 Player *player = getPlayerByID(playerId);
2169 if (!player) {
2170 return;
2171 }
2172
2173 player->stopWalk();
2174}
2175
2176void Game::playerUseItemEx(uint32_t playerId, const Position &fromPos,
2177 uint8_t fromStackPos, uint16_t fromSpriteId,
2178 const Position &toPos, uint8_t toStackPos,
2179 uint16_t toSpriteId) {
2180 Player *player = getPlayerByID(playerId);
2181 if (!player) {
2182 return;
2183 }
2184
2185 bool isHotkey = (fromPos.x == 0xFFFF && fromPos.y == 0 && fromPos.z == 0);
2186 if (isHotkey && !g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) {
2187 return;
2188 }
2189
2190 Thing *thing = internalGetThing(player, fromPos, fromStackPos, fromSpriteId,
2191 STACKPOS_USEITEM);
2192 if (!thing) {
2193 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
2194 return;
2195 }
2196
2197 Item *item = thing->getItem();
2198 if (!item || !item->isUseable() || item->getClientID() != fromSpriteId) {
2199 player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
2200 return;
2201 }
2202
2203 Position walkToPos = fromPos;
2204 ReturnValue ret = g_actions->canUse(player, fromPos);
2205 if (ret == RETURNVALUE_NOERROR) {
2206 ret = g_actions->canUse(player, toPos, item);
2207 if (ret == RETURNVALUE_TOOFARAWAY) {
2208 walkToPos = toPos;
2209 }
2210 }
2211
2212 if (ret != RETURNVALUE_NOERROR) {
2213 if (ret == RETURNVALUE_TOOFARAWAY) {
2214 Position itemPos = fromPos;
2215 uint8_t itemStackPos = fromStackPos;
2216
2217 if (fromPos.x != 0xFFFF && toPos.x != 0xFFFF &&
2218 Position::areInRange<1, 1, 0>(fromPos, player->getPosition()) &&
2219 !Position::areInRange<1, 1, 0>(fromPos, toPos)) {
2220 Item *moveItem = nullptr;
2221
2222 ret = internalMoveItem(item->getParent(), player, INDEX_WHEREEVER, item,
2223 item->getItemCount(), &moveItem, 0, player,
2224 nullptr, &fromPos, &toPos);
2225 if (ret != RETURNVALUE_NOERROR) {
2226 player->sendCancelMessage(ret);
2227 return;
2228 }
2229
2230 // changing the position since its now in the inventory of the player
2231 internalGetPosition(moveItem, itemPos, itemStackPos);
2232 }
2233
2234 std::list<Direction> listDir;
2235 if (player->getPathTo(walkToPos, listDir, 0, 1, true, true)) {
2236 g_dispatcher.addTask(createTask(
2237 std::bind(&Game::playerAutoWalk, this, player->getID(), listDir)));
2238
2239 SchedulerTask *task = createSchedulerTask(
2240 400, std::bind(&Game::playerUseItemEx, this, playerId, itemPos,
2241 itemStackPos, fromSpriteId, toPos, toStackPos,
2242 toSpriteId));
2243 player->setNextWalkActionTask(task);
2244 } else {
2245 player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
2246 }
2247 return;
2248 }
2249
2250 player->sendCancelMessage(ret);
2251 return;
2252 }
2253
2254 if (!player->canDoAction()) {
2255 uint32_t delay = player->getNextActionTime();
2256 SchedulerTask *task = createSchedulerTask(
2257 delay,
2258 std::bind(&Game::playerUseItemEx, this, playerId, fromPos, fromStackPos,
2259 fromSpriteId, toPos, toStackPos, toSpriteId));
2260 player->setNextActionTask(task);
2261 return;
2262 }
2263
2264 player->resetIdleTime();
2265 player->setNextActionTask(nullptr);
2266
2267 g_actions->useItemEx(player, fromPos, toPos, toStackPos, item, isHotkey);
2268}
2269
2270void Game::playerUseItem(uint32_t playerId, const Position &pos,
2271 uint8_t stackPos, uint8_t index, uint16_t spriteId) {
2272 Player *player = getPlayerByID(playerId);
2273 if (!player) {
2274 return;
2275 }
2276
2277 bool isHotkey = (pos.x == 0xFFFF && pos.y == 0 && pos.z == 0);
2278 if (isHotkey && !g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) {
2279 return;
2280 }
2281
2282 Thing *thing =
2283 internalGetThing(player, pos, stackPos, spriteId, STACKPOS_USEITEM);
2284 if (!thing) {
2285 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
2286 return;
2287 }
2288
2289 Item *item = thing->getItem();
2290 if (!item || item->isUseable() || item->getClientID() != spriteId) {
2291 player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
2292 return;
2293 }
2294
2295 ReturnValue ret = g_actions->canUse(player, pos);
2296 if (ret != RETURNVALUE_NOERROR) {
2297 if (ret == RETURNVALUE_TOOFARAWAY) {
2298 std::list<Direction> listDir;
2299 if (player->getPathTo(pos, listDir, 0, 1, true, true)) {
2300 g_dispatcher.addTask(createTask(
2301 std::bind(&Game::playerAutoWalk, this, player->getID(), listDir)));
2302
2303 SchedulerTask *task = createSchedulerTask(
2304 400, std::bind(&Game::playerUseItem, this, playerId, pos, stackPos,
2305 index, spriteId));
2306 player->setNextWalkActionTask(task);
2307 return;
2308 }
2309
2310 ret = RETURNVALUE_THEREISNOWAY;
2311 }
2312
2313 player->sendCancelMessage(ret);
2314 return;
2315 }
2316
2317 if (!player->canDoAction()) {
2318 uint32_t delay = player->getNextActionTime();
2319 SchedulerTask *task = createSchedulerTask(
2320 delay, std::bind(&Game::playerUseItem, this, playerId, pos, stackPos,
2321 index, spriteId));
2322 player->setNextActionTask(task);
2323 return;
2324 }
2325
2326 player->resetIdleTime();
2327 player->setNextActionTask(nullptr);
2328
2329 g_actions->useItem(player, pos, index, item, isHotkey);
2330}
2331
2332void Game::playerUseWithCreature(uint32_t playerId, const Position &fromPos,
2333 uint8_t fromStackPos, uint32_t creatureId,
2334 uint16_t spriteId) {
2335 Player *player = getPlayerByID(playerId);
2336 if (!player) {
2337 return;
2338 }
2339
2340 Creature *creature = getCreatureByID(creatureId);
2341 if (!creature) {
2342 return;
2343 }
2344
2345 if (!Position::areInRange<7, 5, 0>(creature->getPosition(),
2346 player->getPosition())) {
2347 return;
2348 }
2349
2350 bool isHotkey = (fromPos.x == 0xFFFF && fromPos.y == 0 && fromPos.z == 0);
2351 if (!g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) {
2352 if (creature->getPlayer() || isHotkey) {
2353 player->sendCancelMessage(RETURNVALUE_DIRECTPLAYERSHOOT);
2354 return;
2355 }
2356 }
2357
2358 Thing *thing = internalGetThing(player, fromPos, fromStackPos, spriteId,
2359 STACKPOS_USEITEM);
2360 if (!thing) {
2361 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
2362 return;
2363 }
2364
2365 Item *item = thing->getItem();
2366 if (!item || !item->isUseable() || item->getClientID() != spriteId) {
2367 player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
2368 return;
2369 }
2370
2371 Position toPos = creature->getPosition();
2372 Position walkToPos = fromPos;
2373 ReturnValue ret = g_actions->canUse(player, fromPos);
2374 if (ret == RETURNVALUE_NOERROR) {
2375 ret = g_actions->canUse(player, toPos, item);
2376 if (ret == RETURNVALUE_TOOFARAWAY) {
2377 walkToPos = toPos;
2378 }
2379 }
2380
2381 if (ret != RETURNVALUE_NOERROR) {
2382 if (ret == RETURNVALUE_TOOFARAWAY) {
2383 Position itemPos = fromPos;
2384 uint8_t itemStackPos = fromStackPos;
2385
2386 if (fromPos.x != 0xFFFF &&
2387 Position::areInRange<1, 1, 0>(fromPos, player->getPosition()) &&
2388 !Position::areInRange<1, 1, 0>(fromPos, toPos)) {
2389 Item *moveItem = nullptr;
2390 ret = internalMoveItem(item->getParent(), player, INDEX_WHEREEVER, item,
2391 item->getItemCount(), &moveItem, 0, player,
2392 nullptr, &fromPos, &toPos);
2393 if (ret != RETURNVALUE_NOERROR) {
2394 player->sendCancelMessage(ret);
2395 return;
2396 }
2397
2398 // changing the position since its now in the inventory of the player
2399 internalGetPosition(moveItem, itemPos, itemStackPos);
2400 }
2401
2402 std::list<Direction> listDir;
2403 if (player->getPathTo(walkToPos, listDir, 0, 1, true, true)) {
2404 g_dispatcher.addTask(createTask(
2405 std::bind(&Game::playerAutoWalk, this, player->getID(), listDir)));
2406
2407 SchedulerTask *task = createSchedulerTask(
2408 400, std::bind(&Game::playerUseWithCreature, this, playerId,
2409 itemPos, itemStackPos, creatureId, spriteId));
2410 player->setNextWalkActionTask(task);
2411 } else {
2412 player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
2413 }
2414 return;
2415 }
2416
2417 player->sendCancelMessage(ret);
2418 return;
2419 }
2420
2421 if (!player->canDoAction()) {
2422 uint32_t delay = player->getNextActionTime();
2423 SchedulerTask *task = createSchedulerTask(
2424 delay, std::bind(&Game::playerUseWithCreature, this, playerId, fromPos,
2425 fromStackPos, creatureId, spriteId));
2426 player->setNextActionTask(task);
2427 return;
2428 }
2429
2430 player->resetIdleTime();
2431 player->setNextActionTask(nullptr);
2432
2433 g_actions->useItemEx(player, fromPos, creature->getPosition(),
2434 creature->getParent()->getThingIndex(creature), item,
2435 isHotkey, creature);
2436}
2437
2438void Game::playerCloseContainer(uint32_t playerId, uint8_t cid) {
2439 Player *player = getPlayerByID(playerId);
2440 if (!player) {
2441 return;
2442 }
2443
2444 player->closeContainer(cid);
2445 player->sendCloseContainer(cid);
2446}
2447
2448void Game::playerMoveUpContainer(uint32_t playerId, uint8_t cid) {
2449 Player *player = getPlayerByID(playerId);
2450 if (!player) {
2451 return;
2452 }
2453
2454 Container *container = player->getContainerByID(cid);
2455 if (!container) {
2456 return;
2457 }
2458
2459 Container *parentContainer =
2460 dynamic_cast<Container *>(container->getRealParent());
2461 if (!parentContainer) {
2462 Tile *tile = container->getTile();
2463 if (!tile) {
2464 return;
2465 }
2466
2467 auto it = browseFields.find(tile);
2468 if (it == browseFields.end()) {
2469 parentContainer = new Container(tile);
2470 parentContainer->incrementReferenceCounter();
2471 browseFields[tile] = parentContainer;
2472 g_scheduler.addEvent(
2473 createSchedulerTask(30000, std::bind(&Game::decreaseBrowseFieldRef,
2474 this, tile->getPosition())));
2475 } else {
2476 parentContainer = it->second;
2477 }
2478 }
2479
2480 player->addContainer(cid, parentContainer);
2481 player->sendContainer(cid, parentContainer, parentContainer->hasParent(),
2482 player->getContainerIndex(cid));
2483}
2484
2485void Game::playerUpdateContainer(uint32_t playerId, uint8_t cid) {
2486 Player *player = getPlayerByID(playerId);
2487 if (!player) {
2488 return;
2489 }
2490
2491 Container *container = player->getContainerByID(cid);
2492 if (!container) {
2493 return;
2494 }
2495
2496 player->sendContainer(cid, container, container->hasParent(),
2497 player->getContainerIndex(cid));
2498}
2499
2500void Game::playerRotateItem(uint32_t playerId, const Position &pos,
2501 uint8_t stackPos, const uint16_t spriteId) {
2502 Player *player = getPlayerByID(playerId);
2503 if (!player) {
2504 return;
2505 }
2506
2507 Thing *thing =
2508 internalGetThing(player, pos, stackPos, 0, STACKPOS_TOPDOWN_ITEM);
2509 if (!thing) {
2510 return;
2511 }
2512
2513 Item *item = thing->getItem();
2514 if (!item || item->getClientID() != spriteId || !item->isRotatable() ||
2515 item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) {
2516 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
2517 return;
2518 }
2519
2520 if (pos.x != 0xFFFF &&
2521 !Position::areInRange<1, 1, 0>(pos, player->getPosition())) {
2522 std::list<Direction> listDir;
2523 if (player->getPathTo(pos, listDir, 0, 1, true, true)) {
2524 g_dispatcher.addTask(createTask(
2525 std::bind(&Game::playerAutoWalk, this, player->getID(), listDir)));
2526
2527 SchedulerTask *task = createSchedulerTask(
2528 400, std::bind(&Game::playerRotateItem, this, playerId, pos, stackPos,
2529 spriteId));
2530 player->setNextWalkActionTask(task);
2531 } else {
2532 player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
2533 }
2534 return;
2535 }
2536
2537 uint16_t newId = Item::items[item->getID()].rotateTo;
2538 if (newId != 0) {
2539 transformItem(item, newId);
2540 }
2541}
2542
2543void Game::playerWriteItem(uint32_t playerId, uint32_t windowTextId,
2544 const std::string &text) {
2545 Player *player = getPlayerByID(playerId);
2546 if (!player) {
2547 return;
2548 }
2549
2550 uint16_t maxTextLength = 0;
2551 uint32_t internalWindowTextId = 0;
2552
2553 Item *writeItem = player->getWriteItem(internalWindowTextId, maxTextLength);
2554 if (text.length() > maxTextLength || windowTextId != internalWindowTextId) {
2555 return;
2556 }
2557
2558 if (!writeItem || writeItem->isRemoved()) {
2559 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
2560 return;
2561 }
2562
2563 Cylinder *topParent = writeItem->getTopParent();
2564
2565 Player *owner = dynamic_cast<Player *>(topParent);
2566 if (owner && owner != player) {
2567 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
2568 return;
2569 }
2570
2571 if (!Position::areInRange<1, 1, 0>(writeItem->getPosition(),
2572 player->getPosition())) {
2573 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
2574 return;
2575 }
2576
2577 for (auto creatureEvent :
2578 player->getCreatureEvents(CREATURE_EVENT_TEXTEDIT)) {
2579 if (!creatureEvent->executeTextEdit(player, writeItem, text)) {
2580 player->setWriteItem(nullptr);
2581 return;
2582 }
2583 }
2584
2585 if (!text.empty()) {
2586 if (writeItem->getText() != text) {
2587 writeItem->setText(text);
2588 writeItem->setWriter(player->getName());
2589 writeItem->setDate(time(nullptr));
2590 }
2591 } else {
2592 writeItem->resetText();
2593 writeItem->resetWriter();
2594 writeItem->resetDate();
2595 }
2596
2597 uint16_t newId = Item::items[writeItem->getID()].writeOnceItemId;
2598 if (newId != 0) {
2599 transformItem(writeItem, newId);
2600 }
2601
2602 player->setWriteItem(nullptr);
2603}
2604
2605void Game::playerBrowseField(uint32_t playerId, const Position &pos) {
2606 Player *player = getPlayerByID(playerId);
2607 if (!player) {
2608 return;
2609 }
2610
2611 const Position &playerPos = player->getPosition();
2612 if (playerPos.z != pos.z) {
2613 player->sendCancelMessage(playerPos.z > pos.z
2614 ? RETURNVALUE_FIRSTGOUPSTAIRS
2615 : RETURNVALUE_FIRSTGODOWNSTAIRS);
2616 return;
2617 }
2618
2619 if (!Position::areInRange<1, 1>(playerPos, pos)) {
2620 std::list<Direction> listDir;
2621 if (player->getPathTo(pos, listDir, 0, 1, true, true)) {
2622 g_dispatcher.addTask(createTask(
2623 std::bind(&Game::playerAutoWalk, this, player->getID(), listDir)));
2624 SchedulerTask *task = createSchedulerTask(
2625 400, std::bind(&Game::playerBrowseField, this, playerId, pos));
2626 player->setNextWalkActionTask(task);
2627 } else {
2628 player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
2629 }
2630 return;
2631 }
2632
2633 Tile *tile = map.getTile(pos);
2634 if (!tile) {
2635 return;
2636 }
2637
2638 if (!g_events->eventPlayerOnBrowseField(player, pos)) {
2639 return;
2640 }
2641
2642 Container *container;
2643
2644 auto it = browseFields.find(tile);
2645 if (it == browseFields.end()) {
2646 container = new Container(tile);
2647 container->incrementReferenceCounter();
2648 browseFields[tile] = container;
2649 g_scheduler.addEvent(
2650 createSchedulerTask(30000, std::bind(&Game::decreaseBrowseFieldRef,
2651 this, tile->getPosition())));
2652 } else {
2653 container = it->second;
2654 }
2655
2656 uint8_t dummyContainerId = 0xF - ((pos.x % 3) * 3 + (pos.y % 3));
2657 Container *openContainer = player->getContainerByID(dummyContainerId);
2658 if (openContainer) {
2659 player->onCloseContainer(openContainer);
2660 player->closeContainer(dummyContainerId);
2661 } else {
2662 player->addContainer(dummyContainerId, container);
2663 player->sendContainer(dummyContainerId, container, false, 0);
2664 }
2665}
2666
2667void Game::playerSeekInContainer(uint32_t playerId, uint8_t containerId,
2668 uint16_t index) {
2669 Player *player = getPlayerByID(playerId);
2670 if (!player) {
2671 return;
2672 }
2673
2674 Container *container = player->getContainerByID(containerId);
2675 if (!container || !container->hasPagination()) {
2676 return;
2677 }
2678
2679 if ((index % container->capacity()) != 0 || index >= container->size()) {
2680 return;
2681 }
2682
2683 player->setContainerIndex(containerId, index);
2684 player->sendContainer(containerId, container, container->hasParent(), index);
2685}
2686
2687void Game::playerUpdateHouseWindow(uint32_t playerId, uint8_t listId,
2688 uint32_t windowTextId,
2689 const std::string &text) {
2690 Player *player = getPlayerByID(playerId);
2691 if (!player) {
2692 return;
2693 }
2694
2695 uint32_t internalWindowTextId;
2696 uint32_t internalListId;
2697
2698 House *house = player->getEditHouse(internalWindowTextId, internalListId);
2699 if (house && house->canEditAccessList(internalListId, player) &&
2700 internalWindowTextId == windowTextId && listId == 0) {
2701 house->setAccessList(internalListId, text);
2702 }
2703
2704 player->setEditHouse(nullptr);
2705}
2706
2707void Game::playerWrapItem(uint32_t playerId, const Position &position,
2708 uint8_t stackPos, const uint16_t spriteId) {
2709 Player *player = getPlayerByID(playerId);
2710 if (!player) {
2711 return;
2712 }
2713
2714 Thing *thing =
2715 internalGetThing(player, position, stackPos, 0, STACKPOS_TOPDOWN_ITEM);
2716 if (!thing) {
2717 return;
2718 }
2719
2720 Item *item = thing->getItem();
2721 if (!item || item->getClientID() != spriteId ||
2722 !item->hasAttribute(ITEM_ATTRIBUTE_WRAPID) ||
2723 item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) {
2724 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
2725 return;
2726 }
2727
2728 if (position.x != 0xFFFF &&
2729 !Position::areInRange<1, 1, 0>(position, player->getPosition())) {
2730 std::list<Direction> listDir;
2731 if (player->getPathTo(position, listDir, 0, 1, true, true)) {
2732 g_dispatcher.addTask(createTask(
2733 std::bind(&Game::playerAutoWalk, this, player->getID(), listDir)));
2734
2735 SchedulerTask *task = createSchedulerTask(
2736 400, std::bind(&Game::playerWrapItem, this, playerId, position,
2737 stackPos, spriteId));
2738 player->setNextWalkActionTask(task);
2739 } else {
2740 player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
2741 }
2742 return;
2743 }
2744
2745 g_events->eventPlayerOnWrapItem(player, item);
2746}
2747
2748void Game::playerRequestTrade(uint32_t playerId, const Position &pos,
2749 uint8_t stackPos, uint32_t tradePlayerId,
2750 uint16_t spriteId) {
2751 Player *player = getPlayerByID(playerId);
2752 if (!player) {
2753 return;
2754 }
2755
2756 Player *tradePartner = getPlayerByID(tradePlayerId);
2757 if (!tradePartner || tradePartner == player) {
2758 player->sendTextMessage(MESSAGE_INFO_DESCR, "Sorry, not possible.");
2759 return;
2760 }
2761
2762 if (!Position::areInRange<2, 2, 0>(tradePartner->getPosition(),
2763 player->getPosition())) {
2764 std::ostringstream ss;
2765 ss << tradePartner->getName() << " tells you to move closer.";
2766 player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
2767 return;
2768 }
2769
2770 if (!canThrowObjectTo(tradePartner->getPosition(), player->getPosition())) {
2771 player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE);
2772 return;
2773 }
2774
2775 Thing *tradeThing =
2776 internalGetThing(player, pos, stackPos, 0, STACKPOS_TOPDOWN_ITEM);
2777 if (!tradeThing) {
2778 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
2779 return;
2780 }
2781
2782 Item *tradeItem = tradeThing->getItem();
2783 if (tradeItem->getClientID() != spriteId || !tradeItem->isPickupable() ||
2784 tradeItem->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) {
2785 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
2786 return;
2787 }
2788
2789 const Position &playerPosition = player->getPosition();
2790 const Position &tradeItemPosition = tradeItem->getPosition();
2791 if (playerPosition.z != tradeItemPosition.z) {
2792 player->sendCancelMessage(playerPosition.z > tradeItemPosition.z
2793 ? RETURNVALUE_FIRSTGOUPSTAIRS
2794 : RETURNVALUE_FIRSTGODOWNSTAIRS);
2795 return;
2796 }
2797
2798 if (!Position::areInRange<1, 1>(tradeItemPosition, playerPosition)) {
2799 std::list<Direction> listDir;
2800 if (player->getPathTo(pos, listDir, 0, 1, true, true)) {
2801 g_dispatcher.addTask(createTask(
2802 std::bind(&Game::playerAutoWalk, this, player->getID(), listDir)));
2803
2804 SchedulerTask *task = createSchedulerTask(
2805 400, std::bind(&Game::playerRequestTrade, this, playerId, pos,
2806 stackPos, tradePlayerId, spriteId));
2807 player->setNextWalkActionTask(task);
2808 } else {
2809 player->sendCancelMessage(RETURNVALUE_THEREISNOWAY);
2810 }
2811 return;
2812 }
2813
2814 Container *tradeItemContainer = tradeItem->getContainer();
2815 if (tradeItemContainer) {
2816 for (const auto &it : tradeItems) {
2817 Item *item = it.first;
2818 if (tradeItem == item) {
2819 player->sendTextMessage(MESSAGE_INFO_DESCR,
2820 "This item is already being traded.");
2821 return;
2822 }
2823
2824 if (tradeItemContainer->isHoldingItem(item)) {
2825 player->sendTextMessage(MESSAGE_INFO_DESCR,
2826 "This item is already being traded.");
2827 return;
2828 }
2829
2830 Container *container = item->getContainer();
2831 if (container && container->isHoldingItem(tradeItem)) {
2832 player->sendTextMessage(MESSAGE_INFO_DESCR,
2833 "This item is already being traded.");
2834 return;
2835 }
2836 }
2837 } else {
2838 for (const auto &it : tradeItems) {
2839 Item *item = it.first;
2840 if (tradeItem == item) {
2841 player->sendTextMessage(MESSAGE_INFO_DESCR,
2842 "This item is already being traded.");
2843 return;
2844 }
2845
2846 Container *container = item->getContainer();
2847 if (container && container->isHoldingItem(tradeItem)) {
2848 player->sendTextMessage(MESSAGE_INFO_DESCR,
2849 "This item is already being traded.");
2850 return;
2851 }
2852 }
2853 }
2854
2855 Container *tradeContainer = tradeItem->getContainer();
2856 if (tradeContainer && tradeContainer->getItemHoldingCount() + 1 > 100) {
2857 player->sendTextMessage(MESSAGE_INFO_DESCR,
2858 "You can not trade more than 100 items.");
2859 return;
2860 }
2861
2862 if (!g_events->eventPlayerOnTradeRequest(player, tradePartner, tradeItem)) {
2863 return;
2864 }
2865
2866 internalStartTrade(player, tradePartner, tradeItem);
2867}
2868
2869bool Game::internalStartTrade(Player *player, Player *tradePartner,
2870 Item *tradeItem) {
2871 if (player->tradeState != TRADE_NONE &&
2872 !(player->tradeState == TRADE_ACKNOWLEDGE &&
2873 player->tradePartner == tradePartner)) {
2874 player->sendCancelMessage(RETURNVALUE_YOUAREALREADYTRADING);
2875 return false;
2876 } else if (tradePartner->tradeState != TRADE_NONE &&
2877 tradePartner->tradePartner != player) {
2878 player->sendCancelMessage(RETURNVALUE_THISPLAYERISALREADYTRADING);
2879 return false;
2880 }
2881
2882 player->tradePartner = tradePartner;
2883 player->tradeItem = tradeItem;
2884 player->tradeState = TRADE_INITIATED;
2885 tradeItem->incrementReferenceCounter();
2886 tradeItems[tradeItem] = player->getID();
2887
2888 player->sendTradeItemRequest(player->getName(), tradeItem, true);
2889
2890 if (tradePartner->tradeState == TRADE_NONE) {
2891 std::ostringstream ss;
2892 ss << player->getName() << " wants to trade with you.";
2893 tradePartner->sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str());
2894 tradePartner->tradeState = TRADE_ACKNOWLEDGE;
2895 tradePartner->tradePartner = player;
2896 } else {
2897 Item *counterOfferItem = tradePartner->tradeItem;
2898 player->sendTradeItemRequest(tradePartner->getName(), counterOfferItem,
2899 false);
2900 tradePartner->sendTradeItemRequest(player->getName(), tradeItem, false);
2901 }
2902
2903 return true;
2904}
2905
2906void Game::playerAcceptTrade(uint32_t playerId) {
2907 Player *player = getPlayerByID(playerId);
2908 if (!player) {
2909 return;
2910 }
2911
2912 if (!(player->getTradeState() == TRADE_ACKNOWLEDGE ||
2913 player->getTradeState() == TRADE_INITIATED)) {
2914 return;
2915 }
2916
2917 Player *tradePartner = player->tradePartner;
2918 if (!tradePartner) {
2919 return;
2920 }
2921
2922 if (!canThrowObjectTo(tradePartner->getPosition(), player->getPosition())) {
2923 player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE);
2924 return;
2925 }
2926
2927 player->setTradeState(TRADE_ACCEPT);
2928
2929 if (tradePartner->getTradeState() == TRADE_ACCEPT) {
2930 Item *playerTradeItem = player->tradeItem;
2931 Item *partnerTradeItem = tradePartner->tradeItem;
2932
2933 if (!g_events->eventPlayerOnTradeAccept(
2934 player, tradePartner, playerTradeItem, partnerTradeItem)) {
2935 internalCloseTrade(player);
2936 return;
2937 }
2938
2939 player->setTradeState(TRADE_TRANSFER);
2940 tradePartner->setTradeState(TRADE_TRANSFER);
2941
2942 auto it = tradeItems.find(playerTradeItem);
2943 if (it != tradeItems.end()) {
2944 ReleaseItem(it->first);
2945 tradeItems.erase(it);
2946 }
2947
2948 it = tradeItems.find(partnerTradeItem);
2949 if (it != tradeItems.end()) {
2950 ReleaseItem(it->first);
2951 tradeItems.erase(it);
2952 }
2953
2954 bool isSuccess = false;
2955
2956 ReturnValue tradePartnerRet = RETURNVALUE_NOERROR;
2957 ReturnValue playerRet = RETURNVALUE_NOERROR;
2958
2959 // if player is trying to trade its own backpack
2960 if (tradePartner->getInventoryItem(CONST_SLOT_BACKPACK) ==
2961 partnerTradeItem) {
2962 tradePartnerRet = (tradePartner->getInventoryItem(
2963 getSlotType(Item::items[playerTradeItem->getID()]))
2964 ? RETURNVALUE_NOTENOUGHROOM
2965 : RETURNVALUE_NOERROR);
2966 }
2967
2968 if (player->getInventoryItem(CONST_SLOT_BACKPACK) == playerTradeItem) {
2969 playerRet = (player->getInventoryItem(
2970 getSlotType(Item::items[partnerTradeItem->getID()]))
2971 ? RETURNVALUE_NOTENOUGHROOM
2972 : RETURNVALUE_NOERROR);
2973 }
2974
2975 if (tradePartnerRet == RETURNVALUE_NOERROR &&
2976 playerRet == RETURNVALUE_NOERROR) {
2977 tradePartnerRet = internalAddItem(tradePartner, playerTradeItem,
2978 INDEX_WHEREEVER, 0, true);
2979 playerRet =
2980 internalAddItem(player, partnerTradeItem, INDEX_WHEREEVER, 0, true);
2981 if (tradePartnerRet == RETURNVALUE_NOERROR &&
2982 playerRet == RETURNVALUE_NOERROR) {
2983 playerRet = internalRemoveItem(playerTradeItem,
2984 playerTradeItem->getItemCount(), true);
2985 tradePartnerRet = internalRemoveItem(
2986 partnerTradeItem, partnerTradeItem->getItemCount(), true);
2987 if (tradePartnerRet == RETURNVALUE_NOERROR &&
2988 playerRet == RETURNVALUE_NOERROR) {
2989 tradePartnerRet = internalMoveItem(
2990 playerTradeItem->getParent(), tradePartner, INDEX_WHEREEVER,
2991 playerTradeItem, playerTradeItem->getItemCount(), nullptr,
2992 FLAG_IGNOREAUTOSTACK, nullptr, partnerTradeItem);
2993 if (tradePartnerRet == RETURNVALUE_NOERROR) {
2994 internalMoveItem(partnerTradeItem->getParent(), player,
2995 INDEX_WHEREEVER, partnerTradeItem,
2996 partnerTradeItem->getItemCount(), nullptr,
2997 FLAG_IGNOREAUTOSTACK);
2998 playerTradeItem->onTradeEvent(ON_TRADE_TRANSFER, tradePartner);
2999 partnerTradeItem->onTradeEvent(ON_TRADE_TRANSFER, player);
3000 isSuccess = true;
3001 }
3002 }
3003 }
3004 }
3005
3006 if (!isSuccess) {
3007 std::string errorDescription;
3008
3009 if (tradePartner->tradeItem) {
3010 errorDescription =
3011 getTradeErrorDescription(tradePartnerRet, playerTradeItem);
3012 tradePartner->sendTextMessage(MESSAGE_EVENT_ADVANCE, errorDescription);
3013 tradePartner->tradeItem->onTradeEvent(ON_TRADE_CANCEL, tradePartner);
3014 }
3015
3016 if (player->tradeItem) {
3017 errorDescription =
3018 getTradeErrorDescription(playerRet, partnerTradeItem);
3019 player->sendTextMessage(MESSAGE_EVENT_ADVANCE, errorDescription);
3020 player->tradeItem->onTradeEvent(ON_TRADE_CANCEL, player);
3021 }
3022 }
3023
3024 g_events->eventPlayerOnTradeCompleted(player, tradePartner, playerTradeItem,
3025 partnerTradeItem, isSuccess);
3026
3027 player->setTradeState(TRADE_NONE);
3028 player->tradeItem = nullptr;
3029 player->tradePartner = nullptr;
3030 player->sendTradeClose();
3031
3032 tradePartner->setTradeState(TRADE_NONE);
3033 tradePartner->tradeItem = nullptr;
3034 tradePartner->tradePartner = nullptr;
3035 tradePartner->sendTradeClose();
3036 }
3037}
3038
3039std::string Game::getTradeErrorDescription(ReturnValue ret, Item *item) {
3040 if (item) {
3041 if (ret == RETURNVALUE_NOTENOUGHCAPACITY) {
3042 std::ostringstream ss;
3043 ss << "You do not have enough capacity to carry";
3044
3045 if (item->isStackable() && item->getItemCount() > 1) {
3046 ss << " these objects.";
3047 } else {
3048 ss << " this object.";
3049 }
3050
3051 ss << "\n " << item->getWeightDescription();
3052 return ss.str();
3053 } else if (ret == RETURNVALUE_NOTENOUGHROOM ||
3054 ret == RETURNVALUE_CONTAINERNOTENOUGHROOM) {
3055 std::ostringstream ss;
3056 ss << "You do not have enough room to carry";
3057
3058 if (item->isStackable() && item->getItemCount() > 1) {
3059 ss << " these objects.";
3060 } else {
3061 ss << " this object.";
3062 }
3063
3064 return ss.str();
3065 }
3066 }
3067 return "Trade could not be completed.";
3068}
3069
3070void Game::playerLookInTrade(uint32_t playerId, bool lookAtCounterOffer,
3071 uint8_t index) {
3072 Player *player = getPlayerByID(playerId);
3073 if (!player) {
3074 return;
3075 }
3076
3077 Player *tradePartner = player->tradePartner;
3078 if (!tradePartner) {
3079 return;
3080 }
3081
3082 Item *tradeItem;
3083 if (lookAtCounterOffer) {
3084 tradeItem = tradePartner->getTradeItem();
3085 } else {
3086 tradeItem = player->getTradeItem();
3087 }
3088
3089 if (!tradeItem) {
3090 return;
3091 }
3092
3093 const Position &playerPosition = player->getPosition();
3094 const Position &tradeItemPosition = tradeItem->getPosition();
3095
3096 int32_t lookDistance = std::max<int32_t>(
3097 Position::getDistanceX(playerPosition, tradeItemPosition),
3098 Position::getDistanceY(playerPosition, tradeItemPosition));
3099 if (index == 0) {
3100 g_events->eventPlayerOnLookInTrade(player, tradePartner, tradeItem,
3101 lookDistance);
3102 return;
3103 }
3104
3105 Container *tradeContainer = tradeItem->getContainer();
3106 if (!tradeContainer) {
3107 return;
3108 }
3109
3110 std::vector<const Container *> containers{tradeContainer};
3111 size_t i = 0;
3112 while (i < containers.size()) {
3113 const Container *container = containers[i++];
3114 for (Item *item : container->getItemList()) {
3115 Container *tmpContainer = item->getContainer();
3116 if (tmpContainer) {
3117 containers.push_back(tmpContainer);
3118 }
3119
3120 if (--index == 0) {
3121 g_events->eventPlayerOnLookInTrade(player, tradePartner, item,
3122 lookDistance);
3123 return;
3124 }
3125 }
3126 }
3127}
3128
3129void Game::playerCloseTrade(uint32_t playerId) {
3130 Player *player = getPlayerByID(playerId);
3131 if (!player) {
3132 return;
3133 }
3134
3135 internalCloseTrade(player);
3136}
3137
3138void Game::internalCloseTrade(Player *player) {
3139 Player *tradePartner = player->tradePartner;
3140 if ((tradePartner && tradePartner->getTradeState() == TRADE_TRANSFER) ||
3141 player->getTradeState() == TRADE_TRANSFER) {
3142 return;
3143 }
3144
3145 if (player->getTradeItem()) {
3146 auto it = tradeItems.find(player->getTradeItem());
3147 if (it != tradeItems.end()) {
3148 ReleaseItem(it->first);
3149 tradeItems.erase(it);
3150 }
3151
3152 player->tradeItem->onTradeEvent(ON_TRADE_CANCEL, player);
3153 player->tradeItem = nullptr;
3154 }
3155
3156 player->setTradeState(TRADE_NONE);
3157 player->tradePartner = nullptr;
3158
3159 player->sendTextMessage(MESSAGE_STATUS_SMALL, "Trade cancelled.");
3160 player->sendTradeClose();
3161
3162 if (tradePartner) {
3163 if (tradePartner->getTradeItem()) {
3164 auto it = tradeItems.find(tradePartner->getTradeItem());
3165 if (it != tradeItems.end()) {
3166 ReleaseItem(it->first);
3167 tradeItems.erase(it);
3168 }
3169
3170 tradePartner->tradeItem->onTradeEvent(ON_TRADE_CANCEL, tradePartner);
3171 tradePartner->tradeItem = nullptr;
3172 }
3173
3174 tradePartner->setTradeState(TRADE_NONE);
3175 tradePartner->tradePartner = nullptr;
3176
3177 tradePartner->sendTextMessage(MESSAGE_STATUS_SMALL, "Trade cancelled.");
3178 tradePartner->sendTradeClose();
3179 }
3180}
3181
3182void Game::playerPurchaseItem(uint32_t playerId, uint16_t spriteId,
3183 uint8_t count, uint8_t amount,
3184 bool ignoreCap /* = false*/,
3185 bool inBackpacks /* = false*/) {
3186 if (amount == 0 || amount > 100) {
3187 return;
3188 }
3189
3190 Player *player = getPlayerByID(playerId);
3191 if (!player) {
3192 return;
3193 }
3194
3195 int32_t onBuy, onSell;
3196
3197 Npc *merchant = player->getShopOwner(onBuy, onSell);
3198 if (!merchant) {
3199 return;
3200 }
3201
3202 const ItemType &it = Item::items.getItemIdByClientId(spriteId);
3203 if (it.id == 0) {
3204 return;
3205 }
3206
3207 uint8_t subType;
3208 if (it.isSplash() || it.isFluidContainer()) {
3209 subType = clientFluidToServer(count);
3210 } else {
3211 subType = count;
3212 }
3213
3214 if (!player->hasShopItemForSale(it.id, subType)) {
3215 return;
3216 }
3217
3218 merchant->onPlayerTrade(player, onBuy, it.id, subType, amount, ignoreCap,
3219 inBackpacks);
3220}
3221
3222void Game::playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count,
3223 uint8_t amount, bool ignoreEquipped) {
3224 if (amount == 0 || amount > 100) {
3225 return;
3226 }
3227
3228 Player *player = getPlayerByID(playerId);
3229 if (!player) {
3230 return;
3231 }
3232
3233 int32_t onBuy, onSell;
3234
3235 Npc *merchant = player->getShopOwner(onBuy, onSell);
3236 if (!merchant) {
3237 return;
3238 }
3239
3240 const ItemType &it = Item::items.getItemIdByClientId(spriteId);
3241 if (it.id == 0) {
3242 return;
3243 }
3244
3245 uint8_t subType;
3246 if (it.isSplash() || it.isFluidContainer()) {
3247 subType = clientFluidToServer(count);
3248 } else {
3249 subType = count;
3250 }
3251
3252 merchant->onPlayerTrade(player, onSell, it.id, subType, amount,
3253 ignoreEquipped);
3254}
3255
3256void Game::playerCloseShop(uint32_t playerId) {
3257 Player *player = getPlayerByID(playerId);
3258 if (!player) {
3259 return;
3260 }
3261
3262 player->closeShopWindow();
3263}
3264
3265void Game::playerLookInShop(uint32_t playerId, uint16_t spriteId,
3266 uint8_t count) {
3267 Player *player = getPlayerByID(playerId);
3268 if (!player) {
3269 return;
3270 }
3271
3272 int32_t onBuy, onSell;
3273
3274 Npc *merchant = player->getShopOwner(onBuy, onSell);
3275 if (!merchant) {
3276 return;
3277 }
3278
3279 const ItemType &it = Item::items.getItemIdByClientId(spriteId);
3280 if (it.id == 0) {
3281 return;
3282 }
3283
3284 int32_t subType;
3285 if (it.isFluidContainer() || it.isSplash()) {
3286 subType = clientFluidToServer(count);
3287 } else {
3288 subType = count;
3289 }
3290
3291 if (!player->hasShopItemForSale(it.id, subType)) {
3292 return;
3293 }
3294
3295 if (!g_events->eventPlayerOnLookInShop(player, &it, subType)) {
3296 return;
3297 }
3298
3299 std::ostringstream ss;
3300 ss << "You see " << Item::getDescription(it, 1, nullptr, subType);
3301 player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
3302}
3303
3304void Game::playerLookAt(uint32_t playerId, const Position &pos,
3305 uint8_t stackPos) {
3306 Player *player = getPlayerByID(playerId);
3307 if (!player) {
3308 return;
3309 }
3310
3311 Thing *thing = internalGetThing(player, pos, stackPos, 0, STACKPOS_LOOK);
3312 if (!thing) {
3313 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
3314 return;
3315 }
3316
3317 Position thingPos = thing->getPosition();
3318 if (!player->canSee(thingPos)) {
3319 player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
3320 return;
3321 }
3322
3323 Position playerPos = player->getPosition();
3324
3325 int32_t lookDistance;
3326 if (thing != player) {
3327 lookDistance =
3328 std::max<int32_t>(Position::getDistanceX(playerPos, thingPos),
3329 Position::getDistanceY(playerPos, thingPos));
3330 if (playerPos.z != thingPos.z) {
3331 lookDistance += 15;
3332 }
3333 } else {
3334 lookDistance = -1;
3335 }
3336
3337 g_events->eventPlayerOnLook(player, pos, thing, stackPos, lookDistance);
3338}
3339
3340void Game::playerLookInBattleList(uint32_t playerId, uint32_t creatureId) {
3341 Player *player = getPlayerByID(playerId);
3342 if (!player) {
3343 return;
3344 }
3345
3346 Creature *creature = getCreatureByID(creatureId);
3347 if (!creature) {
3348 return;
3349 }
3350
3351 if (!player->canSeeCreature(creature)) {
3352 return;
3353 }
3354
3355 const Position &creaturePos = creature->getPosition();
3356 if (!player->canSee(creaturePos)) {
3357 return;
3358 }
3359
3360 int32_t lookDistance;
3361 if (creature != player) {
3362 const Position &playerPos = player->getPosition();
3363 lookDistance =
3364 std::max<int32_t>(Position::getDistanceX(playerPos, creaturePos),
3365 Position::getDistanceY(playerPos, creaturePos));
3366 if (playerPos.z != creaturePos.z) {
3367 lookDistance += 15;
3368 }
3369 } else {
3370 lookDistance = -1;
3371 }
3372
3373 g_events->eventPlayerOnLookInBattleList(player, creature, lookDistance);
3374}
3375
3376void Game::playerCancelAttackAndFollow(uint32_t playerId) {
3377 Player *player = getPlayerByID(playerId);
3378 if (!player) {
3379 return;
3380 }
3381
3382 playerSetAttackedCreature(playerId, 0);
3383 playerFollowCreature(playerId, 0);
3384 player->stopWalk();
3385}
3386
3387void Game::playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId) {
3388 Player *player = getPlayerByID(playerId);
3389 if (!player) {
3390 return;
3391 }
3392
3393 if (player->getAttackedCreature() && creatureId == 0) {
3394 player->setAttackedCreature(nullptr);
3395 player->sendCancelTarget();
3396 return;
3397 }
3398
3399 Creature *attackCreature = getCreatureByID(creatureId);
3400 if (!attackCreature) {
3401 player->setAttackedCreature(nullptr);
3402 player->sendCancelTarget();
3403 return;
3404 }
3405
3406 ReturnValue ret = Combat::canTargetCreature(player, attackCreature);
3407 if (ret != RETURNVALUE_NOERROR) {
3408 player->sendCancelMessage(ret);
3409 player->sendCancelTarget();
3410 player->setAttackedCreature(nullptr);
3411 return;
3412 }
3413
3414 player->setAttackedCreature(attackCreature);
3415
3416 // Rotate towards target immediately
3417 const Position &myPos = player->getPosition();
3418 const Position &targetPos = attackCreature->getPosition();
3419 int32_t diffX = targetPos.x - myPos.x;
3420 int32_t diffY = targetPos.y - myPos.y;
3421 Direction dir = DIRECTION_NONE;
3422 int32_t absX = std::abs(diffX);
3423 int32_t absY = std::abs(diffY);
3424 if (absX > 0 || absY > 0) {
3425 if (absX > absY * 2) {
3426 dir = (diffX > 0) ? DIRECTION_EAST : DIRECTION_WEST;
3427 } else if (absY > absX * 2) {
3428 dir = (diffY > 0) ? DIRECTION_SOUTH : DIRECTION_NORTH;
3429 } else {
3430 if (diffX > 0 && diffY < 0)
3431 dir = DIRECTION_NORTHEAST;
3432 else if (diffX > 0 && diffY > 0)
3433 dir = DIRECTION_SOUTHEAST;
3434 else if (diffX < 0 && diffY < 0)
3435 dir = DIRECTION_NORTHWEST;
3436 else if (diffX < 0 && diffY > 0)
3437 dir = DIRECTION_SOUTHWEST;
3438 }
3439 }
3440
3441 if (dir != DIRECTION_NONE) {
3442 internalCreatureTurn(player, dir);
3443 }
3444
3445 g_dispatcher.addTask(
3446 createTask(std::bind(&Game::updateCreatureWalk, this, player->getID())));
3447}
3448
3449void Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId) {
3450 Player *player = getPlayerByID(playerId);
3451 if (!player) {
3452 return;
3453 }
3454
3455 player->setAttackedCreature(nullptr);
3456 g_dispatcher.addTask(
3457 createTask(std::bind(&Game::updateCreatureWalk, this, player->getID())));
3458 player->setFollowCreature(getCreatureByID(creatureId));
3459}
3460
3461void Game::playerSetFightModes(uint32_t playerId, fightMode_t fightMode,
3462 bool chaseMode, bool secureMode) {
3463 Player *player = getPlayerByID(playerId);
3464 if (!player) {
3465 return;
3466 }
3467
3468 player->setFightMode(fightMode);
3469 player->setChaseMode(chaseMode);
3470 player->setSecureMode(secureMode);
3471}
3472
3473void Game::playerRequestAddVip(uint32_t playerId, const std::string &name) {
3474 if (name.length() > 20) {
3475 return;
3476 }
3477
3478 Player *player = getPlayerByID(playerId);
3479 if (!player) {
3480 return;
3481 }
3482
3483 Player *vipPlayer = getPlayerByName(name);
3484 if (!vipPlayer) {
3485 uint32_t guid;
3486 bool specialVip;
3487 std::string formattedName = name;
3488 if (!IOLoginData::getGuidByNameEx(guid, specialVip, formattedName)) {
3489 player->sendTextMessage(MESSAGE_STATUS_SMALL,
3490 "A player with this name does not exist.");
3491 return;
3492 }
3493
3494 if (specialVip && !player->hasFlag(PlayerFlag_SpecialVIP)) {
3495 player->sendTextMessage(MESSAGE_STATUS_SMALL,
3496 "You can not add this player.");
3497 return;
3498 }
3499
3500 player->addVIP(guid, formattedName, VIPSTATUS_OFFLINE);
3501 } else {
3502 if (vipPlayer->hasFlag(PlayerFlag_SpecialVIP) &&
3503 !player->hasFlag(PlayerFlag_SpecialVIP)) {
3504 player->sendTextMessage(MESSAGE_STATUS_SMALL,
3505 "You can not add this player.");
3506 return;
3507 }
3508
3509 if (!vipPlayer->isInGhostMode() || player->isAccessPlayer()) {
3510 player->addVIP(vipPlayer->getGUID(), vipPlayer->getName(),
3511 VIPSTATUS_ONLINE);
3512 } else {
3513 player->addVIP(vipPlayer->getGUID(), vipPlayer->getName(),
3514 VIPSTATUS_OFFLINE);
3515 }
3516 }
3517}
3518
3519void Game::playerRequestRemoveVip(uint32_t playerId, uint32_t guid) {
3520 Player *player = getPlayerByID(playerId);
3521 if (!player) {
3522 return;
3523 }
3524
3525 player->removeVIP(guid);
3526}
3527
3528void Game::playerRequestEditVip(uint32_t playerId, uint32_t guid,
3529 const std::string &description, uint32_t icon,
3530 bool notify) {
3531 Player *player = getPlayerByID(playerId);
3532 if (!player) {
3533 return;
3534 }
3535
3536 player->editVIP(guid, description, icon, notify);
3537}
3538
3539void Game::playerTurn(uint32_t playerId, Direction dir) {
3540 Player *player = getPlayerByID(playerId);
3541 if (!player) {
3542 return;
3543 }
3544
3545 if (!g_events->eventPlayerOnTurn(player, dir)) {
3546 return;
3547 }
3548
3549 player->resetIdleTime();
3550 internalCreatureTurn(player, dir);
3551}
3552
3553void Game::playerRequestOutfit(uint32_t playerId) {
3554 if (!g_config.getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) {
3555 return;
3556 }
3557
3558 Player *player = getPlayerByID(playerId);
3559 if (!player) {
3560 return;
3561 }
3562
3563 player->sendOutfitWindow();
3564}
3565
3566void Game::playerToggleOutfitExtension(uint32_t playerId, int mount, int, int,
3567 int, int) {
3568 Player *player = getPlayerByID(playerId);
3569 if (!player) {
3570 return;
3571 }
3572
3573 if (mount != -1)
3574 player->toggleMount(mount == 1);
3575}
3576
3577void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit) {
3578 if (!g_config.getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) {
3579 return;
3580 }
3581
3582 Player *player = getPlayerByID(playerId);
3583 if (!player) {
3584 return;
3585 }
3586
3587 const Outfit *playerOutfit = Outfits::getInstance().getOutfitByLookType(
3588 player->getSex(), outfit.lookType);
3589 if (!playerOutfit) {
3590 outfit.lookMount = 0;
3591 outfit.lookWings = 0;
3592 outfit.lookAura = 0;
3593 outfit.lookShader = 0;
3594 outfit.lookRuneEffect = 0;
3595 }
3596
3597 if (outfit.lookMount != 0) {
3598 Mount *mount = mounts.getMountByClientID(outfit.lookMount);
3599 if (!mount) {
3600 return;
3601 }
3602
3603 if (!player->hasMount(mount)) {
3604 return;
3605 }
3606
3607 if (player->isMounted()) {
3608 Mount *prevMount = mounts.getMountByID(player->getCurrentMount());
3609 if (prevMount) {
3610 changeSpeed(player, mount->speed - prevMount->speed);
3611 }
3612
3613 player->setCurrentMount(mount->id);
3614 } else {
3615 player->setCurrentMount(mount->id);
3616 outfit.lookMount = 0;
3617 }
3618 } else if (player->isMounted()) {
3619 player->dismount();
3620 }
3621
3622 if (outfit.lookWings != 0) {
3623 Wing *wing = wings.getWingByClientID(outfit.lookWings);
3624 if (!wing) {
3625 return;
3626 }
3627
3628 if (!player->hasWing(wing)) {
3629 return;
3630 }
3631 }
3632
3633 if (outfit.lookAura != 0) {
3634 Aura *aura = auras.getAuraByClientID(outfit.lookAura);
3635 if (!aura) {
3636 return;
3637 }
3638
3639 if (!player->hasAura(aura)) {
3640 return;
3641 }
3642 }
3643
3644 if (outfit.lookShader) {
3645 Shader *shader = shaders.getShaderByID(outfit.lookShader);
3646 if (!shader) {
3647 return;
3648 }
3649
3650 if (!player->hasShader(shader)) {
3651 return;
3652 }
3653 }
3654
3655 if (outfit.lookRuneEffect) {
3656 RuneEffect *runeEffect = runeEffects.getRuneEffectByID(outfit.lookRuneEffect);
3657 if (!runeEffect) {
3658 return;
3659 }
3660
3661 if (!player->hasRuneEffect(runeEffect)) {
3662 return;
3663 }
3664 }
3665
3666 if (player->canWear(outfit.lookType, outfit.lookAddons)) {
3667 player->defaultOutfit = outfit;
3668
3669 // Reset the stored attackOutfit so getAttackOutfitLookType() will perform
3670 // a fresh lookup in Outfits.xml for the new outfit's attack type.
3671 // Without this, the old outfit's attackType would still be returned even
3672 // after a legitimate outfit change (e.g. changing from lookType 1544 to
3673 // 7415 would still show attackType 1543 instead of 7416).
3674 player->setAttackOutfitLookType(0);
3675
3676 if (player->hasCondition(CONDITION_OUTFIT)) {
3677 return;
3678 }
3679
3680 internalCreatureChangeOutfit(player, outfit);
3681 }
3682}
3683
3684void Game::playerShowQuestLog(uint32_t playerId) {
3685 Player *player = getPlayerByID(playerId);
3686 if (!player) {
3687 return;
3688 }
3689
3690 player->sendQuestLog();
3691}
3692
3693void Game::playerShowQuestLine(uint32_t playerId, uint16_t questId) {
3694 Player *player = getPlayerByID(playerId);
3695 if (!player) {
3696 return;
3697 }
3698
3699 Quest *quest = quests.getQuestByID(questId);
3700 if (!quest) {
3701 return;
3702 }
3703
3704 player->sendQuestLine(quest);
3705}
3706
3707void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type,
3708 const std::string &receiver, const std::string &text) {
3709 Player *player = getPlayerByID(playerId);
3710 if (!player) {
3711 return;
3712 }
3713
3714 player->resetIdleTime();
3715
3716 if (playerSaySpell(player, type, text)) {
3717 return;
3718 }
3719
3720 uint32_t muteTime = player->isMuted();
3721 if (muteTime > 0) {
3722 std::ostringstream ss;
3723 ss << "You are still muted for " << muteTime << " seconds.";
3724 player->sendTextMessage(MESSAGE_STATUS_SMALL, ss.str());
3725 return;
3726 }
3727
3728 if (!text.empty() && text.front() == '/' && player->isAccessPlayer()) {
3729 return;
3730 }
3731
3732 if (type != TALKTYPE_PRIVATE_PN) {
3733 player->removeMessageBuffer();
3734 }
3735
3736 switch (type) {
3737 case TALKTYPE_SAY:
3738 internalCreatureSay(player, TALKTYPE_SAY, text, false);
3739 break;
3740
3741 case TALKTYPE_WHISPER:
3742 playerWhisper(player, text);
3743 break;
3744
3745 case TALKTYPE_YELL:
3746 playerYell(player, text);
3747 break;
3748
3749 case TALKTYPE_PRIVATE_TO:
3750 case TALKTYPE_PRIVATE_RED_TO:
3751 playerSpeakTo(player, type, receiver, text);
3752 break;
3753
3754 case TALKTYPE_CHANNEL_O:
3755 case TALKTYPE_CHANNEL_Y:
3756 case TALKTYPE_CHANNEL_R1:
3757 g_chat->talkToChannel(*player, type, text, channelId);
3758 break;
3759
3760 case TALKTYPE_PRIVATE_PN:
3761 playerSpeakToNpc(player, text);
3762 break;
3763
3764 case TALKTYPE_BROADCAST:
3765 playerBroadcastMessage(player, text);
3766 break;
3767
3768 default:
3769 break;
3770 }
3771}
3772
3773bool Game::playerSaySpell(Player *player, SpeakClasses type,
3774 const std::string &text) {
3775 std::string words = text;
3776
3777 TalkActionResult_t result =
3778 g_talkActions->playerSaySpell(player, type, words);
3779 if (result == TALKACTION_BREAK) {
3780 return true;
3781 }
3782
3783 result = g_spells->playerSaySpell(player, words);
3784 if (result == TALKACTION_BREAK) {
3785 if (!g_config.getBoolean(ConfigManager::EMOTE_SPELLS)) {
3786 return true;
3787 } else {
3788 return true;
3789 }
3790
3791 } else if (result == TALKACTION_FAILED) {
3792 return true;
3793 }
3794
3795 return false;
3796}
3797
3798void Game::playerWhisper(Player *player, const std::string &text) {
3799 SpectatorVec spectators;
3800 map.getSpectators(spectators, player->getPosition(), false, false,
3801 Map::maxClientViewportX, Map::maxClientViewportX,
3802 Map::maxClientViewportY, Map::maxClientViewportY);
3803
3804 // send to client
3805 for (Creature *spectator : spectators) {
3806 if (Player *spectatorPlayer = spectator->getPlayer()) {
3807 if (!Position::areInRange<1, 1>(player->getPosition(),
3808 spectatorPlayer->getPosition())) {
3809 spectatorPlayer->sendCreatureSay(player, TALKTYPE_WHISPER, "pspsps");
3810 } else {
3811 spectatorPlayer->sendCreatureSay(player, TALKTYPE_WHISPER, text);
3812 }
3813 }
3814 }
3815
3816 // event method
3817 for (Creature *spectator : spectators) {
3818 spectator->onCreatureSay(player, TALKTYPE_WHISPER, text);
3819 }
3820}
3821
3822bool Game::playerYell(Player *player, const std::string &text) {
3823 if (player->hasCondition(CONDITION_YELLTICKS)) {
3824 player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
3825 return false;
3826 }
3827
3828 uint32_t minimumLevel = g_config.getNumber(ConfigManager::YELL_MINIMUM_LEVEL);
3829 if (player->getLevel() < minimumLevel) {
3830 std::ostringstream ss;
3831 ss << "You may not yell unless you have reached level " << minimumLevel;
3832 if (g_config.getBoolean(ConfigManager::YELL_ALLOW_PREMIUM)) {
3833 if (player->isPremium()) {
3834 internalCreatureSay(player, TALKTYPE_YELL, asUpperCaseString(text),
3835 false);
3836 return true;
3837 } else {
3838 ss << " or have a premium account";
3839 }
3840 }
3841 ss << ".";
3842 player->sendTextMessage(MESSAGE_STATUS_SMALL, ss.str());
3843 return false;
3844 }
3845
3846 if (player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER) {
3847 Condition *condition = Condition::createCondition(
3848 CONDITIONID_DEFAULT, CONDITION_YELLTICKS, 30000, 0);
3849 player->addCondition(condition);
3850 }
3851
3852 internalCreatureSay(player, TALKTYPE_YELL, asUpperCaseString(text), false);
3853 return true;
3854}
3855
3856bool Game::playerSpeakTo(Player *player, SpeakClasses type,
3857 const std::string &receiver, const std::string &text) {
3858 Player *toPlayer = getPlayerByName(receiver);
3859 if (!toPlayer) {
3860 player->sendTextMessage(MESSAGE_STATUS_SMALL,
3861 "A player with this name is not online.");
3862 return false;
3863 }
3864
3865 if (type == TALKTYPE_PRIVATE_RED_TO &&
3866 (player->hasFlag(PlayerFlag_CanTalkRedPrivate) ||
3867 player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER)) {
3868 type = TALKTYPE_PRIVATE_RED_FROM;
3869 } else {
3870 type = TALKTYPE_PRIVATE_FROM;
3871 }
3872
3873 toPlayer->sendPrivateMessage(player, type, text);
3874 toPlayer->onCreatureSay(player, type, text);
3875
3876 if (toPlayer->isInGhostMode() && !player->isAccessPlayer()) {
3877 player->sendTextMessage(MESSAGE_STATUS_SMALL,
3878 "A player with this name is not online.");
3879 } else {
3880 std::ostringstream ss;
3881 ss << "Message sent to " << toPlayer->getName() << '.';
3882 player->sendTextMessage(MESSAGE_STATUS_SMALL, ss.str());
3883 }
3884 return true;
3885}
3886
3887void Game::playerSpeakToNpc(Player *player, const std::string &text) {
3888 SpectatorVec spectators;
3889 map.getSpectators(spectators, player->getPosition());
3890 for (Creature *spectator : spectators) {
3891 if (spectator->getNpc()) {
3892 spectator->onCreatureSay(player, TALKTYPE_PRIVATE_PN, text);
3893 }
3894 }
3895}
3896
3897//--
3898bool Game::canThrowObjectTo(
3899 const Position &fromPos, const Position &toPos,
3900 bool checkLineOfSight /*= true*/,
3901 int32_t rangex /*= Map::maxClientViewportX*/,
3902 int32_t rangey /*= Map::maxClientViewportY*/) const {
3903 return map.canThrowObjectTo(fromPos, toPos, checkLineOfSight, rangex, rangey);
3904}
3905
3906bool Game::isSightClear(const Position &fromPos, const Position &toPos,
3907 bool floorCheck) const {
3908 return map.isSightClear(fromPos, toPos, floorCheck);
3909}
3910
3911bool Game::internalCreatureTurn(Creature *creature, Direction dir) {
3912 if (creature->getDirection() == dir) {
3913 return false;
3914 }
3915
3916 creature->setDirection(dir);
3917
3918 // send to client
3919 SpectatorVec spectators;
3920 map.getSpectators(spectators, creature->getPosition(), true, true);
3921 for (Creature *spectator : spectators) {
3922 spectator->getPlayer()->sendCreatureTurn(creature);
3923 }
3924 return true;
3925}
3926
3927bool Game::internalCreatureSay(Creature *creature, SpeakClasses type,
3928 const std::string &text, bool ghostMode,
3929 SpectatorVec *spectatorsPtr /* = nullptr*/,
3930 const Position *pos /* = nullptr*/) {
3931 if (text.empty()) {
3932 return false;
3933 }
3934
3935 if (!pos) {
3936 pos = &creature->getPosition();
3937 }
3938
3939 SpectatorVec spectators;
3940
3941 if (!spectatorsPtr || spectatorsPtr->empty()) {
3942 // This somewhat complex construct ensures that the cached SpectatorVec
3943 // is used if available and if it can be used, else a local vector is
3944 // used (hopefully the compiler will optimize away the construction of
3945 // the temporary when it's not used).
3946 if (type != TALKTYPE_YELL && type != TALKTYPE_MONSTER_YELL) {
3947 map.getSpectators(spectators, *pos, false, false, Map::maxClientViewportX,
3948 Map::maxClientViewportX, Map::maxClientViewportY,
3949 Map::maxClientViewportY);
3950 } else {
3951 map.getSpectators(spectators, *pos, true, false, 18, 18, 14, 14);
3952 }
3953 } else {
3954 spectators = (*spectatorsPtr);
3955 }
3956
3957 // send to client
3958 for (Creature *spectator : spectators) {
3959 if (Player *tmpPlayer = spectator->getPlayer()) {
3960 if (!ghostMode || tmpPlayer->canSeeCreature(creature)) {
3961 tmpPlayer->sendCreatureSay(creature, type, text, pos);
3962 }
3963 }
3964 }
3965
3966 // event method
3967 for (Creature *spectator : spectators) {
3968 spectator->onCreatureSay(creature, type, text);
3969 if (creature != spectator) {
3970 g_events->eventCreatureOnHear(spectator, creature, text, type);
3971 }
3972 }
3973 return true;
3974}
3975
3976void Game::checkCreatureWalk(uint32_t creatureId) {
3977 Creature *creature = getCreatureByID(creatureId);
3978 if (creature && creature->getHealth() > 0) {
3979 creature->onWalk();
3980 cleanup();
3981 }
3982}
3983
3984void Game::updateCreatureWalk(uint32_t creatureId) {
3985 Creature *creature = getCreatureByID(creatureId);
3986 if (creature && creature->getHealth() > 0) {
3987 creature->goToFollowCreature();
3988 }
3989}
3990
3991void Game::checkCreatureAttack(uint32_t creatureId) {
3992 Creature *creature = getCreatureByID(creatureId);
3993 if (creature && creature->getHealth() > 0) {
3994 creature->onAttacking(0);
3995 }
3996}
3997
3998void Game::addCreatureCheck(Creature *creature) {
3999 creature->creatureCheck = true;
4000
4001 if (creature->inCheckCreaturesVector) {
4002 // already in a vector
4003 return;
4004 }
4005
4006 creature->inCheckCreaturesVector = true;
4007 checkCreatureLists[uniform_random(0, EVENT_CREATURECOUNT - 1)].push_back(
4008 creature);
4009 creature->incrementReferenceCounter();
4010}
4011
4012void Game::removeCreatureCheck(Creature *creature) {
4013 if (creature->inCheckCreaturesVector) {
4014 creature->creatureCheck = false;
4015 }
4016}
4017
4018void Game::checkCreatures(size_t index) {
4019 g_scheduler.addEvent(
4020 createSchedulerTask(EVENT_CHECK_CREATURE_INTERVAL,
4021 std::bind(&Game::checkCreatures, this,
4022 (index + 1) % EVENT_CREATURECOUNT)));
4023
4024 auto &checkCreatureList = checkCreatureLists[index];
4025 auto it = checkCreatureList.begin(), end = checkCreatureList.end();
4026 while (it != end) {
4027 Creature *creature = *it;
4028 if (creature->creatureCheck) {
4029 if (creature->getHealth() > 0) {
4030 creature->onThink(EVENT_CREATURE_THINK_INTERVAL);
4031 creature->onAttacking(EVENT_CREATURE_THINK_INTERVAL);
4032 creature->executeConditions(EVENT_CREATURE_THINK_INTERVAL);
4033 } else {
4034 creature->onDeath();
4035 }
4036 ++it;
4037 } else {
4038 creature->inCheckCreaturesVector = false;
4039 it = checkCreatureList.erase(it);
4040 ReleaseCreature(creature);
4041 }
4042 }
4043
4044 cleanup();
4045}
4046
4047void Game::changeSpeed(Creature *creature, int32_t varSpeedDelta) {
4048 int32_t varSpeed = creature->getSpeed() - creature->getBaseSpeed();
4049 varSpeed += varSpeedDelta;
4050
4051 creature->setSpeed(varSpeed);
4052
4053 // send to clients
4054 SpectatorVec spectators;
4055 map.getSpectators(spectators, creature->getPosition(), false, true);
4056 for (Creature *spectator : spectators) {
4057 spectator->getPlayer()->sendChangeSpeed(creature, creature->getStepSpeed());
4058 }
4059}
4060
4061void Game::internalCreatureChangeOutfit(Creature *creature,
4062 const Outfit_t &outfit) {
4063 if (!g_events->eventCreatureOnChangeOutfit(creature, outfit)) {
4064 return;
4065 }
4066
4067 if (creature->isAttackPulsing) {
4068 if (creature->attackOutfitEventId != 0) {
4069 g_scheduler.stopEvent(creature->attackOutfitEventId);
4070 creature->attackOutfitEventId = 0;
4071 }
4072 creature->isAttackPulsing = false;
4073 }
4074
4075 creature->setCurrentOutfit(outfit);
4076
4077 if (creature->isInvisible()) {
4078 return;
4079 }
4080
4081 // send to clients
4082 SpectatorVec spectators;
4083 map.getSpectators(spectators, creature->getPosition(), true, true);
4084 for (Creature *spectator : spectators) {
4085 spectator->getPlayer()->sendCreatureChangeOutfit(creature, outfit);
4086 }
4087}
4088
4089void Game::internalCreatureChangeVisible(Creature *creature, bool visible) {
4090 // send to clients
4091 SpectatorVec spectators;
4092 map.getSpectators(spectators, creature->getPosition(), true, true);
4093 for (Creature *spectator : spectators) {
4094 spectator->getPlayer()->sendCreatureChangeVisible(creature, visible);
4095 }
4096}
4097
4098void Game::changeLight(const Creature *creature) {
4099 // send to clients
4100 SpectatorVec spectators;
4101 map.getSpectators(spectators, creature->getPosition(), true, true);
4102 for (Creature *spectator : spectators) {
4103 spectator->getPlayer()->sendCreatureLight(creature);
4104 }
4105}
4106
4107bool Game::combatBlockHit(CombatDamage &damage, Creature *attacker,
4108 Creature *target, bool checkDefense, bool checkArmor,
4109 bool field) {
4110 if (damage.primary.type == COMBAT_NONE &&
4111 damage.secondary.type == COMBAT_NONE) {
4112 return true;
4113 }
4114
4115 if (target->getPlayer() && target->isInGhostMode()) {
4116 return true;
4117 }
4118
4119 if (damage.primary.value > 0) {
4120 return false;
4121 }
4122
4123 static const auto sendBlockEffect = [this](BlockType_t blockType,
4124 CombatType_t combatType,
4125 const Position &targetPos) {
4126 if (blockType == BLOCK_DEFENSE) {
4127 addMagicEffect(targetPos, CONST_ME_POFF);
4128 } else if (blockType == BLOCK_ARMOR) {
4129 addMagicEffect(targetPos, CONST_ME_BLOCKHIT);
4130 } else if (blockType == BLOCK_IMMUNITY) {
4131 uint8_t hitEffect = 0;
4132 switch (combatType) {
4133 case COMBAT_UNDEFINEDDAMAGE: {
4134 return;
4135 }
4136 case COMBAT_ENERGYDAMAGE:
4137 case COMBAT_FIREDAMAGE:
4138 case COMBAT_PHYSICALDAMAGE:
4139 case COMBAT_ICEDAMAGE:
4140 case COMBAT_DEATHDAMAGE: {
4141 hitEffect = CONST_ME_BLOCKHIT;
4142 break;
4143 }
4144 case COMBAT_EARTHDAMAGE: {
4145 hitEffect = CONST_ME_GREEN_RINGS;
4146 break;
4147 }
4148 case COMBAT_HOLYDAMAGE: {
4149 hitEffect = CONST_ME_HOLYDAMAGE;
4150 break;
4151 }
4152 default: {
4153 hitEffect = CONST_ME_POFF;
4154 break;
4155 }
4156 }
4157 addMagicEffect(targetPos, hitEffect);
4158 }
4159 };
4160
4161 BlockType_t primaryBlockType, secondaryBlockType;
4162 if (damage.primary.type != COMBAT_NONE) {
4163 damage.primary.value = -damage.primary.value;
4164 primaryBlockType =
4165 target->blockHit(attacker, damage.primary.type, damage.primary.value,
4166 checkDefense, checkArmor, field);
4167
4168 damage.primary.value = -damage.primary.value;
4169 sendBlockEffect(primaryBlockType, damage.primary.type,
4170 target->getPosition());
4171 } else {
4172 primaryBlockType = BLOCK_NONE;
4173 }
4174
4175 if (damage.secondary.type != COMBAT_NONE) {
4176 damage.secondary.value = -damage.secondary.value;
4177 secondaryBlockType =
4178 target->blockHit(attacker, damage.secondary.type,
4179 damage.secondary.value, false, false, field);
4180 damage.secondary.value = -damage.secondary.value;
4181 sendBlockEffect(secondaryBlockType, damage.secondary.type,
4182 target->getPosition());
4183 } else {
4184 secondaryBlockType = BLOCK_NONE;
4185 }
4186
4187 damage.blockType = primaryBlockType;
4188
4189 return (primaryBlockType != BLOCK_NONE) && (secondaryBlockType != BLOCK_NONE);
4190}
4191
4192void Game::combatGetTypeInfo(CombatType_t combatType, Creature *target,
4193 TextColor_t &color, uint8_t &effect) {
4194 switch (combatType) {
4195 case COMBAT_PHYSICALDAMAGE: {
4196 Item *splash = nullptr;
4197 switch (target->getRace()) {
4198 case RACE_VENOM:
4199 color = TEXTCOLOR_LIGHTGREEN;
4200 effect = CONST_ME_HITBYPOISON;
4201 splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_SLIME);
4202 break;
4203 case RACE_BLOOD:
4204 color = TEXTCOLOR_RED;
4205 effect = CONST_ME_DRAWBLOOD;
4206 if (const Tile *tile = target->getTile()) {
4207 if (!tile->hasFlag(TILESTATE_PROTECTIONZONE)) {
4208 splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_BLOOD);
4209 }
4210 }
4211 break;
4212 case RACE_UNDEAD:
4213 color = TEXTCOLOR_LIGHTGREY;
4214 effect = CONST_ME_HITAREA;
4215 break;
4216 case RACE_FIRE:
4217 color = TEXTCOLOR_ORANGE;
4218 effect = CONST_ME_DRAWBLOOD;
4219 break;
4220 case RACE_ENERGY:
4221 color = TEXTCOLOR_ELECTRICPURPLE;
4222 effect = CONST_ME_ENERGYHIT;
4223 break;
4224 default:
4225 color = TEXTCOLOR_NONE;
4226 effect = CONST_ME_NONE;
4227 break;
4228 }
4229
4230 if (splash) {
4231 internalAddItem(target->getTile(), splash, INDEX_WHEREEVER, FLAG_NOLIMIT);
4232 startDecay(splash);
4233 }
4234
4235 break;
4236 }
4237
4238 case COMBAT_ENERGYDAMAGE: {
4239 color = TEXTCOLOR_ELECTRICPURPLE;
4240 effect = CONST_ME_ENERGYHIT;
4241 break;
4242 }
4243
4244 case COMBAT_EARTHDAMAGE: {
4245 color = TEXTCOLOR_LIGHTGREEN;
4246 effect = CONST_ME_GREEN_RINGS;
4247 break;
4248 }
4249
4250 case COMBAT_DROWNDAMAGE: {
4251 color = TEXTCOLOR_LIGHTBLUE;
4252 effect = CONST_ME_LOSEENERGY;
4253 break;
4254 }
4255 case COMBAT_FIREDAMAGE: {
4256 color = TEXTCOLOR_ORANGE;
4257 effect = CONST_ME_HITBYFIRE;
4258 break;
4259 }
4260 case COMBAT_ICEDAMAGE: {
4261 color = TEXTCOLOR_SKYBLUE;
4262 effect = CONST_ME_ICEATTACK;
4263 break;
4264 }
4265 case COMBAT_HOLYDAMAGE: {
4266 color = TEXTCOLOR_YELLOW;
4267 effect = CONST_ME_HOLYDAMAGE;
4268 break;
4269 }
4270 case COMBAT_DEATHDAMAGE: {
4271 color = TEXTCOLOR_DARKRED;
4272 effect = CONST_ME_SMALLCLOUDS;
4273 break;
4274 }
4275 case COMBAT_LIFEDRAIN: {
4276 color = TEXTCOLOR_RED;
4277 effect = CONST_ME_MAGIC_RED;
4278 break;
4279 }
4280 default: {
4281 color = TEXTCOLOR_NONE;
4282 effect = CONST_ME_NONE;
4283 break;
4284 }
4285 }
4286}
4287
4288bool Game::combatChangeHealth(Creature *attacker, Creature *target,
4289 CombatDamage &damage) {
4290 const Position &targetPos = target->getPosition();
4291 if (damage.primary.value > 0) {
4292 if (target->getHealth() <= 0) {
4293 return false;
4294 }
4295
4296 Player *attackerPlayer;
4297 if (attacker) {
4298 attackerPlayer = attacker->getPlayer();
4299 } else {
4300 attackerPlayer = nullptr;
4301 }
4302
4303 Player *targetPlayer = target->getPlayer();
4304 if (attackerPlayer && targetPlayer &&
4305 attackerPlayer->getSkull() == SKULL_BLACK &&
4306 attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) {
4307 return false;
4308 }
4309
4310 if (damage.origin != ORIGIN_NONE) {
4311 const auto &events =
4312 target->getCreatureEvents(CREATURE_EVENT_HEALTHCHANGE);
4313 if (!events.empty()) {
4314 for (CreatureEvent *creatureEvent : events) {
4315 creatureEvent->executeHealthChange(target, attacker, damage);
4316 }
4317 damage.origin = ORIGIN_NONE;
4318 return combatChangeHealth(attacker, target, damage);
4319 }
4320 }
4321
4322 int32_t realHealthChange = target->getHealth();
4323 target->gainHealth(attacker, damage.primary.value);
4324 realHealthChange = target->getHealth() - realHealthChange;
4325
4326 if (realHealthChange > 0 && !target->isInGhostMode()) {
4327 std::stringstream ss;
4328
4329 ss << realHealthChange
4330 << (realHealthChange != 1 ? " hitpoints." : " hitpoint.");
4331 std::string damageString = ss.str();
4332
4333 std::string spectatorMessage;
4334
4335 TextMessage message;
4336 message.position = targetPos;
4337 message.primary.value = realHealthChange;
4338 message.primary.color = TEXTCOLOR_PASTELRED;
4339
4340 SpectatorVec spectators;
4341 map.getSpectators(spectators, targetPos, false, true);
4342 for (Creature *spectator : spectators) {
4343 Player *tmpPlayer = spectator->getPlayer();
4344 if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
4345 ss.str({});
4346 ss << "You heal " << target->getNameDescription() << " for "
4347 << damageString;
4348 message.type = MESSAGE_HEALED;
4349 message.text = ss.str();
4350 } else if (tmpPlayer == targetPlayer) {
4351 ss.str({});
4352 if (!attacker) {
4353 ss << "You were healed";
4354 } else if (targetPlayer == attackerPlayer) {
4355 ss << "You healed yourself";
4356 } else {
4357 ss << "You were healed by " << attacker->getNameDescription();
4358 }
4359 ss << " for " << damageString;
4360 message.type = MESSAGE_HEALED;
4361 message.text = ss.str();
4362 } else {
4363 if (spectatorMessage.empty()) {
4364 ss.str({});
4365 if (!attacker) {
4366 ss << ucfirst(target->getNameDescription()) << " was healed";
4367 } else {
4368 ss << ucfirst(attacker->getNameDescription()) << " healed ";
4369 if (attacker == target) {
4370 ss << (targetPlayer
4371 ? (targetPlayer->getSex() == PLAYERSEX_FEMALE
4372 ? "herself"
4373 : "himself")
4374 : "itself");
4375 } else {
4376 ss << target->getNameDescription();
4377 }
4378 }
4379 ss << " for " << damageString;
4380 spectatorMessage = ss.str();
4381 }
4382 message.type = MESSAGE_HEALED_OTHERS;
4383 message.text = spectatorMessage;
4384 }
4385 tmpPlayer->sendTextMessage(message);
4386 }
4387 }
4388 } else {
4389 if (!target->isAttackable()) {
4390 if (!target->isInGhostMode()) {
4391 addMagicEffect(targetPos, CONST_ME_POFF);
4392 }
4393 return true;
4394 }
4395
4396 Player *attackerPlayer;
4397 if (attacker) {
4398 attackerPlayer = attacker->getPlayer();
4399 } else {
4400 attackerPlayer = nullptr;
4401 }
4402
4403 Player *targetPlayer = target->getPlayer();
4404 if (attackerPlayer && targetPlayer &&
4405 attackerPlayer->getSkull() == SKULL_BLACK &&
4406 attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) {
4407 return false;
4408 }
4409
4410 damage.primary.value = std::abs(damage.primary.value);
4411 damage.secondary.value = std::abs(damage.secondary.value);
4412
4413 if (attacker && attacker->getPlayer()) {
4414 RuneEffect* activeEffect = g_game.runeEffects.getRuneEffectByID(attacker->getPlayer()->getCurrentRuneEffect());
4415 if (activeEffect && activeEffect->bonusDamage > 0) {
4416 int32_t bonus = (damage.primary.value * activeEffect->bonusDamage) / 100;
4417 damage.primary.value += bonus;
4418 }
4419 }
4420
4421 int32_t healthChange = damage.primary.value + damage.secondary.value;
4422 if (healthChange == 0) {
4423 return true;
4424 }
4425
4426 TextMessage message;
4427 message.position = targetPos;
4428
4429 SpectatorVec spectators;
4430 if (targetPlayer && target->hasCondition(CONDITION_MANASHIELD) &&
4431 damage.primary.type != COMBAT_UNDEFINEDDAMAGE) {
4432 int32_t manaDamage =
4433 std::min<int32_t>(targetPlayer->getMana(), healthChange);
4434 if (manaDamage != 0) {
4435 if (damage.origin != ORIGIN_NONE) {
4436 const auto &events =
4437 target->getCreatureEvents(CREATURE_EVENT_MANACHANGE);
4438 if (!events.empty()) {
4439 for (CreatureEvent *creatureEvent : events) {
4440 creatureEvent->executeManaChange(target, attacker, damage);
4441 }
4442 healthChange = damage.primary.value + damage.secondary.value;
4443 if (healthChange == 0) {
4444 return true;
4445 }
4446 manaDamage =
4447 std::min<int32_t>(targetPlayer->getMana(), healthChange);
4448 }
4449 }
4450
4451 targetPlayer->drainMana(attacker, manaDamage);
4452 map.getSpectators(spectators, targetPos, true, true);
4453 addMagicEffect(spectators, targetPos, CONST_ME_LOSEENERGY);
4454
4455 std::stringstream ss;
4456
4457 std::string damageString = std::to_string(manaDamage);
4458
4459 std::string spectatorMessage;
4460
4461 message.primary.value = manaDamage;
4462 message.primary.color = TEXTCOLOR_BLUE;
4463
4464 for (Creature *spectator : spectators) {
4465 Player *tmpPlayer = spectator->getPlayer();
4466 if (tmpPlayer->getPosition().z != targetPos.z) {
4467 continue;
4468 }
4469
4470 if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
4471 ss.str({});
4472 ss << ucfirst(target->getNameDescription()) << " loses "
4473 << damageString + " mana due to your attack.";
4474 message.type = MESSAGE_DAMAGE_DEALT;
4475 message.text = ss.str();
4476 } else if (tmpPlayer == targetPlayer) {
4477 ss.str({});
4478 ss << "You lose " << damageString << " mana";
4479 if (!attacker) {
4480 ss << '.';
4481 } else if (targetPlayer == attackerPlayer) {
4482 ss << " due to your own attack.";
4483 } else {
4484 ss << " due to an attack by " << attacker->getNameDescription()
4485 << '.';
4486 }
4487 message.type = MESSAGE_DAMAGE_RECEIVED;
4488 message.text = ss.str();
4489 } else {
4490 if (spectatorMessage.empty()) {
4491 ss.str({});
4492 ss << ucfirst(target->getNameDescription()) << " loses "
4493 << damageString + " mana";
4494 if (attacker) {
4495 ss << " due to ";
4496 if (attacker == target) {
4497 ss << (targetPlayer->getSex() == PLAYERSEX_FEMALE
4498 ? "her own attack"
4499 : "his own attack");
4500 } else {
4501 ss << "an attack by " << attacker->getNameDescription();
4502 }
4503 }
4504 ss << '.';
4505 spectatorMessage = ss.str();
4506 }
4507 message.type = MESSAGE_DAMAGE_OTHERS;
4508 message.text = spectatorMessage;
4509 }
4510 tmpPlayer->sendTextMessage(message);
4511 }
4512
4513 damage.primary.value -= manaDamage;
4514 if (damage.primary.value < 0) {
4515 damage.secondary.value = std::max<int32_t>(
4516 0, damage.secondary.value + damage.primary.value);
4517 damage.primary.value = 0;
4518 }
4519 }
4520 }
4521
4522 int32_t realDamage = damage.primary.value + damage.secondary.value;
4523 if (realDamage == 0) {
4524 return true;
4525 }
4526
4527 if (damage.origin != ORIGIN_NONE) {
4528 const auto &events =
4529 target->getCreatureEvents(CREATURE_EVENT_HEALTHCHANGE);
4530 if (!events.empty()) {
4531 for (CreatureEvent *creatureEvent : events) {
4532 creatureEvent->executeHealthChange(target, attacker, damage);
4533 }
4534 damage.origin = ORIGIN_NONE;
4535 return combatChangeHealth(attacker, target, damage);
4536 }
4537 }
4538
4539 int32_t targetHealth = target->getHealth();
4540 if (damage.primary.value >= targetHealth) {
4541 damage.primary.value = targetHealth;
4542 damage.secondary.value = 0;
4543 } else if (damage.secondary.value) {
4544 damage.secondary.value = std::min<int32_t>(
4545 damage.secondary.value, targetHealth - damage.primary.value);
4546 }
4547
4548 realDamage = damage.primary.value + damage.secondary.value;
4549 if (realDamage == 0) {
4550 return true;
4551 }
4552
4553 if (spectators.empty()) {
4554 map.getSpectators(spectators, targetPos, true, true);
4555 }
4556
4557 message.primary.value = damage.primary.value;
4558 message.secondary.value = damage.secondary.value;
4559
4560 uint8_t hitEffect;
4561 if (message.primary.value) {
4562 combatGetTypeInfo(damage.primary.type, target, message.primary.color,
4563 hitEffect);
4564 if (hitEffect != CONST_ME_NONE) {
4565 addMagicEffect(spectators, targetPos, hitEffect);
4566 }
4567 }
4568
4569 if (message.secondary.value) {
4570 combatGetTypeInfo(damage.secondary.type, target, message.secondary.color,
4571 hitEffect);
4572 if (hitEffect != CONST_ME_NONE) {
4573 addMagicEffect(spectators, targetPos, hitEffect);
4574 }
4575 }
4576
4577 if (message.primary.color != TEXTCOLOR_NONE ||
4578 message.secondary.color != TEXTCOLOR_NONE) {
4579 std::stringstream ss;
4580
4581 ss << realDamage << (realDamage != 1 ? " hitpoints" : " hitpoint");
4582 std::string damageString = ss.str();
4583
4584 std::string spectatorMessage;
4585
4586 for (Creature *spectator : spectators) {
4587 Player *tmpPlayer = spectator->getPlayer();
4588 if (tmpPlayer->getPosition().z != targetPos.z) {
4589 continue;
4590 }
4591
4592 if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
4593 ss.str({});
4594 ss << ucfirst(target->getNameDescription()) << " loses "
4595 << damageString << " due to your attack.";
4596 message.type = MESSAGE_DAMAGE_DEALT;
4597 message.text = ss.str();
4598 } else if (tmpPlayer == targetPlayer) {
4599 ss.str({});
4600 ss << "You lose " << damageString;
4601 if (!attacker) {
4602 ss << '.';
4603 } else if (targetPlayer == attackerPlayer) {
4604 ss << " due to your own attack.";
4605 } else {
4606 ss << " due to an attack by " << attacker->getNameDescription()
4607 << '.';
4608 }
4609 message.type = MESSAGE_DAMAGE_RECEIVED;
4610 message.text = ss.str();
4611 } else {
4612 message.type = MESSAGE_DAMAGE_OTHERS;
4613
4614 if (spectatorMessage.empty()) {
4615 ss.str({});
4616 ss << ucfirst(target->getNameDescription()) << " loses "
4617 << damageString;
4618 if (attacker) {
4619 ss << " due to ";
4620 if (attacker == target) {
4621 if (targetPlayer) {
4622 ss << (targetPlayer->getSex() == PLAYERSEX_FEMALE
4623 ? "her own attack"
4624 : "his own attack");
4625 } else {
4626 ss << "its own attack";
4627 }
4628 } else {
4629 ss << "an attack by " << attacker->getNameDescription();
4630 }
4631 }
4632 ss << '.';
4633 spectatorMessage = ss.str();
4634 }
4635
4636 message.text = spectatorMessage;
4637 }
4638 tmpPlayer->sendTextMessage(message);
4639 }
4640 }
4641
4642 if (realDamage >= targetHealth) {
4643 for (CreatureEvent *creatureEvent :
4644 target->getCreatureEvents(CREATURE_EVENT_PREPAREDEATH)) {
4645 if (!creatureEvent->executeOnPrepareDeath(target, attacker)) {
4646 return false;
4647 }
4648 }
4649 }
4650
4651 target->drainHealth(attacker, realDamage);
4652 addCreatureHealth(spectators, target);
4653 }
4654
4655 return true;
4656}
4657
4658bool Game::combatChangeMana(Creature *attacker, Creature *target,
4659 CombatDamage &damage) {
4660 Player *targetPlayer = target->getPlayer();
4661 if (!targetPlayer) {
4662 return true;
4663 }
4664
4665 int32_t manaChange = damage.primary.value + damage.secondary.value;
4666 if (manaChange > 0) {
4667 if (attacker) {
4668 const Player *attackerPlayer = attacker->getPlayer();
4669 if (attackerPlayer && attackerPlayer->getSkull() == SKULL_BLACK &&
4670 attackerPlayer->getSkullClient(target) == SKULL_NONE) {
4671 return false;
4672 }
4673 }
4674
4675 if (damage.origin != ORIGIN_NONE) {
4676 const auto &events = target->getCreatureEvents(CREATURE_EVENT_MANACHANGE);
4677 if (!events.empty()) {
4678 for (CreatureEvent *creatureEvent : events) {
4679 creatureEvent->executeManaChange(target, attacker, damage);
4680 }
4681 damage.origin = ORIGIN_NONE;
4682 return combatChangeMana(attacker, target, damage);
4683 }
4684 }
4685
4686 int32_t realManaChange = targetPlayer->getMana();
4687 targetPlayer->changeMana(manaChange);
4688 realManaChange = targetPlayer->getMana() - realManaChange;
4689
4690 if (realManaChange > 0 && !targetPlayer->isInGhostMode()) {
4691 TextMessage message(MESSAGE_HEALED, "You gained " +
4692 std::to_string(realManaChange) +
4693 " mana.");
4694 message.position = target->getPosition();
4695 message.primary.value = realManaChange;
4696 message.primary.color = TEXTCOLOR_MAYABLUE;
4697 targetPlayer->sendTextMessage(message);
4698 }
4699 } else {
4700 const Position &targetPos = target->getPosition();
4701 if (!target->isAttackable()) {
4702 if (!target->isInGhostMode()) {
4703 addMagicEffect(targetPos, CONST_ME_POFF);
4704 }
4705 return false;
4706 }
4707
4708 Player *attackerPlayer;
4709 if (attacker) {
4710 attackerPlayer = attacker->getPlayer();
4711 } else {
4712 attackerPlayer = nullptr;
4713 }
4714
4715 if (attackerPlayer && attackerPlayer->getSkull() == SKULL_BLACK &&
4716 attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) {
4717 return false;
4718 }
4719
4720 int32_t manaLoss = std::min<int32_t>(targetPlayer->getMana(), -manaChange);
4721 BlockType_t blockType =
4722 target->blockHit(attacker, COMBAT_MANADRAIN, manaLoss);
4723 if (blockType != BLOCK_NONE) {
4724 addMagicEffect(targetPos, CONST_ME_POFF);
4725 return false;
4726 }
4727
4728 if (manaLoss <= 0) {
4729 return true;
4730 }
4731
4732 if (damage.origin != ORIGIN_NONE) {
4733 const auto &events = target->getCreatureEvents(CREATURE_EVENT_MANACHANGE);
4734 if (!events.empty()) {
4735 for (CreatureEvent *creatureEvent : events) {
4736 creatureEvent->executeManaChange(target, attacker, damage);
4737 }
4738 damage.origin = ORIGIN_NONE;
4739 return combatChangeMana(attacker, target, damage);
4740 }
4741 }
4742
4743 targetPlayer->drainMana(attacker, manaLoss);
4744
4745 std::stringstream ss;
4746
4747 std::string damageString = std::to_string(manaLoss);
4748
4749 std::string spectatorMessage;
4750
4751 TextMessage message;
4752 message.position = targetPos;
4753 message.primary.value = manaLoss;
4754 message.primary.color = TEXTCOLOR_BLUE;
4755
4756 SpectatorVec spectators;
4757 map.getSpectators(spectators, targetPos, false, true);
4758 for (Creature *spectator : spectators) {
4759 Player *tmpPlayer = spectator->getPlayer();
4760 if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) {
4761 ss.str({});
4762 ss << ucfirst(target->getNameDescription()) << " loses " << damageString
4763 << " mana due to your attack.";
4764 message.type = MESSAGE_DAMAGE_DEALT;
4765 message.text = ss.str();
4766 } else if (tmpPlayer == targetPlayer) {
4767 ss.str({});
4768 ss << "You lose " << damageString << " mana";
4769 if (!attacker) {
4770 ss << '.';
4771 } else if (targetPlayer == attackerPlayer) {
4772 ss << " due to your own attack.";
4773 } else {
4774 ss << " mana due to an attack by " << attacker->getNameDescription()
4775 << '.';
4776 }
4777 message.type = MESSAGE_DAMAGE_RECEIVED;
4778 message.text = ss.str();
4779 } else {
4780 if (spectatorMessage.empty()) {
4781 ss.str({});
4782 ss << ucfirst(target->getNameDescription()) << " loses "
4783 << damageString << " mana";
4784 if (attacker) {
4785 ss << " due to ";
4786 if (attacker == target) {
4787 ss << (targetPlayer->getSex() == PLAYERSEX_FEMALE
4788 ? "her own attack"
4789 : "his own attack");
4790 } else {
4791 ss << "an attack by " << attacker->getNameDescription();
4792 }
4793 }
4794 ss << '.';
4795 spectatorMessage = ss.str();
4796 }
4797 message.type = MESSAGE_DAMAGE_OTHERS;
4798 message.text = spectatorMessage;
4799 }
4800 tmpPlayer->sendTextMessage(message);
4801 }
4802 }
4803
4804 return true;
4805}
4806
4807void Game::addCreatureHealth(const Creature *target) {
4808 SpectatorVec spectators;
4809 map.getSpectators(spectators, target->getPosition(), true, true);
4810 addCreatureHealth(spectators, target);
4811}
4812
4813void Game::addCreatureHealth(const SpectatorVec &spectators,
4814 const Creature *target) {
4815 for (Creature *spectator : spectators) {
4816 if (Player *tmpPlayer = spectator->getPlayer()) {
4817 tmpPlayer->sendCreatureHealth(target);
4818 }
4819 }
4820}
4821
4822void Game::addMagicEffect(const Position &pos, uint16_t effect) {
4823 SpectatorVec spectators;
4824 map.getSpectators(spectators, pos, true, true);
4825 addMagicEffect(spectators, pos, effect);
4826}
4827
4828void Game::addMagicEffect(const SpectatorVec &spectators, const Position &pos,
4829 uint16_t effect) {
4830 for (Creature *spectator : spectators) {
4831 if (Player *tmpPlayer = spectator->getPlayer()) {
4832 tmpPlayer->sendMagicEffect(pos, effect);
4833 }
4834 }
4835}
4836
4837void Game::addDistanceEffect(const Position &fromPos, const Position &toPos,
4838 uint8_t effect) {
4839 SpectatorVec spectators, toPosSpectators;
4840 map.getSpectators(spectators, fromPos, false, true);
4841 map.getSpectators(toPosSpectators, toPos, false, true);
4842 spectators.addSpectators(toPosSpectators);
4843
4844 addDistanceEffect(spectators, fromPos, toPos, effect);
4845}
4846
4847void Game::addDistanceEffect(const SpectatorVec &spectators,
4848 const Position &fromPos, const Position &toPos,
4849 uint8_t effect) {
4850 for (Creature *spectator : spectators) {
4851 if (Player *tmpPlayer = spectator->getPlayer()) {
4852 tmpPlayer->sendDistanceShoot(fromPos, toPos, effect);
4853 }
4854 }
4855}
4856
4857void Game::startDecay(Item *item) {
4858 if (!item || !item->canDecay()) {
4859 return;
4860 }
4861
4862 ItemDecayState_t decayState = item->getDecaying();
4863 if (decayState == DECAYING_TRUE) {
4864 return;
4865 }
4866
4867 if (item->getDuration() > 0) {
4868 item->incrementReferenceCounter();
4869 item->setDecaying(DECAYING_TRUE);
4870 toDecayItems.push_front(item);
4871 } else {
4872 internalDecayItem(item);
4873 }
4874}
4875
4876void Game::internalDecayItem(Item *item) {
4877 const ItemType &it = Item::items[item->getID()];
4878 if (it.decayTo != 0) {
4879 Item *newItem = transformItem(item, item->getDecayTo());
4880 startDecay(newItem);
4881 } else {
4882 ReturnValue ret = internalRemoveItem(item);
4883 if (ret != RETURNVALUE_NOERROR) {
4884 std::cout << "[Debug - Game::internalDecayItem] internalDecayItem "
4885 "failed, error code: "
4886 << static_cast<uint32_t>(ret) << ", item id: " << item->getID()
4887 << std::endl;
4888 }
4889 }
4890}
4891
4892void Game::checkDecay() {
4893 g_scheduler.addEvent(createSchedulerTask(EVENT_DECAYINTERVAL,
4894 std::bind(&Game::checkDecay, this)));
4895
4896 size_t bucket = (lastBucket + 1) % EVENT_DECAY_BUCKETS;
4897
4898 auto it = decayItems[bucket].begin(), end = decayItems[bucket].end();
4899 while (it != end) {
4900 Item *item = *it;
4901 if (!item->canDecay()) {
4902 item->setDecaying(DECAYING_FALSE);
4903 ReleaseItem(item);
4904 it = decayItems[bucket].erase(it);
4905 continue;
4906 }
4907
4908 int32_t duration = item->getDuration();
4909 int32_t decreaseTime =
4910 std::min<int32_t>(EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS, duration);
4911
4912 duration -= decreaseTime;
4913 item->decreaseDuration(decreaseTime);
4914
4915 if (duration <= 0) {
4916 it = decayItems[bucket].erase(it);
4917 internalDecayItem(item);
4918 ReleaseItem(item);
4919 } else if (duration < EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS) {
4920 it = decayItems[bucket].erase(it);
4921 size_t newBucket =
4922 (bucket + ((duration + EVENT_DECAYINTERVAL / 2) / 1000)) %
4923 EVENT_DECAY_BUCKETS;
4924 if (newBucket == bucket) {
4925 internalDecayItem(item);
4926 ReleaseItem(item);
4927 } else {
4928 decayItems[newBucket].push_back(item);
4929 }
4930 } else {
4931 ++it;
4932 }
4933 }
4934
4935 lastBucket = bucket;
4936 cleanup();
4937}
4938
4939void Game::checkLight() {
4940 g_scheduler.addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL,
4941 std::bind(&Game::checkLight, this)));
4942
4943 lightHour += lightHourDelta;
4944
4945 if (lightHour > 1440) {
4946 lightHour -= 1440;
4947 }
4948
4949 if (std::abs(lightHour - SUNRISE) < 2 * lightHourDelta) {
4950 lightState = LIGHT_STATE_SUNRISE;
4951 } else if (std::abs(lightHour - SUNSET) < 2 * lightHourDelta) {
4952 lightState = LIGHT_STATE_SUNSET;
4953 }
4954
4955 int32_t newLightLevel = lightLevel;
4956 bool lightChange = false;
4957
4958 switch (lightState) {
4959 case LIGHT_STATE_SUNRISE: {
4960 newLightLevel += (LIGHT_LEVEL_DAY - LIGHT_LEVEL_NIGHT) / 30;
4961 lightChange = true;
4962 break;
4963 }
4964 case LIGHT_STATE_SUNSET: {
4965 newLightLevel -= (LIGHT_LEVEL_DAY - LIGHT_LEVEL_NIGHT) / 30;
4966 lightChange = true;
4967 break;
4968 }
4969 default:
4970 break;
4971 }
4972
4973 if (newLightLevel <= LIGHT_LEVEL_NIGHT) {
4974 lightLevel = LIGHT_LEVEL_NIGHT;
4975 lightState = LIGHT_STATE_NIGHT;
4976 } else if (newLightLevel >= LIGHT_LEVEL_DAY) {
4977 lightLevel = LIGHT_LEVEL_DAY;
4978 lightState = LIGHT_STATE_DAY;
4979 } else {
4980 lightLevel = newLightLevel;
4981 }
4982
4983 if (lightChange) {
4984 LightInfo lightInfo = getWorldLightInfo();
4985
4986 for (const auto &it : players) {
4987 it.second->sendWorldLight(lightInfo);
4988 }
4989 }
4990}
4991
4992LightInfo Game::getWorldLightInfo() const { return {lightLevel, 0xD7}; }
4993
4994void Game::shutdown() {
4995 std::cout << "Shutting down..." << std::flush;
4996
4997 g_scheduler.shutdown();
4998 g_databaseTasks.shutdown();
4999 g_dispatcher.shutdown();
5000 map.spawns.clear();
5001 raids.clear();
5002
5003 cleanup();
5004
5005 if (serviceManager) {
5006 serviceManager->stop();
5007 }
5008
5009 ConnectionManager::getInstance().closeAll();
5010
5011 std::cout << " done!" << std::endl;
5012}
5013
5014void Game::cleanup() {
5015 // free memory
5016 for (auto creature : ToReleaseCreatures) {
5017 creature->decrementReferenceCounter();
5018 }
5019 ToReleaseCreatures.clear();
5020
5021 for (auto item : ToReleaseItems) {
5022 item->decrementReferenceCounter();
5023 }
5024 ToReleaseItems.clear();
5025
5026 for (Item *item : toDecayItems) {
5027 const uint32_t dur = item->getDuration();
5028 if (dur >= EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS) {
5029 decayItems[lastBucket].push_back(item);
5030 } else {
5031 decayItems[(lastBucket + 1 + dur / 1000) % EVENT_DECAY_BUCKETS].push_back(
5032 item);
5033 }
5034 }
5035 toDecayItems.clear();
5036}
5037
5038void Game::ReleaseCreature(Creature *creature) {
5039 ToReleaseCreatures.push_back(creature);
5040}
5041
5042void Game::ReleaseItem(Item *item) { ToReleaseItems.push_back(item); }
5043
5044void Game::broadcastMessage(const std::string &text,
5045 MessageClasses type) const {
5046 std::cout << "> Broadcasted message: \"" << text << "\"." << std::endl;
5047 for (const auto &it : players) {
5048 it.second->sendTextMessage(type, text);
5049 }
5050}
5051
5052void Game::updateCreatureWalkthrough(const Creature *creature) {
5053 // send to clients
5054 SpectatorVec spectators;
5055 map.getSpectators(spectators, creature->getPosition(), true, true);
5056 for (Creature *spectator : spectators) {
5057 Player *tmpPlayer = spectator->getPlayer();
5058 tmpPlayer->sendCreatureWalkthrough(creature,
5059 tmpPlayer->canWalkthroughEx(creature));
5060 }
5061}
5062
5063void Game::updateCreatureSkull(const Creature *creature) {
5064 if (getWorldType() != WORLD_TYPE_PVP) {
5065 return;
5066 }
5067
5068 SpectatorVec spectators;
5069 map.getSpectators(spectators, creature->getPosition(), true, true);
5070 for (Creature *spectator : spectators) {
5071 spectator->getPlayer()->sendCreatureSkull(creature);
5072 }
5073}
5074
5075void Game::updatePlayerShield(Player *player) {
5076 SpectatorVec spectators;
5077 map.getSpectators(spectators, player->getPosition(), true, true);
5078 for (Creature *spectator : spectators) {
5079 spectator->getPlayer()->sendCreatureShield(player);
5080 }
5081}
5082
5083void Game::updatePlayerHelpers(const Player &player) {
5084 uint32_t creatureId = player.getID();
5085 uint16_t helpers = player.getHelpers();
5086
5087 SpectatorVec spectators;
5088 map.getSpectators(spectators, player.getPosition(), true, true);
5089 for (Creature *spectator : spectators) {
5090 spectator->getPlayer()->sendCreatureHelpers(creatureId, helpers);
5091 }
5092}
5093
5094void Game::updateCreatureType(Creature *creature) {
5095 const Player *masterPlayer = nullptr;
5096
5097 uint32_t creatureId = creature->getID();
5098 CreatureType_t creatureType = creature->getType();
5099 if (creatureType == CREATURETYPE_MONSTER) {
5100 const Creature *master = creature->getMaster();
5101 if (master) {
5102 masterPlayer = master->getPlayer();
5103 if (masterPlayer) {
5104 creatureType = CREATURETYPE_SUMMON_OTHERS;
5105 }
5106 }
5107 }
5108
5109 // send to clients
5110 SpectatorVec spectators;
5111 map.getSpectators(spectators, creature->getPosition(), true, true);
5112
5113 if (creatureType == CREATURETYPE_SUMMON_OTHERS) {
5114 for (Creature *spectator : spectators) {
5115 Player *player = spectator->getPlayer();
5116 if (masterPlayer == player) {
5117 player->sendCreatureType(creatureId, CREATURETYPE_SUMMON_OWN);
5118 } else {
5119 player->sendCreatureType(creatureId, creatureType);
5120 }
5121 }
5122 } else {
5123 for (Creature *spectator : spectators) {
5124 spectator->getPlayer()->sendCreatureType(creatureId, creatureType);
5125 }
5126 }
5127}
5128
5129void Game::updatePremium(Account &account) {
5130 bool save = false;
5131 time_t timeNow = time(nullptr);
5132
5133 if (account.premiumDays != 0 &&
5134 account.premiumDays != std::numeric_limits<uint16_t>::max()) {
5135 if (account.lastDay == 0) {
5136 account.lastDay = timeNow;
5137 save = true;
5138 } else {
5139 uint32_t days = (timeNow - account.lastDay) / 86400;
5140 if (days > 0) {
5141 if (days >= account.premiumDays) {
5142 account.premiumDays = 0;
5143 account.lastDay = 0;
5144 } else {
5145 account.premiumDays -= days;
5146 time_t remainder = (timeNow - account.lastDay) % 86400;
5147 account.lastDay = timeNow - remainder;
5148 }
5149
5150 save = true;
5151 }
5152 }
5153 } else if (account.lastDay != 0) {
5154 account.lastDay = 0;
5155 save = true;
5156 }
5157
5158 if (save && !IOLoginData::saveAccount(account)) {
5159 std::cout << "> ERROR: Failed to save account: " << account.name << "!"
5160 << std::endl;
5161 }
5162}
5163
5164void Game::loadMotdNum() {
5165 Database &db = Database::getInstance();
5166
5167 DBResult_ptr result = db.storeQuery(
5168 "SELECT `value` FROM `server_config` WHERE `config` = 'motd_num'");
5169 if (result) {
5170 motdNum = result->getNumber<uint32_t>("value");
5171 } else {
5172 db.executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES "
5173 "('motd_num', '0')");
5174 }
5175
5176 result = db.storeQuery(
5177 "SELECT `value` FROM `server_config` WHERE `config` = 'motd_hash'");
5178 if (result) {
5179 motdHash = result->getString("value");
5180 if (motdHash != transformToSHA1(g_config.getString(ConfigManager::MOTD))) {
5181 ++motdNum;
5182 }
5183 } else {
5184 db.executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES "
5185 "('motd_hash', '')");
5186 }
5187}
5188
5189void Game::saveMotdNum() const {
5190 Database &db = Database::getInstance();
5191
5192 std::ostringstream query;
5193 query << "UPDATE `server_config` SET `value` = '" << motdNum
5194 << "' WHERE `config` = 'motd_num'";
5195 db.executeQuery(query.str());
5196
5197 query.str(std::string());
5198 query << "UPDATE `server_config` SET `value` = '"
5199 << transformToSHA1(g_config.getString(ConfigManager::MOTD))
5200 << "' WHERE `config` = 'motd_hash'";
5201 db.executeQuery(query.str());
5202}
5203
5204void Game::checkPlayersRecord() {
5205 const size_t playersOnline = getPlayersOnline();
5206 if (playersOnline > playersRecord) {
5207 uint32_t previousRecord = playersRecord;
5208 playersRecord = playersOnline;
5209
5210 for (auto &it : g_globalEvents->getEventMap(GLOBALEVENT_RECORD)) {
5211 it.second.executeRecord(playersRecord, previousRecord);
5212 }
5213 updatePlayersRecord();
5214 }
5215}
5216
5217void Game::updatePlayersRecord() const {
5218 Database &db = Database::getInstance();
5219
5220 std::ostringstream query;
5221 query << "UPDATE `server_config` SET `value` = '" << playersRecord
5222 << "' WHERE `config` = 'players_record'";
5223 db.executeQuery(query.str());
5224}
5225
5226void Game::loadPlayersRecord() {
5227 Database &db = Database::getInstance();
5228
5229 DBResult_ptr result = db.storeQuery(
5230 "SELECT `value` FROM `server_config` WHERE `config` = 'players_record'");
5231 if (result) {
5232 playersRecord = result->getNumber<uint32_t>("value");
5233 } else {
5234 db.executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES "
5235 "('players_record', '0')");
5236 }
5237}
5238
5239uint64_t Game::getExperienceStage(uint32_t level) {
5240 if (!stagesEnabled) {
5241 return g_config.getNumber(ConfigManager::RATE_EXPERIENCE);
5242 }
5243
5244 if (useLastStageLevel && level >= lastStageLevel) {
5245 return stages[lastStageLevel];
5246 }
5247
5248 return stages[level];
5249}
5250
5251bool Game::loadExperienceStages() {
5252 pugi::xml_document doc;
5253 pugi::xml_parse_result result = doc.load_file("data/XML/stages.xml");
5254 if (!result) {
5255 printXMLError("Error - Game::loadExperienceStages", "data/XML/stages.xml",
5256 result);
5257 return false;
5258 }
5259
5260 for (auto stageNode : doc.child("stages").children()) {
5261 if (strcasecmp(stageNode.name(), "config") == 0) {
5262 stagesEnabled = stageNode.attribute("enabled").as_bool();
5263 } else {
5264 uint32_t minLevel, maxLevel, multiplier;
5265
5266 pugi::xml_attribute minLevelAttribute = stageNode.attribute("minlevel");
5267 if (minLevelAttribute) {
5268 minLevel = pugi::cast<uint32_t>(minLevelAttribute.value());
5269 } else {
5270 minLevel = 1;
5271 }
5272
5273 pugi::xml_attribute maxLevelAttribute = stageNode.attribute("maxlevel");
5274 if (maxLevelAttribute) {
5275 maxLevel = pugi::cast<uint32_t>(maxLevelAttribute.value());
5276 } else {
5277 maxLevel = 0;
5278 lastStageLevel = minLevel;
5279 useLastStageLevel = true;
5280 }
5281
5282 pugi::xml_attribute multiplierAttribute =
5283 stageNode.attribute("multiplier");
5284 if (multiplierAttribute) {
5285 multiplier = pugi::cast<uint32_t>(multiplierAttribute.value());
5286 } else {
5287 multiplier = 1;
5288 }
5289
5290 if (useLastStageLevel) {
5291 stages[lastStageLevel] = multiplier;
5292 } else {
5293 for (uint32_t i = minLevel; i <= maxLevel; ++i) {
5294 stages[i] = multiplier;
5295 }
5296 }
5297 }
5298 }
5299 return true;
5300}
5301
5302void Game::playerInviteToParty(uint32_t playerId, uint32_t invitedId) {
5303 if (playerId == invitedId) {
5304 return;
5305 }
5306
5307 Player *player = getPlayerByID(playerId);
5308 if (!player) {
5309 return;
5310 }
5311
5312 Player *invitedPlayer = getPlayerByID(invitedId);
5313 if (!invitedPlayer || invitedPlayer->isInviting(player)) {
5314 return;
5315 }
5316
5317 if (invitedPlayer->getParty()) {
5318 std::ostringstream ss;
5319 ss << invitedPlayer->getName() << " is already in a party.";
5320 player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
5321 return;
5322 }
5323
5324 Party *party = player->getParty();
5325 if (!party) {
5326 party = new Party(player);
5327 } else if (party->getLeader() != player) {
5328 return;
5329 }
5330
5331 party->invitePlayer(*invitedPlayer);
5332}
5333
5334void Game::playerJoinParty(uint32_t playerId, uint32_t leaderId) {
5335 Player *player = getPlayerByID(playerId);
5336 if (!player) {
5337 return;
5338 }
5339
5340 Player *leader = getPlayerByID(leaderId);
5341 if (!leader || !leader->isInviting(player)) {
5342 return;
5343 }
5344
5345 Party *party = leader->getParty();
5346 if (!party || party->getLeader() != leader) {
5347 return;
5348 }
5349
5350 if (player->getParty()) {
5351 player->sendTextMessage(MESSAGE_INFO_DESCR, "You are already in a party.");
5352 return;
5353 }
5354
5355 party->joinParty(*player);
5356}
5357
5358void Game::playerRevokePartyInvitation(uint32_t playerId, uint32_t invitedId) {
5359 Player *player = getPlayerByID(playerId);
5360 if (!player) {
5361 return;
5362 }
5363
5364 Party *party = player->getParty();
5365 if (!party || party->getLeader() != player) {
5366 return;
5367 }
5368
5369 Player *invitedPlayer = getPlayerByID(invitedId);
5370 if (!invitedPlayer || !player->isInviting(invitedPlayer)) {
5371 return;
5372 }
5373
5374 party->revokeInvitation(*invitedPlayer);
5375}
5376
5377void Game::playerPassPartyLeadership(uint32_t playerId, uint32_t newLeaderId) {
5378 Player *player = getPlayerByID(playerId);
5379 if (!player) {
5380 return;
5381 }
5382
5383 Party *party = player->getParty();
5384 if (!party || party->getLeader() != player) {
5385 return;
5386 }
5387
5388 Player *newLeader = getPlayerByID(newLeaderId);
5389 if (!newLeader || !player->isPartner(newLeader)) {
5390 return;
5391 }
5392
5393 party->passPartyLeadership(newLeader);
5394}
5395
5396void Game::playerLeaveParty(uint32_t playerId) {
5397 Player *player = getPlayerByID(playerId);
5398 if (!player) {
5399 return;
5400 }
5401
5402 Party *party = player->getParty();
5403 if (!party || player->hasCondition(CONDITION_INFIGHT)) {
5404 return;
5405 }
5406
5407 party->leaveParty(player);
5408}
5409
5410void Game::playerEnableSharedPartyExperience(uint32_t playerId,
5411 bool sharedExpActive) {
5412 Player *player = getPlayerByID(playerId);
5413 if (!player) {
5414 return;
5415 }
5416
5417 Party *party = player->getParty();
5418 if (!party || (player->hasCondition(CONDITION_INFIGHT) &&
5419 player->getZone() != ZONE_PROTECTION)) {
5420 return;
5421 }
5422
5423 party->setSharedExperience(player, sharedExpActive);
5424}
5425
5426void Game::sendGuildMotd(uint32_t playerId) {
5427 Player *player = getPlayerByID(playerId);
5428 if (!player) {
5429 return;
5430 }
5431
5432 Guild *guild = player->getGuild();
5433 if (guild) {
5434 player->sendChannelMessage("Message of the Day", guild->getMotd(),
5435 TALKTYPE_CHANNEL_R1, CHANNEL_GUILD);
5436 }
5437}
5438
5439void Game::kickPlayer(uint32_t playerId, bool displayEffect) {
5440 Player *player = getPlayerByID(playerId);
5441 if (!player) {
5442 return;
5443 }
5444
5445 player->kickPlayer(displayEffect);
5446}
5447
5448void Game::playerReportRuleViolation(uint32_t playerId,
5449 const std::string &targetName,
5450 uint8_t reportType, uint8_t reportReason,
5451 const std::string &comment,
5452 const std::string &translation) {
5453 Player *player = getPlayerByID(playerId);
5454 if (!player) {
5455 return;
5456 }
5457
5458 g_events->eventPlayerOnReportRuleViolation(
5459 player, targetName, reportType, reportReason, comment, translation);
5460}
5461
5462void Game::playerReportBug(uint32_t playerId, const std::string &message,
5463 const Position &position, uint8_t category) {
5464 Player *player = getPlayerByID(playerId);
5465 if (!player) {
5466 return;
5467 }
5468
5469 g_events->eventPlayerOnReportBug(player, message, position, category);
5470}
5471
5472void Game::playerDebugAssert(uint32_t playerId, const std::string &assertLine,
5473 const std::string &date,
5474 const std::string &description,
5475 const std::string &comment) {
5476 Player *player = getPlayerByID(playerId);
5477 if (!player) {
5478 return;
5479 }
5480
5481 // TODO: move debug assertions to database
5482 FILE *file = fopen("client_assertions.txt", "a");
5483 if (file) {
5484 fprintf(file, "----- %s - %s (%s) -----\n",
5485 formatDate(time(nullptr)).c_str(), player->getName().c_str(),
5486 convertIPToString(player->getIP()).c_str());
5487 fprintf(file, "%s\n%s\n%s\n%s\n", assertLine.c_str(), date.c_str(),
5488 description.c_str(), comment.c_str());
5489 fclose(file);
5490 }
5491}
5492
5493void Game::playerLeaveMarket(uint32_t playerId) {
5494 Player *player = getPlayerByID(playerId);
5495 if (!player) {
5496 return;
5497 }
5498
5499 player->setInMarket(false);
5500}
5501
5502void Game::playerBrowseMarket(uint32_t playerId, uint16_t spriteId) {
5503 Player *player = getPlayerByID(playerId);
5504 if (!player) {
5505 return;
5506 }
5507
5508 if (!player->isInMarket()) {
5509 return;
5510 }
5511
5512 const ItemType &it = Item::items.getItemIdByClientId(spriteId);
5513 if (it.id == 0) {
5514 return;
5515 }
5516
5517 if (it.wareId == 0) {
5518 return;
5519 }
5520
5521 const MarketOfferList &buyOffers =
5522 IOMarket::getActiveOffers(MARKETACTION_BUY, it.id);
5523 const MarketOfferList &sellOffers =
5524 IOMarket::getActiveOffers(MARKETACTION_SELL, it.id);
5525 player->sendMarketBrowseItem(it.id, buyOffers, sellOffers);
5526 player->sendMarketDetail(it.id);
5527}
5528
5529void Game::playerBrowseMarketOwnOffers(uint32_t playerId) {
5530 Player *player = getPlayerByID(playerId);
5531 if (!player) {
5532 return;
5533 }
5534
5535 if (!player->isInMarket()) {
5536 return;
5537 }
5538
5539 const MarketOfferList &buyOffers =
5540 IOMarket::getOwnOffers(MARKETACTION_BUY, player->getGUID());
5541 const MarketOfferList &sellOffers =
5542 IOMarket::getOwnOffers(MARKETACTION_SELL, player->getGUID());
5543 player->sendMarketBrowseOwnOffers(buyOffers, sellOffers);
5544}
5545
5546void Game::playerBrowseMarketOwnHistory(uint32_t playerId) {
5547 Player *player = getPlayerByID(playerId);
5548 if (!player) {
5549 return;
5550 }
5551
5552 if (!player->isInMarket()) {
5553 return;
5554 }
5555
5556 const HistoryMarketOfferList &buyOffers =
5557 IOMarket::getOwnHistory(MARKETACTION_BUY, player->getGUID());
5558 const HistoryMarketOfferList &sellOffers =
5559 IOMarket::getOwnHistory(MARKETACTION_SELL, player->getGUID());
5560 player->sendMarketBrowseOwnHistory(buyOffers, sellOffers);
5561}
5562
5563void Game::playerCreateMarketOffer(uint32_t playerId, uint8_t type,
5564 uint16_t spriteId, uint16_t amount,
5565 uint32_t price, bool anonymous) {
5566 if (amount == 0 || amount > 64000) {
5567 return;
5568 }
5569
5570 if (price == 0 || price > 999999999) {
5571 return;
5572 }
5573
5574 if (type != MARKETACTION_BUY && type != MARKETACTION_SELL) {
5575 return;
5576 }
5577
5578 Player *player = getPlayerByID(playerId);
5579 if (!player) {
5580 return;
5581 }
5582
5583 if (!player->isInMarket()) {
5584 return;
5585 }
5586
5587 if (g_config.getBoolean(ConfigManager::MARKET_PREMIUM) &&
5588 !player->isPremium()) {
5589 player->sendMarketLeave();
5590 return;
5591 }
5592
5593 const ItemType &itt = Item::items.getItemIdByClientId(spriteId);
5594 if (itt.id == 0 || itt.wareId == 0) {
5595 return;
5596 }
5597
5598 const ItemType &it = Item::items.getItemIdByClientId(itt.wareId);
5599 if (it.id == 0 || it.wareId == 0) {
5600 return;
5601 }
5602
5603 if (!it.stackable && amount > 2000) {
5604 return;
5605 }
5606
5607 const uint32_t maxOfferCount =
5608 g_config.getNumber(ConfigManager::MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER);
5609 if (maxOfferCount != 0 &&
5610 IOMarket::getPlayerOfferCount(player->getGUID()) >= maxOfferCount) {
5611 return;
5612 }
5613
5614 uint64_t fee = (price / 100.) * amount;
5615 if (fee < 20) {
5616 fee = 20;
5617 } else if (fee > 1000) {
5618 fee = 1000;
5619 }
5620
5621 if (type == MARKETACTION_SELL) {
5622 if (fee > player->bankBalance) {
5623 return;
5624 }
5625
5626 DepotChest *depotChest =
5627 player->getDepotChest(player->getLastDepotId(), false);
5628 if (!depotChest) {
5629 return;
5630 }
5631
5632 std::forward_list<Item *> itemList =
5633 getMarketItemList(it.wareId, amount, depotChest, player->getInbox());
5634 if (itemList.empty()) {
5635 return;
5636 }
5637
5638 if (it.stackable) {
5639 uint16_t tmpAmount = amount;
5640 for (Item *item : itemList) {
5641 uint16_t removeCount =
5642 std::min<uint16_t>(tmpAmount, item->getItemCount());
5643 tmpAmount -= removeCount;
5644 internalRemoveItem(item, removeCount);
5645
5646 if (tmpAmount == 0) {
5647 break;
5648 }
5649 }
5650 } else {
5651 for (Item *item : itemList) {
5652 internalRemoveItem(item);
5653 }
5654 }
5655
5656 player->bankBalance -= fee;
5657 } else {
5658 uint64_t totalPrice = static_cast<uint64_t>(price) * amount;
5659 totalPrice += fee;
5660 if (totalPrice > player->bankBalance) {
5661 return;
5662 }
5663
5664 player->bankBalance -= totalPrice;
5665 }
5666
5667 IOMarket::createOffer(player->getGUID(), static_cast<MarketAction_t>(type),
5668 it.id, amount, price, anonymous);
5669
5670 player->sendMarketEnter(player->getLastDepotId());
5671 const MarketOfferList &buyOffers =
5672 IOMarket::getActiveOffers(MARKETACTION_BUY, it.id);
5673 const MarketOfferList &sellOffers =
5674 IOMarket::getActiveOffers(MARKETACTION_SELL, it.id);
5675 player->sendMarketBrowseItem(it.id, buyOffers, sellOffers);
5676}
5677
5678void Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp,
5679 uint16_t counter) {
5680 Player *player = getPlayerByID(playerId);
5681 if (!player) {
5682 return;
5683 }
5684
5685 if (!player->isInMarket()) {
5686 return;
5687 }
5688
5689 MarketOfferEx offer = IOMarket::getOfferByCounter(timestamp, counter);
5690 if (offer.id == 0 || offer.playerId != player->getGUID()) {
5691 return;
5692 }
5693
5694 if (offer.type == MARKETACTION_BUY) {
5695 player->bankBalance += static_cast<uint64_t>(offer.price) * offer.amount;
5696 player->sendMarketEnter(player->getLastDepotId());
5697 } else {
5698 const ItemType &it = Item::items[offer.itemId];
5699 if (it.id == 0) {
5700 return;
5701 }
5702
5703 if (it.stackable) {
5704 uint16_t tmpAmount = offer.amount;
5705 while (tmpAmount > 0) {
5706 int32_t stackCount = std::min<int32_t>(100, tmpAmount);
5707 Item *item = Item::CreateItem(it.id, stackCount);
5708 if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER,
5709 FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
5710 delete item;
5711 break;
5712 }
5713
5714 tmpAmount -= stackCount;
5715 }
5716 } else {
5717 int32_t subType;
5718 if (it.charges != 0) {
5719 subType = it.charges;
5720 } else {
5721 subType = -1;
5722 }
5723
5724 for (uint16_t i = 0; i < offer.amount; ++i) {
5725 Item *item = Item::CreateItem(it.id, subType);
5726 if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER,
5727 FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
5728 delete item;
5729 break;
5730 }
5731 }
5732 }
5733 }
5734
5735 IOMarket::moveOfferToHistory(offer.id, OFFERSTATE_CANCELLED);
5736 offer.amount = 0;
5737 offer.timestamp += g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION);
5738 player->sendMarketCancelOffer(offer);
5739 player->sendMarketEnter(player->getLastDepotId());
5740}
5741
5742void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp,
5743 uint16_t counter, uint16_t amount) {
5744 if (amount == 0 || amount > 64000) {
5745 return;
5746 }
5747
5748 Player *player = getPlayerByID(playerId);
5749 if (!player) {
5750 return;
5751 }
5752
5753 if (!player->isInMarket()) {
5754 return;
5755 }
5756
5757 MarketOfferEx offer = IOMarket::getOfferByCounter(timestamp, counter);
5758 if (offer.id == 0) {
5759 return;
5760 }
5761
5762 if (amount > offer.amount) {
5763 return;
5764 }
5765
5766 const ItemType &it = Item::items[offer.itemId];
5767 if (it.id == 0) {
5768 return;
5769 }
5770
5771 uint64_t totalPrice = static_cast<uint64_t>(offer.price) * amount;
5772
5773 if (offer.type == MARKETACTION_BUY) {
5774 DepotChest *depotChest =
5775 player->getDepotChest(player->getLastDepotId(), false);
5776 if (!depotChest) {
5777 return;
5778 }
5779
5780 std::forward_list<Item *> itemList =
5781 getMarketItemList(it.wareId, amount, depotChest, player->getInbox());
5782 if (itemList.empty()) {
5783 return;
5784 }
5785
5786 Player *buyerPlayer = getPlayerByGUID(offer.playerId);
5787 if (!buyerPlayer) {
5788 buyerPlayer = new Player(nullptr);
5789 if (!IOLoginData::loadPlayerById(buyerPlayer, offer.playerId)) {
5790 delete buyerPlayer;
5791 return;
5792 }
5793 }
5794
5795 if (it.stackable) {
5796 uint16_t tmpAmount = amount;
5797 for (Item *item : itemList) {
5798 uint16_t removeCount =
5799 std::min<uint16_t>(tmpAmount, item->getItemCount());
5800 tmpAmount -= removeCount;
5801 internalRemoveItem(item, removeCount);
5802
5803 if (tmpAmount == 0) {
5804 break;
5805 }
5806 }
5807 } else {
5808 for (Item *item : itemList) {
5809 internalRemoveItem(item);
5810 }
5811 }
5812
5813 player->bankBalance += totalPrice;
5814
5815 if (it.stackable) {
5816 uint16_t tmpAmount = amount;
5817 while (tmpAmount > 0) {
5818 uint16_t stackCount = std::min<uint16_t>(100, tmpAmount);
5819 Item *item = Item::CreateItem(it.id, stackCount);
5820 if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER,
5821 FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
5822 delete item;
5823 break;
5824 }
5825
5826 tmpAmount -= stackCount;
5827 }
5828 } else {
5829 int32_t subType;
5830 if (it.charges != 0) {
5831 subType = it.charges;
5832 } else {
5833 subType = -1;
5834 }
5835
5836 for (uint16_t i = 0; i < amount; ++i) {
5837 Item *item = Item::CreateItem(it.id, subType);
5838 if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER,
5839 FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
5840 delete item;
5841 break;
5842 }
5843 }
5844 }
5845
5846 if (buyerPlayer->isOffline()) {
5847 IOLoginData::savePlayer(buyerPlayer);
5848 delete buyerPlayer;
5849 } else {
5850 buyerPlayer->onReceiveMail();
5851 }
5852 } else {
5853 if (totalPrice > player->bankBalance) {
5854 return;
5855 }
5856
5857 player->bankBalance -= totalPrice;
5858
5859 if (it.stackable) {
5860 uint16_t tmpAmount = amount;
5861 while (tmpAmount > 0) {
5862 uint16_t stackCount = std::min<uint16_t>(100, tmpAmount);
5863 Item *item = Item::CreateItem(it.id, stackCount);
5864 if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER,
5865 FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
5866 delete item;
5867 break;
5868 }
5869
5870 tmpAmount -= stackCount;
5871 }
5872 } else {
5873 int32_t subType;
5874 if (it.charges != 0) {
5875 subType = it.charges;
5876 } else {
5877 subType = -1;
5878 }
5879
5880 for (uint16_t i = 0; i < amount; ++i) {
5881 Item *item = Item::CreateItem(it.id, subType);
5882 if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER,
5883 FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
5884 delete item;
5885 break;
5886 }
5887 }
5888 }
5889
5890 Player *sellerPlayer = getPlayerByGUID(offer.playerId);
5891 if (sellerPlayer) {
5892 sellerPlayer->bankBalance += totalPrice;
5893 } else {
5894 IOLoginData::increaseBankBalance(offer.playerId, totalPrice);
5895 }
5896
5897 player->onReceiveMail();
5898 }
5899
5900 const int32_t marketOfferDuration =
5901 g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION);
5902
5903 IOMarket::appendHistory(
5904 player->getGUID(),
5905 (offer.type == MARKETACTION_BUY ? MARKETACTION_SELL : MARKETACTION_BUY),
5906 offer.itemId, amount, offer.price, offer.timestamp + marketOfferDuration,
5907 OFFERSTATE_ACCEPTEDEX);
5908
5909 IOMarket::appendHistory(offer.playerId, offer.type, offer.itemId, amount,
5910 offer.price, offer.timestamp + marketOfferDuration,
5911 OFFERSTATE_ACCEPTED);
5912
5913 offer.amount -= amount;
5914
5915 if (offer.amount == 0) {
5916 IOMarket::deleteOffer(offer.id);
5917 } else {
5918 IOMarket::acceptOffer(offer.id, amount);
5919 }
5920
5921 player->sendMarketEnter(player->getLastDepotId());
5922 offer.timestamp += marketOfferDuration;
5923 player->sendMarketAcceptOffer(offer);
5924}
5925
5926void Game::parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode,
5927 const std::string &buffer) {
5928 Player *player = getPlayerByID(playerId);
5929 if (!player) {
5930 return;
5931 }
5932
5933 for (CreatureEvent *creatureEvent :
5934 player->getCreatureEvents(CREATURE_EVENT_EXTENDED_OPCODE)) {
5935 creatureEvent->executeExtendedOpcode(player, opcode, buffer);
5936 }
5937}
5938
5939std::forward_list<Item *> Game::getMarketItemList(uint16_t wareId,
5940 uint16_t sufficientCount,
5941 DepotChest *depotChest,
5942 Inbox *inbox) {
5943 std::forward_list<Item *> itemList;
5944 uint16_t count = 0;
5945
5946 std::list<Container *> containers{depotChest, inbox};
5947 do {
5948 Container *container = containers.front();
5949 containers.pop_front();
5950
5951 for (Item *item : container->getItemList()) {
5952 Container *c = item->getContainer();
5953 if (c && !c->empty()) {
5954 containers.push_back(c);
5955 continue;
5956 }
5957
5958 const ItemType &itemType = Item::items[item->getID()];
5959 if (itemType.wareId != wareId) {
5960 continue;
5961 }
5962
5963 if (c &&
5964 (!itemType.isContainer() || c->capacity() != itemType.maxItems)) {
5965 continue;
5966 }
5967
5968 if (!item->hasMarketAttributes()) {
5969 continue;
5970 }
5971
5972 itemList.push_front(item);
5973
5974 count += Item::countByType(item, -1);
5975 if (count >= sufficientCount) {
5976 return itemList;
5977 }
5978 }
5979 } while (!containers.empty());
5980 return std::forward_list<Item *>();
5981}
5982
5983void Game::forceAddCondition(uint32_t creatureId, Condition *condition) {
5984 Creature *creature = getCreatureByID(creatureId);
5985 if (!creature) {
5986 delete condition;
5987 return;
5988 }
5989
5990 creature->addCondition(condition, true);
5991}
5992
5993void Game::forceRemoveCondition(uint32_t creatureId, ConditionType_t type) {
5994 Creature *creature = getCreatureByID(creatureId);
5995 if (!creature) {
5996 return;
5997 }
5998
5999 creature->removeCondition(type, true);
6000}
6001
6002void Game::sendOfflineTrainingDialog(Player *player) {
6003 if (!player) {
6004 return;
6005 }
6006
6007 if (!player->hasModalWindowOpen(offlineTrainingWindow.id)) {
6008 player->sendModalWindow(offlineTrainingWindow);
6009 }
6010}
6011
6012void Game::playerAnswerModalWindow(uint32_t playerId, uint32_t modalWindowId,
6013 uint8_t button, uint8_t choice) {
6014 Player *player = getPlayerByID(playerId);
6015 if (!player) {
6016 return;
6017 }
6018
6019 if (!player->hasModalWindowOpen(modalWindowId)) {
6020 return;
6021 }
6022
6023 player->onModalWindowHandled(modalWindowId);
6024
6025 // offline training, hardcoded
6026 if (modalWindowId == std::numeric_limits<uint32_t>::max()) {
6027 if (button == 1) {
6028 if (choice == SKILL_SWORD || choice == SKILL_AXE ||
6029 choice == SKILL_CLUB || choice == SKILL_DISTANCE ||
6030 choice == SKILL_MAGLEVEL) {
6031 BedItem *bedItem = player->getBedItem();
6032 if (bedItem && bedItem->sleep(player)) {
6033 player->setOfflineTrainingSkill(choice);
6034 return;
6035 }
6036 }
6037 } else {
6038 player->sendTextMessage(MESSAGE_EVENT_ADVANCE,
6039 "Offline training aborted.");
6040 }
6041
6042 player->setBedItem(nullptr);
6043 } else {
6044 for (auto creatureEvent :
6045 player->getCreatureEvents(CREATURE_EVENT_MODALWINDOW)) {
6046 creatureEvent->executeModalWindow(player, modalWindowId, button, choice);
6047 }
6048 }
6049}
6050
6051void Game::addPlayer(Player *player) {
6052 const std::string &lowercase_name = asLowerCaseString(player->getName());
6053 mappedPlayerNames[lowercase_name] = player;
6054 mappedPlayerGuids[player->getGUID()] = player;
6055 wildcardTree.insert(lowercase_name);
6056 players[player->getID()] = player;
6057}
6058
6059void Game::removePlayer(Player *player) {
6060 const std::string &lowercase_name = asLowerCaseString(player->getName());
6061 mappedPlayerNames.erase(lowercase_name);
6062 mappedPlayerGuids.erase(player->getGUID());
6063 wildcardTree.remove(lowercase_name);
6064 players.erase(player->getID());
6065}
6066
6067void Game::addNpc(Npc *npc) { npcs[npc->getID()] = npc; }
6068
6069void Game::removeNpc(Npc *npc) { npcs.erase(npc->getID()); }
6070
6071void Game::addMonster(Monster *monster) {
6072 monsters[monster->getID()] = monster;
6073}
6074
6075void Game::removeMonster(Monster *monster) { monsters.erase(monster->getID()); }
6076
6077Guild *Game::getGuild(uint32_t id) const {
6078 auto it = guilds.find(id);
6079 if (it == guilds.end()) {
6080 return nullptr;
6081 }
6082 return it->second;
6083}
6084
6085void Game::addGuild(Guild *guild) { guilds[guild->getId()] = guild; }
6086
6087void Game::removeGuild(uint32_t guildId) { guilds.erase(guildId); }
6088
6089void Game::decreaseBrowseFieldRef(const Position &pos) {
6090 Tile *tile = map.getTile(pos.x, pos.y, pos.z);
6091 if (!tile) {
6092 return;
6093 }
6094
6095 auto it = browseFields.find(tile);
6096 if (it != browseFields.end()) {
6097 it->second->decrementReferenceCounter();
6098 }
6099}
6100
6101void Game::internalRemoveItems(std::vector<Item *> itemList, uint32_t amount,
6102 bool stackable) {
6103 if (stackable) {
6104 for (Item *item : itemList) {
6105 if (item->getItemCount() > amount) {
6106 internalRemoveItem(item, amount);
6107 break;
6108 } else {
6109 amount -= item->getItemCount();
6110 internalRemoveItem(item);
6111 }
6112 }
6113 } else {
6114 for (Item *item : itemList) {
6115 internalRemoveItem(item);
6116 }
6117 }
6118}
6119
6120BedItem *Game::getBedBySleeper(uint32_t guid) const {
6121 auto it = bedSleepersMap.find(guid);
6122 if (it == bedSleepersMap.end()) {
6123 return nullptr;
6124 }
6125 return it->second;
6126}
6127
6128void Game::setBedSleeper(BedItem *bed, uint32_t guid) {
6129 bedSleepersMap[guid] = bed;
6130}
6131
6132void Game::removeBedSleeper(uint32_t guid) {
6133 auto it = bedSleepersMap.find(guid);
6134 if (it != bedSleepersMap.end()) {
6135 bedSleepersMap.erase(it);
6136 }
6137}
6138
6139Item *Game::getUniqueItem(uint16_t uniqueId) {
6140 auto it = uniqueItems.find(uniqueId);
6141 if (it == uniqueItems.end()) {
6142 return nullptr;
6143 }
6144 return it->second;
6145}
6146
6147bool Game::addUniqueItem(uint16_t uniqueId, Item *item) {
6148 auto result = uniqueItems.emplace(uniqueId, item);
6149 if (!result.second) {
6150 std::cout << "Duplicate unique id: " << uniqueId << std::endl;
6151 }
6152 return result.second;
6153}
6154
6155void Game::removeUniqueItem(uint16_t uniqueId) {
6156 auto it = uniqueItems.find(uniqueId);
6157 if (it != uniqueItems.end()) {
6158 uniqueItems.erase(it);
6159 }
6160}
6161
6162bool Game::reload(ReloadTypes_t reloadType) {
6163 switch (reloadType) {
6164 case RELOAD_TYPE_ACTIONS:
6165 return g_actions->reload();
6166 case RELOAD_TYPE_AURAS:
6167 return auras.reload();
6168 case RELOAD_TYPE_CHAT:
6169 return g_chat->load();
6170 case RELOAD_TYPE_CONFIG:
6171 return g_config.reload();
6172 case RELOAD_TYPE_CREATURESCRIPTS: {
6173 g_creatureEvents->reload();
6174 g_creatureEvents->removeInvalidEvents();
6175 return true;
6176 }
6177 case RELOAD_TYPE_EVENTS:
6178 return g_events->load();
6179 case RELOAD_TYPE_GLOBALEVENTS:
6180 return g_globalEvents->reload();
6181 case RELOAD_TYPE_ITEMS:
6182 return Item::items.reload();
6183 case RELOAD_TYPE_MONSTERS:
6184 return g_monsters.reload();
6185 case RELOAD_TYPE_MOUNTS:
6186 return mounts.reload();
6187 case RELOAD_TYPE_MOVEMENTS:
6188 return g_moveEvents->reload();
6189 case RELOAD_TYPE_NPCS: {
6190 Npcs::reload();
6191 return true;
6192 }
6193
6194 case RELOAD_TYPE_QUESTS:
6195 return quests.reload();
6196 case RELOAD_TYPE_RAIDS:
6197 return raids.reload() && raids.startup();
6198 case RELOAD_TYPE_SHADERS:
6199 return shaders.reload();
6200 case RELOAD_TYPE_RUNEEFFECTS:
6201 if (!runeEffects.reload()) {
6202 return false;
6203 }
6204 return true;
6205
6206 case RELOAD_TYPE_OUTFITS: {
6207 if (!Outfits::getInstance().reload()) {
6208 return false;
6209 }
6210 for (const auto &it : players) {
6211 if (Player *player = it.second) {
6212 if (player->isRemoved()) {
6213 continue;
6214 }
6215 Outfit_t outfit = player->getCurrentOutfit();
6216 const Outfit *outfitDef = Outfits::getInstance().getOutfitByLookType(player->getSex(), outfit.lookType);
6217 if (outfitDef) {
6218 for (int d = 0; d < OUTFIT_DIRECTIONS; ++d) {
6219 outfit.offsets[d] = outfitDef->offsets[d];
6220 }
6221 }
6222 SpectatorVec spectators;
6223 map.getSpectators(spectators, player->getPosition(), true);
6224 for (Creature *spectator : spectators) {
6225 if (Player *tmpPlayer = spectator->getPlayer()) {
6226 tmpPlayer->sendCreatureChangeOutfit(player, outfit);
6227 }
6228 }
6229 }
6230 }
6231 return true;
6232 }
6233
6234 case RELOAD_TYPE_SPELLS: {
6235 if (!g_spells->reload()) {
6236 std::cout << "[Error - Game::reload] Failed to reload spells."
6237 << std::endl;
6238 std::terminate();
6239 } else if (!g_monsters.reload()) {
6240 std::cout << "[Error - Game::reload] Failed to reload monsters."
6241 << std::endl;
6242 std::terminate();
6243 }
6244 return true;
6245 }
6246
6247 case RELOAD_TYPE_TALKACTIONS:
6248 return g_talkActions->reload();
6249
6250 case RELOAD_TYPE_WEAPONS: {
6251 bool results = g_weapons->reload();
6252 g_weapons->loadDefaults();
6253 return results;
6254 }
6255
6256 case RELOAD_TYPE_WINGS:
6257 return wings.reload();
6258
6259 case RELOAD_TYPE_SCRIPTS: {
6260 // commented out stuff is TODO, once we approach further in revscriptsys
6261 g_actions->clear(true);
6262 g_creatureEvents->clear(true);
6263 g_moveEvents->clear(true);
6264 g_talkActions->clear(true);
6265 g_globalEvents->clear(true);
6266 g_weapons->clear(true);
6267 g_weapons->loadDefaults();
6268 g_spells->clear(true);
6269 g_scripts->loadScripts("scripts", false, true);
6270 g_creatureEvents->removeInvalidEvents();
6271 /*
6272 Npcs::reload();
6273 raids.reload() && raids.startup();
6274 Item::items.reload();
6275 quests.reload();
6276 mounts.reload();
6277 auras.reload();
6278 wings.reload();
6279 shaders.reload();
6280 g_config.reload();
6281 g_events->load();
6282 g_chat->load();
6283 */
6284 return true;
6285 }
6286
6287 default: {
6288 if (!g_spells->reload()) {
6289 std::cout << "[Error - Game::reload] Failed to reload spells."
6290 << std::endl;
6291 std::terminate();
6292 } else if (!g_monsters.reload()) {
6293 std::cout << "[Error - Game::reload] Failed to reload monsters."
6294 << std::endl;
6295 std::terminate();
6296 }
6297
6298 g_actions->reload();
6299 g_config.reload();
6300 g_creatureEvents->reload();
6301 g_monsters.reload();
6302 g_moveEvents->reload();
6303 Npcs::reload();
6304 raids.reload() && raids.startup();
6305 g_talkActions->reload();
6306 Item::items.reload();
6307 g_weapons->reload();
6308 g_weapons->clear(true);
6309 g_weapons->loadDefaults();
6310 quests.reload();
6311 auras.reload();
6312 mounts.reload();
6313 wings.reload();
6314 shaders.reload();
6315 runeEffects.reload();
6316 g_globalEvents->reload();
6317 g_events->load();
6318 g_chat->load();
6319 g_actions->clear(true);
6320 g_creatureEvents->clear(true);
6321 g_moveEvents->clear(true);
6322 g_talkActions->clear(true);
6323 g_globalEvents->clear(true);
6324 g_spells->clear(true);
6325 g_scripts->loadScripts("scripts", false, true);
6326 g_creatureEvents->removeInvalidEvents();
6327 return true;
6328 }
6329 }
6330 return true;
6331}
6332