mirror of
https://github.com/ErikasKontenis/SabrehavenServer.git
synced 2025-04-30 17:49:20 +02:00
1029 lines
26 KiB
C++
1029 lines
26 KiB
C++
/**
|
|
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
|
|
* Copyright (C) 2019 Sabrehaven and Mark Samman <mark.samman@gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include "otpch.h"
|
|
|
|
#include "iomap.h"
|
|
#include "iomapserialize.h"
|
|
#include "combat.h"
|
|
#include "creature.h"
|
|
#include "monster.h"
|
|
#include "game.h"
|
|
|
|
extern Game g_game;
|
|
|
|
bool Map::loadMap(const std::string& identifier, bool loadHouses)
|
|
{
|
|
IOMap loader;
|
|
if (!loader.loadMap(this, identifier)) {
|
|
std::cout << "[Fatal - Map::loadMap] " << loader.getLastErrorString() << std::endl;
|
|
return false;
|
|
}
|
|
|
|
Npcs::loadNpcs();
|
|
if (!IOMap::loadSpawns(this)) {
|
|
std::cout << "[Warning - Map::loadMap] Failed to load spawn data." << std::endl;
|
|
}
|
|
|
|
if (loadHouses) {
|
|
if (!IOMap::loadHouses(this)) {
|
|
std::cout << "[Warning - Map::loadMap] Failed to load house data." << std::endl;
|
|
}
|
|
|
|
IOMapSerialize::loadHouseInfo();
|
|
IOMapSerialize::loadHouseItems(this);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Map::save()
|
|
{
|
|
bool saved = false;
|
|
for (uint32_t tries = 0; tries < 3; tries++) {
|
|
if (IOMapSerialize::saveHouseInfo()) {
|
|
saved = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!saved) {
|
|
return false;
|
|
}
|
|
|
|
saved = false;
|
|
for (uint32_t tries = 0; tries < 3; tries++) {
|
|
if (IOMapSerialize::saveHouseItems()) {
|
|
saved = true;
|
|
break;
|
|
}
|
|
}
|
|
return saved;
|
|
}
|
|
|
|
Tile* Map::getTile(uint16_t x, uint16_t y, uint8_t z) const
|
|
{
|
|
if (z >= MAP_MAX_LAYERS) {
|
|
return nullptr;
|
|
}
|
|
|
|
const QTreeLeafNode* leaf = QTreeNode::getLeafStatic<const QTreeLeafNode*, const QTreeNode*>(&root, x, y);
|
|
if (!leaf) {
|
|
return nullptr;
|
|
}
|
|
|
|
const Floor* floor = leaf->getFloor(z);
|
|
if (!floor) {
|
|
return nullptr;
|
|
}
|
|
return floor->tiles[x & FLOOR_MASK][y & FLOOR_MASK];
|
|
}
|
|
|
|
void Map::setTile(uint16_t x, uint16_t y, uint8_t z, Tile* newTile)
|
|
{
|
|
if (z >= MAP_MAX_LAYERS) {
|
|
std::cout << "ERROR: Attempt to set tile on invalid coordinate " << Position(x, y, z) << "!" << std::endl;
|
|
return;
|
|
}
|
|
|
|
QTreeLeafNode::newLeaf = false;
|
|
QTreeLeafNode* leaf = root.createLeaf(x, y, 15);
|
|
|
|
if (QTreeLeafNode::newLeaf) {
|
|
//update north
|
|
QTreeLeafNode* northLeaf = root.getLeaf(x, y - FLOOR_SIZE);
|
|
if (northLeaf) {
|
|
northLeaf->leafS = leaf;
|
|
}
|
|
|
|
//update west leaf
|
|
QTreeLeafNode* westLeaf = root.getLeaf(x - FLOOR_SIZE, y);
|
|
if (westLeaf) {
|
|
westLeaf->leafE = leaf;
|
|
}
|
|
|
|
//update south
|
|
QTreeLeafNode* southLeaf = root.getLeaf(x, y + FLOOR_SIZE);
|
|
if (southLeaf) {
|
|
leaf->leafS = southLeaf;
|
|
}
|
|
|
|
//update east
|
|
QTreeLeafNode* eastLeaf = root.getLeaf(x + FLOOR_SIZE, y);
|
|
if (eastLeaf) {
|
|
leaf->leafE = eastLeaf;
|
|
}
|
|
}
|
|
|
|
Floor* floor = leaf->createFloor(z);
|
|
uint32_t offsetX = x & FLOOR_MASK;
|
|
uint32_t offsetY = y & FLOOR_MASK;
|
|
|
|
Tile*& tile = floor->tiles[offsetX][offsetY];
|
|
if (tile) {
|
|
TileItemVector* items = newTile->getItemList();
|
|
if (items) {
|
|
for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) {
|
|
tile->addThing(*it);
|
|
}
|
|
items->clear();
|
|
}
|
|
|
|
Item* ground = newTile->getGround();
|
|
if (ground) {
|
|
tile->addThing(ground);
|
|
newTile->setGround(nullptr);
|
|
}
|
|
delete newTile;
|
|
} else {
|
|
tile = newTile;
|
|
}
|
|
}
|
|
|
|
bool Map::placeCreature(const Position& centerPos, Creature* creature, bool extendedPos/* = false*/, bool forceLogin/* = false*/)
|
|
{
|
|
bool foundTile;
|
|
bool placeInPZ;
|
|
|
|
Tile* tile = getTile(centerPos.x, centerPos.y, centerPos.z);
|
|
if (tile) {
|
|
placeInPZ = tile->hasFlag(TILESTATE_PROTECTIONZONE);
|
|
|
|
ReturnValue ret;
|
|
if (creature->getPlayer()) {
|
|
ret = tile->queryAdd(0, *creature, 1, 0);
|
|
} else {
|
|
ret = tile->queryAdd(0, *creature, 1, (creature->getMonster() ? FLAG_PLACECHECK : FLAG_IGNOREBLOCKITEM));
|
|
}
|
|
|
|
foundTile = forceLogin || ret == RETURNVALUE_NOERROR || ret == RETURNVALUE_PLAYERISNOTINVITED;
|
|
} else {
|
|
placeInPZ = false;
|
|
foundTile = false;
|
|
}
|
|
|
|
if (!foundTile) {
|
|
static std::vector<std::pair<int32_t, int32_t>> extendedRelList {
|
|
{0, -2},
|
|
{-1, -1}, {0, -1}, {1, -1},
|
|
{-2, 0}, {-1, 0}, {1, 0}, {2, 0},
|
|
{-1, 1}, {0, 1}, {1, 1},
|
|
{0, 2}
|
|
};
|
|
|
|
static std::vector<std::pair<int32_t, int32_t>> normalRelList {
|
|
{-1, -1}, {0, -1}, {1, -1},
|
|
{-1, 0}, {1, 0},
|
|
{-1, 1}, {0, 1}, {1, 1}
|
|
};
|
|
|
|
std::vector<std::pair<int32_t, int32_t>>& relList = (extendedPos ? extendedRelList : normalRelList);
|
|
|
|
if (extendedPos) {
|
|
std::shuffle(relList.begin(), relList.begin() + 4, getRandomGenerator());
|
|
std::shuffle(relList.begin() + 4, relList.end(), getRandomGenerator());
|
|
} else {
|
|
std::shuffle(relList.begin(), relList.end(), getRandomGenerator());
|
|
}
|
|
|
|
for (const auto& it : relList) {
|
|
Position tryPos(centerPos.x + it.first, centerPos.y + it.second, centerPos.z);
|
|
|
|
tile = getTile(tryPos.x, tryPos.y, tryPos.z);
|
|
if (!tile || (placeInPZ && !tile->hasFlag(TILESTATE_PROTECTIONZONE))) {
|
|
continue;
|
|
}
|
|
|
|
if (tile->queryAdd(0, *creature, 1, (creature->getMonster() ? FLAG_PLACECHECK : 0)) == RETURNVALUE_NOERROR) {
|
|
if (!extendedPos || isSightClear(centerPos, tryPos, false)) {
|
|
foundTile = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!foundTile) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int32_t index = 0;
|
|
uint32_t flags = 0;
|
|
Item* toItem = nullptr;
|
|
|
|
Cylinder* toCylinder = tile->queryDestination(index, *creature, &toItem, flags);
|
|
toCylinder->internalAddThing(creature);
|
|
|
|
const Position& dest = toCylinder->getPosition();
|
|
getQTNode(dest.x, dest.y)->addCreature(creature);
|
|
return true;
|
|
}
|
|
|
|
void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport/* = false*/)
|
|
{
|
|
Tile& oldTile = *creature.getTile();
|
|
|
|
Position oldPos = oldTile.getPosition();
|
|
Position newPos = newTile.getPosition();
|
|
|
|
bool teleport = forceTeleport || !newTile.getGround() || !Position::areInRange<1, 1, 0>(oldPos, newPos);
|
|
|
|
SpectatorVec list;
|
|
getSpectators(list, oldPos, true);
|
|
getSpectators(list, newPos, true);
|
|
|
|
std::vector<int32_t> oldStackPosVector;
|
|
for (Creature* spectator : list) {
|
|
if (Player* tmpPlayer = spectator->getPlayer()) {
|
|
if (tmpPlayer->canSeeCreature(&creature)) {
|
|
oldStackPosVector.push_back(oldTile.getClientIndexOfCreature(tmpPlayer, &creature));
|
|
} else {
|
|
oldStackPosVector.push_back(-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
//remove the creature
|
|
oldTile.removeThing(&creature, 0);
|
|
|
|
QTreeLeafNode* leaf = getQTNode(oldPos.x, oldPos.y);
|
|
QTreeLeafNode* new_leaf = getQTNode(newPos.x, newPos.y);
|
|
|
|
// Switch the node ownership
|
|
if (leaf != new_leaf) {
|
|
leaf->removeCreature(&creature);
|
|
new_leaf->addCreature(&creature);
|
|
}
|
|
|
|
//add the creature
|
|
newTile.addThing(&creature);
|
|
|
|
if (!teleport) {
|
|
if (oldPos.y > newPos.y) {
|
|
creature.setDirection(DIRECTION_NORTH);
|
|
} else if (oldPos.y < newPos.y) {
|
|
creature.setDirection(DIRECTION_SOUTH);
|
|
}
|
|
|
|
if (oldPos.x < newPos.x) {
|
|
creature.setDirection(DIRECTION_EAST);
|
|
} else if (oldPos.x > newPos.x) {
|
|
creature.setDirection(DIRECTION_WEST);
|
|
}
|
|
}
|
|
|
|
//send to client
|
|
size_t i = 0;
|
|
for (Creature* spectator : list) {
|
|
if (Player* tmpPlayer = spectator->getPlayer()) {
|
|
//Use the correct stackpos
|
|
int32_t stackpos = oldStackPosVector[i++];
|
|
if (stackpos != -1) {
|
|
tmpPlayer->sendCreatureMove(&creature, newPos, newTile.getStackposOfCreature(tmpPlayer, &creature), oldPos, stackpos, teleport);
|
|
}
|
|
}
|
|
}
|
|
|
|
//event method
|
|
for (Creature* spectator : list) {
|
|
spectator->onCreatureMove(&creature, &newTile, newPos, &oldTile, oldPos, teleport);
|
|
}
|
|
|
|
oldTile.postRemoveNotification(&creature, &newTile, 0);
|
|
newTile.postAddNotification(&creature, &oldTile, 0);
|
|
}
|
|
|
|
void Map::getSpectatorsInternal(SpectatorVec& list, const Position& centerPos, int32_t minRangeX, int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY, int32_t minRangeZ, int32_t maxRangeZ, bool onlyPlayers) const
|
|
{
|
|
int_fast16_t min_y = centerPos.y + minRangeY;
|
|
int_fast16_t min_x = centerPos.x + minRangeX;
|
|
int_fast16_t max_y = centerPos.y + maxRangeY;
|
|
int_fast16_t max_x = centerPos.x + maxRangeX;
|
|
|
|
int32_t minoffset = centerPos.getZ() - maxRangeZ;
|
|
uint16_t x1 = std::min<uint32_t>(0xFFFF, std::max<int32_t>(0, (min_x + minoffset)));
|
|
uint16_t y1 = std::min<uint32_t>(0xFFFF, std::max<int32_t>(0, (min_y + minoffset)));
|
|
|
|
int32_t maxoffset = centerPos.getZ() - minRangeZ;
|
|
uint16_t x2 = std::min<uint32_t>(0xFFFF, std::max<int32_t>(0, (max_x + maxoffset)));
|
|
uint16_t y2 = std::min<uint32_t>(0xFFFF, std::max<int32_t>(0, (max_y + maxoffset)));
|
|
|
|
int32_t startx1 = x1 - (x1 % FLOOR_SIZE);
|
|
int32_t starty1 = y1 - (y1 % FLOOR_SIZE);
|
|
int32_t endx2 = x2 - (x2 % FLOOR_SIZE);
|
|
int32_t endy2 = y2 - (y2 % FLOOR_SIZE);
|
|
|
|
const QTreeLeafNode* startLeaf = QTreeNode::getLeafStatic<const QTreeLeafNode*, const QTreeNode*>(&root, startx1, starty1);
|
|
const QTreeLeafNode* leafS = startLeaf;
|
|
const QTreeLeafNode* leafE;
|
|
|
|
for (int_fast32_t ny = starty1; ny <= endy2; ny += FLOOR_SIZE) {
|
|
leafE = leafS;
|
|
for (int_fast32_t nx = startx1; nx <= endx2; nx += FLOOR_SIZE) {
|
|
if (leafE) {
|
|
const CreatureVector& node_list = (onlyPlayers ? leafE->player_list : leafE->creature_list);
|
|
for (Creature* creature : node_list) {
|
|
const Position& cpos = creature->getPosition();
|
|
if (minRangeZ > cpos.z || maxRangeZ < cpos.z) {
|
|
continue;
|
|
}
|
|
|
|
int_fast16_t offsetZ = Position::getOffsetZ(centerPos, cpos);
|
|
if ((min_y + offsetZ) > cpos.y || (max_y + offsetZ) < cpos.y || (min_x + offsetZ) > cpos.x || (max_x + offsetZ) < cpos.x) {
|
|
continue;
|
|
}
|
|
|
|
list.insert(creature);
|
|
}
|
|
leafE = leafE->leafE;
|
|
} else {
|
|
leafE = QTreeNode::getLeafStatic<const QTreeLeafNode*, const QTreeNode*>(&root, nx + FLOOR_SIZE, ny);
|
|
}
|
|
}
|
|
|
|
if (leafS) {
|
|
leafS = leafS->leafS;
|
|
} else {
|
|
leafS = QTreeNode::getLeafStatic<const QTreeLeafNode*, const QTreeNode*>(&root, startx1, ny + FLOOR_SIZE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Map::getSpectators(SpectatorVec& list, const Position& centerPos, bool multifloor /*= false*/, bool onlyPlayers /*= false*/, int32_t minRangeX /*= 0*/, int32_t maxRangeX /*= 0*/, int32_t minRangeY /*= 0*/, int32_t maxRangeY /*= 0*/)
|
|
{
|
|
if (centerPos.z >= MAP_MAX_LAYERS) {
|
|
return;
|
|
}
|
|
|
|
bool foundCache = false;
|
|
bool cacheResult = false;
|
|
|
|
minRangeX = (minRangeX == 0 ? -maxViewportX : -minRangeX);
|
|
maxRangeX = (maxRangeX == 0 ? maxViewportX : maxRangeX);
|
|
minRangeY = (minRangeY == 0 ? -maxViewportY : -minRangeY);
|
|
maxRangeY = (maxRangeY == 0 ? maxViewportY : maxRangeY);
|
|
|
|
if (minRangeX == -maxViewportX && maxRangeX == maxViewportX && minRangeY == -maxViewportY && maxRangeY == maxViewportY && multifloor) {
|
|
if (onlyPlayers) {
|
|
auto it = playersSpectatorCache.find(centerPos);
|
|
if (it != playersSpectatorCache.end()) {
|
|
if (!list.empty()) {
|
|
const SpectatorVec& cachedList = it->second;
|
|
list.insert(cachedList.begin(), cachedList.end());
|
|
} else {
|
|
list = it->second;
|
|
}
|
|
|
|
foundCache = true;
|
|
}
|
|
}
|
|
|
|
if (!foundCache) {
|
|
auto it = spectatorCache.find(centerPos);
|
|
if (it != spectatorCache.end()) {
|
|
if (!onlyPlayers) {
|
|
if (!list.empty()) {
|
|
const SpectatorVec& cachedList = it->second;
|
|
list.insert(cachedList.begin(), cachedList.end());
|
|
} else {
|
|
list = it->second;
|
|
}
|
|
} else {
|
|
const SpectatorVec& cachedList = it->second;
|
|
for (Creature* spectator : cachedList) {
|
|
if (spectator->getPlayer()) {
|
|
list.insert(spectator);
|
|
}
|
|
}
|
|
}
|
|
|
|
foundCache = true;
|
|
} else {
|
|
cacheResult = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!foundCache) {
|
|
int32_t minRangeZ;
|
|
int32_t maxRangeZ;
|
|
|
|
if (multifloor) {
|
|
if (centerPos.z > 7) {
|
|
//underground
|
|
|
|
//8->15
|
|
minRangeZ = std::max<int32_t>(centerPos.getZ() - 2, 0);
|
|
maxRangeZ = std::min<int32_t>(centerPos.getZ() + 2, MAP_MAX_LAYERS - 1);
|
|
} else if (centerPos.z == 6) {
|
|
minRangeZ = 0;
|
|
maxRangeZ = 8;
|
|
} else if (centerPos.z == 7) {
|
|
minRangeZ = 0;
|
|
maxRangeZ = 9;
|
|
} else {
|
|
minRangeZ = 0;
|
|
maxRangeZ = 7;
|
|
}
|
|
} else {
|
|
minRangeZ = centerPos.z;
|
|
maxRangeZ = centerPos.z;
|
|
}
|
|
|
|
getSpectatorsInternal(list, centerPos, minRangeX, maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ, onlyPlayers);
|
|
|
|
if (cacheResult) {
|
|
if (onlyPlayers) {
|
|
playersSpectatorCache[centerPos] = list;
|
|
} else {
|
|
spectatorCache[centerPos] = list;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Map::clearSpectatorCache()
|
|
{
|
|
spectatorCache.clear();
|
|
playersSpectatorCache.clear();
|
|
}
|
|
|
|
bool Map::canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight /*= true*/,
|
|
int32_t rangex /*= Map::maxClientViewportX*/, int32_t rangey /*= Map::maxClientViewportY*/) const
|
|
{
|
|
//z checks
|
|
//underground 8->15
|
|
//ground level and above 7->0
|
|
if ((fromPos.z >= 8 && toPos.z < 8) || (toPos.z >= 8 && fromPos.z < 8)) {
|
|
return false;
|
|
}
|
|
|
|
int32_t deltaz = Position::getDistanceZ(fromPos, toPos);
|
|
if (deltaz > 2) {
|
|
return false;
|
|
}
|
|
|
|
if ((Position::getDistanceX(fromPos, toPos) - deltaz) > rangex) {
|
|
return false;
|
|
}
|
|
|
|
//distance checks
|
|
if ((Position::getDistanceY(fromPos, toPos) - deltaz) > rangey) {
|
|
return false;
|
|
}
|
|
|
|
if (!checkLineOfSight) {
|
|
return true;
|
|
}
|
|
return isSightClear(fromPos, toPos, false);
|
|
}
|
|
|
|
bool Map::checkSightLine(const Position& fromPos, const Position& toPos) const
|
|
{
|
|
if (fromPos == toPos) {
|
|
return true;
|
|
}
|
|
|
|
Position start(fromPos.z > toPos.z ? toPos : fromPos);
|
|
Position destination(fromPos.z > toPos.z ? fromPos : toPos);
|
|
|
|
const int8_t mx = start.x < destination.x ? 1 : start.x == destination.x ? 0 : -1;
|
|
const int8_t my = start.y < destination.y ? 1 : start.y == destination.y ? 0 : -1;
|
|
|
|
int32_t A = Position::getOffsetY(destination, start);
|
|
int32_t B = Position::getOffsetX(start, destination);
|
|
int32_t C = -(A * destination.x + B * destination.y);
|
|
|
|
while (start.x != destination.x || start.y != destination.y) {
|
|
int32_t move_hor = std::abs(A * (start.x + mx) + B * (start.y) + C);
|
|
int32_t move_ver = std::abs(A * (start.x) + B * (start.y + my) + C);
|
|
int32_t move_cross = std::abs(A * (start.x + mx) + B * (start.y + my) + C);
|
|
|
|
if (start.y != destination.y && (start.x == destination.x || move_hor > move_ver || move_hor > move_cross)) {
|
|
start.y += my;
|
|
}
|
|
|
|
if (start.x != destination.x && (start.y == destination.y || move_ver > move_hor || move_ver > move_cross)) {
|
|
start.x += mx;
|
|
}
|
|
|
|
const Tile* tile = getTile(start.x, start.y, start.z);
|
|
if (tile && tile->hasProperty(CONST_PROP_BLOCKPROJECTILE)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// now we need to perform a jump between floors to see if everything is clear (literally)
|
|
while (start.z != destination.z) {
|
|
const Tile* tile = getTile(start.x, start.y, start.z);
|
|
if (tile && tile->getThingCount() > 0) {
|
|
return false;
|
|
}
|
|
|
|
start.z++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Map::isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const
|
|
{
|
|
if (floorCheck && fromPos.z != toPos.z) {
|
|
return false;
|
|
}
|
|
|
|
// Cast two converging rays and see if either yields a result.
|
|
return checkSightLine(fromPos, toPos) || checkSightLine(toPos, fromPos);
|
|
}
|
|
|
|
const Tile* Map::canWalkTo(const Creature& creature, const Position& pos) const
|
|
{
|
|
int32_t walkCache = creature.getWalkCache(pos);
|
|
if (walkCache == 0) {
|
|
return nullptr;
|
|
} else if (walkCache == 1) {
|
|
return getTile(pos.x, pos.y, pos.z);
|
|
}
|
|
|
|
//used for non-cached tiles
|
|
Tile* tile = getTile(pos.x, pos.y, pos.z);
|
|
if (creature.getTile() != tile) {
|
|
if (!tile || tile->queryAdd(0, creature, 1, FLAG_PATHFINDING) != RETURNVALUE_NOERROR) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
return tile;
|
|
}
|
|
|
|
bool Map::getPathMatching(const Creature& creature, std::forward_list<Direction>& dirList, const FrozenPathingConditionCall& pathCondition, const FindPathParams& fpp) const
|
|
{
|
|
Position pos = creature.getPosition();
|
|
Position endPos;
|
|
|
|
AStarNodes nodes(pos.x, pos.y);
|
|
|
|
int32_t bestMatch = 0;
|
|
|
|
static int_fast32_t dirNeighbors[8][5][2] = {
|
|
{{-1, 0}, {0, 1}, {1, 0}, {1, 1}, {-1, 1}},
|
|
{{-1, 0}, {0, 1}, {0, -1}, {-1, -1}, {-1, 1}},
|
|
{{-1, 0}, {1, 0}, {0, -1}, {-1, -1}, {1, -1}},
|
|
{{0, 1}, {1, 0}, {0, -1}, {1, -1}, {1, 1}},
|
|
{{1, 0}, {0, -1}, {-1, -1}, {1, -1}, {1, 1}},
|
|
{{-1, 0}, {0, -1}, {-1, -1}, {1, -1}, {-1, 1}},
|
|
{{0, 1}, {1, 0}, {1, -1}, {1, 1}, {-1, 1}},
|
|
{{-1, 0}, {0, 1}, {-1, -1}, {1, 1}, {-1, 1}}
|
|
};
|
|
static int_fast32_t allNeighbors[8][2] = {
|
|
{-1, 0}, {0, 1}, {1, 0}, {0, -1}, {-1, -1}, {1, -1}, {1, 1}, {-1, 1}
|
|
};
|
|
|
|
const Position startPos = pos;
|
|
|
|
AStarNode* found = nullptr;
|
|
while (fpp.maxSearchDist != 0 || nodes.getClosedNodes() < 100) {
|
|
AStarNode* n = nodes.getBestNode();
|
|
if (!n) {
|
|
if (found) {
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const int_fast32_t x = n->x;
|
|
const int_fast32_t y = n->y;
|
|
pos.x = x;
|
|
pos.y = y;
|
|
if (pathCondition(startPos, pos, fpp, bestMatch)) {
|
|
found = n;
|
|
endPos = pos;
|
|
if (bestMatch == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint_fast32_t dirCount;
|
|
int_fast32_t* neighbors;
|
|
if (n->parent) {
|
|
const int_fast32_t offset_x = n->parent->x - x;
|
|
const int_fast32_t offset_y = n->parent->y - y;
|
|
if (offset_y == 0) {
|
|
if (offset_x == -1) {
|
|
neighbors = *dirNeighbors[DIRECTION_WEST];
|
|
} else {
|
|
neighbors = *dirNeighbors[DIRECTION_EAST];
|
|
}
|
|
} else if (!fpp.allowDiagonal || offset_x == 0) {
|
|
if (offset_y == -1) {
|
|
neighbors = *dirNeighbors[DIRECTION_NORTH];
|
|
} else {
|
|
neighbors = *dirNeighbors[DIRECTION_SOUTH];
|
|
}
|
|
} else if (offset_y == -1) {
|
|
if (offset_x == -1) {
|
|
neighbors = *dirNeighbors[DIRECTION_NORTHWEST];
|
|
} else {
|
|
neighbors = *dirNeighbors[DIRECTION_NORTHEAST];
|
|
}
|
|
} else if (offset_x == -1) {
|
|
neighbors = *dirNeighbors[DIRECTION_SOUTHWEST];
|
|
} else {
|
|
neighbors = *dirNeighbors[DIRECTION_SOUTHEAST];
|
|
}
|
|
dirCount = fpp.allowDiagonal ? 5 : 3;
|
|
} else {
|
|
dirCount = 8;
|
|
neighbors = *allNeighbors;
|
|
}
|
|
|
|
const int_fast32_t f = n->f;
|
|
for (uint_fast32_t i = 0; i < dirCount; ++i) {
|
|
pos.x = x + *neighbors++;
|
|
pos.y = y + *neighbors++;
|
|
|
|
if (fpp.maxSearchDist != 0 && (Position::getDistanceX(startPos, pos) > fpp.maxSearchDist || Position::getDistanceY(startPos, pos) > fpp.maxSearchDist)) {
|
|
continue;
|
|
}
|
|
|
|
if (fpp.keepDistance && !pathCondition.isInRange(startPos, pos, fpp)) {
|
|
continue;
|
|
}
|
|
|
|
const Tile* tile;
|
|
AStarNode* neighborNode = nodes.getNodeByPosition(pos.x, pos.y);
|
|
if (neighborNode) {
|
|
tile = getTile(pos.x, pos.y, pos.z);
|
|
} else {
|
|
tile = canWalkTo(creature, pos);
|
|
if (!tile) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//The cost (g) for this neighbor
|
|
const int_fast32_t cost = AStarNodes::getMapWalkCost(n, pos);
|
|
const int_fast32_t extraCost = AStarNodes::getTileWalkCost(creature, tile);
|
|
const int_fast32_t newf = f + cost + extraCost;
|
|
|
|
if (neighborNode) {
|
|
if (neighborNode->f <= newf) {
|
|
//The node on the closed/open list is cheaper than this one
|
|
continue;
|
|
}
|
|
|
|
neighborNode->f = newf;
|
|
neighborNode->parent = n;
|
|
nodes.openNode(neighborNode);
|
|
} else {
|
|
//Does not exist in the open/closed list, create a new node
|
|
neighborNode = nodes.createOpenNode(n, pos.x, pos.y, newf);
|
|
if (!neighborNode) {
|
|
if (found) {
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
nodes.closeNode(n);
|
|
}
|
|
|
|
if (!found) {
|
|
return false;
|
|
}
|
|
|
|
int_fast32_t prevx = endPos.x;
|
|
int_fast32_t prevy = endPos.y;
|
|
|
|
found = found->parent;
|
|
while (found) {
|
|
pos.x = found->x;
|
|
pos.y = found->y;
|
|
|
|
int_fast32_t dx = pos.getX() - prevx;
|
|
int_fast32_t dy = pos.getY() - prevy;
|
|
|
|
prevx = pos.x;
|
|
prevy = pos.y;
|
|
|
|
if (dx == 1 && dy == 1) {
|
|
dirList.push_front(DIRECTION_NORTHWEST);
|
|
} else if (dx == -1 && dy == 1) {
|
|
dirList.push_front(DIRECTION_NORTHEAST);
|
|
} else if (dx == 1 && dy == -1) {
|
|
dirList.push_front(DIRECTION_SOUTHWEST);
|
|
} else if (dx == -1 && dy == -1) {
|
|
dirList.push_front(DIRECTION_SOUTHEAST);
|
|
} else if (dx == 1) {
|
|
dirList.push_front(DIRECTION_WEST);
|
|
} else if (dx == -1) {
|
|
dirList.push_front(DIRECTION_EAST);
|
|
} else if (dy == 1) {
|
|
dirList.push_front(DIRECTION_NORTH);
|
|
} else if (dy == -1) {
|
|
dirList.push_front(DIRECTION_SOUTH);
|
|
}
|
|
|
|
found = found->parent;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// AStarNodes
|
|
|
|
AStarNodes::AStarNodes(uint32_t x, uint32_t y)
|
|
: nodes(), openNodes()
|
|
{
|
|
curNode = 1;
|
|
closedNodes = 0;
|
|
openNodes[0] = true;
|
|
|
|
AStarNode& startNode = nodes[0];
|
|
startNode.parent = nullptr;
|
|
startNode.x = x;
|
|
startNode.y = y;
|
|
startNode.f = 0;
|
|
nodeTable[(x << 16) | y] = nodes;
|
|
}
|
|
|
|
AStarNode* AStarNodes::createOpenNode(AStarNode* parent, uint32_t x, uint32_t y, int_fast32_t f)
|
|
{
|
|
if (curNode >= MAX_NODES) {
|
|
return nullptr;
|
|
}
|
|
|
|
size_t retNode = curNode++;
|
|
openNodes[retNode] = true;
|
|
|
|
AStarNode* node = nodes + retNode;
|
|
nodeTable[(x << 16) | y] = node;
|
|
node->parent = parent;
|
|
node->x = x;
|
|
node->y = y;
|
|
node->f = f;
|
|
return node;
|
|
}
|
|
|
|
AStarNode* AStarNodes::getBestNode()
|
|
{
|
|
if (curNode == 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
int32_t best_node_f = std::numeric_limits<int32_t>::max();
|
|
int32_t best_node = -1;
|
|
for (size_t i = 0; i < curNode; i++) {
|
|
if (openNodes[i] && nodes[i].f < best_node_f) {
|
|
best_node_f = nodes[i].f;
|
|
best_node = i;
|
|
}
|
|
}
|
|
|
|
if (best_node >= 0) {
|
|
return nodes + best_node;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void AStarNodes::closeNode(AStarNode* node)
|
|
{
|
|
size_t index = node - nodes;
|
|
assert(index < MAX_NODES);
|
|
openNodes[index] = false;
|
|
++closedNodes;
|
|
}
|
|
|
|
void AStarNodes::openNode(AStarNode* node)
|
|
{
|
|
size_t index = node - nodes;
|
|
assert(index < MAX_NODES);
|
|
if (!openNodes[index]) {
|
|
openNodes[index] = true;
|
|
--closedNodes;
|
|
}
|
|
}
|
|
|
|
int_fast32_t AStarNodes::getClosedNodes() const
|
|
{
|
|
return closedNodes;
|
|
}
|
|
|
|
AStarNode* AStarNodes::getNodeByPosition(uint32_t x, uint32_t y)
|
|
{
|
|
auto it = nodeTable.find((x << 16) | y);
|
|
if (it == nodeTable.end()) {
|
|
return nullptr;
|
|
}
|
|
return it->second;
|
|
}
|
|
|
|
int_fast32_t AStarNodes::getMapWalkCost(AStarNode* node, const Position& neighborPos)
|
|
{
|
|
if (std::abs(node->x - neighborPos.x) == std::abs(node->y - neighborPos.y)) {
|
|
//diagonal movement extra cost
|
|
return MAP_DIAGONALWALKCOST;
|
|
}
|
|
return MAP_NORMALWALKCOST;
|
|
}
|
|
|
|
int_fast32_t AStarNodes::getTileWalkCost(const Creature& creature, const Tile* tile)
|
|
{
|
|
int_fast32_t cost = 0;
|
|
if (tile->getTopVisibleCreature(&creature) != nullptr) {
|
|
if (const Monster* monster = creature.getMonster()) {
|
|
if (monster->canPushCreatures()) {
|
|
return cost;
|
|
}
|
|
}
|
|
|
|
//destroy creature cost
|
|
cost += MAP_NORMALWALKCOST * 3;
|
|
}
|
|
|
|
if (const MagicField* field = tile->getFieldItem()) {
|
|
CombatType_t combatType = field->getCombatType();
|
|
if (combatType != COMBAT_NONE) {
|
|
if (!creature.isImmune(combatType) && !creature.hasCondition(Combat::DamageToConditionType(combatType))) {
|
|
cost += MAP_NORMALWALKCOST * 18;
|
|
}
|
|
}
|
|
}
|
|
|
|
return cost;
|
|
}
|
|
|
|
// Floor
|
|
Floor::~Floor()
|
|
{
|
|
for (auto& row : tiles) {
|
|
for (auto tile : row) {
|
|
delete tile;
|
|
}
|
|
}
|
|
}
|
|
|
|
// QTreeNode
|
|
QTreeNode::~QTreeNode()
|
|
{
|
|
for (auto* ptr : child) {
|
|
delete ptr;
|
|
}
|
|
}
|
|
|
|
QTreeLeafNode* QTreeNode::getLeaf(uint32_t x, uint32_t y)
|
|
{
|
|
if (leaf) {
|
|
return static_cast<QTreeLeafNode*>(this);
|
|
}
|
|
|
|
QTreeNode* node = child[((x & 0x8000) >> 15) | ((y & 0x8000) >> 14)];
|
|
if (!node) {
|
|
return nullptr;
|
|
}
|
|
return node->getLeaf(x << 1, y << 1);
|
|
}
|
|
|
|
QTreeLeafNode* QTreeNode::createLeaf(uint32_t x, uint32_t y, uint32_t level)
|
|
{
|
|
if (!isLeaf()) {
|
|
uint32_t index = ((x & 0x8000) >> 15) | ((y & 0x8000) >> 14);
|
|
if (!child[index]) {
|
|
if (level != FLOOR_BITS) {
|
|
child[index] = new QTreeNode();
|
|
} else {
|
|
child[index] = new QTreeLeafNode();
|
|
QTreeLeafNode::newLeaf = true;
|
|
}
|
|
}
|
|
return child[index]->createLeaf(x * 2, y * 2, level - 1);
|
|
}
|
|
return static_cast<QTreeLeafNode*>(this);
|
|
}
|
|
|
|
// QTreeLeafNode
|
|
bool QTreeLeafNode::newLeaf = false;
|
|
|
|
QTreeLeafNode::~QTreeLeafNode()
|
|
{
|
|
for (auto* ptr : array) {
|
|
delete ptr;
|
|
}
|
|
}
|
|
|
|
Floor* QTreeLeafNode::createFloor(uint32_t z)
|
|
{
|
|
if (!array[z]) {
|
|
array[z] = new Floor();
|
|
}
|
|
return array[z];
|
|
}
|
|
|
|
void QTreeLeafNode::addCreature(Creature* c)
|
|
{
|
|
creature_list.push_back(c);
|
|
|
|
if (c->getPlayer()) {
|
|
player_list.push_back(c);
|
|
}
|
|
}
|
|
|
|
void QTreeLeafNode::removeCreature(Creature* c)
|
|
{
|
|
auto iter = std::find(creature_list.begin(), creature_list.end(), c);
|
|
assert(iter != creature_list.end());
|
|
*iter = creature_list.back();
|
|
creature_list.pop_back();
|
|
|
|
if (c->getPlayer()) {
|
|
iter = std::find(player_list.begin(), player_list.end(), c);
|
|
assert(iter != player_list.end());
|
|
*iter = player_list.back();
|
|
player_list.pop_back();
|
|
}
|
|
}
|
|
|
|
uint32_t Map::clean() const
|
|
{
|
|
uint64_t start = OTSYS_TIME();
|
|
size_t count = 0, tiles = 0;
|
|
|
|
if (g_game.getGameState() == GAME_STATE_NORMAL) {
|
|
g_game.setGameState(GAME_STATE_MAINTAIN);
|
|
}
|
|
|
|
std::vector<const QTreeNode*> nodes {
|
|
&root
|
|
};
|
|
std::vector<Item*> toRemove;
|
|
do {
|
|
const QTreeNode* node = nodes.back();
|
|
nodes.pop_back();
|
|
if (node->isLeaf()) {
|
|
const QTreeLeafNode* leafNode = static_cast<const QTreeLeafNode*>(node);
|
|
for (uint8_t z = 0; z < MAP_MAX_LAYERS; ++z) {
|
|
Floor* floor = leafNode->getFloor(z);
|
|
if (!floor) {
|
|
continue;
|
|
}
|
|
|
|
for (auto& row : floor->tiles) {
|
|
for (auto tile : row) {
|
|
if (!tile || tile->hasFlag(TILESTATE_PROTECTIONZONE)) {
|
|
continue;
|
|
}
|
|
|
|
TileItemVector* itemList = tile->getItemList();
|
|
if (!itemList) {
|
|
continue;
|
|
}
|
|
|
|
++tiles;
|
|
for (Item* item : *itemList) {
|
|
if (item->isCleanable()) {
|
|
toRemove.push_back(item);
|
|
}
|
|
}
|
|
|
|
for (Item* item : toRemove) {
|
|
g_game.internalRemoveItem(item, -1);
|
|
}
|
|
count += toRemove.size();
|
|
toRemove.clear();
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
for (auto childNode : node->child) {
|
|
if (childNode) {
|
|
nodes.push_back(childNode);
|
|
}
|
|
}
|
|
}
|
|
} while (!nodes.empty());
|
|
|
|
if (g_game.getGameState() == GAME_STATE_MAINTAIN) {
|
|
g_game.setGameState(GAME_STATE_NORMAL);
|
|
}
|
|
|
|
std::cout << "> CLEAN: Removed " << count << " item" << (count != 1 ? "s" : "")
|
|
<< " from " << tiles << " tile" << (tiles != 1 ? "s" : "") << " in "
|
|
<< (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl;
|
|
return count;
|
|
}
|