1/**
2 * The Forgotten Server - a free and open-source MMORPG server emulator
3 * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "otpch.h"
21
22#include <boost/range/adaptor/reversed.hpp>
23
24#include "tile.h"
25
26#include "creature.h"
27#include "combat.h"
28#include "game.h"
29#include "mailbox.h"
30#include "monster.h"
31#include "movement.h"
32#include "teleport.h"
33#include "trashholder.h"
34#include "configmanager.h"
35
36extern Game g_game;
37extern MoveEvents* g_moveEvents;
38extern ConfigManager g_config;
39
40StaticTile real_nullptr_tile(0xFFFF, 0xFFFF, 0xFF);
41Tile& Tile::nullptr_tile = real_nullptr_tile;
42
43bool Tile::hasProperty(ITEMPROPERTY prop) const
44{
45 if (ground && ground->hasProperty(prop)) {
46 return true;
47 }
48
49 if (const TileItemVector* items = getItemList()) {
50 for (const Item* item : *items) {
51 if (item->hasProperty(prop)) {
52 return true;
53 }
54 }
55 }
56 return false;
57}
58
59bool Tile::hasProperty(const Item* exclude, ITEMPROPERTY prop) const
60{
61 assert(exclude);
62
63 if (ground && exclude != ground && ground->hasProperty(prop)) {
64 return true;
65 }
66
67 if (const TileItemVector* items = getItemList()) {
68 for (const Item* item : *items) {
69 if (item != exclude && item->hasProperty(prop)) {
70 return true;
71 }
72 }
73 }
74
75 return false;
76}
77
78bool Tile::hasHeight(uint32_t n) const
79{
80 uint32_t height = 0;
81
82 if (ground) {
83 if (ground->hasProperty(CONST_PROP_HASHEIGHT)) {
84 ++height;
85 }
86
87 if (n == height) {
88 return true;
89 }
90 }
91
92 if (const TileItemVector* items = getItemList()) {
93 for (const Item* item : *items) {
94 if (item->hasProperty(CONST_PROP_HASHEIGHT)) {
95 ++height;
96 }
97
98 if (n == height) {
99 return true;
100 }
101 }
102 }
103 return false;
104}
105
106size_t Tile::getCreatureCount() const
107{
108 if (const CreatureVector* creatures = getCreatures()) {
109 return creatures->size();
110 }
111 return 0;
112}
113
114size_t Tile::getItemCount() const
115{
116 if (const TileItemVector* items = getItemList()) {
117 return items->size();
118 }
119 return 0;
120}
121
122uint32_t Tile::getTopItemCount() const
123{
124 if (const TileItemVector* items = getItemList()) {
125 return items->getTopItemCount();
126 }
127 return 0;
128}
129
130uint32_t Tile::getDownItemCount() const
131{
132 if (const TileItemVector* items = getItemList()) {
133 return items->getDownItemCount();
134 }
135 return 0;
136}
137
138std::string Tile::getDescription(int32_t) const
139{
140 return "You dont know why, but you cant see anything!";
141}
142
143Teleport* Tile::getTeleportItem() const
144{
145 if (!hasFlag(TILESTATE_TELEPORT)) {
146 return nullptr;
147 }
148
149 if (const TileItemVector* items = getItemList()) {
150 for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) {
151 if ((*it)->getTeleport()) {
152 return (*it)->getTeleport();
153 }
154 }
155 }
156 return nullptr;
157}
158
159MagicField* Tile::getFieldItem() const
160{
161 if (!hasFlag(TILESTATE_MAGICFIELD)) {
162 return nullptr;
163 }
164
165 if (ground && ground->getMagicField()) {
166 return ground->getMagicField();
167 }
168
169 if (const TileItemVector* items = getItemList()) {
170 for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) {
171 if ((*it)->getMagicField()) {
172 return (*it)->getMagicField();
173 }
174 }
175 }
176 return nullptr;
177}
178
179TrashHolder* Tile::getTrashHolder() const
180{
181 if (!hasFlag(TILESTATE_TRASHHOLDER)) {
182 return nullptr;
183 }
184
185 if (ground && ground->getTrashHolder()) {
186 return ground->getTrashHolder();
187 }
188
189 if (const TileItemVector* items = getItemList()) {
190 for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) {
191 if ((*it)->getTrashHolder()) {
192 return (*it)->getTrashHolder();
193 }
194 }
195 }
196 return nullptr;
197}
198
199Mailbox* Tile::getMailbox() const
200{
201 if (!hasFlag(TILESTATE_MAILBOX)) {
202 return nullptr;
203 }
204
205 if (ground && ground->getMailbox()) {
206 return ground->getMailbox();
207 }
208
209 if (const TileItemVector* items = getItemList()) {
210 for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) {
211 if ((*it)->getMailbox()) {
212 return (*it)->getMailbox();
213 }
214 }
215 }
216 return nullptr;
217}
218
219BedItem* Tile::getBedItem() const
220{
221 if (!hasFlag(TILESTATE_BED)) {
222 return nullptr;
223 }
224
225 if (ground && ground->getBed()) {
226 return ground->getBed();
227 }
228
229 if (const TileItemVector* items = getItemList()) {
230 for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) {
231 if ((*it)->getBed()) {
232 return (*it)->getBed();
233 }
234 }
235 }
236 return nullptr;
237}
238
239Creature* Tile::getTopCreature() const
240{
241 if (const CreatureVector* creatures = getCreatures()) {
242 if (!creatures->empty()) {
243 return *creatures->begin();
244 }
245 }
246 return nullptr;
247}
248
249const Creature* Tile::getBottomCreature() const
250{
251 if (const CreatureVector* creatures = getCreatures()) {
252 if (!creatures->empty()) {
253 return *creatures->rbegin();
254 }
255 }
256 return nullptr;
257}
258
259Creature* Tile::getTopVisibleCreature(const Creature* creature) const
260{
261 if (const CreatureVector* creatures = getCreatures()) {
262 if (creature) {
263 const Player* player = creature->getPlayer();
264 if (player && player->isAccessPlayer()) {
265 return getTopCreature();
266 }
267
268 for (Creature* tileCreature : *creatures) {
269 if (creature->canSeeCreature(tileCreature)) {
270 return tileCreature;
271 }
272 }
273 } else {
274 for (Creature* tileCreature : *creatures) {
275 if (!tileCreature->isInvisible()) {
276 const Player* player = tileCreature->getPlayer();
277 if (!player || !player->isInGhostMode()) {
278 return tileCreature;
279 }
280 }
281 }
282 }
283 }
284 return nullptr;
285}
286
287const Creature* Tile::getBottomVisibleCreature(const Creature* creature) const
288{
289 if (const CreatureVector* creatures = getCreatures()) {
290 if (creature) {
291 const Player* player = creature->getPlayer();
292 if (player && player->isAccessPlayer()) {
293 return getBottomCreature();
294 }
295
296 for (auto it = creatures->rbegin(), end = creatures->rend(); it != end; ++it) {
297 if (creature->canSeeCreature(*it)) {
298 return *it;
299 }
300 }
301 } else {
302 for (auto it = creatures->rbegin(), end = creatures->rend(); it != end; ++it) {
303 if (!(*it)->isInvisible()) {
304 const Player* player = (*it)->getPlayer();
305 if (!player || !player->isInGhostMode()) {
306 return *it;
307 }
308 }
309 }
310 }
311 }
312 return nullptr;
313}
314
315Item* Tile::getTopDownItem() const
316{
317 if (const TileItemVector* items = getItemList()) {
318 return items->getTopDownItem();
319 }
320 return nullptr;
321}
322
323Item* Tile::getTopTopItem() const
324{
325 if (const TileItemVector* items = getItemList()) {
326 return items->getTopTopItem();
327 }
328 return nullptr;
329}
330
331Item* Tile::getItemByTopOrder(int32_t topOrder)
332{
333 //topOrder:
334 //1: borders
335 //2: ladders, signs, splashes
336 //3: doors etc
337 //4: creatures
338 if (TileItemVector* items = getItemList()) {
339 for (auto it = ItemVector::const_reverse_iterator(items->getEndTopItem()), end = ItemVector::const_reverse_iterator(items->getBeginTopItem()); it != end; ++it) {
340 if (Item::items[(*it)->getID()].alwaysOnTopOrder == topOrder) {
341 return (*it);
342 }
343 }
344 }
345 return nullptr;
346}
347
348Thing* Tile::getTopVisibleThing(const Creature* creature)
349{
350 Thing* thing = getTopVisibleCreature(creature);
351 if (thing) {
352 return thing;
353 }
354
355 TileItemVector* items = getItemList();
356 if (items) {
357 for (ItemVector::const_iterator it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) {
358 const ItemType& iit = Item::items[(*it)->getID()];
359 if (!iit.lookThrough) {
360 return (*it);
361 }
362 }
363
364 for (auto it = ItemVector::const_reverse_iterator(items->getEndTopItem()), end = ItemVector::const_reverse_iterator(items->getBeginTopItem()); it != end; ++it) {
365 const ItemType& iit = Item::items[(*it)->getID()];
366 if (!iit.lookThrough) {
367 return (*it);
368 }
369 }
370 }
371
372 return ground;
373}
374
375void Tile::onAddTileItem(Item* item)
376{
377 if (item->hasProperty(CONST_PROP_MOVEABLE) || item->getContainer()) {
378 auto it = g_game.browseFields.find(this);
379 if (it != g_game.browseFields.end()) {
380 it->second->addItemBack(item);
381 item->setParent(this);
382 }
383 }
384
385 setTileFlags(item);
386
387 const Position& cylinderMapPos = getPosition();
388
389 SpectatorVec spectators;
390 g_game.map.getSpectators(spectators, cylinderMapPos, true);
391
392 //send to client
393 for (Creature* spectator : spectators) {
394 if (Player* tmpPlayer = spectator->getPlayer()) {
395 tmpPlayer->sendAddTileItem(this, cylinderMapPos, item);
396 }
397 }
398
399 //event methods
400 for (Creature* spectator : spectators) {
401 spectator->onAddTileItem(this, cylinderMapPos);
402 }
403
404 if ((!hasFlag(TILESTATE_PROTECTIONZONE) || (g_config.getBoolean(ConfigManager::CLEAN_PROTECTION_ZONES) && hasFlag(TILESTATE_PROTECTIONZONE))) && item->isCleanable()) {
405 if (!dynamic_cast<HouseTile*>(this)) {
406 g_game.addTileToClean(this);
407 }
408 }
409}
410
411void Tile::onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newItem, const ItemType& newType)
412{
413 if (newItem->hasProperty(CONST_PROP_MOVEABLE) || newItem->getContainer()) {
414 auto it = g_game.browseFields.find(this);
415 if (it != g_game.browseFields.end()) {
416 int32_t index = it->second->getThingIndex(oldItem);
417 if (index != -1) {
418 it->second->replaceThing(index, newItem);
419 newItem->setParent(this);
420 }
421 }
422 } else if (oldItem->hasProperty(CONST_PROP_MOVEABLE) || oldItem->getContainer()) {
423 auto it = g_game.browseFields.find(this);
424 if (it != g_game.browseFields.end()) {
425 Cylinder* oldParent = oldItem->getParent();
426 it->second->removeThing(oldItem, oldItem->getItemCount());
427 oldItem->setParent(oldParent);
428 }
429 }
430
431 const Position& cylinderMapPos = getPosition();
432
433 SpectatorVec spectators;
434 g_game.map.getSpectators(spectators, cylinderMapPos, true);
435
436 //send to client
437 for (Creature* spectator : spectators) {
438 if (Player* tmpPlayer = spectator->getPlayer()) {
439 tmpPlayer->sendUpdateTileItem(this, cylinderMapPos, newItem);
440 }
441 }
442
443 //event methods
444 for (Creature* spectator : spectators) {
445 spectator->onUpdateTileItem(this, cylinderMapPos, oldItem, oldType, newItem, newType);
446 }
447}
448
449void Tile::onRemoveTileItem(const SpectatorVec& spectators, const std::vector<int32_t>& oldStackPosVector, Item* item)
450{
451 if (item->hasProperty(CONST_PROP_MOVEABLE) || item->getContainer()) {
452 auto it = g_game.browseFields.find(this);
453 if (it != g_game.browseFields.end()) {
454 it->second->removeThing(item, item->getItemCount());
455 }
456 }
457
458 resetTileFlags(item);
459
460 const Position& cylinderMapPos = getPosition();
461 const ItemType& iType = Item::items[item->getID()];
462
463 //send to client
464 size_t i = 0;
465 for (Creature* spectator : spectators) {
466 if (Player* tmpPlayer = spectator->getPlayer()) {
467 tmpPlayer->sendRemoveTileThing(cylinderMapPos, oldStackPosVector[i++]);
468 }
469 }
470
471 //event methods
472 for (Creature* spectator : spectators) {
473 spectator->onRemoveTileItem(this, cylinderMapPos, iType, item);
474 }
475
476 if (!hasFlag(TILESTATE_PROTECTIONZONE) || (g_config.getBoolean(ConfigManager::CLEAN_PROTECTION_ZONES) && hasFlag(TILESTATE_PROTECTIONZONE))) {
477 auto it = getItemList();
478 if (it->empty()) {
479 g_game.removeTileToClean(this);
480 return;
481 }
482
483 bool ret = false;
484 for (auto toCheck : *it) {
485 if (toCheck->isCleanable()) {
486 ret = true;
487 break;
488 }
489 }
490
491 if (!ret) {
492 g_game.removeTileToClean(this);
493 }
494 }
495}
496
497void Tile::onUpdateTile(const SpectatorVec& spectators)
498{
499 const Position& cylinderMapPos = getPosition();
500
501 //send to clients
502 for (Creature* spectator : spectators) {
503 spectator->getPlayer()->sendUpdateTile(this, cylinderMapPos);
504 }
505}
506
507ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags, Creature*) const
508{
509 if (const Creature* creature = thing.getCreature()) {
510 if (hasBitSet(FLAG_NOLIMIT, flags)) {
511 return RETURNVALUE_NOERROR;
512 }
513
514 if (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_FLOORCHANGE | TILESTATE_TELEPORT)) {
515 return RETURNVALUE_NOTPOSSIBLE;
516 }
517
518 if (ground == nullptr) {
519 return RETURNVALUE_NOTPOSSIBLE;
520 }
521
522 if (const Monster* monster = creature->getMonster()) {
523 if (hasFlag(TILESTATE_PROTECTIONZONE | TILESTATE_FLOORCHANGE | TILESTATE_TELEPORT)) {
524 return RETURNVALUE_NOTPOSSIBLE;
525 }
526
527 const CreatureVector* creatures = getCreatures();
528 if (monster->canPushCreatures() && !monster->isSummon()) {
529 if (creatures) {
530 for (Creature* tileCreature : *creatures) {
531 if (tileCreature->getPlayer() && tileCreature->getPlayer()->isInGhostMode()) {
532 continue;
533 }
534
535 const Monster* creatureMonster = tileCreature->getMonster();
536 if (!creatureMonster || !tileCreature->isPushable() ||
537 (creatureMonster->isSummon() && creatureMonster->getMaster()->getPlayer())) {
538 return RETURNVALUE_NOTPOSSIBLE;
539 }
540 }
541 }
542 } else if (creatures && !creatures->empty()) {
543 for (const Creature* tileCreature : *creatures) {
544 if (!tileCreature->isInGhostMode()) {
545 return RETURNVALUE_NOTENOUGHROOM;
546 }
547 }
548 }
549
550 if (hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)) {
551 return RETURNVALUE_NOTPOSSIBLE;
552 }
553
554 if (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH)) {
555 return RETURNVALUE_NOTPOSSIBLE;
556 }
557
558 if (hasFlag(TILESTATE_BLOCKSOLID) || (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_NOFIELDBLOCKPATH))) {
559 if (!(monster->canPushItems() || hasBitSet(FLAG_IGNOREBLOCKITEM, flags))) {
560 return RETURNVALUE_NOTPOSSIBLE;
561 }
562 }
563
564 MagicField* field = getFieldItem();
565 if (!field || field->isBlocking() || field->getDamage() == 0) {
566 return RETURNVALUE_NOERROR;
567 }
568
569 CombatType_t combatType = field->getCombatType();
570
571 //There is 3 options for a monster to enter a magic field
572 //1) Monster is immune
573 if (!monster->isImmune(combatType)) {
574 //1) Monster is able to walk over field type
575 //2) Being attacked while random stepping will make it ignore field damages
576 if (hasBitSet(FLAG_IGNOREFIELDDAMAGE, flags)) {
577 if (!(monster->canWalkOnFieldType(combatType) || monster->isIgnoringFieldDamage())) {
578 return RETURNVALUE_NOTPOSSIBLE;
579 }
580 } else {
581 return RETURNVALUE_NOTPOSSIBLE;
582 }
583 }
584
585 return RETURNVALUE_NOERROR;
586 }
587
588 const CreatureVector* creatures = getCreatures();
589 if (const Player* player = creature->getPlayer()) {
590 if (creatures && !creatures->empty() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags) && !player->isAccessPlayer()) {
591 for (const Creature* tileCreature : *creatures) {
592 if (!player->canWalkthrough(tileCreature)) {
593 return RETURNVALUE_NOTPOSSIBLE;
594 }
595 }
596 }
597
598 if (player->getParent() == nullptr && hasFlag(TILESTATE_NOLOGOUT)) {
599 //player is trying to login to a "no logout" tile
600 return RETURNVALUE_NOTPOSSIBLE;
601 }
602
603 const Tile* playerTile = player->getTile();
604 if (playerTile && player->isPzLocked()) {
605 if (!playerTile->hasFlag(TILESTATE_PVPZONE)) {
606 //player is trying to enter a pvp zone while being pz-locked
607 if (hasFlag(TILESTATE_PVPZONE)) {
608 return RETURNVALUE_PLAYERISPZLOCKEDENTERPVPZONE;
609 }
610 } else if (!hasFlag(TILESTATE_PVPZONE)) {
611 // player is trying to leave a pvp zone while being pz-locked
612 return RETURNVALUE_PLAYERISPZLOCKEDLEAVEPVPZONE;
613 }
614
615 if ((!playerTile->hasFlag(TILESTATE_NOPVPZONE) && hasFlag(TILESTATE_NOPVPZONE)) ||
616 (!playerTile->hasFlag(TILESTATE_PROTECTIONZONE) && hasFlag(TILESTATE_PROTECTIONZONE))) {
617 // player is trying to enter a non-pvp/protection zone while being pz-locked
618 return RETURNVALUE_PLAYERISPZLOCKED;
619 }
620 }
621 } else if (creatures && !creatures->empty() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags)) {
622 for (const Creature* tileCreature : *creatures) {
623 if (!tileCreature->isInGhostMode()) {
624 return RETURNVALUE_NOTENOUGHROOM;
625 }
626 }
627 }
628
629 if (!hasBitSet(FLAG_IGNOREBLOCKITEM, flags)) {
630 //If the FLAG_IGNOREBLOCKITEM bit isn't set we dont have to iterate every single item
631 if (hasFlag(TILESTATE_BLOCKSOLID)) {
632 return RETURNVALUE_NOTENOUGHROOM;
633 }
634 } else {
635 //FLAG_IGNOREBLOCKITEM is set
636 if (ground) {
637 const ItemType& iiType = Item::items[ground->getID()];
638 if (iiType.blockSolid && (!iiType.moveable || ground->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID))) {
639 return RETURNVALUE_NOTPOSSIBLE;
640 }
641 }
642
643 if (const auto items = getItemList()) {
644 for (const Item* item : *items) {
645 const ItemType& iiType = Item::items[item->getID()];
646 if (iiType.blockSolid && (!iiType.moveable || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID))) {
647 return RETURNVALUE_NOTPOSSIBLE;
648 }
649 }
650 }
651 }
652 } else if (const Item* item = thing.getItem()) {
653 const TileItemVector* items = getItemList();
654 if (items && items->size() >= 0xFFFF) {
655 return RETURNVALUE_NOTPOSSIBLE;
656 }
657
658 if (hasBitSet(FLAG_NOLIMIT, flags)) {
659 return RETURNVALUE_NOERROR;
660 }
661
662 bool itemIsHangable = item->isHangable();
663 if (ground == nullptr && !itemIsHangable) {
664 return RETURNVALUE_NOTPOSSIBLE;
665 }
666
667 const CreatureVector* creatures = getCreatures();
668 if (creatures && !creatures->empty() && item->isBlocking() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags)) {
669 for (const Creature* tileCreature : *creatures) {
670 if (!tileCreature->isInGhostMode()) {
671 return RETURNVALUE_NOTENOUGHROOM;
672 }
673 }
674 }
675
676 if (itemIsHangable && hasFlag(TILESTATE_SUPPORTS_HANGABLE)) {
677 if (items) {
678 for (const Item* tileItem : *items) {
679 if (tileItem->isHangable()) {
680 return RETURNVALUE_NEEDEXCHANGE;
681 }
682 }
683 }
684 } else {
685 if (ground) {
686 const ItemType& iiType = Item::items[ground->getID()];
687 if (iiType.blockSolid) {
688 if (!iiType.allowPickupable || item->isMagicField() || item->isBlocking()) {
689 if (!item->isPickupable()) {
690 return RETURNVALUE_NOTENOUGHROOM;
691 }
692
693 if (!iiType.hasHeight || iiType.pickupable || iiType.isBed()) {
694 return RETURNVALUE_NOTENOUGHROOM;
695 }
696 }
697 }
698 }
699
700 if (items) {
701 for (const Item* tileItem : *items) {
702 const ItemType& iiType = Item::items[tileItem->getID()];
703 if (!iiType.blockSolid) {
704 continue;
705 }
706
707 if (iiType.allowPickupable && !item->isMagicField() && !item->isBlocking()) {
708 continue;
709 }
710
711 if (!item->isPickupable()) {
712 return RETURNVALUE_NOTENOUGHROOM;
713 }
714
715 if (!iiType.hasHeight || iiType.pickupable || iiType.isBed()) {
716 return RETURNVALUE_NOTENOUGHROOM;
717 }
718 }
719 }
720 }
721 }
722 return RETURNVALUE_NOERROR;
723}
724
725ReturnValue Tile::queryMaxCount(int32_t, const Thing&, uint32_t count, uint32_t& maxQueryCount, uint32_t) const
726{
727 maxQueryCount = std::max<uint32_t>(1, count);
728 return RETURNVALUE_NOERROR;
729}
730
731ReturnValue Tile::queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const
732{
733 int32_t index = getThingIndex(&thing);
734 if (index == -1) {
735 return RETURNVALUE_NOTPOSSIBLE;
736 }
737
738 const Item* item = thing.getItem();
739 if (item == nullptr) {
740 return RETURNVALUE_NOTPOSSIBLE;
741 }
742
743 if (count == 0 || (item->isStackable() && count > item->getItemCount())) {
744 return RETURNVALUE_NOTPOSSIBLE;
745 }
746
747 if (!item->isMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) {
748 return RETURNVALUE_NOTMOVEABLE;
749 }
750
751 return RETURNVALUE_NOERROR;
752}
753
754Tile* Tile::queryDestination(int32_t&, const Thing&, Item** destItem, uint32_t& flags)
755{
756 Tile* destTile = nullptr;
757 *destItem = nullptr;
758
759 if (hasFlag(TILESTATE_FLOORCHANGE_DOWN)) {
760 uint16_t dx = tilePos.x;
761 uint16_t dy = tilePos.y;
762 uint8_t dz = tilePos.z + 1;
763
764 Tile* southDownTile = g_game.map.getTile(dx, dy - 1, dz);
765 if (southDownTile && southDownTile->hasFlag(TILESTATE_FLOORCHANGE_SOUTH_ALT)) {
766 dy -= 2;
767 destTile = g_game.map.getTile(dx, dy, dz);
768 } else {
769 Tile* eastDownTile = g_game.map.getTile(dx - 1, dy, dz);
770 if (eastDownTile && eastDownTile->hasFlag(TILESTATE_FLOORCHANGE_EAST_ALT)) {
771 dx -= 2;
772 destTile = g_game.map.getTile(dx, dy, dz);
773 } else {
774 Tile* downTile = g_game.map.getTile(dx, dy, dz);
775 if (downTile) {
776 if (downTile->hasFlag(TILESTATE_FLOORCHANGE_NORTH)) {
777 ++dy;
778 }
779
780 if (downTile->hasFlag(TILESTATE_FLOORCHANGE_SOUTH)) {
781 --dy;
782 }
783
784 if (downTile->hasFlag(TILESTATE_FLOORCHANGE_SOUTH_ALT)) {
785 dy -= 2;
786 }
787
788 if (downTile->hasFlag(TILESTATE_FLOORCHANGE_EAST)) {
789 --dx;
790 }
791
792 if (downTile->hasFlag(TILESTATE_FLOORCHANGE_EAST_ALT)) {
793 dx -= 2;
794 }
795
796 if (downTile->hasFlag(TILESTATE_FLOORCHANGE_WEST)) {
797 ++dx;
798 }
799
800 destTile = g_game.map.getTile(dx, dy, dz);
801 }
802 }
803 }
804 } else if (hasFlag(TILESTATE_FLOORCHANGE)) {
805 uint16_t dx = tilePos.x;
806 uint16_t dy = tilePos.y;
807 uint8_t dz = tilePos.z - 1;
808
809 if (hasFlag(TILESTATE_FLOORCHANGE_NORTH)) {
810 --dy;
811 }
812
813 if (hasFlag(TILESTATE_FLOORCHANGE_SOUTH)) {
814 ++dy;
815 }
816
817 if (hasFlag(TILESTATE_FLOORCHANGE_EAST)) {
818 ++dx;
819 }
820
821 if (hasFlag(TILESTATE_FLOORCHANGE_WEST)) {
822 --dx;
823 }
824
825 if (hasFlag(TILESTATE_FLOORCHANGE_SOUTH_ALT)) {
826 dy += 2;
827 }
828
829 if (hasFlag(TILESTATE_FLOORCHANGE_EAST_ALT)) {
830 dx += 2;
831 }
832
833 destTile = g_game.map.getTile(dx, dy, dz);
834 }
835
836 if (destTile == nullptr) {
837 destTile = this;
838 } else {
839 flags |= FLAG_NOLIMIT; //Will ignore that there is blocking items/creatures
840 }
841
842 if (destTile) {
843 Thing* destThing = destTile->getTopDownItem();
844 if (destThing) {
845 *destItem = destThing->getItem();
846 }
847 }
848 return destTile;
849}
850
851void Tile::addThing(Thing* thing)
852{
853 addThing(0, thing);
854}
855
856void Tile::addThing(int32_t, Thing* thing)
857{
858 Creature* creature = thing->getCreature();
859 if (creature) {
860 g_game.map.clearSpectatorCache();
861 if (creature->getPlayer()) {
862 g_game.map.clearPlayersSpectatorCache();
863 }
864
865 creature->setParent(this);
866 CreatureVector* creatures = makeCreatures();
867 creatures->insert(creatures->begin(), creature);
868 } else {
869 Item* item = thing->getItem();
870 if (item == nullptr) {
871 return /*RETURNVALUE_NOTPOSSIBLE*/;
872 }
873
874 TileItemVector* items = getItemList();
875 if (items && items->size() >= 0xFFFF) {
876 return /*RETURNVALUE_NOTPOSSIBLE*/;
877 }
878
879 item->setParent(this);
880
881 const ItemType& itemType = Item::items[item->getID()];
882 if (itemType.isGroundTile()) {
883 if (ground == nullptr) {
884 ground = item;
885 onAddTileItem(item);
886 } else {
887 const ItemType& oldType = Item::items[ground->getID()];
888
889 Item* oldGround = ground;
890 ground->setParent(nullptr);
891 g_game.ReleaseItem(ground);
892 ground = item;
893 resetTileFlags(oldGround);
894 setTileFlags(item);
895 onUpdateTileItem(oldGround, oldType, item, itemType);
896 postRemoveNotification(oldGround, nullptr, 0);
897 }
898 } else if (itemType.alwaysOnTop) {
899 if (itemType.isSplash() && items) {
900 //remove old splash if exists
901 for (ItemVector::const_iterator it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) {
902 Item* oldSplash = *it;
903 if (!Item::items[oldSplash->getID()].isSplash()) {
904 continue;
905 }
906
907 removeThing(oldSplash, 1);
908 oldSplash->setParent(nullptr);
909 g_game.ReleaseItem(oldSplash);
910 postRemoveNotification(oldSplash, nullptr, 0);
911 break;
912 }
913 }
914
915 bool isInserted = false;
916
917 if (items) {
918 for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) {
919 //Note: this is different from internalAddThing
920 if (itemType.alwaysOnTopOrder <= Item::items[(*it)->getID()].alwaysOnTopOrder) {
921 items->insert(it, item);
922 isInserted = true;
923 break;
924 }
925 }
926 } else {
927 items = makeItemList();
928 }
929
930 if (!isInserted) {
931 items->push_back(item);
932 }
933
934 onAddTileItem(item);
935 } else {
936 if (itemType.isMagicField()) {
937 //remove old field item if exists
938 if (items) {
939 for (ItemVector::const_iterator it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) {
940 MagicField* oldField = (*it)->getMagicField();
941 if (oldField) {
942 if (oldField->isReplaceable()) {
943 removeThing(oldField, 1);
944
945 oldField->setParent(nullptr);
946 g_game.ReleaseItem(oldField);
947 postRemoveNotification(oldField, nullptr, 0);
948 break;
949 } else {
950 //This magic field cannot be replaced.
951 item->setParent(nullptr);
952 g_game.ReleaseItem(item);
953 return;
954 }
955 }
956 }
957 }
958 }
959
960 items = makeItemList();
961 items->insert(items->getBeginDownItem(), item);
962 items->addDownItemCount(1);
963 onAddTileItem(item);
964 }
965 }
966}
967
968void Tile::updateThing(Thing* thing, uint16_t itemId, uint32_t count)
969{
970 int32_t index = getThingIndex(thing);
971 if (index == -1) {
972 return /*RETURNVALUE_NOTPOSSIBLE*/;
973 }
974
975 Item* item = thing->getItem();
976 if (item == nullptr) {
977 return /*RETURNVALUE_NOTPOSSIBLE*/;
978 }
979
980 const ItemType& oldType = Item::items[item->getID()];
981 const ItemType& newType = Item::items[itemId];
982 resetTileFlags(item);
983 item->setID(itemId);
984 item->setSubType(count);
985 setTileFlags(item);
986 onUpdateTileItem(item, oldType, item, newType);
987}
988
989void Tile::replaceThing(uint32_t index, Thing* thing)
990{
991 int32_t pos = index;
992
993 Item* item = thing->getItem();
994 if (item == nullptr) {
995 return /*RETURNVALUE_NOTPOSSIBLE*/;
996 }
997
998 Item* oldItem = nullptr;
999 bool isInserted = false;
1000
1001 if (ground) {
1002 if (pos == 0) {
1003 oldItem = ground;
1004 ground = item;
1005 isInserted = true;
1006 }
1007
1008 --pos;
1009 }
1010
1011 TileItemVector* items = getItemList();
1012 if (items && !isInserted) {
1013 int32_t topItemSize = getTopItemCount();
1014 if (pos < topItemSize) {
1015 auto it = items->getBeginTopItem();
1016 it += pos;
1017
1018 oldItem = (*it);
1019 it = items->erase(it);
1020 items->insert(it, item);
1021 isInserted = true;
1022 }
1023
1024 pos -= topItemSize;
1025 }
1026
1027 CreatureVector* creatures = getCreatures();
1028 if (creatures) {
1029 if (!isInserted && pos < static_cast<int32_t>(creatures->size())) {
1030 return /*RETURNVALUE_NOTPOSSIBLE*/;
1031 }
1032
1033 pos -= static_cast<uint32_t>(creatures->size());
1034 }
1035
1036 if (items && !isInserted) {
1037 int32_t downItemSize = getDownItemCount();
1038 if (pos < downItemSize) {
1039 auto it = items->getBeginDownItem() + pos;
1040 oldItem = *it;
1041 it = items->erase(it);
1042 items->insert(it, item);
1043 isInserted = true;
1044 }
1045 }
1046
1047 if (isInserted) {
1048 item->setParent(this);
1049
1050 resetTileFlags(oldItem);
1051 setTileFlags(item);
1052 const ItemType& oldType = Item::items[oldItem->getID()];
1053 const ItemType& newType = Item::items[item->getID()];
1054 onUpdateTileItem(oldItem, oldType, item, newType);
1055
1056 oldItem->setParent(nullptr);
1057 return /*RETURNVALUE_NOERROR*/;
1058 }
1059}
1060
1061void Tile::removeThing(Thing* thing, uint32_t count)
1062{
1063 Creature* creature = thing->getCreature();
1064 if (creature) {
1065 CreatureVector* creatures = getCreatures();
1066 if (creatures) {
1067 auto it = std::find(creatures->begin(), creatures->end(), thing);
1068 if (it != creatures->end()) {
1069 g_game.map.clearSpectatorCache();
1070 if (creature->getPlayer()) {
1071 g_game.map.clearPlayersSpectatorCache();
1072 }
1073
1074 creatures->erase(it);
1075 }
1076 }
1077 return;
1078 }
1079
1080 Item* item = thing->getItem();
1081 if (!item) {
1082 return;
1083 }
1084
1085 int32_t index = getThingIndex(item);
1086 if (index == -1) {
1087 return;
1088 }
1089
1090 if (item == ground) {
1091 ground->setParent(nullptr);
1092 ground = nullptr;
1093
1094 SpectatorVec spectators;
1095 g_game.map.getSpectators(spectators, getPosition(), true);
1096 onRemoveTileItem(spectators, std::vector<int32_t>(spectators.size(), 0), item);
1097 return;
1098 }
1099
1100 TileItemVector* items = getItemList();
1101 if (!items) {
1102 return;
1103 }
1104
1105 const ItemType& itemType = Item::items[item->getID()];
1106 if (itemType.alwaysOnTop) {
1107 auto it = std::find(items->getBeginTopItem(), items->getEndTopItem(), item);
1108 if (it == items->getEndTopItem()) {
1109 return;
1110 }
1111
1112 std::vector<int32_t> oldStackPosVector;
1113
1114 SpectatorVec spectators;
1115 g_game.map.getSpectators(spectators, getPosition(), true);
1116 for (Creature* spectator : spectators) {
1117 if (Player* tmpPlayer = spectator->getPlayer()) {
1118 oldStackPosVector.push_back(getStackposOfItem(tmpPlayer, item));
1119 }
1120 }
1121
1122 item->setParent(nullptr);
1123 items->erase(it);
1124 onRemoveTileItem(spectators, oldStackPosVector, item);
1125 } else {
1126 auto it = std::find(items->getBeginDownItem(), items->getEndDownItem(), item);
1127 if (it == items->getEndDownItem()) {
1128 return;
1129 }
1130
1131 if (itemType.stackable && count != item->getItemCount()) {
1132 uint8_t newCount = static_cast<uint8_t>(std::max<int32_t>(0, static_cast<int32_t>(item->getItemCount() - count)));
1133 item->setItemCount(newCount);
1134 onUpdateTileItem(item, itemType, item, itemType);
1135 } else {
1136 std::vector<int32_t> oldStackPosVector;
1137
1138 SpectatorVec spectators;
1139 g_game.map.getSpectators(spectators, getPosition(), true);
1140 for (Creature* spectator : spectators) {
1141 if (Player* tmpPlayer = spectator->getPlayer()) {
1142 oldStackPosVector.push_back(getStackposOfItem(tmpPlayer, item));
1143 }
1144 }
1145
1146 item->setParent(nullptr);
1147 items->erase(it);
1148 items->addDownItemCount(-1);
1149 onRemoveTileItem(spectators, oldStackPosVector, item);
1150 }
1151 }
1152}
1153
1154void Tile::removeCreature(Creature* creature)
1155{
1156 g_game.map.getQTNode(tilePos.x, tilePos.y)->removeCreature(creature);
1157 removeThing(creature, 0);
1158}
1159
1160int32_t Tile::getThingIndex(const Thing* thing) const
1161{
1162 int32_t n = -1;
1163 if (ground) {
1164 if (ground == thing) {
1165 return 0;
1166 }
1167 ++n;
1168 }
1169
1170 const TileItemVector* items = getItemList();
1171 if (items) {
1172 const Item* item = thing->getItem();
1173 if (item && item->isAlwaysOnTop()) {
1174 for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) {
1175 ++n;
1176 if (*it == item) {
1177 return n;
1178 }
1179 }
1180 } else {
1181 n += items->getTopItemCount();
1182 }
1183 }
1184
1185 if (const CreatureVector* creatures = getCreatures()) {
1186 if (thing->getCreature()) {
1187 for (Creature* creature : *creatures) {
1188 ++n;
1189 if (creature == thing) {
1190 return n;
1191 }
1192 }
1193 } else {
1194 n += creatures->size();
1195 }
1196 }
1197
1198 if (items) {
1199 const Item* item = thing->getItem();
1200 if (item && !item->isAlwaysOnTop()) {
1201 for (auto it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) {
1202 ++n;
1203 if (*it == item) {
1204 return n;
1205 }
1206 }
1207 }
1208 }
1209 return -1;
1210}
1211
1212int32_t Tile::getClientIndexOfCreature(const Player* player, const Creature* creature) const
1213{
1214 int32_t n;
1215 if (ground) {
1216 n = 1;
1217 } else {
1218 n = 0;
1219 }
1220
1221 const TileItemVector* items = getItemList();
1222 if (items) {
1223 n += items->getTopItemCount();
1224 }
1225
1226 if (const CreatureVector* creatures = getCreatures()) {
1227 for (const Creature* c : boost::adaptors::reverse(*creatures)) {
1228 if (c == creature) {
1229 return n;
1230 } else if (player->canSeeCreature(c)) {
1231 ++n;
1232 }
1233 }
1234 }
1235 return -1;
1236}
1237
1238int32_t Tile::getStackposOfCreature(const Player* player, const Creature* creature) const
1239{
1240 int32_t n;
1241 if (ground) {
1242 n = 1;
1243 } else {
1244 n = 0;
1245 }
1246
1247 const TileItemVector* items = getItemList();
1248 if (items) {
1249 n += items->getTopItemCount();
1250 if (n >= 10) {
1251 return -1;
1252 }
1253 }
1254
1255 if (const CreatureVector* creatures = getCreatures()) {
1256 for (const Creature* c : boost::adaptors::reverse(*creatures)) {
1257 if (c == creature) {
1258 return n;
1259 } else if (player->canSeeCreature(c)) {
1260 if (++n >= 10) {
1261 return -1;
1262 }
1263 }
1264 }
1265 }
1266 return -1;
1267}
1268
1269int32_t Tile::getStackposOfItem(const Player* player, const Item* item) const
1270{
1271 int32_t n = 0;
1272 if (ground) {
1273 if (ground == item) {
1274 return n;
1275 }
1276 ++n;
1277 }
1278
1279 const TileItemVector* items = getItemList();
1280 if (items) {
1281 if (item->isAlwaysOnTop()) {
1282 for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) {
1283 if (*it == item) {
1284 return n;
1285 } else if (++n == 10) {
1286 return -1;
1287 }
1288 }
1289 } else {
1290 n += items->getTopItemCount();
1291 if (n >= 10) {
1292 return -1;
1293 }
1294 }
1295 }
1296
1297 if (const CreatureVector* creatures = getCreatures()) {
1298 for (const Creature* creature : *creatures) {
1299 if (player->canSeeCreature(creature)) {
1300 if (++n >= 10) {
1301 return -1;
1302 }
1303 }
1304 }
1305 }
1306
1307 if (items && !item->isAlwaysOnTop()) {
1308 for (auto it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) {
1309 if (*it == item) {
1310 return n;
1311 } else if (++n >= 10) {
1312 return -1;
1313 }
1314 }
1315 }
1316 return -1;
1317}
1318
1319size_t Tile::getFirstIndex() const
1320{
1321 return 0;
1322}
1323
1324size_t Tile::getLastIndex() const
1325{
1326 return getThingCount();
1327}
1328
1329uint32_t Tile::getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) const
1330{
1331 uint32_t count = 0;
1332 if (ground && ground->getID() == itemId) {
1333 count += Item::countByType(ground, subType);
1334 }
1335
1336 const TileItemVector* items = getItemList();
1337 if (items) {
1338 for (const Item* item : *items) {
1339 if (item->getID() == itemId) {
1340 count += Item::countByType(item, subType);
1341 }
1342 }
1343 }
1344 return count;
1345}
1346
1347Thing* Tile::getThing(size_t index) const
1348{
1349 if (ground) {
1350 if (index == 0) {
1351 return ground;
1352 }
1353
1354 --index;
1355 }
1356
1357 const TileItemVector* items = getItemList();
1358 if (items) {
1359 uint32_t topItemSize = items->getTopItemCount();
1360 if (index < topItemSize) {
1361 return items->at(items->getDownItemCount() + index);
1362 }
1363 index -= topItemSize;
1364 }
1365
1366 if (const CreatureVector* creatures = getCreatures()) {
1367 if (index < creatures->size()) {
1368 return (*creatures)[index];
1369 }
1370 index -= creatures->size();
1371 }
1372
1373 if (items && index < items->getDownItemCount()) {
1374 return items->at(index);
1375 }
1376 return nullptr;
1377}
1378
1379void Tile::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/)
1380{
1381 SpectatorVec spectators;
1382 g_game.map.getSpectators(spectators, getPosition(), true, true);
1383 for (Creature* spectator : spectators) {
1384 spectator->getPlayer()->postAddNotification(thing, oldParent, index, LINK_NEAR);
1385 }
1386
1387 //add a reference to this item, it may be deleted after being added (mailbox for example)
1388 Creature* creature = thing->getCreature();
1389 Item* item;
1390 if (creature) {
1391 creature->incrementReferenceCounter();
1392 item = nullptr;
1393 } else {
1394 item = thing->getItem();
1395 if (item) {
1396 item->incrementReferenceCounter();
1397 }
1398 }
1399
1400 if (link == LINK_OWNER) {
1401 if (hasFlag(TILESTATE_TELEPORT)) {
1402 Teleport* teleport = getTeleportItem();
1403 if (teleport) {
1404 teleport->addThing(thing);
1405 }
1406 } else if (hasFlag(TILESTATE_TRASHHOLDER)) {
1407 TrashHolder* trashholder = getTrashHolder();
1408 if (trashholder) {
1409 trashholder->addThing(thing);
1410 }
1411 } else if (hasFlag(TILESTATE_MAILBOX)) {
1412 Mailbox* mailbox = getMailbox();
1413 if (mailbox) {
1414 mailbox->addThing(thing);
1415 }
1416 }
1417
1418 //calling movement scripts
1419 if (creature) {
1420 g_moveEvents->onCreatureMove(creature, this, MOVE_EVENT_STEP_IN);
1421 } else if (item) {
1422 g_moveEvents->onItemMove(item, this, true);
1423 }
1424 }
1425
1426 //release the reference to this item onces we are finished
1427 if (creature) {
1428 g_game.ReleaseCreature(creature);
1429 } else if (item) {
1430 g_game.ReleaseItem(item);
1431 }
1432}
1433
1434void Tile::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t)
1435{
1436 SpectatorVec spectators;
1437 g_game.map.getSpectators(spectators, getPosition(), true, true);
1438
1439 if (getThingCount() > 8) {
1440 onUpdateTile(spectators);
1441 }
1442
1443 for (Creature* spectator : spectators) {
1444 spectator->getPlayer()->postRemoveNotification(thing, newParent, index, LINK_NEAR);
1445 }
1446
1447 //calling movement scripts
1448 Creature* creature = thing->getCreature();
1449 if (creature) {
1450 g_moveEvents->onCreatureMove(creature, this, MOVE_EVENT_STEP_OUT);
1451 } else {
1452 Item* item = thing->getItem();
1453 if (item) {
1454 g_moveEvents->onItemMove(item, this, false);
1455 }
1456 }
1457}
1458
1459void Tile::internalAddThing(Thing* thing)
1460{
1461 internalAddThing(0, thing);
1462}
1463
1464void Tile::internalAddThing(uint32_t, Thing* thing)
1465{
1466 thing->setParent(this);
1467
1468 Creature* creature = thing->getCreature();
1469 if (creature) {
1470 g_game.map.clearSpectatorCache();
1471 if (creature->getPlayer()) {
1472 g_game.map.clearPlayersSpectatorCache();
1473 }
1474
1475 CreatureVector* creatures = makeCreatures();
1476 creatures->insert(creatures->begin(), creature);
1477 } else {
1478 Item* item = thing->getItem();
1479 if (item == nullptr) {
1480 return;
1481 }
1482
1483 const ItemType& itemType = Item::items[item->getID()];
1484 if (itemType.isGroundTile()) {
1485 if (ground == nullptr) {
1486 ground = item;
1487 setTileFlags(item);
1488 }
1489 return;
1490 }
1491
1492 TileItemVector* items = makeItemList();
1493 if (items->size() >= 0xFFFF) {
1494 return /*RETURNVALUE_NOTPOSSIBLE*/;
1495 }
1496
1497 if (itemType.alwaysOnTop) {
1498 bool isInserted = false;
1499 for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) {
1500 if (Item::items[(*it)->getID()].alwaysOnTopOrder > itemType.alwaysOnTopOrder) {
1501 items->insert(it, item);
1502 isInserted = true;
1503 break;
1504 }
1505 }
1506
1507 if (!isInserted) {
1508 items->push_back(item);
1509 }
1510 } else {
1511 items->insert(items->getBeginDownItem(), item);
1512 items->addDownItemCount(1);
1513 }
1514
1515 setTileFlags(item);
1516 }
1517}
1518
1519void Tile::setTileFlags(const Item* item)
1520{
1521 if (!hasFlag(TILESTATE_FLOORCHANGE)) {
1522 const ItemType& it = Item::items[item->getID()];
1523 if (it.floorChange != 0) {
1524 setFlag(it.floorChange);
1525 }
1526 }
1527
1528 if (item->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID)) {
1529 setFlag(TILESTATE_IMMOVABLEBLOCKSOLID);
1530 }
1531
1532 if (item->hasProperty(CONST_PROP_BLOCKPATH)) {
1533 setFlag(TILESTATE_BLOCKPATH);
1534 }
1535
1536 if (item->hasProperty(CONST_PROP_NOFIELDBLOCKPATH)) {
1537 setFlag(TILESTATE_NOFIELDBLOCKPATH);
1538 }
1539
1540 if (item->hasProperty(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH)) {
1541 setFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH);
1542 }
1543
1544 if (item->getTeleport()) {
1545 setFlag(TILESTATE_TELEPORT);
1546 }
1547
1548 if (item->getMagicField()) {
1549 setFlag(TILESTATE_MAGICFIELD);
1550 }
1551
1552 if (item->getMailbox()) {
1553 setFlag(TILESTATE_MAILBOX);
1554 }
1555
1556 if (item->getTrashHolder()) {
1557 setFlag(TILESTATE_TRASHHOLDER);
1558 }
1559
1560 if (item->hasProperty(CONST_PROP_BLOCKSOLID)) {
1561 setFlag(TILESTATE_BLOCKSOLID);
1562 }
1563
1564 if (item->getBed()) {
1565 setFlag(TILESTATE_BED);
1566 }
1567
1568 const Container* container = item->getContainer();
1569 if (container && container->getDepotLocker()) {
1570 setFlag(TILESTATE_DEPOT);
1571 }
1572
1573 if (item->hasProperty(CONST_PROP_SUPPORTHANGABLE)) {
1574 setFlag(TILESTATE_SUPPORTS_HANGABLE);
1575 }
1576}
1577
1578void Tile::resetTileFlags(const Item* item)
1579{
1580 const ItemType& it = Item::items[item->getID()];
1581 if (it.floorChange != 0) {
1582 resetFlag(TILESTATE_FLOORCHANGE);
1583 }
1584
1585 if (item->hasProperty(CONST_PROP_BLOCKSOLID) && !hasProperty(item, CONST_PROP_BLOCKSOLID)) {
1586 resetFlag(TILESTATE_BLOCKSOLID);
1587 }
1588
1589 if (item->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) && !hasProperty(item, CONST_PROP_IMMOVABLEBLOCKSOLID)) {
1590 resetFlag(TILESTATE_IMMOVABLEBLOCKSOLID);
1591 }
1592
1593 if (item->hasProperty(CONST_PROP_BLOCKPATH) && !hasProperty(item, CONST_PROP_BLOCKPATH)) {
1594 resetFlag(TILESTATE_BLOCKPATH);
1595 }
1596
1597 if (item->hasProperty(CONST_PROP_NOFIELDBLOCKPATH) && !hasProperty(item, CONST_PROP_NOFIELDBLOCKPATH)) {
1598 resetFlag(TILESTATE_NOFIELDBLOCKPATH);
1599 }
1600
1601 if (item->hasProperty(CONST_PROP_IMMOVABLEBLOCKPATH) && !hasProperty(item, CONST_PROP_IMMOVABLEBLOCKPATH)) {
1602 resetFlag(TILESTATE_IMMOVABLEBLOCKPATH);
1603 }
1604
1605 if (item->hasProperty(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH) && !hasProperty(item, CONST_PROP_IMMOVABLENOFIELDBLOCKPATH)) {
1606 resetFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH);
1607 }
1608
1609 if (item->getTeleport()) {
1610 resetFlag(TILESTATE_TELEPORT);
1611 }
1612
1613 if (item->getMagicField()) {
1614 resetFlag(TILESTATE_MAGICFIELD);
1615 }
1616
1617 if (item->getMailbox()) {
1618 resetFlag(TILESTATE_MAILBOX);
1619 }
1620
1621 if (item->getTrashHolder()) {
1622 resetFlag(TILESTATE_TRASHHOLDER);
1623 }
1624
1625 if (item->getBed()) {
1626 resetFlag(TILESTATE_BED);
1627 }
1628
1629 const Container* container = item->getContainer();
1630 if (container && container->getDepotLocker()) {
1631 resetFlag(TILESTATE_DEPOT);
1632 }
1633
1634 if (item->hasProperty(CONST_PROP_SUPPORTHANGABLE)) {
1635 resetFlag(TILESTATE_SUPPORTS_HANGABLE);
1636 }
1637}
1638
1639bool Tile::isMoveableBlocking() const
1640{
1641 return !ground || hasFlag(TILESTATE_BLOCKSOLID);
1642}
1643
1644Item* Tile::getUseItem(int32_t index) const
1645{
1646 const TileItemVector* items = getItemList();
1647 if (!items || items->size() == 0) {
1648 return ground;
1649 }
1650
1651 if (Thing* thing = getThing(index)) {
1652 return thing->getItem();
1653 }
1654
1655 return nullptr;
1656}
1657