mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-23 22:37:55 +02:00
fix berserk
This commit is contained in:
@@ -22,21 +22,11 @@ PotentialTargets::PotentialTargets(
|
|||||||
auto avHexes = state->battleGetAvailableHexes(reachability, attackerInfo, false);
|
auto avHexes = state->battleGetAvailableHexes(reachability, attackerInfo, false);
|
||||||
|
|
||||||
//FIXME: this should part of battleGetAvailableHexes
|
//FIXME: this should part of battleGetAvailableHexes
|
||||||
bool forceTarget = false;
|
bool isBerserk = attackerInfo->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE);
|
||||||
const battle::Unit * forcedTarget = nullptr;
|
ForcedAction forcedAction;
|
||||||
BattleHex forcedHex;
|
|
||||||
|
|
||||||
if(attackerInfo->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE))
|
if(isBerserk)
|
||||||
{
|
forcedAction = state->getBerserkForcedAction(attackerInfo);
|
||||||
forceTarget = true;
|
|
||||||
auto nearest = state->getNearestStack(attackerInfo);
|
|
||||||
|
|
||||||
if(nearest.first != nullptr)
|
|
||||||
{
|
|
||||||
forcedTarget = nearest.first;
|
|
||||||
forcedHex = nearest.second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto aliveUnits = state->battleGetUnitsIf([=](const battle::Unit * unit)
|
auto aliveUnits = state->battleGetUnitsIf([=](const battle::Unit * unit)
|
||||||
{
|
{
|
||||||
@@ -45,7 +35,7 @@ PotentialTargets::PotentialTargets(
|
|||||||
|
|
||||||
for(auto defender : aliveUnits)
|
for(auto defender : aliveUnits)
|
||||||
{
|
{
|
||||||
if(!forceTarget && !state->battleMatchOwner(attackerInfo, defender))
|
if(!isBerserk && !state->battleMatchOwner(attackerInfo, defender))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto GenerateAttackInfo = [&](bool shooting, const BattleHex & hex) -> AttackPossibility
|
auto GenerateAttackInfo = [&](bool shooting, const BattleHex & hex) -> AttackPossibility
|
||||||
@@ -56,12 +46,19 @@ PotentialTargets::PotentialTargets(
|
|||||||
return AttackPossibility::evaluate(bai, hex, damageCache, state);
|
return AttackPossibility::evaluate(bai, hex, damageCache, state);
|
||||||
};
|
};
|
||||||
|
|
||||||
if(forceTarget)
|
if(isBerserk)
|
||||||
{
|
{
|
||||||
if(forcedTarget && defender->unitId() == forcedTarget->unitId())
|
bool isActionAttack = forcedAction.type == EActionType::WALK_AND_ATTACK || forcedAction.type == EActionType::SHOOT;
|
||||||
possibleAttacks.push_back(GenerateAttackInfo(false, forcedHex));
|
if (isActionAttack && defender->unitId() == forcedAction.target->unitId())
|
||||||
|
{
|
||||||
|
bool rangeAttack = forcedAction.type == EActionType::SHOOT;
|
||||||
|
BattleHex hex = forcedAction.type == EActionType::WALK_AND_ATTACK ? forcedAction.position : BattleHex::INVALID;
|
||||||
|
possibleAttacks.push_back(GenerateAttackInfo(rangeAttack, hex));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
unreachableEnemies.push_back(defender);
|
unreachableEnemies.push_back(defender);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if(state->battleCanShoot(attackerInfo, defender->getPosition()))
|
else if(state->battleCanShoot(attackerInfo, defender->getPosition()))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -762,7 +762,8 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker, const Ba
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(emptyHexAreaAttack || (battleMatchOwner(attacker, defender) && defender->alive()))
|
bool attackerIsBerserk = attacker->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE);
|
||||||
|
if(emptyHexAreaAttack || (defender->alive() && (attackerIsBerserk || battleMatchOwner(attacker, defender))))
|
||||||
{
|
{
|
||||||
if(battleCanShoot(attacker))
|
if(battleCanShoot(attacker))
|
||||||
{
|
{
|
||||||
@@ -1165,44 +1166,101 @@ BattleHexArray CBattleInfoCallback::getStoppers(BattleSide whichSidePerspective)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<const battle::Unit *, BattleHex> CBattleInfoCallback::getNearestStack(const battle::Unit * closest) const
|
ForcedAction CBattleInfoCallback::getBerserkForcedAction(const battle::Unit * berserker) const
|
||||||
{
|
{
|
||||||
auto reachability = getReachability(closest);
|
logGlobal->trace("Handle Berserk effect");
|
||||||
auto avHexes = battleGetAvailableHexes(reachability, closest, false);
|
auto targets = battleGetUnitsIf([&berserker](const battle::Unit * u)
|
||||||
|
|
||||||
// I hate std::pairs with their undescriptive member names first / second
|
|
||||||
struct DistStack
|
|
||||||
{
|
{
|
||||||
uint32_t distanceToPred;
|
return u->isValidTarget(false) && u->unitId() != berserker->unitId();
|
||||||
BattleHex destination;
|
|
||||||
const battle::Unit * stack;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<DistStack> stackPairs;
|
|
||||||
|
|
||||||
battle::Units possible = battleGetUnitsIf([closest](const battle::Unit * unit)
|
|
||||||
{
|
|
||||||
return unit->isValidTarget(false) && unit != closest;
|
|
||||||
});
|
});
|
||||||
|
auto cache = getReachability(berserker);
|
||||||
|
|
||||||
for(const battle::Unit * st : possible)
|
if (battleCanShoot(berserker))
|
||||||
{
|
{
|
||||||
for(const BattleHex & hex : avHexes)
|
const auto target = boost::min_element(targets, [&berserker](const battle::Unit * lhs, const battle::Unit * rhs)
|
||||||
if(CStack::isMeleeAttackPossible(closest, st, hex))
|
{
|
||||||
{
|
return BattleHex::getDistance(berserker->getPosition(), lhs->getPosition()) < BattleHex::getDistance(berserker->getPosition(), rhs->getPosition());
|
||||||
DistStack hlp = {reachability.distances[hex.toInt()], hex, st};
|
})[0];
|
||||||
stackPairs.push_back(hlp);
|
ForcedAction result = {
|
||||||
}
|
EActionType::SHOOT,
|
||||||
}
|
berserker->getPosition(),
|
||||||
|
target
|
||||||
if(!stackPairs.empty())
|
};
|
||||||
{
|
return result;
|
||||||
auto comparator = [](DistStack lhs, DistStack rhs) { return lhs.distanceToPred < rhs.distanceToPred; };
|
|
||||||
auto minimal = boost::min_element(stackPairs, comparator);
|
|
||||||
return std::make_pair(minimal->stack, minimal->destination);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return std::make_pair<const battle::Unit * , BattleHex>(nullptr, BattleHex::INVALID);
|
{
|
||||||
|
struct TargetData
|
||||||
|
{
|
||||||
|
const battle::Unit * target;
|
||||||
|
BattleHex closestAttackableHex;
|
||||||
|
uint32_t distance;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<TargetData> targetData;
|
||||||
|
targetData.reserve(targets.size());
|
||||||
|
for (const battle::Unit * uTarget : targets)
|
||||||
|
{
|
||||||
|
BattleHexArray attackableHexes = uTarget->getAttackableHexes(berserker);
|
||||||
|
auto closestAttackableHex = boost::min_element(attackableHexes, [&cache](const BattleHex & lhs, const BattleHex & rhs)
|
||||||
|
{
|
||||||
|
return cache.distances[lhs.toInt()] < cache.distances[rhs.toInt()];
|
||||||
|
})[0];
|
||||||
|
uint32_t distance = cache.distances[closestAttackableHex.toInt()];
|
||||||
|
TargetData temp = {uTarget, closestAttackableHex, distance};
|
||||||
|
targetData.push_back(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto closestUnit = boost::min_element(targetData, [](const TargetData & lhs, const TargetData & rhs)
|
||||||
|
{
|
||||||
|
return lhs.distance < rhs.distance;
|
||||||
|
})[0];
|
||||||
|
|
||||||
|
if (closestUnit.distance <= berserker->getMovementRange())
|
||||||
|
{
|
||||||
|
ForcedAction result = {
|
||||||
|
EActionType::WALK_AND_ATTACK,
|
||||||
|
closestUnit.closestAttackableHex,
|
||||||
|
closestUnit.target
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else if (closestUnit.distance != ReachabilityInfo::INFINITE_DIST && berserker->getMovementRange() > 0)
|
||||||
|
{
|
||||||
|
BattleHex intermediaryHex;
|
||||||
|
if (berserker->hasBonusOfType(BonusType::FLYING))
|
||||||
|
{
|
||||||
|
BattleHexArray reachableHexes = battleGetAvailableHexes(cache, berserker, false);
|
||||||
|
BattleHex targetPosition = closestUnit.target->getPosition();
|
||||||
|
intermediaryHex = boost::min_element(reachableHexes, [&targetPosition](const BattleHex & lhs, const BattleHex & rhs)
|
||||||
|
{
|
||||||
|
return BattleHex::getDistance(lhs, targetPosition) < BattleHex::getDistance(rhs, targetPosition);
|
||||||
|
})[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BattleHexArray path = getPath(berserker->getPosition(), closestUnit.closestAttackableHex, berserker).first;
|
||||||
|
intermediaryHex = path[path.size() - berserker->getMovementRange()];
|
||||||
|
}
|
||||||
|
|
||||||
|
ForcedAction result = {
|
||||||
|
EActionType::WALK,
|
||||||
|
intermediaryHex,
|
||||||
|
closestUnit.target
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logGlobal->trace("No target found or unit cannot move");
|
||||||
|
ForcedAction result = {
|
||||||
|
EActionType::NO_ACTION,
|
||||||
|
berserker->getPosition(),
|
||||||
|
nullptr
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BattleHex CBattleInfoCallback::getAvailableHex(const CreatureID & creID, BattleSide side, int initialPos) const
|
BattleHex CBattleInfoCallback::getAvailableHex(const CreatureID & creID, BattleSide side, int initialPos) const
|
||||||
@@ -1721,9 +1779,10 @@ bool CBattleInfoCallback::battleIsUnitBlocked(const battle::Unit * unit) const
|
|||||||
{
|
{
|
||||||
RETURN_IF_NOT_BATTLE(false);
|
RETURN_IF_NOT_BATTLE(false);
|
||||||
|
|
||||||
|
bool isBerserk = unit->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE);
|
||||||
for(const auto * adjacent : battleAdjacentUnits(unit))
|
for(const auto * adjacent : battleAdjacentUnits(unit))
|
||||||
{
|
{
|
||||||
if(adjacent->unitOwner() != unit->unitOwner()) //blocked by enemy stack
|
if(adjacent->unitOwner() != unit->unitOwner() || isBerserk)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ struct DLL_LINKAGE BattleClientInterfaceData
|
|||||||
ui8 tacticsMode;
|
ui8 tacticsMode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ForcedAction {
|
||||||
|
EActionType type;
|
||||||
|
BattleHex position;
|
||||||
|
const battle::Unit * target;
|
||||||
|
};
|
||||||
|
|
||||||
class DLL_LINKAGE CBattleInfoCallback : public virtual CBattleInfoEssentials
|
class DLL_LINKAGE CBattleInfoCallback : public virtual CBattleInfoEssentials
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -162,7 +168,7 @@ public:
|
|||||||
AccessibilityInfo getAccessibility() const;
|
AccessibilityInfo getAccessibility() const;
|
||||||
AccessibilityInfo getAccessibility(const battle::Unit * stack) const; //Hexes occupied by stack will be marked as accessible.
|
AccessibilityInfo getAccessibility(const battle::Unit * stack) const; //Hexes occupied by stack will be marked as accessible.
|
||||||
AccessibilityInfo getAccessibility(const BattleHexArray & accessibleHexes) const; //given hexes will be marked as accessible
|
AccessibilityInfo getAccessibility(const BattleHexArray & accessibleHexes) const; //given hexes will be marked as accessible
|
||||||
std::pair<const battle::Unit *, BattleHex> getNearestStack(const battle::Unit * closest) const;
|
ForcedAction getBerserkForcedAction(const battle::Unit * berserker) const;
|
||||||
|
|
||||||
BattleHex getAvailableHex(const CreatureID & creID, BattleSide side, int initialPos = -1) const; //find place for adding new stack
|
BattleHex getAvailableHex(const CreatureID & creID, BattleSide side, int initialPos = -1) const; //find place for adding new stack
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -388,24 +388,36 @@ bool BattleFlowProcessor::tryActivateBerserkPenalty(const CBattleInfoCallback &
|
|||||||
{
|
{
|
||||||
if (next->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE)) //while in berserk
|
if (next->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE)) //while in berserk
|
||||||
{
|
{
|
||||||
logGlobal->trace("Handle Berserk effect");
|
ForcedAction forcedAction = battle.getBerserkForcedAction(next);
|
||||||
std::pair<const battle::Unit *, BattleHex> attackInfo = battle.getNearestStack(next);
|
if (forcedAction.type == EActionType::SHOOT)
|
||||||
if (attackInfo.first != nullptr)
|
|
||||||
{
|
{
|
||||||
BattleAction attack;
|
BattleAction rangeAttack;
|
||||||
attack.actionType = EActionType::WALK_AND_ATTACK;
|
rangeAttack.actionType = EActionType::SHOOT;
|
||||||
attack.side = next->unitSide();
|
rangeAttack.side = next->unitSide();
|
||||||
attack.stackNumber = next->unitId();
|
rangeAttack.stackNumber = next->unitId();
|
||||||
attack.aimToHex(attackInfo.second);
|
rangeAttack.aimToUnit(forcedAction.target);
|
||||||
attack.aimToUnit(attackInfo.first);
|
makeAutomaticAction(battle, next, rangeAttack);
|
||||||
|
}
|
||||||
makeAutomaticAction(battle, next, attack);
|
else if (forcedAction.type == EActionType::WALK_AND_ATTACK)
|
||||||
logGlobal->trace("Attacked nearest target %s", attackInfo.first->getDescription());
|
{
|
||||||
|
BattleAction meleeAttack;
|
||||||
|
meleeAttack.actionType = EActionType::WALK_AND_ATTACK;
|
||||||
|
meleeAttack.side = next->unitSide();
|
||||||
|
meleeAttack.stackNumber = next->unitId();
|
||||||
|
meleeAttack.aimToHex(forcedAction.position);
|
||||||
|
meleeAttack.aimToUnit(forcedAction.target);
|
||||||
|
makeAutomaticAction(battle, next, meleeAttack);
|
||||||
|
} else if (forcedAction.type == EActionType::WALK)
|
||||||
|
{
|
||||||
|
BattleAction movement;
|
||||||
|
movement.actionType = EActionType::WALK;
|
||||||
|
movement.stackNumber = next->unitId();
|
||||||
|
movement.aimToHex(forcedAction.position);
|
||||||
|
makeAutomaticAction(battle, next, movement);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
makeStackDoNothing(battle, next);
|
makeStackDoNothing(battle, next);
|
||||||
logGlobal->trace("No target found");
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user