////////////////////////////////////////////////////////////////////////
// OpenTibia - an opensource roleplaying game
////////////////////////////////////////////////////////////////////////
// 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 3 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, see .
////////////////////////////////////////////////////////////////////////
#include "otpch.h"
#include
#include
#include
#include "iomap.h"
#include "map.h"
#include "tile.h"
#include "creature.h"
#include "player.h"
#include "combat.h"
#include "iomapserialize.h"
#include "items.h"
#include "game.h"
extern Game g_game;
Map::Map()
{
mapWidth = 0;
mapHeight = 0;
}
bool Map::loadMap(const std::string& identifier)
{
int64_t start = OTSYS_TIME();
IOMap* loader = new IOMap();
if(!loader->loadMap(this, identifier))
{
std::cout << "> FATAL: OTBM Loader - " << loader->getLastErrorString() << std::endl;
return false;
}
std::cout << "> Map loading time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl;
start = OTSYS_TIME();
if(!loader->loadSpawns(this))
std::cout << "> WARNING: Could not load spawn data." << std::endl;
if(!loader->loadHouses(this))
std::cout << "> WARNING: Could not load house data." << std::endl;
delete loader;
std::cout << "> Data parsing time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl;
start = OTSYS_TIME();
IOMapSerialize::getInstance()->updateHouses();
IOMapSerialize::getInstance()->updateAuctions();
std::cout << "> Houses synchronization time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl;
start = OTSYS_TIME();
IOMapSerialize::getInstance()->loadHouses();
IOMapSerialize::getInstance()->loadMap(this);
std::cout << "> Content unserialization time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl;
return true;
}
bool Map::saveMap()
{
IOMapSerialize* IOLoader = IOMapSerialize::getInstance();
bool saved = false;
for(uint32_t tries = 0; tries < 3; ++tries)
{
if(!IOLoader->saveHouses())
continue;
saved = true;
break;
}
if(!saved)
return false;
saved = false;
for(uint32_t tries = 0; tries < 3; ++tries)
{
if(!IOLoader->saveMap(this))
continue;
saved = true;
break;
}
return saved;
}
Tile* Map::getTile(int32_t x, int32_t y, int32_t z)
{
if(x < 0 || x >= 0xFFFF || y < 0 || y >= 0xFFFF || z < 0 || z >= MAP_MAX_LAYERS)
return NULL;
QTreeLeafNode* leaf = QTreeNode::getLeafStatic(&root, x, y);
if(!leaf)
return NULL;
Floor* floor = leaf->getFloor(z);
if(!floor)
return NULL;
return floor->tiles[x & FLOOR_MASK][y & FLOOR_MASK];
}
void Map::setTile(uint16_t x, uint16_t y, uint16_t z, Tile* newTile)
{
if(z >= MAP_MAX_LAYERS)
{
std::cout << "[Error - Map::setTile]: Attempt to set tile on invalid Z coordinate - " << 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->m_leafS = leaf;
//update west leaf
QTreeLeafNode* westLeaf = root.getLeaf(x - FLOOR_SIZE, y);
if(westLeaf)
westLeaf->m_leafE = leaf;
//update south
QTreeLeafNode* southLeaf = root.getLeaf(x, y + FLOOR_SIZE);
if(southLeaf)
leaf->m_leafS = southLeaf;
//update east
QTreeLeafNode* eastLeaf = root.getLeaf(x + FLOOR_SIZE, y);
if(eastLeaf)
leaf->m_leafE = eastLeaf;
}
uint32_t offsetX = x & FLOOR_MASK, offsetY = y & FLOOR_MASK;
Floor* floor = leaf->createFloor(z);
if(!floor->tiles[offsetX][offsetY])
{
floor->tiles[offsetX][offsetY] = newTile;
newTile->qt_node = leaf;
}
else
std::cout << "[Error - Map::setTile] Tile already exists - pos " << offsetX << "/" << offsetY << "/" << z << std::endl;
if(newTile->hasFlag(TILESTATE_REFRESH))
{
RefreshBlock_t rb;
if(TileItemVector* tileItems = newTile->getItemList())
{
for(ItemVector::iterator it = tileItems->getBeginDownItem(); it != tileItems->getEndDownItem(); ++it)
rb.list.push_back((*it)->clone());
}
rb.lastRefresh = OTSYS_TIME();
g_game.addRefreshTile(newTile, rb);
}
}
bool Map::placeCreature(const Position& centerPos, Creature* creature, bool extendedPos /*= false*/, bool forced /*= false*/)
{
bool foundTile = false, placeInPz = false;
Tile* tile = getTile(centerPos);
if(tile)
{
placeInPz = tile->hasFlag(TILESTATE_PROTECTIONZONE);
uint32_t flags = FLAG_IGNOREBLOCKITEM;
if(creature->isAccountManager())
flags |= FLAG_IGNOREBLOCKCREATURE;
ReturnValue ret = tile->__queryAdd(0, creature, 1, flags);
if(forced || ret == RET_NOERROR || ret == RET_PLAYERISNOTINVITED)
foundTile = true;
}
size_t shufflePos = 0;
PairVector relList;
if(extendedPos)
{
shufflePos = 8;
relList.push_back(PositionPair(-2, 0));
relList.push_back(PositionPair(0, -2));
relList.push_back(PositionPair(0, 2));
relList.push_back(PositionPair(2, 0));
std::random_shuffle(relList.begin(), relList.end());
}
relList.push_back(PositionPair(-1, -1));
relList.push_back(PositionPair(-1, 0));
relList.push_back(PositionPair(-1, 1));
relList.push_back(PositionPair(0, -1));
relList.push_back(PositionPair(0, 1));
relList.push_back(PositionPair(1, -1));
relList.push_back(PositionPair(1, 0));
relList.push_back(PositionPair(1, 1));
std::random_shuffle(relList.begin() + shufflePos, relList.end());
uint32_t radius = 1;
Position tryPos;
for(uint32_t n = 1; n <= radius && !foundTile; ++n)
{
for(PairVector::iterator it = relList.begin(); it != relList.end() && !foundTile; ++it)
{
int32_t dx = it->first * n, dy = it->second * n;
tryPos = centerPos;
tryPos.x = tryPos.x + dx;
tryPos.y = tryPos.y + dy;
if(!(tile = getTile(tryPos)) || (placeInPz && !tile->hasFlag(TILESTATE_PROTECTIONZONE)))
continue;
if(tile->__queryAdd(0, creature, 1, 0) == RET_NOERROR)
{
if(!extendedPos)
{
foundTile = true;
break;
}
if(isSightClear(centerPos, tryPos, false))
{
foundTile = true;
break;
}
}
}
}
if(!foundTile)
return false;
int32_t index = 0;
uint32_t flags = 0;
Item* toItem = NULL;
if(Cylinder* toCylinder = tile->__queryDestination(index, creature, &toItem, flags))
{
toCylinder->__internalAddThing(creature);
if(Tile* toTile = toCylinder->getTile())
toTile->qt_node->addCreature(creature);
}
return true;
}
bool Map::removeCreature(Creature* creature)
{
Tile* tile = creature->getTile();
if(!tile)
return false;
tile->qt_node->removeCreature(creature);
tile->__removeThing(creature, 0);
return true;
}
void Map::getSpectatorsInternal(SpectatorVec& list, const Position& centerPos, bool checkForDuplicate,
int32_t minRangeX, int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY,
int32_t minRangeZ, int32_t maxRangeZ)
{
int32_t minoffset = centerPos.z - maxRangeZ, maxoffset = centerPos.z - minRangeZ,
x1 = std::min((int32_t)0xFFFF, std::max((int32_t)0, (centerPos.x + minRangeX + minoffset))),
y1 = std::min((int32_t)0xFFFF, std::max((int32_t)0, (centerPos.y + minRangeY + minoffset))),
x2 = std::min((int32_t)0xFFFF, std::max((int32_t)0, (centerPos.x + maxRangeX + maxoffset))),
y2 = std::min((int32_t)0xFFFF, std::max((int32_t)0, (centerPos.y + maxRangeY + maxoffset))),
startx1 = x1 - (x1 % FLOOR_SIZE), starty1 = y1 - (y1 % FLOOR_SIZE),
endx2 = x2 - (x2 % FLOOR_SIZE), endy2 = y2 - (y2 % FLOOR_SIZE);
QTreeLeafNode* startLeaf = getLeaf(startx1, starty1);
QTreeLeafNode* leafS = startLeaf;
QTreeLeafNode* leafE;
for(int32_t ny = starty1; ny <= endy2; ny += FLOOR_SIZE)
{
leafE = leafS;
for(int32_t nx = startx1; nx <= endx2; nx += FLOOR_SIZE)
{
if(leafE)
{
CreatureVector& nodeList = leafE->creatureList;
CreatureVector::const_iterator it = nodeList.begin();
if(it != nodeList.end())
{
do
{
Creature* creature = (*it);
const Position& pos = creature->getPosition();
int32_t offsetZ = centerPos.z - pos.z;
if(pos.z < minRangeZ || pos.z > maxRangeZ)
continue;
if(pos.y < (centerPos.y + minRangeY + offsetZ) || pos.y > (centerPos.y + maxRangeY + offsetZ))
continue;
if(pos.x < (centerPos.x + minRangeX + offsetZ) || pos.x > (centerPos.x + maxRangeX + offsetZ))
continue;
if(!checkForDuplicate || std::find(list.begin(), list.end(), creature) == list.end())
list.push_back(creature);
}
while(++it != nodeList.end());
}
leafE = leafE->stepEast();
}
else
leafE = getLeaf(nx + FLOOR_SIZE, ny);
}
if(leafS)
leafS = leafS->stepSouth();
else
leafS = getLeaf(startx1, ny + FLOOR_SIZE);
}
}
void Map::getSpectators(SpectatorVec& list, const Position& centerPos, bool checkforduplicate /*= false*/, bool multifloor /*= 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, cacheResult = false;
if(!minRangeX && !maxRangeX && !minRangeY && !maxRangeY && multifloor && !checkforduplicate)
{
SpectatorCache::iterator it = spectatorCache.find(centerPos);
if(it != spectatorCache.end())
{
list = *it->second;
foundCache = true;
}
else
cacheResult = true;
}
if(!foundCache)
{
minRangeX = (!minRangeX ? -maxViewportX : -minRangeX);
maxRangeX = (!maxRangeX ? maxViewportX : maxRangeX);
minRangeY = (!minRangeY ? -maxViewportY : -minRangeY);
maxRangeY = (!maxRangeY ? maxViewportY : maxRangeY);
int32_t minRangeZ, maxRangeZ;
if(multifloor)
{
if(centerPos.z > 7)
{
//underground, 8->15
minRangeZ = std::max(centerPos.z - 2, 0);
maxRangeZ = std::min(centerPos.z + 2, MAP_MAX_LAYERS - 1);
}
//above ground
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, true, minRangeX,
maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ);
if(cacheResult)
spectatorCache[centerPos].reset(new SpectatorVec(list));
}
}
const SpectatorVec& Map::getSpectators(const Position& centerPos)
{
if(centerPos.z >= MAP_MAX_LAYERS)
{
boost::shared_ptr p(new SpectatorVec());
SpectatorVec& list = *p;
return list;
}
SpectatorCache::iterator it = spectatorCache.find(centerPos);
if(it != spectatorCache.end())
return *it->second;
boost::shared_ptr p(new SpectatorVec());
spectatorCache[centerPos] = p;
SpectatorVec& list = *p;
int32_t minRangeX = -maxViewportX, maxRangeX = maxViewportX, minRangeY = -maxViewportY,
maxRangeY = maxViewportY, minRangeZ, maxRangeZ;
if(centerPos.z > 7)
{
//underground, 8->15
minRangeZ = std::max(centerPos.z - 2, 0);
maxRangeZ = std::min(centerPos.z + 2, MAP_MAX_LAYERS - 1);
}
//above ground
else if(centerPos.z == 6)
{
minRangeZ = 0;
maxRangeZ = 8;
}
else if(centerPos.z == 7)
{
minRangeZ = 0;
maxRangeZ = 9;
}
else
{
minRangeZ = 0;
maxRangeZ = 7;
}
getSpectatorsInternal(list, centerPos, false, minRangeX, maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ);
return list;
}
bool Map::canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight /*= true*/,
int32_t rangex /*= Map::maxClientViewportX*/, int32_t rangey /*= Map::maxClientViewportY*/)
{
//z checks
//underground 8->15
//ground level and above 7->0
if((fromPos.z >= 8 && toPos.z < 8) || (toPos.z >= 8 &&
fromPos.z < 8) || fromPos.z - fromPos.z > 2)
return false;
int32_t deltax = std::abs(fromPos.x - toPos.x), deltay = std::abs(
fromPos.y - toPos.y), deltaz = std::abs(fromPos.z - toPos.z);
if(deltax - deltaz > rangex || deltay - deltaz > rangey)
return false;
if(!checkLineOfSight)
return true;
return isSightClear(fromPos, toPos, false);
}
bool Map::checkSightLine(const Position& fromPos, const Position& toPos) const
{
Position start = fromPos;
Position end = toPos;
int32_t x, y, z, dx = std::abs(start.x - end.x), dy = std::abs(start.y - end.y),
dz = std::abs(start.z - end.z), sx, sy, sz, ey, ez, max = dx, dir = 0;
if(dy > max)
{
max = dy;
dir = 1;
}
if(dz > max)
{
max = dz;
dir = 2;
}
switch(dir)
{
case 1:
//x -> y
//y -> x
//z -> z
std::swap(start.x, start.y);
std::swap(end.x, end.y);
std::swap(dx, dy);
break;
case 2:
//x -> z
//y -> y
//z -> x
std::swap(start.x, start.z);
std::swap(end.x, end.z);
std::swap(dx, dz);
break;
default:
//x -> x
//y -> y
//z -> z
break;
}
sx = ((start.x < end.x) ? 1 : -1);
sy = ((start.y < end.y) ? 1 : -1);
sz = ((start.z < end.z) ? 1 : -1);
ey = ez = 0;
x = start.x;
y = start.y;
z = start.z;
int32_t lastrx = x, lastry = y, lastrz = z;
for(; x != end.x + sx; x += sx)
{
int32_t rx, ry, rz;
switch(dir)
{
case 1:
rx = y; ry = x; rz = z;
break;
case 2:
rx = z; ry = y; rz = x;
break;
default:
rx = x; ry = y; rz = z;
break;
}
if((toPos.x != rx || toPos.y != ry || toPos.z != rz) && (fromPos.x != rx || fromPos.y != ry || fromPos.z != rz))
{
if(lastrz != rz && const_cast