diff --git a/AI/BattleAI/PotentialTargets.cpp b/AI/BattleAI/PotentialTargets.cpp index 7a8b82ebb..5f86b4303 100644 --- a/AI/BattleAI/PotentialTargets.cpp +++ b/AI/BattleAI/PotentialTargets.cpp @@ -22,21 +22,11 @@ PotentialTargets::PotentialTargets( auto avHexes = state->battleGetAvailableHexes(reachability, attackerInfo, false); //FIXME: this should part of battleGetAvailableHexes - bool forceTarget = false; - const battle::Unit * forcedTarget = nullptr; - BattleHex forcedHex; + bool isBerserk = attackerInfo->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE); + ForcedAction forcedAction; - if(attackerInfo->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE)) - { - forceTarget = true; - auto nearest = state->getNearestStack(attackerInfo); - - if(nearest.first != nullptr) - { - forcedTarget = nearest.first; - forcedHex = nearest.second; - } - } + if(isBerserk) + forcedAction = state->getBerserkForcedAction(attackerInfo); auto aliveUnits = state->battleGetUnitsIf([=](const battle::Unit * unit) { @@ -45,7 +35,7 @@ PotentialTargets::PotentialTargets( for(auto defender : aliveUnits) { - if(!forceTarget && !state->battleMatchOwner(attackerInfo, defender)) + if(!isBerserk && !state->battleMatchOwner(attackerInfo, defender)) continue; auto GenerateAttackInfo = [&](bool shooting, const BattleHex & hex) -> AttackPossibility @@ -56,12 +46,19 @@ PotentialTargets::PotentialTargets( return AttackPossibility::evaluate(bai, hex, damageCache, state); }; - if(forceTarget) + if(isBerserk) { - if(forcedTarget && defender->unitId() == forcedTarget->unitId()) - possibleAttacks.push_back(GenerateAttackInfo(false, forcedHex)); + bool isActionAttack = forcedAction.type == EActionType::WALK_AND_ATTACK || forcedAction.type == EActionType::SHOOT; + 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 + { unreachableEnemies.push_back(defender); + } } else if(state->battleCanShoot(attackerInfo, defender->getPosition())) { diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 4e7837311..687c5653c 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -762,7 +762,8 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker, const Ba 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)) { @@ -1165,44 +1166,101 @@ BattleHexArray CBattleInfoCallback::getStoppers(BattleSide whichSidePerspective) return ret; } -std::pair CBattleInfoCallback::getNearestStack(const battle::Unit * closest) const +ForcedAction CBattleInfoCallback::getBerserkForcedAction(const battle::Unit * berserker) const { - auto reachability = getReachability(closest); - auto avHexes = battleGetAvailableHexes(reachability, closest, false); - - // I hate std::pairs with their undescriptive member names first / second - struct DistStack + logGlobal->trace("Handle Berserk effect"); + auto targets = battleGetUnitsIf([&berserker](const battle::Unit * u) { - uint32_t distanceToPred; - BattleHex destination; - const battle::Unit * stack; - }; - - std::vector stackPairs; - - battle::Units possible = battleGetUnitsIf([closest](const battle::Unit * unit) - { - return unit->isValidTarget(false) && unit != closest; + return u->isValidTarget(false) && u->unitId() != berserker->unitId(); }); + auto cache = getReachability(berserker); - for(const battle::Unit * st : possible) + if (battleCanShoot(berserker)) { - for(const BattleHex & hex : avHexes) - if(CStack::isMeleeAttackPossible(closest, st, hex)) - { - DistStack hlp = {reachability.distances[hex.toInt()], hex, st}; - stackPairs.push_back(hlp); - } - } - - if(!stackPairs.empty()) - { - 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); + const auto target = boost::min_element(targets, [&berserker](const battle::Unit * lhs, const battle::Unit * rhs) + { + return BattleHex::getDistance(berserker->getPosition(), lhs->getPosition()) < BattleHex::getDistance(berserker->getPosition(), rhs->getPosition()); + })[0]; + ForcedAction result = { + EActionType::SHOOT, + berserker->getPosition(), + target + }; + return result; } else - return std::make_pair(nullptr, BattleHex::INVALID); + { + struct TargetData + { + const battle::Unit * target; + BattleHex closestAttackableHex; + uint32_t distance; + }; + + std::vector 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 @@ -1721,9 +1779,10 @@ bool CBattleInfoCallback::battleIsUnitBlocked(const battle::Unit * unit) const { RETURN_IF_NOT_BATTLE(false); + bool isBerserk = unit->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE); for(const auto * adjacent : battleAdjacentUnits(unit)) { - if(adjacent->unitOwner() != unit->unitOwner()) //blocked by enemy stack + if(adjacent->unitOwner() != unit->unitOwner() || isBerserk) return true; } return false; diff --git a/lib/battle/CBattleInfoCallback.h b/lib/battle/CBattleInfoCallback.h index 831b1e064..1f875d051 100644 --- a/lib/battle/CBattleInfoCallback.h +++ b/lib/battle/CBattleInfoCallback.h @@ -52,6 +52,12 @@ struct DLL_LINKAGE BattleClientInterfaceData ui8 tacticsMode; }; +struct ForcedAction { + EActionType type; + BattleHex position; + const battle::Unit * target; +}; + class DLL_LINKAGE CBattleInfoCallback : public virtual CBattleInfoEssentials { public: @@ -162,7 +168,7 @@ public: AccessibilityInfo getAccessibility() const; 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 - std::pair 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 protected: diff --git a/server/battles/BattleFlowProcessor.cpp b/server/battles/BattleFlowProcessor.cpp index ea9fecc6f..c1a063046 100644 --- a/server/battles/BattleFlowProcessor.cpp +++ b/server/battles/BattleFlowProcessor.cpp @@ -388,24 +388,36 @@ bool BattleFlowProcessor::tryActivateBerserkPenalty(const CBattleInfoCallback & { if (next->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE)) //while in berserk { - logGlobal->trace("Handle Berserk effect"); - std::pair attackInfo = battle.getNearestStack(next); - if (attackInfo.first != nullptr) + ForcedAction forcedAction = battle.getBerserkForcedAction(next); + if (forcedAction.type == EActionType::SHOOT) { - BattleAction attack; - attack.actionType = EActionType::WALK_AND_ATTACK; - attack.side = next->unitSide(); - attack.stackNumber = next->unitId(); - attack.aimToHex(attackInfo.second); - attack.aimToUnit(attackInfo.first); - - makeAutomaticAction(battle, next, attack); - logGlobal->trace("Attacked nearest target %s", attackInfo.first->getDescription()); + BattleAction rangeAttack; + rangeAttack.actionType = EActionType::SHOOT; + rangeAttack.side = next->unitSide(); + rangeAttack.stackNumber = next->unitId(); + rangeAttack.aimToUnit(forcedAction.target); + makeAutomaticAction(battle, next, rangeAttack); + } + else if (forcedAction.type == EActionType::WALK_AND_ATTACK) + { + 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 { makeStackDoNothing(battle, next); - logGlobal->trace("No target found"); } return true; }