bool Map::canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight /*= true*/, bool sameFloor /*= false*/,
int32_t rangex /*= Map::maxClientViewportX*/, int32_t rangey /*= Map::maxClientViewportY*/) const
{
if (Position::getDistanceX(fromPos, toPos) > rangex || Position::getDistanceY(fromPos, toPos) > rangey) {
return false;
}
return !checkLineOfSight || isSightClear(fromPos, toPos, sameFloor);
}
bool Map::isTileClear(uint16_t x, uint16_t y, uint8_t z, bool blockFloor /*= false*/) const
{
const Tile* tile = getTile(x, y, z);
if (!tile) {
return true;
}
if (blockFloor && tile->getGround()) {
return false;
}
return !tile->hasProperty(CONST_PROP_BLOCKPROJECTILE);
}
namespace {
bool checkSteepLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t z)
{
float dx = x1 - x0;
float slope = (dx == 0) ? 1 : (y1 - y0) / dx;
float yi = y0 + slope;
for (uint16_t x = x0 + 1; x < x1; ++x) {
//0.1 is necessary to avoid loss of precision during calculation
if (!g_game.map.isTileClear(std::floor(yi + 0.1), x, z)) {
return false;
}
yi += slope;
}
return true;
}
bool checkSlightLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t z)
{
float dx = x1 - x0;
float slope = (dx == 0) ? 1 : (y1 - y0) / dx;
float yi = y0 + slope;
for (uint16_t x = x0 + 1; x < x1; ++x) {
//0.1 is necessary to avoid loss of precision during calculation
if (!g_game.map.isTileClear(x, std::floor(yi + 0.1), z)) {
return false;
}
yi += slope;
}
return true;
}
}
bool Map::checkSightLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t z) const
{
if (x0 == x1 && y0 == y1) {
return true;
}
if (std::abs(y1 - y0) > std::abs(x1 - x0)) {
if (y1 > y0) {
return checkSteepLine(y0, x0, y1, x1, z);
}
return checkSteepLine(y1, x1, y0, x0, z);
}
if (x0 > x1) {
return checkSlightLine(x1, y1, x0, y0, z);
}
return checkSlightLine(x0, y0, x1, y1, z);
}
bool Map::isSightClear(const Position& fromPos, const Position& toPos, bool sameFloor /*= false*/) const
{
//target is on the same floor
if (fromPos.z == toPos.z) {
//skip checks if toPos is next to us
if (Position::getDistanceX(fromPos, toPos) < 2 && Position::getDistanceY(fromPos, toPos) < 2) {
return true;
}
//sight is clear or sameFloor is enabled
bool sightClear = checkSightLine(fromPos.x, fromPos.y, toPos.x, toPos.y, fromPos.z);
if (sightClear || sameFloor) {
return sightClear;
}
//no obstacles above floor 0 so we can throw above the obstacle
if (fromPos.z == 0) {
return true;
}
//check if tiles above us and the target are clear and check for a clear sight between them
uint8_t newZ = fromPos.z - 1;
return isTileClear(fromPos.x, fromPos.y, newZ, true) && isTileClear(toPos.x, toPos.y, newZ, true) && checkSightLine(fromPos.x, fromPos.y, toPos.x, toPos.y, newZ);
}
//target is on a different floor
if (sameFloor) {
return false;
}
//skip checks for sight line in case fromPos and toPos cross the ground floor
if (fromPos.z < 8 && toPos.z > 7 || fromPos.z > 7 && toPos.z < 8) {
return false;
}
//target is above us
if (fromPos.z > toPos.z) {
if (Position::getDistanceZ(fromPos, toPos) > 1) {
return false;
}
//check a tile above us and the path to the target
uint8_t newZ = fromPos.z - 1;
return isTileClear(fromPos.x, fromPos.y, newZ, true) && checkSightLine(fromPos.x, fromPos.y, toPos.x, toPos.y, newZ);
}
//target is below us
//check if tiles above the target are clear
for (uint8_t z = fromPos.z; z < toPos.z; ++z) {
if (!isTileClear(toPos.x, toPos.y, z, true)) {
return false;
}
}
//check if we can throw to the tile above the target
return checkSightLine(fromPos.x, fromPos.y, toPos.x, toPos.y, fromPos.z);
}