1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-07-03 00:46:55 +02:00

Refactor BattleHex, remake the use of precomputed neighbouring tiles containers.

- Moved short, frequently used functions to the BattleHex header for inlining
- Made BattleHex a class with a private hex value
- Moved getClosestTile implementation back to BattleHex
- Enabled access to static precomputed data in BattleHexArray via BattleHex
(note: circular dependency prevented static precomputed containers being directly placed in BattleHex)
This commit is contained in:
MichalZr6
2025-01-02 23:56:04 +01:00
parent ac8104d56d
commit dad6437661
27 changed files with 338 additions and 312 deletions

View File

@ -384,7 +384,7 @@ AttackPossibility AttackPossibility::evaluate(
affectedUnits = defenderUnits; affectedUnits = defenderUnits;
vstd::concatenate(affectedUnits, retaliatedUnits); vstd::concatenate(affectedUnits, retaliatedUnits);
logAi->trace("Attacked battle units count %d, %d->%d", affectedUnits.size(), hex.hex, defHex.hex); logAi->trace("Attacked battle units count %d, %d->%d", affectedUnits.size(), hex, defHex);
std::map<uint32_t, std::shared_ptr<battle::CUnitState>> defenderStates; std::map<uint32_t, std::shared_ptr<battle::CUnitState>> defenderStates;

View File

@ -215,7 +215,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
bestAttack.affectedUnits[0]->unitType()->getJsonKey(), bestAttack.affectedUnits[0]->unitType()->getJsonKey(),
bestAttack.affectedUnits[0]->getCount(), bestAttack.affectedUnits[0]->getCount(),
(int)bestAttack.from, (int)bestAttack.from,
(int)bestAttack.attack.attacker->getPosition().hex, (int)bestAttack.attack.attacker->getPosition(),
bestAttack.attack.chargeDistance, bestAttack.attack.chargeDistance,
bestAttack.attack.attacker->getMovementRange(0), bestAttack.attack.attacker->getMovementRange(0),
bestAttack.defenderDamageReduce, bestAttack.defenderDamageReduce,
@ -252,7 +252,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
if(siegeDefense) if(siegeDefense)
{ {
logAi->trace("Evaluating exchange at %d self-defense", stack->getPosition().hex); logAi->trace("Evaluating exchange at %d self-defense", stack->getPosition());
BattleAttackInfo bai(stack, stack, 0, false); BattleAttackInfo bai(stack, stack, 0, false);
AttackPossibility apDefend(stack->getPosition(), stack->getPosition(), bai); AttackPossibility apDefend(stack->getPosition(), stack->getPosition(), bai);
@ -286,7 +286,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
"Moving %s towards hex %s[%d], score: %2f", "Moving %s towards hex %s[%d], score: %2f",
stack->getDescription(), stack->getDescription(),
moveTarget.cachedAttack->attack.defender->getDescription(), moveTarget.cachedAttack->attack.defender->getDescription(),
moveTarget.cachedAttack->attack.defender->getPosition().hex, moveTarget.cachedAttack->attack.defender->getPosition(),
moveTarget.score); moveTarget.score);
return goTowardsNearest(stack, moveTarget.positions, *targets); return goTowardsNearest(stack, moveTarget.positions, *targets);

View File

@ -960,7 +960,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
if(hexStack && cb->battleMatchOwner(unit, hexStack, false)) if(hexStack && cb->battleMatchOwner(unit, hexStack, false))
{ {
for(BattleHex neighbour : BattleHexArray::neighbouringTilesCache[hex.hex]) for(BattleHex neighbour : hex.getNeighbouringTiles())
{ {
reachable = unitReachability.distances.at(neighbour) <= radius; reachable = unitReachability.distances.at(neighbour) <= radius;
@ -1021,7 +1021,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
if(hexStack && cb->battleMatchOwner(unit, hexStack, false)) if(hexStack && cb->battleMatchOwner(unit, hexStack, false))
{ {
enemyUnit = true; enemyUnit = true;
for(BattleHex neighbour : BattleHexArray::neighbouringTilesCache[hex.hex]) for(BattleHex neighbour : hex.getNeighbouringTiles())
{ {
reachable = unitReachability.distances.at(neighbour) <= unitSpeed; reachable = unitReachability.distances.at(neighbour) <= unitSpeed;

View File

@ -107,7 +107,8 @@ static bool willSecondHexBlockMoreEnemyShooters(std::shared_ptr<CBattleCallback>
for(int i = 0; i < 2; i++) for(int i = 0; i < 2; i++)
{ {
for (auto neighbour : BattleHexArray::neighbouringTilesCache[i ? h2.hex : h1.hex]) BattleHex hex = i ? h2 : h1;
for (auto neighbour : hex.getNeighbouringTiles())
if(const auto * s = cb->getBattle(battleID)->battleGetUnitByPos(neighbour)) if(const auto * s = cb->getBattle(battleID)->battleGetUnitByPos(neighbour))
if(s->isShooter()) if(s->isShooter())
shooters[i]++; shooters[i]++;

View File

@ -482,7 +482,7 @@ std::vector<std::vector<BattleHex::EDir>> BattleFieldController::getOutsideNeigh
{ {
// get all neighbours and their directions // get all neighbours and their directions
const BattleHexArray & neighbouringTiles = BattleHexArray::getAllNeighbouringTiles(hex); const BattleHexArray & neighbouringTiles = hex.getAllNeighbouringTiles();
std::vector<BattleHex::EDir> outsideNeighbourDirections; std::vector<BattleHex::EDir> outsideNeighbourDirections;
@ -492,9 +492,7 @@ std::vector<std::vector<BattleHex::EDir>> BattleFieldController::getOutsideNeigh
if(!neighbouringTiles[direction].isAvailable()) if(!neighbouringTiles[direction].isAvailable())
continue; continue;
auto it = std::find(wholeRangeHexes.begin(), wholeRangeHexes.end(), neighbouringTiles[direction]); if(!wholeRangeHexes.contains(neighbouringTiles[direction]))
if(it == wholeRangeHexes.end())
outsideNeighbourDirections.push_back(BattleHex::EDir(direction)); // push direction outsideNeighbourDirections.push_back(BattleHex::EDir(direction)); // push direction
} }
@ -680,7 +678,7 @@ BattleHex BattleFieldController::getHexAtPosition(Point hoverPos)
BattleHex::EDir BattleFieldController::selectAttackDirection(BattleHex myNumber) BattleHex::EDir BattleFieldController::selectAttackDirection(BattleHex myNumber)
{ {
const bool doubleWide = owner.stacksController->getActiveStack()->doubleWide(); const bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
const BattleHexArray & neighbours = BattleHexArray::getAllNeighbouringTiles(myNumber); const BattleHexArray & neighbours = myNumber.getAllNeighbouringTiles();
// 0 1 // 0 1
// 5 x 2 // 5 x 2
// 4 3 // 4 3

View File

@ -10,6 +10,7 @@
#pragma once #pragma once
#include "BattleConstants.h" #include "BattleConstants.h"
#include "../lib/battle/BattleHex.h"
#include "../gui/CIntObject.h" #include "../gui/CIntObject.h"
#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation #include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
#include "../ConditionalWait.h" #include "../ConditionalWait.h"
@ -27,7 +28,6 @@ class BattleAction;
class CGTownInstance; class CGTownInstance;
struct CatapultAttack; struct CatapultAttack;
struct BattleTriggerEffect; struct BattleTriggerEffect;
struct BattleHex;
struct InfoAboutHero; struct InfoAboutHero;
class ObstacleChanges; class ObstacleChanges;
class CPlayerBattleCallback; class CPlayerBattleCallback;

View File

@ -13,7 +13,7 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
struct BattleHex; class BattleHex;
struct CObstacleInstance; struct CObstacleInstance;
class JsonNode; class JsonNode;
class ObstacleChanges; class ObstacleChanges;

View File

@ -195,7 +195,7 @@ const CCreature *BattleSiegeController::getTurretCreature(BattleHex position) co
return town->fortificationsLevel().lowerTowerShooter.toCreature(); return town->fortificationsLevel().lowerTowerShooter.toCreature();
} }
throw std::runtime_error("Unable to select shooter for tower at " + std::to_string(position.hex)); throw std::runtime_error("Unable to select shooter for tower at " + std::to_string(position));
} }
Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) const Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) const

View File

@ -13,7 +13,7 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
struct BattleHex; class BattleHex;
class BattleHexArray; class BattleHexArray;
class BattleAction; class BattleAction;
class CStack; class CStack;

View File

@ -25,7 +25,7 @@ bool AccessibilityInfo::tileAccessibleWithGate(BattleHex tile, BattleSide side)
if(!destructibleEnemyTurns) if(!destructibleEnemyTurns)
return false; return false;
return destructibleEnemyTurns->at(tile.hex) >= 0; return destructibleEnemyTurns->at(tile) >= 0;
} }
if(accessibility != EAccessibility::ACCESSIBLE) if(accessibility != EAccessibility::ACCESSIBLE)

View File

@ -9,130 +9,63 @@
*/ */
#include "StdInc.h" #include "StdInc.h"
#include "BattleHex.h" #include "BattleHex.h"
#include "BattleHexArray.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
BattleHex::BattleHex() : hex(INVALID) {} BattleHex BattleHex::getClosestTile(const BattleHexArray & hexes, BattleSide side, BattleHex initialPos)
BattleHex::BattleHex(si16 _hex) : hex(_hex) {}
BattleHex::BattleHex(si16 x, si16 y)
{ {
setXY(x, y); if(hexes.empty())
} return BattleHex();
BattleHex::BattleHex(std::pair<si16, si16> xy) BattleHex initialHex = BattleHex(initialPos);
{ int closestDistance = std::numeric_limits<int>::max();
setXY(xy); BattleHexArray closestTiles;
}
BattleHex::operator si16() const for(auto hex : hexes)
{
return hex;
}
void BattleHex::setX(si16 x)
{
setXY(x, getY());
}
void BattleHex::setY(si16 y)
{
setXY(getX(), y);
}
void BattleHex::setXY(si16 x, si16 y, bool hasToBeValid)
{
if(hasToBeValid)
{ {
if(x < 0 || x >= GameConstants::BFIELD_WIDTH || y < 0 || y >= GameConstants::BFIELD_HEIGHT) int distance = initialHex.getDistance(initialHex, hex);
throw std::runtime_error("Valid hex required"); if(distance < closestDistance)
{
closestDistance = distance;
closestTiles.clear();
closestTiles.insert(hex);
}
else if(distance == closestDistance)
closestTiles.insert(hex);
} }
hex = x + y * GameConstants::BFIELD_WIDTH; auto compareHorizontal = [side, initialPos](const BattleHex & left, const BattleHex & right)
}
void BattleHex::setXY(std::pair<si16, si16> xy)
{
setXY(xy.first, xy.second);
}
si16 BattleHex::getX() const
{
return hex % GameConstants::BFIELD_WIDTH;
}
si16 BattleHex::getY() const
{
return hex / GameConstants::BFIELD_WIDTH;
}
std::pair<si16, si16> BattleHex::getXY() const
{
return std::make_pair(getX(), getY());
}
BattleHex & BattleHex::moveInDirection(EDir dir, bool hasToBeValid)
{
si16 x = getX();
si16 y = getY();
switch(dir)
{ {
case TOP_LEFT: if(left.getX() != right.getX())
setXY((y%2) ? x-1 : x, y-1, hasToBeValid); {
break; return (side == BattleSide::ATTACKER) ? (left.getX() > right.getX()) : (left.getX() < right.getX());
case TOP_RIGHT: }
setXY((y%2) ? x : x+1, y-1, hasToBeValid); return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY());
break; };
case RIGHT:
setXY(x+1, y, hasToBeValid); auto bestTile = std::min_element(closestTiles.begin(), closestTiles.end(), compareHorizontal);
break; return (bestTile != closestTiles.end()) ? *bestTile : BattleHex();
case BOTTOM_RIGHT: }
setXY((y%2) ? x : x+1, y+1, hasToBeValid);
break; const BattleHexArray & BattleHex::getAllNeighbouringTiles() const
case BOTTOM_LEFT: {
setXY((y%2) ? x-1 : x, y+1, hasToBeValid); return BattleHexArray::getAllNeighbouringTiles(*this);
break;
case LEFT:
setXY(x-1, y, hasToBeValid);
break;
case NONE:
break;
default:
throw std::runtime_error("Disaster: wrong direction in BattleHex::operator+=!\n");
break;
}
return *this;
} }
BattleHex & BattleHex::operator+=(BattleHex::EDir dir) const BattleHexArray & BattleHex::getNeighbouringTiles() const
{ {
return moveInDirection(dir); return BattleHexArray::getNeighbouringTiles(*this);
} }
BattleHex BattleHex::cloneInDirection(BattleHex::EDir dir, bool hasToBeValid) const const BattleHexArray & BattleHex::getNeighbouringTilesDblWide(BattleSide side) const
{ {
BattleHex result(hex); return BattleHexArray::getNeighbouringTilesDblWide(*this, side);
result.moveInDirection(dir, hasToBeValid); }
return result;
}
BattleHex BattleHex::operator+(BattleHex::EDir dir) const
{
return cloneInDirection(dir);
}
BattleHex::EDir BattleHex::mutualPosition(BattleHex hex1, BattleHex hex2)
{
for(auto dir : hexagonalDirections())
if(hex2 == hex1.cloneInDirection(dir, false))
return dir;
return NONE;
}
std::ostream & operator<<(std::ostream & os, const BattleHex & hex) std::ostream & operator<<(std::ostream & os, const BattleHex & hex)
{ {
return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % hex.hex); return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % static_cast<si16>(hex));
} }
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@ -22,9 +22,13 @@ namespace GameConstants
const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT; const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT;
} }
class BattleHexArray;
// for battle stacks' positions // for battle stacks' positions
struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class for better code design class DLL_LINKAGE BattleHex
{ {
public:
// helpers for siege // helpers for siege
static constexpr si16 CASTLE_CENTRAL_TOWER = -2; static constexpr si16 CASTLE_CENTRAL_TOWER = -2;
static constexpr si16 CASTLE_BOTTOM_TOWER = -3; static constexpr si16 CASTLE_BOTTOM_TOWER = -3;
@ -46,8 +50,8 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f
static constexpr si16 GATE_OUTER = 95; static constexpr si16 GATE_OUTER = 95;
static constexpr si16 GATE_INNER = 96; static constexpr si16 GATE_INNER = 96;
si16 hex;
static constexpr si16 INVALID = -1; static constexpr si16 INVALID = -1;
enum EDir enum EDir
{ {
NONE = -1, NONE = -1,
@ -64,11 +68,25 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f
BOTTOM BOTTOM
}; };
BattleHex(); BattleHex()
BattleHex(si16 _hex); : hex(INVALID)
BattleHex(si16 x, si16 y); {}
BattleHex(std::pair<si16, si16> xy); BattleHex(si16 _hex)
operator si16() const; : hex(_hex)
{}
BattleHex(si16 x, si16 y)
{
setXY(x, y);
}
BattleHex(std::pair<si16, si16> xy)
{
setXY(xy);
}
operator si16() const
{
return hex;
}
inline bool isValid() const inline bool isValid() const
{ {
return hex >= 0 && hex < GameConstants::BFIELD_SIZE; return hex >= 0 && hex < GameConstants::BFIELD_SIZE;
@ -79,19 +97,97 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f
return isValid() && getX() > 0 && getX() < GameConstants::BFIELD_WIDTH - 1; return isValid() && getX() > 0 && getX() < GameConstants::BFIELD_WIDTH - 1;
} }
void setX(si16 x); void setX(si16 x)
void setY(si16 y); {
void setXY(si16 x, si16 y, bool hasToBeValid = true); setXY(x, getY());
void setXY(std::pair<si16, si16> xy); }
si16 getX() const;
si16 getY() const; void setY(si16 y)
std::pair<si16, si16> getXY() const; {
BattleHex& moveInDirection(EDir dir, bool hasToBeValid = true); setXY(getX(), y);
BattleHex& operator+=(EDir dir); }
BattleHex cloneInDirection(EDir dir, bool hasToBeValid = true) const;
BattleHex operator+(EDir dir) const; void setXY(si16 x, si16 y, bool hasToBeValid = true)
{
if(hasToBeValid)
{
if(x < 0 || x >= GameConstants::BFIELD_WIDTH || y < 0 || y >= GameConstants::BFIELD_HEIGHT)
throw std::runtime_error("Valid hex required");
}
hex = x + y * GameConstants::BFIELD_WIDTH;
}
void setXY(std::pair<si16, si16> xy)
{
setXY(xy.first, xy.second);
}
si16 getX() const
{
return hex % GameConstants::BFIELD_WIDTH;
}
si16 getY() const
{
return hex / GameConstants::BFIELD_WIDTH;
}
std::pair<si16, si16> getXY() const
{
return std::make_pair(getX(), getY());
}
BattleHex & moveInDirection(EDir dir, bool hasToBeValid = true)
{
si16 x = getX();
si16 y = getY();
switch(dir)
{
case TOP_LEFT:
setXY((y % 2) ? x - 1 : x, y - 1, hasToBeValid);
break;
case TOP_RIGHT:
setXY((y % 2) ? x : x + 1, y - 1, hasToBeValid);
break;
case RIGHT:
setXY(x + 1, y, hasToBeValid);
break;
case BOTTOM_RIGHT:
setXY((y % 2) ? x : x + 1, y + 1, hasToBeValid);
break;
case BOTTOM_LEFT:
setXY((y % 2) ? x - 1 : x, y + 1, hasToBeValid);
break;
case LEFT:
setXY(x - 1, y, hasToBeValid);
break;
case NONE:
break;
default:
throw std::runtime_error("Disaster: wrong direction in BattleHex::operator+=!\n");
break;
}
return *this;
}
BattleHex & operator+=(EDir dir)
{
return moveInDirection(dir);
}
BattleHex operator+(EDir dir) const
{
return cloneInDirection(dir);
}
BattleHex cloneInDirection(EDir dir, bool hasToBeValid = true) const
{
BattleHex result(hex);
result.moveInDirection(dir, hasToBeValid);
return result;
}
static EDir mutualPosition(BattleHex hex1, BattleHex hex2);
static uint8_t getDistance(BattleHex hex1, BattleHex hex2) static uint8_t getDistance(BattleHex hex1, BattleHex hex2)
{ {
int y1 = hex1.getY(); int y1 = hex1.getY();
@ -108,17 +204,41 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f
return std::abs(xDst) + std::abs(yDst); return std::abs(xDst) + std::abs(yDst);
} }
static BattleHex getClosestTile(const BattleHexArray & hexes, BattleSide side, BattleHex initialPos);
//Constexpr defined array with all directions used in battle
static constexpr auto hexagonalDirections()
{
return std::array<EDir,6>{TOP_LEFT, TOP_RIGHT, RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT, LEFT};
}
static EDir mutualPosition(BattleHex hex1, BattleHex hex2)
{
for(auto dir : hexagonalDirections())
if(hex2 == hex1.cloneInDirection(dir, false))
return dir;
return NONE;
}
/// get (precomputed) all possible surrounding tiles
const BattleHexArray & getAllNeighbouringTiles() const;
/// get (precomputed) only valid and available surrounding tiles
const BattleHexArray & getNeighbouringTiles() const;
/// get (precomputed) only valid and available surrounding tiles for double wide creatures
const BattleHexArray & getNeighbouringTilesDblWide(BattleSide side) const;
template <typename Handler> template <typename Handler>
void serialize(Handler &h) void serialize(Handler & h)
{ {
h & hex; h & hex;
} }
//Constexpr defined array with all directions used in battle private:
static constexpr auto hexagonalDirections() {
return std::array<EDir,6>{BattleHex::TOP_LEFT, BattleHex::TOP_RIGHT, BattleHex::RIGHT, BattleHex::BOTTOM_RIGHT, BattleHex::BOTTOM_LEFT, BattleHex::LEFT}; si16 hex;
}
}; };
DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleHex & hex); DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleHex & hex);

View File

@ -22,104 +22,6 @@ BattleHexArray::BattleHexArray(std::initializer_list<BattleHex> initList) noexce
} }
} }
BattleHex BattleHexArray::getClosestTile(BattleSide side, BattleHex initialPos) const
{
if(this->empty())
return BattleHex();
BattleHex initialHex = BattleHex(initialPos);
int closestDistance = std::numeric_limits<int>::max();
BattleHexArray closestTiles;
for(auto hex : internalStorage)
{
int distance = initialHex.getDistance(initialHex, hex);
if(distance < closestDistance)
{
closestDistance = distance;
closestTiles.clear();
closestTiles.insert(hex);
}
else if(distance == closestDistance)
closestTiles.insert(hex);
}
auto compareHorizontal = [side, initialPos](const BattleHex & left, const BattleHex & right)
{
if(left.getX() != right.getX())
{
return (side == BattleSide::ATTACKER) ? (left.getX() > right.getX()) : (left.getX() < right.getX());
}
return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY());
};
auto bestTile = std::min_element(closestTiles.begin(), closestTiles.end(), compareHorizontal);
return (bestTile != closestTiles.end()) ? *bestTile : BattleHex();
}
BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::calculateNeighbouringTiles()
{
BattleHexArray::ArrayOfBattleHexArrays ret;
for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++)
{
BattleHexArray hexes = BattleHexArray::generateNeighbouringTiles(hex);
size_t index = 0;
ret[hex].resize(hexes.size());
for(auto neighbour : hexes)
ret[hex].set(index++, neighbour);
}
return ret;
}
BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::calculateNeighbouringTilesDblWide(BattleSide side)
{
ArrayOfBattleHexArrays ret;
for(BattleHex hex = 0; hex < GameConstants::BFIELD_SIZE; hex.hex++)
{
BattleHexArray hexes;
if(side == BattleSide::ATTACKER)
{
const BattleHex otherHex = hex - 1;
for(auto dir = static_cast<BattleHex::EDir>(0); dir <= static_cast<BattleHex::EDir>(4); dir = static_cast<BattleHex::EDir>(dir + 1))
hexes.checkAndPush(hex.cloneInDirection(dir, false));
hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false));
hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::LEFT, false));
hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false));
}
else if(side == BattleSide::DEFENDER)
{
const BattleHex otherHex = hex + 1;
hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false));
for(auto dir = static_cast<BattleHex::EDir>(0); dir <= static_cast<BattleHex::EDir>(4); dir = static_cast<BattleHex::EDir>(dir + 1))
hexes.checkAndPush(otherHex.cloneInDirection(dir, false));
hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false));
hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::LEFT, false));
}
ret[hex.hex] = std::move(hexes);
}
return ret;
}
BattleHexArray BattleHexArray::generateNeighbouringTiles(BattleHex hex)
{
BattleHexArray ret;
for(auto dir : BattleHex::hexagonalDirections())
ret.checkAndPush(hex.cloneInDirection(dir, false));
return ret;
}
void BattleHexArray::insert(const BattleHexArray & other) noexcept void BattleHexArray::insert(const BattleHexArray & other) noexcept
{ {
for(auto hex : other) for(auto hex : other)
@ -146,9 +48,85 @@ void BattleHexArray::clear() noexcept
internalStorage.clear(); internalStorage.clear();
} }
const BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::neighbouringTilesCache = calculateNeighbouringTiles(); BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::precalculateNeighbouringTiles()
const std::map<BattleSide, BattleHexArray::ArrayOfBattleHexArrays> BattleHexArray::neighbouringTilesDblWide = {
{ { BattleSide::ATTACKER, calculateNeighbouringTilesDblWide(BattleSide::ATTACKER) }, BattleHexArray::ArrayOfBattleHexArrays ret;
{ BattleSide::DEFENDER, calculateNeighbouringTilesDblWide(BattleSide::DEFENDER) } };
for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++)
{
BattleHexArray hexes;
for(auto dir : BattleHex::hexagonalDirections())
hexes.checkAndPush(BattleHex(hex).cloneInDirection(dir, false));
size_t index = 0;
ret[hex].resize(hexes.size());
for(auto neighbour : hexes)
ret[hex].set(index++, neighbour);
}
return ret;
}
BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::precalculateAllNeighbouringTiles()
{
ArrayOfBattleHexArrays ret;
for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++)
{
ret[hex].resize(6);
for(auto dir : BattleHex::hexagonalDirections())
ret[hex].set(dir, BattleHex(hex).cloneInDirection(dir, false));
}
return ret;
}
BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::precalculateNeighbouringTilesDblWide(BattleSide side)
{
ArrayOfBattleHexArrays ret;
for(si16 h = 0; h < GameConstants::BFIELD_SIZE; h++)
{
BattleHexArray hexes;
BattleHex hex(h);
if(side == BattleSide::ATTACKER)
{
const BattleHex otherHex = h - 1;
for(auto dir = static_cast<BattleHex::EDir>(0); dir <= static_cast<BattleHex::EDir>(4); dir = static_cast<BattleHex::EDir>(dir + 1))
hexes.checkAndPush(hex.cloneInDirection(dir, false));
hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false));
hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::LEFT, false));
hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false));
}
else if(side == BattleSide::DEFENDER)
{
const BattleHex otherHex = h + 1;
hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false));
for(auto dir = static_cast<BattleHex::EDir>(0); dir <= static_cast<BattleHex::EDir>(4); dir = static_cast<BattleHex::EDir>(dir + 1))
hexes.checkAndPush(otherHex.cloneInDirection(dir, false));
hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false));
hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::LEFT, false));
}
ret[h] = std::move(hexes);
}
return ret;
}
const BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::neighbouringTiles = precalculateNeighbouringTiles();
const BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::allNeighbouringTiles = precalculateAllNeighbouringTiles();
const std::map<BattleSide, BattleHexArray::ArrayOfBattleHexArrays> BattleHexArray::neighbouringTilesDblWide =
{
{ BattleSide::ATTACKER, precalculateNeighbouringTilesDblWide(BattleSide::ATTACKER) },
{ BattleSide::DEFENDER, precalculateNeighbouringTilesDblWide(BattleSide::DEFENDER) }
};
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@ -21,6 +21,7 @@ class DLL_LINKAGE BattleHexArray
public: public:
static constexpr uint8_t totalSize = GameConstants::BFIELD_SIZE; static constexpr uint8_t totalSize = GameConstants::BFIELD_SIZE;
using StorageType = boost::container::small_vector<BattleHex, 8>; using StorageType = boost::container::small_vector<BattleHex, 8>;
using ArrayOfBattleHexArrays = std::array<BattleHexArray, totalSize>;
using value_type = BattleHex; using value_type = BattleHex;
using size_type = StorageType::size_type; using size_type = StorageType::size_type;
@ -34,11 +35,6 @@ public:
using reverse_iterator = typename StorageType::reverse_iterator; using reverse_iterator = typename StorageType::reverse_iterator;
using const_reverse_iterator = typename StorageType::const_reverse_iterator; using const_reverse_iterator = typename StorageType::const_reverse_iterator;
using ArrayOfBattleHexArrays = std::array<BattleHexArray, GameConstants::BFIELD_SIZE>;
static const ArrayOfBattleHexArrays neighbouringTilesCache;
static const std::map<BattleSide, ArrayOfBattleHexArrays> neighbouringTilesDblWide;
BattleHexArray() = default; BattleHexArray() = default;
template <typename Container, typename = std::enable_if_t< template <typename Container, typename = std::enable_if_t<
@ -60,28 +56,6 @@ public:
BattleHexArray(std::initializer_list<BattleHex> initList) noexcept; BattleHexArray(std::initializer_list<BattleHex> initList) noexcept;
/// returns all tiles, unavailable tiles will be set as invalid
/// order of returned tiles matches EDir enum
static BattleHexArray getAllNeighbouringTiles(BattleHex hex)
{
static ArrayOfBattleHexArrays cache;
static bool initialized = false;
if(initialized)
return cache[hex.hex];
for(BattleHex h = 0; h < GameConstants::BFIELD_SIZE; h.hex++)
{
cache[h].resize(6);
for(auto dir : BattleHex::hexagonalDirections())
cache[h].set(dir, h.cloneInDirection(dir, false));
}
initialized = true;
return cache[hex.hex];
}
void checkAndPush(BattleHex tile) void checkAndPush(BattleHex tile)
{ {
if(tile.isAvailable() && !contains(tile)) if(tile.isAvailable() && !contains(tile))
@ -126,8 +100,6 @@ public:
return internalStorage.insert(pos, hex); return internalStorage.insert(pos, hex);
} }
BattleHex getClosestTile(BattleSide side, BattleHex initialPos) const;
void insert(const BattleHexArray & other) noexcept; void insert(const BattleHexArray & other) noexcept;
template <typename Container, typename = std::enable_if_t< template <typename Container, typename = std::enable_if_t<
@ -185,6 +157,30 @@ public:
return filtered; return filtered;
} }
/// get (precomputed) all possible surrounding tiles
static const BattleHexArray & getAllNeighbouringTiles(BattleHex hex)
{
assert(hex.isValid());
return allNeighbouringTiles[hex];
}
/// get (precomputed) only valid and available surrounding tiles
static const BattleHexArray & getNeighbouringTiles(BattleHex hex)
{
assert(hex.isValid());
return neighbouringTiles[hex];
}
/// get (precomputed) only valid and available surrounding tiles for double wide creatures
static const BattleHexArray & getNeighbouringTilesDblWide(BattleHex hex, BattleSide side)
{
assert(hex.isValid() && (side == BattleSide::ATTACKER || BattleSide::DEFENDER));
return neighbouringTilesDblWide.at(side)[hex];
}
[[nodiscard]] inline bool contains(BattleHex hex) const noexcept [[nodiscard]] inline bool contains(BattleHex hex) const noexcept
{ {
if(hex.isValid()) if(hex.isValid())
@ -301,10 +297,13 @@ private:
return hex == BattleHex::CASTLE_CENTRAL_TOWER || hex == BattleHex::CASTLE_UPPER_TOWER || hex == BattleHex::CASTLE_BOTTOM_TOWER; return hex == BattleHex::CASTLE_CENTRAL_TOWER || hex == BattleHex::CASTLE_UPPER_TOWER || hex == BattleHex::CASTLE_BOTTOM_TOWER;
} }
/// returns all valid neighbouring tiles static const ArrayOfBattleHexArrays neighbouringTiles;
static ArrayOfBattleHexArrays calculateNeighbouringTiles(); static const ArrayOfBattleHexArrays allNeighbouringTiles;
static ArrayOfBattleHexArrays calculateNeighbouringTilesDblWide(BattleSide side); static const std::map<BattleSide, ArrayOfBattleHexArrays> neighbouringTilesDblWide;
static BattleHexArray generateNeighbouringTiles(BattleHex hex);
static ArrayOfBattleHexArrays precalculateNeighbouringTiles();
static ArrayOfBattleHexArrays precalculateAllNeighbouringTiles();
static ArrayOfBattleHexArrays precalculateNeighbouringTilesDblWide(BattleSide side);
}; };
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@ -709,7 +709,7 @@ void BattleInfo::setUnitState(uint32_t id, const JsonNode & data, int64_t health
if(!accessibility.accessible(changedStack->getPosition(), changedStack)) if(!accessibility.accessible(changedStack->getPosition(), changedStack))
{ {
logNetwork->error("Cannot resurrect %s because hex %d is occupied!", changedStack->nodeName(), changedStack->getPosition().hex); logNetwork->error("Cannot resurrect %s because hex %d is occupied!", changedStack->nodeName(), changedStack->getPosition());
return; //position is already occupied return; //position is already occupied
} }
} }

View File

@ -176,7 +176,7 @@ bool CBattleInfoCallback::battleIsInsideWalls(BattleHex from) const
bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const
{ {
if (!from.isAvailable() || !dest.isAvailable()) if (!from.isAvailable() || !dest.isAvailable())
throw std::runtime_error("Invalid hex (" + std::to_string(from.hex) + " and " + std::to_string(dest.hex) + ") received in battleHasPenaltyOnLine!" ); throw std::runtime_error("Invalid hex (" + std::to_string(from) + " and " + std::to_string(dest) + ") received in battleHasPenaltyOnLine!" );
auto isTileBlocked = [&](BattleHex tile) auto isTileBlocked = [&](BattleHex tile)
{ {
@ -204,7 +204,7 @@ bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest,
while (next != dest) while (next != dest)
{ {
next = BattleHexArray::neighbouringTilesCache[next].getClosestTile(direction, dest); next = BattleHex::getClosestTile(next.getNeighbouringTiles(), direction, dest);
ret.insert(next); ret.insert(next);
} }
assert(!ret.empty()); assert(!ret.empty());
@ -1077,9 +1077,9 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo & accessib
if(isInObstacle(curHex, obstacles, checkParams)) if(isInObstacle(curHex, obstacles, checkParams))
continue; continue;
const int costToNeighbour = ret.distances.at(curHex.hex) + 1; const int costToNeighbour = ret.distances.at(curHex) + 1;
for(BattleHex neighbour : BattleHexArray::neighbouringTilesCache[curHex.hex]) for(BattleHex neighbour : curHex.getNeighbouringTiles())
{ {
auto additionalCost = 0; auto additionalCost = 0;
@ -1093,13 +1093,13 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo & accessib
} }
} }
const int costFoundSoFar = ret.distances[neighbour.hex]; const int costFoundSoFar = ret.distances[neighbour];
if(accessibleCache[neighbour.hex] && costToNeighbour + additionalCost < costFoundSoFar) if(accessibleCache[neighbour] && costToNeighbour + additionalCost < costFoundSoFar)
{ {
hexq.push(neighbour); hexq.push(neighbour);
ret.distances[neighbour.hex] = costToNeighbour + additionalCost; ret.distances[neighbour] = costToNeighbour + additionalCost;
ret.predecessors[neighbour.hex] = curHex; ret.predecessors[neighbour] = curHex;
} }
} }
} }
@ -1222,7 +1222,7 @@ BattleHex CBattleInfoCallback::getAvailableHex(const CreatureID & creID, BattleS
return BattleHex::INVALID; //all tiles are covered return BattleHex::INVALID; //all tiles are covered
} }
return occupyable.getClosestTile(side, pos); return BattleHex::getClosestTile(occupyable, side, pos);
} }
si8 CBattleInfoCallback::battleGetTacticDist() const si8 CBattleInfoCallback::battleGetTacticDist() const
@ -1353,7 +1353,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(
} }
if(attacker->hasBonusOfType(BonusType::WIDE_BREATH)) if(attacker->hasBonusOfType(BonusType::WIDE_BREATH))
{ {
BattleHexArray hexes = BattleHexArray::neighbouringTilesCache[destinationTile]; BattleHexArray hexes = destinationTile.getNeighbouringTiles();
for(int i = 0; i < hexes.size(); i++) for(int i = 0; i < hexes.size(); i++)
{ {
if(hexes.at(i) == attackOriginHex) if(hexes.at(i) == attackOriginHex)
@ -1426,9 +1426,9 @@ AttackableTiles CBattleInfoCallback::getPotentiallyShootableHexes(const battle::
AttackableTiles at; AttackableTiles at;
RETURN_IF_NOT_BATTLE(at); RETURN_IF_NOT_BATTLE(at);
if(attacker->hasBonusOfType(BonusType::SHOOTS_ALL_ADJACENT) && !BattleHexArray::neighbouringTilesCache[attackerPos].contains(destinationTile)) if(attacker->hasBonusOfType(BonusType::SHOOTS_ALL_ADJACENT) && !attackerPos.getNeighbouringTiles().contains(destinationTile))
{ {
at.hostileCreaturePositions.insert(BattleHexArray::neighbouringTilesCache[destinationTile]); at.hostileCreaturePositions.insert(destinationTile.getNeighbouringTiles());
at.hostileCreaturePositions.insert(destinationTile); at.hostileCreaturePositions.insert(destinationTile);
} }

View File

@ -10,7 +10,7 @@
#pragma once #pragma once
#include "BattleHexArray.h" #include "BattleHex.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN

View File

@ -44,7 +44,7 @@ uint32_t ReachabilityInfo::distToNearestNeighbour(
for(auto targetHex : targetHexes) for(auto targetHex : targetHexes)
{ {
for(auto & n : BattleHexArray::neighbouringTilesCache[targetHex]) for(auto & n : targetHex.getNeighbouringTiles())
{ {
if(distances[n] < ret) if(distances[n] < ret)
{ {

View File

@ -60,12 +60,10 @@ const BattleHexArray & Unit::getSurroundingHexes(BattleHex assumedPosition) cons
const BattleHexArray & Unit::getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side) const BattleHexArray & Unit::getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side)
{ {
assert(position.isValid()); // check outside if position isValid
if(!twoHex) if(!twoHex)
return BattleHexArray::neighbouringTilesCache[position]; return position.getNeighbouringTiles();
return BattleHexArray::neighbouringTilesDblWide.at(side).at(position); return position.getNeighbouringTilesDblWide(side);
} }
BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const
@ -88,7 +86,7 @@ BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const
hexes.pop_back(); hexes.pop_back();
for(auto hex : hexes) for(auto hex : hexes)
targetableHexes.insert(BattleHexArray::neighbouringTilesCache[hex]); targetableHexes.insert(hex.getNeighbouringTiles());
} }
return targetableHexes; return targetableHexes;

View File

@ -11,6 +11,7 @@
#include "NetPacksBase.h" #include "NetPacksBase.h"
#include "BattleChanges.h" #include "BattleChanges.h"
#include "../battle/BattleHexArray.h"
#include "../battle/BattleAction.h" #include "../battle/BattleAction.h"
#include "../texts/MetaString.h" #include "../texts/MetaString.h"
@ -22,7 +23,6 @@ class CGHeroInstance;
class CArmedInstance; class CArmedInstance;
class IBattleState; class IBattleState;
class BattleInfo; class BattleInfo;
class BattleHexArray;
struct DLL_LINKAGE BattleStart : public CPackForClient struct DLL_LINKAGE BattleStart : public CPackForClient
{ {

View File

@ -610,7 +610,7 @@ std::vector<Destination> BattleSpellMechanics::getPossibleDestinations(size_t in
{ {
hexesToCheck.insert(stack->getPosition()); hexesToCheck.insert(stack->getPosition());
for(auto adjacent : BattleHexArray::neighbouringTilesCache[stack->getPosition().hex]) for(auto adjacent : stack->getPosition().getNeighbouringTiles())
hexesToCheck.insert(adjacent); hexesToCheck.insert(adjacent);
} }

View File

@ -16,8 +16,6 @@
#include "../IHandlerBase.h" #include "../IHandlerBase.h"
#include "../ConstTransitivePtr.h" #include "../ConstTransitivePtr.h"
#include "../int3.h" #include "../int3.h"
#include "../GameConstants.h"
#include "../battle/BattleHexArray.h"
#include "../bonuses/Bonus.h" #include "../bonuses/Bonus.h"
#include "../filesystem/ResourcePath.h" #include "../filesystem/ResourcePath.h"
#include "../json/JsonNode.h" #include "../json/JsonNode.h"

View File

@ -14,7 +14,7 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
struct BattleHex; class BattleHex;
class BattleHexArray; class BattleHexArray;
class CBattleInfoCallback; class CBattleInfoCallback;
class JsonSerializeFormat; class JsonSerializeFormat;

View File

@ -11,6 +11,7 @@
#include "LocationEffect.h" #include "LocationEffect.h"
#include "../ISpellMechanics.h" #include "../ISpellMechanics.h"
#include "battle/BattleHexArray.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN

View File

@ -228,7 +228,7 @@ EffectTarget UnitEffect::transformTargetByChain(const Mechanics * m, const Targe
if(possibleHexes.empty()) if(possibleHexes.empty())
break; break;
destHex = possibleHexes.getClosestTile(unit->unitSide(), destHex); destHex = BattleHex::getClosestTile(possibleHexes, unit->unitSide(), destHex);
} }
return effectTarget; return effectTarget;

View File

@ -16,7 +16,7 @@ struct BattleLogMessage;
struct BattleAttack; struct BattleAttack;
class BattleAction; class BattleAction;
class CBattleInfoCallback; class CBattleInfoCallback;
struct BattleHex; class BattleHex;
class CStack; class CStack;
class PlayerColor; class PlayerColor;
enum class BonusType : uint8_t; enum class BonusType : uint8_t;

View File

@ -13,7 +13,7 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
class CStack; class CStack;
struct BattleHex; class BattleHex;
class BattleHexArray; class BattleHexArray;
class BattleAction; class BattleAction;
class CBattleInfoCallback; class CBattleInfoCallback;