diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp new file mode 100644 index 000000000..d060d16bc --- /dev/null +++ b/AI/BattleAI/AttackPossibility.cpp @@ -0,0 +1,66 @@ +/* + * AttackPossibility.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "AttackPossibility.h" + +int AttackPossibility::damageDiff() const +{ + if (!priorities) + priorities = new Priorities; + const auto dealtDmgValue = priorities->stackEvaluator(enemy) * damageDealt; + const auto receivedDmgValue = priorities->stackEvaluator(attack.attacker) * damageReceived; + return dealtDmgValue - receivedDmgValue; +} + +int AttackPossibility::attackValue() const +{ + return damageDiff() + tacticImpact; +} + +AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex) +{ + auto attacker = AttackInfo.attacker; + auto enemy = AttackInfo.defender; + + const int remainingCounterAttacks = getValOr(state.counterAttacksLeft, enemy, enemy->counterAttacksRemaining()); + const bool counterAttacksBlocked = attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || enemy->hasBonusOfType(Bonus::NO_RETALIATION); + const int totalAttacks = 1 + AttackInfo.attackerBonuses->getBonuses(Selector::type(Bonus::ADDITIONAL_ATTACK), (Selector::effectRange (Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))))->totalValue(); + + AttackPossibility ap = {enemy, hex, AttackInfo, 0, 0, 0}; + + auto curBai = AttackInfo; //we'll modify here the stack counts + for(int i = 0; i < totalAttacks; i++) + { + std::pair retaliation(0,0); + auto attackDmg = getCbc()->battleEstimateDamage(CRandomGenerator::getDefault(), curBai, &retaliation); + ap.damageDealt = (attackDmg.first + attackDmg.second) / 2; + ap.damageReceived = (retaliation.first + retaliation.second) / 2; + + if(remainingCounterAttacks <= i || counterAttacksBlocked) + ap.damageReceived = 0; + + curBai.attackerCount = attacker->count - attacker->countKilledByAttack(ap.damageReceived).first; + curBai.defenderCount = enemy->count - enemy->countKilledByAttack(ap.damageDealt).first; + if(!curBai.attackerCount) + break; + //TODO what about defender? should we break? but in pessimistic scenario defender might be alive + } + + //TODO other damage related to attack (eg. fire shield and other abilities) + + //Limit damages by total stack health + vstd::amin(ap.damageDealt, enemy->count * enemy->MaxHealth() - (enemy->MaxHealth() - enemy->firstHPleft)); + vstd::amin(ap.damageReceived, attacker->count * attacker->MaxHealth() - (attacker->MaxHealth() - attacker->firstHPleft)); + + return ap; +} + + +Priorities* AttackPossibility::priorities = nullptr; diff --git a/AI/BattleAI/AttackPossibility.h b/AI/BattleAI/AttackPossibility.h new file mode 100644 index 000000000..bab525c50 --- /dev/null +++ b/AI/BattleAI/AttackPossibility.h @@ -0,0 +1,50 @@ +/* + * AttackPossibility.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once +#include "../../lib/BattleState.h" +#include "CCallback.h" +#include "common.h" + + +struct HypotheticChangesToBattleState +{ + std::map bonusesOfStacks; + std::map counterAttacksLeft; +}; + +class Priorities +{ +public: + std::vector resourceTypeBaseValues; + std::function stackEvaluator; + Priorities() + { + // range::copy(VLC->objh->resVals, std::back_inserter(resourceTypeBaseValues)); + stackEvaluator = [](const CStack*){ return 1.0; }; + } +}; + +class AttackPossibility +{ +public: + const CStack *enemy; //redundant (to attack.defender) but looks nice + BattleHex tile; //tile from which we attack + BattleAttackInfo attack; + + int damageDealt; + int damageReceived; //usually by counter-attack + int tacticImpact; + + int damageDiff() const; + int attackValue() const; + + static AttackPossibility evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex); + static Priorities * priorities; +}; diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index 326350801..b43feef8f 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -1,88 +1,47 @@ +/* + * BattleAI.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ #include "StdInc.h" -#include "../../lib/AI_Base.h" #include "BattleAI.h" -#include "../../lib/BattleState.h" -#include "../../CCallback.h" -#include "../../lib/CCreatureHandler.h" +#include "StackWithBonuses.h" +#include "EnemyInfo.h" #include "../../lib/spells/CSpellHandler.h" -#include "../../lib/VCMI_Lib.h" -using boost::optional; -static std::shared_ptr cbc; + + +/* + // + // //set has its own order, so remove_if won't work. TODO - reuse for map + // template + // void erase_if(std::set &setContainer, Predicate pred) + // { + // auto itr = setContainer.begin(); + // auto endItr = setContainer.end(); + // while(itr != endItr) + // { + // auto tmpItr = itr++; + // if(pred(*tmpItr)) + // setContainer.erase(tmpItr); + // } + // } + */ #define LOGL(text) print(text) #define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl)) -struct Priorities -{ - double manaValue; - double generalResourceValueModifier; - std::vector resourceTypeBaseValues; - std::function stackEvaluator; - - - Priorities() - { - manaValue = 0.; - generalResourceValueModifier = 1.; - range::copy(VLC->objh->resVals, std::back_inserter(resourceTypeBaseValues)); - stackEvaluator = [](const CStack*){ return 1.0; }; - } -}; - -Priorities *priorities = nullptr; - - -namespace { - -int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& dists, BattleHex *chosenHex = nullptr) -{ - int ret = 1000000; - for(BattleHex n : hex.neighbouringTiles()) - { - if(dists[n] >= 0 && dists[n] < ret) - { - ret = dists[n]; - if(chosenHex) - *chosenHex = n; - } - } - - return ret; -} - -bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists) -{ - return distToNearestNeighbour(ei1.s->position, dists) < distToNearestNeighbour(ei2.s->position, dists); -} - -} - -template -auto sum(const Container & c, Pred p) -> decltype(p(*std::begin(c))) -{ - double ret = 0; - for(const auto &element : c) - { - ret += p(element); - } - - return ret; -} - - - CBattleAI::CBattleAI(void) : side(-1) { - print("created"); } - CBattleAI::~CBattleAI(void) { - print("destroyed"); - if(cb) { //Restore previous state of CB - it may be shared with the main AI (like VCAI) @@ -93,10 +52,9 @@ CBattleAI::~CBattleAI(void) void CBattleAI::init(std::shared_ptr CB) { - print("init called, saving ptr to IBattleCallback"); - cbc = cb = CB; + setCbc(CB); + cb = CB; playerID = *CB->getPlayerID();; //TODO should be sth in callback - wasWaitingForRealize = cb->waitTillRealize; wasUnlockingGs = CB->unlockGsWhenWaiting; CB->waitTillRealize = true; @@ -106,14 +64,11 @@ void CBattleAI::init(std::shared_ptr CB) BattleAction CBattleAI::activeStack( const CStack * stack ) { LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName()) ; - - cbc = cb; //TODO: make solid sure that AIs always use their callbacks (need to take care of event handlers too) + setCbc(cb); //TODO: make solid sure that AIs always use their callbacks (need to take care of event handlers too) try { - print("activeStack called for " + stack->nodeName()); if(stack->type->idNumber == CreatureID::CATAPULT) return useCatapult(stack); - if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->hasBonusOfType(Bonus::HEALER)) { auto healingTargets = cb->battleGetStacks(CBattleInfoEssentials::ONLY_MINE); @@ -121,7 +76,6 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) for(auto stack : healingTargets) if(auto woundHp = stack->MaxHealth() - stack->firstHPleft) woundHpToStack[woundHp] = stack; - if(woundHpToStack.empty()) return BattleAction::makeDefend(stack); else @@ -130,8 +84,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) if(cb->battleCanCastSpell()) attemptCastingSpell(); - - if(auto ret = cbc->battleIsFinished()) + if(auto ret = getCbc()->battleIsFinished()) { //spellcast may finish battle //send special preudo-action @@ -142,9 +95,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) if(auto action = considerFleeingOrSurrendering()) return *action; - PotentialTargets targets(stack); - if(targets.possibleAttacks.size()) { auto hlp = targets.bestAction(); @@ -157,8 +108,8 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) { if(stack->waited()) { - ThreatMap threatsToUs(stack); - auto dists = cbc->battleGetDistances(stack); + //ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code. + auto dists = getCbc()->battleGetDistances(stack); const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, std::bind(isCloser, _1, _2, std::ref(dists))); if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE) { @@ -175,105 +126,16 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) { logAi->error("Exception occurred in %s %s",__FUNCTION__, e.what()); } - return BattleAction::makeDefend(stack); } -void CBattleAI::actionFinished(const BattleAction &action) -{ - print("actionFinished called"); -} - -void CBattleAI::actionStarted(const BattleAction &action) -{ - print("actionStarted called"); -} - -void CBattleAI::battleAttack(const BattleAttack *ba) -{ - print("battleAttack called"); -} - -void CBattleAI::battleStacksAttacked(const std::vector & bsa) -{ - print("battleStacksAttacked called"); -} - -void CBattleAI::battleEnd(const BattleResult *br) -{ - print("battleEnd called"); -} - -void CBattleAI::battleNewRoundFirst(int round) -{ - print("battleNewRoundFirst called"); -} - -void CBattleAI::battleNewRound(int round) -{ - print("battleNewRound called"); -} - -void CBattleAI::battleStackMoved(const CStack * stack, std::vector dest, int distance) -{ - print("battleStackMoved called");; -} - -void CBattleAI::battleSpellCast(const BattleSpellCast *sc) -{ - print("battleSpellCast called"); -} - -void CBattleAI::battleStacksEffectsSet(const SetStackEffect & sse) -{ - print("battleStacksEffectsSet called"); -} - -void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side) -{ - print("battleStart called"); - side = Side; -} - -void CBattleAI::battleStacksHealedRes(const std::vector > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) -{ - print("battleStacksHealedRes called"); -} - -void CBattleAI::battleNewStackAppeared(const CStack * stack) -{ - print("battleNewStackAppeared called"); -} - -void CBattleAI::battleObstaclesRemoved(const std::set & removedObstacles) -{ - print("battleObstaclesRemoved called"); -} - -void CBattleAI::battleCatapultAttacked(const CatapultAttack & ca) -{ - print("battleCatapultAttacked called"); -} - -void CBattleAI::battleStacksRemoved(const BattleStacksRemoved & bsr) -{ - print("battleStacksRemoved called"); -} - -void CBattleAI::print(const std::string &text) const -{ - logAi->trace("CBattleAI [%p]: %s", this, text); -} - BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination) { assert(destination.isValid()); auto avHexes = cb->battleGetAvailableHexes(stack, false); auto reachability = cb->getReachability(stack); - if(vstd::contains(avHexes, destination)) return BattleAction::makeMove(stack, destination); - auto destNeighbours = destination.neighbouringTiles(); if(vstd::contains_if(destNeighbours, [&](BattleHex n) { return stack->coversPos(destination); })) { @@ -281,15 +143,11 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination) //We shouldn't even be here... return BattleAction::makeDefend(stack); } - vstd::erase_if(destNeighbours, [&](BattleHex hex){ return !reachability.accessibility.accessible(hex, stack); }); - if(!avHexes.size() || !destNeighbours.size()) //we are blocked or dest is blocked { - print("goTowards: Stack cannot move! That's " + stack->nodeName()); return BattleAction::makeDefend(stack); } - if(stack->hasBonusOfType(Bonus::FLYING)) { // Flying stack doesn't go hex by hex, so we can't backtrack using predecessors. @@ -297,13 +155,9 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination) auto distToDestNeighbour = [&](BattleHex hex) -> int { auto nearestNeighbourToHex = vstd::minElementByFun(destNeighbours, [&](BattleHex a) - { - return BattleHex::getDistance(a, hex); - }); - + {return BattleHex::getDistance(a, hex);}); return BattleHex::getDistance(*nearestNeighbourToHex, hex); }; - auto nearestAvailableHex = vstd::minElementByFun(avHexes, distToDestNeighbour); return BattleAction::makeMove(stack, *nearestAvailableHex); } @@ -312,17 +166,14 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination) BattleHex bestNeighbor = destination; if(distToNearestNeighbour(destination, reachability.distances, &bestNeighbor) > GameConstants::BFIELD_SIZE) { - print("goTowards: Cannot reach"); return BattleAction::makeDefend(stack); } - BattleHex currentDest = bestNeighbor; while(1) { assert(currentDest.isValid()); if(vstd::contains(avHexes, currentDest)) return BattleAction::makeMove(stack, currentDest); - currentDest = reachability.predecessors[currentDest]; } } @@ -333,6 +184,7 @@ BattleAction CBattleAI::useCatapult(const CStack * stack) throw std::runtime_error("The method or operation is not implemented."); } + enum SpellTypes { OFFENSIVE_SPELL, TIMED_EFFECT, OTHER @@ -345,74 +197,17 @@ SpellTypes spellType(const CSpell *spell) if (spell->hasEffects()) return TIMED_EFFECT; return OTHER; - } -struct PossibleSpellcast -{ - const CSpell *spell; - BattleHex dest; - si32 value; -}; - -struct CurrentOffensivePotential -{ - std::map ourAttacks; - std::map enemyAttacks; - - CurrentOffensivePotential(ui8 side) - { - for(auto stack : cbc->battleGetStacks()) - { - if(stack->attackerOwned == !side) - ourAttacks[stack] = PotentialTargets(stack); - else - enemyAttacks[stack] = PotentialTargets(stack); - } - } - - int potentialValue() - { - int ourPotential = 0, enemyPotential = 0; - for(auto &p : ourAttacks) - ourPotential += p.second.bestAction().attackValue(); - - for(auto &p : enemyAttacks) - enemyPotential += p.second.bestAction().attackValue(); - - return ourPotential - enemyPotential; - } -}; - - -// -// //set has its own order, so remove_if won't work. TODO - reuse for map -// template -// void erase_if(std::set &setContainer, Predicate pred) -// { -// auto itr = setContainer.begin(); -// auto endItr = setContainer.end(); -// while(itr != endItr) -// { -// auto tmpItr = itr++; -// if(pred(*tmpItr)) -// setContainer.erase(tmpItr); -// } -// } - void CBattleAI::attemptCastingSpell() { LOGL("Casting spells sounds like fun. Let's see..."); - - auto hero = cb->battleGetMyHero(); - - //auto known = cb->battleGetFightingHero(side); - + auto hero = cb->battleGetMyHero(); //auto known = cb->battleGetFightingHero(side); //Get all spells we can cast std::vector possibleSpells; vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this] (const CSpell *s) -> bool { - auto problem = cbc->battleCanCastThisSpell(s); + auto problem = getCbc()->battleCanCastThisSpell(s); return problem == ESpellCastProblem::OK; }); LOGFL("I can cast %d spells.", possibleSpells.size()); @@ -446,74 +241,59 @@ void CBattleAI::attemptCastingSpell() { const int skillLevel = hero->getSpellSchoolLevel(ps.spell); const int spellPower = hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER); - switch(spellType(ps.spell)) { case OFFENSIVE_SPELL: + { + int damageDealt = 0, damageReceived = 0; + auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest); + if(stacksSuffering.empty()) + return -1; + for(auto stack : stacksSuffering) { - int damageDealt = 0, damageReceived = 0; - - auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest); - - if(stacksSuffering.empty()) - return -1; - - for(auto stack : stacksSuffering) - { - const int dmg = ps.spell->calculateDamage(hero, stack, skillLevel, spellPower); - if(stack->owner == playerID) - damageReceived += dmg; - else - damageDealt += dmg; - } - - const int damageDiff = damageDealt - damageReceived * 10; - - LOGFL("Casting %s on hex %d would deal { %d %d } damage points among %d stacks.", - ps.spell->name % ps.dest % damageDealt % damageReceived % stacksSuffering.size()); - //TODO tactic effect too - return damageDiff; + const int dmg = ps.spell->calculateDamage(hero, stack, skillLevel, spellPower); + if(stack->owner == playerID) + damageReceived += dmg; + else + damageDealt += dmg; } + const int damageDiff = damageDealt - damageReceived * 10; + LOGFL("Casting %s on hex %d would deal { %d %d } damage points among %d stacks.", + ps.spell->name % ps.dest % damageDealt % damageReceived % stacksSuffering.size()); + //TODO tactic effect too + return damageDiff; + } case TIMED_EFFECT: + { + auto stacksAffected = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest); + if(stacksAffected.empty()) + return -1; + int totalGain = 0; + for(const CStack * sta : stacksAffected) { - auto stacksAffected = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest); - - if(stacksAffected.empty()) - return -1; - - int totalGain = 0; - - for(const CStack * sta : stacksAffected) - { - StackWithBonuses swb; - swb.stack = sta; - - Bonus pseudoBonus; - pseudoBonus.sid = ps.spell->id; - pseudoBonus.val = skillLevel; - pseudoBonus.turnsRemain = 1; //TODO - CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus); - - HypotheticChangesToBattleState state; - state.bonusesOfStacks[swb.stack] = &swb; - - PotentialTargets pt(swb.stack, state); - auto newValue = pt.bestActionValue(); - auto oldValue = valueOfStack[swb.stack]; - auto gain = newValue - oldValue; - if(swb.stack->owner != playerID) //enemy - gain = -gain; - - LOGFL("Casting %s on %s would improve the stack by %d points (from %d to %d)", - ps.spell->name % sta->nodeName() % (gain) % (oldValue) % (newValue)); - - totalGain += gain; - } - - LOGFL("Total gain of cast %s at hex %d is %d", ps.spell->name % (ps.dest.hex) % (totalGain)); - - return totalGain; + StackWithBonuses swb; + swb.stack = sta; + Bonus pseudoBonus; + pseudoBonus.sid = ps.spell->id; + pseudoBonus.val = skillLevel; + pseudoBonus.turnsRemain = 1; //TODO + CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus); + HypotheticChangesToBattleState state; + state.bonusesOfStacks[swb.stack] = &swb; + PotentialTargets pt(swb.stack, state); + auto newValue = pt.bestActionValue(); + auto oldValue = valueOfStack[swb.stack]; + auto gain = newValue - oldValue; + if(swb.stack->owner != playerID) //enemy + gain = -gain; + LOGFL("Casting %s on %s would improve the stack by %d points (from %d to %d)", + ps.spell->name % sta->nodeName() % (gain) % (oldValue) % (newValue)); + totalGain += gain; } + + LOGFL("Total gain of cast %s at hex %d is %d", ps.spell->name % (ps.dest.hex) % (totalGain)); + return totalGain; + } default: assert(0); return 0; @@ -522,22 +302,18 @@ void CBattleAI::attemptCastingSpell() for(PossibleSpellcast & psc : possibleCasts) psc.value = evaluateSpellcast(psc); - auto pscValue = [] (const PossibleSpellcast &ps) -> int { return ps.value; }; - auto castToPerform = *vstd::maxElementByFun(possibleCasts, pscValue); LOGFL("Best spell is %s. Will cast.", castToPerform.spell->name); - BattleAction spellcast; spellcast.actionType = Battle::HERO_SPELL; spellcast.additionalInfo = castToPerform.spell->id; spellcast.destinationTile = castToPerform.dest; spellcast.side = side; spellcast.stackNumber = (!side) ? -1 : -2; - cb->battleMakeAction(&spellcast); } @@ -545,7 +321,6 @@ std::vector CBattleAI::getTargetsToConsider(const CSpell * spell, con { const CSpell::TargetInfo targetInfo(spell, caster->getSpellSchoolLevel(spell)); std::vector ret; - if(targetInfo.massive || targetInfo.type == CSpell::NO_TARGET) { ret.push_back(BattleHex()); @@ -555,240 +330,86 @@ std::vector CBattleAI::getTargetsToConsider(const CSpell * spell, con switch(targetInfo.type) { case CSpell::CREATURE: + { + for(const CStack * stack : getCbc()->battleAliveStacks()) { - for(const CStack * stack : cbc->battleAliveStacks()) - { - bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack); - bool casterStack = stack->owner == caster->getOwner(); + bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack); + bool casterStack = stack->owner == caster->getOwner(); - if(!immune) - switch (spell->positiveness) - { - case CSpell::POSITIVE: - if(casterStack || targetInfo.smart) - ret.push_back(stack->position); - break; - - case CSpell::NEUTRAL: + if(!immune) + switch (spell->positiveness) + { + case CSpell::POSITIVE: + if(casterStack || targetInfo.smart) ret.push_back(stack->position); - break; - - case CSpell::NEGATIVE: - if(!casterStack || targetInfo.smart) - ret.push_back(stack->position); - break; - } - } + break; + case CSpell::NEUTRAL: + ret.push_back(stack->position); + break; + case CSpell::NEGATIVE: + if(!casterStack || targetInfo.smart) + ret.push_back(stack->position); + break; + } } + } break; case CSpell::LOCATION: - { - for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) - if(BattleHex(i).isAvailable()) - ret.push_back(i); - } + { + for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) + if(BattleHex(i).isAvailable()) + ret.push_back(i); + } break; default: break; } } - return ret; } +int CBattleAI::distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances &dists, BattleHex *chosenHex) +{ + int ret = 1000000; + for(BattleHex n : hex.neighbouringTiles()) + { + if(dists[n] >= 0 && dists[n] < ret) + { + ret = dists[n]; + if(chosenHex) + *chosenHex = n; + } + } + return ret; +} + +void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side) +{ + print("battleStart called"); + side = Side; +} + +bool CBattleAI::isCloser(const EnemyInfo &ei1, const EnemyInfo &ei2, const ReachabilityInfo::TDistances &dists) +{ + return distToNearestNeighbour(ei1.s->position, dists) < distToNearestNeighbour(ei2.s->position, dists); +} + +void CBattleAI::print(const std::string &text) const +{ + logAi->trace("CBattleAI [%p]: %s", this, text); +} + boost::optional CBattleAI::considerFleeingOrSurrendering() { if(cb->battleCanSurrender(playerID)) { - } if(cb->battleCanFlee()) { - } return boost::none; } -ThreatMap::ThreatMap(const CStack *Endangered) : endangered(Endangered) -{ - sufferedDamage.fill(0); - - for(const CStack *enemy : cbc->battleGetStacks()) - { - //Consider only stacks of different owner - if(enemy->attackerOwned == endangered->attackerOwned) - continue; - - //Look-up which tiles can be melee-attacked - std::array meleeAttackable; - meleeAttackable.fill(false); - auto enemyReachability = cbc->getReachability(enemy); - for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) - { - if(enemyReachability.isReachable(i)) - { - meleeAttackable[i] = true; - for(auto n : BattleHex(i).neighbouringTiles()) - meleeAttackable[n] = true; - } - } - - //Gather possible assaults - for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) - { - if(cbc->battleCanShoot(enemy, i)) - threatMap[i].push_back(BattleAttackInfo(enemy, endangered, true)); - else if(meleeAttackable[i]) - { - BattleAttackInfo bai(enemy, endangered, false); - bai.chargedFields = std::max(BattleHex::getDistance(enemy->position, i) - 1, 0); //TODO check real distance (BFS), not just metric - threatMap[i].push_back(BattleAttackInfo(bai)); - } - } - } - - for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) - { - sufferedDamage[i] = sum(threatMap[i], [](const BattleAttackInfo &bai) -> int - { - auto dmg = cbc->calculateDmgRange(bai); - return (dmg.first + dmg.second)/2; - }); - } -} - -const TBonusListPtr StackWithBonuses::getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root /*= nullptr*/, const std::string &cachingStr /*= ""*/) const -{ - TBonusListPtr ret = std::make_shared(); - const TBonusListPtr originalList = stack->getAllBonuses(selector, limit, root, cachingStr); - range::copy(*originalList, std::back_inserter(*ret)); - for(auto &bonus : bonusesToAdd) - { - auto b = std::make_shared(bonus); - if(selector(b.get()) && (!limit || !limit(b.get()))) - ret->push_back(b); - } - - //TODO limiters? - - return ret; -} - -int AttackPossibility::damageDiff() const -{ - if (!priorities) - priorities = new Priorities; - const auto dealtDmgValue = priorities->stackEvaluator(enemy) * damageDealt; - const auto receivedDmgValue = priorities->stackEvaluator(attack.attacker) * damageReceived; - return dealtDmgValue - receivedDmgValue; -} - -int AttackPossibility::attackValue() const -{ - return damageDiff() + tacticImpact; -} - -AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex) -{ - auto attacker = AttackInfo.attacker; - auto enemy = AttackInfo.defender; - - const int remainingCounterAttacks = getValOr(state.counterAttacksLeft, enemy, enemy->counterAttacksRemaining()); - const bool counterAttacksBlocked = attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || enemy->hasBonusOfType(Bonus::NO_RETALIATION); - const int totalAttacks = 1 + AttackInfo.attackerBonuses->getBonuses(Selector::type(Bonus::ADDITIONAL_ATTACK), (Selector::effectRange (Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))))->totalValue(); - - AttackPossibility ap = {enemy, hex, AttackInfo, 0, 0, 0}; - - auto curBai = AttackInfo; //we'll modify here the stack counts - for(int i = 0; i < totalAttacks; i++) - { - std::pair retaliation(0,0); - auto attackDmg = cbc->battleEstimateDamage(CRandomGenerator::getDefault(), curBai, &retaliation); - ap.damageDealt = (attackDmg.first + attackDmg.second) / 2; - ap.damageReceived = (retaliation.first + retaliation.second) / 2; - - if(remainingCounterAttacks <= i || counterAttacksBlocked) - ap.damageReceived = 0; - - curBai.attackerCount = attacker->count - attacker->countKilledByAttack(ap.damageReceived).first; - curBai.defenderCount = enemy->count - enemy->countKilledByAttack(ap.damageDealt).first; - if(!curBai.attackerCount) - break; - //TODO what about defender? should we break? but in pessimistic scenario defender might be alive - } - - //TODO other damage related to attack (eg. fire shield and other abilities) - - //Limit damages by total stack health - vstd::amin(ap.damageDealt, enemy->count * enemy->MaxHealth() - (enemy->MaxHealth() - enemy->firstHPleft)); - vstd::amin(ap.damageReceived, attacker->count * attacker->MaxHealth() - (attacker->MaxHealth() - attacker->firstHPleft)); - - return ap; -} -PotentialTargets::PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state /*= HypotheticChangesToBattleState()*/) -{ - auto dists = cbc->battleGetDistances(attacker); - auto avHexes = cbc->battleGetAvailableHexes(attacker, false); - - for(const CStack *enemy : cbc->battleGetStacks()) - { - //Consider only stacks of different owner - if(enemy->attackerOwned == attacker->attackerOwned) - continue; - - auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility - { - auto bai = BattleAttackInfo(attacker, enemy, shooting); - bai.attackerBonuses = getValOr(state.bonusesOfStacks, bai.attacker, bai.attacker); - bai.defenderBonuses = getValOr(state.bonusesOfStacks, bai.defender, bai.defender); - - if(hex.isValid()) - { - assert(dists[hex] <= attacker->Speed()); - bai.chargedFields = dists[hex]; - } - - return AttackPossibility::evaluate(bai, state, hex); - }; - - if(cbc->battleCanShoot(attacker, enemy->position)) - { - possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID)); - } - else - { - for(BattleHex hex : avHexes) - if(CStack::isMeleeAttackPossible(attacker, enemy, hex)) - possibleAttacks.push_back(GenerateAttackInfo(false, hex)); - - if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility &pa) { return pa.enemy == enemy; })) - unreachableEnemies.push_back(enemy); - } - } -} - -AttackPossibility PotentialTargets::bestAction() const -{ - if(possibleAttacks.empty()) - throw std::runtime_error("No best action, since we don't have any actions"); - - return *vstd::maxElementByFun(possibleAttacks, [](const AttackPossibility &ap) { return ap.attackValue(); } ); -} - -int PotentialTargets::bestActionValue() const -{ - if(possibleAttacks.empty()) - return 0; - - return bestAction().attackValue(); -} - -void EnemyInfo::calcDmg(const CStack * ourStack) -{ - TDmgRange retal, dmg = cbc->battleEstimateDamage(CRandomGenerator::getDefault(), ourStack, s, &retal); - adi = (dmg.first + dmg.second) / 2; - adr = (retal.first + retal.second) / 2; -} diff --git a/AI/BattleAI/BattleAI.h b/AI/BattleAI/BattleAI.h index e3cc42cbb..d16c1efdf 100644 --- a/AI/BattleAI/BattleAI.h +++ b/AI/BattleAI/BattleAI.h @@ -1,110 +1,55 @@ +/* + * BattleAI.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ #pragma once - -#include "../../lib/BattleHex.h" -#include "../../lib/HeroBonus.h" -#include "../../lib/CBattleCallback.h" +#include "../../lib/AI_Base.h" +#include "PotentialTargets.h" class CSpell; +class EnemyInfo; - -class StackWithBonuses : public IBonusBearer +/* +struct CurrentOffensivePotential { -public: - const CStack *stack; - mutable std::vector bonusesToAdd; + std::map ourAttacks; + std::map enemyAttacks; - virtual const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override; -}; - -struct EnemyInfo -{ - const CStack * s; - int adi, adr; - std::vector attackFrom; //for melee fight - EnemyInfo(const CStack * _s) : s(_s) - {} - void calcDmg(const CStack * ourStack); - - bool operator==(const EnemyInfo& ei) const + CurrentOffensivePotential(ui8 side) { - return s == ei.s; + for(auto stack : cbc->battleGetStacks()) + { + if(stack->attackerOwned == !side) + ourAttacks[stack] = PotentialTargets(stack); + else + enemyAttacks[stack] = PotentialTargets(stack); + } + } + + int potentialValue() + { + int ourPotential = 0, enemyPotential = 0; + for(auto &p : ourAttacks) + ourPotential += p.second.bestAction().attackValue(); + + for(auto &p : enemyAttacks) + enemyPotential += p.second.bestAction().attackValue(); + + return ourPotential - enemyPotential; } }; +*/ // These lines may be usefull but they are't used in the code. - -//FIXME: unused function -/* -static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const BattleHex &h2) +struct PossibleSpellcast { - int shooters[2] = {0}; //count of shooters on hexes - - for(int i = 0; i < 2; i++) - BOOST_FOREACH(BattleHex neighbour, (i ? h2 : h1).neighbouringTiles()) - if(const CStack *s = cbc->battleGetStackByPos(neighbour)) - if(s->getCreature()->isShooting()) - shooters[i]++; - - return shooters[0] < shooters[1]; -} -*/ - - - -struct ThreatMap -{ - std::array, GameConstants::BFIELD_SIZE> threatMap; // [hexNr] -> enemies able to strike - - const CStack *endangered; - std::array sufferedDamage; - - ThreatMap(const CStack *Endangered); -}; - -struct HypotheticChangesToBattleState -{ - std::map bonusesOfStacks; - std::map counterAttacksLeft; -}; - -struct AttackPossibility -{ - const CStack *enemy; //redundant (to attack.defender) but looks nice - BattleHex tile; //tile from which we attack - BattleAttackInfo attack; - - int damageDealt; - int damageReceived; //usually by counter-attack - int tacticImpact; - - int damageDiff() const; - int attackValue() const; - - static AttackPossibility evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex); -}; - -template -const Val getValOr(const std::map &Map, const Key &key, const Val2 defaultValue) -{ - //returning references here won't work: defaultValue must be converted into Val, creating temporary - auto i = Map.find(key); - if(i != Map.end()) - return i->second; - else - return defaultValue; -} - -struct PotentialTargets -{ - std::vector possibleAttacks; - std::vector unreachableEnemies; - - //std::function GenerateAttackInfo; //args: shooting, destHex - - PotentialTargets(){}; - PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state = HypotheticChangesToBattleState()); - - AttackPossibility bestAction() const; - int bestActionValue() const; + const CSpell *spell; + BattleHex dest; + si32 value; }; class CBattleAI : public CBattleGameInterface @@ -115,39 +60,41 @@ class CBattleAI : public CBattleGameInterface //Previous setting of cb bool wasWaitingForRealize, wasUnlockingGs; - void print(const std::string &text) const; public: CBattleAI(void); ~CBattleAI(void); void init(std::shared_ptr CB) override; - void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero - void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero + void attemptCastingSpell(); + BattleAction activeStack(const CStack * stack) override; //called when it's turn of that stack - - void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack - void battleStacksAttacked(const std::vector & bsa) override; //called when stack receives damage (after battleAttack()) - void battleEnd(const BattleResult *br) override; - //void battleResultsApplied() override; //called when all effects of last battle are applied - void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied; - void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn - void battleStackMoved(const CStack * stack, std::vector dest, int distance) override; - void battleSpellCast(const BattleSpellCast *sc) override; - void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks - //void battleTriggerEffect(const BattleTriggerEffect & bte) override; - void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right - void battleStacksHealedRes(const std::vector > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; //called when stacks are healed / resurrected first element of pair - stack id, second - healed hp - void battleNewStackAppeared(const CStack * stack) override; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned - void battleObstaclesRemoved(const std::set & removedObstacles) override; //called when a certain set of obstacles is removed from batlefield; IDs of them are given - void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack - void battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield - BattleAction goTowards(const CStack * stack, BattleHex hex ); - BattleAction useCatapult(const CStack * stack); boost::optional considerFleeingOrSurrendering(); - void attemptCastingSpell(); std::vector getTargetsToConsider(const CSpell *spell, const ISpellCaster * caster) const; -}; + static int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& dists, BattleHex *chosenHex = nullptr); + static bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists); + void print(const std::string &text) const; + BattleAction useCatapult(const CStack *stack); + void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool Side); + //void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero + //void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero + //void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack + //void battleStacksAttacked(const std::vector & bsa) override; //called when stack receives damage (after battleAttack()) + //void battleEnd(const BattleResult *br) override; + //void battleResultsApplied() override; //called when all effects of last battle are applied + //void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied; + //void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn + //void battleStackMoved(const CStack * stack, std::vector dest, int distance) override; + //void battleSpellCast(const BattleSpellCast *sc) override; + //void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks + //void battleTriggerEffect(const BattleTriggerEffect & bte) override; + //void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right + //void battleStacksHealedRes(const std::vector > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; //called when stacks are healed / resurrected first element of pair - stack id, second - healed hp + //void battleNewStackAppeared(const CStack * stack) override; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned + //void battleObstaclesRemoved(const std::set & removedObstacles) override; //called when a certain set of obstacles is removed from batlefield; IDs of them are given + //void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack + //void battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield +}; diff --git a/AI/BattleAI/CMakeLists.txt b/AI/BattleAI/CMakeLists.txt index c1697e5f5..17a123541 100644 --- a/AI/BattleAI/CMakeLists.txt +++ b/AI/BattleAI/CMakeLists.txt @@ -6,7 +6,13 @@ include_directories(${Boost_INCLUDE_DIRS} ${CMAKE_HOME_DIRECTORY} ${CMAKE_HOME_D set(battleAI_SRCS StdInc.cpp BattleAI.cpp + StackWithBonuses.cpp + EnemyInfo.cpp + AttackPossibility.cpp + PotentialTargets.cpp main.cpp + common.cpp + ThreatMap.cpp ) add_library(BattleAI SHARED ${battleAI_SRCS}) @@ -16,6 +22,6 @@ set_target_properties(BattleAI PROPERTIES ${PCH_PROPERTIES}) cotire(BattleAI) if (NOT APPLE) # Already inside vcmiclient bundle - install(TARGETS BattleAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR}) + install(TARGETS BattleAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR}) endif() diff --git a/AI/BattleAI/EnemyInfo.cpp b/AI/BattleAI/EnemyInfo.cpp new file mode 100644 index 000000000..404e61f24 --- /dev/null +++ b/AI/BattleAI/EnemyInfo.cpp @@ -0,0 +1,21 @@ +/* + * EnemyInfo.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "EnemyInfo.h" +#include "CRandomGenerator.h" +#include "CCallback.h" +#include "common.h" + +void EnemyInfo::calcDmg(const CStack * ourStack) +{ + TDmgRange retal, dmg = getCbc()->battleEstimateDamage(CRandomGenerator::getDefault(), ourStack, s, &retal); + adi = (dmg.first + dmg.second) / 2; + adr = (retal.first + retal.second) / 2; +} diff --git a/AI/BattleAI/EnemyInfo.h b/AI/BattleAI/EnemyInfo.h new file mode 100644 index 000000000..085dd793c --- /dev/null +++ b/AI/BattleAI/EnemyInfo.h @@ -0,0 +1,28 @@ +/* + * EnemyInfo.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once +#include "BattleHex.h" + +class CStack; + +class EnemyInfo +{ +public: + const CStack * s; + int adi, adr; + std::vector attackFrom; //for melee fight + EnemyInfo(const CStack * _s) : s(_s) + {} + void calcDmg(const CStack * ourStack); + bool operator==(const EnemyInfo& ei) const + { + return s == ei.s; + } +}; diff --git a/AI/BattleAI/PotentialTargets.cpp b/AI/BattleAI/PotentialTargets.cpp new file mode 100644 index 000000000..669de85e3 --- /dev/null +++ b/AI/BattleAI/PotentialTargets.cpp @@ -0,0 +1,71 @@ +/* + * PotentialTargets.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "PotentialTargets.h" + +PotentialTargets::PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state /*= HypotheticChangesToBattleState()*/) +{ + auto dists = getCbc()->battleGetDistances(attacker); + auto avHexes = getCbc()->battleGetAvailableHexes(attacker, false); + + for(const CStack *enemy : getCbc()->battleGetStacks()) + { + //Consider only stacks of different owner + if(enemy->attackerOwned == attacker->attackerOwned) + continue; + + auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility + { + auto bai = BattleAttackInfo(attacker, enemy, shooting); + bai.attackerBonuses = getValOr(state.bonusesOfStacks, bai.attacker, bai.attacker); + bai.defenderBonuses = getValOr(state.bonusesOfStacks, bai.defender, bai.defender); + + if(hex.isValid()) + { + assert(dists[hex] <= attacker->Speed()); + bai.chargedFields = dists[hex]; + } + + return AttackPossibility::evaluate(bai, state, hex); + }; + + if(getCbc()->battleCanShoot(attacker, enemy->position)) + { + possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID)); + } + else + { + for(BattleHex hex : avHexes) + if(CStack::isMeleeAttackPossible(attacker, enemy, hex)) + possibleAttacks.push_back(GenerateAttackInfo(false, hex)); + + if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility &pa) { return pa.enemy == enemy; })) + unreachableEnemies.push_back(enemy); + } + } +} + + + +int PotentialTargets::bestActionValue() const +{ + if(possibleAttacks.empty()) + return 0; + + return bestAction().attackValue(); +} + +AttackPossibility PotentialTargets::bestAction() const +{ + if(possibleAttacks.empty()) + throw std::runtime_error("No best action, since we don't have any actions"); + + return *vstd::maxElementByFun(possibleAttacks, [](const AttackPossibility &ap) { return ap.attackValue(); } ); +} diff --git a/AI/BattleAI/PotentialTargets.h b/AI/BattleAI/PotentialTargets.h new file mode 100644 index 000000000..27c10799a --- /dev/null +++ b/AI/BattleAI/PotentialTargets.h @@ -0,0 +1,26 @@ +/* + * PotentialTargets.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once +#include "AttackPossibility.h" + +class PotentialTargets +{ +public: + std::vector possibleAttacks; + std::vector unreachableEnemies; + + //std::function GenerateAttackInfo; //args: shooting, destHex + + PotentialTargets(){}; + PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state = HypotheticChangesToBattleState()); + + AttackPossibility bestAction() const; + int bestActionValue() const; +}; diff --git a/AI/BattleAI/StackWithBonuses.cpp b/AI/BattleAI/StackWithBonuses.cpp new file mode 100644 index 000000000..ad706eeab --- /dev/null +++ b/AI/BattleAI/StackWithBonuses.cpp @@ -0,0 +1,28 @@ +/* + * StackWithBonuses.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "StackWithBonuses.h" +#include "../../lib/BattleState.h" + +const TBonusListPtr StackWithBonuses::getAllBonuses(const CSelector &selector, const CSelector &limit, + const CBonusSystemNode *root /*= nullptr*/, const std::string &cachingStr /*= ""*/) const +{ + TBonusListPtr ret = std::make_shared(); + const TBonusListPtr originalList = stack->getAllBonuses(selector, limit, root, cachingStr); + range::copy(*originalList, std::back_inserter(*ret)); + for(auto &bonus : bonusesToAdd) + { + auto b = std::make_shared(bonus); + if(selector(b.get()) && (!limit || !limit(b.get()))) + ret->push_back(b); + } + //TODO limiters? + return ret; +} diff --git a/AI/BattleAI/StackWithBonuses.h b/AI/BattleAI/StackWithBonuses.h new file mode 100644 index 000000000..e68e56433 --- /dev/null +++ b/AI/BattleAI/StackWithBonuses.h @@ -0,0 +1,23 @@ +/* + * StackWithBonuses.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once +#include "../../lib/HeroBonus.h" + +class CStack; + +class StackWithBonuses : public IBonusBearer +{ +public: + const CStack *stack; + mutable std::vector bonusesToAdd; + + virtual const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, + const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override; +}; diff --git a/AI/BattleAI/StdInc.cpp b/AI/BattleAI/StdInc.cpp index f500fe6d0..a284091d2 100644 --- a/AI/BattleAI/StdInc.cpp +++ b/AI/BattleAI/StdInc.cpp @@ -1,2 +1,11 @@ +/* + * StdInc.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ // Creates the precompiled header -#include "StdInc.h" +#include "StdInc.h" diff --git a/AI/BattleAI/StdInc.h b/AI/BattleAI/StdInc.h index 81a6cb308..1e6eda607 100644 --- a/AI/BattleAI/StdInc.h +++ b/AI/BattleAI/StdInc.h @@ -1,7 +1,15 @@ +/* + * StdInc.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ #pragma once - #include "../../Global.h" // This header should be treated as a pre compiled header file(PCH) in the compiler building settings. -// Here you can add specific libraries and macros which are specific to this project. \ No newline at end of file +// Here you can add specific libraries and macros which are specific to this project. diff --git a/AI/BattleAI/ThreatMap.cpp b/AI/BattleAI/ThreatMap.cpp new file mode 100644 index 000000000..443467a3b --- /dev/null +++ b/AI/BattleAI/ThreatMap.cpp @@ -0,0 +1,72 @@ +/* + * ThreatMap.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +/* +#include "ThreatMap.h" +#include "StdInc.h" + +template +auto sum(const Container & c, Pred p) -> decltype(p(*std::begin(c))) +{ + double ret = 0; + for(const auto &element : c) + { + ret += p(element); + } + + return ret; +} +ThreatMap::ThreatMap(const CStack *Endangered) : endangered(Endangered) +{ + sufferedDamage.fill(0); + + for(const CStack *enemy : getCbc()->battleGetStacks()) + { + //Consider only stacks of different owner + if(enemy->attackerOwned == endangered->attackerOwned) + continue; + + //Look-up which tiles can be melee-attacked + std::array meleeAttackable; + meleeAttackable.fill(false); + auto enemyReachability = getCbc()->getReachability(enemy); + for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) + { + if(enemyReachability.isReachable(i)) + { + meleeAttackable[i] = true; + for(auto n : BattleHex(i).neighbouringTiles()) + meleeAttackable[n] = true; + } + } + + //Gather possible assaults + for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) + { + if(getCbc()->battleCanShoot(enemy, i)) + threatMap[i].push_back(BattleAttackInfo(enemy, endangered, true)); + else if(meleeAttackable[i]) + { + BattleAttackInfo bai(enemy, endangered, false); + bai.chargedFields = std::max(BattleHex::getDistance(enemy->position, i) - 1, 0); //TODO check real distance (BFS), not just metric + threatMap[i].push_back(BattleAttackInfo(bai)); + } + } + } + + for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) + { + sufferedDamage[i] = sum(threatMap[i], [](const BattleAttackInfo &bai) -> int + { + auto dmg = getCbc()->calculateDmgRange(bai); + return (dmg.first + dmg.second)/2; + }); + } +} +*/ // These lines may be usefull but they are't used in the code. diff --git a/AI/BattleAI/ThreatMap.h b/AI/BattleAI/ThreatMap.h new file mode 100644 index 000000000..bd5df698c --- /dev/null +++ b/AI/BattleAI/ThreatMap.h @@ -0,0 +1,26 @@ +/* + * ThreatMap.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +/* +#pragma once + +#include "common.h" +#include "../../lib/BattleState.h" +#include "CCallback.h" +/* +class ThreatMap +{ +public: + std::array, GameConstants::BFIELD_SIZE> threatMap; // [hexNr] -> enemies able to strike + + const CStack *endangered; + std::array sufferedDamage; + + ThreatMap(const CStack *Endangered); +};*/ // These lines may be usefull but they are't used in the code. diff --git a/AI/BattleAI/common.cpp b/AI/BattleAI/common.cpp new file mode 100644 index 000000000..4a467c8cc --- /dev/null +++ b/AI/BattleAI/common.cpp @@ -0,0 +1,23 @@ +/* + * common.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "common.h" + +std::shared_ptr cbc; + +void setCbc(std::shared_ptr cb) +{ + cbc = cb; +} + +std::shared_ptr getCbc() +{ + return cbc; +} diff --git a/AI/BattleAI/common.h b/AI/BattleAI/common.h new file mode 100644 index 000000000..dfc9b4623 --- /dev/null +++ b/AI/BattleAI/common.h @@ -0,0 +1,26 @@ +/* + * common.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +class CBattleCallback; + +template +const Val getValOr(const std::map &Map, const Key &key, const Val2 defaultValue) +{ + //returning references here won't work: defaultValue must be converted into Val, creating temporary + auto i = Map.find(key); + if(i != Map.end()) + return i->second; + else + return defaultValue; +} + +void setCbc(std::shared_ptr cb); +std::shared_ptr getCbc(); diff --git a/AI/BattleAI/main.cpp b/AI/BattleAI/main.cpp index b174fe02c..9c1f08a58 100644 --- a/AI/BattleAI/main.cpp +++ b/AI/BattleAI/main.cpp @@ -1,5 +1,13 @@ +/* + * main.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ #include "StdInc.h" - #include "../../lib/AI_Base.h" #include "BattleAI.h" diff --git a/AUTHORS b/AUTHORS index 67548af38..9b55c43ef 100644 --- a/AUTHORS +++ b/AUTHORS @@ -57,3 +57,6 @@ Arseniy Shestakov aka SXX, Vadim Markovtsev, * resolving problems with macOS, bug fixes + +MichaƂ Kalinowski, + * refactoring code