Opuszek пре 3 месеци
родитељ
комит
6b97fc306d

+ 15 - 18
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()))
 		{

+ 89 - 30
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<const battle::Unit *, BattleHex> 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);
+	logGlobal->trace("Handle Berserk effect");
+	auto targets = battleGetUnitsIf([&berserker](const battle::Unit * u)
+	{
+		return u->isValidTarget(false) && u->unitId() != berserker->unitId();
+	});
+	auto cache = getReachability(berserker);
 
-	// I hate std::pairs with their undescriptive member names first / second
-	struct DistStack
+	if (battleCanShoot(berserker))
 	{
-		uint32_t distanceToPred;
-		BattleHex destination;
-		const battle::Unit * stack;
-	};
+		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
+	{
+		struct TargetData
+		{
+			const battle::Unit * target;
+			BattleHex closestAttackableHex;
+			uint32_t distance;
+		};
 
-	std::vector<DistStack> stackPairs;
+		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);
+		}
 
-	battle::Units possible = battleGetUnitsIf([closest](const battle::Unit * unit)
-	{
-		return unit->isValidTarget(false) && unit != closest;
-	});
+		auto closestUnit = boost::min_element(targetData, [](const TargetData & lhs, const TargetData & rhs)
+		{
+			return lhs.distance < rhs.distance;
+		})[0];
 
-	for(const battle::Unit * st : possible)
-	{
-		for(const BattleHex & hex : avHexes)
-			if(CStack::isMeleeAttackPossible(closest, st, hex))
+		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))
 			{
-				DistStack hlp = {reachability.distances[hex.toInt()], hex, st};
-				stackPairs.push_back(hlp);
+				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()];
 			}
-	}
 
-	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);
+			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;
+		}
 	}
-	else
-		return std::make_pair<const battle::Unit * , BattleHex>(nullptr, BattleHex::INVALID);
 }
 
 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;

+ 7 - 1
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<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
 protected:

+ 25 - 13
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<const battle::Unit *, BattleHex> 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;
 	}