mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-30 23:18:08 +02:00
Merge pull request #4306 from vcmi/battle-ai-bait-archers
BattleAI: fix bait for archers when need to go long way
This commit is contained in:
commit
9d18a2269c
@ -201,6 +201,8 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(
|
||||
if(attackInfo.shooting)
|
||||
return 0;
|
||||
|
||||
std::set<uint32_t> checkedUnits;
|
||||
|
||||
auto attacker = attackInfo.attacker;
|
||||
auto hexes = attacker->getSurroundingHexes(hex);
|
||||
for(BattleHex tile : hexes)
|
||||
@ -208,9 +210,13 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(
|
||||
auto st = state->battleGetUnitByPos(tile, true);
|
||||
if(!st || !state->battleMatchOwner(st, attacker))
|
||||
continue;
|
||||
if(vstd::contains(checkedUnits, st->unitId()))
|
||||
continue;
|
||||
if(!state->battleCanShoot(st))
|
||||
continue;
|
||||
|
||||
checkedUnits.insert(st->unitId());
|
||||
|
||||
// FIXME: provide distance info for Jousting bonus
|
||||
BattleAttackInfo rangeAttackInfo(st, attacker, 0, true);
|
||||
rangeAttackInfo.defenderPos = hex;
|
||||
@ -220,9 +226,10 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(
|
||||
|
||||
auto rangeDmg = state->battleEstimateDamage(rangeAttackInfo);
|
||||
auto meleeDmg = state->battleEstimateDamage(meleeAttackInfo);
|
||||
auto cachedDmg = damageCache.getOriginalDamage(st, attacker, state);
|
||||
|
||||
int64_t gain = averageDmg(rangeDmg.damage) - averageDmg(meleeDmg.damage) + 1;
|
||||
res += gain;
|
||||
res += gain * cachedDmg / std::max<uint64_t>(1, averageDmg(rangeDmg.damage));
|
||||
}
|
||||
|
||||
return res;
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "../../lib/battle/BattleAction.h"
|
||||
#include "../../lib/battle/BattleStateInfoForRetreat.h"
|
||||
#include "../../lib/battle/CObstacleInstance.h"
|
||||
#include "../../lib/StartInfo.h"
|
||||
#include "../../lib/CStack.h" // TODO: remove
|
||||
// Eventually only IBattleInfoCallback and battle::Unit should be used,
|
||||
// CUnitState should be private and CStack should be removed completely
|
||||
@ -122,6 +123,11 @@ static float getStrengthRatio(std::shared_ptr<CBattleInfoCallback> cb, BattleSid
|
||||
return enemy == 0 ? 1.0f : static_cast<float>(our) / enemy;
|
||||
}
|
||||
|
||||
int getSimulationTurnsCount(const StartInfo * startInfo)
|
||||
{
|
||||
return startInfo->difficulty < 4 ? 2 : 10;
|
||||
}
|
||||
|
||||
void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
|
||||
{
|
||||
LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName());
|
||||
@ -154,7 +160,10 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
|
||||
logAi->trace("Build evaluator and targets");
|
||||
#endif
|
||||
|
||||
BattleEvaluator evaluator(env, cb, stack, playerID, battleID, side, getStrengthRatio(cb->getBattle(battleID), side));
|
||||
BattleEvaluator evaluator(
|
||||
env, cb, stack, playerID, battleID, side,
|
||||
getStrengthRatio(cb->getBattle(battleID), side),
|
||||
getSimulationTurnsCount(env->game()->getStartInfo()));
|
||||
|
||||
result = evaluator.selectStackAction(stack);
|
||||
|
||||
|
@ -49,6 +49,45 @@ SpellTypes spellType(const CSpell * spell)
|
||||
return SpellTypes::OTHER;
|
||||
}
|
||||
|
||||
BattleEvaluator::BattleEvaluator(
|
||||
std::shared_ptr<Environment> env,
|
||||
std::shared_ptr<CBattleCallback> cb,
|
||||
const battle::Unit * activeStack,
|
||||
PlayerColor playerID,
|
||||
BattleID battleID,
|
||||
BattleSide side,
|
||||
float strengthRatio,
|
||||
int simulationTurnsCount)
|
||||
:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio, simulationTurnsCount),
|
||||
cachedAttack(), playerID(playerID), side(side), env(env),
|
||||
cb(cb), strengthRatio(strengthRatio), battleID(battleID), simulationTurnsCount(simulationTurnsCount)
|
||||
{
|
||||
hb = std::make_shared<HypotheticBattle>(env.get(), cb->getBattle(battleID));
|
||||
damageCache.buildDamageCache(hb, side);
|
||||
|
||||
targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
|
||||
cachedScore = EvaluationResult::INEFFECTIVE_SCORE;
|
||||
}
|
||||
|
||||
BattleEvaluator::BattleEvaluator(
|
||||
std::shared_ptr<Environment> env,
|
||||
std::shared_ptr<CBattleCallback> cb,
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
DamageCache & damageCache,
|
||||
const battle::Unit * activeStack,
|
||||
PlayerColor playerID,
|
||||
BattleID battleID,
|
||||
BattleSide side,
|
||||
float strengthRatio,
|
||||
int simulationTurnsCount)
|
||||
:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio, simulationTurnsCount),
|
||||
cachedAttack(), playerID(playerID), side(side), env(env), cb(cb), hb(hb),
|
||||
damageCache(damageCache), strengthRatio(strengthRatio), battleID(battleID), simulationTurnsCount(simulationTurnsCount)
|
||||
{
|
||||
targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
|
||||
cachedScore = EvaluationResult::INEFFECTIVE_SCORE;
|
||||
}
|
||||
|
||||
std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
|
||||
{
|
||||
std::vector<BattleHex> result;
|
||||
@ -167,7 +206,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
score
|
||||
);
|
||||
|
||||
if (moveTarget.scorePerTurn <= score)
|
||||
if (moveTarget.score <= score)
|
||||
{
|
||||
if(evaluationResult.wait)
|
||||
{
|
||||
@ -197,7 +236,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
}
|
||||
|
||||
//ThreatMap threatsToUs(stack); // These lines may be useful but they are't used in the code.
|
||||
if(moveTarget.scorePerTurn > score)
|
||||
if(moveTarget.score > score)
|
||||
{
|
||||
score = moveTarget.score;
|
||||
cachedAttack = moveTarget.cachedAttack;
|
||||
@ -206,14 +245,13 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
if(stack->waited())
|
||||
{
|
||||
logAi->debug(
|
||||
"Moving %s towards hex %s[%d], score: %2f/%2f",
|
||||
"Moving %s towards hex %s[%d], score: %2f",
|
||||
stack->getDescription(),
|
||||
moveTarget.cachedAttack->attack.defender->getDescription(),
|
||||
moveTarget.cachedAttack->attack.defender->getPosition().hex,
|
||||
moveTarget.score,
|
||||
moveTarget.scorePerTurn);
|
||||
moveTarget.score);
|
||||
|
||||
return goTowardsNearest(stack, moveTarget.positions);
|
||||
return goTowardsNearest(stack, moveTarget.positions, *targets);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -235,7 +273,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
if(stack->doubleWide() && vstd::contains(brokenWallMoat, stack->getPosition()))
|
||||
return BattleAction::makeMove(stack, stack->getPosition().cloneInDirection(BattleHex::RIGHT));
|
||||
else
|
||||
return goTowardsNearest(stack, brokenWallMoat);
|
||||
return goTowardsNearest(stack, brokenWallMoat, *targets);
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,7 +287,32 @@ uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock>
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
|
||||
}
|
||||
|
||||
BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes)
|
||||
BattleAction BattleEvaluator::moveOrAttack(const CStack * stack, BattleHex hex, const PotentialTargets & targets)
|
||||
{
|
||||
auto additionalScore = 0;
|
||||
std::optional<AttackPossibility> attackOnTheWay;
|
||||
|
||||
for(auto & target : targets.possibleAttacks)
|
||||
{
|
||||
if(!target.attack.shooting && target.from == hex && target.attackValue() > additionalScore)
|
||||
{
|
||||
additionalScore = target.attackValue();
|
||||
attackOnTheWay = target;
|
||||
}
|
||||
}
|
||||
|
||||
if(attackOnTheWay)
|
||||
{
|
||||
activeActionMade = true;
|
||||
return BattleAction::makeMeleeAttack(stack, attackOnTheWay->attack.defender->getPosition(), attackOnTheWay->from);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BattleAction::makeMove(stack, hex);
|
||||
}
|
||||
}
|
||||
|
||||
BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes, const PotentialTargets & targets)
|
||||
{
|
||||
auto reachability = cb->getBattle(battleID)->getReachability(stack);
|
||||
auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
|
||||
@ -261,49 +324,38 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
|
||||
|
||||
std::vector<BattleHex> targetHexes = hexes;
|
||||
|
||||
for(int i = 0; i < 5; i++)
|
||||
{
|
||||
std::sort(targetHexes.begin(), targetHexes.end(), [&](BattleHex h1, BattleHex h2) -> bool
|
||||
{
|
||||
return reachability.distances[h1] < reachability.distances[h2];
|
||||
});
|
||||
vstd::erase_if(targetHexes, [](const BattleHex & hex) { return !hex.isValid(); });
|
||||
|
||||
for(auto hex : targetHexes)
|
||||
std::sort(targetHexes.begin(), targetHexes.end(), [&](BattleHex h1, BattleHex h2) -> bool
|
||||
{
|
||||
if(vstd::contains(avHexes, hex))
|
||||
{
|
||||
return BattleAction::makeMove(stack, hex);
|
||||
}
|
||||
|
||||
if(stack->coversPos(hex))
|
||||
{
|
||||
logAi->warn("Warning: already standing on neighbouring tile!");
|
||||
//We shouldn't even be here...
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
}
|
||||
|
||||
if(reachability.distances[targetHexes.front()] <= GameConstants::BFIELD_SIZE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
std::vector<BattleHex> copy = targetHexes;
|
||||
|
||||
for(auto hex : copy)
|
||||
vstd::concatenate(targetHexes, hex.allNeighbouringTiles());
|
||||
|
||||
vstd::erase_if(targetHexes, [](const BattleHex & hex) {return !hex.isValid();});
|
||||
vstd::removeDuplicates(targetHexes);
|
||||
}
|
||||
return reachability.distances[h1] < reachability.distances[h2];
|
||||
});
|
||||
|
||||
BattleHex bestNeighbor = targetHexes.front();
|
||||
|
||||
if(reachability.distances[bestNeighbor] > GameConstants::BFIELD_SIZE)
|
||||
{
|
||||
logAi->trace("No richable hexes.");
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
|
||||
// this turn
|
||||
for(auto hex : targetHexes)
|
||||
{
|
||||
if(vstd::contains(avHexes, hex))
|
||||
{
|
||||
return moveOrAttack(stack, hex, targets);
|
||||
}
|
||||
|
||||
if(stack->coversPos(hex))
|
||||
{
|
||||
logAi->warn("Warning: already standing on neighbouring hex!");
|
||||
//We shouldn't even be here...
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
}
|
||||
|
||||
// not this turn
|
||||
scoreEvaluator.updateReachabilityMap(hb);
|
||||
|
||||
if(stack->hasBonusOfType(BonusType::FLYING))
|
||||
@ -343,7 +395,7 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
|
||||
return scoreEvaluator.checkPositionBlocksOurStacks(*hb, stack, hex) ? BLOCKED_STACK_PENALTY + distance : distance;
|
||||
});
|
||||
|
||||
return BattleAction::makeMove(stack, *nearestAvailableHex);
|
||||
return moveOrAttack(stack, *nearestAvailableHex, targets);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -357,11 +409,16 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
|
||||
|
||||
if(vstd::contains(avHexes, currentDest)
|
||||
&& !scoreEvaluator.checkPositionBlocksOurStacks(*hb, stack, currentDest))
|
||||
return BattleAction::makeMove(stack, currentDest);
|
||||
{
|
||||
return moveOrAttack(stack, currentDest, targets);
|
||||
}
|
||||
|
||||
currentDest = reachability.predecessors[currentDest];
|
||||
}
|
||||
}
|
||||
|
||||
logAi->error("We should either detect that hexes are unreachable or make a move!");
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
|
||||
bool BattleEvaluator::canCastSpell()
|
||||
@ -600,7 +657,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
#endif
|
||||
|
||||
PotentialTargets innerTargets(activeStack, innerCache, state);
|
||||
BattleExchangeEvaluator innerEvaluator(state, env, strengthRatio);
|
||||
BattleExchangeEvaluator innerEvaluator(state, env, strengthRatio, simulationTurnsCount);
|
||||
|
||||
if(!innerTargets.possibleAttacks.empty())
|
||||
{
|
||||
|
@ -37,16 +37,18 @@ class BattleEvaluator
|
||||
float cachedScore;
|
||||
DamageCache damageCache;
|
||||
float strengthRatio;
|
||||
int simulationTurnsCount;
|
||||
|
||||
public:
|
||||
BattleAction selectStackAction(const CStack * stack);
|
||||
bool attemptCastingSpell(const CStack * stack);
|
||||
bool canCastSpell();
|
||||
std::optional<PossibleSpellcast> findBestCreatureSpell(const CStack * stack);
|
||||
BattleAction goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes);
|
||||
BattleAction goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes, const PotentialTargets & targets);
|
||||
std::vector<BattleHex> getBrokenWallMoatHexes() const;
|
||||
void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
|
||||
void print(const std::string & text) const;
|
||||
BattleAction moveOrAttack(const CStack * stack, BattleHex hex, const PotentialTargets & targets);
|
||||
|
||||
BattleEvaluator(
|
||||
std::shared_ptr<Environment> env,
|
||||
@ -55,15 +57,8 @@ public:
|
||||
PlayerColor playerID,
|
||||
BattleID battleID,
|
||||
BattleSide side,
|
||||
float strengthRatio)
|
||||
:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio), cachedAttack(), playerID(playerID), side(side), env(env), cb(cb), strengthRatio(strengthRatio), battleID(battleID)
|
||||
{
|
||||
hb = std::make_shared<HypotheticBattle>(env.get(), cb->getBattle(battleID));
|
||||
damageCache.buildDamageCache(hb, side);
|
||||
|
||||
targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
|
||||
cachedScore = EvaluationResult::INEFFECTIVE_SCORE;
|
||||
}
|
||||
float strengthRatio,
|
||||
int simulationTurnsCount);
|
||||
|
||||
BattleEvaluator(
|
||||
std::shared_ptr<Environment> env,
|
||||
@ -74,10 +69,6 @@ public:
|
||||
PlayerColor playerID,
|
||||
BattleID battleID,
|
||||
BattleSide side,
|
||||
float strengthRatio)
|
||||
:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio), cachedAttack(), playerID(playerID), side(side), env(env), cb(cb), hb(hb), damageCache(damageCache), strengthRatio(strengthRatio), battleID(battleID)
|
||||
{
|
||||
targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
|
||||
cachedScore = EvaluationResult::INEFFECTIVE_SCORE;
|
||||
}
|
||||
float strengthRatio,
|
||||
int simulationTurnsCount);
|
||||
};
|
||||
|
@ -18,7 +18,7 @@ AttackerValue::AttackerValue()
|
||||
}
|
||||
|
||||
MoveTarget::MoveTarget()
|
||||
: positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE), scorePerTurn(EvaluationResult::INEFFECTIVE_SCORE)
|
||||
: positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE)
|
||||
{
|
||||
turnsToRich = 1;
|
||||
}
|
||||
@ -42,7 +42,7 @@ float BattleExchangeVariant::trackAttack(
|
||||
for(auto affectedUnit : affectedUnits)
|
||||
{
|
||||
auto unitToUpdate = hb->getForUpdate(affectedUnit->unitId());
|
||||
auto damageDealt = unitToUpdate->getTotalHealth() - affectedUnit->getTotalHealth();
|
||||
auto damageDealt = unitToUpdate->getAvailableHealth() - affectedUnit->getAvailableHealth();
|
||||
|
||||
if(damageDealt > 0)
|
||||
{
|
||||
@ -58,7 +58,7 @@ float BattleExchangeVariant::trackAttack(
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace(
|
||||
"%s -> %s, ap retaliation, %s, dps: %lld",
|
||||
ap.attack.defender->getDescription(),
|
||||
hb->getForUpdate(ap.attack.defender->unitId())->getDescription(),
|
||||
ap.attack.attacker->getDescription(),
|
||||
ap.attack.shooting ? "shot" : "mellee",
|
||||
damageDealt);
|
||||
@ -277,6 +277,36 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
return result;
|
||||
}
|
||||
|
||||
ReachabilityInfo getReachabilityWithEnemyBypass(
|
||||
const battle::Unit * activeStack,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> state)
|
||||
{
|
||||
ReachabilityInfo::Parameters params(activeStack, activeStack->getPosition());
|
||||
|
||||
if(!params.flying)
|
||||
{
|
||||
for(const auto * unit : state->battleAliveUnits())
|
||||
{
|
||||
if(unit->unitSide() == activeStack->unitSide())
|
||||
continue;
|
||||
|
||||
auto dmg = damageCache.getOriginalDamage(activeStack, unit, state);
|
||||
auto turnsToKill = unit->getAvailableHealth() / dmg;
|
||||
|
||||
vstd::amin(turnsToKill, 100);
|
||||
|
||||
for(auto & hex : unit->getHexes())
|
||||
if(hex.isAvailable()) //towers can have <0 pos; we don't also want to overwrite side columns
|
||||
params.destructibleEnemyTurns[hex] = turnsToKill * unit->getMovementRange();
|
||||
}
|
||||
|
||||
params.bypassEnemyStacks = true;
|
||||
}
|
||||
|
||||
return state->getReachability(params);
|
||||
}
|
||||
|
||||
MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
const battle::Unit * activeStack,
|
||||
PotentialTargets & targets,
|
||||
@ -286,6 +316,8 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
MoveTarget result;
|
||||
BattleExchangeVariant ev;
|
||||
|
||||
logAi->trace("Find move towards unreachable. Enemies count %d", targets.unreachableEnemies.size());
|
||||
|
||||
if(targets.unreachableEnemies.empty())
|
||||
return result;
|
||||
|
||||
@ -296,17 +328,17 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
|
||||
updateReachabilityMap(hb);
|
||||
|
||||
auto dists = cb->getReachability(activeStack);
|
||||
auto dists = getReachabilityWithEnemyBypass(activeStack, damageCache, hb);
|
||||
auto flying = activeStack->hasBonusOfType(BonusType::FLYING);
|
||||
|
||||
for(const battle::Unit * enemy : targets.unreachableEnemies)
|
||||
{
|
||||
std::vector<const battle::Unit *> adjacentStacks = getAdjacentUnits(enemy);
|
||||
auto closestStack = *vstd::minElementByFun(adjacentStacks, [&](const battle::Unit * u) -> int64_t
|
||||
{
|
||||
return dists.distToNearestNeighbour(activeStack, u) * 100000 - activeStack->getTotalHealth();
|
||||
});
|
||||
logAi->trace(
|
||||
"Checking movement towards %d of %s",
|
||||
enemy->getCount(),
|
||||
enemy->creatureId().toCreature()->getNameSingularTranslated());
|
||||
|
||||
auto distance = dists.distToNearestNeighbour(activeStack, closestStack);
|
||||
auto distance = dists.distToNearestNeighbour(activeStack, enemy);
|
||||
|
||||
if(distance >= GameConstants::BFIELD_SIZE)
|
||||
continue;
|
||||
@ -315,30 +347,84 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
continue;
|
||||
|
||||
auto turnsToRich = (distance - 1) / speed + 1;
|
||||
auto hexes = closestStack->getSurroundingHexes();
|
||||
auto enemySpeed = closestStack->getMovementRange();
|
||||
auto hexes = enemy->getSurroundingHexes();
|
||||
auto enemySpeed = enemy->getMovementRange();
|
||||
auto speedRatio = speed / static_cast<float>(enemySpeed);
|
||||
auto multiplier = speedRatio > 1 ? 1 : speedRatio;
|
||||
|
||||
if(enemy->canShoot())
|
||||
multiplier *= 1.5f;
|
||||
|
||||
for(auto hex : hexes)
|
||||
for(auto & hex : hexes)
|
||||
{
|
||||
// FIXME: provide distance info for Jousting bonus
|
||||
auto bai = BattleAttackInfo(activeStack, closestStack, 0, cb->battleCanShoot(activeStack));
|
||||
auto bai = BattleAttackInfo(activeStack, enemy, 0, cb->battleCanShoot(activeStack));
|
||||
auto attack = AttackPossibility::evaluate(bai, hex, damageCache, hb);
|
||||
|
||||
attack.shootersBlockedDmg = 0; // we do not want to count on it, it is not for sure
|
||||
|
||||
auto score = calculateExchange(attack, turnsToRich, targets, damageCache, hb);
|
||||
auto scorePerTurn = BattleScore(score.enemyDamageReduce * std::sqrt(multiplier / turnsToRich), score.ourDamageReduce);
|
||||
|
||||
if(result.scorePerTurn < scoreValue(scorePerTurn))
|
||||
score.enemyDamageReduce *= multiplier;
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("Multiplier: %f, turns: %d, current score %f, new score %f", multiplier, turnsToRich, result.score, scoreValue(score));
|
||||
#endif
|
||||
|
||||
if(result.score < scoreValue(score)
|
||||
|| (result.turnsToRich > turnsToRich && vstd::isAlmostEqual(result.score, scoreValue(score))))
|
||||
{
|
||||
result.scorePerTurn = scoreValue(scorePerTurn);
|
||||
result.score = scoreValue(score);
|
||||
result.positions = closestStack->getAttackableHexes(activeStack);
|
||||
result.positions.clear();
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("New high score");
|
||||
#endif
|
||||
|
||||
for(BattleHex enemyHex : enemy->getAttackableHexes(activeStack))
|
||||
{
|
||||
while(!flying && dists.distances[enemyHex] > speed)
|
||||
{
|
||||
enemyHex = dists.predecessors.at(enemyHex);
|
||||
if(dists.accessibility[enemyHex] == EAccessibility::ALIVE_STACK)
|
||||
{
|
||||
auto defenderToBypass = hb->battleGetUnitByPos(enemyHex);
|
||||
|
||||
if(defenderToBypass)
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("Found target to bypass at %d", enemyHex.hex);
|
||||
#endif
|
||||
|
||||
auto attackHex = dists.predecessors[enemyHex];
|
||||
auto baiBypass = BattleAttackInfo(activeStack, defenderToBypass, 0, cb->battleCanShoot(activeStack));
|
||||
auto attackBypass = AttackPossibility::evaluate(baiBypass, attackHex, damageCache, hb);
|
||||
|
||||
auto adjacentStacks = getAdjacentUnits(enemy);
|
||||
|
||||
adjacentStacks.push_back(defenderToBypass);
|
||||
vstd::removeDuplicates(adjacentStacks);
|
||||
|
||||
auto bypassScore = calculateExchange(
|
||||
attackBypass,
|
||||
dists.distances[attackHex],
|
||||
targets,
|
||||
damageCache,
|
||||
hb,
|
||||
adjacentStacks);
|
||||
|
||||
if(scoreValue(bypassScore) > result.score)
|
||||
{
|
||||
result.score = scoreValue(bypassScore);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("New high score after bypass %f", scoreValue(bypassScore));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.positions.push_back(enemyHex);
|
||||
}
|
||||
|
||||
result.cachedAttack = attack;
|
||||
result.turnsToRich = turnsToRich;
|
||||
}
|
||||
@ -382,7 +468,8 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
const AttackPossibility & ap,
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
std::shared_ptr<HypotheticBattle> hb) const
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits) const
|
||||
{
|
||||
ReachabilityData result;
|
||||
|
||||
@ -390,13 +477,26 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
|
||||
if(!ap.attack.shooting) hexes.push_back(ap.from);
|
||||
|
||||
std::vector<const battle::Unit *> allReachableUnits;
|
||||
|
||||
std::vector<const battle::Unit *> allReachableUnits = additionalUnits;
|
||||
|
||||
for(auto hex : hexes)
|
||||
{
|
||||
vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex));
|
||||
}
|
||||
|
||||
for(auto hex : ap.attack.attacker->getHexes())
|
||||
{
|
||||
auto unitsReachingAttacker = turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex);
|
||||
for(auto unit : unitsReachingAttacker)
|
||||
{
|
||||
if(unit->unitSide() != ap.attack.attacker->unitSide())
|
||||
{
|
||||
allReachableUnits.push_back(unit);
|
||||
result.enemyUnitsReachingAttacker.insert(unit->unitId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vstd::removeDuplicates(allReachableUnits);
|
||||
|
||||
auto copy = allReachableUnits;
|
||||
@ -432,7 +532,7 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
|
||||
for(auto unit : allReachableUnits)
|
||||
{
|
||||
auto accessible = !unit->canShoot();
|
||||
auto accessible = !unit->canShoot() || vstd::contains(additionalUnits, unit);
|
||||
|
||||
if(!accessible)
|
||||
{
|
||||
@ -456,14 +556,14 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
for(auto unit : turnOrder[turn])
|
||||
{
|
||||
if(vstd::contains(allReachableUnits, unit))
|
||||
result.units.push_back(unit);
|
||||
result.units[turn].push_back(unit);
|
||||
}
|
||||
}
|
||||
|
||||
vstd::erase_if(result.units, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !hb->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
vstd::erase_if(result.units[turn], [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !hb->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -494,7 +594,8 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> hb) const
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits) const
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Battle exchange at %d", ap.attack.shooting ? ap.dest.hex : ap.from.hex);
|
||||
@ -513,7 +614,7 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
if(hb->battleGetUnitByID(ap.attack.defender->unitId())->alive())
|
||||
enemyStacks.push_back(ap.attack.defender);
|
||||
|
||||
ReachabilityData exchangeUnits = getExchangeUnits(ap, turn, targets, hb);
|
||||
ReachabilityData exchangeUnits = getExchangeUnits(ap, turn, targets, hb, additionalUnits);
|
||||
|
||||
if(exchangeUnits.units.empty())
|
||||
{
|
||||
@ -523,22 +624,25 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
auto exchangeBattle = std::make_shared<HypotheticBattle>(env.get(), hb);
|
||||
BattleExchangeVariant v;
|
||||
|
||||
for(auto unit : exchangeUnits.units)
|
||||
for(int exchangeTurn = 0; exchangeTurn < exchangeUnits.units.size(); exchangeTurn++)
|
||||
{
|
||||
if(unit->isTurret())
|
||||
continue;
|
||||
|
||||
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, unit, true);
|
||||
auto & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||
auto u = exchangeBattle->getForUpdate(unit->unitId());
|
||||
|
||||
if(u->alive() && !vstd::contains(attackerQueue, unit))
|
||||
for(auto unit : exchangeUnits.units.at(exchangeTurn))
|
||||
{
|
||||
attackerQueue.push_back(unit);
|
||||
if(unit->isTurret())
|
||||
continue;
|
||||
|
||||
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, unit, true);
|
||||
auto & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||
auto u = exchangeBattle->getForUpdate(unit->unitId());
|
||||
|
||||
if(u->alive() && !vstd::contains(attackerQueue, unit))
|
||||
{
|
||||
attackerQueue.push_back(unit);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL
|
||||
logAi->trace("Exchanging: %s", u->getDescription());
|
||||
logAi->trace("Exchanging: %s", u->getDescription());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -552,122 +656,166 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
|
||||
bool canUseAp = true;
|
||||
|
||||
for(auto activeUnit : exchangeUnits.units)
|
||||
std::set<uint32_t> blockedShooters;
|
||||
|
||||
int totalTurnsCount = simulationTurnsCount >= turn + turnOrder.size()
|
||||
? simulationTurnsCount
|
||||
: turn + turnOrder.size();
|
||||
|
||||
for(int exchangeTurn = 0; exchangeTurn < simulationTurnsCount; exchangeTurn++)
|
||||
{
|
||||
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, activeUnit, true);
|
||||
battle::Units & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||
battle::Units & oppositeQueue = isOur ? enemyStacks : ourStacks;
|
||||
bool isMovingTurm = exchangeTurn < turn;
|
||||
int queueTurn = exchangeTurn >= exchangeUnits.units.size()
|
||||
? exchangeUnits.units.size() - 1
|
||||
: exchangeTurn;
|
||||
|
||||
auto attacker = exchangeBattle->getForUpdate(activeUnit->unitId());
|
||||
|
||||
if(!attacker->alive())
|
||||
for(auto activeUnit : exchangeUnits.units.at(queueTurn))
|
||||
{
|
||||
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, activeUnit, true);
|
||||
battle::Units & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||
battle::Units & oppositeQueue = isOur ? enemyStacks : ourStacks;
|
||||
|
||||
auto attacker = exchangeBattle->getForUpdate(activeUnit->unitId());
|
||||
auto shooting = exchangeBattle->battleCanShoot(attacker.get())
|
||||
&& !vstd::contains(blockedShooters, attacker->unitId());
|
||||
|
||||
if(!attacker->alive())
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace( "Attacker is dead");
|
||||
logAi->trace("Attacker is dead");
|
||||
#endif
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
auto targetUnit = ap.attack.defender;
|
||||
|
||||
if(!isOur || !exchangeBattle->battleGetUnitByID(targetUnit->unitId())->alive())
|
||||
{
|
||||
auto estimateAttack = [&](const battle::Unit * u) -> float
|
||||
{
|
||||
auto stackWithBonuses = exchangeBattle->getForUpdate(u->unitId());
|
||||
auto score = v.trackAttack(
|
||||
attacker,
|
||||
stackWithBonuses,
|
||||
exchangeBattle->battleCanShoot(stackWithBonuses.get()),
|
||||
isOur,
|
||||
damageCache,
|
||||
hb,
|
||||
true);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Best target selector %s->%s score = %2f", attacker->getDescription(), stackWithBonuses->getDescription(), score);
|
||||
#endif
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
auto unitsInOppositeQueueExceptInaccessible = oppositeQueue;
|
||||
|
||||
vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u)->bool
|
||||
{
|
||||
return vstd::contains(exchangeUnits.shooters, u);
|
||||
});
|
||||
|
||||
if(!unitsInOppositeQueueExceptInaccessible.empty())
|
||||
{
|
||||
targetUnit = *vstd::maxElementByFun(unitsInOppositeQueueExceptInaccessible, estimateAttack);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
if(isMovingTurm && !shooting
|
||||
&& !vstd::contains(exchangeUnits.enemyUnitsReachingAttacker, attacker->unitId()))
|
||||
{
|
||||
auto reachable = exchangeBattle->battleGetUnitsIf([this, &exchangeBattle, &attacker](const battle::Unit * u) -> bool
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Attacker is moving");
|
||||
#endif
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
auto targetUnit = ap.attack.defender;
|
||||
|
||||
if(!isOur || !exchangeBattle->battleGetUnitByID(targetUnit->unitId())->alive())
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=2
|
||||
logAi->trace("Best target selector for %s", attacker->getDescription());
|
||||
#endif
|
||||
auto estimateAttack = [&](const battle::Unit * u) -> float
|
||||
{
|
||||
auto stackWithBonuses = exchangeBattle->getForUpdate(u->unitId());
|
||||
auto score = v.trackAttack(
|
||||
attacker,
|
||||
stackWithBonuses,
|
||||
exchangeBattle->battleCanShoot(stackWithBonuses.get()),
|
||||
isOur,
|
||||
damageCache,
|
||||
hb,
|
||||
true);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=2
|
||||
logAi->trace("Best target selector %s->%s score = %2f", attacker->getDescription(), stackWithBonuses->getDescription(), score);
|
||||
#endif
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
auto unitsInOppositeQueueExceptInaccessible = oppositeQueue;
|
||||
|
||||
vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u)->bool
|
||||
{
|
||||
if(u->unitSide() == attacker->unitSide())
|
||||
return false;
|
||||
|
||||
if(!exchangeBattle->getForUpdate(u->unitId())->alive())
|
||||
return false;
|
||||
|
||||
if (!u->getPosition().isValid())
|
||||
return false; // e.g. tower shooters
|
||||
|
||||
return vstd::contains_if(reachabilityMap.at(u->getPosition()), [&attacker](const battle::Unit * other) -> bool
|
||||
{
|
||||
return attacker->unitId() == other->unitId();
|
||||
});
|
||||
return vstd::contains(exchangeUnits.shooters, u);
|
||||
});
|
||||
|
||||
if(!reachable.empty())
|
||||
if(!isOur
|
||||
&& exchangeTurn == 0
|
||||
&& exchangeUnits.units.at(exchangeTurn).at(0)->unitId() != ap.attack.attacker->unitId()
|
||||
&& !vstd::contains(exchangeUnits.enemyUnitsReachingAttacker, attacker->unitId()))
|
||||
{
|
||||
targetUnit = *vstd::maxElementByFun(reachable, estimateAttack);
|
||||
vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return u->unitId() == ap.attack.attacker->unitId();
|
||||
});
|
||||
}
|
||||
|
||||
if(!unitsInOppositeQueueExceptInaccessible.empty())
|
||||
{
|
||||
targetUnit = *vstd::maxElementByFun(unitsInOppositeQueueExceptInaccessible, estimateAttack);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto reachable = exchangeBattle->battleGetUnitsIf([this, &exchangeBattle, &attacker](const battle::Unit * u) -> bool
|
||||
{
|
||||
if(u->unitSide() == attacker->unitSide())
|
||||
return false;
|
||||
|
||||
if(!exchangeBattle->getForUpdate(u->unitId())->alive())
|
||||
return false;
|
||||
|
||||
if(!u->getPosition().isValid())
|
||||
return false; // e.g. tower shooters
|
||||
|
||||
return vstd::contains_if(reachabilityMap.at(u->getPosition()), [&attacker](const battle::Unit * other) -> bool
|
||||
{
|
||||
return attacker->unitId() == other->unitId();
|
||||
});
|
||||
});
|
||||
|
||||
if(!reachable.empty())
|
||||
{
|
||||
targetUnit = *vstd::maxElementByFun(reachable, estimateAttack);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Battle queue is empty and no reachable enemy.");
|
||||
logAi->trace("Battle queue is empty and no reachable enemy.");
|
||||
#endif
|
||||
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto defender = exchangeBattle->getForUpdate(targetUnit->unitId());
|
||||
auto shooting = exchangeBattle->battleCanShoot(attacker.get());
|
||||
const int totalAttacks = attacker->getTotalAttacks(shooting);
|
||||
auto defender = exchangeBattle->getForUpdate(targetUnit->unitId());
|
||||
const int totalAttacks = attacker->getTotalAttacks(shooting);
|
||||
|
||||
if(canUseAp && activeUnit->unitId() == ap.attack.attacker->unitId()
|
||||
&& targetUnit->unitId() == ap.attack.defender->unitId())
|
||||
{
|
||||
v.trackAttack(ap, exchangeBattle, damageCache);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int i = 0; i < totalAttacks; i++)
|
||||
if(canUseAp && activeUnit->unitId() == ap.attack.attacker->unitId()
|
||||
&& targetUnit->unitId() == ap.attack.defender->unitId())
|
||||
{
|
||||
v.trackAttack(attacker, defender, shooting, isOur, damageCache, exchangeBattle);
|
||||
|
||||
if(!attacker->alive() || !defender->alive())
|
||||
break;
|
||||
v.trackAttack(ap, exchangeBattle, damageCache);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int i = 0; i < totalAttacks; i++)
|
||||
{
|
||||
v.trackAttack(attacker, defender, shooting, isOur, damageCache, exchangeBattle);
|
||||
|
||||
if(!attacker->alive() || !defender->alive())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!shooting)
|
||||
blockedShooters.insert(defender->unitId());
|
||||
|
||||
canUseAp = false;
|
||||
|
||||
vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
|
||||
vstd::erase_if(oppositeQueue, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
}
|
||||
|
||||
canUseAp = false;
|
||||
|
||||
vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
|
||||
vstd::erase_if(oppositeQueue, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
exchangeBattle->nextRound();
|
||||
}
|
||||
|
||||
// avoid blocking path for stronger stack by weaker stack
|
||||
@ -679,11 +827,28 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
for(auto hex : hexes)
|
||||
reachabilityMap[hex] = getOneTurnReachableUnits(turn, hex);
|
||||
|
||||
auto score = v.getScore();
|
||||
|
||||
if(simulationTurnsCount < totalTurnsCount)
|
||||
{
|
||||
float scalingRatio = simulationTurnsCount / static_cast<float>(totalTurnsCount);
|
||||
|
||||
score.enemyDamageReduce *= scalingRatio;
|
||||
score.ourDamageReduce *= scalingRatio;
|
||||
}
|
||||
|
||||
if(turn > 0)
|
||||
{
|
||||
auto turnMultiplier = 1 - std::min(0.2, 0.05 * turn);
|
||||
|
||||
score.enemyDamageReduce *= turnMultiplier;
|
||||
}
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Exchange score: enemy: %2f, our -%2f", v.getScore().enemyDamageReduce, v.getScore().ourDamageReduce);
|
||||
logAi->trace("Exchange score: enemy: %2f, our -%2f", score.enemyDamageReduce, score.ourDamageReduce);
|
||||
#endif
|
||||
|
||||
return v.getScore();
|
||||
return score;
|
||||
}
|
||||
|
||||
bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap)
|
||||
|
@ -54,7 +54,6 @@ struct AttackerValue
|
||||
struct MoveTarget
|
||||
{
|
||||
float score;
|
||||
float scorePerTurn;
|
||||
std::vector<BattleHex> positions;
|
||||
std::optional<AttackPossibility> cachedAttack;
|
||||
uint8_t turnsToRich;
|
||||
@ -64,7 +63,7 @@ struct MoveTarget
|
||||
|
||||
struct EvaluationResult
|
||||
{
|
||||
static const int64_t INEFFECTIVE_SCORE = -10000;
|
||||
static const int64_t INEFFECTIVE_SCORE = -100000000;
|
||||
|
||||
AttackPossibility bestAttack;
|
||||
MoveTarget bestMove;
|
||||
@ -113,13 +112,15 @@ private:
|
||||
|
||||
struct ReachabilityData
|
||||
{
|
||||
std::vector<const battle::Unit *> units;
|
||||
std::map<int, std::vector<const battle::Unit *>> units;
|
||||
|
||||
// shooters which are within mellee attack and mellee units
|
||||
std::vector<const battle::Unit *> melleeAccessible;
|
||||
|
||||
// far shooters
|
||||
std::vector<const battle::Unit *> shooters;
|
||||
|
||||
std::set<uint32_t> enemyUnitsReachingAttacker;
|
||||
};
|
||||
|
||||
class BattleExchangeEvaluator
|
||||
@ -131,6 +132,7 @@ private:
|
||||
std::map<BattleHex, std::vector<const battle::Unit *>> reachabilityMap;
|
||||
std::vector<battle::Units> turnOrder;
|
||||
float negativeEffectMultiplier;
|
||||
int simulationTurnsCount;
|
||||
|
||||
float scoreValue(const BattleScore & score) const;
|
||||
|
||||
@ -139,7 +141,8 @@ private:
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> hb) const;
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits = {}) const;
|
||||
|
||||
bool canBeHitThisTurn(const AttackPossibility & ap);
|
||||
|
||||
@ -147,7 +150,8 @@ public:
|
||||
BattleExchangeEvaluator(
|
||||
std::shared_ptr<CBattleInfoCallback> cb,
|
||||
std::shared_ptr<Environment> env,
|
||||
float strengthRatio): cb(cb), env(env) {
|
||||
float strengthRatio,
|
||||
int simulationTurnsCount): cb(cb), env(env), simulationTurnsCount(simulationTurnsCount){
|
||||
negativeEffectMultiplier = strengthRatio >= 1 ? 1 : strengthRatio * strengthRatio;
|
||||
}
|
||||
|
||||
@ -171,7 +175,8 @@ public:
|
||||
const AttackPossibility & ap,
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
std::shared_ptr<HypotheticBattle> hb) const;
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits = {}) const;
|
||||
|
||||
bool checkPositionBlocksOurStacks(HypotheticBattle & hb, const battle::Unit * unit, BattleHex position);
|
||||
|
||||
|
@ -89,6 +89,14 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
|
||||
heroes[hero->tempOwner][hero] = HeroRole::MAIN;
|
||||
}
|
||||
|
||||
if(obj->ID == Obj::TOWN)
|
||||
{
|
||||
auto town = dynamic_cast<const CGTownInstance *>(obj);
|
||||
|
||||
if(town->garrisonHero)
|
||||
heroes[town->garrisonHero->tempOwner][town->garrisonHero] = HeroRole::MAIN;
|
||||
}
|
||||
}
|
||||
|
||||
auto ourTowns = cb->getTownsInfo();
|
||||
|
@ -56,7 +56,7 @@ public:
|
||||
|
||||
// //various
|
||||
virtual int getDate(Date mode=Date::DAY) const = 0; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month
|
||||
// const StartInfo * getStartInfo(bool beforeRandomization = false)const;
|
||||
virtual const StartInfo * getStartInfo(bool beforeRandomization = false) const = 0;
|
||||
virtual bool isAllowed(SpellID id) const = 0;
|
||||
virtual bool isAllowed(ArtifactID id) const = 0;
|
||||
virtual bool isAllowed(SecondarySkill id) const = 0;
|
||||
@ -143,7 +143,7 @@ protected:
|
||||
public:
|
||||
//various
|
||||
int getDate(Date mode=Date::DAY)const override; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month
|
||||
virtual const StartInfo * getStartInfo(bool beforeRandomization = false)const;
|
||||
const StartInfo * getStartInfo(bool beforeRandomization = false) const override;
|
||||
bool isAllowed(SpellID id) const override;
|
||||
bool isAllowed(ArtifactID id) const override;
|
||||
bool isAllowed(SecondarySkill id) const override;
|
||||
|
@ -18,9 +18,19 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
bool AccessibilityInfo::tileAccessibleWithGate(BattleHex tile, BattleSide side) const
|
||||
{
|
||||
//at(otherHex) != EAccessibility::ACCESSIBLE && (at(otherHex) != EAccessibility::GATE || side != BattleSide::DEFENDER)
|
||||
if(at(tile) != EAccessibility::ACCESSIBLE)
|
||||
if(at(tile) != EAccessibility::GATE || side != BattleSide::DEFENDER)
|
||||
auto accessibility = at(tile);
|
||||
|
||||
if(accessibility == EAccessibility::ALIVE_STACK)
|
||||
{
|
||||
auto destructible = destructibleEnemyTurns.find(tile);
|
||||
|
||||
return destructible != destructibleEnemyTurns.end();
|
||||
}
|
||||
|
||||
if(accessibility != EAccessibility::ACCESSIBLE)
|
||||
if(accessibility != EAccessibility::GATE || side != BattleSide::DEFENDER)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,8 @@ using TAccessibilityArray = std::array<EAccessibility, GameConstants::BFIELD_SIZ
|
||||
|
||||
struct DLL_LINKAGE AccessibilityInfo : TAccessibilityArray
|
||||
{
|
||||
std::map<BattleHex, ui8> destructibleEnemyTurns;
|
||||
|
||||
public:
|
||||
bool accessible(BattleHex tile, const battle::Unit * stack) const; //checks for both tiles if stack is double wide
|
||||
bool accessible(BattleHex tile, bool doubleWide, BattleSide side) const; //checks for both tiles if stack is double wide
|
||||
|
@ -1052,16 +1052,29 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi
|
||||
continue;
|
||||
|
||||
const int costToNeighbour = ret.distances[curHex.hex] + 1;
|
||||
|
||||
for(BattleHex neighbour : BattleHex::neighbouringTilesCache[curHex.hex])
|
||||
{
|
||||
if(neighbour.isValid())
|
||||
{
|
||||
auto additionalCost = 0;
|
||||
|
||||
if(params.bypassEnemyStacks)
|
||||
{
|
||||
auto enemyToBypass = params.destructibleEnemyTurns.find(neighbour);
|
||||
|
||||
if(enemyToBypass != params.destructibleEnemyTurns.end())
|
||||
{
|
||||
additionalCost = enemyToBypass->second;
|
||||
}
|
||||
}
|
||||
|
||||
const int costFoundSoFar = ret.distances[neighbour.hex];
|
||||
|
||||
if(accessibleCache[neighbour.hex] && costToNeighbour < costFoundSoFar)
|
||||
if(accessibleCache[neighbour.hex] && costToNeighbour + additionalCost < costFoundSoFar)
|
||||
{
|
||||
hexq.push(neighbour);
|
||||
ret.distances[neighbour.hex] = costToNeighbour;
|
||||
ret.distances[neighbour.hex] = costToNeighbour + additionalCost;
|
||||
ret.predecessors[neighbour.hex] = curHex;
|
||||
}
|
||||
}
|
||||
@ -1236,7 +1249,13 @@ ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Pa
|
||||
if(params.flying)
|
||||
return getFlyingReachability(params);
|
||||
else
|
||||
return makeBFS(getAccessibility(params.knownAccessible), params);
|
||||
{
|
||||
auto accessibility = getAccessibility(params.knownAccessible);
|
||||
|
||||
accessibility.destructibleEnemyTurns = params.destructibleEnemyTurns;
|
||||
|
||||
return makeBFS(accessibility, params);
|
||||
}
|
||||
}
|
||||
|
||||
ReachabilityInfo CBattleInfoCallback::getFlyingReachability(const ReachabilityInfo::Parameters ¶ms) const
|
||||
|
@ -29,7 +29,9 @@ struct DLL_LINKAGE ReachabilityInfo
|
||||
bool doubleWide = false;
|
||||
bool flying = false;
|
||||
bool ignoreKnownAccessible = false; //Ignore obstacles if it is in accessible hexes
|
||||
bool bypassEnemyStacks = false; // in case of true will count amount of turns needed to kill enemy and thus move forward
|
||||
std::vector<BattleHex> knownAccessible; //hexes that will be treated as accessible, even if they're occupied by stack (by default - tiles occupied by stack we do reachability for, so it doesn't block itself)
|
||||
std::map<BattleHex, ui8> destructibleEnemyTurns; // hom many turns it is needed to kill enemy on specific hex
|
||||
|
||||
BattleHex startPosition; //assumed position of stack
|
||||
BattleSide perspective = BattleSide::ALL_KNOWING; //some obstacles (eg. quicksands) may be invisible for some side
|
||||
|
@ -17,6 +17,7 @@ class IGameInfoCallbackMock : public IGameInfoCallback
|
||||
public:
|
||||
//various
|
||||
MOCK_CONST_METHOD1(getDate, int(Date));
|
||||
MOCK_CONST_METHOD1(getStartInfo, const StartInfo *(bool));
|
||||
|
||||
MOCK_CONST_METHOD1(isAllowed, bool(SpellID));
|
||||
MOCK_CONST_METHOD1(isAllowed, bool(ArtifactID));
|
||||
|
Loading…
Reference in New Issue
Block a user