mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-23 22:37:55 +02:00
refactor logic to CBattleInfoCallback and leave UI in BattleFieldController, BattleActionsController
- move fromWhichHexAttack to CBattleInfoCallback - add toWhichHexMove (unifying incoherent duplicates) - add battleGetOccupiableHexes - add battleCanAttackHex for spatial attack check - add battleCanAttackUnit for non-spatial attack check - add headDirection to Unit (removing destShiftDir from CStack) - remove redundant game logic from selectAttackDirection - remove redundant game logic from BattleFieldController and BattleActionsController - fix no consideration for double-wide tail attack in BattleFlowProcessor - fix #6302 wrong moat stopping condition - throw exception on attacker nullptr in battleCanAttackHex, fromWhichHexAttack - safer actionIsLegal on attack, move - remove redundant canStackMoveHere from ui code - throw exception on nullptr unit in battleGetOccupiableHexes - ensure activeStack in redrawBackgroundWithHexes - test point validity in selectAttackDirection
This commit is contained in:
@@ -527,7 +527,8 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
|
||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
|
||||
{
|
||||
const auto * attacker = owner.stacksController->getActiveStack();
|
||||
BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex);
|
||||
BattleHex attackFromHex = owner.getBattle()->fromWhichHexAttack(attacker, targetHex, owner.fieldController->selectAttackDirection(targetHex));
|
||||
assert(attackFromHex.isValid());
|
||||
int distance = attacker->position.isValid() ? owner.getBattle()->battleGetDistances(attacker, attacker->getPosition())[attackFromHex.toInt()] : 0;
|
||||
DamageEstimation retaliation;
|
||||
BattleAttackInfo attackInfo(attacker, targetStack, distance, false );
|
||||
@@ -644,8 +645,8 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, c
|
||||
case PossiblePlayerBattleAction::MOVE_STACK:
|
||||
if (!(targetStack && targetStack->alive())) //we can walk on dead stacks
|
||||
{
|
||||
if(canStackMoveHere(owner.stacksController->getActiveStack(), targetHex))
|
||||
return true;
|
||||
const CStack * currentStack = owner.stacksController->getActiveStack();
|
||||
return currentStack && owner.getBattle()->toWhichHexMove(currentStack, targetHex).isValid();
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -653,16 +654,11 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, c
|
||||
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
|
||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
|
||||
{
|
||||
auto activeStack = owner.stacksController->getActiveStack();
|
||||
if (targetStack && targetStack != activeStack && owner.fieldController->isTileAttackable(targetHex)) // move isTileAttackable to be part of battleCanAttack?
|
||||
{
|
||||
BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex);
|
||||
if(owner.getBattle()->battleCanAttack(activeStack, targetStack, attackFromHex))
|
||||
return true;
|
||||
const CStack * currentStack = owner.stacksController->getActiveStack();
|
||||
return currentStack &&
|
||||
owner.getBattle()->battleCanAttackUnit(currentStack, targetStack) &&
|
||||
owner.getBattle()->battleCanAttackHex(currentStack, targetHex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
case PossiblePlayerBattleAction::SHOOT:
|
||||
{
|
||||
auto currentStack = owner.stacksController->getActiveStack();
|
||||
@@ -740,26 +736,9 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, c
|
||||
case PossiblePlayerBattleAction::MOVE_STACK:
|
||||
{
|
||||
const auto * activeStack = owner.stacksController->getActiveStack();
|
||||
|
||||
if(activeStack->doubleWide())
|
||||
{
|
||||
BattleHexArray availableHexes = owner.getBattle()->battleGetAvailableHexes(activeStack, false);
|
||||
BattleHex shiftedDest = targetHex.cloneInDirection(activeStack->destShiftDir(), false);
|
||||
const bool canMoveHeadHere = availableHexes.contains(targetHex);
|
||||
const bool canMoveTailHere = availableHexes.contains(shiftedDest);
|
||||
const bool backwardsMove = activeStack->unitSide() == BattleSide::ATTACKER ?
|
||||
targetHex.getX() < activeStack->getPosition().getX():
|
||||
targetHex.getX() > activeStack->getPosition().getX();
|
||||
|
||||
if(canMoveTailHere && (backwardsMove || !canMoveHeadHere))
|
||||
owner.giveCommand(EActionType::WALK, shiftedDest);
|
||||
else
|
||||
owner.giveCommand(EActionType::WALK, targetHex);
|
||||
}
|
||||
else
|
||||
{
|
||||
owner.giveCommand(EActionType::WALK, targetHex);
|
||||
}
|
||||
auto toHex = owner.getBattle()->toWhichHexMove(activeStack, targetHex);
|
||||
assert(toHex.isValid());
|
||||
owner.giveCommand(EActionType::WALK, toHex);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -768,12 +747,11 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, c
|
||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
|
||||
{
|
||||
bool returnAfterAttack = action.get() == PossiblePlayerBattleAction::ATTACK_AND_RETURN;
|
||||
BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex);
|
||||
if(attackFromHex.isValid()) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
|
||||
{
|
||||
BattleAction command = BattleAction::makeMeleeAttack(owner.stacksController->getActiveStack(), targetHex, attackFromHex, returnAfterAttack);
|
||||
owner.sendCommand(command, owner.stacksController->getActiveStack());
|
||||
}
|
||||
auto attacker = owner.stacksController->getActiveStack();
|
||||
BattleHex attackFromHex = owner.getBattle()->fromWhichHexAttack(attacker, targetHex, owner.fieldController->selectAttackDirection(targetHex));
|
||||
assert(attackFromHex.isValid());
|
||||
BattleAction command = BattleAction::makeMeleeAttack(attacker, targetHex, attackFromHex, returnAfterAttack);
|
||||
owner.sendCommand(command, attacker);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1032,19 +1010,6 @@ bool BattleActionsController::isCastingPossibleHere(const CSpell * currentSpell,
|
||||
return m->canBeCastAt(target, problem);
|
||||
}
|
||||
|
||||
bool BattleActionsController::canStackMoveHere(const CStack * stackToMove, const BattleHex & myNumber) const
|
||||
{
|
||||
BattleHexArray acc = owner.getBattle()->battleGetAvailableHexes(stackToMove, false);
|
||||
BattleHex shiftedDest = myNumber.cloneInDirection(stackToMove->destShiftDir(), false);
|
||||
|
||||
if (acc.contains(myNumber))
|
||||
return true;
|
||||
else if (stackToMove->doubleWide() && acc.contains(shiftedDest))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void BattleActionsController::activateStack()
|
||||
{
|
||||
const CStack * s = owner.stacksController->getActiveStack();
|
||||
|
||||
@@ -45,7 +45,6 @@ class BattleActionsController
|
||||
const CStack * selectedStack;
|
||||
|
||||
bool isCastingPossibleHere (const CSpell * spell, const CStack *shere, const BattleHex & myNumber);
|
||||
bool canStackMoveHere (const CStack *sactive, const BattleHex & MyNumber) const; //TODO: move to BattleState / callback
|
||||
std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack) const; //called when stack gets its turn
|
||||
void reorderPossibleActionsPriority(const CStack * stack, const CStack * targetStack);
|
||||
|
||||
|
||||
@@ -263,9 +263,10 @@ void BattleFieldController::showBackgroundImageWithHexes(Canvas & canvas)
|
||||
void BattleFieldController::redrawBackgroundWithHexes()
|
||||
{
|
||||
const CStack *activeStack = owner.stacksController->getActiveStack();
|
||||
BattleHexArray attackableHexes;
|
||||
if(activeStack)
|
||||
occupiableHexes = owner.getBattle()->battleGetAvailableHexes(activeStack, false, true, &attackableHexes);
|
||||
availableHexes = owner.getBattle()->battleGetAvailableHexes(activeStack, false);
|
||||
else
|
||||
availableHexes.clear();
|
||||
|
||||
// prepare background graphic with hexes and shaded hexes
|
||||
backgroundWithHexes->draw(background, Point(0,0));
|
||||
@@ -274,12 +275,15 @@ void BattleFieldController::redrawBackgroundWithHexes()
|
||||
owner.siegeController->showAbsoluteObstacles(*backgroundWithHexes);
|
||||
|
||||
// show shaded hexes for active's stack valid movement and the hexes that it can attack
|
||||
if(settings["battle"]["stackRange"].Bool())
|
||||
if(activeStack && settings["battle"]["stackRange"].Bool())
|
||||
{
|
||||
BattleHexArray hexesToShade = occupiableHexes;
|
||||
hexesToShade.insert(attackableHexes);
|
||||
for(const BattleHex & hex : hexesToShade)
|
||||
auto occupiableHexes = owner.getBattle()->battleGetOccupiableHexes(availableHexes, activeStack);
|
||||
for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++)
|
||||
{
|
||||
//shade occupiable and attackable hexes
|
||||
if (occupiableHexes.contains(hex) ||
|
||||
(owner.getBattle()->battleCanAttackUnit(activeStack, owner.getBattle()->battleGetStackByPos(hex, true)) &&
|
||||
owner.getBattle()->battleCanAttackHex(availableHexes, activeStack, hex)))
|
||||
showHighlightedHex(*backgroundWithHexes, cellShade, hex, false);
|
||||
}
|
||||
}
|
||||
@@ -330,10 +334,7 @@ BattleHexArray BattleFieldController::getMovementRangeForHoveredStack()
|
||||
return BattleHexArray();
|
||||
|
||||
auto hoveredStack = getHoveredStack();
|
||||
if(hoveredStack)
|
||||
return owner.getBattle()->battleGetAvailableHexes(hoveredStack, true, true, nullptr);
|
||||
else
|
||||
return BattleHexArray();
|
||||
return hoveredStack ? owner.getBattle()->battleGetOccupiableHexes(hoveredStack, true) : BattleHexArray();
|
||||
}
|
||||
|
||||
BattleHexArray BattleFieldController::getHighlightedHexesForSpellRange()
|
||||
@@ -371,45 +372,26 @@ BattleHexArray BattleFieldController::getHighlightedHexesForMovementTarget()
|
||||
if(!stack)
|
||||
return {};
|
||||
|
||||
BattleHexArray availableHexes = owner.getBattle()->battleGetAvailableHexes(stack, false, false, nullptr);
|
||||
|
||||
auto hoveredStack = owner.getBattle()->battleGetStackByPos(hoveredHex, true);
|
||||
|
||||
if(owner.getBattle()->battleCanAttack(stack, hoveredStack, hoveredHex) && isTileAttackable(hoveredHex))
|
||||
{
|
||||
BattleHex attackFromHex = fromWhichHexAttack(hoveredHex);
|
||||
if(owner.getBattle()->battleCanAttack(stack, hoveredStack, attackFromHex))
|
||||
if(owner.getBattle()->battleCanAttackUnit(stack, hoveredStack) && owner.getBattle()->battleCanAttackHex(availableHexes, stack, hoveredHex))
|
||||
{
|
||||
BattleHex fromHex = owner.getBattle()->fromWhichHexAttack(stack, hoveredHex, selectAttackDirection(hoveredHex));
|
||||
assert(fromHex.isValid());
|
||||
if(stack->doubleWide())
|
||||
return {attackFromHex, stack->occupiedHex(attackFromHex)};
|
||||
return {fromHex, stack->occupiedHex(fromHex)};
|
||||
|
||||
return {attackFromHex};
|
||||
}
|
||||
return {fromHex};
|
||||
}
|
||||
|
||||
auto toHex = owner.getBattle()->toWhichHexMove(availableHexes, stack, hoveredHex);
|
||||
if (!toHex.isValid())
|
||||
return {};
|
||||
|
||||
if (stack->doubleWide())
|
||||
{
|
||||
const bool canMoveHeadHere = hoveredHex.isAvailable() && availableHexes.contains(hoveredHex);
|
||||
const bool canMoveTailHere = hoveredHex.isAvailable() && availableHexes.contains(hoveredHex.cloneInDirection(stack->destShiftDir()));
|
||||
const bool backwardsMove = stack->unitSide() == BattleSide::ATTACKER ?
|
||||
hoveredHex.getX() < stack->getPosition().getX():
|
||||
hoveredHex.getX() > stack->getPosition().getX();
|
||||
|
||||
if(canMoveTailHere && (backwardsMove || !canMoveHeadHere))
|
||||
return {hoveredHex, hoveredHex.cloneInDirection(stack->destShiftDir())};
|
||||
|
||||
if (canMoveHeadHere)
|
||||
return {hoveredHex, stack->occupiedHex(hoveredHex)};
|
||||
|
||||
return {};
|
||||
}
|
||||
return {toHex, stack->occupiedHex(toHex)};
|
||||
else
|
||||
{
|
||||
if (availableHexes.contains(hoveredHex))
|
||||
return {hoveredHex};
|
||||
else
|
||||
return {};
|
||||
}
|
||||
return {toHex};
|
||||
}
|
||||
|
||||
// Range limit highlight helpers
|
||||
@@ -667,164 +649,47 @@ BattleHex BattleFieldController::getHexAtPosition(Point hoverPos)
|
||||
|
||||
BattleHex::EDir BattleFieldController::selectAttackDirection(const BattleHex & myNumber) const
|
||||
{
|
||||
const bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
|
||||
auto attacker = owner.stacksController->getActiveStack();
|
||||
assert(attacker);
|
||||
const BattleHexArray & neighbours = myNumber.getAllNeighbouringTiles();
|
||||
// 0 1
|
||||
// 5 x 2
|
||||
// 4 3
|
||||
|
||||
// if true - our current stack can move into this hex (and attack)
|
||||
std::array<bool, 8> attackAvailability;
|
||||
|
||||
if (doubleWide)
|
||||
{
|
||||
// For double-hexes we need to ensure that both hexes needed for this direction are occupyable:
|
||||
// | -0- | -1- | -2- | -3- | -4- | -5- | -6- | -7-
|
||||
// | o o - | - o o | - - | - - | - - | - - | o o | - -
|
||||
// | - x - | - x - | - x o o| - x - | - x - |o o x - | - x - | - x -
|
||||
// | - - | - - | - - | - o o | o o - | - - | - - | o o
|
||||
|
||||
for (size_t i : { 1, 2, 3})
|
||||
{
|
||||
BattleHex target = neighbours[i].cloneInDirection(BattleHex::RIGHT, false);
|
||||
attackAvailability[i] = neighbours[i].isValid() && occupiableHexes.contains(neighbours[i]) && target.isValid() && occupiableHexes.contains(target);
|
||||
}
|
||||
|
||||
for (size_t i : { 4, 5, 0})
|
||||
{
|
||||
BattleHex target = neighbours[i].cloneInDirection(BattleHex::LEFT, false);
|
||||
attackAvailability[i] = neighbours[i].isValid() && occupiableHexes.contains(neighbours[i]) && target.isValid() && occupiableHexes.contains(target);
|
||||
}
|
||||
|
||||
attackAvailability[6] = neighbours[0].isValid() && neighbours[1].isValid() && occupiableHexes.contains(neighbours[0]) && occupiableHexes.contains(neighbours[1]);
|
||||
attackAvailability[7] = neighbours[3].isValid() && neighbours[4].isValid() && occupiableHexes.contains(neighbours[3]) && occupiableHexes.contains(neighbours[4]);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < 6; ++i)
|
||||
attackAvailability[i] = neighbours[i].isValid() && occupiableHexes.contains(neighbours[i]);
|
||||
|
||||
attackAvailability[6] = false;
|
||||
attackAvailability[7] = false;
|
||||
}
|
||||
|
||||
// Zero available tiles to attack from
|
||||
if ( vstd::find(attackAvailability, true) == attackAvailability.end())
|
||||
{
|
||||
logGlobal->error("Error: cannot find a hex to attack hex %d from!", myNumber);
|
||||
return BattleHex::NONE;
|
||||
}
|
||||
|
||||
// For each valid direction, select position to test against
|
||||
std::array<Point, 8> testPoint;
|
||||
testPoint.fill(Point::makeInvalid());
|
||||
|
||||
for (size_t i = 0; i < 6; ++i)
|
||||
if (attackAvailability[i])
|
||||
if (owner.getBattle()->battleCanAttackHex(availableHexes, attacker, myNumber, BattleHex::EDir(i)))
|
||||
testPoint[i] = hexPositionAbsolute(neighbours[i]).center();
|
||||
|
||||
// For bottom/top directions select central point, but move it a bit away from true center to reduce zones allocated to them
|
||||
if (attackAvailability[6])
|
||||
if (owner.getBattle()->battleCanAttackHex(availableHexes, attacker, myNumber, BattleHex::EDir(6)))
|
||||
testPoint[6] = (hexPositionAbsolute(neighbours[0]).center() + hexPositionAbsolute(neighbours[1]).center()) / 2 + Point(0, -5);
|
||||
|
||||
if (attackAvailability[7])
|
||||
if (owner.getBattle()->battleCanAttackHex(availableHexes, attacker, myNumber, BattleHex::EDir(7)))
|
||||
testPoint[7] = (hexPositionAbsolute(neighbours[3]).center() + hexPositionAbsolute(neighbours[4]).center()) / 2 + Point(0, 5);
|
||||
|
||||
// Compute distance between tested position & cursor position and pick nearest
|
||||
std::array<int, 8> distance2;
|
||||
|
||||
for (size_t i = 0; i < 8; ++i)
|
||||
if (attackAvailability[i])
|
||||
distance2[i] = (testPoint[i].y - currentAttackOriginPoint.y)*(testPoint[i].y - currentAttackOriginPoint.y) + (testPoint[i].x - currentAttackOriginPoint.x)*(testPoint[i].x - currentAttackOriginPoint.x);
|
||||
|
||||
int nearestDistance = std::numeric_limits<int>::max();
|
||||
size_t nearest = -1;
|
||||
|
||||
for (size_t i = 0; i < 8; ++i)
|
||||
if (attackAvailability[i] && (nearest == -1 || distance2[i] < distance2[nearest]) )
|
||||
{
|
||||
if (testPoint[i].isValid())
|
||||
{
|
||||
int distance = (testPoint[i].y - currentAttackOriginPoint.y)*(testPoint[i].y - currentAttackOriginPoint.y) + (testPoint[i].x - currentAttackOriginPoint.x)*(testPoint[i].x - currentAttackOriginPoint.x);
|
||||
if (nearest == -1 || distance < nearestDistance)
|
||||
{
|
||||
nearestDistance = distance;
|
||||
nearest = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(nearest != -1);
|
||||
if (nearest == -1)
|
||||
// Zero available tiles to attack from
|
||||
logGlobal->error("Error: cannot find a hex to attack hex %d from!", myNumber);
|
||||
return BattleHex::EDir(nearest);
|
||||
}
|
||||
|
||||
BattleHex BattleFieldController::fromWhichHexAttack(const BattleHex & attackTarget)
|
||||
{
|
||||
BattleHex::EDir direction = selectAttackDirection(attackTarget);
|
||||
|
||||
const CStack * attacker = owner.stacksController->getActiveStack();
|
||||
|
||||
assert(direction != BattleHex::NONE);
|
||||
assert(attacker);
|
||||
|
||||
if (!attacker->doubleWide())
|
||||
{
|
||||
assert(direction != BattleHex::BOTTOM);
|
||||
assert(direction != BattleHex::TOP);
|
||||
return attackTarget.cloneInDirection(direction);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We need to find position of right hex of double-hex creature (or left for defending side)
|
||||
// | TOP_LEFT |TOP_RIGHT | RIGHT |BOTTOM_RIGHT|BOTTOM_LEFT| LEFT | TOP |BOTTOM
|
||||
// | o o - | - o o | - - | - - | - - | - - | o o | - -
|
||||
// | - x - | - x - | - x o o| - x - | - x - |o o x - | - x - | - x -
|
||||
// | - - | - - | - - | - o o | o o - | - - | - - | o o
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case BattleHex::TOP_LEFT:
|
||||
case BattleHex::LEFT:
|
||||
case BattleHex::BOTTOM_LEFT:
|
||||
{
|
||||
if ( attacker->unitSide() == BattleSide::ATTACKER )
|
||||
return attackTarget.cloneInDirection(direction);
|
||||
else
|
||||
return attackTarget.cloneInDirection(direction).cloneInDirection(BattleHex::LEFT);
|
||||
}
|
||||
|
||||
case BattleHex::TOP_RIGHT:
|
||||
case BattleHex::RIGHT:
|
||||
case BattleHex::BOTTOM_RIGHT:
|
||||
{
|
||||
if ( attacker->unitSide() == BattleSide::ATTACKER )
|
||||
return attackTarget.cloneInDirection(direction).cloneInDirection(BattleHex::RIGHT);
|
||||
else
|
||||
return attackTarget.cloneInDirection(direction);
|
||||
}
|
||||
|
||||
case BattleHex::TOP:
|
||||
{
|
||||
if ( attacker->unitSide() == BattleSide::ATTACKER )
|
||||
return attackTarget.cloneInDirection(BattleHex::TOP_RIGHT);
|
||||
else
|
||||
return attackTarget.cloneInDirection(BattleHex::TOP_LEFT);
|
||||
}
|
||||
|
||||
case BattleHex::BOTTOM:
|
||||
{
|
||||
if ( attacker->unitSide() == BattleSide::ATTACKER )
|
||||
return attackTarget.cloneInDirection(BattleHex::BOTTOM_RIGHT);
|
||||
else
|
||||
return attackTarget.cloneInDirection(BattleHex::BOTTOM_LEFT);
|
||||
}
|
||||
default:
|
||||
assert(0);
|
||||
return BattleHex::INVALID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BattleFieldController::isTileAttackable(const BattleHex & number) const
|
||||
{
|
||||
if(!number.isValid())
|
||||
return false;
|
||||
|
||||
for (auto & elem : occupiableHexes)
|
||||
{
|
||||
if (BattleHex::mutualPosition(elem, number) != BattleHex::EDir::NONE)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BattleFieldController::updateAccessibleHexes()
|
||||
{
|
||||
auto accessibility = owner.getBattle()->getAccessibility();
|
||||
|
||||
@@ -46,8 +46,8 @@ class BattleFieldController : public CIntObject
|
||||
/// hex currently under mouse hover
|
||||
BattleHex hoveredHex;
|
||||
|
||||
/// hexes to which currently active stack can move
|
||||
BattleHexArray occupiableHexes;
|
||||
/// hexes to which the currently active stack can move (for double-wide units only the head is considered)
|
||||
BattleHexArray availableHexes;
|
||||
|
||||
/// hexes that when in front of a unit cause it's amount box to move back
|
||||
std::array<bool, GameConstants::BFIELD_SIZE> stackCountOutsideHexes;
|
||||
@@ -126,13 +126,8 @@ public:
|
||||
/// Returns the currently hovered stack
|
||||
const CStack* getHoveredStack();
|
||||
|
||||
/// returns true if selected tile can be attacked in melee by current stack
|
||||
bool isTileAttackable(const BattleHex & number) const;
|
||||
|
||||
/// returns true if stack should render its stack count image in default position - outside own hex
|
||||
bool stackCountOutsideHex(const BattleHex & number) const;
|
||||
|
||||
BattleHex::EDir selectAttackDirection(const BattleHex & myNumber) const;
|
||||
|
||||
BattleHex fromWhichHexAttack(const BattleHex & myNumber);
|
||||
};
|
||||
|
||||
@@ -107,21 +107,6 @@ si32 CStack::magicResistance() const
|
||||
return static_cast<si32>(100 - castChance);
|
||||
}
|
||||
|
||||
BattleHex::EDir CStack::destShiftDir() const
|
||||
{
|
||||
if(doubleWide())
|
||||
{
|
||||
if(side == BattleSide::ATTACKER)
|
||||
return BattleHex::EDir::RIGHT;
|
||||
else
|
||||
return BattleHex::EDir::LEFT;
|
||||
}
|
||||
else
|
||||
{
|
||||
return BattleHex::EDir::NONE;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<SpellID> CStack::activeSpells() const
|
||||
{
|
||||
std::vector<SpellID> ret;
|
||||
|
||||
@@ -66,8 +66,6 @@ public:
|
||||
static BattleHexArray meleeAttackHexes(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID);
|
||||
static bool isMeleeAttackPossible(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID);
|
||||
|
||||
BattleHex::EDir destShiftDir() const;
|
||||
|
||||
void prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand) const; //requires bsa.damageAmount filled
|
||||
static void prepareAttacked(BattleStackAttacked & bsa,
|
||||
vstd::RNG & rand,
|
||||
|
||||
@@ -616,91 +616,169 @@ BattleHexArray CBattleInfoCallback::battleGetAvailableHexes(const ReachabilityIn
|
||||
return ret;
|
||||
}
|
||||
|
||||
BattleHexArray CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange, bool addOccupiable, BattleHexArray * attackable) const
|
||||
BattleHexArray CBattleInfoCallback::battleGetOccupiableHexes(const battle::Unit * unit, bool obtainMovementRange) const
|
||||
{
|
||||
BattleHexArray ret = battleGetAvailableHexes(unit, obtainMovementRange);
|
||||
|
||||
if(ret.empty())
|
||||
return ret;
|
||||
|
||||
if(addOccupiable && unit->doubleWide())
|
||||
{
|
||||
BattleHexArray occupiable;
|
||||
|
||||
for(const auto & hex : ret)
|
||||
occupiable.insert(unit->occupiedHex(hex));
|
||||
|
||||
ret.insert(occupiable);
|
||||
return battleGetOccupiableHexes(battleGetAvailableHexes(unit, obtainMovementRange), unit);
|
||||
}
|
||||
|
||||
BattleHexArray CBattleInfoCallback::battleGetOccupiableHexes(const BattleHexArray & availableHexes, const battle::Unit * unit) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(BattleHexArray());
|
||||
if (!unit)
|
||||
throw std::runtime_error("Undefined unit in battleGetOccupiableHexes!");
|
||||
|
||||
if(attackable)
|
||||
if (unit->doubleWide())
|
||||
{
|
||||
auto meleeAttackable = [&](const BattleHex & hex) -> bool
|
||||
{
|
||||
// Return true if given hex has at least one available neighbour.
|
||||
// Available hexes are already present in ret vector.
|
||||
auto availableNeighbour = boost::find_if(ret, [=] (const BattleHex & availableHex)
|
||||
{
|
||||
return BattleHex::mutualPosition(hex, availableHex) >= 0;
|
||||
});
|
||||
return availableNeighbour != ret.end();
|
||||
};
|
||||
for(const auto * otherSt : battleAliveUnits(otherSide(unit->unitSide())))
|
||||
{
|
||||
if(!otherSt->isValidTarget(false))
|
||||
continue;
|
||||
|
||||
const BattleHexArray & occupied = otherSt->getHexes();
|
||||
|
||||
if(battleCanShoot(unit, otherSt->getPosition()))
|
||||
{
|
||||
attackable->insert(occupied);
|
||||
continue;
|
||||
auto occupiableHexes = BattleHexArray(availableHexes);
|
||||
for (auto hex : availableHexes)
|
||||
occupiableHexes.insert(unit->occupiedHex(hex));
|
||||
return occupiableHexes;
|
||||
}
|
||||
return availableHexes;
|
||||
}
|
||||
|
||||
for(const BattleHex & he : occupied)
|
||||
BattleHex CBattleInfoCallback::fromWhichHexAttack(const battle::Unit * attacker, const BattleHex & target, const BattleHex::EDir & direction) const
|
||||
{
|
||||
if(meleeAttackable(he))
|
||||
attackable->insert(he);
|
||||
RETURN_IF_NOT_BATTLE(BattleHex::INVALID);
|
||||
if (!attacker)
|
||||
throw std::runtime_error("Undefined attacker in fromWhichHexAttack!");
|
||||
|
||||
if (!target.isValid() || direction == BattleHex::NONE)
|
||||
return BattleHex::INVALID;
|
||||
|
||||
bool isAttacker = attacker->unitSide() == BattleSide::ATTACKER;
|
||||
if (attacker->doubleWide())
|
||||
{
|
||||
// We need to find position of right hex of double-hex creature (or left for defending side)
|
||||
// | TOP_LEFT | TOP_RIGHT | RIGHT |BOTTOM_RIGHT|BOTTOM_LEFT| LEFT | TOP | BOTTOM
|
||||
// | o o - | - o o | - - | - - | - - | - - | o o | - -
|
||||
// | - x - | - x - | - x o o | - x - | - x - | o o x - | - x - | - x -
|
||||
// | - - | - - | - - | - o o | o o - | - - | - - | o o
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case BattleHex::TOP_LEFT:
|
||||
case BattleHex::LEFT:
|
||||
case BattleHex::BOTTOM_LEFT:
|
||||
return target.cloneInDirection(direction, false)
|
||||
.cloneInDirection(isAttacker ? BattleHex::NONE : BattleHex::LEFT, false);
|
||||
|
||||
case BattleHex::TOP_RIGHT:
|
||||
case BattleHex::RIGHT:
|
||||
case BattleHex::BOTTOM_RIGHT:
|
||||
return target.cloneInDirection(direction, false)
|
||||
.cloneInDirection(isAttacker ? BattleHex::RIGHT : BattleHex::NONE, false);
|
||||
|
||||
case BattleHex::TOP:
|
||||
return target.cloneInDirection(isAttacker ? BattleHex::TOP_RIGHT : BattleHex::TOP_LEFT, false);
|
||||
|
||||
case BattleHex::BOTTOM:
|
||||
return target.cloneInDirection(isAttacker ? BattleHex::BOTTOM_RIGHT : BattleHex::BOTTOM_LEFT, false);
|
||||
|
||||
default:
|
||||
return BattleHex::INVALID;
|
||||
}
|
||||
}
|
||||
if (direction == BattleHex::TOP || direction == BattleHex::BOTTOM)
|
||||
return BattleHex::INVALID;
|
||||
return target.cloneInDirection(direction, false);
|
||||
}
|
||||
|
||||
BattleHex CBattleInfoCallback::toWhichHexMove(const battle::Unit * unit, const BattleHex & position) const
|
||||
{
|
||||
return toWhichHexMove(battleGetAvailableHexes(unit, false), unit, position);
|
||||
}
|
||||
|
||||
BattleHex CBattleInfoCallback::toWhichHexMove(const BattleHexArray & availableHexes, const battle::Unit * unit, const BattleHex & position) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(false);
|
||||
|
||||
if (!unit)
|
||||
throw std::runtime_error("Undefined unit in toWhichHexMove!");
|
||||
if (!position.isValid())
|
||||
return BattleHex::INVALID;
|
||||
|
||||
if (availableHexes.contains(position))
|
||||
return position;
|
||||
if (unit->doubleWide())
|
||||
{
|
||||
auto headPosition = position.cloneInDirection(unit->headDirection(), false);
|
||||
if (availableHexes.contains(headPosition))
|
||||
return headPosition;
|
||||
}
|
||||
return BattleHex::INVALID;
|
||||
}
|
||||
|
||||
bool CBattleInfoCallback::battleCanAttackHex(const battle::Unit * attacker, const BattleHex & position) const
|
||||
{
|
||||
return battleCanAttackHex(battleGetAvailableHexes(attacker, false), attacker, position);
|
||||
}
|
||||
|
||||
bool CBattleInfoCallback::battleCanAttackHex(const BattleHexArray & availableHexes, const battle::Unit * attacker, const BattleHex & position) const
|
||||
{
|
||||
for (auto direction = 0; direction < 8; direction++)
|
||||
{
|
||||
if (battleCanAttackHex(availableHexes, attacker, position, BattleHex::EDir(direction)))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CBattleInfoCallback::battleCanAttackHex(const battle::Unit * attacker, const BattleHex & position, const BattleHex::EDir & direction) const
|
||||
{
|
||||
return battleCanAttackHex(battleGetAvailableHexes(attacker, false), attacker, position, direction);
|
||||
}
|
||||
|
||||
bool CBattleInfoCallback::battleCanAttackHex(const BattleHexArray & availableHexes, const battle::Unit * attacker, const BattleHex & position, const BattleHex::EDir & direction) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(false);
|
||||
|
||||
if (!attacker)
|
||||
throw std::runtime_error("Undefined attacker in battleCanAttackHex!");
|
||||
|
||||
if (!position.isValid() || direction == BattleHex::NONE)
|
||||
return false;
|
||||
|
||||
BattleHex fromHex = fromWhichHexAttack(attacker, position, direction);
|
||||
|
||||
//check if the attack is performed from an available hex
|
||||
if (!fromHex.isValid() || !availableHexes.contains(fromHex))
|
||||
return false;
|
||||
|
||||
//if the movement ends in an obstacle, check if the obstacle allows attacking from that position
|
||||
if (attacker->getPosition() != fromHex)
|
||||
{
|
||||
for (const auto & obstacle : battleGetAllObstacles())
|
||||
{
|
||||
if (obstacle->getStoppingTile().contains(fromHex))
|
||||
return false;
|
||||
if (attacker->doubleWide() && obstacle->getStoppingTile().contains(attacker->occupiedHex(fromHex)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBattleInfoCallback::battleCanAttack(const battle::Unit * stack, const battle::Unit * target, const BattleHex & dest) const
|
||||
bool CBattleInfoCallback::battleCanAttackUnit(const battle::Unit * attacker, const battle::Unit * target) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(false);
|
||||
|
||||
if(battleTacticDist())
|
||||
return false;
|
||||
|
||||
if (!stack || !target)
|
||||
if (!attacker)
|
||||
throw std::runtime_error("Undefined attacker in battleCanAttackUnit!");
|
||||
|
||||
if(!target || target->isInvincible())
|
||||
return false;
|
||||
|
||||
if(target->isInvincible())
|
||||
if(attacker == target || !battleMatchOwner(attacker, target))
|
||||
return false;
|
||||
|
||||
if(!battleMatchOwner(stack, target))
|
||||
if (!attacker->isMeleeAttacker())
|
||||
return false;
|
||||
|
||||
if (!stack->isMeleeAttacker())
|
||||
return false;
|
||||
|
||||
if (stack->getPosition() != dest)
|
||||
{
|
||||
for (const auto & obstacle : battleGetAllObstacles())
|
||||
{
|
||||
if (obstacle->getStoppingTile().contains(dest))
|
||||
return false;
|
||||
|
||||
if (stack->doubleWide() && obstacle->getStoppingTile().contains(stack->occupiedHex(dest)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return target->alive();
|
||||
}
|
||||
|
||||
|
||||
@@ -79,14 +79,26 @@ public:
|
||||
|
||||
void battleGetTurnOrder(std::vector<battle::Units> & out, const size_t maxUnits, const int maxTurns, const int turn = 0, BattleSide lastMoved = BattleSide::NONE) const;
|
||||
|
||||
///returns reachable hexes (valid movement destinations), DOES contain stack current position
|
||||
BattleHexArray battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange, bool addOccupiable, BattleHexArray * attackable) const;
|
||||
|
||||
///returns reachable hexes (valid movement destinations), DOES contain stack current position (lite version)
|
||||
BattleHexArray battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange) const;
|
||||
|
||||
BattleHexArray battleGetAvailableHexes(const ReachabilityInfo & cache, const battle::Unit * unit, bool obtainMovementRange) const;
|
||||
|
||||
//returns hexes the unit can occupy, obtainMovementRange ignores tactics mode (for double-wide units includes both head and tail)
|
||||
BattleHexArray battleGetOccupiableHexes(const battle::Unit * unit, bool obtainMovementRange) const;
|
||||
BattleHexArray battleGetOccupiableHexes(const BattleHexArray & availableHexes, const battle::Unit * unit) const;
|
||||
//returns from which hex the attacker would attack the target from given direction; INVALID if not possible; the hex may be inccessible
|
||||
BattleHex fromWhichHexAttack(const battle::Unit * attacker, const BattleHex & target, const BattleHex::EDir & direction) const;
|
||||
|
||||
//returns to which hex the (head of) unit would move to occupy position (possibly by tail)
|
||||
BattleHex toWhichHexMove(const battle::Unit * unit, const BattleHex & position) const;
|
||||
BattleHex toWhichHexMove(const BattleHexArray & availableHexes, const battle::Unit * unit, const BattleHex & position) const;
|
||||
|
||||
//return true iff attacker move towards and attack position from direction (spatial reasoning only)
|
||||
bool battleCanAttackHex(const battle::Unit * attacker, const BattleHex & position, const BattleHex::EDir & direction) const;
|
||||
bool battleCanAttackHex(const BattleHexArray & availableHexes, const battle::Unit * attacker, const BattleHex & position, const BattleHex::EDir & direction) const; //reuse availableHexes on multiple calls
|
||||
bool battleCanAttackHex(const battle::Unit * attacker, const BattleHex & position) const; //check all directions
|
||||
bool battleCanAttackHex(const BattleHexArray & availableHexes, const battle::Unit * attacker, const BattleHex & position) const; //reuse availableHexes on multiple calls
|
||||
|
||||
int battleGetSurrenderCost(const PlayerColor & Player) const; //returns cost of surrendering battle, -1 if surrendering is not possible
|
||||
ReachabilityInfo::TDistances battleGetDistances(const battle::Unit * unit, const BattleHex & assumedPosition) const;
|
||||
BattleHexArray battleGetAttackedHexes(const battle::Unit * attacker, const BattleHex & destinationTile, const BattleHex & attackerPos = BattleHex::INVALID) const;
|
||||
@@ -96,7 +108,7 @@ public:
|
||||
std::pair< BattleHexArray, int > getPath(const BattleHex & start, const BattleHex & dest, const battle::Unit * stack) const;
|
||||
|
||||
bool battleCanTargetEmptyHex(const battle::Unit * attacker) const; //determines of stack with given ID can target empty hex to attack - currently used only for SPELL_LIKE_ATTACK shooting
|
||||
bool battleCanAttack(const battle::Unit * stack, const battle::Unit * target, const BattleHex & dest) const; //determines if stack with given ID can attack target at the selected destination
|
||||
bool battleCanAttackUnit(const battle::Unit * attacker, const battle::Unit * target) const; //determines if attacker can attack target (no spatial reasoning)
|
||||
bool battleCanShoot(const battle::Unit * attacker, const BattleHex & dest) const; //determines if stack with given ID shoot at the selected destination
|
||||
bool battleCanShoot(const battle::Unit * attacker) const; //determines if stack with given ID shoot in principle
|
||||
bool battleIsUnitBlocked(const battle::Unit * unit) const; //returns true if there is neighboring enemy stack
|
||||
|
||||
@@ -91,9 +91,7 @@ BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const
|
||||
if (!coversPos(attacker->occupiedHex(attackOrigin)) && attackOrigin.isAvailable())
|
||||
result.insert(attackOrigin);
|
||||
|
||||
bool isAttacker = attacker->unitSide() == BattleSide::ATTACKER;
|
||||
BattleHex::EDir headDirection = isAttacker ? BattleHex::RIGHT : BattleHex::LEFT;
|
||||
BattleHex headHex = attackOrigin.cloneInDirection(headDirection);
|
||||
BattleHex headHex = attackOrigin.cloneInDirection(attacker->headDirection());
|
||||
|
||||
if (!coversPos(headHex) && headHex.isAvailable())
|
||||
result.insert(headHex);
|
||||
@@ -107,6 +105,21 @@ bool Unit::coversPos(const BattleHex & pos) const
|
||||
return getPosition() == pos || (doubleWide() && (occupiedHex() == pos));
|
||||
}
|
||||
|
||||
BattleHex::EDir Unit::headDirection() const
|
||||
{
|
||||
if(doubleWide())
|
||||
{
|
||||
if(unitSide() == BattleSide::ATTACKER)
|
||||
return BattleHex::EDir::RIGHT;
|
||||
else
|
||||
return BattleHex::EDir::LEFT;
|
||||
}
|
||||
else
|
||||
{
|
||||
return BattleHex::EDir::NONE;
|
||||
}
|
||||
}
|
||||
|
||||
const BattleHexArray & Unit::getHexes() const
|
||||
{
|
||||
return getHexes(getPosition(), doubleWide(), unitSide());
|
||||
|
||||
@@ -140,6 +140,9 @@ public:
|
||||
|
||||
bool coversPos(const BattleHex & position) const; //checks also if unit is double-wide
|
||||
|
||||
/// Returns the direction the double-wide unit is facing; returns NONE for single-hex units
|
||||
BattleHex::EDir headDirection() const;
|
||||
|
||||
const BattleHexArray & getHexes() const; //up to two occupied hexes, starting from front
|
||||
const BattleHexArray & getHexes(const BattleHex & assumedPos) const; //up to two occupied hexes, starting from front
|
||||
static const BattleHexArray & getHexes(const BattleHex & assumedPos, bool twoHex, BattleSide side);
|
||||
|
||||
@@ -649,7 +649,7 @@ BattleActionProcessor::MovementResult BattleActionProcessor::moveStack(const CBa
|
||||
//shifting destination (if we have double wide stack and we can occupy dest but not be exactly there)
|
||||
if(!stackAtEnd && curStack->doubleWide() && !accessibility.accessible(dest, curStack))
|
||||
{
|
||||
BattleHex shifted = dest.cloneInDirection(curStack->destShiftDir(), false);
|
||||
BattleHex shifted = dest.cloneInDirection(curStack->headDirection(), false);
|
||||
|
||||
if(accessibility.accessible(shifted, curStack))
|
||||
dest = shifted;
|
||||
|
||||
@@ -473,9 +473,19 @@ bool BattleFlowProcessor::tryMakeAutomaticActionOfBallistaOrTowers(const CBattle
|
||||
if (battle.battleCanShoot(unit))
|
||||
return true;
|
||||
|
||||
BattleHexArray attackableHexes;
|
||||
battle.battleGetAvailableHexes(unit, true, false, &attackableHexes);
|
||||
return !attackableHexes.empty();
|
||||
auto units = battle.battleAliveUnits();
|
||||
auto availableHexes = battle.battleGetAvailableHexes(unit, true);
|
||||
|
||||
for (auto otherUnit : units)
|
||||
{
|
||||
if (battle.battleCanAttackUnit(unit, otherUnit))
|
||||
for (auto position : otherUnit->getHexes())
|
||||
{
|
||||
if (battle.battleCanAttackHex(availableHexes, unit, position))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const auto & getTowerAttackValue = [&battle, &next] (const battle::Unit * unit)
|
||||
|
||||
Reference in New Issue
Block a user