1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-23 22:37:55 +02:00

fix berserk

This commit is contained in:
Opuszek
2025-07-22 22:11:22 +02:00
parent ac3991b899
commit 6b97fc306d
4 changed files with 139 additions and 65 deletions

View File

@@ -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()))
{ {

View File

@@ -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;

View File

@@ -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:

View File

@@ -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;
} }