forgottenserver/src/tile.cpp
Ramon Bernardo afd9165725
refactor(thing): remove Cylinder class and merge receiver logic into Thing (#5058)
* refactor(cylinder): remove Cylinder class and merge receiver logic into Thing

* fix(game): code format

* fix(trashholder): code format

* refactor(thing): remove unused params

* refactor: remove unused params

* fix(luascript): use default switch case

* fix(item): parent nullptr check

* refactor(creature): use tile property instead getTile method

Co-authored-by: Ranieri Althoff <1993083+ranisalt@users.noreply.github.com>

* refactor(item): getTile method on get parent

* refactor(player): simplify query destination logic

* refactor(container): simplify query destination logic

* refactor(item): use const parent var

* fix(luascript): support move removed item

* fix(game): rename thing param

* fix(player): destThing nullptr check

* fix(thing): remove startDecaying method

* fix(container): merge master branch

* fix(depotchest): merge master branch

* refactor(container): remove unnecessary if state on top-parent

---------

Co-authored-by: Ranieri Althoff <1993083+ranisalt@users.noreply.github.com>
2025-11-29 01:38:42 +01:00

1610 lines
38 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2023 The Forgotten Server Authors. All rights reserved.
// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file.
#include "otpch.h"
#include "tile.h"
#include "combat.h"
#include "configmanager.h"
#include "creature.h"
#include "game.h"
#include "housetile.h"
#include "mailbox.h"
#include "monster.h"
#include "movement.h"
#include "spectators.h"
#include "teleport.h"
#include "trashholder.h"
extern Game g_game;
extern MoveEvents* g_moveEvents;
StaticTile real_nullptr_tile(0xFFFF, 0xFFFF, 0xFF);
Tile& Tile::nullptr_tile = real_nullptr_tile;
bool Tile::hasProperty(ITEMPROPERTY prop) const
{
if (ground && ground->hasProperty(prop)) {
return true;
}
if (const TileItemVector* items = getItemList()) {
for (const Item* item : *items) {
if (item->hasProperty(prop)) {
return true;
}
}
}
return false;
}
bool Tile::hasProperty(const Item* exclude, ITEMPROPERTY prop) const
{
assert(exclude);
if (ground && exclude != ground && ground->hasProperty(prop)) {
return true;
}
if (const TileItemVector* items = getItemList()) {
for (const Item* item : *items) {
if (item != exclude && item->hasProperty(prop)) {
return true;
}
}
}
return false;
}
bool Tile::hasHeight(uint32_t n) const
{
uint32_t height = 0;
if (ground) {
if (ground->hasProperty(CONST_PROP_HASHEIGHT)) {
++height;
}
if (n == height) {
return true;
}
}
if (const TileItemVector* items = getItemList()) {
for (const Item* item : *items) {
if (item->hasProperty(CONST_PROP_HASHEIGHT)) {
++height;
}
if (n == height) {
return true;
}
}
}
return false;
}
size_t Tile::getCreatureCount() const
{
if (const CreatureVector* creatures = getCreatures()) {
return creatures->size();
}
return 0;
}
size_t Tile::getItemCount() const
{
if (const TileItemVector* items = getItemList()) {
return items->size();
}
return 0;
}
uint32_t Tile::getTopItemCount() const
{
if (const TileItemVector* items = getItemList()) {
return items->getTopItemCount();
}
return 0;
}
uint32_t Tile::getDownItemCount() const
{
if (const TileItemVector* items = getItemList()) {
return items->getDownItemCount();
}
return 0;
}
Teleport* Tile::getTeleportItem() const
{
if (!hasFlag(TILESTATE_TELEPORT)) {
return nullptr;
}
if (const TileItemVector* items = getItemList()) {
for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) {
if ((*it)->getTeleport()) {
return (*it)->getTeleport();
}
}
}
return nullptr;
}
MagicField* Tile::getFieldItem() const
{
if (!hasFlag(TILESTATE_MAGICFIELD)) {
return nullptr;
}
if (ground && ground->getMagicField()) {
return ground->getMagicField();
}
if (const TileItemVector* items = getItemList()) {
for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) {
if ((*it)->getMagicField()) {
return (*it)->getMagicField();
}
}
}
return nullptr;
}
TrashHolder* Tile::getTrashHolder() const
{
if (!hasFlag(TILESTATE_TRASHHOLDER)) {
return nullptr;
}
if (ground && ground->getTrashHolder()) {
return ground->getTrashHolder();
}
if (const TileItemVector* items = getItemList()) {
for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) {
if ((*it)->getTrashHolder()) {
return (*it)->getTrashHolder();
}
}
}
return nullptr;
}
Mailbox* Tile::getMailbox() const
{
if (!hasFlag(TILESTATE_MAILBOX)) {
return nullptr;
}
if (ground && ground->getMailbox()) {
return ground->getMailbox();
}
if (const TileItemVector* items = getItemList()) {
for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) {
if ((*it)->getMailbox()) {
return (*it)->getMailbox();
}
}
}
return nullptr;
}
BedItem* Tile::getBedItem() const
{
if (!hasFlag(TILESTATE_BED)) {
return nullptr;
}
if (ground && ground->getBed()) {
return ground->getBed();
}
if (const TileItemVector* items = getItemList()) {
for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) {
if ((*it)->getBed()) {
return (*it)->getBed();
}
}
}
return nullptr;
}
Creature* Tile::getTopCreature() const
{
if (const CreatureVector* creatures = getCreatures()) {
if (!creatures->empty()) {
return *creatures->begin();
}
}
return nullptr;
}
const Creature* Tile::getBottomCreature() const
{
if (const CreatureVector* creatures = getCreatures()) {
if (!creatures->empty()) {
return *creatures->rbegin();
}
}
return nullptr;
}
Creature* Tile::getTopVisibleCreature(const Creature* creature) const
{
if (const CreatureVector* creatures = getCreatures()) {
if (creature) {
for (Creature* tileCreature : *creatures) {
if (creature->canSeeCreature(tileCreature)) {
return tileCreature;
}
}
} else {
for (Creature* tileCreature : *creatures) {
if (!tileCreature->isInvisible()) {
const Player* player = tileCreature->getPlayer();
if (!player || !player->isInGhostMode()) {
return tileCreature;
}
}
}
}
}
return nullptr;
}
const Creature* Tile::getBottomVisibleCreature(const Creature* creature) const
{
if (const CreatureVector* creatures = getCreatures()) {
if (creature) {
for (auto it = creatures->rbegin(), end = creatures->rend(); it != end; ++it) {
if (creature->canSeeCreature(*it)) {
return *it;
}
}
} else {
for (auto it = creatures->rbegin(), end = creatures->rend(); it != end; ++it) {
if (!(*it)->isInvisible()) {
const Player* player = (*it)->getPlayer();
if (!player || !player->isInGhostMode()) {
return *it;
}
}
}
}
}
return nullptr;
}
Item* Tile::getTopDownItem() const
{
if (const TileItemVector* items = getItemList()) {
return items->getTopDownItem();
}
return nullptr;
}
Item* Tile::getTopTopItem() const
{
if (const TileItemVector* items = getItemList()) {
return items->getTopTopItem();
}
return nullptr;
}
Item* Tile::getItemByTopOrder(int32_t topOrder)
{
// topOrder:
// 1: borders
// 2: ladders, signs, splashes
// 3: doors etc
// 4: creatures
if (TileItemVector* items = getItemList()) {
for (auto it = ItemVector::const_reverse_iterator(items->getEndTopItem()),
end = ItemVector::const_reverse_iterator(items->getBeginTopItem());
it != end; ++it) {
if (Item::items[(*it)->getID()].alwaysOnTopOrder == topOrder) {
return (*it);
}
}
}
return nullptr;
}
Thing* Tile::getTopVisibleThing(const Creature* creature)
{
Thing* thing = getTopVisibleCreature(creature);
if (thing) {
return thing;
}
TileItemVector* items = getItemList();
if (items) {
for (ItemVector::const_iterator it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end;
++it) {
const ItemType& iit = Item::items[(*it)->getID()];
if (!iit.lookThrough) {
return (*it);
}
}
for (auto it = ItemVector::const_reverse_iterator(items->getEndTopItem()),
end = ItemVector::const_reverse_iterator(items->getBeginTopItem());
it != end; ++it) {
const ItemType& iit = Item::items[(*it)->getID()];
if (!iit.lookThrough) {
return (*it);
}
}
}
return ground;
}
void Tile::onAddTileItem(Item* item)
{
if (item->hasProperty(CONST_PROP_MOVEABLE) || item->getContainer()) {
auto it = g_game.browseFields.find(this);
if (it != g_game.browseFields.end()) {
it->second->addItemBack(item);
item->setParent(it->second);
}
}
setTileFlags(item);
SpectatorVec spectators;
g_game.map.getSpectators(spectators, tilePos, true);
// send to client
for (Creature* spectator : spectators) {
if (Player* spectatorPlayer = spectator->getPlayer()) {
spectatorPlayer->sendAddTileItem(this, tilePos, item);
}
}
if ((!hasFlag(TILESTATE_PROTECTIONZONE) || getBoolean(ConfigManager::CLEAN_PROTECTION_ZONES)) &&
item->isCleanable()) {
if (!getHouseTile()) {
g_game.addTileToClean(this);
}
}
}
void Tile::onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newItem, const ItemType& newType)
{
if (newItem->hasProperty(CONST_PROP_MOVEABLE) || newItem->getContainer()) {
auto it = g_game.browseFields.find(this);
if (it != g_game.browseFields.end()) {
int32_t index = it->second->getThingIndex(oldItem);
if (index != -1) {
it->second->replaceThing(index, newItem);
newItem->setParent(it->second);
}
}
} else if (oldItem->hasProperty(CONST_PROP_MOVEABLE) || oldItem->getContainer()) {
auto it = g_game.browseFields.find(this);
if (it != g_game.browseFields.end()) {
const auto oldParent = oldItem->getParent();
it->second->removeThing(oldItem, oldItem->getItemCount());
oldItem->setParent(oldParent);
}
}
SpectatorVec spectators;
g_game.map.getSpectators(spectators, tilePos, true);
// send to client
for (Creature* spectator : spectators) {
if (Player* spectatorPlayer = spectator->getPlayer()) {
spectatorPlayer->sendUpdateTileItem(this, tilePos, newItem);
}
}
// event methods
for (Creature* spectator : spectators) {
spectator->onUpdateTileItem(this, tilePos, oldItem, oldType, newItem, newType);
}
}
void Tile::onRemoveTileItem(const SpectatorVec& spectators, const std::vector<int32_t>& oldStackPosVector, Item* item)
{
if (item->hasProperty(CONST_PROP_MOVEABLE) || item->getContainer()) {
auto it = g_game.browseFields.find(this);
if (it != g_game.browseFields.end()) {
it->second->removeThing(item, item->getItemCount());
}
}
resetTileFlags(item);
const ItemType& iType = Item::items[item->getID()];
// send to client
size_t i = 0;
for (Creature* spectator : spectators) {
if (Player* tmpPlayer = spectator->getPlayer()) {
tmpPlayer->sendRemoveTileThing(tilePos, oldStackPosVector[i++]);
}
}
// event methods
for (Creature* spectator : spectators) {
spectator->onRemoveTileItem(this, tilePos, iType, item);
}
if (!hasFlag(TILESTATE_PROTECTIONZONE) || getBoolean(ConfigManager::CLEAN_PROTECTION_ZONES)) {
auto items = getItemList();
if (!items || items->empty()) {
g_game.removeTileToClean(this);
return;
}
bool ret = false;
for (auto toCheck : *items) {
if (toCheck->isCleanable()) {
ret = true;
break;
}
}
if (!ret) {
g_game.removeTileToClean(this);
}
}
}
ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags, Creature*) const
{
if (const Creature* creature = thing.getCreature()) {
if (hasBitSet(FLAG_NOLIMIT, flags)) {
return RETURNVALUE_NOERROR;
}
if (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_FLOORCHANGE | TILESTATE_TELEPORT)) {
return RETURNVALUE_NOTPOSSIBLE;
}
if (!ground) {
return RETURNVALUE_NOTPOSSIBLE;
}
if (const Monster* monster = creature->getMonster()) {
if (hasFlag(TILESTATE_PROTECTIONZONE | TILESTATE_FLOORCHANGE | TILESTATE_TELEPORT)) {
return RETURNVALUE_NOTPOSSIBLE;
}
const CreatureVector* creatures = getCreatures();
if (monster->canPushCreatures() && !monster->isSummon()) {
if (creatures) {
for (Creature* tileCreature : *creatures) {
if (tileCreature->getPlayer() && tileCreature->getPlayer()->isInGhostMode()) {
continue;
}
const Monster* creatureMonster = tileCreature->getMonster();
if (!creatureMonster || !tileCreature->isPushable() ||
(creatureMonster->isSummon() && creatureMonster->getMaster()->getPlayer())) {
return RETURNVALUE_NOTPOSSIBLE;
}
}
}
} else if (creatures && !creatures->empty()) {
for (const Creature* tileCreature : *creatures) {
if (!tileCreature->isInGhostMode()) {
return RETURNVALUE_NOTENOUGHROOM;
}
}
}
if (hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)) {
return RETURNVALUE_NOTPOSSIBLE;
}
if (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH)) {
return RETURNVALUE_NOTPOSSIBLE;
}
if (hasFlag(TILESTATE_BLOCKSOLID) ||
(hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_NOFIELDBLOCKPATH))) {
if (!(monster->canPushItems() || hasBitSet(FLAG_IGNOREBLOCKITEM, flags))) {
return RETURNVALUE_NOTPOSSIBLE;
}
}
MagicField* field = getFieldItem();
if (!field || field->isBlocking() || field->getDamage() == 0) {
return RETURNVALUE_NOERROR;
}
CombatType_t combatType = field->getCombatType();
// There is 3 options for a monster to enter a magic field
// 1) Monster is immune
if (!monster->isImmune(combatType)) {
// 1) Monster is able to walk over field type
// 2) Being attacked while random stepping will make it ignore
// field damages
if (hasBitSet(FLAG_IGNOREFIELDDAMAGE, flags)) {
if (!(monster->canWalkOnFieldType(combatType) || monster->isIgnoringFieldDamage())) {
return RETURNVALUE_NOTPOSSIBLE;
}
} else {
return RETURNVALUE_NOTPOSSIBLE;
}
}
return RETURNVALUE_NOERROR;
}
const CreatureVector* creatures = getCreatures();
if (const Player* player = creature->getPlayer()) {
if (creatures && !creatures->empty() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags) &&
!player->isAccessPlayer()) {
for (const Creature* tileCreature : *creatures) {
if (!player->canWalkthrough(tileCreature)) {
return RETURNVALUE_NOTPOSSIBLE;
}
}
}
if (MagicField* field = getFieldItem()) {
if (field->getDamage() != 0 && hasBitSet(FLAG_PATHFINDING, flags) &&
!hasBitSet(FLAG_IGNOREFIELDDAMAGE, flags)) {
return RETURNVALUE_NOTPOSSIBLE;
}
}
if (!player->hasParent() && hasFlag(TILESTATE_NOLOGOUT)) {
// player is trying to login to a "no logout" tile
return RETURNVALUE_NOTPOSSIBLE;
}
const Tile* playerTile = player->getTile();
if (playerTile && player->isPzLocked()) {
if (!playerTile->hasFlag(TILESTATE_PVPZONE)) {
// player is trying to enter a pvp zone while being pz-locked
if (hasFlag(TILESTATE_PVPZONE)) {
return RETURNVALUE_PLAYERISPZLOCKEDENTERPVPZONE;
}
} else if (!hasFlag(TILESTATE_PVPZONE)) {
// player is trying to leave a pvp zone while being pz-locked
return RETURNVALUE_PLAYERISPZLOCKEDLEAVEPVPZONE;
}
if ((!playerTile->hasFlag(TILESTATE_NOPVPZONE) && hasFlag(TILESTATE_NOPVPZONE)) ||
(!playerTile->hasFlag(TILESTATE_PROTECTIONZONE) && hasFlag(TILESTATE_PROTECTIONZONE))) {
// player is trying to enter a non-pvp/protection zone while being pz-locked
return RETURNVALUE_PLAYERISPZLOCKED;
}
}
} else if (creatures && !creatures->empty() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags)) {
for (const Creature* tileCreature : *creatures) {
if (!tileCreature->isInGhostMode()) {
return RETURNVALUE_NOTENOUGHROOM;
}
}
}
if (!hasBitSet(FLAG_IGNOREBLOCKITEM, flags)) {
// If the FLAG_IGNOREBLOCKITEM bit isn't set we dont have to iterate every single item
if (hasFlag(TILESTATE_BLOCKSOLID)) {
return RETURNVALUE_NOTENOUGHROOM;
}
} else {
// FLAG_IGNOREBLOCKITEM is set
if (ground) {
const ItemType& iiType = Item::items[ground->getID()];
if (iiType.blockSolid && (!iiType.moveable || ground->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID))) {
return RETURNVALUE_NOTPOSSIBLE;
}
}
if (const auto items = getItemList()) {
for (const Item* item : *items) {
const ItemType& iiType = Item::items[item->getID()];
if (iiType.blockSolid && (!iiType.moveable || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID))) {
return RETURNVALUE_NOTPOSSIBLE;
}
}
}
}
} else if (const Item* item = thing.getItem()) {
const TileItemVector* items = getItemList();
if (items && items->size() >= 0xFFFF) {
return RETURNVALUE_NOTPOSSIBLE;
}
if (hasBitSet(FLAG_NOLIMIT, flags)) {
return RETURNVALUE_NOERROR;
}
if (item->isStoreItem()) {
return RETURNVALUE_ITEMCANNOTBEMOVEDTHERE;
}
bool itemIsHangable = item->isHangable();
if (!ground && !itemIsHangable) {
return RETURNVALUE_NOTPOSSIBLE;
}
const CreatureVector* creatures = getCreatures();
if (creatures && !creatures->empty() && item->isBlocking() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags)) {
for (const Creature* tileCreature : *creatures) {
if (!tileCreature->isInGhostMode()) {
return RETURNVALUE_NOTENOUGHROOM;
}
}
}
if (itemIsHangable && hasFlag(TILESTATE_SUPPORTS_HANGABLE)) {
if (items) {
for (const Item* tileItem : *items) {
if (tileItem->isHangable()) {
return RETURNVALUE_NEEDEXCHANGE;
}
}
}
} else {
if (ground) {
const ItemType& iiType = Item::items[ground->getID()];
if (iiType.blockSolid) {
if (!iiType.allowPickupable || item->isMagicField() || item->isBlocking()) {
if (!item->isPickupable()) {
return RETURNVALUE_NOTENOUGHROOM;
}
if (!iiType.hasHeight || iiType.pickupable || iiType.isBed()) {
return RETURNVALUE_NOTENOUGHROOM;
}
}
}
}
if (items) {
for (const Item* tileItem : *items) {
const ItemType& iiType = Item::items[tileItem->getID()];
if (!iiType.blockSolid) {
continue;
}
if (iiType.allowPickupable && !item->isMagicField() && !item->isBlocking()) {
continue;
}
if (!item->isPickupable()) {
return RETURNVALUE_NOTENOUGHROOM;
}
if (!iiType.hasHeight || iiType.pickupable || iiType.isBed()) {
return RETURNVALUE_NOTENOUGHROOM;
}
}
}
}
}
return RETURNVALUE_NOERROR;
}
ReturnValue Tile::queryMaxCount(int32_t, const Thing&, uint32_t count, uint32_t& maxQueryCount, uint32_t) const
{
maxQueryCount = std::max<uint32_t>(1, count);
return RETURNVALUE_NOERROR;
}
ReturnValue Tile::queryRemove(const Thing& thing, uint32_t count, uint32_t flags, Creature* /*= nullptr */) const
{
int32_t index = getThingIndex(&thing);
if (index == -1) {
return RETURNVALUE_NOTPOSSIBLE;
}
const Item* item = thing.getItem();
if (!item) {
return RETURNVALUE_NOTPOSSIBLE;
}
if (count == 0 || (item->isStackable() && count > item->getItemCount())) {
return RETURNVALUE_NOTPOSSIBLE;
}
if (!item->isMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) {
return RETURNVALUE_NOTMOVEABLE;
}
return RETURNVALUE_NOERROR;
}
Tile* Tile::queryDestination(int32_t&, const Thing&, Item** destItem, uint32_t& flags)
{
Tile* destTile = nullptr;
*destItem = nullptr;
if (hasFlag(TILESTATE_FLOORCHANGE_DOWN)) {
uint16_t dx = tilePos.x;
uint16_t dy = tilePos.y;
uint8_t dz = tilePos.z + 1;
Tile* southDownTile = g_game.map.getTile(dx, dy - 1, dz);
if (southDownTile && southDownTile->hasFlag(TILESTATE_FLOORCHANGE_SOUTH_ALT)) {
dy -= 2;
destTile = g_game.map.getTile(dx, dy, dz);
} else {
Tile* eastDownTile = g_game.map.getTile(dx - 1, dy, dz);
if (eastDownTile && eastDownTile->hasFlag(TILESTATE_FLOORCHANGE_EAST_ALT)) {
dx -= 2;
destTile = g_game.map.getTile(dx, dy, dz);
} else {
Tile* downTile = g_game.map.getTile(dx, dy, dz);
if (downTile) {
if (downTile->hasFlag(TILESTATE_FLOORCHANGE_NORTH)) {
++dy;
}
if (downTile->hasFlag(TILESTATE_FLOORCHANGE_SOUTH)) {
--dy;
}
if (downTile->hasFlag(TILESTATE_FLOORCHANGE_SOUTH_ALT)) {
dy -= 2;
}
if (downTile->hasFlag(TILESTATE_FLOORCHANGE_EAST)) {
--dx;
}
if (downTile->hasFlag(TILESTATE_FLOORCHANGE_EAST_ALT)) {
dx -= 2;
}
if (downTile->hasFlag(TILESTATE_FLOORCHANGE_WEST)) {
++dx;
}
destTile = g_game.map.getTile(dx, dy, dz);
}
}
}
} else if (hasFlag(TILESTATE_FLOORCHANGE)) {
uint16_t dx = tilePos.x;
uint16_t dy = tilePos.y;
uint8_t dz = tilePos.z - 1;
if (hasFlag(TILESTATE_FLOORCHANGE_NORTH)) {
--dy;
}
if (hasFlag(TILESTATE_FLOORCHANGE_SOUTH)) {
++dy;
}
if (hasFlag(TILESTATE_FLOORCHANGE_EAST)) {
++dx;
}
if (hasFlag(TILESTATE_FLOORCHANGE_WEST)) {
--dx;
}
if (hasFlag(TILESTATE_FLOORCHANGE_SOUTH_ALT)) {
dy += 2;
}
if (hasFlag(TILESTATE_FLOORCHANGE_EAST_ALT)) {
dx += 2;
}
destTile = g_game.map.getTile(dx, dy, dz);
}
if (!destTile) {
destTile = this;
} else {
flags |= FLAG_NOLIMIT; // Will ignore that there is blocking items/creatures
}
if (destTile) {
Thing* destThing = destTile->getTopDownItem();
if (destThing) {
*destItem = destThing->getItem();
}
}
return destTile;
}
void Tile::addThing(int32_t, Thing* thing)
{
Creature* creature = thing->getCreature();
if (creature) {
g_game.map.clearSpectatorCache();
if (creature->getPlayer()) {
g_game.map.clearPlayersSpectatorCache();
}
creature->setParent(this);
CreatureVector* creatures = makeCreatures();
creatures->insert(creatures->begin(), creature);
} else {
Item* item = thing->getItem();
if (!item) {
return /*RETURNVALUE_NOTPOSSIBLE*/;
}
TileItemVector* items = getItemList();
if (items && items->size() >= 0xFFFF) {
return /*RETURNVALUE_NOTPOSSIBLE*/;
}
item->setParent(this);
const ItemType& itemType = Item::items[item->getID()];
if (itemType.isGroundTile()) {
if (!ground) {
ground = item;
onAddTileItem(item);
} else {
const ItemType& oldType = Item::items[ground->getID()];
Item* oldGround = ground;
ground->setParent(nullptr);
g_game.ReleaseItem(ground);
ground = item;
resetTileFlags(oldGround);
setTileFlags(item);
onUpdateTileItem(oldGround, oldType, item, itemType);
postRemoveNotification(oldGround, nullptr, 0);
}
} else if (itemType.alwaysOnTop) {
if (itemType.isSplash() && items) {
// remove old splash if exists
for (ItemVector::const_iterator it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end;
++it) {
Item* oldSplash = *it;
if (!Item::items[oldSplash->getID()].isSplash()) {
continue;
}
removeThing(oldSplash, 1);
oldSplash->setParent(nullptr);
g_game.ReleaseItem(oldSplash);
postRemoveNotification(oldSplash, nullptr, 0);
break;
}
}
bool isInserted = false;
if (items) {
for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) {
// Note: this is different from internalAddThing
if (itemType.alwaysOnTopOrder < Item::items[(*it)->getID()].alwaysOnTopOrder) {
items->insert(it, item);
isInserted = true;
break;
}
}
} else {
items = makeItemList();
}
if (!isInserted) {
items->push_back(item);
}
onAddTileItem(item);
} else {
if (itemType.isMagicField()) {
// remove old field item if exists
if (items) {
for (ItemVector::const_iterator it = items->getBeginDownItem(), end = items->getEndDownItem();
it != end; ++it) {
MagicField* oldField = (*it)->getMagicField();
if (oldField) {
if (oldField->isReplaceable()) {
removeThing(oldField, 1);
oldField->setParent(nullptr);
g_game.ReleaseItem(oldField);
postRemoveNotification(oldField, nullptr, 0);
break;
} else {
// This magic field cannot be replaced.
item->setParent(nullptr);
g_game.ReleaseItem(item);
return;
}
}
}
}
}
items = makeItemList();
items->insert(items->getBeginDownItem(), item);
items->addDownItemCount(1);
onAddTileItem(item);
}
}
}
void Tile::updateThing(Thing* thing, uint16_t itemId, uint32_t count)
{
int32_t index = getThingIndex(thing);
if (index == -1) {
return /*RETURNVALUE_NOTPOSSIBLE*/;
}
Item* item = thing->getItem();
if (!item) {
return /*RETURNVALUE_NOTPOSSIBLE*/;
}
const ItemType& oldType = Item::items[item->getID()];
const ItemType& newType = Item::items[itemId];
resetTileFlags(item);
item->setID(itemId);
item->setSubType(count);
setTileFlags(item);
onUpdateTileItem(item, oldType, item, newType);
}
void Tile::replaceThing(uint32_t index, Thing* thing)
{
int32_t pos = index;
Item* item = thing->getItem();
if (!item) {
return /*RETURNVALUE_NOTPOSSIBLE*/;
}
Item* oldItem = nullptr;
bool isInserted = false;
if (ground) {
if (pos == 0) {
oldItem = ground;
ground = item;
isInserted = true;
}
--pos;
}
TileItemVector* items = getItemList();
if (items && !isInserted) {
int32_t topItemSize = getTopItemCount();
if (pos < topItemSize) {
auto it = items->getBeginTopItem();
it += pos;
oldItem = (*it);
it = items->erase(it);
items->insert(it, item);
isInserted = true;
}
pos -= topItemSize;
}
CreatureVector* creatures = getCreatures();
if (creatures) {
if (!isInserted && pos < static_cast<int32_t>(creatures->size())) {
return /*RETURNVALUE_NOTPOSSIBLE*/;
}
pos -= static_cast<uint32_t>(creatures->size());
}
if (items && !isInserted) {
int32_t downItemSize = getDownItemCount();
if (pos < downItemSize) {
auto it = items->getBeginDownItem() + pos;
oldItem = *it;
it = items->erase(it);
items->insert(it, item);
isInserted = true;
}
}
if (isInserted) {
item->setParent(this);
resetTileFlags(oldItem);
setTileFlags(item);
const ItemType& oldType = Item::items[oldItem->getID()];
const ItemType& newType = Item::items[item->getID()];
onUpdateTileItem(oldItem, oldType, item, newType);
oldItem->setParent(nullptr);
return /*RETURNVALUE_NOERROR*/;
}
}
void Tile::removeThing(Thing* thing, uint32_t count)
{
Creature* creature = thing->getCreature();
if (creature) {
CreatureVector* creatures = getCreatures();
if (creatures) {
auto it = std::find(creatures->begin(), creatures->end(), thing);
if (it != creatures->end()) {
g_game.map.clearSpectatorCache();
if (creature->getPlayer()) {
g_game.map.clearPlayersSpectatorCache();
}
creatures->erase(it);
}
}
return;
}
Item* item = thing->getItem();
if (!item) {
return;
}
int32_t index = getThingIndex(item);
if (index == -1) {
return;
}
if (item == ground) {
ground->setParent(nullptr);
ground = nullptr;
SpectatorVec spectators;
g_game.map.getSpectators(spectators, getPosition(), true);
onRemoveTileItem(spectators, std::vector<int32_t>(spectators.size(), 0), item);
return;
}
TileItemVector* items = getItemList();
if (!items) {
return;
}
const ItemType& itemType = Item::items[item->getID()];
if (itemType.alwaysOnTop) {
auto it = std::find(items->getBeginTopItem(), items->getEndTopItem(), item);
if (it == items->getEndTopItem()) {
return;
}
std::vector<int32_t> oldStackPosVector;
SpectatorVec spectators;
g_game.map.getSpectators(spectators, getPosition(), true);
for (Creature* spectator : spectators) {
if (Player* spectatorPlayer = spectator->getPlayer()) {
oldStackPosVector.push_back(getStackposOfItem(spectatorPlayer, item));
}
}
item->setParent(nullptr);
items->erase(it);
onRemoveTileItem(spectators, oldStackPosVector, item);
} else {
auto it = std::find(items->getBeginDownItem(), items->getEndDownItem(), item);
if (it == items->getEndDownItem()) {
return;
}
if (itemType.stackable && count != item->getItemCount()) {
uint8_t newCount =
static_cast<uint8_t>(std::max<int32_t>(0, static_cast<int32_t>(item->getItemCount() - count)));
item->setItemCount(newCount);
onUpdateTileItem(item, itemType, item, itemType);
} else {
std::vector<int32_t> oldStackPosVector;
SpectatorVec spectators;
g_game.map.getSpectators(spectators, getPosition(), true);
for (Creature* spectator : spectators) {
if (Player* spectatorPlayer = spectator->getPlayer()) {
oldStackPosVector.push_back(getStackposOfItem(spectatorPlayer, item));
}
}
item->setParent(nullptr);
items->erase(it);
items->addDownItemCount(-1);
onRemoveTileItem(spectators, oldStackPosVector, item);
}
}
}
bool Tile::hasCreature(Creature* creature) const
{
if (const CreatureVector* creatures = getCreatures()) {
return std::find(creatures->begin(), creatures->end(), creature) != creatures->end();
}
return false;
}
void Tile::removeCreature(Creature* creature)
{
g_game.map.getQTNode(tilePos.x, tilePos.y)->removeCreature(creature);
removeThing(creature, 0);
}
int32_t Tile::getThingIndex(const Thing* thing) const
{
int32_t n = -1;
if (ground) {
if (ground == thing) {
return 0;
}
++n;
}
const TileItemVector* items = getItemList();
if (items) {
const Item* item = thing->getItem();
if (item && item->isAlwaysOnTop()) {
for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) {
++n;
if (*it == item) {
return n;
}
}
} else {
n += items->getTopItemCount();
}
}
if (const CreatureVector* creatures = getCreatures()) {
if (thing->getCreature()) {
for (Creature* creature : *creatures) {
++n;
if (creature == thing) {
return n;
}
}
} else {
n += creatures->size();
}
}
if (items) {
const Item* item = thing->getItem();
if (item && !item->isAlwaysOnTop()) {
for (auto it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) {
++n;
if (*it == item) {
return n;
}
}
}
}
return -1;
}
int32_t Tile::getClientIndexOfCreature(const Player* player, const Creature* creature) const
{
int32_t n;
if (ground) {
n = 1;
} else {
n = 0;
}
const TileItemVector* items = getItemList();
if (items) {
n += items->getTopItemCount();
}
if (const CreatureVector* creatures = getCreatures()) {
for (auto it = creatures->rbegin(), end = creatures->rend(); it != end; ++it) {
const Creature* c = (*it);
if (c == creature) {
return n;
} else if (player->canSeeCreature(c)) {
++n;
}
}
}
return -1;
}
int32_t Tile::getStackposOfItem(const Player* player, const Item* item) const
{
int32_t n = 0;
if (ground) {
if (ground == item) {
return n;
}
++n;
}
const TileItemVector* items = getItemList();
if (items) {
if (item->isAlwaysOnTop()) {
for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) {
if (*it == item) {
return n;
} else if (++n == MAX_STACKPOS) {
return -1;
}
}
} else {
n += items->getTopItemCount();
if (n >= MAX_STACKPOS) {
return -1;
}
}
}
if (const CreatureVector* creatures = getCreatures()) {
for (const Creature* creature : *creatures) {
if (player->canSeeCreature(creature)) {
if (++n >= MAX_STACKPOS) {
return -1;
}
}
}
}
if (items && !item->isAlwaysOnTop()) {
for (auto it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) {
if (*it == item) {
return n;
} else if (++n >= MAX_STACKPOS) {
return -1;
}
}
}
return -1;
}
uint32_t Tile::getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) const
{
uint32_t count = 0;
if (ground && ground->getID() == itemId) {
count += Item::countByType(ground, subType);
}
const TileItemVector* items = getItemList();
if (items) {
for (const Item* item : *items) {
if (item->getID() == itemId) {
count += Item::countByType(item, subType);
}
}
}
return count;
}
Thing* Tile::getThing(size_t index) const
{
if (ground) {
if (index == 0) {
return ground;
}
--index;
}
const TileItemVector* items = getItemList();
if (items) {
uint32_t topItemSize = items->getTopItemCount();
if (index < topItemSize) {
return items->at(items->getDownItemCount() + index);
}
index -= topItemSize;
}
if (const CreatureVector* creatures = getCreatures()) {
if (index < creatures->size()) {
return (*creatures)[index];
}
index -= creatures->size();
}
if (items && index < items->getDownItemCount()) {
return items->at(index);
}
return nullptr;
}
void Tile::postAddNotification(Thing* thing, const Thing* oldParent, int32_t index,
ReceiverLink_t link /*= LINK_OWNER*/)
{
SpectatorVec spectators;
g_game.map.getSpectators(spectators, getPosition(), true, true);
for (Creature* spectator : spectators) {
assert(dynamic_cast<Player*>(spectator) != nullptr);
static_cast<Player*>(spectator)->postAddNotification(thing, oldParent, index, LINK_NEAR);
}
// add a reference to this item, it may be deleted after being added (mailbox for example)
Creature* creature = thing->getCreature();
Item* item;
if (creature) {
creature->incrementReferenceCounter();
item = nullptr;
} else {
item = thing->getItem();
if (item) {
item->incrementReferenceCounter();
}
}
if (link == LINK_OWNER) {
if (hasFlag(TILESTATE_TELEPORT)) {
Teleport* teleport = getTeleportItem();
if (teleport) {
teleport->addThing(thing);
}
} else if (hasFlag(TILESTATE_TRASHHOLDER)) {
TrashHolder* trashholder = getTrashHolder();
if (trashholder) {
trashholder->addThing(thing);
}
} else if (hasFlag(TILESTATE_MAILBOX)) {
Mailbox* mailbox = getMailbox();
if (mailbox) {
mailbox->addThing(thing);
}
}
// calling movement scripts
if (creature) {
g_moveEvents->onCreatureMove(creature, this, MOVE_EVENT_STEP_IN);
} else if (item) {
g_moveEvents->onItemMove(item, this, true);
}
}
// release the reference to this item onces we are finished
if (creature) {
g_game.ReleaseCreature(creature);
} else if (item) {
g_game.ReleaseItem(item);
}
}
void Tile::postRemoveNotification(Thing* thing, const Thing* newParent, int32_t index, ReceiverLink_t)
{
const auto thingCount = getThingCount();
SpectatorVec spectators;
g_game.map.getSpectators(spectators, tilePos, true, true);
for (Creature* spectator : spectators) {
assert(dynamic_cast<Player*>(spectator) != nullptr);
if (thingCount > TILE_UPDATE_THRESHOLD) {
// If the tile contains more than the defined threshold of things,
// send a full tile update to the player to keep the clients view in sync
static_cast<Player*>(spectator)->sendUpdateTile(this, tilePos);
}
static_cast<Player*>(spectator)->postRemoveNotification(thing, newParent, index, LINK_NEAR);
}
// calling movement scripts
Creature* creature = thing->getCreature();
if (creature) {
g_moveEvents->onCreatureMove(creature, this, MOVE_EVENT_STEP_OUT);
} else {
Item* item = thing->getItem();
if (item) {
g_moveEvents->onItemMove(item, this, false);
}
}
}
void Tile::internalAddThing(uint32_t, Thing* thing)
{
thing->setParent(this);
Creature* creature = thing->getCreature();
if (creature) {
g_game.map.clearSpectatorCache();
if (creature->getPlayer()) {
g_game.map.clearPlayersSpectatorCache();
}
CreatureVector* creatures = makeCreatures();
creatures->insert(creatures->begin(), creature);
} else {
Item* item = thing->getItem();
if (!item) {
return;
}
const ItemType& itemType = Item::items[item->getID()];
if (itemType.isGroundTile()) {
if (!ground) {
ground = item;
setTileFlags(item);
}
return;
}
TileItemVector* items = makeItemList();
if (items->size() >= 0xFFFF) {
return /*RETURNVALUE_NOTPOSSIBLE*/;
}
if (itemType.alwaysOnTop) {
bool isInserted = false;
for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) {
if (Item::items[(*it)->getID()].alwaysOnTopOrder >= itemType.alwaysOnTopOrder) {
items->insert(it, item);
isInserted = true;
break;
}
}
if (!isInserted) {
items->push_back(item);
}
} else {
items->insert(items->getBeginDownItem(), item);
items->addDownItemCount(1);
}
setTileFlags(item);
}
}
void Tile::setTileFlags(const Item* item)
{
if (!hasFlag(TILESTATE_FLOORCHANGE)) {
const ItemType& it = Item::items[item->getID()];
if (it.floorChange != 0) {
setFlag(it.floorChange);
}
}
if (item->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID)) {
setFlag(TILESTATE_IMMOVABLEBLOCKSOLID);
}
if (item->hasProperty(CONST_PROP_BLOCKPATH)) {
setFlag(TILESTATE_BLOCKPATH);
}
if (item->hasProperty(CONST_PROP_NOFIELDBLOCKPATH)) {
setFlag(TILESTATE_NOFIELDBLOCKPATH);
}
if (item->hasProperty(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH)) {
setFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH);
}
if (item->getTeleport()) {
setFlag(TILESTATE_TELEPORT);
}
if (item->getMagicField()) {
setFlag(TILESTATE_MAGICFIELD);
}
if (item->getMailbox()) {
setFlag(TILESTATE_MAILBOX);
}
if (item->getTrashHolder()) {
setFlag(TILESTATE_TRASHHOLDER);
}
if (item->hasProperty(CONST_PROP_BLOCKSOLID)) {
setFlag(TILESTATE_BLOCKSOLID);
}
if (item->getBed()) {
setFlag(TILESTATE_BED);
}
const Container* container = item->getContainer();
if (container && container->getDepotLocker()) {
setFlag(TILESTATE_DEPOT);
}
if (item->hasProperty(CONST_PROP_SUPPORTHANGABLE)) {
setFlag(TILESTATE_SUPPORTS_HANGABLE);
}
}
void Tile::resetTileFlags(const Item* item)
{
const ItemType& it = Item::items[item->getID()];
if (it.floorChange != 0) {
resetFlag(TILESTATE_FLOORCHANGE);
}
if (item->hasProperty(CONST_PROP_BLOCKSOLID) && !hasProperty(item, CONST_PROP_BLOCKSOLID)) {
resetFlag(TILESTATE_BLOCKSOLID);
}
if (item->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) && !hasProperty(item, CONST_PROP_IMMOVABLEBLOCKSOLID)) {
resetFlag(TILESTATE_IMMOVABLEBLOCKSOLID);
}
if (item->hasProperty(CONST_PROP_BLOCKPATH) && !hasProperty(item, CONST_PROP_BLOCKPATH)) {
resetFlag(TILESTATE_BLOCKPATH);
}
if (item->hasProperty(CONST_PROP_NOFIELDBLOCKPATH) && !hasProperty(item, CONST_PROP_NOFIELDBLOCKPATH)) {
resetFlag(TILESTATE_NOFIELDBLOCKPATH);
}
if (item->hasProperty(CONST_PROP_IMMOVABLEBLOCKPATH) && !hasProperty(item, CONST_PROP_IMMOVABLEBLOCKPATH)) {
resetFlag(TILESTATE_IMMOVABLEBLOCKPATH);
}
if (item->hasProperty(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH) &&
!hasProperty(item, CONST_PROP_IMMOVABLENOFIELDBLOCKPATH)) {
resetFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH);
}
if (item->getTeleport()) {
resetFlag(TILESTATE_TELEPORT);
}
if (item->getMagicField()) {
resetFlag(TILESTATE_MAGICFIELD);
}
if (item->getMailbox()) {
resetFlag(TILESTATE_MAILBOX);
}
if (item->getTrashHolder()) {
resetFlag(TILESTATE_TRASHHOLDER);
}
if (item->getBed()) {
resetFlag(TILESTATE_BED);
}
const Container* container = item->getContainer();
if (container && container->getDepotLocker()) {
resetFlag(TILESTATE_DEPOT);
}
if (item->hasProperty(CONST_PROP_SUPPORTHANGABLE)) {
resetFlag(TILESTATE_SUPPORTS_HANGABLE);
}
}
bool Tile::isMoveableBlocking() const { return !ground || hasFlag(TILESTATE_BLOCKSOLID); }
Item* Tile::getUseItem(int32_t index) const
{
const TileItemVector* items = getItemList();
// no items, get ground
if (!items || items->size() == 0) {
return ground;
}
// try getting thing by index
if (Thing* thing = getThing(index)) {
Item* thingItem = thing->getItem();
if (thingItem) {
return thingItem;
}
}
// try getting top movable item
Item* topDownItem = getTopDownItem();
if (topDownItem) {
return topDownItem;
}
// try getting door
for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) {
if ((*it)->getDoor()) {
return (*it)->getItem();
}
}
return *items->begin();
}