mirror of
https://github.com/vcmi/vcmi.git
synced 2025-04-21 12:06:49 +02:00
Merge remote-tracking branch 'origin/develop' into fix_rmg_teams
# Conflicts: # client/lobby/RandomMapTab.cpp
This commit is contained in:
commit
36911d1e0a
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
*.json linguist-language=JSON-with-Comments
|
||||||
|
*.h linguist-language=C++
|
||||||
|
*.cpp linguist-language=C++
|
24
.github/workflows/github.yml
vendored
24
.github/workflows/github.yml
vendored
@ -103,7 +103,7 @@ jobs:
|
|||||||
test: 0
|
test: 0
|
||||||
pack: 1
|
pack: 1
|
||||||
extension: ipa
|
extension: ipa
|
||||||
preset: ios-release-conan
|
preset: ios-release-conan-ccache
|
||||||
conan_profile: ios-arm64
|
conan_profile: ios-arm64
|
||||||
conan_options: --options with_apple_system_libs=True
|
conan_options: --options with_apple_system_libs=True
|
||||||
- platform: msvc
|
- platform: msvc
|
||||||
@ -111,7 +111,7 @@ jobs:
|
|||||||
test: 0
|
test: 0
|
||||||
pack: 1
|
pack: 1
|
||||||
extension: exe
|
extension: exe
|
||||||
preset: windows-msvc-release
|
preset: windows-msvc-release-ccache
|
||||||
- platform: mingw-ubuntu
|
- platform: mingw-ubuntu
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
test: 0
|
test: 0
|
||||||
@ -145,11 +145,27 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Validate JSON
|
||||||
|
# the Python yaml module doesn't seem to work on mac-arm
|
||||||
|
# also, running it on multiple presets is redundant and slightly increases already long CI built times
|
||||||
|
if: ${{ startsWith(matrix.preset, 'linux-clang-test') }}
|
||||||
|
run: |
|
||||||
|
pip3 install json5 jstyleson
|
||||||
|
python3 CI/linux-qt6/validate_json.py
|
||||||
|
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
run: source '${{github.workspace}}/CI/${{matrix.platform}}/before_install.sh'
|
run: source '${{github.workspace}}/CI/${{matrix.platform}}/before_install.sh'
|
||||||
env:
|
env:
|
||||||
VCMI_BUILD_PLATFORM: x64
|
VCMI_BUILD_PLATFORM: x64
|
||||||
|
|
||||||
|
- name: ccache
|
||||||
|
uses: hendrikmuhs/ccache-action@v1.2
|
||||||
|
with:
|
||||||
|
key: ${{ matrix.preset }}
|
||||||
|
# actual cache takes up less space, at most ~1 GB
|
||||||
|
max-size: "5G"
|
||||||
|
verbose: 2
|
||||||
|
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
if: "${{ matrix.conan_profile != '' }}"
|
if: "${{ matrix.conan_profile != '' }}"
|
||||||
with:
|
with:
|
||||||
@ -185,9 +201,9 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
PULL_REQUEST: ${{ github.event.pull_request.number }}
|
PULL_REQUEST: ${{ github.event.pull_request.number }}
|
||||||
|
|
||||||
- name: CMake Preset
|
- name: CMake Preset with ccache
|
||||||
run: |
|
run: |
|
||||||
cmake --preset ${{ matrix.preset }}
|
cmake -DCMAKE_CXX_COMPILER_LAUNCHER=ccache --preset ${{ matrix.preset }}
|
||||||
|
|
||||||
- name: Build Preset
|
- name: Build Preset
|
||||||
run: |
|
run: |
|
||||||
|
@ -18,17 +18,98 @@ uint64_t averageDmg(const DamageRange & range)
|
|||||||
return (range.min + range.max) / 2;
|
return (range.min + range.max) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DamageCache::cacheDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb)
|
||||||
|
{
|
||||||
|
auto damage = averageDmg(hb->battleEstimateDamage(attacker, defender, 0).damage);
|
||||||
|
|
||||||
|
damageCache[attacker->unitId()][defender->unitId()] = static_cast<float>(damage) / attacker->getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DamageCache::buildDamageCache(std::shared_ptr<HypotheticBattle> hb, int side)
|
||||||
|
{
|
||||||
|
auto stacks = hb->battleGetUnitsIf([=](const battle::Unit * u) -> bool
|
||||||
|
{
|
||||||
|
return u->isValidTarget();
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<const battle::Unit *> ourUnits, enemyUnits;
|
||||||
|
|
||||||
|
for(auto stack : stacks)
|
||||||
|
{
|
||||||
|
if(stack->unitSide() == side)
|
||||||
|
ourUnits.push_back(stack);
|
||||||
|
else
|
||||||
|
enemyUnits.push_back(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto ourUnit : ourUnits)
|
||||||
|
{
|
||||||
|
if(!ourUnit->alive())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for(auto enemyUnit : enemyUnits)
|
||||||
|
{
|
||||||
|
if(enemyUnit->alive())
|
||||||
|
{
|
||||||
|
cacheDamage(ourUnit, enemyUnit, hb);
|
||||||
|
cacheDamage(enemyUnit, ourUnit, hb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t DamageCache::getDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb)
|
||||||
|
{
|
||||||
|
auto damage = damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount();
|
||||||
|
|
||||||
|
if(damage == 0)
|
||||||
|
{
|
||||||
|
cacheDamage(attacker, defender, hb);
|
||||||
|
|
||||||
|
damage = damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<int64_t>(damage);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t DamageCache::getOriginalDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb)
|
||||||
|
{
|
||||||
|
if(parent)
|
||||||
|
{
|
||||||
|
auto attackerDamageMap = parent->damageCache.find(attacker->unitId());
|
||||||
|
|
||||||
|
if(attackerDamageMap != parent->damageCache.end())
|
||||||
|
{
|
||||||
|
auto targetDamage = attackerDamageMap->second.find(defender->unitId());
|
||||||
|
|
||||||
|
if(targetDamage != attackerDamageMap->second.end())
|
||||||
|
{
|
||||||
|
return static_cast<int64_t>(targetDamage->second * attacker->getCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDamage(attacker, defender, hb);
|
||||||
|
}
|
||||||
|
|
||||||
AttackPossibility::AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack)
|
AttackPossibility::AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack)
|
||||||
: from(from), dest(dest), attack(attack)
|
: from(from), dest(dest), attack(attack)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t AttackPossibility::damageDiff() const
|
float AttackPossibility::damageDiff() const
|
||||||
{
|
{
|
||||||
return defenderDamageReduce - attackerDamageReduce - collateralDamageReduce + shootersBlockedDmg;
|
return defenderDamageReduce - attackerDamageReduce - collateralDamageReduce + shootersBlockedDmg;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t AttackPossibility::attackValue() const
|
float AttackPossibility::damageDiff(float positiveEffectMultiplier, float negativeEffectMultiplier) const
|
||||||
|
{
|
||||||
|
return positiveEffectMultiplier * (defenderDamageReduce + shootersBlockedDmg)
|
||||||
|
- negativeEffectMultiplier * (attackerDamageReduce + collateralDamageReduce);
|
||||||
|
}
|
||||||
|
|
||||||
|
float AttackPossibility::attackValue() const
|
||||||
{
|
{
|
||||||
return damageDiff();
|
return damageDiff();
|
||||||
}
|
}
|
||||||
@ -38,25 +119,28 @@ int64_t AttackPossibility::attackValue() const
|
|||||||
/// Half bounty for kill, half for making damage equal to enemy health
|
/// Half bounty for kill, half for making damage equal to enemy health
|
||||||
/// Bounty - the killed creature average damage calculated against attacker
|
/// Bounty - the killed creature average damage calculated against attacker
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int64_t AttackPossibility::calculateDamageReduce(
|
float AttackPossibility::calculateDamageReduce(
|
||||||
const battle::Unit * attacker,
|
const battle::Unit * attacker,
|
||||||
const battle::Unit * defender,
|
const battle::Unit * defender,
|
||||||
uint64_t damageDealt,
|
uint64_t damageDealt,
|
||||||
const CBattleInfoCallback & cb)
|
DamageCache & damageCache,
|
||||||
|
std::shared_ptr<CBattleInfoCallback> state)
|
||||||
{
|
{
|
||||||
const float HEALTH_BOUNTY = 0.5;
|
const float HEALTH_BOUNTY = 0.5;
|
||||||
const float KILL_BOUNTY = 1.0 - HEALTH_BOUNTY;
|
|
||||||
|
|
||||||
vstd::amin(damageDealt, defender->getAvailableHealth());
|
|
||||||
|
|
||||||
// FIXME: provide distance info for Jousting bonus
|
// FIXME: provide distance info for Jousting bonus
|
||||||
auto attackerUnitForMeasurement = attacker;
|
auto attackerUnitForMeasurement = attacker;
|
||||||
|
|
||||||
if(attackerUnitForMeasurement->isTurret())
|
if(!attackerUnitForMeasurement || attackerUnitForMeasurement->isTurret())
|
||||||
{
|
{
|
||||||
auto ourUnits = cb.battleGetUnitsIf([&](const battle::Unit * u) -> bool
|
auto ourUnits = state->battleGetUnitsIf([&](const battle::Unit * u) -> bool
|
||||||
{
|
{
|
||||||
return u->unitSide() == attacker->unitSide() && !u->isTurret();
|
return u->unitSide() != defender->unitSide()
|
||||||
|
&& !u->isTurret()
|
||||||
|
&& u->creatureId() != CreatureID::CATAPULT
|
||||||
|
&& u->creatureId() != CreatureID::BALLISTA
|
||||||
|
&& u->creatureId() != CreatureID::FIRST_AID_TENT
|
||||||
|
&& u->getCount();
|
||||||
});
|
});
|
||||||
|
|
||||||
if(ourUnits.empty())
|
if(ourUnits.empty())
|
||||||
@ -65,15 +149,28 @@ int64_t AttackPossibility::calculateDamageReduce(
|
|||||||
attackerUnitForMeasurement = ourUnits.front();
|
attackerUnitForMeasurement = ourUnits.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto enemyDamageBeforeAttack = cb.battleEstimateDamage(defender, attackerUnitForMeasurement, 0);
|
auto maxHealth = defender->getMaxHealth();
|
||||||
auto enemiesKilled = damageDealt / defender->getMaxHealth() + (damageDealt % defender->getMaxHealth() >= defender->getFirstHPleft() ? 1 : 0);
|
auto availableHealth = defender->getFirstHPleft() + ((defender->getCount() - 1) * maxHealth);
|
||||||
auto enemyDamage = averageDmg(enemyDamageBeforeAttack.damage);
|
|
||||||
auto damagePerEnemy = enemyDamage / (double)defender->getCount();
|
|
||||||
|
|
||||||
return (int64_t)(damagePerEnemy * (enemiesKilled * KILL_BOUNTY + damageDealt * HEALTH_BOUNTY / (double)defender->getMaxHealth()));
|
vstd::amin(damageDealt, availableHealth);
|
||||||
|
|
||||||
|
auto enemyDamageBeforeAttack = damageCache.getOriginalDamage(defender, attackerUnitForMeasurement, state);
|
||||||
|
auto enemiesKilled = damageDealt / maxHealth + (damageDealt % maxHealth >= defender->getFirstHPleft() ? 1 : 0);
|
||||||
|
auto damagePerEnemy = enemyDamageBeforeAttack / (double)defender->getCount();
|
||||||
|
|
||||||
|
// lets use cached maxHealth here instead of getAvailableHealth
|
||||||
|
auto firstUnitHpLeft = (availableHealth - damageDealt) % maxHealth;
|
||||||
|
auto firstUnitHealthRatio = firstUnitHpLeft == 0 ? 1 : static_cast<float>(firstUnitHpLeft) / maxHealth;
|
||||||
|
auto firstUnitKillValue = (1 - firstUnitHealthRatio) * (1 - firstUnitHealthRatio);
|
||||||
|
|
||||||
|
return damagePerEnemy * (enemiesKilled + firstUnitKillValue * HEALTH_BOUNTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state)
|
int64_t AttackPossibility::evaluateBlockedShootersDmg(
|
||||||
|
const BattleAttackInfo & attackInfo,
|
||||||
|
BattleHex hex,
|
||||||
|
DamageCache & damageCache,
|
||||||
|
std::shared_ptr<CBattleInfoCallback> state)
|
||||||
{
|
{
|
||||||
int64_t res = 0;
|
int64_t res = 0;
|
||||||
|
|
||||||
@ -84,10 +181,10 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & a
|
|||||||
auto hexes = attacker->getSurroundingHexes(hex);
|
auto hexes = attacker->getSurroundingHexes(hex);
|
||||||
for(BattleHex tile : hexes)
|
for(BattleHex tile : hexes)
|
||||||
{
|
{
|
||||||
auto st = state.battleGetUnitByPos(tile, true);
|
auto st = state->battleGetUnitByPos(tile, true);
|
||||||
if(!st || !state.battleMatchOwner(st, attacker))
|
if(!st || !state->battleMatchOwner(st, attacker))
|
||||||
continue;
|
continue;
|
||||||
if(!state.battleCanShoot(st))
|
if(!state->battleCanShoot(st))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// FIXME: provide distance info for Jousting bonus
|
// FIXME: provide distance info for Jousting bonus
|
||||||
@ -97,8 +194,8 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & a
|
|||||||
BattleAttackInfo meleeAttackInfo(st, attacker, 0, false);
|
BattleAttackInfo meleeAttackInfo(st, attacker, 0, false);
|
||||||
meleeAttackInfo.defenderPos = hex;
|
meleeAttackInfo.defenderPos = hex;
|
||||||
|
|
||||||
auto rangeDmg = state.battleEstimateDamage(rangeAttackInfo);
|
auto rangeDmg = state->battleEstimateDamage(rangeAttackInfo);
|
||||||
auto meleeDmg = state.battleEstimateDamage(meleeAttackInfo);
|
auto meleeDmg = state->battleEstimateDamage(meleeAttackInfo);
|
||||||
|
|
||||||
int64_t gain = averageDmg(rangeDmg.damage) - averageDmg(meleeDmg.damage) + 1;
|
int64_t gain = averageDmg(rangeDmg.damage) - averageDmg(meleeDmg.damage) + 1;
|
||||||
res += gain;
|
res += gain;
|
||||||
@ -107,13 +204,17 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & a
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state)
|
AttackPossibility AttackPossibility::evaluate(
|
||||||
|
const BattleAttackInfo & attackInfo,
|
||||||
|
BattleHex hex,
|
||||||
|
DamageCache & damageCache,
|
||||||
|
std::shared_ptr<CBattleInfoCallback> state)
|
||||||
{
|
{
|
||||||
auto attacker = attackInfo.attacker;
|
auto attacker = attackInfo.attacker;
|
||||||
auto defender = attackInfo.defender;
|
auto defender = attackInfo.defender;
|
||||||
const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
|
const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
|
||||||
static const auto selectorBlocksRetaliation = Selector::type()(BonusType::BLOCKS_RETALIATION);
|
static const auto selectorBlocksRetaliation = Selector::type()(BonusType::BLOCKS_RETALIATION);
|
||||||
const auto attackerSide = state.playerToSide(state.battleGetOwner(attacker));
|
const auto attackerSide = state->playerToSide(state->battleGetOwner(attacker));
|
||||||
const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
|
const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
|
||||||
|
|
||||||
AttackPossibility bestAp(hex, BattleHex::INVALID, attackInfo);
|
AttackPossibility bestAp(hex, BattleHex::INVALID, attackInfo);
|
||||||
@ -141,9 +242,9 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf
|
|||||||
std::vector<const battle::Unit*> units;
|
std::vector<const battle::Unit*> units;
|
||||||
|
|
||||||
if (attackInfo.shooting)
|
if (attackInfo.shooting)
|
||||||
units = state.getAttackedBattleUnits(attacker, defHex, true, BattleHex::INVALID);
|
units = state->getAttackedBattleUnits(attacker, defHex, true, BattleHex::INVALID);
|
||||||
else
|
else
|
||||||
units = state.getAttackedBattleUnits(attacker, defHex, false, hex);
|
units = state->getAttackedBattleUnits(attacker, defHex, false, hex);
|
||||||
|
|
||||||
// ensure the defender is also affected
|
// ensure the defender is also affected
|
||||||
bool addDefender = true;
|
bool addDefender = true;
|
||||||
@ -169,10 +270,11 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf
|
|||||||
|
|
||||||
for(int i = 0; i < totalAttacks; i++)
|
for(int i = 0; i < totalAttacks; i++)
|
||||||
{
|
{
|
||||||
int64_t damageDealt, damageReceived, defenderDamageReduce, attackerDamageReduce;
|
int64_t damageDealt, damageReceived;
|
||||||
|
float defenderDamageReduce, attackerDamageReduce;
|
||||||
|
|
||||||
DamageEstimation retaliation;
|
DamageEstimation retaliation;
|
||||||
auto attackDmg = state.battleEstimateDamage(ap.attack, &retaliation);
|
auto attackDmg = state->battleEstimateDamage(ap.attack, &retaliation);
|
||||||
|
|
||||||
vstd::amin(attackDmg.damage.min, defenderState->getAvailableHealth());
|
vstd::amin(attackDmg.damage.min, defenderState->getAvailableHealth());
|
||||||
vstd::amin(attackDmg.damage.max, defenderState->getAvailableHealth());
|
vstd::amin(attackDmg.damage.max, defenderState->getAvailableHealth());
|
||||||
@ -181,7 +283,7 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf
|
|||||||
vstd::amin(retaliation.damage.max, ap.attackerState->getAvailableHealth());
|
vstd::amin(retaliation.damage.max, ap.attackerState->getAvailableHealth());
|
||||||
|
|
||||||
damageDealt = averageDmg(attackDmg.damage);
|
damageDealt = averageDmg(attackDmg.damage);
|
||||||
defenderDamageReduce = calculateDamageReduce(attacker, defender, damageDealt, state);
|
defenderDamageReduce = calculateDamageReduce(attacker, defender, damageDealt, damageCache, state);
|
||||||
ap.attackerState->afterAttack(attackInfo.shooting, false);
|
ap.attackerState->afterAttack(attackInfo.shooting, false);
|
||||||
|
|
||||||
//FIXME: use ranged retaliation
|
//FIXME: use ranged retaliation
|
||||||
@ -191,11 +293,11 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf
|
|||||||
if (!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked)
|
if (!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked)
|
||||||
{
|
{
|
||||||
damageReceived = averageDmg(retaliation.damage);
|
damageReceived = averageDmg(retaliation.damage);
|
||||||
attackerDamageReduce = calculateDamageReduce(defender, attacker, damageReceived, state);
|
attackerDamageReduce = calculateDamageReduce(defender, attacker, damageReceived, damageCache, state);
|
||||||
defenderState->afterAttack(attackInfo.shooting, true);
|
defenderState->afterAttack(attackInfo.shooting, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isEnemy = state.battleMatchOwner(attacker, u);
|
bool isEnemy = state->battleMatchOwner(attacker, u);
|
||||||
|
|
||||||
// this includes enemy units as well as attacker units under enemy's mind control
|
// this includes enemy units as well as attacker units under enemy's mind control
|
||||||
if(isEnemy)
|
if(isEnemy)
|
||||||
@ -225,7 +327,7 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check how much damage we gain from blocking enemy shooters on this hex
|
// check how much damage we gain from blocking enemy shooters on this hex
|
||||||
bestAp.shootersBlockedDmg = evaluateBlockedShootersDmg(attackInfo, hex, state);
|
bestAp.shootersBlockedDmg = evaluateBlockedShootersDmg(attackInfo, hex, damageCache, state);
|
||||||
|
|
||||||
#if BATTLE_TRACE_LEVEL>=1
|
#if BATTLE_TRACE_LEVEL>=1
|
||||||
logAi->trace("BattleAI best AP: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld",
|
logAi->trace("BattleAI best AP: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld",
|
||||||
|
@ -15,6 +15,22 @@
|
|||||||
|
|
||||||
#define BATTLE_TRACE_LEVEL 0
|
#define BATTLE_TRACE_LEVEL 0
|
||||||
|
|
||||||
|
class DamageCache
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::unordered_map<uint32_t, std::unordered_map<uint32_t, float>> damageCache;
|
||||||
|
DamageCache * parent;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DamageCache() : parent(nullptr) {}
|
||||||
|
DamageCache(DamageCache * parent) : parent(parent) {}
|
||||||
|
|
||||||
|
void cacheDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb);
|
||||||
|
int64_t getDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb);
|
||||||
|
int64_t getOriginalDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb);
|
||||||
|
void buildDamageCache(std::shared_ptr<HypotheticBattle> hb, int side);
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Evaluate attack value of one particular attack taking into account various effects like
|
/// Evaluate attack value of one particular attack taking into account various effects like
|
||||||
/// retaliation, 2-hex breath, collateral damage, shooters blocked damage
|
/// retaliation, 2-hex breath, collateral damage, shooters blocked damage
|
||||||
@ -30,24 +46,34 @@ public:
|
|||||||
|
|
||||||
std::vector<std::shared_ptr<battle::CUnitState>> affectedUnits;
|
std::vector<std::shared_ptr<battle::CUnitState>> affectedUnits;
|
||||||
|
|
||||||
int64_t defenderDamageReduce = 0;
|
float defenderDamageReduce = 0;
|
||||||
int64_t attackerDamageReduce = 0; //usually by counter-attack
|
float attackerDamageReduce = 0; //usually by counter-attack
|
||||||
int64_t collateralDamageReduce = 0; // friendly fire (usually by two-hex attacks)
|
float collateralDamageReduce = 0; // friendly fire (usually by two-hex attacks)
|
||||||
int64_t shootersBlockedDmg = 0;
|
int64_t shootersBlockedDmg = 0;
|
||||||
|
|
||||||
AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack_);
|
AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack_);
|
||||||
|
|
||||||
int64_t damageDiff() const;
|
float damageDiff() const;
|
||||||
int64_t attackValue() const;
|
float attackValue() const;
|
||||||
|
float damageDiff(float positiveEffectMultiplier, float negativeEffectMultiplier) const;
|
||||||
|
|
||||||
static AttackPossibility evaluate(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state);
|
static AttackPossibility evaluate(
|
||||||
|
const BattleAttackInfo & attackInfo,
|
||||||
|
BattleHex hex,
|
||||||
|
DamageCache & damageCache,
|
||||||
|
std::shared_ptr<CBattleInfoCallback> state);
|
||||||
|
|
||||||
static int64_t calculateDamageReduce(
|
static float calculateDamageReduce(
|
||||||
const battle::Unit * attacker,
|
const battle::Unit * attacker,
|
||||||
const battle::Unit * defender,
|
const battle::Unit * defender,
|
||||||
uint64_t damageDealt,
|
uint64_t damageDealt,
|
||||||
const CBattleInfoCallback & cb);
|
DamageCache & damageCache,
|
||||||
|
std::shared_ptr<CBattleInfoCallback> cb);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static int64_t evaluateBlockedShootersDmg(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state);
|
static int64_t evaluateBlockedShootersDmg(
|
||||||
|
const BattleAttackInfo & attackInfo,
|
||||||
|
BattleHex hex,
|
||||||
|
DamageCache & damageCache,
|
||||||
|
std::shared_ptr<CBattleInfoCallback> state);
|
||||||
};
|
};
|
||||||
|
@ -9,15 +9,18 @@
|
|||||||
*/
|
*/
|
||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
#include "BattleAI.h"
|
#include "BattleAI.h"
|
||||||
|
#include "BattleEvaluator.h"
|
||||||
#include "BattleExchangeVariant.h"
|
#include "BattleExchangeVariant.h"
|
||||||
|
|
||||||
#include "StackWithBonuses.h"
|
#include "StackWithBonuses.h"
|
||||||
#include "EnemyInfo.h"
|
#include "EnemyInfo.h"
|
||||||
|
#include "tbb/parallel_for.h"
|
||||||
#include "../../lib/CStopWatch.h"
|
#include "../../lib/CStopWatch.h"
|
||||||
#include "../../lib/CThreadHelper.h"
|
#include "../../lib/CThreadHelper.h"
|
||||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||||
#include "../../lib/spells/CSpellHandler.h"
|
#include "../../lib/spells/CSpellHandler.h"
|
||||||
#include "../../lib/spells/ISpellMechanics.h"
|
#include "../../lib/spells/ISpellMechanics.h"
|
||||||
|
#include "../../lib/battle/BattleAction.h"
|
||||||
#include "../../lib/battle/BattleStateInfoForRetreat.h"
|
#include "../../lib/battle/BattleStateInfoForRetreat.h"
|
||||||
#include "../../lib/battle/CObstacleInstance.h"
|
#include "../../lib/battle/CObstacleInstance.h"
|
||||||
#include "../../lib/CStack.h" // TODO: remove
|
#include "../../lib/CStack.h" // TODO: remove
|
||||||
@ -27,42 +30,6 @@
|
|||||||
#define LOGL(text) print(text)
|
#define LOGL(text) print(text)
|
||||||
#define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
|
#define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
|
||||||
|
|
||||||
enum class SpellTypes
|
|
||||||
{
|
|
||||||
ADVENTURE, BATTLE, OTHER
|
|
||||||
};
|
|
||||||
|
|
||||||
SpellTypes spellType(const CSpell * spell)
|
|
||||||
{
|
|
||||||
if(!spell->isCombat() || spell->isCreatureAbility())
|
|
||||||
return SpellTypes::OTHER;
|
|
||||||
|
|
||||||
if(spell->isOffensive() || spell->hasEffects() || spell->hasBattleEffects())
|
|
||||||
return SpellTypes::BATTLE;
|
|
||||||
|
|
||||||
return SpellTypes::OTHER;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<BattleHex> CBattleAI::getBrokenWallMoatHexes() const
|
|
||||||
{
|
|
||||||
std::vector<BattleHex> result;
|
|
||||||
|
|
||||||
for(EWallPart wallPart : { EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL })
|
|
||||||
{
|
|
||||||
auto state = cb->battleGetWallState(wallPart);
|
|
||||||
|
|
||||||
if(state != EWallState::DESTROYED)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto wallHex = cb->wallPartToBattleHex((EWallPart)wallPart);
|
|
||||||
auto moatHex = wallHex.cloneInDirection(BattleHex::LEFT);
|
|
||||||
|
|
||||||
result.push_back(moatHex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
CBattleAI::CBattleAI()
|
CBattleAI::CBattleAI()
|
||||||
: side(-1),
|
: side(-1),
|
||||||
wasWaitingForRealize(false),
|
wasWaitingForRealize(false),
|
||||||
@ -85,7 +52,7 @@ void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
|
|||||||
setCbc(CB);
|
setCbc(CB);
|
||||||
env = ENV;
|
env = ENV;
|
||||||
cb = CB;
|
cb = CB;
|
||||||
playerID = *CB->getPlayerID(); //TODO should be sth in callback
|
playerID = *CB->getPlayerID();
|
||||||
wasWaitingForRealize = CB->waitTillRealize;
|
wasWaitingForRealize = CB->waitTillRealize;
|
||||||
wasUnlockingGs = CB->unlockGsWhenWaiting;
|
wasUnlockingGs = CB->unlockGsWhenWaiting;
|
||||||
CB->waitTillRealize = false;
|
CB->waitTillRealize = false;
|
||||||
@ -93,9 +60,15 @@ void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
|
|||||||
movesSkippedByDefense = 0;
|
movesSkippedByDefense = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
BattleAction CBattleAI::useHealingTent(const CStack *stack)
|
void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB, AutocombatPreferences autocombatPreferences)
|
||||||
{
|
{
|
||||||
auto healingTargets = cb->battleGetStacks(CBattleInfoEssentials::ONLY_MINE);
|
initBattleInterface(ENV, CB);
|
||||||
|
autobattlePreferences = autocombatPreferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
BattleAction CBattleAI::useHealingTent(const BattleID & battleID, const CStack *stack)
|
||||||
|
{
|
||||||
|
auto healingTargets = cb->getBattle(battleID)->battleGetStacks(CBattleInfoEssentials::ONLY_MINE);
|
||||||
std::map<int, const CStack*> woundHpToStack;
|
std::map<int, const CStack*> woundHpToStack;
|
||||||
for(const auto * stack : healingTargets)
|
for(const auto * stack : healingTargets)
|
||||||
{
|
{
|
||||||
@ -109,161 +82,42 @@ BattleAction CBattleAI::useHealingTent(const CStack *stack)
|
|||||||
return BattleAction::makeHeal(stack, woundHpToStack.rbegin()->second); //last element of the woundHpToStack is the most wounded stack
|
return BattleAction::makeHeal(stack, woundHpToStack.rbegin()->second); //last element of the woundHpToStack is the most wounded stack
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<PossibleSpellcast> CBattleAI::findBestCreatureSpell(const CStack *stack)
|
void CBattleAI::yourTacticPhase(const BattleID & battleID, int distance)
|
||||||
{
|
{
|
||||||
//TODO: faerie dragon type spell should be selected by server
|
cb->battleMakeTacticAction(battleID, BattleAction::makeEndOFTacticPhase(cb->getBattle(battleID)->battleGetTacticsSide()));
|
||||||
SpellID creatureSpellToCast = cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), stack, CBattleInfoCallback::RANDOM_AIMED);
|
|
||||||
if(stack->hasBonusOfType(BonusType::SPELLCASTER) && stack->canCast() && creatureSpellToCast != SpellID::NONE)
|
|
||||||
{
|
|
||||||
const CSpell * spell = creatureSpellToCast.toSpell();
|
|
||||||
|
|
||||||
if(spell->canBeCast(getCbc().get(), spells::Mode::CREATURE_ACTIVE, stack))
|
|
||||||
{
|
|
||||||
std::vector<PossibleSpellcast> possibleCasts;
|
|
||||||
spells::BattleCast temp(getCbc().get(), stack, spells::Mode::CREATURE_ACTIVE, spell);
|
|
||||||
for(auto & target : temp.findPotentialTargets())
|
|
||||||
{
|
|
||||||
PossibleSpellcast ps;
|
|
||||||
ps.dest = target;
|
|
||||||
ps.spell = spell;
|
|
||||||
evaluateCreatureSpellcast(stack, ps);
|
|
||||||
possibleCasts.push_back(ps);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::sort(possibleCasts.begin(), possibleCasts.end(), [&](const PossibleSpellcast & lhs, const PossibleSpellcast & rhs) { return lhs.value > rhs.value; });
|
|
||||||
if(!possibleCasts.empty() && possibleCasts.front().value > 0)
|
|
||||||
{
|
|
||||||
return possibleCasts.front();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BattleAction CBattleAI::selectStackAction(const CStack * stack)
|
static float getStrengthRatio(std::shared_ptr<CBattleInfoCallback> cb, int side)
|
||||||
{
|
{
|
||||||
//evaluate casting spell for spellcasting stack
|
auto stacks = cb->battleGetAllStacks();
|
||||||
std::optional<PossibleSpellcast> bestSpellcast = findBestCreatureSpell(stack);
|
auto our = 0, enemy = 0;
|
||||||
|
|
||||||
HypotheticBattle hb(env.get(), cb);
|
for(auto stack : stacks)
|
||||||
|
|
||||||
PotentialTargets targets(stack, hb);
|
|
||||||
BattleExchangeEvaluator scoreEvaluator(cb, env);
|
|
||||||
auto moveTarget = scoreEvaluator.findMoveTowardsUnreachable(stack, targets, hb);
|
|
||||||
|
|
||||||
int64_t score = EvaluationResult::INEFFECTIVE_SCORE;
|
|
||||||
|
|
||||||
|
|
||||||
if(targets.possibleAttacks.empty() && bestSpellcast.has_value())
|
|
||||||
{
|
{
|
||||||
movesSkippedByDefense = 0;
|
auto creature = stack->creatureId().toCreature();
|
||||||
return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!targets.possibleAttacks.empty())
|
if(!creature)
|
||||||
{
|
continue;
|
||||||
#if BATTLE_TRACE_LEVEL>=1
|
|
||||||
logAi->trace("Evaluating attack for %s", stack->getDescription());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
auto evaluationResult = scoreEvaluator.findBestTarget(stack, targets, hb);
|
if(stack->unitSide() == side)
|
||||||
auto & bestAttack = evaluationResult.bestAttack;
|
our += stack->getCount() * creature->getAIValue();
|
||||||
|
|
||||||
//TODO: consider more complex spellcast evaluation, f.e. because "re-retaliation" during enemy move in same turn for melee attack etc.
|
|
||||||
if(bestSpellcast.has_value() && bestSpellcast->value > bestAttack.damageDiff())
|
|
||||||
{
|
|
||||||
// return because spellcast value is damage dealt and score is dps reduce
|
|
||||||
movesSkippedByDefense = 0;
|
|
||||||
return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(evaluationResult.score > score)
|
|
||||||
{
|
|
||||||
score = evaluationResult.score;
|
|
||||||
|
|
||||||
logAi->debug("BattleAI: %s -> %s x %d, from %d curpos %d dist %d speed %d: +%lld -%lld = %lld",
|
|
||||||
bestAttack.attackerState->unitType()->getJsonKey(),
|
|
||||||
bestAttack.affectedUnits[0]->unitType()->getJsonKey(),
|
|
||||||
(int)bestAttack.affectedUnits[0]->getCount(),
|
|
||||||
(int)bestAttack.from,
|
|
||||||
(int)bestAttack.attack.attacker->getPosition().hex,
|
|
||||||
bestAttack.attack.chargeDistance,
|
|
||||||
bestAttack.attack.attacker->speed(0, true),
|
|
||||||
bestAttack.defenderDamageReduce,
|
|
||||||
bestAttack.attackerDamageReduce, bestAttack.attackValue()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moveTarget.score <= score)
|
|
||||||
{
|
|
||||||
if(evaluationResult.wait)
|
|
||||||
{
|
|
||||||
return BattleAction::makeWait(stack);
|
|
||||||
}
|
|
||||||
else if(bestAttack.attack.shooting)
|
|
||||||
{
|
|
||||||
movesSkippedByDefense = 0;
|
|
||||||
return BattleAction::makeShotAttack(stack, bestAttack.attack.defender);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
enemy += stack->getCount() * creature->getAIValue();
|
||||||
movesSkippedByDefense = 0;
|
|
||||||
return BattleAction::makeMeleeAttack(stack, bestAttack.attack.defender->getPosition(), bestAttack.from);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code.
|
return enemy == 0 ? 1.0f : static_cast<float>(our) / enemy;
|
||||||
if(moveTarget.score > score)
|
|
||||||
{
|
|
||||||
score = moveTarget.score;
|
|
||||||
|
|
||||||
if(stack->waited())
|
|
||||||
{
|
|
||||||
return goTowardsNearest(stack, moveTarget.positions);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return BattleAction::makeWait(stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(score <= EvaluationResult::INEFFECTIVE_SCORE
|
|
||||||
&& !stack->hasBonusOfType(BonusType::FLYING)
|
|
||||||
&& stack->unitSide() == BattleSide::ATTACKER
|
|
||||||
&& cb->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
|
|
||||||
{
|
|
||||||
auto brokenWallMoat = getBrokenWallMoatHexes();
|
|
||||||
|
|
||||||
if(brokenWallMoat.size())
|
|
||||||
{
|
|
||||||
movesSkippedByDefense = 0;
|
|
||||||
|
|
||||||
if(stack->doubleWide() && vstd::contains(brokenWallMoat, stack->getPosition()))
|
|
||||||
return BattleAction::makeMove(stack, stack->getPosition().cloneInDirection(BattleHex::RIGHT));
|
|
||||||
else
|
|
||||||
return goTowardsNearest(stack, brokenWallMoat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return BattleAction::makeDefend(stack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CBattleAI::yourTacticPhase(int distance)
|
void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
|
||||||
{
|
{
|
||||||
cb->battleMakeTacticAction(BattleAction::makeEndOFTacticPhase(cb->battleGetTacticsSide()));
|
LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName());
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock> start)
|
auto timeElapsed = [](std::chrono::time_point<std::chrono::high_resolution_clock> start) -> uint64_t
|
||||||
{
|
{
|
||||||
auto end = std::chrono::high_resolution_clock::now();
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
|
return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
|
||||||
}
|
};
|
||||||
|
|
||||||
void CBattleAI::activeStack( const CStack * stack )
|
|
||||||
{
|
|
||||||
LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName());
|
|
||||||
|
|
||||||
BattleAction result = BattleAction::makeDefend(stack);
|
BattleAction result = BattleAction::makeDefend(stack);
|
||||||
setCbc(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)
|
||||||
@ -274,36 +128,40 @@ void CBattleAI::activeStack( const CStack * stack )
|
|||||||
{
|
{
|
||||||
if(stack->creatureId() == CreatureID::CATAPULT)
|
if(stack->creatureId() == CreatureID::CATAPULT)
|
||||||
{
|
{
|
||||||
cb->battleMakeUnitAction(useCatapult(stack));
|
cb->battleMakeUnitAction(battleID, useCatapult(battleID, stack));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(stack->hasBonusOfType(BonusType::SIEGE_WEAPON) && stack->hasBonusOfType(BonusType::HEALER))
|
if(stack->hasBonusOfType(BonusType::SIEGE_WEAPON) && stack->hasBonusOfType(BonusType::HEALER))
|
||||||
{
|
{
|
||||||
cb->battleMakeUnitAction(useHealingTent(stack));
|
cb->battleMakeUnitAction(battleID, useHealingTent(battleID, stack));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
attemptCastingSpell();
|
#if BATTLE_TRACE_LEVEL>=1
|
||||||
|
logAi->trace("Build evaluator and targets");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
BattleEvaluator evaluator(env, cb, stack, playerID, battleID, side, getStrengthRatio(cb->getBattle(battleID), side));
|
||||||
|
|
||||||
|
result = evaluator.selectStackAction(stack);
|
||||||
|
|
||||||
|
if(!skipCastUntilNextBattle && evaluator.canCastSpell())
|
||||||
|
{
|
||||||
|
auto spelCasted = evaluator.attemptCastingSpell(stack);
|
||||||
|
|
||||||
|
if(spelCasted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
skipCastUntilNextBattle = true;
|
||||||
|
}
|
||||||
|
|
||||||
logAi->trace("Spellcast attempt completed in %lld", timeElapsed(start));
|
logAi->trace("Spellcast attempt completed in %lld", timeElapsed(start));
|
||||||
|
|
||||||
if(cb->battleIsFinished() || !stack->alive())
|
if(auto action = considerFleeingOrSurrendering(battleID))
|
||||||
{
|
{
|
||||||
//spellcast may finish battle or kill active stack
|
cb->battleMakeUnitAction(battleID, *action);
|
||||||
//send special preudo-action
|
|
||||||
BattleAction cancel;
|
|
||||||
cancel.actionType = EActionType::CANCEL;
|
|
||||||
cb->battleMakeUnitAction(cancel);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(auto action = considerFleeingOrSurrendering())
|
|
||||||
{
|
|
||||||
cb->battleMakeUnitAction(*action);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = selectStackAction(stack);
|
|
||||||
}
|
}
|
||||||
catch(boost::thread_interrupted &)
|
catch(boost::thread_interrupted &)
|
||||||
{
|
{
|
||||||
@ -325,117 +183,17 @@ void CBattleAI::activeStack( const CStack * stack )
|
|||||||
|
|
||||||
logAi->trace("BattleAI decission made in %lld", timeElapsed(start));
|
logAi->trace("BattleAI decission made in %lld", timeElapsed(start));
|
||||||
|
|
||||||
cb->battleMakeUnitAction(result);
|
cb->battleMakeUnitAction(battleID, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
BattleAction CBattleAI::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes) const
|
BattleAction CBattleAI::useCatapult(const BattleID & battleID, const CStack * stack)
|
||||||
{
|
|
||||||
auto reachability = cb->getReachability(stack);
|
|
||||||
auto avHexes = cb->battleGetAvailableHexes(reachability, stack, false);
|
|
||||||
|
|
||||||
if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
|
|
||||||
{
|
|
||||||
return BattleAction::makeDefend(stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::sort(hexes.begin(), hexes.end(), [&](BattleHex h1, BattleHex h2) -> bool
|
|
||||||
{
|
|
||||||
return reachability.distances[h1] < reachability.distances[h2];
|
|
||||||
});
|
|
||||||
|
|
||||||
for(auto hex : hexes)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BattleHex bestNeighbor = hexes.front();
|
|
||||||
|
|
||||||
if(reachability.distances[bestNeighbor] > GameConstants::BFIELD_SIZE)
|
|
||||||
{
|
|
||||||
return BattleAction::makeDefend(stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
BattleExchangeEvaluator scoreEvaluator(cb, env);
|
|
||||||
HypotheticBattle hb(env.get(), cb);
|
|
||||||
|
|
||||||
scoreEvaluator.updateReachabilityMap(hb);
|
|
||||||
|
|
||||||
if(stack->hasBonusOfType(BonusType::FLYING))
|
|
||||||
{
|
|
||||||
std::set<BattleHex> obstacleHexes;
|
|
||||||
|
|
||||||
auto insertAffected = [](const CObstacleInstance & spellObst, std::set<BattleHex> obstacleHexes) {
|
|
||||||
auto affectedHexes = spellObst.getAffectedTiles();
|
|
||||||
obstacleHexes.insert(affectedHexes.cbegin(), affectedHexes.cend());
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto & obstacles = hb.battleGetAllObstacles();
|
|
||||||
|
|
||||||
for (const auto & obst: obstacles) {
|
|
||||||
|
|
||||||
if(obst->triggersEffects())
|
|
||||||
{
|
|
||||||
auto triggerAbility = VLC->spells()->getById(obst->getTrigger());
|
|
||||||
auto triggerIsNegative = triggerAbility->isNegative() || triggerAbility->isDamage();
|
|
||||||
|
|
||||||
if(triggerIsNegative)
|
|
||||||
insertAffected(*obst, obstacleHexes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Flying stack doesn't go hex by hex, so we can't backtrack using predecessors.
|
|
||||||
// We just check all available hexes and pick the one closest to the target.
|
|
||||||
auto nearestAvailableHex = vstd::minElementByFun(avHexes, [&](BattleHex hex) -> int
|
|
||||||
{
|
|
||||||
const int NEGATIVE_OBSTACLE_PENALTY = 100; // avoid landing on negative obstacle (moat, fire wall, etc)
|
|
||||||
const int BLOCKED_STACK_PENALTY = 100; // avoid landing on moat
|
|
||||||
|
|
||||||
auto distance = BattleHex::getDistance(bestNeighbor, hex);
|
|
||||||
|
|
||||||
if(vstd::contains(obstacleHexes, hex))
|
|
||||||
distance += NEGATIVE_OBSTACLE_PENALTY;
|
|
||||||
|
|
||||||
return scoreEvaluator.checkPositionBlocksOurStacks(hb, stack, hex) ? BLOCKED_STACK_PENALTY + distance : distance;
|
|
||||||
});
|
|
||||||
|
|
||||||
return BattleAction::makeMove(stack, *nearestAvailableHex);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
BattleHex currentDest = bestNeighbor;
|
|
||||||
while(1)
|
|
||||||
{
|
|
||||||
if(!currentDest.isValid())
|
|
||||||
{
|
|
||||||
return BattleAction::makeDefend(stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(vstd::contains(avHexes, currentDest)
|
|
||||||
&& !scoreEvaluator.checkPositionBlocksOurStacks(hb, stack, currentDest))
|
|
||||||
return BattleAction::makeMove(stack, currentDest);
|
|
||||||
|
|
||||||
currentDest = reachability.predecessors[currentDest];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BattleAction CBattleAI::useCatapult(const CStack * stack)
|
|
||||||
{
|
{
|
||||||
BattleAction attack;
|
BattleAction attack;
|
||||||
BattleHex targetHex = BattleHex::INVALID;
|
BattleHex targetHex = BattleHex::INVALID;
|
||||||
|
|
||||||
if(cb->battleGetGateState() == EGateState::CLOSED)
|
if(cb->getBattle(battleID)->battleGetGateState() == EGateState::CLOSED)
|
||||||
{
|
{
|
||||||
targetHex = cb->wallPartToBattleHex(EWallPart::GATE);
|
targetHex = cb->getBattle(battleID)->wallPartToBattleHex(EWallPart::GATE);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -451,11 +209,11 @@ BattleAction CBattleAI::useCatapult(const CStack * stack)
|
|||||||
|
|
||||||
for(auto wallPart : wallParts)
|
for(auto wallPart : wallParts)
|
||||||
{
|
{
|
||||||
auto wallState = cb->battleGetWallState(wallPart);
|
auto wallState = cb->getBattle(battleID)->battleGetWallState(wallPart);
|
||||||
|
|
||||||
if(wallState == EWallState::REINFORCED || wallState == EWallState::INTACT || wallState == EWallState::DAMAGED)
|
if(wallState == EWallState::REINFORCED || wallState == EWallState::INTACT || wallState == EWallState::DAMAGED)
|
||||||
{
|
{
|
||||||
targetHex = cb->wallPartToBattleHex(wallPart);
|
targetHex = cb->getBattle(battleID)->wallPartToBattleHex(wallPart);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -476,396 +234,30 @@ BattleAction CBattleAI::useCatapult(const CStack * stack)
|
|||||||
return attack;
|
return attack;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CBattleAI::attemptCastingSpell()
|
void CBattleAI::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side, bool replayAllowed)
|
||||||
{
|
|
||||||
auto hero = cb->battleGetMyHero();
|
|
||||||
if(!hero)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(cb->battleCanCastSpell(hero, spells::Mode::HERO) != ESpellCastProblem::OK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
LOGL("Casting spells sounds like fun. Let's see...");
|
|
||||||
//Get all spells we can cast
|
|
||||||
std::vector<const CSpell*> possibleSpells;
|
|
||||||
vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [hero, this](const CSpell *s) -> bool
|
|
||||||
{
|
|
||||||
return s->canBeCast(cb.get(), spells::Mode::HERO, hero);
|
|
||||||
});
|
|
||||||
LOGFL("I can cast %d spells.", possibleSpells.size());
|
|
||||||
|
|
||||||
vstd::erase_if(possibleSpells, [](const CSpell *s)
|
|
||||||
{
|
|
||||||
return spellType(s) != SpellTypes::BATTLE;
|
|
||||||
});
|
|
||||||
|
|
||||||
LOGFL("I know how %d of them works.", possibleSpells.size());
|
|
||||||
|
|
||||||
//Get possible spell-target pairs
|
|
||||||
std::vector<PossibleSpellcast> possibleCasts;
|
|
||||||
for(auto spell : possibleSpells)
|
|
||||||
{
|
|
||||||
spells::BattleCast temp(cb.get(), hero, spells::Mode::HERO, spell);
|
|
||||||
|
|
||||||
if(!spell->isDamage() && spell->getTargetType() == spells::AimType::LOCATION)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const bool FAST = true;
|
|
||||||
|
|
||||||
for(auto & target : temp.findPotentialTargets(FAST))
|
|
||||||
{
|
|
||||||
PossibleSpellcast ps;
|
|
||||||
ps.dest = target;
|
|
||||||
ps.spell = spell;
|
|
||||||
possibleCasts.push_back(ps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOGFL("Found %d spell-target combinations.", possibleCasts.size());
|
|
||||||
if(possibleCasts.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
using ValueMap = PossibleSpellcast::ValueMap;
|
|
||||||
|
|
||||||
auto evaluateQueue = [&](ValueMap & values, const std::vector<battle::Units> & queue, HypotheticBattle & state, size_t minTurnSpan, bool * enemyHadTurnOut) -> bool
|
|
||||||
{
|
|
||||||
bool firstRound = true;
|
|
||||||
bool enemyHadTurn = false;
|
|
||||||
size_t ourTurnSpan = 0;
|
|
||||||
|
|
||||||
bool stop = false;
|
|
||||||
|
|
||||||
for(auto & round : queue)
|
|
||||||
{
|
|
||||||
if(!firstRound)
|
|
||||||
state.nextRound(0);//todo: set actual value?
|
|
||||||
for(auto unit : round)
|
|
||||||
{
|
|
||||||
if(!vstd::contains(values, unit->unitId()))
|
|
||||||
values[unit->unitId()] = 0;
|
|
||||||
|
|
||||||
if(!unit->alive())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if(state.battleGetOwner(unit) != playerID)
|
|
||||||
{
|
|
||||||
enemyHadTurn = true;
|
|
||||||
|
|
||||||
if(!firstRound || state.battleCastSpells(unit->unitSide()) == 0)
|
|
||||||
{
|
|
||||||
//enemy could counter our spell at this point
|
|
||||||
//anyway, we do not know what enemy will do
|
|
||||||
//just stop evaluation
|
|
||||||
stop = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(!enemyHadTurn)
|
|
||||||
{
|
|
||||||
ourTurnSpan++;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.nextTurn(unit->unitId());
|
|
||||||
|
|
||||||
PotentialTargets pt(unit, state);
|
|
||||||
|
|
||||||
if(!pt.possibleAttacks.empty())
|
|
||||||
{
|
|
||||||
AttackPossibility ap = pt.bestAction();
|
|
||||||
|
|
||||||
auto swb = state.getForUpdate(unit->unitId());
|
|
||||||
*swb = *ap.attackerState;
|
|
||||||
|
|
||||||
if(ap.defenderDamageReduce > 0)
|
|
||||||
swb->removeUnitBonus(Bonus::UntilAttack);
|
|
||||||
if(ap.attackerDamageReduce > 0)
|
|
||||||
swb->removeUnitBonus(Bonus::UntilBeingAttacked);
|
|
||||||
|
|
||||||
for(auto affected : ap.affectedUnits)
|
|
||||||
{
|
|
||||||
swb = state.getForUpdate(affected->unitId());
|
|
||||||
*swb = *affected;
|
|
||||||
|
|
||||||
if(ap.defenderDamageReduce > 0)
|
|
||||||
swb->removeUnitBonus(Bonus::UntilBeingAttacked);
|
|
||||||
if(ap.attackerDamageReduce > 0 && ap.attack.defender->unitId() == affected->unitId())
|
|
||||||
swb->removeUnitBonus(Bonus::UntilAttack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto bav = pt.bestActionValue();
|
|
||||||
|
|
||||||
//best action is from effective owner`s point if view, we need to convert to our point if view
|
|
||||||
if(state.battleGetOwner(unit) != playerID)
|
|
||||||
bav = -bav;
|
|
||||||
values[unit->unitId()] += bav;
|
|
||||||
}
|
|
||||||
|
|
||||||
firstRound = false;
|
|
||||||
|
|
||||||
if(stop)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(enemyHadTurnOut)
|
|
||||||
*enemyHadTurnOut = enemyHadTurn;
|
|
||||||
|
|
||||||
return ourTurnSpan >= minTurnSpan;
|
|
||||||
};
|
|
||||||
|
|
||||||
ValueMap valueOfStack;
|
|
||||||
ValueMap healthOfStack;
|
|
||||||
|
|
||||||
TStacks all = cb->battleGetAllStacks(false);
|
|
||||||
|
|
||||||
size_t ourRemainingTurns = 0;
|
|
||||||
|
|
||||||
for(auto unit : all)
|
|
||||||
{
|
|
||||||
healthOfStack[unit->unitId()] = unit->getAvailableHealth();
|
|
||||||
valueOfStack[unit->unitId()] = 0;
|
|
||||||
|
|
||||||
if(cb->battleGetOwner(unit) == playerID && unit->canMove() && !unit->moved())
|
|
||||||
ourRemainingTurns++;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGFL("I have %d turns left in this round", ourRemainingTurns);
|
|
||||||
|
|
||||||
const bool castNow = ourRemainingTurns <= 1;
|
|
||||||
|
|
||||||
if(castNow)
|
|
||||||
print("I should try to cast a spell now");
|
|
||||||
else
|
|
||||||
print("I could wait better moment to cast a spell");
|
|
||||||
|
|
||||||
auto amount = all.size();
|
|
||||||
|
|
||||||
std::vector<battle::Units> turnOrder;
|
|
||||||
|
|
||||||
cb->battleGetTurnOrder(turnOrder, amount, 2); //no more than 1 turn after current, each unit at least once
|
|
||||||
|
|
||||||
{
|
|
||||||
bool enemyHadTurn = false;
|
|
||||||
|
|
||||||
HypotheticBattle state(env.get(), cb);
|
|
||||||
|
|
||||||
evaluateQueue(valueOfStack, turnOrder, state, 0, &enemyHadTurn);
|
|
||||||
|
|
||||||
if(!enemyHadTurn)
|
|
||||||
{
|
|
||||||
auto battleIsFinishedOpt = state.battleIsFinished();
|
|
||||||
|
|
||||||
if(battleIsFinishedOpt)
|
|
||||||
{
|
|
||||||
print("No need to cast a spell. Battle will finish soon.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ScriptsCache
|
|
||||||
{
|
|
||||||
//todo: re-implement scripts context cache
|
|
||||||
};
|
|
||||||
|
|
||||||
auto evaluateSpellcast = [&] (PossibleSpellcast * ps, std::shared_ptr<ScriptsCache>)
|
|
||||||
{
|
|
||||||
HypotheticBattle state(env.get(), cb);
|
|
||||||
|
|
||||||
spells::BattleCast cast(&state, hero, spells::Mode::HERO, ps->spell);
|
|
||||||
cast.castEval(state.getServerCallback(), ps->dest);
|
|
||||||
ValueMap newHealthOfStack;
|
|
||||||
ValueMap newValueOfStack;
|
|
||||||
|
|
||||||
size_t ourUnits = 0;
|
|
||||||
|
|
||||||
std::set<uint32_t> unitIds;
|
|
||||||
|
|
||||||
state.battleGetUnitsIf([&](const battle::Unit * u)->bool
|
|
||||||
{
|
|
||||||
if(!u->isGhost() && !u->isTurret())
|
|
||||||
unitIds.insert(u->unitId());
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
for(auto unitId : unitIds)
|
|
||||||
{
|
|
||||||
auto localUnit = state.battleGetUnitByID(unitId);
|
|
||||||
|
|
||||||
newHealthOfStack[unitId] = localUnit->getAvailableHealth();
|
|
||||||
newValueOfStack[unitId] = 0;
|
|
||||||
|
|
||||||
if(state.battleGetOwner(localUnit) == playerID && localUnit->alive() && localUnit->willMove())
|
|
||||||
ourUnits++;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t minTurnSpan = ourUnits/3; //todo: tweak this
|
|
||||||
|
|
||||||
std::vector<battle::Units> newTurnOrder;
|
|
||||||
|
|
||||||
state.battleGetTurnOrder(newTurnOrder, amount, 2);
|
|
||||||
|
|
||||||
const bool turnSpanOK = evaluateQueue(newValueOfStack, newTurnOrder, state, minTurnSpan, nullptr);
|
|
||||||
|
|
||||||
if(turnSpanOK || castNow)
|
|
||||||
{
|
|
||||||
int64_t totalGain = 0;
|
|
||||||
|
|
||||||
for(auto unitId : unitIds)
|
|
||||||
{
|
|
||||||
auto localUnit = state.battleGetUnitByID(unitId);
|
|
||||||
|
|
||||||
auto newValue = getValOr(newValueOfStack, unitId, 0);
|
|
||||||
auto oldValue = getValOr(valueOfStack, unitId, 0);
|
|
||||||
|
|
||||||
auto healthDiff = newHealthOfStack[unitId] - healthOfStack[unitId];
|
|
||||||
|
|
||||||
if(localUnit->unitOwner() != playerID)
|
|
||||||
healthDiff = -healthDiff;
|
|
||||||
|
|
||||||
if(healthDiff < 0)
|
|
||||||
{
|
|
||||||
ps->value = -1;
|
|
||||||
return; //do not damage own units at all
|
|
||||||
}
|
|
||||||
|
|
||||||
totalGain += (newValue - oldValue + healthDiff);
|
|
||||||
}
|
|
||||||
|
|
||||||
ps->value = totalGain;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ps->value = -1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using EvalRunner = ThreadPool<ScriptsCache>;
|
|
||||||
|
|
||||||
EvalRunner::Tasks tasks;
|
|
||||||
|
|
||||||
for(PossibleSpellcast & psc : possibleCasts)
|
|
||||||
tasks.push_back(std::bind(evaluateSpellcast, &psc, _1));
|
|
||||||
|
|
||||||
uint32_t threadCount = boost::thread::hardware_concurrency();
|
|
||||||
|
|
||||||
if(threadCount == 0)
|
|
||||||
{
|
|
||||||
logGlobal->warn("No information of CPU cores available");
|
|
||||||
threadCount = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
CStopWatch timer;
|
|
||||||
|
|
||||||
std::vector<std::shared_ptr<ScriptsCache>> scriptsPool;
|
|
||||||
|
|
||||||
for(uint32_t idx = 0; idx < threadCount; idx++)
|
|
||||||
{
|
|
||||||
scriptsPool.emplace_back();
|
|
||||||
}
|
|
||||||
|
|
||||||
EvalRunner runner(&tasks, scriptsPool);
|
|
||||||
runner.run();
|
|
||||||
|
|
||||||
LOGFL("Evaluation took %d ms", timer.getDiff());
|
|
||||||
|
|
||||||
auto pscValue = [](const PossibleSpellcast &ps) -> int64_t
|
|
||||||
{
|
|
||||||
return ps.value;
|
|
||||||
};
|
|
||||||
auto castToPerform = *vstd::maxElementByFun(possibleCasts, pscValue);
|
|
||||||
|
|
||||||
if(castToPerform.value > 0)
|
|
||||||
{
|
|
||||||
LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->getNameTranslated() % castToPerform.value);
|
|
||||||
BattleAction spellcast;
|
|
||||||
spellcast.actionType = EActionType::HERO_SPELL;
|
|
||||||
spellcast.actionSubtype = castToPerform.spell->id;
|
|
||||||
spellcast.setTarget(castToPerform.dest);
|
|
||||||
spellcast.side = side;
|
|
||||||
spellcast.stackNumber = (!side) ? -1 : -2;
|
|
||||||
cb->battleMakeSpellAction(spellcast);
|
|
||||||
movesSkippedByDefense = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOGFL("Best spell is %s. But it is actually useless (value %d).", castToPerform.spell->getNameTranslated() % castToPerform.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Below method works only for offensive spells
|
|
||||||
void CBattleAI::evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps)
|
|
||||||
{
|
|
||||||
using ValueMap = PossibleSpellcast::ValueMap;
|
|
||||||
|
|
||||||
RNGStub rngStub;
|
|
||||||
HypotheticBattle state(env.get(), cb);
|
|
||||||
TStacks all = cb->battleGetAllStacks(false);
|
|
||||||
|
|
||||||
ValueMap healthOfStack;
|
|
||||||
ValueMap newHealthOfStack;
|
|
||||||
|
|
||||||
for(auto unit : all)
|
|
||||||
{
|
|
||||||
healthOfStack[unit->unitId()] = unit->getAvailableHealth();
|
|
||||||
}
|
|
||||||
|
|
||||||
spells::BattleCast cast(&state, stack, spells::Mode::CREATURE_ACTIVE, ps.spell);
|
|
||||||
cast.castEval(state.getServerCallback(), ps.dest);
|
|
||||||
|
|
||||||
for(auto unit : all)
|
|
||||||
{
|
|
||||||
auto unitId = unit->unitId();
|
|
||||||
auto localUnit = state.battleGetUnitByID(unitId);
|
|
||||||
newHealthOfStack[unitId] = localUnit->getAvailableHealth();
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t totalGain = 0;
|
|
||||||
|
|
||||||
for(auto unit : all)
|
|
||||||
{
|
|
||||||
auto unitId = unit->unitId();
|
|
||||||
auto localUnit = state.battleGetUnitByID(unitId);
|
|
||||||
|
|
||||||
auto healthDiff = newHealthOfStack[unitId] - healthOfStack[unitId];
|
|
||||||
|
|
||||||
if(localUnit->unitOwner() != getCbc()->getPlayerID())
|
|
||||||
healthDiff = -healthDiff;
|
|
||||||
|
|
||||||
if(healthDiff < 0)
|
|
||||||
{
|
|
||||||
ps.value = -1;
|
|
||||||
return; //do not damage own units at all
|
|
||||||
}
|
|
||||||
|
|
||||||
totalGain += healthDiff;
|
|
||||||
}
|
|
||||||
|
|
||||||
ps.value = totalGain;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side, bool replayAllowed)
|
|
||||||
{
|
{
|
||||||
LOG_TRACE(logAi);
|
LOG_TRACE(logAi);
|
||||||
side = Side;
|
side = Side;
|
||||||
|
|
||||||
|
skipCastUntilNextBattle = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CBattleAI::print(const std::string &text) const
|
void CBattleAI::print(const std::string &text) const
|
||||||
{
|
{
|
||||||
logAi->trace("%s Battle AI[%p]: %s", playerID.getStr(), this, text);
|
logAi->trace("%s Battle AI[%p]: %s", playerID.toString(), this, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
|
std::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering(const BattleID & battleID)
|
||||||
{
|
{
|
||||||
BattleStateInfoForRetreat bs;
|
BattleStateInfoForRetreat bs;
|
||||||
|
|
||||||
bs.canFlee = cb->battleCanFlee();
|
bs.canFlee = cb->getBattle(battleID)->battleCanFlee();
|
||||||
bs.canSurrender = cb->battleCanSurrender(playerID);
|
bs.canSurrender = cb->getBattle(battleID)->battleCanSurrender(playerID);
|
||||||
bs.ourSide = cb->battleGetMySide();
|
bs.ourSide = cb->getBattle(battleID)->battleGetMySide();
|
||||||
bs.ourHero = cb->battleGetMyHero();
|
bs.ourHero = cb->getBattle(battleID)->battleGetMyHero();
|
||||||
bs.enemyHero = nullptr;
|
bs.enemyHero = nullptr;
|
||||||
|
|
||||||
for(auto stack : cb->battleGetAllStacks(false))
|
for(auto stack : cb->getBattle(battleID)->battleGetAllStacks(false))
|
||||||
{
|
{
|
||||||
if(stack->alive())
|
if(stack->alive())
|
||||||
{
|
{
|
||||||
@ -874,7 +266,7 @@ std::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
bs.enemyStacks.push_back(stack);
|
bs.enemyStacks.push_back(stack);
|
||||||
bs.enemyHero = cb->battleGetOwnerHero(stack);
|
bs.enemyHero = cb->getBattle(battleID)->battleGetOwnerHero(stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -886,7 +278,7 @@ std::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto result = cb->makeSurrenderRetreatDecision(bs);
|
auto result = cb->makeSurrenderRetreatDecision(battleID, bs);
|
||||||
|
|
||||||
if(!result && bs.canFlee && bs.turnsSkippedByDefense > 30)
|
if(!result && bs.canFlee && bs.turnsSkippedByDefense > 30)
|
||||||
{
|
{
|
||||||
|
@ -62,28 +62,25 @@ class CBattleAI : public CBattleGameInterface
|
|||||||
bool wasWaitingForRealize;
|
bool wasWaitingForRealize;
|
||||||
bool wasUnlockingGs;
|
bool wasUnlockingGs;
|
||||||
int movesSkippedByDefense;
|
int movesSkippedByDefense;
|
||||||
|
bool skipCastUntilNextBattle;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CBattleAI();
|
CBattleAI();
|
||||||
~CBattleAI();
|
~CBattleAI();
|
||||||
|
|
||||||
void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override;
|
void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override;
|
||||||
void attemptCastingSpell();
|
void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB, AutocombatPreferences autocombatPreferences) override;
|
||||||
|
|
||||||
void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
|
void activeStack(const BattleID & battleID, const CStack * stack) override; //called when it's turn of that stack
|
||||||
|
void yourTacticPhase(const BattleID & battleID, int distance) override;
|
||||||
|
|
||||||
void activeStack(const CStack * stack) override; //called when it's turn of that stack
|
std::optional<BattleAction> considerFleeingOrSurrendering(const BattleID & battleID);
|
||||||
void yourTacticPhase(int distance) override;
|
|
||||||
|
|
||||||
std::optional<BattleAction> considerFleeingOrSurrendering();
|
|
||||||
|
|
||||||
void print(const std::string &text) const;
|
void print(const std::string &text) const;
|
||||||
BattleAction useCatapult(const CStack *stack);
|
BattleAction useCatapult(const BattleID & battleID, const CStack *stack);
|
||||||
BattleAction useHealingTent(const CStack *stack);
|
BattleAction useHealingTent(const BattleID & battleID, const CStack *stack);
|
||||||
BattleAction selectStackAction(const CStack * stack);
|
|
||||||
std::optional<PossibleSpellcast> findBestCreatureSpell(const CStack *stack);
|
|
||||||
|
|
||||||
void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool Side, bool replayAllowed) override;
|
void battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool Side, bool replayAllowed) override;
|
||||||
//void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
|
//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 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 battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
|
||||||
@ -98,8 +95,5 @@ public:
|
|||||||
//void battleTriggerEffect(const BattleTriggerEffect & bte) override;
|
//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 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 battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
|
//void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
|
||||||
|
AutocombatPreferences autobattlePreferences = AutocombatPreferences();
|
||||||
private:
|
|
||||||
BattleAction goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes) const;
|
|
||||||
std::vector<BattleHex> getBrokenWallMoatHexes() const;
|
|
||||||
};
|
};
|
||||||
|
710
AI/BattleAI/BattleEvaluator.cpp
Normal file
710
AI/BattleAI/BattleEvaluator.cpp
Normal file
@ -0,0 +1,710 @@
|
|||||||
|
/*
|
||||||
|
* 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 "BattleEvaluator.h"
|
||||||
|
#include "BattleExchangeVariant.h"
|
||||||
|
|
||||||
|
#include "StackWithBonuses.h"
|
||||||
|
#include "EnemyInfo.h"
|
||||||
|
#include "tbb/parallel_for.h"
|
||||||
|
#include "../../lib/CStopWatch.h"
|
||||||
|
#include "../../lib/CThreadHelper.h"
|
||||||
|
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||||
|
#include "../../lib/spells/CSpellHandler.h"
|
||||||
|
#include "../../lib/spells/ISpellMechanics.h"
|
||||||
|
#include "../../lib/battle/BattleStateInfoForRetreat.h"
|
||||||
|
#include "../../lib/battle/CObstacleInstance.h"
|
||||||
|
#include "../../lib/battle/BattleAction.h"
|
||||||
|
|
||||||
|
// TODO: remove
|
||||||
|
// Eventually only IBattleInfoCallback and battle::Unit should be used,
|
||||||
|
// CUnitState should be private and CStack should be removed completely
|
||||||
|
#include "../../lib/CStack.h"
|
||||||
|
|
||||||
|
#define LOGL(text) print(text)
|
||||||
|
#define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
|
||||||
|
|
||||||
|
enum class SpellTypes
|
||||||
|
{
|
||||||
|
ADVENTURE, BATTLE, OTHER
|
||||||
|
};
|
||||||
|
|
||||||
|
SpellTypes spellType(const CSpell * spell)
|
||||||
|
{
|
||||||
|
if(!spell->isCombat() || spell->isCreatureAbility())
|
||||||
|
return SpellTypes::OTHER;
|
||||||
|
|
||||||
|
if(spell->isOffensive() || spell->hasEffects() || spell->hasBattleEffects())
|
||||||
|
return SpellTypes::BATTLE;
|
||||||
|
|
||||||
|
return SpellTypes::OTHER;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
|
||||||
|
{
|
||||||
|
std::vector<BattleHex> result;
|
||||||
|
|
||||||
|
for(EWallPart wallPart : { EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL })
|
||||||
|
{
|
||||||
|
auto state = cb->getBattle(battleID)->battleGetWallState(wallPart);
|
||||||
|
|
||||||
|
if(state != EWallState::DESTROYED)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto wallHex = cb->getBattle(battleID)->wallPartToBattleHex((EWallPart)wallPart);
|
||||||
|
auto moatHex = wallHex.cloneInDirection(BattleHex::LEFT);
|
||||||
|
|
||||||
|
result.push_back(moatHex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<PossibleSpellcast> BattleEvaluator::findBestCreatureSpell(const CStack *stack)
|
||||||
|
{
|
||||||
|
//TODO: faerie dragon type spell should be selected by server
|
||||||
|
SpellID creatureSpellToCast = cb->getBattle(battleID)->battleGetRandomStackSpell(CRandomGenerator::getDefault(), stack, CBattleInfoCallback::RANDOM_AIMED);
|
||||||
|
if(stack->hasBonusOfType(BonusType::SPELLCASTER) && stack->canCast() && creatureSpellToCast != SpellID::NONE)
|
||||||
|
{
|
||||||
|
const CSpell * spell = creatureSpellToCast.toSpell();
|
||||||
|
|
||||||
|
if(spell->canBeCast(cb->getBattle(battleID).get(), spells::Mode::CREATURE_ACTIVE, stack))
|
||||||
|
{
|
||||||
|
std::vector<PossibleSpellcast> possibleCasts;
|
||||||
|
spells::BattleCast temp(cb->getBattle(battleID).get(), stack, spells::Mode::CREATURE_ACTIVE, spell);
|
||||||
|
for(auto & target : temp.findPotentialTargets())
|
||||||
|
{
|
||||||
|
PossibleSpellcast ps;
|
||||||
|
ps.dest = target;
|
||||||
|
ps.spell = spell;
|
||||||
|
evaluateCreatureSpellcast(stack, ps);
|
||||||
|
possibleCasts.push_back(ps);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(possibleCasts.begin(), possibleCasts.end(), [&](const PossibleSpellcast & lhs, const PossibleSpellcast & rhs) { return lhs.value > rhs.value; });
|
||||||
|
if(!possibleCasts.empty() && possibleCasts.front().value > 0)
|
||||||
|
{
|
||||||
|
return possibleCasts.front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||||
|
{
|
||||||
|
#if BATTLE_TRACE_LEVEL >= 1
|
||||||
|
logAi->trace("Select stack action");
|
||||||
|
#endif
|
||||||
|
//evaluate casting spell for spellcasting stack
|
||||||
|
std::optional<PossibleSpellcast> bestSpellcast = findBestCreatureSpell(stack);
|
||||||
|
|
||||||
|
auto moveTarget = scoreEvaluator.findMoveTowardsUnreachable(stack, *targets, damageCache, hb);
|
||||||
|
float score = EvaluationResult::INEFFECTIVE_SCORE;
|
||||||
|
|
||||||
|
if(targets->possibleAttacks.empty() && bestSpellcast.has_value())
|
||||||
|
{
|
||||||
|
activeActionMade = true;
|
||||||
|
return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!targets->possibleAttacks.empty())
|
||||||
|
{
|
||||||
|
#if BATTLE_TRACE_LEVEL>=1
|
||||||
|
logAi->trace("Evaluating attack for %s", stack->getDescription());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto evaluationResult = scoreEvaluator.findBestTarget(stack, *targets, damageCache, hb);
|
||||||
|
auto & bestAttack = evaluationResult.bestAttack;
|
||||||
|
|
||||||
|
cachedAttack = bestAttack;
|
||||||
|
cachedScore = evaluationResult.score;
|
||||||
|
|
||||||
|
//TODO: consider more complex spellcast evaluation, f.e. because "re-retaliation" during enemy move in same turn for melee attack etc.
|
||||||
|
if(bestSpellcast.has_value() && bestSpellcast->value > bestAttack.damageDiff())
|
||||||
|
{
|
||||||
|
// return because spellcast value is damage dealt and score is dps reduce
|
||||||
|
activeActionMade = true;
|
||||||
|
return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(evaluationResult.score > score)
|
||||||
|
{
|
||||||
|
score = evaluationResult.score;
|
||||||
|
|
||||||
|
logAi->debug("BattleAI: %s -> %s x %d, from %d curpos %d dist %d speed %d: +%2f -%2f = %2f",
|
||||||
|
bestAttack.attackerState->unitType()->getJsonKey(),
|
||||||
|
bestAttack.affectedUnits[0]->unitType()->getJsonKey(),
|
||||||
|
(int)bestAttack.affectedUnits[0]->getCount(),
|
||||||
|
(int)bestAttack.from,
|
||||||
|
(int)bestAttack.attack.attacker->getPosition().hex,
|
||||||
|
bestAttack.attack.chargeDistance,
|
||||||
|
bestAttack.attack.attacker->speed(0, true),
|
||||||
|
bestAttack.defenderDamageReduce,
|
||||||
|
bestAttack.attackerDamageReduce,
|
||||||
|
bestAttack.attackValue()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moveTarget.scorePerTurn <= score)
|
||||||
|
{
|
||||||
|
if(evaluationResult.wait)
|
||||||
|
{
|
||||||
|
return BattleAction::makeWait(stack);
|
||||||
|
}
|
||||||
|
else if(bestAttack.attack.shooting)
|
||||||
|
{
|
||||||
|
activeActionMade = true;
|
||||||
|
return BattleAction::makeShotAttack(stack, bestAttack.attack.defender);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(bestAttack.collateralDamageReduce
|
||||||
|
&& bestAttack.collateralDamageReduce >= bestAttack.defenderDamageReduce / 2
|
||||||
|
&& score < 0)
|
||||||
|
{
|
||||||
|
return BattleAction::makeDefend(stack);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
activeActionMade = true;
|
||||||
|
return BattleAction::makeMeleeAttack(stack, bestAttack.attack.defender->getPosition(), bestAttack.from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code.
|
||||||
|
if(moveTarget.scorePerTurn > score)
|
||||||
|
{
|
||||||
|
score = moveTarget.score;
|
||||||
|
cachedAttack = moveTarget.cachedAttack;
|
||||||
|
cachedScore = score;
|
||||||
|
|
||||||
|
if(stack->waited())
|
||||||
|
{
|
||||||
|
return goTowardsNearest(stack, moveTarget.positions);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return BattleAction::makeWait(stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(score <= EvaluationResult::INEFFECTIVE_SCORE
|
||||||
|
&& !stack->hasBonusOfType(BonusType::FLYING)
|
||||||
|
&& stack->unitSide() == BattleSide::ATTACKER
|
||||||
|
&& cb->getBattle(battleID)->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
|
||||||
|
{
|
||||||
|
auto brokenWallMoat = getBrokenWallMoatHexes();
|
||||||
|
|
||||||
|
if(brokenWallMoat.size())
|
||||||
|
{
|
||||||
|
activeActionMade = true;
|
||||||
|
|
||||||
|
if(stack->doubleWide() && vstd::contains(brokenWallMoat, stack->getPosition()))
|
||||||
|
return BattleAction::makeMove(stack, stack->getPosition().cloneInDirection(BattleHex::RIGHT));
|
||||||
|
else
|
||||||
|
return goTowardsNearest(stack, brokenWallMoat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return BattleAction::makeDefend(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock> start)
|
||||||
|
{
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes)
|
||||||
|
{
|
||||||
|
auto reachability = cb->getBattle(battleID)->getReachability(stack);
|
||||||
|
auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
|
||||||
|
|
||||||
|
if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
|
||||||
|
{
|
||||||
|
return BattleAction::makeDefend(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(hexes.begin(), hexes.end(), [&](BattleHex h1, BattleHex h2) -> bool
|
||||||
|
{
|
||||||
|
return reachability.distances[h1] < reachability.distances[h2];
|
||||||
|
});
|
||||||
|
|
||||||
|
for(auto hex : hexes)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BattleHex bestNeighbor = hexes.front();
|
||||||
|
|
||||||
|
if(reachability.distances[bestNeighbor] > GameConstants::BFIELD_SIZE)
|
||||||
|
{
|
||||||
|
return BattleAction::makeDefend(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
scoreEvaluator.updateReachabilityMap(hb);
|
||||||
|
|
||||||
|
if(stack->hasBonusOfType(BonusType::FLYING))
|
||||||
|
{
|
||||||
|
std::set<BattleHex> obstacleHexes;
|
||||||
|
|
||||||
|
auto insertAffected = [](const CObstacleInstance & spellObst, std::set<BattleHex> obstacleHexes) {
|
||||||
|
auto affectedHexes = spellObst.getAffectedTiles();
|
||||||
|
obstacleHexes.insert(affectedHexes.cbegin(), affectedHexes.cend());
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto & obstacles = hb->battleGetAllObstacles();
|
||||||
|
|
||||||
|
for (const auto & obst: obstacles) {
|
||||||
|
|
||||||
|
if(obst->triggersEffects())
|
||||||
|
{
|
||||||
|
auto triggerAbility = VLC->spells()->getById(obst->getTrigger());
|
||||||
|
auto triggerIsNegative = triggerAbility->isNegative() || triggerAbility->isDamage();
|
||||||
|
|
||||||
|
if(triggerIsNegative)
|
||||||
|
insertAffected(*obst, obstacleHexes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Flying stack doesn't go hex by hex, so we can't backtrack using predecessors.
|
||||||
|
// We just check all available hexes and pick the one closest to the target.
|
||||||
|
auto nearestAvailableHex = vstd::minElementByFun(avHexes, [&](BattleHex hex) -> int
|
||||||
|
{
|
||||||
|
const int NEGATIVE_OBSTACLE_PENALTY = 100; // avoid landing on negative obstacle (moat, fire wall, etc)
|
||||||
|
const int BLOCKED_STACK_PENALTY = 100; // avoid landing on moat
|
||||||
|
|
||||||
|
auto distance = BattleHex::getDistance(bestNeighbor, hex);
|
||||||
|
|
||||||
|
if(vstd::contains(obstacleHexes, hex))
|
||||||
|
distance += NEGATIVE_OBSTACLE_PENALTY;
|
||||||
|
|
||||||
|
return scoreEvaluator.checkPositionBlocksOurStacks(*hb, stack, hex) ? BLOCKED_STACK_PENALTY + distance : distance;
|
||||||
|
});
|
||||||
|
|
||||||
|
return BattleAction::makeMove(stack, *nearestAvailableHex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BattleHex currentDest = bestNeighbor;
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
if(!currentDest.isValid())
|
||||||
|
{
|
||||||
|
return BattleAction::makeDefend(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(vstd::contains(avHexes, currentDest)
|
||||||
|
&& !scoreEvaluator.checkPositionBlocksOurStacks(*hb, stack, currentDest))
|
||||||
|
return BattleAction::makeMove(stack, currentDest);
|
||||||
|
|
||||||
|
currentDest = reachability.predecessors[currentDest];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleEvaluator::canCastSpell()
|
||||||
|
{
|
||||||
|
auto hero = cb->getBattle(battleID)->battleGetMyHero();
|
||||||
|
if(!hero)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return cb->getBattle(battleID)->battleCanCastSpell(hero, spells::Mode::HERO) == ESpellCastProblem::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||||
|
{
|
||||||
|
auto hero = cb->getBattle(battleID)->battleGetMyHero();
|
||||||
|
if(!hero)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
LOGL("Casting spells sounds like fun. Let's see...");
|
||||||
|
//Get all spells we can cast
|
||||||
|
std::vector<const CSpell*> possibleSpells;
|
||||||
|
vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [hero, this](const CSpell *s) -> bool
|
||||||
|
{
|
||||||
|
return s->canBeCast(cb->getBattle(battleID).get(), spells::Mode::HERO, hero);
|
||||||
|
});
|
||||||
|
LOGFL("I can cast %d spells.", possibleSpells.size());
|
||||||
|
|
||||||
|
vstd::erase_if(possibleSpells, [](const CSpell *s)
|
||||||
|
{
|
||||||
|
return spellType(s) != SpellTypes::BATTLE || s->getTargetType() == spells::AimType::LOCATION;
|
||||||
|
});
|
||||||
|
|
||||||
|
LOGFL("I know how %d of them works.", possibleSpells.size());
|
||||||
|
|
||||||
|
//Get possible spell-target pairs
|
||||||
|
std::vector<PossibleSpellcast> possibleCasts;
|
||||||
|
for(auto spell : possibleSpells)
|
||||||
|
{
|
||||||
|
spells::BattleCast temp(cb->getBattle(battleID).get(), hero, spells::Mode::HERO, spell);
|
||||||
|
|
||||||
|
if(spell->getTargetType() == spells::AimType::LOCATION)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const bool FAST = true;
|
||||||
|
|
||||||
|
for(auto & target : temp.findPotentialTargets(FAST))
|
||||||
|
{
|
||||||
|
PossibleSpellcast ps;
|
||||||
|
ps.dest = target;
|
||||||
|
ps.spell = spell;
|
||||||
|
possibleCasts.push_back(ps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOGFL("Found %d spell-target combinations.", possibleCasts.size());
|
||||||
|
if(possibleCasts.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
using ValueMap = PossibleSpellcast::ValueMap;
|
||||||
|
|
||||||
|
auto evaluateQueue = [&](ValueMap & values, const std::vector<battle::Units> & queue, std::shared_ptr<HypotheticBattle> state, size_t minTurnSpan, bool * enemyHadTurnOut) -> bool
|
||||||
|
{
|
||||||
|
bool firstRound = true;
|
||||||
|
bool enemyHadTurn = false;
|
||||||
|
size_t ourTurnSpan = 0;
|
||||||
|
|
||||||
|
bool stop = false;
|
||||||
|
|
||||||
|
for(auto & round : queue)
|
||||||
|
{
|
||||||
|
if(!firstRound)
|
||||||
|
state->nextRound();
|
||||||
|
for(auto unit : round)
|
||||||
|
{
|
||||||
|
if(!vstd::contains(values, unit->unitId()))
|
||||||
|
values[unit->unitId()] = 0;
|
||||||
|
|
||||||
|
if(!unit->alive())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(state->battleGetOwner(unit) != playerID)
|
||||||
|
{
|
||||||
|
enemyHadTurn = true;
|
||||||
|
|
||||||
|
if(!firstRound || state->battleCastSpells(unit->unitSide()) == 0)
|
||||||
|
{
|
||||||
|
//enemy could counter our spell at this point
|
||||||
|
//anyway, we do not know what enemy will do
|
||||||
|
//just stop evaluation
|
||||||
|
stop = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(!enemyHadTurn)
|
||||||
|
{
|
||||||
|
ourTurnSpan++;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->nextTurn(unit->unitId());
|
||||||
|
|
||||||
|
PotentialTargets pt(unit, damageCache, state);
|
||||||
|
|
||||||
|
if(!pt.possibleAttacks.empty())
|
||||||
|
{
|
||||||
|
AttackPossibility ap = pt.bestAction();
|
||||||
|
|
||||||
|
auto swb = state->getForUpdate(unit->unitId());
|
||||||
|
*swb = *ap.attackerState;
|
||||||
|
|
||||||
|
if(ap.defenderDamageReduce > 0)
|
||||||
|
swb->removeUnitBonus(Bonus::UntilAttack);
|
||||||
|
if(ap.attackerDamageReduce > 0)
|
||||||
|
swb->removeUnitBonus(Bonus::UntilBeingAttacked);
|
||||||
|
|
||||||
|
for(auto affected : ap.affectedUnits)
|
||||||
|
{
|
||||||
|
swb = state->getForUpdate(affected->unitId());
|
||||||
|
*swb = *affected;
|
||||||
|
|
||||||
|
if(ap.defenderDamageReduce > 0)
|
||||||
|
swb->removeUnitBonus(Bonus::UntilBeingAttacked);
|
||||||
|
if(ap.attackerDamageReduce > 0 && ap.attack.defender->unitId() == affected->unitId())
|
||||||
|
swb->removeUnitBonus(Bonus::UntilAttack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bav = pt.bestActionValue();
|
||||||
|
|
||||||
|
//best action is from effective owner`s point if view, we need to convert to our point if view
|
||||||
|
if(state->battleGetOwner(unit) != playerID)
|
||||||
|
bav = -bav;
|
||||||
|
values[unit->unitId()] += bav;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstRound = false;
|
||||||
|
|
||||||
|
if(stop)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(enemyHadTurnOut)
|
||||||
|
*enemyHadTurnOut = enemyHadTurn;
|
||||||
|
|
||||||
|
return ourTurnSpan >= minTurnSpan;
|
||||||
|
};
|
||||||
|
|
||||||
|
ValueMap valueOfStack;
|
||||||
|
ValueMap healthOfStack;
|
||||||
|
|
||||||
|
TStacks all = cb->getBattle(battleID)->battleGetAllStacks(false);
|
||||||
|
|
||||||
|
size_t ourRemainingTurns = 0;
|
||||||
|
|
||||||
|
for(auto unit : all)
|
||||||
|
{
|
||||||
|
healthOfStack[unit->unitId()] = unit->getAvailableHealth();
|
||||||
|
valueOfStack[unit->unitId()] = 0;
|
||||||
|
|
||||||
|
if(cb->getBattle(battleID)->battleGetOwner(unit) == playerID && unit->canMove() && !unit->moved())
|
||||||
|
ourRemainingTurns++;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGFL("I have %d turns left in this round", ourRemainingTurns);
|
||||||
|
|
||||||
|
const bool castNow = ourRemainingTurns <= 1;
|
||||||
|
|
||||||
|
if(castNow)
|
||||||
|
print("I should try to cast a spell now");
|
||||||
|
else
|
||||||
|
print("I could wait better moment to cast a spell");
|
||||||
|
|
||||||
|
auto amount = all.size();
|
||||||
|
|
||||||
|
std::vector<battle::Units> turnOrder;
|
||||||
|
|
||||||
|
cb->getBattle(battleID)->battleGetTurnOrder(turnOrder, amount, 2); //no more than 1 turn after current, each unit at least once
|
||||||
|
|
||||||
|
{
|
||||||
|
bool enemyHadTurn = false;
|
||||||
|
|
||||||
|
auto state = std::make_shared<HypotheticBattle>(env.get(), cb->getBattle(battleID));
|
||||||
|
|
||||||
|
evaluateQueue(valueOfStack, turnOrder, state, 0, &enemyHadTurn);
|
||||||
|
|
||||||
|
if(!enemyHadTurn)
|
||||||
|
{
|
||||||
|
auto battleIsFinishedOpt = state->battleIsFinished();
|
||||||
|
|
||||||
|
if(battleIsFinishedOpt)
|
||||||
|
{
|
||||||
|
print("No need to cast a spell. Battle will finish soon.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CStopWatch timer;
|
||||||
|
|
||||||
|
#if BATTLE_TRACE_LEVEL >= 1
|
||||||
|
tbb::blocked_range<size_t> r(0, possibleCasts.size());
|
||||||
|
#else
|
||||||
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, possibleCasts.size()), [&](const tbb::blocked_range<size_t> & r)
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
for(auto i = r.begin(); i != r.end(); i++)
|
||||||
|
{
|
||||||
|
auto & ps = possibleCasts[i];
|
||||||
|
|
||||||
|
#if BATTLE_TRACE_LEVEL >= 1
|
||||||
|
logAi->trace("Evaluating %s", ps.spell->getNameTranslated());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto state = std::make_shared<HypotheticBattle>(env.get(), cb->getBattle(battleID));
|
||||||
|
|
||||||
|
spells::BattleCast cast(state.get(), hero, spells::Mode::HERO, ps.spell);
|
||||||
|
cast.castEval(state->getServerCallback(), ps.dest);
|
||||||
|
|
||||||
|
auto allUnits = state->battleGetUnitsIf([](const battle::Unit * u) -> bool { return true; });
|
||||||
|
|
||||||
|
auto needFullEval = vstd::contains_if(allUnits, [&](const battle::Unit * u) -> bool
|
||||||
|
{
|
||||||
|
auto original = cb->getBattle(battleID)->battleGetUnitByID(u->unitId());
|
||||||
|
return !original || u->speed() != original->speed();
|
||||||
|
});
|
||||||
|
|
||||||
|
DamageCache safeCopy = damageCache;
|
||||||
|
DamageCache innerCache(&safeCopy);
|
||||||
|
innerCache.buildDamageCache(state, side);
|
||||||
|
|
||||||
|
if(needFullEval || !cachedAttack)
|
||||||
|
{
|
||||||
|
#if BATTLE_TRACE_LEVEL >= 1
|
||||||
|
logAi->trace("Full evaluation is started due to stack speed affected.");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
PotentialTargets innerTargets(activeStack, innerCache, state);
|
||||||
|
BattleExchangeEvaluator innerEvaluator(state, env, strengthRatio);
|
||||||
|
|
||||||
|
if(!innerTargets.possibleAttacks.empty())
|
||||||
|
{
|
||||||
|
innerEvaluator.updateReachabilityMap(state);
|
||||||
|
|
||||||
|
auto newStackAction = innerEvaluator.findBestTarget(activeStack, innerTargets, innerCache, state);
|
||||||
|
|
||||||
|
ps.value = newStackAction.score;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ps.value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ps.value = scoreEvaluator.calculateExchange(*cachedAttack, *targets, innerCache, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto unit : allUnits)
|
||||||
|
{
|
||||||
|
auto newHealth = unit->getAvailableHealth();
|
||||||
|
auto oldHealth = healthOfStack[unit->unitId()];
|
||||||
|
|
||||||
|
if(oldHealth != newHealth)
|
||||||
|
{
|
||||||
|
auto damage = std::abs(oldHealth - newHealth);
|
||||||
|
auto originalDefender = cb->getBattle(battleID)->battleGetUnitByID(unit->unitId());
|
||||||
|
|
||||||
|
auto dpsReduce = AttackPossibility::calculateDamageReduce(
|
||||||
|
nullptr,
|
||||||
|
originalDefender && originalDefender->alive() ? originalDefender : unit,
|
||||||
|
damage,
|
||||||
|
innerCache,
|
||||||
|
state);
|
||||||
|
|
||||||
|
auto ourUnit = unit->unitSide() == side ? 1 : -1;
|
||||||
|
auto goodEffect = newHealth > oldHealth ? 1 : -1;
|
||||||
|
|
||||||
|
if(ourUnit * goodEffect == 1)
|
||||||
|
{
|
||||||
|
if(ourUnit && goodEffect && (unit->isClone() || unit->isGhost() || !unit->unitSlot().validSlot()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ps.value += dpsReduce * scoreEvaluator.getPositiveEffectMultiplier();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ps.value -= dpsReduce * scoreEvaluator.getNegativeEffectMultiplier();
|
||||||
|
|
||||||
|
#if BATTLE_TRACE_LEVEL >= 1
|
||||||
|
logAi->trace(
|
||||||
|
"Spell affects %s (%d), dps: %2f",
|
||||||
|
unit->creatureId().toCreature()->getNameSingularTranslated(),
|
||||||
|
unit->getCount(),
|
||||||
|
dpsReduce);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if BATTLE_TRACE_LEVEL >= 1
|
||||||
|
logAi->trace("Total score: %2f", ps.value);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#if BATTLE_TRACE_LEVEL == 0
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LOGFL("Evaluation took %d ms", timer.getDiff());
|
||||||
|
|
||||||
|
auto pscValue = [](const PossibleSpellcast &ps) -> float
|
||||||
|
{
|
||||||
|
return ps.value;
|
||||||
|
};
|
||||||
|
auto castToPerform = *vstd::maxElementByFun(possibleCasts, pscValue);
|
||||||
|
|
||||||
|
if(castToPerform.value > cachedScore)
|
||||||
|
{
|
||||||
|
LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->getNameTranslated() % castToPerform.value);
|
||||||
|
BattleAction spellcast;
|
||||||
|
spellcast.actionType = EActionType::HERO_SPELL;
|
||||||
|
spellcast.spell = castToPerform.spell->id;
|
||||||
|
spellcast.setTarget(castToPerform.dest);
|
||||||
|
spellcast.side = side;
|
||||||
|
spellcast.stackNumber = (!side) ? -1 : -2;
|
||||||
|
cb->battleMakeSpellAction(battleID, spellcast);
|
||||||
|
activeActionMade = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGFL("Best spell is %s. But it is actually useless (value %d).", castToPerform.spell->getNameTranslated() % castToPerform.value);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Below method works only for offensive spells
|
||||||
|
void BattleEvaluator::evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps)
|
||||||
|
{
|
||||||
|
using ValueMap = PossibleSpellcast::ValueMap;
|
||||||
|
|
||||||
|
RNGStub rngStub;
|
||||||
|
HypotheticBattle state(env.get(), cb->getBattle(battleID));
|
||||||
|
TStacks all = cb->getBattle(battleID)->battleGetAllStacks(false);
|
||||||
|
|
||||||
|
ValueMap healthOfStack;
|
||||||
|
ValueMap newHealthOfStack;
|
||||||
|
|
||||||
|
for(auto unit : all)
|
||||||
|
{
|
||||||
|
healthOfStack[unit->unitId()] = unit->getAvailableHealth();
|
||||||
|
}
|
||||||
|
|
||||||
|
spells::BattleCast cast(&state, stack, spells::Mode::CREATURE_ACTIVE, ps.spell);
|
||||||
|
cast.castEval(state.getServerCallback(), ps.dest);
|
||||||
|
|
||||||
|
for(auto unit : all)
|
||||||
|
{
|
||||||
|
auto unitId = unit->unitId();
|
||||||
|
auto localUnit = state.battleGetUnitByID(unitId);
|
||||||
|
newHealthOfStack[unitId] = localUnit->getAvailableHealth();
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t totalGain = 0;
|
||||||
|
|
||||||
|
for(auto unit : all)
|
||||||
|
{
|
||||||
|
auto unitId = unit->unitId();
|
||||||
|
auto localUnit = state.battleGetUnitByID(unitId);
|
||||||
|
|
||||||
|
auto healthDiff = newHealthOfStack[unitId] - healthOfStack[unitId];
|
||||||
|
|
||||||
|
if(localUnit->unitOwner() != cb->getBattle(battleID)->getPlayerID())
|
||||||
|
healthDiff = -healthDiff;
|
||||||
|
|
||||||
|
if(healthDiff < 0)
|
||||||
|
{
|
||||||
|
ps.value = -1;
|
||||||
|
return; //do not damage own units at all
|
||||||
|
}
|
||||||
|
|
||||||
|
totalGain += healthDiff;
|
||||||
|
}
|
||||||
|
|
||||||
|
ps.value = totalGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BattleEvaluator::print(const std::string & text) const
|
||||||
|
{
|
||||||
|
logAi->trace("%s Battle AI[%p]: %s", playerID.toString(), this, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
83
AI/BattleAI/BattleEvaluator.h
Normal file
83
AI/BattleAI/BattleEvaluator.h
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* BattleEvaluator.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/AI_Base.h"
|
||||||
|
#include "../../lib/battle/ReachabilityInfo.h"
|
||||||
|
#include "PossibleSpellcast.h"
|
||||||
|
#include "PotentialTargets.h"
|
||||||
|
#include "BattleExchangeVariant.h"
|
||||||
|
|
||||||
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
class CSpell;
|
||||||
|
|
||||||
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
|
||||||
|
class EnemyInfo;
|
||||||
|
|
||||||
|
class BattleEvaluator
|
||||||
|
{
|
||||||
|
std::unique_ptr<PotentialTargets> targets;
|
||||||
|
std::shared_ptr<HypotheticBattle> hb;
|
||||||
|
BattleExchangeEvaluator scoreEvaluator;
|
||||||
|
std::shared_ptr<CBattleCallback> cb;
|
||||||
|
std::shared_ptr<Environment> env;
|
||||||
|
bool activeActionMade = false;
|
||||||
|
std::optional<AttackPossibility> cachedAttack;
|
||||||
|
PlayerColor playerID;
|
||||||
|
BattleID battleID;
|
||||||
|
int side;
|
||||||
|
float cachedScore;
|
||||||
|
DamageCache damageCache;
|
||||||
|
float strengthRatio;
|
||||||
|
|
||||||
|
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);
|
||||||
|
std::vector<BattleHex> getBrokenWallMoatHexes() const;
|
||||||
|
void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
|
||||||
|
void print(const std::string & text) const;
|
||||||
|
|
||||||
|
BattleEvaluator(
|
||||||
|
std::shared_ptr<Environment> env,
|
||||||
|
std::shared_ptr<CBattleCallback> cb,
|
||||||
|
const battle::Unit * activeStack,
|
||||||
|
PlayerColor playerID,
|
||||||
|
BattleID battleID,
|
||||||
|
int 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
int 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;
|
||||||
|
}
|
||||||
|
};
|
@ -18,75 +18,135 @@ AttackerValue::AttackerValue()
|
|||||||
}
|
}
|
||||||
|
|
||||||
MoveTarget::MoveTarget()
|
MoveTarget::MoveTarget()
|
||||||
: positions()
|
: positions(), cachedAttack()
|
||||||
{
|
{
|
||||||
score = EvaluationResult::INEFFECTIVE_SCORE;
|
score = EvaluationResult::INEFFECTIVE_SCORE;
|
||||||
|
scorePerTurn = EvaluationResult::INEFFECTIVE_SCORE;
|
||||||
|
turnsToRich = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t BattleExchangeVariant::trackAttack(const AttackPossibility & ap, HypotheticBattle & state)
|
float BattleExchangeVariant::trackAttack(
|
||||||
|
const AttackPossibility & ap,
|
||||||
|
std::shared_ptr<HypotheticBattle> hb,
|
||||||
|
DamageCache & damageCache)
|
||||||
{
|
{
|
||||||
|
auto attacker = hb->getForUpdate(ap.attack.attacker->unitId());
|
||||||
|
|
||||||
|
const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
|
||||||
|
static const auto selectorBlocksRetaliation = Selector::type()(BonusType::BLOCKS_RETALIATION);
|
||||||
|
const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
|
||||||
|
|
||||||
|
float attackValue = 0;
|
||||||
auto affectedUnits = ap.affectedUnits;
|
auto affectedUnits = ap.affectedUnits;
|
||||||
|
|
||||||
affectedUnits.push_back(ap.attackerState);
|
affectedUnits.push_back(ap.attackerState);
|
||||||
|
|
||||||
for(auto affectedUnit : affectedUnits)
|
for(auto affectedUnit : affectedUnits)
|
||||||
{
|
{
|
||||||
auto unitToUpdate = state.getForUpdate(affectedUnit->unitId());
|
auto unitToUpdate = hb->getForUpdate(affectedUnit->unitId());
|
||||||
|
|
||||||
unitToUpdate->health = affectedUnit->health;
|
if(unitToUpdate->unitSide() == attacker->unitSide())
|
||||||
unitToUpdate->shots = affectedUnit->shots;
|
{
|
||||||
unitToUpdate->counterAttacks = affectedUnit->counterAttacks;
|
if(unitToUpdate->unitId() == attacker->unitId())
|
||||||
unitToUpdate->movedThisRound = affectedUnit->movedThisRound;
|
{
|
||||||
}
|
auto defender = hb->getForUpdate(ap.attack.defender->unitId());
|
||||||
|
|
||||||
auto attackValue = ap.attackValue();
|
if(!defender->alive() || counterAttacksBlocked || ap.attack.shooting || !defender->ableToRetaliate())
|
||||||
|
continue;
|
||||||
|
|
||||||
dpsScore += attackValue;
|
auto retaliationDamage = damageCache.getDamage(defender.get(), unitToUpdate.get(), hb);
|
||||||
|
auto attackerDamageReduce = AttackPossibility::calculateDamageReduce(defender.get(), unitToUpdate.get(), retaliationDamage, damageCache, hb);
|
||||||
|
|
||||||
|
attackValue -= attackerDamageReduce;
|
||||||
|
dpsScore -= attackerDamageReduce * negativeEffectMultiplier;
|
||||||
|
attackerValue[unitToUpdate->unitId()].isRetalitated = true;
|
||||||
|
|
||||||
|
unitToUpdate->damage(retaliationDamage);
|
||||||
|
defender->afterAttack(false, true);
|
||||||
|
|
||||||
#if BATTLE_TRACE_LEVEL>=1
|
#if BATTLE_TRACE_LEVEL>=1
|
||||||
logAi->trace(
|
logAi->trace(
|
||||||
"%s -> %s, ap attack, %s, dps: %lld, score: %lld",
|
"%s -> %s, ap retalitation, %s, dps: %2f, score: %2f",
|
||||||
ap.attack.attacker->getDescription(),
|
defender->getDescription(),
|
||||||
ap.attack.defender->getDescription(),
|
unitToUpdate->getDescription(),
|
||||||
ap.attack.shooting ? "shot" : "mellee",
|
ap.attack.shooting ? "shot" : "mellee",
|
||||||
ap.damageDealt,
|
retaliationDamage,
|
||||||
attackValue);
|
attackerDamageReduce);
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto collateralDamage = damageCache.getDamage(attacker.get(), unitToUpdate.get(), hb);
|
||||||
|
auto collateralDamageReduce = AttackPossibility::calculateDamageReduce(attacker.get(), unitToUpdate.get(), collateralDamage, damageCache, hb);
|
||||||
|
|
||||||
|
attackValue -= collateralDamageReduce;
|
||||||
|
dpsScore -= collateralDamageReduce * negativeEffectMultiplier;
|
||||||
|
|
||||||
|
unitToUpdate->damage(collateralDamage);
|
||||||
|
|
||||||
|
#if BATTLE_TRACE_LEVEL>=1
|
||||||
|
logAi->trace(
|
||||||
|
"%s -> %s, ap collateral, %s, dps: %2f, score: %2f",
|
||||||
|
attacker->getDescription(),
|
||||||
|
unitToUpdate->getDescription(),
|
||||||
|
ap.attack.shooting ? "shot" : "mellee",
|
||||||
|
collateralDamage,
|
||||||
|
collateralDamageReduce);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int64_t attackDamage = damageCache.getDamage(attacker.get(), unitToUpdate.get(), hb);
|
||||||
|
float defenderDamageReduce = AttackPossibility::calculateDamageReduce(attacker.get(), unitToUpdate.get(), attackDamage, damageCache, hb);
|
||||||
|
|
||||||
|
attackValue += defenderDamageReduce;
|
||||||
|
dpsScore += defenderDamageReduce * positiveEffectMultiplier;
|
||||||
|
attackerValue[attacker->unitId()].value += defenderDamageReduce;
|
||||||
|
|
||||||
|
unitToUpdate->damage(attackDamage);
|
||||||
|
|
||||||
|
#if BATTLE_TRACE_LEVEL>=1
|
||||||
|
logAi->trace(
|
||||||
|
"%s -> %s, ap attack, %s, dps: %2f, score: %2f",
|
||||||
|
attacker->getDescription(),
|
||||||
|
unitToUpdate->getDescription(),
|
||||||
|
ap.attack.shooting ? "shot" : "mellee",
|
||||||
|
attackDamage,
|
||||||
|
defenderDamageReduce);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attackValue += ap.shootersBlockedDmg;
|
||||||
|
dpsScore += ap.shootersBlockedDmg * positiveEffectMultiplier;
|
||||||
|
attacker->afterAttack(ap.attack.shooting, false);
|
||||||
|
|
||||||
return attackValue;
|
return attackValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t BattleExchangeVariant::trackAttack(
|
float BattleExchangeVariant::trackAttack(
|
||||||
std::shared_ptr<StackWithBonuses> attacker,
|
std::shared_ptr<StackWithBonuses> attacker,
|
||||||
std::shared_ptr<StackWithBonuses> defender,
|
std::shared_ptr<StackWithBonuses> defender,
|
||||||
bool shooting,
|
bool shooting,
|
||||||
bool isOurAttack,
|
bool isOurAttack,
|
||||||
const CBattleInfoCallback & cb,
|
DamageCache & damageCache,
|
||||||
|
std::shared_ptr<HypotheticBattle> hb,
|
||||||
bool evaluateOnly)
|
bool evaluateOnly)
|
||||||
{
|
{
|
||||||
const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
|
const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
|
||||||
static const auto selectorBlocksRetaliation = Selector::type()(BonusType::BLOCKS_RETALIATION);
|
static const auto selectorBlocksRetaliation = Selector::type()(BonusType::BLOCKS_RETALIATION);
|
||||||
const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
|
const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
|
||||||
|
|
||||||
DamageEstimation retaliation;
|
int64_t attackDamage = damageCache.getDamage(attacker.get(), defender.get(), hb);
|
||||||
// FIXME: provide distance info for Jousting bonus
|
float defenderDamageReduce = AttackPossibility::calculateDamageReduce(attacker.get(), defender.get(), attackDamage, damageCache, hb);
|
||||||
BattleAttackInfo bai(attacker.get(), defender.get(), 0, shooting);
|
float attackerDamageReduce = 0;
|
||||||
|
|
||||||
if(shooting)
|
|
||||||
{
|
|
||||||
bai.attackerPos.setXY(8, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto attack = cb.battleEstimateDamage(bai, &retaliation);
|
|
||||||
int64_t attackDamage = (attack.damage.min + attack.damage.max) / 2;
|
|
||||||
int64_t defenderDamageReduce = AttackPossibility::calculateDamageReduce(attacker.get(), defender.get(), attackDamage, cb);
|
|
||||||
int64_t attackerDamageReduce = 0;
|
|
||||||
|
|
||||||
if(!evaluateOnly)
|
if(!evaluateOnly)
|
||||||
{
|
{
|
||||||
#if BATTLE_TRACE_LEVEL>=1
|
#if BATTLE_TRACE_LEVEL>=1
|
||||||
logAi->trace(
|
logAi->trace(
|
||||||
"%s -> %s, normal attack, %s, dps: %lld, %lld",
|
"%s -> %s, normal attack, %s, dps: %lld, %2f",
|
||||||
attacker->getDescription(),
|
attacker->getDescription(),
|
||||||
defender->getDescription(),
|
defender->getDescription(),
|
||||||
shooting ? "shot" : "mellee",
|
shooting ? "shot" : "mellee",
|
||||||
@ -96,28 +156,24 @@ int64_t BattleExchangeVariant::trackAttack(
|
|||||||
|
|
||||||
if(isOurAttack)
|
if(isOurAttack)
|
||||||
{
|
{
|
||||||
dpsScore += defenderDamageReduce;
|
dpsScore += defenderDamageReduce * positiveEffectMultiplier;
|
||||||
attackerValue[attacker->unitId()].value += defenderDamageReduce;
|
attackerValue[attacker->unitId()].value += defenderDamageReduce;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
dpsScore -= defenderDamageReduce;
|
dpsScore -= defenderDamageReduce * negativeEffectMultiplier;
|
||||||
|
|
||||||
defender->damage(attackDamage);
|
defender->damage(attackDamage);
|
||||||
attacker->afterAttack(shooting, false);
|
attacker->afterAttack(shooting, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(defender->alive() && defender->ableToRetaliate() && !counterAttacksBlocked && !shooting)
|
if(!evaluateOnly && defender->alive() && defender->ableToRetaliate() && !counterAttacksBlocked && !shooting)
|
||||||
{
|
{
|
||||||
if(retaliation.damage.max != 0)
|
auto retaliationDamage = damageCache.getDamage(defender.get(), attacker.get(), hb);
|
||||||
{
|
attackerDamageReduce = AttackPossibility::calculateDamageReduce(defender.get(), attacker.get(), retaliationDamage, damageCache, hb);
|
||||||
auto retaliationDamage = (retaliation.damage.min + retaliation.damage.max) / 2;
|
|
||||||
attackerDamageReduce = AttackPossibility::calculateDamageReduce(defender.get(), attacker.get(), retaliationDamage, cb);
|
|
||||||
|
|
||||||
if(!evaluateOnly)
|
|
||||||
{
|
|
||||||
#if BATTLE_TRACE_LEVEL>=1
|
#if BATTLE_TRACE_LEVEL>=1
|
||||||
logAi->trace(
|
logAi->trace(
|
||||||
"%s -> %s, retaliation, dps: %lld, %lld",
|
"%s -> %s, retaliation, dps: %lld, %2f",
|
||||||
defender->getDescription(),
|
defender->getDescription(),
|
||||||
attacker->getDescription(),
|
attacker->getDescription(),
|
||||||
retaliationDamage,
|
retaliationDamage,
|
||||||
@ -126,64 +182,55 @@ int64_t BattleExchangeVariant::trackAttack(
|
|||||||
|
|
||||||
if(isOurAttack)
|
if(isOurAttack)
|
||||||
{
|
{
|
||||||
dpsScore -= attackerDamageReduce;
|
dpsScore -= attackerDamageReduce * negativeEffectMultiplier;
|
||||||
attackerValue[attacker->unitId()].isRetalitated = true;
|
attackerValue[attacker->unitId()].isRetalitated = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dpsScore += attackerDamageReduce;
|
dpsScore += attackerDamageReduce * positiveEffectMultiplier;
|
||||||
attackerValue[defender->unitId()].value += attackerDamageReduce;
|
attackerValue[defender->unitId()].value += attackerDamageReduce;
|
||||||
}
|
}
|
||||||
|
|
||||||
attacker->damage(retaliationDamage);
|
attacker->damage(retaliationDamage);
|
||||||
defender->afterAttack(false, true);
|
defender->afterAttack(false, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto score = defenderDamageReduce - attackerDamageReduce;
|
auto score = defenderDamageReduce - attackerDamageReduce;
|
||||||
|
|
||||||
#if BATTLE_TRACE_LEVEL>=1
|
#if BATTLE_TRACE_LEVEL>=1
|
||||||
if(!score)
|
if(!score)
|
||||||
{
|
{
|
||||||
logAi->trace("Attack has zero score d:%lld a:%lld", defenderDamageReduce, attackerDamageReduce);
|
logAi->trace("Attack has zero score d:%2f a:%2f", defenderDamageReduce, attackerDamageReduce);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
EvaluationResult BattleExchangeEvaluator::findBestTarget(const battle::Unit * activeStack, PotentialTargets & targets, HypotheticBattle & hb)
|
EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||||
|
const battle::Unit * activeStack,
|
||||||
|
PotentialTargets & targets,
|
||||||
|
DamageCache & damageCache,
|
||||||
|
std::shared_ptr<HypotheticBattle> hb)
|
||||||
{
|
{
|
||||||
EvaluationResult result(targets.bestAction());
|
EvaluationResult result(targets.bestAction());
|
||||||
|
|
||||||
updateReachabilityMap(hb);
|
|
||||||
|
|
||||||
for(auto & ap : targets.possibleAttacks)
|
|
||||||
{
|
|
||||||
int64_t score = calculateExchange(ap, targets, hb);
|
|
||||||
|
|
||||||
if(score > result.score)
|
|
||||||
{
|
|
||||||
result.score = score;
|
|
||||||
result.bestAttack = ap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!activeStack->waited())
|
if(!activeStack->waited())
|
||||||
{
|
{
|
||||||
#if BATTLE_TRACE_LEVEL>=1
|
#if BATTLE_TRACE_LEVEL>=1
|
||||||
logAi->trace("Evaluating waited attack for %s", activeStack->getDescription());
|
logAi->trace("Evaluating waited attack for %s", activeStack->getDescription());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
hb.getForUpdate(activeStack->unitId())->waiting = true;
|
auto hbWaited = std::make_shared<HypotheticBattle>(env.get(), hb);
|
||||||
hb.getForUpdate(activeStack->unitId())->waitedThisTurn = true;
|
|
||||||
|
|
||||||
updateReachabilityMap(hb);
|
hbWaited->getForUpdate(activeStack->unitId())->waiting = true;
|
||||||
|
hbWaited->getForUpdate(activeStack->unitId())->waitedThisTurn = true;
|
||||||
|
|
||||||
|
updateReachabilityMap(hbWaited);
|
||||||
|
|
||||||
for(auto & ap : targets.possibleAttacks)
|
for(auto & ap : targets.possibleAttacks)
|
||||||
{
|
{
|
||||||
int64_t score = calculateExchange(ap, targets, hb);
|
float score = calculateExchange(ap, targets, damageCache, hbWaited);
|
||||||
|
|
||||||
if(score > result.score)
|
if(score > result.score)
|
||||||
{
|
{
|
||||||
@ -194,13 +241,35 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(const battle::Unit * ac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if BATTLE_TRACE_LEVEL>=1
|
||||||
|
logAi->trace("Evaluating normal attack for %s", activeStack->getDescription());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
updateReachabilityMap(hb);
|
||||||
|
|
||||||
|
for(auto & ap : targets.possibleAttacks)
|
||||||
|
{
|
||||||
|
float score = calculateExchange(ap, targets, damageCache, hb);
|
||||||
|
|
||||||
|
if(score >= result.score)
|
||||||
|
{
|
||||||
|
result.score = score;
|
||||||
|
result.bestAttack = ap;
|
||||||
|
result.wait = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(const battle::Unit * activeStack, PotentialTargets & targets, HypotheticBattle & hb)
|
MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||||
|
const battle::Unit * activeStack,
|
||||||
|
PotentialTargets & targets,
|
||||||
|
DamageCache & damageCache,
|
||||||
|
std::shared_ptr<HypotheticBattle> hb)
|
||||||
{
|
{
|
||||||
MoveTarget result;
|
MoveTarget result;
|
||||||
BattleExchangeVariant ev;
|
BattleExchangeVariant ev(getPositiveEffectMultiplier(), getNegativeEffectMultiplier());
|
||||||
|
|
||||||
if(targets.unreachableEnemies.empty())
|
if(targets.unreachableEnemies.empty())
|
||||||
return result;
|
return result;
|
||||||
@ -237,16 +306,20 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(const battle::Uni
|
|||||||
{
|
{
|
||||||
// FIXME: provide distance info for Jousting bonus
|
// FIXME: provide distance info for Jousting bonus
|
||||||
auto bai = BattleAttackInfo(activeStack, closestStack, 0, cb->battleCanShoot(activeStack));
|
auto bai = BattleAttackInfo(activeStack, closestStack, 0, cb->battleCanShoot(activeStack));
|
||||||
auto attack = AttackPossibility::evaluate(bai, hex, hb);
|
auto attack = AttackPossibility::evaluate(bai, hex, damageCache, hb);
|
||||||
|
|
||||||
attack.shootersBlockedDmg = 0; // we do not want to count on it, it is not for sure
|
attack.shootersBlockedDmg = 0; // we do not want to count on it, it is not for sure
|
||||||
|
|
||||||
auto score = calculateExchange(attack, targets, hb) / turnsToRich;
|
auto score = calculateExchange(attack, targets, damageCache, hb);
|
||||||
|
auto scorePerTurn = score / turnsToRich;
|
||||||
|
|
||||||
if(result.score < score)
|
if(result.scorePerTurn < scorePerTurn)
|
||||||
{
|
{
|
||||||
|
result.scorePerTurn = scorePerTurn;
|
||||||
result.score = score;
|
result.score = score;
|
||||||
result.positions = closestStack->getAttackableHexes(activeStack);
|
result.positions = closestStack->getAttackableHexes(activeStack);
|
||||||
|
result.cachedAttack = attack;
|
||||||
|
result.turnsToRich = turnsToRich;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -287,7 +360,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getAdjacentUnits(cons
|
|||||||
std::vector<const battle::Unit *> BattleExchangeEvaluator::getExchangeUnits(
|
std::vector<const battle::Unit *> BattleExchangeEvaluator::getExchangeUnits(
|
||||||
const AttackPossibility & ap,
|
const AttackPossibility & ap,
|
||||||
PotentialTargets & targets,
|
PotentialTargets & targets,
|
||||||
HypotheticBattle & hb)
|
std::shared_ptr<HypotheticBattle> hb)
|
||||||
{
|
{
|
||||||
auto hexes = ap.attack.defender->getHexes();
|
auto hexes = ap.attack.defender->getHexes();
|
||||||
|
|
||||||
@ -308,7 +381,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getExchangeUnits(
|
|||||||
{
|
{
|
||||||
for(auto adjacentUnit : getAdjacentUnits(unit))
|
for(auto adjacentUnit : getAdjacentUnits(unit))
|
||||||
{
|
{
|
||||||
auto unitWithBonuses = hb.battleGetUnitByID(adjacentUnit->unitId());
|
auto unitWithBonuses = hb->battleGetUnitByID(adjacentUnit->unitId());
|
||||||
|
|
||||||
if(vstd::contains(targets.unreachableEnemies, adjacentUnit)
|
if(vstd::contains(targets.unreachableEnemies, adjacentUnit)
|
||||||
&& !vstd::contains(allReachableUnits, unitWithBonuses))
|
&& !vstd::contains(allReachableUnits, unitWithBonuses))
|
||||||
@ -343,21 +416,27 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getExchangeUnits(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vstd::erase_if(exchangeUnits, [&](const battle::Unit * u) -> bool
|
||||||
|
{
|
||||||
|
return !hb->battleGetUnitByID(u->unitId())->alive();
|
||||||
|
});
|
||||||
|
|
||||||
return exchangeUnits;
|
return exchangeUnits;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t BattleExchangeEvaluator::calculateExchange(
|
float BattleExchangeEvaluator::calculateExchange(
|
||||||
const AttackPossibility & ap,
|
const AttackPossibility & ap,
|
||||||
PotentialTargets & targets,
|
PotentialTargets & targets,
|
||||||
HypotheticBattle & hb)
|
DamageCache & damageCache,
|
||||||
|
std::shared_ptr<HypotheticBattle> hb)
|
||||||
{
|
{
|
||||||
#if BATTLE_TRACE_LEVEL>=1
|
#if BATTLE_TRACE_LEVEL>=1
|
||||||
logAi->trace("Battle exchange at %lld", ap.attack.shooting ? ap.dest : ap.from);
|
logAi->trace("Battle exchange at %d", ap.attack.shooting ? ap.dest.hex : ap.from.hex);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if(cb->battleGetMySide() == BattlePerspective::LEFT_SIDE
|
if(cb->battleGetMySide() == BattlePerspective::LEFT_SIDE
|
||||||
&& cb->battleGetGateState() == EGateState::BLOCKED
|
&& cb->battleGetGateState() == EGateState::BLOCKED
|
||||||
&& ap.attack.defender->coversPos(ESiegeHex::GATE_BRIDGE))
|
&& ap.attack.defender->coversPos(BattleHex::GATE_BRIDGE))
|
||||||
{
|
{
|
||||||
return EvaluationResult::INEFFECTIVE_SCORE;
|
return EvaluationResult::INEFFECTIVE_SCORE;
|
||||||
}
|
}
|
||||||
@ -365,6 +444,7 @@ int64_t BattleExchangeEvaluator::calculateExchange(
|
|||||||
std::vector<const battle::Unit *> ourStacks;
|
std::vector<const battle::Unit *> ourStacks;
|
||||||
std::vector<const battle::Unit *> enemyStacks;
|
std::vector<const battle::Unit *> enemyStacks;
|
||||||
|
|
||||||
|
if(hb->battleGetUnitByID(ap.attack.defender->unitId())->alive())
|
||||||
enemyStacks.push_back(ap.attack.defender);
|
enemyStacks.push_back(ap.attack.defender);
|
||||||
|
|
||||||
std::vector<const battle::Unit *> exchangeUnits = getExchangeUnits(ap, targets, hb);
|
std::vector<const battle::Unit *> exchangeUnits = getExchangeUnits(ap, targets, hb);
|
||||||
@ -374,8 +454,23 @@ int64_t BattleExchangeEvaluator::calculateExchange(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
HypotheticBattle exchangeBattle(env.get(), cb);
|
auto exchangeBattle = std::make_shared<HypotheticBattle>(env.get(), hb);
|
||||||
BattleExchangeVariant v;
|
BattleExchangeVariant v(getPositiveEffectMultiplier(), getNegativeEffectMultiplier());
|
||||||
|
|
||||||
|
for(auto unit : exchangeUnits)
|
||||||
|
{
|
||||||
|
if(unit->isTurret())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, unit, true);
|
||||||
|
auto & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||||
|
|
||||||
|
if(exchangeBattle->getForUpdate(unit->unitId())->alive() && !vstd::contains(attackerQueue, unit))
|
||||||
|
{
|
||||||
|
attackerQueue.push_back(unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto melleeAttackers = ourStacks;
|
auto melleeAttackers = ourStacks;
|
||||||
|
|
||||||
vstd::removeDuplicates(melleeAttackers);
|
vstd::removeDuplicates(melleeAttackers);
|
||||||
@ -384,30 +479,15 @@ int64_t BattleExchangeEvaluator::calculateExchange(
|
|||||||
return !cb->battleCanShoot(u);
|
return !cb->battleCanShoot(u);
|
||||||
});
|
});
|
||||||
|
|
||||||
for(auto unit : exchangeUnits)
|
|
||||||
{
|
|
||||||
if(unit->isTurret())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
bool isOur = cb->battleMatchOwner(ap.attack.attacker, unit, true);
|
|
||||||
auto & attackerQueue = isOur ? ourStacks : enemyStacks;
|
|
||||||
|
|
||||||
|
|
||||||
if(!vstd::contains(attackerQueue, unit))
|
|
||||||
{
|
|
||||||
attackerQueue.push_back(unit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool canUseAp = true;
|
bool canUseAp = true;
|
||||||
|
|
||||||
for(auto activeUnit : exchangeUnits)
|
for(auto activeUnit : exchangeUnits)
|
||||||
{
|
{
|
||||||
bool isOur = cb->battleMatchOwner(ap.attack.attacker, activeUnit, true);
|
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, activeUnit, true);
|
||||||
battle::Units & attackerQueue = isOur ? ourStacks : enemyStacks;
|
battle::Units & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||||
battle::Units & oppositeQueue = isOur ? enemyStacks : ourStacks;
|
battle::Units & oppositeQueue = isOur ? enemyStacks : ourStacks;
|
||||||
|
|
||||||
auto attacker = exchangeBattle.getForUpdate(activeUnit->unitId());
|
auto attacker = exchangeBattle->getForUpdate(activeUnit->unitId());
|
||||||
|
|
||||||
if(!attacker->alive())
|
if(!attacker->alive())
|
||||||
{
|
{
|
||||||
@ -420,21 +500,22 @@ int64_t BattleExchangeEvaluator::calculateExchange(
|
|||||||
|
|
||||||
auto targetUnit = ap.attack.defender;
|
auto targetUnit = ap.attack.defender;
|
||||||
|
|
||||||
if(!isOur || !exchangeBattle.getForUpdate(targetUnit->unitId())->alive())
|
if(!isOur || !exchangeBattle->battleGetUnitByID(targetUnit->unitId())->alive())
|
||||||
{
|
{
|
||||||
auto estimateAttack = [&](const battle::Unit * u) -> int64_t
|
auto estimateAttack = [&](const battle::Unit * u) -> float
|
||||||
{
|
{
|
||||||
auto stackWithBonuses = exchangeBattle.getForUpdate(u->unitId());
|
auto stackWithBonuses = exchangeBattle->getForUpdate(u->unitId());
|
||||||
auto score = v.trackAttack(
|
auto score = v.trackAttack(
|
||||||
attacker,
|
attacker,
|
||||||
stackWithBonuses,
|
stackWithBonuses,
|
||||||
exchangeBattle.battleCanShoot(stackWithBonuses.get()),
|
exchangeBattle->battleCanShoot(stackWithBonuses.get()),
|
||||||
isOur,
|
isOur,
|
||||||
*cb,
|
damageCache,
|
||||||
|
hb,
|
||||||
true);
|
true);
|
||||||
|
|
||||||
#if BATTLE_TRACE_LEVEL>=1
|
#if BATTLE_TRACE_LEVEL>=1
|
||||||
logAi->trace("Best target selector %s->%s score = %lld", attacker->getDescription(), u->getDescription(), score);
|
logAi->trace("Best target selector %s->%s score = %2f", attacker->getDescription(), u->getDescription(), score);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
@ -446,9 +527,12 @@ int64_t BattleExchangeEvaluator::calculateExchange(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto reachable = exchangeBattle.battleGetUnitsIf([&](const battle::Unit * u) -> bool
|
auto reachable = exchangeBattle->battleGetUnitsIf([&](const battle::Unit * u) -> bool
|
||||||
{
|
{
|
||||||
if(!u->alive() || u->unitSide() == attacker->unitSide())
|
if(u->unitSide() == attacker->unitSide())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(!exchangeBattle->getForUpdate(u->unitId())->alive())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return vstd::contains_if(reachabilityMap[u->getPosition()], [&](const battle::Unit * other) -> bool
|
return vstd::contains_if(reachabilityMap[u->getPosition()], [&](const battle::Unit * other) -> bool
|
||||||
@ -472,19 +556,20 @@ int64_t BattleExchangeEvaluator::calculateExchange(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto defender = exchangeBattle.getForUpdate(targetUnit->unitId());
|
auto defender = exchangeBattle->getForUpdate(targetUnit->unitId());
|
||||||
auto shooting = cb->battleCanShoot(attacker.get());
|
auto shooting = exchangeBattle->battleCanShoot(attacker.get());
|
||||||
const int totalAttacks = attacker->getTotalAttacks(shooting);
|
const int totalAttacks = attacker->getTotalAttacks(shooting);
|
||||||
|
|
||||||
if(canUseAp && activeUnit == ap.attack.attacker && targetUnit == ap.attack.defender)
|
if(canUseAp && activeUnit->unitId() == ap.attack.attacker->unitId()
|
||||||
|
&& targetUnit->unitId() == ap.attack.defender->unitId())
|
||||||
{
|
{
|
||||||
v.trackAttack(ap, exchangeBattle);
|
v.trackAttack(ap, exchangeBattle, damageCache);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for(int i = 0; i < totalAttacks; i++)
|
for(int i = 0; i < totalAttacks; i++)
|
||||||
{
|
{
|
||||||
v.trackAttack(attacker, defender, shooting, isOur, exchangeBattle);
|
v.trackAttack(attacker, defender, shooting, isOur, damageCache, exchangeBattle);
|
||||||
|
|
||||||
if(!attacker->alive() || !defender->alive())
|
if(!attacker->alive() || !defender->alive())
|
||||||
break;
|
break;
|
||||||
@ -495,12 +580,12 @@ int64_t BattleExchangeEvaluator::calculateExchange(
|
|||||||
|
|
||||||
vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
|
vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
|
||||||
{
|
{
|
||||||
return !exchangeBattle.getForUpdate(u->unitId())->alive();
|
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||||
});
|
});
|
||||||
|
|
||||||
vstd::erase_if(oppositeQueue, [&](const battle::Unit * u) -> bool
|
vstd::erase_if(oppositeQueue, [&](const battle::Unit * u) -> bool
|
||||||
{
|
{
|
||||||
return !exchangeBattle.getForUpdate(u->unitId())->alive();
|
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,7 +594,7 @@ int64_t BattleExchangeEvaluator::calculateExchange(
|
|||||||
v.adjustPositions(melleeAttackers, ap, reachabilityMap);
|
v.adjustPositions(melleeAttackers, ap, reachabilityMap);
|
||||||
|
|
||||||
#if BATTLE_TRACE_LEVEL>=1
|
#if BATTLE_TRACE_LEVEL>=1
|
||||||
logAi->trace("Exchange score: %lld", v.getScore());
|
logAi->trace("Exchange score: %2f", v.getScore());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return v.getScore();
|
return v.getScore();
|
||||||
@ -539,7 +624,7 @@ void BattleExchangeVariant::adjustPositions(
|
|||||||
vstd::erase_if_present(hexes, ap.attack.attacker->occupiedHex(ap.attack.attackerPos));
|
vstd::erase_if_present(hexes, ap.attack.attacker->occupiedHex(ap.attack.attackerPos));
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t notRealizedDamage = 0;
|
float notRealizedDamage = 0;
|
||||||
|
|
||||||
for(auto unit : attackers)
|
for(auto unit : attackers)
|
||||||
{
|
{
|
||||||
@ -555,7 +640,7 @@ void BattleExchangeVariant::adjustPositions(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto desiredPosition = vstd::minElementByFun(hexes, [&](BattleHex h) -> int64_t
|
auto desiredPosition = vstd::minElementByFun(hexes, [&](BattleHex h) -> float
|
||||||
{
|
{
|
||||||
auto score = vstd::contains(reachabilityMap[h], unit)
|
auto score = vstd::contains(reachabilityMap[h], unit)
|
||||||
? reachabilityMap[h].size()
|
? reachabilityMap[h].size()
|
||||||
@ -581,13 +666,13 @@ void BattleExchangeVariant::adjustPositions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleExchangeEvaluator::updateReachabilityMap(HypotheticBattle & hb)
|
void BattleExchangeEvaluator::updateReachabilityMap( std::shared_ptr<HypotheticBattle> hb)
|
||||||
{
|
{
|
||||||
const int TURN_DEPTH = 2;
|
const int TURN_DEPTH = 2;
|
||||||
|
|
||||||
turnOrder.clear();
|
turnOrder.clear();
|
||||||
|
|
||||||
hb.battleGetTurnOrder(turnOrder, std::numeric_limits<int>::max(), TURN_DEPTH);
|
hb->battleGetTurnOrder(turnOrder, std::numeric_limits<int>::max(), TURN_DEPTH);
|
||||||
reachabilityMap.clear();
|
reachabilityMap.clear();
|
||||||
|
|
||||||
for(int turn = 0; turn < turnOrder.size(); turn++)
|
for(int turn = 0; turn < turnOrder.size(); turn++)
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
struct AttackerValue
|
struct AttackerValue
|
||||||
{
|
{
|
||||||
int64_t value;
|
float value;
|
||||||
bool isRetalitated;
|
bool isRetalitated;
|
||||||
BattleHex position;
|
BattleHex position;
|
||||||
|
|
||||||
@ -25,20 +25,23 @@ struct AttackerValue
|
|||||||
|
|
||||||
struct MoveTarget
|
struct MoveTarget
|
||||||
{
|
{
|
||||||
int64_t score;
|
float score;
|
||||||
|
float scorePerTurn;
|
||||||
std::vector<BattleHex> positions;
|
std::vector<BattleHex> positions;
|
||||||
|
std::optional<AttackPossibility> cachedAttack;
|
||||||
|
uint8_t turnsToRich;
|
||||||
|
|
||||||
MoveTarget();
|
MoveTarget();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EvaluationResult
|
struct EvaluationResult
|
||||||
{
|
{
|
||||||
static const int64_t INEFFECTIVE_SCORE = -1000000;
|
static const int64_t INEFFECTIVE_SCORE = -10000;
|
||||||
|
|
||||||
AttackPossibility bestAttack;
|
AttackPossibility bestAttack;
|
||||||
MoveTarget bestMove;
|
MoveTarget bestMove;
|
||||||
bool wait;
|
bool wait;
|
||||||
int64_t score;
|
float score;
|
||||||
bool defend;
|
bool defend;
|
||||||
|
|
||||||
EvaluationResult(const AttackPossibility & ap)
|
EvaluationResult(const AttackPossibility & ap)
|
||||||
@ -56,19 +59,24 @@ struct EvaluationResult
|
|||||||
class BattleExchangeVariant
|
class BattleExchangeVariant
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BattleExchangeVariant(): dpsScore(0) {}
|
BattleExchangeVariant(float positiveEffectMultiplier, float negativeEffectMultiplier)
|
||||||
|
: dpsScore(0), positiveEffectMultiplier(positiveEffectMultiplier), negativeEffectMultiplier(negativeEffectMultiplier) {}
|
||||||
|
|
||||||
int64_t trackAttack(const AttackPossibility & ap, HypotheticBattle & state);
|
float trackAttack(
|
||||||
|
const AttackPossibility & ap,
|
||||||
|
std::shared_ptr<HypotheticBattle> hb,
|
||||||
|
DamageCache & damageCache);
|
||||||
|
|
||||||
int64_t trackAttack(
|
float trackAttack(
|
||||||
std::shared_ptr<StackWithBonuses> attacker,
|
std::shared_ptr<StackWithBonuses> attacker,
|
||||||
std::shared_ptr<StackWithBonuses> defender,
|
std::shared_ptr<StackWithBonuses> defender,
|
||||||
bool shooting,
|
bool shooting,
|
||||||
bool isOurAttack,
|
bool isOurAttack,
|
||||||
const CBattleInfoCallback & cb,
|
DamageCache & damageCache,
|
||||||
|
std::shared_ptr<HypotheticBattle> hb,
|
||||||
bool evaluateOnly = false);
|
bool evaluateOnly = false);
|
||||||
|
|
||||||
int64_t getScore() const { return dpsScore; }
|
float getScore() const { return dpsScore; }
|
||||||
|
|
||||||
void adjustPositions(
|
void adjustPositions(
|
||||||
std::vector<const battle::Unit *> attackers,
|
std::vector<const battle::Unit *> attackers,
|
||||||
@ -76,7 +84,9 @@ public:
|
|||||||
std::map<BattleHex, battle::Units> & reachabilityMap);
|
std::map<BattleHex, battle::Units> & reachabilityMap);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int64_t dpsScore;
|
float positiveEffectMultiplier;
|
||||||
|
float negativeEffectMultiplier;
|
||||||
|
float dpsScore;
|
||||||
std::map<uint32_t, AttackerValue> attackerValue;
|
std::map<uint32_t, AttackerValue> attackerValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -87,15 +97,40 @@ private:
|
|||||||
std::shared_ptr<Environment> env;
|
std::shared_ptr<Environment> env;
|
||||||
std::map<BattleHex, std::vector<const battle::Unit *>> reachabilityMap;
|
std::map<BattleHex, std::vector<const battle::Unit *>> reachabilityMap;
|
||||||
std::vector<battle::Units> turnOrder;
|
std::vector<battle::Units> turnOrder;
|
||||||
|
float negativeEffectMultiplier;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BattleExchangeEvaluator(std::shared_ptr<CBattleInfoCallback> cb, std::shared_ptr<Environment> env): cb(cb), env(env) {}
|
BattleExchangeEvaluator(
|
||||||
|
std::shared_ptr<CBattleInfoCallback> cb,
|
||||||
|
std::shared_ptr<Environment> env,
|
||||||
|
float strengthRatio): cb(cb), env(env) {
|
||||||
|
negativeEffectMultiplier = strengthRatio;
|
||||||
|
}
|
||||||
|
|
||||||
EvaluationResult findBestTarget(const battle::Unit * activeStack, PotentialTargets & targets, HypotheticBattle & hb);
|
EvaluationResult findBestTarget(
|
||||||
int64_t calculateExchange(const AttackPossibility & ap, PotentialTargets & targets, HypotheticBattle & hb);
|
const battle::Unit * activeStack,
|
||||||
void updateReachabilityMap(HypotheticBattle & hb);
|
PotentialTargets & targets,
|
||||||
std::vector<const battle::Unit *> getExchangeUnits(const AttackPossibility & ap, PotentialTargets & targets, HypotheticBattle & hb);
|
DamageCache & damageCache,
|
||||||
|
std::shared_ptr<HypotheticBattle> hb);
|
||||||
|
|
||||||
|
float calculateExchange(
|
||||||
|
const AttackPossibility & ap,
|
||||||
|
PotentialTargets & targets,
|
||||||
|
DamageCache & damageCache,
|
||||||
|
std::shared_ptr<HypotheticBattle> hb);
|
||||||
|
|
||||||
|
void updateReachabilityMap(std::shared_ptr<HypotheticBattle> hb);
|
||||||
|
std::vector<const battle::Unit *> getExchangeUnits(const AttackPossibility & ap, PotentialTargets & targets, std::shared_ptr<HypotheticBattle> hb);
|
||||||
bool checkPositionBlocksOurStacks(HypotheticBattle & hb, const battle::Unit * unit, BattleHex position);
|
bool checkPositionBlocksOurStacks(HypotheticBattle & hb, const battle::Unit * unit, BattleHex position);
|
||||||
MoveTarget findMoveTowardsUnreachable(const battle::Unit * activeStack, PotentialTargets & targets, HypotheticBattle & hb);
|
|
||||||
|
MoveTarget findMoveTowardsUnreachable(
|
||||||
|
const battle::Unit * activeStack,
|
||||||
|
PotentialTargets & targets,
|
||||||
|
DamageCache & damageCache,
|
||||||
|
std::shared_ptr<HypotheticBattle> hb);
|
||||||
|
|
||||||
std::vector<const battle::Unit *> getAdjacentUnits(const battle::Unit * unit);
|
std::vector<const battle::Unit *> getAdjacentUnits(const battle::Unit * unit);
|
||||||
|
|
||||||
|
float getPositiveEffectMultiplier() { return 1; }
|
||||||
|
float getNegativeEffectMultiplier() { return negativeEffectMultiplier; }
|
||||||
};
|
};
|
@ -1,6 +1,7 @@
|
|||||||
set(battleAI_SRCS
|
set(battleAI_SRCS
|
||||||
AttackPossibility.cpp
|
AttackPossibility.cpp
|
||||||
BattleAI.cpp
|
BattleAI.cpp
|
||||||
|
BattleEvaluator.cpp
|
||||||
common.cpp
|
common.cpp
|
||||||
EnemyInfo.cpp
|
EnemyInfo.cpp
|
||||||
PossibleSpellcast.cpp
|
PossibleSpellcast.cpp
|
||||||
@ -15,6 +16,7 @@ set(battleAI_HEADERS
|
|||||||
|
|
||||||
AttackPossibility.h
|
AttackPossibility.h
|
||||||
BattleAI.h
|
BattleAI.h
|
||||||
|
BattleEvaluator.h
|
||||||
common.h
|
common.h
|
||||||
EnemyInfo.h
|
EnemyInfo.h
|
||||||
PotentialTargets.h
|
PotentialTargets.h
|
||||||
@ -37,7 +39,11 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_include_directories(BattleAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(BattleAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
target_link_libraries(BattleAI PRIVATE ${VCMI_LIB_TARGET})
|
target_link_libraries(BattleAI PRIVATE ${VCMI_LIB_TARGET} TBB::tbb)
|
||||||
|
|
||||||
vcmi_set_output_dir(BattleAI "AI")
|
vcmi_set_output_dir(BattleAI "AI")
|
||||||
enable_pch(BattleAI)
|
enable_pch(BattleAI)
|
||||||
|
|
||||||
|
if(APPLE_IOS AND NOT USING_CONAN)
|
||||||
|
install(IMPORTED_RUNTIME_ARTIFACTS TBB::tbb LIBRARY DESTINATION ${LIB_DIR}) # CMake 3.21+
|
||||||
|
endif()
|
||||||
|
@ -27,7 +27,7 @@ public:
|
|||||||
|
|
||||||
const CSpell * spell;
|
const CSpell * spell;
|
||||||
spells::Target dest;
|
spells::Target dest;
|
||||||
int64_t value;
|
float value;
|
||||||
|
|
||||||
PossibleSpellcast();
|
PossibleSpellcast();
|
||||||
virtual ~PossibleSpellcast();
|
virtual ~PossibleSpellcast();
|
||||||
|
@ -11,11 +11,14 @@
|
|||||||
#include "PotentialTargets.h"
|
#include "PotentialTargets.h"
|
||||||
#include "../../lib/CStack.h"//todo: remove
|
#include "../../lib/CStack.h"//todo: remove
|
||||||
|
|
||||||
PotentialTargets::PotentialTargets(const battle::Unit * attacker, const HypotheticBattle & state)
|
PotentialTargets::PotentialTargets(
|
||||||
|
const battle::Unit * attacker,
|
||||||
|
DamageCache & damageCache,
|
||||||
|
std::shared_ptr<HypotheticBattle> state)
|
||||||
{
|
{
|
||||||
auto attackerInfo = state.battleGetUnitByID(attacker->unitId());
|
auto attackerInfo = state->battleGetUnitByID(attacker->unitId());
|
||||||
auto reachability = state.getReachability(attackerInfo);
|
auto reachability = state->getReachability(attackerInfo);
|
||||||
auto avHexes = state.battleGetAvailableHexes(reachability, attackerInfo, false);
|
auto avHexes = state->battleGetAvailableHexes(reachability, attackerInfo, false);
|
||||||
|
|
||||||
//FIXME: this should part of battleGetAvailableHexes
|
//FIXME: this should part of battleGetAvailableHexes
|
||||||
bool forceTarget = false;
|
bool forceTarget = false;
|
||||||
@ -25,7 +28,7 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet
|
|||||||
if(attackerInfo->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE))
|
if(attackerInfo->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE))
|
||||||
{
|
{
|
||||||
forceTarget = true;
|
forceTarget = true;
|
||||||
auto nearest = state.getNearestStack(attackerInfo);
|
auto nearest = state->getNearestStack(attackerInfo);
|
||||||
|
|
||||||
if(nearest.first != nullptr)
|
if(nearest.first != nullptr)
|
||||||
{
|
{
|
||||||
@ -34,14 +37,14 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto aliveUnits = state.battleGetUnitsIf([=](const battle::Unit * unit)
|
auto aliveUnits = state->battleGetUnitsIf([=](const battle::Unit * unit)
|
||||||
{
|
{
|
||||||
return unit->isValidTarget() && unit->unitId() != attackerInfo->unitId();
|
return unit->isValidTarget() && unit->unitId() != attackerInfo->unitId();
|
||||||
});
|
});
|
||||||
|
|
||||||
for(auto defender : aliveUnits)
|
for(auto defender : aliveUnits)
|
||||||
{
|
{
|
||||||
if(!forceTarget && !state.battleMatchOwner(attackerInfo, defender))
|
if(!forceTarget && !state->battleMatchOwner(attackerInfo, defender))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility
|
auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility
|
||||||
@ -49,7 +52,7 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet
|
|||||||
int distance = hex.isValid() ? reachability.distances[hex] : 0;
|
int distance = hex.isValid() ? reachability.distances[hex] : 0;
|
||||||
auto bai = BattleAttackInfo(attackerInfo, defender, distance, shooting);
|
auto bai = BattleAttackInfo(attackerInfo, defender, distance, shooting);
|
||||||
|
|
||||||
return AttackPossibility::evaluate(bai, hex, state);
|
return AttackPossibility::evaluate(bai, hex, damageCache, state);
|
||||||
};
|
};
|
||||||
|
|
||||||
if(forceTarget)
|
if(forceTarget)
|
||||||
@ -59,7 +62,7 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet
|
|||||||
else
|
else
|
||||||
unreachableEnemies.push_back(defender);
|
unreachableEnemies.push_back(defender);
|
||||||
}
|
}
|
||||||
else if(state.battleCanShoot(attackerInfo, defender->getPosition()))
|
else if(state->battleCanShoot(attackerInfo, defender->getPosition()))
|
||||||
{
|
{
|
||||||
possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID));
|
possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID));
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,10 @@ public:
|
|||||||
std::vector<const battle::Unit *> unreachableEnemies;
|
std::vector<const battle::Unit *> unreachableEnemies;
|
||||||
|
|
||||||
PotentialTargets(){};
|
PotentialTargets(){};
|
||||||
PotentialTargets(const battle::Unit * attacker, const HypotheticBattle & state);
|
PotentialTargets(
|
||||||
|
const battle::Unit * attacker,
|
||||||
|
DamageCache & damageCache,
|
||||||
|
std::shared_ptr<HypotheticBattle> hb);
|
||||||
|
|
||||||
const AttackPossibility & bestAction() const;
|
const AttackPossibility & bestAction() const;
|
||||||
int64_t bestActionValue() const;
|
int64_t bestActionValue() const;
|
||||||
|
@ -45,13 +45,32 @@ StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const battle:
|
|||||||
id(Stack->unitId()),
|
id(Stack->unitId()),
|
||||||
side(Stack->unitSide()),
|
side(Stack->unitSide()),
|
||||||
player(Stack->unitOwner()),
|
player(Stack->unitOwner()),
|
||||||
slot(Stack->unitSlot())
|
slot(Stack->unitSlot()),
|
||||||
|
treeVersionLocal(0)
|
||||||
{
|
{
|
||||||
localInit(Owner);
|
localInit(Owner);
|
||||||
|
|
||||||
battle::CUnitState::operator=(*Stack);
|
battle::CUnitState::operator=(*Stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const battle::Unit * Stack)
|
||||||
|
: battle::CUnitState(),
|
||||||
|
origBearer(Stack->getBonusBearer()),
|
||||||
|
owner(Owner),
|
||||||
|
type(Stack->unitType()),
|
||||||
|
baseAmount(Stack->unitBaseAmount()),
|
||||||
|
id(Stack->unitId()),
|
||||||
|
side(Stack->unitSide()),
|
||||||
|
player(Stack->unitOwner()),
|
||||||
|
slot(Stack->unitSlot()),
|
||||||
|
treeVersionLocal(0)
|
||||||
|
{
|
||||||
|
localInit(Owner);
|
||||||
|
|
||||||
|
auto state = Stack->acquireState();
|
||||||
|
battle::CUnitState::operator=(*state);
|
||||||
|
}
|
||||||
|
|
||||||
StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const battle::UnitInfo & info)
|
StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const battle::UnitInfo & info)
|
||||||
: battle::CUnitState(),
|
: battle::CUnitState(),
|
||||||
origBearer(nullptr),
|
origBearer(nullptr),
|
||||||
@ -59,7 +78,8 @@ StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const battle:
|
|||||||
baseAmount(info.count),
|
baseAmount(info.count),
|
||||||
id(info.id),
|
id(info.id),
|
||||||
side(info.side),
|
side(info.side),
|
||||||
slot(SlotID::SUMMONED_SLOT_PLACEHOLDER)
|
slot(SlotID::SUMMONED_SLOT_PLACEHOLDER),
|
||||||
|
treeVersionLocal(0)
|
||||||
{
|
{
|
||||||
type = info.type.toCreature();
|
type = info.type.toCreature();
|
||||||
origBearer = type;
|
origBearer = type;
|
||||||
@ -124,7 +144,7 @@ TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, c
|
|||||||
|
|
||||||
for(const Bonus & bonus : bonusesToUpdate)
|
for(const Bonus & bonus : bonusesToUpdate)
|
||||||
{
|
{
|
||||||
if(selector(&bonus) && (!limit || !limit(&bonus)))
|
if(selector(&bonus) && (!limit || limit(&bonus)))
|
||||||
{
|
{
|
||||||
if(ret->getFirst(Selector::source(BonusSource::SPELL_EFFECT, bonus.sid).And(Selector::typeSubtype(bonus.type, bonus.subtype))))
|
if(ret->getFirst(Selector::source(BonusSource::SPELL_EFFECT, bonus.sid).And(Selector::typeSubtype(bonus.type, bonus.subtype))))
|
||||||
{
|
{
|
||||||
@ -150,12 +170,18 @@ TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, c
|
|||||||
|
|
||||||
int64_t StackWithBonuses::getTreeVersion() const
|
int64_t StackWithBonuses::getTreeVersion() const
|
||||||
{
|
{
|
||||||
return owner->getTreeVersion();
|
auto result = owner->getTreeVersion();
|
||||||
|
|
||||||
|
if(bonusesToAdd.empty() && bonusesToUpdate.empty() && bonusesToRemove.empty())
|
||||||
|
return result;
|
||||||
|
else
|
||||||
|
return result + treeVersionLocal;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StackWithBonuses::addUnitBonus(const std::vector<Bonus> & bonus)
|
void StackWithBonuses::addUnitBonus(const std::vector<Bonus> & bonus)
|
||||||
{
|
{
|
||||||
vstd::concatenate(bonusesToAdd, bonus);
|
vstd::concatenate(bonusesToAdd, bonus);
|
||||||
|
treeVersionLocal++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StackWithBonuses::updateUnitBonus(const std::vector<Bonus> & bonus)
|
void StackWithBonuses::updateUnitBonus(const std::vector<Bonus> & bonus)
|
||||||
@ -163,6 +189,7 @@ void StackWithBonuses::updateUnitBonus(const std::vector<Bonus> & bonus)
|
|||||||
//TODO: optimize, actualize to last value
|
//TODO: optimize, actualize to last value
|
||||||
|
|
||||||
vstd::concatenate(bonusesToUpdate, bonus);
|
vstd::concatenate(bonusesToUpdate, bonus);
|
||||||
|
treeVersionLocal++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StackWithBonuses::removeUnitBonus(const std::vector<Bonus> & bonus)
|
void StackWithBonuses::removeUnitBonus(const std::vector<Bonus> & bonus)
|
||||||
@ -197,12 +224,14 @@ void StackWithBonuses::removeUnitBonus(const CSelector & selector)
|
|||||||
|
|
||||||
vstd::erase_if(bonusesToAdd, [&](const Bonus & b){return selector(&b);});
|
vstd::erase_if(bonusesToAdd, [&](const Bonus & b){return selector(&b);});
|
||||||
vstd::erase_if(bonusesToUpdate, [&](const Bonus & b){return selector(&b);});
|
vstd::erase_if(bonusesToUpdate, [&](const Bonus & b){return selector(&b);});
|
||||||
|
|
||||||
|
treeVersionLocal++;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string StackWithBonuses::getDescription() const
|
std::string StackWithBonuses::getDescription() const
|
||||||
{
|
{
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << unitOwner().getStr();
|
oss << unitOwner().toString();
|
||||||
oss << " battle stack [" << unitId() << "]: " << getCount() << " of ";
|
oss << " battle stack [" << unitId() << "]: " << getCount() << " of ";
|
||||||
if(type)
|
if(type)
|
||||||
oss << type->getJsonKey();
|
oss << type->getJsonKey();
|
||||||
@ -256,7 +285,7 @@ std::shared_ptr<StackWithBonuses> HypotheticBattle::getForUpdate(uint32_t id)
|
|||||||
|
|
||||||
if(iter == stackStates.end())
|
if(iter == stackStates.end())
|
||||||
{
|
{
|
||||||
const CStack * s = subject->battleGetStackByID(id, false);
|
const battle::Unit * s = subject->battleGetUnitByID(id);
|
||||||
|
|
||||||
auto ret = std::make_shared<StackWithBonuses>(this, s);
|
auto ret = std::make_shared<StackWithBonuses>(this, s);
|
||||||
stackStates[id] = ret;
|
stackStates[id] = ret;
|
||||||
@ -291,12 +320,17 @@ battle::Units HypotheticBattle::getUnitsIf(battle::UnitFilter predicate) const
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BattleID HypotheticBattle::getBattleID() const
|
||||||
|
{
|
||||||
|
return subject->getBattle()->getBattleID();
|
||||||
|
}
|
||||||
|
|
||||||
int32_t HypotheticBattle::getActiveStackID() const
|
int32_t HypotheticBattle::getActiveStackID() const
|
||||||
{
|
{
|
||||||
return activeUnitId;
|
return activeUnitId;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HypotheticBattle::nextRound(int32_t roundNr)
|
void HypotheticBattle::nextRound()
|
||||||
{
|
{
|
||||||
//TODO:HypotheticBattle::nextRound
|
//TODO:HypotheticBattle::nextRound
|
||||||
for(auto unit : battleAliveUnits())
|
for(auto unit : battleAliveUnits())
|
||||||
@ -433,6 +467,24 @@ int64_t HypotheticBattle::getActualDamage(const DamageRange & damage, int32_t at
|
|||||||
return (damage.min + damage.max) / 2;
|
return (damage.min + damage.max) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<SpellID> HypotheticBattle::getUsedSpells(ui8 side) const
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int3 HypotheticBattle::getLocation() const
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
return int3(-1, -1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HypotheticBattle::isCreatureBank() const
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int64_t HypotheticBattle::getTreeVersion() const
|
int64_t HypotheticBattle::getTreeVersion() const
|
||||||
{
|
{
|
||||||
return getBonusBearer()->getTreeVersion() + bonusTreeVersion;
|
return getBonusBearer()->getTreeVersion() + bonusTreeVersion;
|
||||||
@ -523,8 +575,9 @@ const Services * HypotheticBattle::HypotheticEnvironment::services() const
|
|||||||
return env->services();
|
return env->services();
|
||||||
}
|
}
|
||||||
|
|
||||||
const Environment::BattleCb * HypotheticBattle::HypotheticEnvironment::battle() const
|
const Environment::BattleCb * HypotheticBattle::HypotheticEnvironment::battle(const BattleID & battleID) const
|
||||||
{
|
{
|
||||||
|
assert(battleID == owner->getBattleID());
|
||||||
return owner;
|
return owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,9 +47,12 @@ public:
|
|||||||
std::vector<Bonus> bonusesToAdd;
|
std::vector<Bonus> bonusesToAdd;
|
||||||
std::vector<Bonus> bonusesToUpdate;
|
std::vector<Bonus> bonusesToUpdate;
|
||||||
std::set<std::shared_ptr<Bonus>> bonusesToRemove;
|
std::set<std::shared_ptr<Bonus>> bonusesToRemove;
|
||||||
|
int treeVersionLocal;
|
||||||
|
|
||||||
StackWithBonuses(const HypotheticBattle * Owner, const battle::CUnitState * Stack);
|
StackWithBonuses(const HypotheticBattle * Owner, const battle::CUnitState * Stack);
|
||||||
|
|
||||||
|
StackWithBonuses(const HypotheticBattle * Owner, const battle::Unit * Stack);
|
||||||
|
|
||||||
StackWithBonuses(const HypotheticBattle * Owner, const battle::UnitInfo & info);
|
StackWithBonuses(const HypotheticBattle * Owner, const battle::UnitInfo & info);
|
||||||
|
|
||||||
virtual ~StackWithBonuses();
|
virtual ~StackWithBonuses();
|
||||||
@ -107,11 +110,13 @@ public:
|
|||||||
|
|
||||||
std::shared_ptr<StackWithBonuses> getForUpdate(uint32_t id);
|
std::shared_ptr<StackWithBonuses> getForUpdate(uint32_t id);
|
||||||
|
|
||||||
|
BattleID getBattleID() const override;
|
||||||
|
|
||||||
int32_t getActiveStackID() const override;
|
int32_t getActiveStackID() const override;
|
||||||
|
|
||||||
battle::Units getUnitsIf(battle::UnitFilter predicate) const override;
|
battle::Units getUnitsIf(battle::UnitFilter predicate) const override;
|
||||||
|
|
||||||
void nextRound(int32_t roundNr) override;
|
void nextRound() override;
|
||||||
void nextTurn(uint32_t unitId) override;
|
void nextTurn(uint32_t unitId) override;
|
||||||
|
|
||||||
void addUnit(uint32_t id, const JsonNode & data) override;
|
void addUnit(uint32_t id, const JsonNode & data) override;
|
||||||
@ -133,6 +138,9 @@ public:
|
|||||||
uint32_t nextUnitId() const override;
|
uint32_t nextUnitId() const override;
|
||||||
|
|
||||||
int64_t getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const override;
|
int64_t getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const override;
|
||||||
|
std::vector<SpellID> getUsedSpells(ui8 side) const override;
|
||||||
|
int3 getLocation() const override;
|
||||||
|
bool isCreatureBank() const override;
|
||||||
|
|
||||||
int64_t getTreeVersion() const;
|
int64_t getTreeVersion() const;
|
||||||
|
|
||||||
@ -174,7 +182,7 @@ private:
|
|||||||
HypotheticEnvironment(HypotheticBattle * owner_, const Environment * upperEnvironment);
|
HypotheticEnvironment(HypotheticBattle * owner_, const Environment * upperEnvironment);
|
||||||
|
|
||||||
const Services * services() const override;
|
const Services * services() const override;
|
||||||
const BattleCb * battle() const override;
|
const BattleCb * battle(const BattleID & battleID) const override;
|
||||||
const GameCb * game() const override;
|
const GameCb * game() const override;
|
||||||
vstd::CLoggerBase * logger() const override;
|
vstd::CLoggerBase * logger() const override;
|
||||||
events::EventBus * eventBus() const override;
|
events::EventBus * eventBus() const override;
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "../../lib/CRandomGenerator.h"
|
#include "../../lib/CRandomGenerator.h"
|
||||||
#include "../../lib/CStack.h"
|
#include "../../lib/CStack.h"
|
||||||
|
#include "../../lib/battle/BattleAction.h"
|
||||||
|
|
||||||
void CEmptyAI::saveGame(BinarySerializer & h, const int version)
|
void CEmptyAI::saveGame(BinarySerializer & h, const int version)
|
||||||
{
|
{
|
||||||
@ -26,25 +27,26 @@ void CEmptyAI::initGameInterface(std::shared_ptr<Environment> ENV, std::shared_p
|
|||||||
cb = CB;
|
cb = CB;
|
||||||
env = ENV;
|
env = ENV;
|
||||||
human=false;
|
human=false;
|
||||||
playerID = *cb->getMyColor();
|
playerID = *cb->getPlayerID();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEmptyAI::yourTurn()
|
void CEmptyAI::yourTurn(QueryID queryID)
|
||||||
{
|
{
|
||||||
|
cb->selectionMade(0, queryID);
|
||||||
cb->endTurn();
|
cb->endTurn();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEmptyAI::activeStack(const CStack * stack)
|
void CEmptyAI::activeStack(const BattleID & battleID, const CStack * stack)
|
||||||
{
|
{
|
||||||
cb->battleMakeUnitAction(BattleAction::makeDefend(stack));
|
cb->battleMakeUnitAction(battleID, BattleAction::makeDefend(stack));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEmptyAI::yourTacticPhase(int distance)
|
void CEmptyAI::yourTacticPhase(const BattleID & battleID, int distance)
|
||||||
{
|
{
|
||||||
cb->battleMakeTacticAction(BattleAction::makeEndOFTacticPhase(cb->battleGetTacticsSide()));
|
cb->battleMakeTacticAction(battleID, BattleAction::makeEndOFTacticPhase(cb->getBattle(battleID)->battleGetTacticsSide()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEmptyAI::heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> &skills, QueryID queryID)
|
void CEmptyAI::heroGotLevel(const CGHeroInstance *hero, PrimarySkill pskill, std::vector<SecondarySkill> &skills, QueryID queryID)
|
||||||
{
|
{
|
||||||
cb->selectionMade(CRandomGenerator::getDefault().nextInt((int)skills.size() - 1), queryID);
|
cb->selectionMade(CRandomGenerator::getDefault().nextInt((int)skills.size() - 1), queryID);
|
||||||
}
|
}
|
||||||
@ -59,7 +61,7 @@ void CEmptyAI::showBlockingDialog(const std::string &text, const std::vector<Com
|
|||||||
cb->selectionMade(0, askID);
|
cb->selectionMade(0, askID);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEmptyAI::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID)
|
void CEmptyAI::showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID)
|
||||||
{
|
{
|
||||||
cb->selectionMade(0, askID);
|
cb->selectionMade(0, askID);
|
||||||
}
|
}
|
||||||
@ -73,3 +75,8 @@ void CEmptyAI::showMapObjectSelectDialog(QueryID askID, const Component & icon,
|
|||||||
{
|
{
|
||||||
cb->selectionMade(0, askID);
|
cb->selectionMade(0, askID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<BattleAction> CEmptyAI::makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState)
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
@ -23,15 +23,16 @@ public:
|
|||||||
virtual void loadGame(BinaryDeserializer & h, const int version) override;
|
virtual void loadGame(BinaryDeserializer & h, const int version) override;
|
||||||
|
|
||||||
void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
|
void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
|
||||||
void yourTurn() override;
|
void yourTurn(QueryID queryID) override;
|
||||||
void yourTacticPhase(int distance) override;
|
void yourTacticPhase(const BattleID & battleID, int distance) override;
|
||||||
void activeStack(const CStack * stack) override;
|
void activeStack(const BattleID & battleID, const CStack * stack) override;
|
||||||
void heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> &skills, QueryID queryID) override;
|
void heroGotLevel(const CGHeroInstance *hero, PrimarySkill pskill, std::vector<SecondarySkill> &skills, QueryID queryID) override;
|
||||||
void commanderGotLevel (const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override;
|
void commanderGotLevel (const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override;
|
||||||
void showBlockingDialog(const std::string &text, const std::vector<Component> &components, QueryID askID, const int soundID, bool selection, bool cancel) override;
|
void showBlockingDialog(const std::string &text, const std::vector<Component> &components, QueryID askID, const int soundID, bool selection, bool cancel) override;
|
||||||
void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
|
void showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
|
||||||
void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override;
|
void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override;
|
||||||
void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
|
void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
|
||||||
|
std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define NAME "EmptyAI 0.1"
|
#define NAME "EmptyAI 0.1"
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include "../../lib/serializer/BinarySerializer.h"
|
#include "../../lib/serializer/BinarySerializer.h"
|
||||||
#include "../../lib/serializer/BinaryDeserializer.h"
|
#include "../../lib/serializer/BinaryDeserializer.h"
|
||||||
#include "../../lib/battle/BattleStateInfoForRetreat.h"
|
#include "../../lib/battle/BattleStateInfoForRetreat.h"
|
||||||
|
#include "../../lib/battle/BattleInfo.h"
|
||||||
|
|
||||||
#include "AIGateway.h"
|
#include "AIGateway.h"
|
||||||
#include "Goals/Goals.h"
|
#include "Goals/Goals.h"
|
||||||
@ -34,26 +35,26 @@ const float RETREAT_THRESHOLD = 0.3f;
|
|||||||
const double RETREAT_ABSOLUTE_THRESHOLD = 10000.;
|
const double RETREAT_ABSOLUTE_THRESHOLD = 10000.;
|
||||||
|
|
||||||
//one thread may be turn of AI and another will be handling a side effect for AI2
|
//one thread may be turn of AI and another will be handling a side effect for AI2
|
||||||
boost::thread_specific_ptr<CCallback> cb;
|
thread_local CCallback * cb = nullptr;
|
||||||
boost::thread_specific_ptr<AIGateway> ai;
|
thread_local AIGateway * ai = nullptr;
|
||||||
|
|
||||||
//helper RAII to manage global ai/cb ptrs
|
//helper RAII to manage global ai/cb ptrs
|
||||||
struct SetGlobalState
|
struct SetGlobalState
|
||||||
{
|
{
|
||||||
SetGlobalState(AIGateway * AI)
|
SetGlobalState(AIGateway * AI)
|
||||||
{
|
{
|
||||||
assert(!ai.get());
|
assert(!ai);
|
||||||
assert(!cb.get());
|
assert(!cb);
|
||||||
|
|
||||||
ai.reset(AI);
|
ai = AI;
|
||||||
cb.reset(AI->myCb.get());
|
cb = AI->myCb.get();
|
||||||
}
|
}
|
||||||
~SetGlobalState()
|
~SetGlobalState()
|
||||||
{
|
{
|
||||||
//TODO: how to handle rm? shouldn't be called after ai is destroyed, hopefully
|
//TODO: how to handle rm? shouldn't be called after ai is destroyed, hopefully
|
||||||
//TODO: to ensure that, make rm unique_ptr
|
//TODO: to ensure that, make rm unique_ptr
|
||||||
ai.release();
|
ai = nullptr;
|
||||||
cb.release();
|
cb = nullptr;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -153,10 +154,13 @@ void AIGateway::artifactAssembled(const ArtifactLocation & al)
|
|||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::showTavernWindow(const CGObjectInstance * townOrTavern)
|
void AIGateway::showTavernWindow(const CGObjectInstance * object, const CGHeroInstance * visitor, QueryID queryID)
|
||||||
{
|
{
|
||||||
LOG_TRACE(logAi);
|
LOG_TRACE(logAi);
|
||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
|
|
||||||
|
status.addQuery(queryID, "TavernWindow");
|
||||||
|
requestActionASAP([=](){ answerQuery(queryID, 0); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::showThievesGuildWindow(const CGObjectInstance * obj)
|
void AIGateway::showThievesGuildWindow(const CGObjectInstance * obj)
|
||||||
@ -192,7 +196,7 @@ void AIGateway::gameOver(PlayerColor player, const EVictoryLossCheckResult & vic
|
|||||||
{
|
{
|
||||||
LOG_TRACE_PARAMS(logAi, "victoryLossCheckResult '%s'", victoryLossCheckResult.messageToSelf.toString());
|
LOG_TRACE_PARAMS(logAi, "victoryLossCheckResult '%s'", victoryLossCheckResult.messageToSelf.toString());
|
||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
logAi->debug("Player %d (%s): I heard that player %d (%s) %s.", playerID, playerID.getStr(), player, player.getStr(), (victoryLossCheckResult.victory() ? "won" : "lost"));
|
logAi->debug("Player %d (%s): I heard that player %d (%s) %s.", playerID, playerID.toString(), player, player.toString(), (victoryLossCheckResult.victory() ? "won" : "lost"));
|
||||||
|
|
||||||
// some whitespace to flush stream
|
// some whitespace to flush stream
|
||||||
logAi->debug(std::string(200, ' '));
|
logAi->debug(std::string(200, ' '));
|
||||||
@ -201,12 +205,12 @@ void AIGateway::gameOver(PlayerColor player, const EVictoryLossCheckResult & vic
|
|||||||
{
|
{
|
||||||
if(victoryLossCheckResult.victory())
|
if(victoryLossCheckResult.victory())
|
||||||
{
|
{
|
||||||
logAi->debug("AIGateway: Player %d (%s) won. I won! Incredible!", player, player.getStr());
|
logAi->debug("AIGateway: Player %d (%s) won. I won! Incredible!", player, player.toString());
|
||||||
logAi->debug("Turn nr %d", myCb->getDate());
|
logAi->debug("Turn nr %d", myCb->getDate());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logAi->debug("AIGateway: Player %d (%s) lost. It's me. What a disappointment! :(", player, player.getStr());
|
logAi->debug("AIGateway: Player %d (%s) lost. It's me. What a disappointment! :(", player, player.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// some whitespace to flush stream
|
// some whitespace to flush stream
|
||||||
@ -314,16 +318,23 @@ void AIGateway::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID her
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::heroPrimarySkillChanged(const CGHeroInstance * hero, int which, si64 val)
|
void AIGateway::heroPrimarySkillChanged(const CGHeroInstance * hero, PrimarySkill which, si64 val)
|
||||||
{
|
{
|
||||||
LOG_TRACE_PARAMS(logAi, "which '%i', val '%i'", which % val);
|
LOG_TRACE_PARAMS(logAi, "which '%i', val '%i'", static_cast<int>(which) % val);
|
||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::showRecruitmentDialog(const CGDwelling * dwelling, const CArmedInstance * dst, int level)
|
void AIGateway::showRecruitmentDialog(const CGDwelling * dwelling, const CArmedInstance * dst, int level, QueryID queryID)
|
||||||
{
|
{
|
||||||
LOG_TRACE_PARAMS(logAi, "level '%i'", level);
|
LOG_TRACE_PARAMS(logAi, "level '%i'", level);
|
||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
|
|
||||||
|
status.addQuery(queryID, "RecruitmentDialog");
|
||||||
|
|
||||||
|
requestActionASAP([=](){
|
||||||
|
recruitCreatures(dwelling, dst);
|
||||||
|
answerQuery(queryID, 0);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::heroMovePointsChanged(const CGHeroInstance * hero)
|
void AIGateway::heroMovePointsChanged(const CGHeroInstance * hero)
|
||||||
@ -348,7 +359,7 @@ void AIGateway::newObject(const CGObjectInstance * obj)
|
|||||||
|
|
||||||
//to prevent AI from accessing objects that got deleted while they became invisible (Cover of Darkness, enemy hero moved etc.) below code allows AI to know deletion of objects out of sight
|
//to prevent AI from accessing objects that got deleted while they became invisible (Cover of Darkness, enemy hero moved etc.) below code allows AI to know deletion of objects out of sight
|
||||||
//see: RemoveObject::applyFirstCl, to keep AI "not cheating" do not use advantage of this and use this function just to prevent crashes
|
//see: RemoveObject::applyFirstCl, to keep AI "not cheating" do not use advantage of this and use this function just to prevent crashes
|
||||||
void AIGateway::objectRemoved(const CGObjectInstance * obj)
|
void AIGateway::objectRemoved(const CGObjectInstance * obj, const PlayerColor & initiator)
|
||||||
{
|
{
|
||||||
LOG_TRACE(logAi);
|
LOG_TRACE(logAi);
|
||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
@ -387,7 +398,7 @@ void AIGateway::heroCreated(const CGHeroInstance * h)
|
|||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::advmapSpellCast(const CGHeroInstance * caster, int spellID)
|
void AIGateway::advmapSpellCast(const CGHeroInstance * caster, SpellID spellID)
|
||||||
{
|
{
|
||||||
LOG_TRACE_PARAMS(logAi, "spellID '%i", spellID);
|
LOG_TRACE_PARAMS(logAi, "spellID '%i", spellID);
|
||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
@ -424,10 +435,13 @@ void AIGateway::receivedResource()
|
|||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::showUniversityWindow(const IMarket * market, const CGHeroInstance * visitor)
|
void AIGateway::showUniversityWindow(const IMarket * market, const CGHeroInstance * visitor, QueryID queryID)
|
||||||
{
|
{
|
||||||
LOG_TRACE(logAi);
|
LOG_TRACE(logAi);
|
||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
|
|
||||||
|
status.addQuery(queryID, "UniversityWindow");
|
||||||
|
requestActionASAP([=](){ answerQuery(queryID, 0); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::heroManaPointsChanged(const CGHeroInstance * hero)
|
void AIGateway::heroManaPointsChanged(const CGHeroInstance * hero)
|
||||||
@ -497,10 +511,13 @@ void AIGateway::heroBonusChanged(const CGHeroInstance * hero, const Bonus & bonu
|
|||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::showMarketWindow(const IMarket * market, const CGHeroInstance * visitor)
|
void AIGateway::showMarketWindow(const IMarket * market, const CGHeroInstance * visitor, QueryID queryID)
|
||||||
{
|
{
|
||||||
LOG_TRACE(logAi);
|
LOG_TRACE(logAi);
|
||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
|
|
||||||
|
status.addQuery(queryID, "MarketWindow");
|
||||||
|
requestActionASAP([=](){ answerQuery(queryID, 0); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain)
|
void AIGateway::showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain)
|
||||||
@ -510,7 +527,7 @@ void AIGateway::showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositio
|
|||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<BattleAction> AIGateway::makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState)
|
std::optional<BattleAction> AIGateway::makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState)
|
||||||
{
|
{
|
||||||
LOG_TRACE(logAi);
|
LOG_TRACE(logAi);
|
||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
@ -535,7 +552,7 @@ void AIGateway::initGameInterface(std::shared_ptr<Environment> env, std::shared_
|
|||||||
cbc = CB;
|
cbc = CB;
|
||||||
|
|
||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
playerID = *myCb->getMyColor();
|
playerID = *myCb->getPlayerID();
|
||||||
myCb->waitTillRealize = true;
|
myCb->waitTillRealize = true;
|
||||||
myCb->unlockGsWhenWaiting = true;
|
myCb->unlockGsWhenWaiting = true;
|
||||||
|
|
||||||
@ -544,15 +561,17 @@ void AIGateway::initGameInterface(std::shared_ptr<Environment> env, std::shared_
|
|||||||
retrieveVisitableObjs();
|
retrieveVisitableObjs();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::yourTurn()
|
void AIGateway::yourTurn(QueryID queryID)
|
||||||
{
|
{
|
||||||
LOG_TRACE(logAi);
|
LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
|
||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
|
status.addQuery(queryID, "YourTurn");
|
||||||
|
requestActionASAP([=](){ answerQuery(queryID, 0); });
|
||||||
status.startedTurn();
|
status.startedTurn();
|
||||||
makingTurn = std::make_unique<boost::thread>(&AIGateway::makeTurn, this);
|
makingTurn = std::make_unique<boost::thread>(&AIGateway::makeTurn, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID)
|
void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID)
|
||||||
{
|
{
|
||||||
LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
|
LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
|
||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
@ -651,7 +670,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID)
|
void AIGateway::showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID)
|
||||||
{
|
{
|
||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
status.addQuery(askID, boost::str(boost::format("Teleport dialog query with %d exits") % exits.size()));
|
status.addQuery(askID, boost::str(boost::format("Teleport dialog query with %d exits") % exits.size()));
|
||||||
@ -699,8 +718,8 @@ void AIGateway::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstan
|
|||||||
LOG_TRACE_PARAMS(logAi, "removableUnits '%i', queryID '%i'", removableUnits % queryID);
|
LOG_TRACE_PARAMS(logAi, "removableUnits '%i', queryID '%i'", removableUnits % queryID);
|
||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
|
|
||||||
std::string s1 = up ? up->nodeName() : "NONE";
|
std::string s1 = up->nodeName();
|
||||||
std::string s2 = down ? down->nodeName() : "NONE";
|
std::string s2 = down->nodeName();
|
||||||
|
|
||||||
status.addQuery(queryID, boost::str(boost::format("Garrison dialog with %s and %s") % s1 % s2));
|
status.addQuery(queryID, boost::str(boost::format("Garrison dialog with %s and %s") % s1 % s2));
|
||||||
|
|
||||||
@ -708,7 +727,9 @@ void AIGateway::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstan
|
|||||||
requestActionASAP([=]()
|
requestActionASAP([=]()
|
||||||
{
|
{
|
||||||
if(removableUnits && up->tempOwner == down->tempOwner)
|
if(removableUnits && up->tempOwner == down->tempOwner)
|
||||||
|
{
|
||||||
pickBestCreatures(down, up);
|
pickBestCreatures(down, up);
|
||||||
|
}
|
||||||
|
|
||||||
answerQuery(queryID, 0);
|
answerQuery(queryID, 0);
|
||||||
});
|
});
|
||||||
@ -757,7 +778,7 @@ bool AIGateway::makePossibleUpgrades(const CArmedInstance * obj)
|
|||||||
{
|
{
|
||||||
UpgradeInfo ui;
|
UpgradeInfo ui;
|
||||||
myCb->fillUpgradeInfo(obj, SlotID(i), ui);
|
myCb->fillUpgradeInfo(obj, SlotID(i), ui);
|
||||||
if(ui.oldID >= 0 && nullkiller->getFreeResources().canAfford(ui.cost[0] * s->count))
|
if(ui.oldID != CreatureID::NONE && nullkiller->getFreeResources().canAfford(ui.cost[0] * s->count))
|
||||||
{
|
{
|
||||||
myCb->upgradeCreature(obj, SlotID(i), ui.newID[0]);
|
myCb->upgradeCreature(obj, SlotID(i), ui.newID[0]);
|
||||||
upgraded = true;
|
upgraded = true;
|
||||||
@ -773,8 +794,8 @@ void AIGateway::makeTurn()
|
|||||||
{
|
{
|
||||||
MAKING_TURN;
|
MAKING_TURN;
|
||||||
|
|
||||||
auto day = cb->getDate(Date::EDateType::DAY);
|
auto day = cb->getDate(Date::DAY);
|
||||||
logAi->info("Player %d (%s) starting turn, day %d", playerID, playerID.getStr(), day);
|
logAi->info("Player %d (%s) starting turn, day %d", playerID, playerID.toString(), day);
|
||||||
|
|
||||||
boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
|
boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
|
||||||
setThreadName("AIGateway::makeTurn");
|
setThreadName("AIGateway::makeTurn");
|
||||||
@ -828,9 +849,6 @@ void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h
|
|||||||
LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->pos.toString());
|
LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->pos.toString());
|
||||||
switch(obj->ID)
|
switch(obj->ID)
|
||||||
{
|
{
|
||||||
case Obj::CREATURE_GENERATOR1:
|
|
||||||
recruitCreatures(dynamic_cast<const CGDwelling *>(obj), h.get());
|
|
||||||
break;
|
|
||||||
case Obj::TOWN:
|
case Obj::TOWN:
|
||||||
if(h->visitedTown) //we are inside, not just attacking
|
if(h->visitedTown) //we are inside, not just attacking
|
||||||
{
|
{
|
||||||
@ -1078,26 +1096,26 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed)
|
void AIGateway::battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed)
|
||||||
{
|
{
|
||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
assert(playerID > PlayerColor::PLAYER_LIMIT || status.getBattle() == UPCOMING_BATTLE);
|
assert(!playerID.isValidPlayer() || status.getBattle() == UPCOMING_BATTLE);
|
||||||
status.setBattle(ONGOING_BATTLE);
|
status.setBattle(ONGOING_BATTLE);
|
||||||
const CGObjectInstance * presumedEnemy = vstd::backOrNull(cb->getVisitableObjs(tile)); //may be nullptr in some very are cases -> eg. visited monolith and fighting with an enemy at the FoW covered exit
|
const CGObjectInstance * presumedEnemy = vstd::backOrNull(cb->getVisitableObjs(tile)); //may be nullptr in some very are cases -> eg. visited monolith and fighting with an enemy at the FoW covered exit
|
||||||
battlename = boost::str(boost::format("Starting battle of %s attacking %s at %s") % (hero1 ? hero1->getNameTranslated() : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString());
|
battlename = boost::str(boost::format("Starting battle of %s attacking %s at %s") % (hero1 ? hero1->getNameTranslated() : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString());
|
||||||
CAdventureAI::battleStart(army1, army2, tile, hero1, hero2, side, replayAllowed);
|
CAdventureAI::battleStart(battleID, army1, army2, tile, hero1, hero2, side, replayAllowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::battleEnd(const BattleResult * br, QueryID queryID)
|
void AIGateway::battleEnd(const BattleID & battleID, const BattleResult * br, QueryID queryID)
|
||||||
{
|
{
|
||||||
NET_EVENT_HANDLER;
|
NET_EVENT_HANDLER;
|
||||||
assert(status.getBattle() == ONGOING_BATTLE);
|
assert(status.getBattle() == ONGOING_BATTLE);
|
||||||
status.setBattle(ENDING_BATTLE);
|
status.setBattle(ENDING_BATTLE);
|
||||||
bool won = br->winner == myCb->battleGetMySide();
|
bool won = br->winner == myCb->getBattle(battleID)->battleGetMySide();
|
||||||
logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.getStr(), (won ? "won" : "lost"), battlename);
|
logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.toString(), (won ? "won" : "lost"), battlename);
|
||||||
battlename.clear();
|
battlename.clear();
|
||||||
|
|
||||||
if (queryID != -1)
|
if (queryID != QueryID::NONE)
|
||||||
{
|
{
|
||||||
status.addQuery(queryID, "Combat result dialog");
|
status.addQuery(queryID, "Combat result dialog");
|
||||||
const int confirmAction = 0;
|
const int confirmAction = 0;
|
||||||
@ -1106,7 +1124,7 @@ void AIGateway::battleEnd(const BattleResult * br, QueryID queryID)
|
|||||||
answerQuery(queryID, confirmAction);
|
answerQuery(queryID, confirmAction);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
CAdventureAI::battleEnd(br, queryID);
|
CAdventureAI::battleEnd(battleID, br, queryID);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::waitTillFree()
|
void AIGateway::waitTillFree()
|
||||||
@ -1419,7 +1437,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
|
|||||||
|
|
||||||
void AIGateway::endTurn()
|
void AIGateway::endTurn()
|
||||||
{
|
{
|
||||||
logAi->info("Player %d (%s) ends turn", playerID, playerID.getStr());
|
logAi->info("Player %d (%s) ends turn", playerID, playerID.toString());
|
||||||
if(!status.haveTurn())
|
if(!status.haveTurn())
|
||||||
{
|
{
|
||||||
logAi->error("Not having turn at the end of turn???");
|
logAi->error("Not having turn at the end of turn???");
|
||||||
@ -1439,7 +1457,7 @@ void AIGateway::endTurn()
|
|||||||
}
|
}
|
||||||
while(status.haveTurn()); //for some reasons, our request may fail -> stop requesting end of turn only after we've received a confirmation that it's over
|
while(status.haveTurn()); //for some reasons, our request may fail -> stop requesting end of turn only after we've received a confirmation that it's over
|
||||||
|
|
||||||
logGlobal->info("Player %d (%s) ended turn", playerID, playerID.getStr());
|
logGlobal->info("Player %d (%s) ended turn", playerID, playerID.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::buildArmyIn(const CGTownInstance * t)
|
void AIGateway::buildArmyIn(const CGTownInstance * t)
|
||||||
@ -1472,6 +1490,8 @@ void AIGateway::requestActionASAP(std::function<void()> whatToDo)
|
|||||||
boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
|
boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
|
||||||
whatToDo();
|
whatToDo();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
newThread.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::lostHero(HeroPtr h)
|
void AIGateway::lostHero(HeroPtr h)
|
||||||
@ -1605,7 +1625,7 @@ void AIStatus::waitTillFree()
|
|||||||
{
|
{
|
||||||
boost::unique_lock<boost::mutex> lock(mx);
|
boost::unique_lock<boost::mutex> lock(mx);
|
||||||
while(battle != NO_BATTLE || !remainingQueries.empty() || !objectsBeingVisited.empty() || ongoingHeroMovement)
|
while(battle != NO_BATTLE || !remainingQueries.empty() || !objectsBeingVisited.empty() || ongoingHeroMovement)
|
||||||
cv.timed_wait(lock, boost::posix_time::milliseconds(10));
|
cv.wait_for(lock, boost::chrono::milliseconds(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AIStatus::haveTurn()
|
bool AIStatus::haveTurn()
|
||||||
|
@ -111,13 +111,13 @@ public:
|
|||||||
std::string getBattleAIName() const override;
|
std::string getBattleAIName() const override;
|
||||||
|
|
||||||
void initGameInterface(std::shared_ptr<Environment> env, std::shared_ptr<CCallback> CB) override;
|
void initGameInterface(std::shared_ptr<Environment> env, std::shared_ptr<CCallback> CB) override;
|
||||||
void yourTurn() override;
|
void yourTurn(QueryID queryID) override;
|
||||||
|
|
||||||
void heroGotLevel(const CGHeroInstance * hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID) override; //pskill is gained primary skill, interface has to choose one of given skills and call callback with selection id
|
void heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID) override; //pskill is gained primary skill, interface has to choose one of given skills and call callback with selection id
|
||||||
void commanderGotLevel(const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override; //TODO
|
void commanderGotLevel(const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override; //TODO
|
||||||
void showBlockingDialog(const std::string & text, const std::vector<Component> & components, QueryID askID, const int soundID, bool selection, bool cancel) override; //Show a dialog, player must take decision. If selection then he has to choose between one of given components, if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called with number of selected component (1 - n) or 0 for cancel (if allowed) and askID.
|
void showBlockingDialog(const std::string & text, const std::vector<Component> & components, QueryID askID, const int soundID, bool selection, bool cancel) override; //Show a dialog, player must take decision. If selection then he has to choose between one of given components, if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called with number of selected component (1 - n) or 0 for cancel (if allowed) and askID.
|
||||||
void showGarrisonDialog(const CArmedInstance * up, const CGHeroInstance * down, bool removableUnits, QueryID queryID) override; //all stacks operations between these objects become allowed, interface has to call onEnd when done
|
void showGarrisonDialog(const CArmedInstance * up, const CGHeroInstance * down, bool removableUnits, QueryID queryID) override; //all stacks operations between these objects become allowed, interface has to call onEnd when done
|
||||||
void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
|
void showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
|
||||||
void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
|
void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
|
||||||
void saveGame(BinarySerializer & h, const int version) override; //saving
|
void saveGame(BinarySerializer & h, const int version) override; //saving
|
||||||
void loadGame(BinaryDeserializer & h, const int version) override; //loading
|
void loadGame(BinaryDeserializer & h, const int version) override; //loading
|
||||||
@ -130,7 +130,7 @@ public:
|
|||||||
void tileHidden(const std::unordered_set<int3> & pos) override;
|
void tileHidden(const std::unordered_set<int3> & pos) override;
|
||||||
void artifactMoved(const ArtifactLocation & src, const ArtifactLocation & dst) override;
|
void artifactMoved(const ArtifactLocation & src, const ArtifactLocation & dst) override;
|
||||||
void artifactAssembled(const ArtifactLocation & al) override;
|
void artifactAssembled(const ArtifactLocation & al) override;
|
||||||
void showTavernWindow(const CGObjectInstance * townOrTavern) override;
|
void showTavernWindow(const CGObjectInstance * object, const CGHeroInstance * visitor, QueryID queryID) override;
|
||||||
void showThievesGuildWindow(const CGObjectInstance * obj) override;
|
void showThievesGuildWindow(const CGObjectInstance * obj) override;
|
||||||
void playerBlocked(int reason, bool start) override;
|
void playerBlocked(int reason, bool start) override;
|
||||||
void showPuzzleMap() override;
|
void showPuzzleMap() override;
|
||||||
@ -144,20 +144,20 @@ public:
|
|||||||
void heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance * town) override;
|
void heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance * town) override;
|
||||||
void tileRevealed(const std::unordered_set<int3> & pos) override;
|
void tileRevealed(const std::unordered_set<int3> & pos) override;
|
||||||
void heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query) override;
|
void heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query) override;
|
||||||
void heroPrimarySkillChanged(const CGHeroInstance * hero, int which, si64 val) override;
|
void heroPrimarySkillChanged(const CGHeroInstance * hero, PrimarySkill which, si64 val) override;
|
||||||
void showRecruitmentDialog(const CGDwelling * dwelling, const CArmedInstance * dst, int level) override;
|
void showRecruitmentDialog(const CGDwelling * dwelling, const CArmedInstance * dst, int level, QueryID queryID) override;
|
||||||
void heroMovePointsChanged(const CGHeroInstance * hero) override;
|
void heroMovePointsChanged(const CGHeroInstance * hero) override;
|
||||||
void garrisonsChanged(ObjectInstanceID id1, ObjectInstanceID id2) override;
|
void garrisonsChanged(ObjectInstanceID id1, ObjectInstanceID id2) override;
|
||||||
void newObject(const CGObjectInstance * obj) override;
|
void newObject(const CGObjectInstance * obj) override;
|
||||||
void showHillFortWindow(const CGObjectInstance * object, const CGHeroInstance * visitor) override;
|
void showHillFortWindow(const CGObjectInstance * object, const CGHeroInstance * visitor) override;
|
||||||
void playerBonusChanged(const Bonus & bonus, bool gain) override;
|
void playerBonusChanged(const Bonus & bonus, bool gain) override;
|
||||||
void heroCreated(const CGHeroInstance *) override;
|
void heroCreated(const CGHeroInstance *) override;
|
||||||
void advmapSpellCast(const CGHeroInstance * caster, int spellID) override;
|
void advmapSpellCast(const CGHeroInstance * caster, SpellID spellID) override;
|
||||||
void showInfoDialog(EInfoWindowMode type, const std::string & text, const std::vector<Component> & components, int soundID) override;
|
void showInfoDialog(EInfoWindowMode type, const std::string & text, const std::vector<Component> & components, int soundID) override;
|
||||||
void requestRealized(PackageApplied * pa) override;
|
void requestRealized(PackageApplied * pa) override;
|
||||||
void receivedResource() override;
|
void receivedResource() override;
|
||||||
void objectRemoved(const CGObjectInstance * obj) override;
|
void objectRemoved(const CGObjectInstance * obj, const PlayerColor & initiator) override;
|
||||||
void showUniversityWindow(const IMarket * market, const CGHeroInstance * visitor) override;
|
void showUniversityWindow(const IMarket * market, const CGHeroInstance * visitor, QueryID queryID) override;
|
||||||
void heroManaPointsChanged(const CGHeroInstance * hero) override;
|
void heroManaPointsChanged(const CGHeroInstance * hero) override;
|
||||||
void heroSecondarySkillChanged(const CGHeroInstance * hero, int which, int val) override;
|
void heroSecondarySkillChanged(const CGHeroInstance * hero, int which, int val) override;
|
||||||
void battleResultsApplied() override;
|
void battleResultsApplied() override;
|
||||||
@ -165,12 +165,12 @@ public:
|
|||||||
void objectPropertyChanged(const SetObjectProperty * sop) override;
|
void objectPropertyChanged(const SetObjectProperty * sop) override;
|
||||||
void buildChanged(const CGTownInstance * town, BuildingID buildingID, int what) override;
|
void buildChanged(const CGTownInstance * town, BuildingID buildingID, int what) override;
|
||||||
void heroBonusChanged(const CGHeroInstance * hero, const Bonus & bonus, bool gain) override;
|
void heroBonusChanged(const CGHeroInstance * hero, const Bonus & bonus, bool gain) override;
|
||||||
void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor) override;
|
void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor, QueryID queryID) override;
|
||||||
void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
|
void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
|
||||||
std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
|
std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState) override;
|
||||||
|
|
||||||
void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed) override;
|
void battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed) override;
|
||||||
void battleEnd(const BattleResult * br, QueryID queryID) override;
|
void battleEnd(const BattleID & battleID, const BattleResult * br, QueryID queryID) override;
|
||||||
|
|
||||||
void makeTurn();
|
void makeTurn();
|
||||||
|
|
||||||
|
@ -25,10 +25,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
//extern static const int3 dirs[8];
|
|
||||||
|
|
||||||
const CGObjectInstance * ObjectIdRef::operator->() const
|
const CGObjectInstance * ObjectIdRef::operator->() const
|
||||||
{
|
{
|
||||||
return cb->getObj(id, false);
|
return cb->getObj(id, false);
|
||||||
@ -245,7 +241,7 @@ bool isObjectPassable(const CGObjectInstance * obj)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pathfinder internal helper
|
// Pathfinder internal helper
|
||||||
bool isObjectPassable(const CGObjectInstance * obj, PlayerColor playerColor, PlayerRelations::PlayerRelations objectRelations)
|
bool isObjectPassable(const CGObjectInstance * obj, PlayerColor playerColor, PlayerRelations objectRelations)
|
||||||
{
|
{
|
||||||
if((obj->ID == Obj::GARRISON || obj->ID == Obj::GARRISON2)
|
if((obj->ID == Obj::GARRISON || obj->ID == Obj::GARRISON2)
|
||||||
&& objectRelations != PlayerRelations::ENEMIES)
|
&& objectRelations != PlayerRelations::ENEMIES)
|
||||||
@ -278,7 +274,7 @@ creInfo infoFromDC(const dwellingContent & dc)
|
|||||||
creInfo ci;
|
creInfo ci;
|
||||||
ci.count = dc.first;
|
ci.count = dc.first;
|
||||||
ci.creID = dc.second.size() ? dc.second.back() : CreatureID(-1); //should never be accessed
|
ci.creID = dc.second.size() ? dc.second.back() : CreatureID(-1); //should never be accessed
|
||||||
if (ci.creID != -1)
|
if (ci.creID != CreatureID::NONE)
|
||||||
{
|
{
|
||||||
ci.cre = VLC->creatures()->getById(ci.creID);
|
ci.cre = VLC->creatures()->getById(ci.creID);
|
||||||
ci.level = ci.cre->getLevel(); //this is creature tier, while tryRealize expects dwelling level. Ignore.
|
ci.level = ci.cre->getLevel(); //this is creature tier, while tryRealize expects dwelling level. Ignore.
|
||||||
|
@ -57,6 +57,7 @@ using dwellingContent = std::pair<ui32, std::vector<CreatureID>>;
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
struct creInfo;
|
struct creInfo;
|
||||||
|
class AIGateway;
|
||||||
class Nullkiller;
|
class Nullkiller;
|
||||||
|
|
||||||
const int GOLD_MINE_PRODUCTION = 1000, WOOD_ORE_MINE_PRODUCTION = 2, RESOURCE_MINE_PRODUCTION = 1;
|
const int GOLD_MINE_PRODUCTION = 1000, WOOD_ORE_MINE_PRODUCTION = 2, RESOURCE_MINE_PRODUCTION = 1;
|
||||||
@ -67,7 +68,8 @@ const int ALLOWED_ROAMING_HEROES = 8;
|
|||||||
extern const float SAFE_ATTACK_CONSTANT;
|
extern const float SAFE_ATTACK_CONSTANT;
|
||||||
extern const int GOLD_RESERVE;
|
extern const int GOLD_RESERVE;
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
extern thread_local CCallback * cb;
|
||||||
|
extern thread_local AIGateway * ai;
|
||||||
|
|
||||||
enum HeroRole
|
enum HeroRole
|
||||||
{
|
{
|
||||||
@ -149,7 +151,7 @@ struct ObjectIdRef
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<int id>
|
template<Obj::Type id>
|
||||||
bool objWithID(const CGObjectInstance * obj)
|
bool objWithID(const CGObjectInstance * obj)
|
||||||
{
|
{
|
||||||
return obj->ID == id;
|
return obj->ID == id;
|
||||||
@ -201,7 +203,7 @@ void foreach_tile_pos(CCallback * cbp, const Func & foo) // avoid costly retriev
|
|||||||
template<class Func>
|
template<class Func>
|
||||||
void foreach_neighbour(const int3 & pos, const Func & foo)
|
void foreach_neighbour(const int3 & pos, const Func & foo)
|
||||||
{
|
{
|
||||||
CCallback * cbp = cb.get(); // avoid costly retrieval of thread-specific pointer
|
CCallback * cbp = cb; // avoid costly retrieval of thread-specific pointer
|
||||||
for(const int3 & dir : int3::getDirs())
|
for(const int3 & dir : int3::getDirs())
|
||||||
{
|
{
|
||||||
const int3 n = pos + dir;
|
const int3 n = pos + dir;
|
||||||
@ -224,7 +226,7 @@ void foreach_neighbour(CCallback * cbp, const int3 & pos, const Func & foo) // a
|
|||||||
bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater);
|
bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater);
|
||||||
bool isObjectPassable(const CGObjectInstance * obj);
|
bool isObjectPassable(const CGObjectInstance * obj);
|
||||||
bool isObjectPassable(const Nullkiller * ai, const CGObjectInstance * obj);
|
bool isObjectPassable(const Nullkiller * ai, const CGObjectInstance * obj);
|
||||||
bool isObjectPassable(const CGObjectInstance * obj, PlayerColor playerColor, PlayerRelations::PlayerRelations objectRelations);
|
bool isObjectPassable(const CGObjectInstance * obj, PlayerColor playerColor, PlayerRelations objectRelations);
|
||||||
bool isBlockVisitObj(const int3 & pos);
|
bool isBlockVisitObj(const int3 & pos);
|
||||||
|
|
||||||
bool isWeeklyRevisitable(const CGObjectInstance * obj);
|
bool isWeeklyRevisitable(const CGObjectInstance * obj);
|
||||||
|
@ -153,7 +153,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
|
|||||||
for(auto bonus : *bonusModifiers)
|
for(auto bonus : *bonusModifiers)
|
||||||
{
|
{
|
||||||
// army bonuses will change and object bonuses are temporary
|
// army bonuses will change and object bonuses are temporary
|
||||||
if(bonus->source != BonusSource::ARMY && bonus->source != BonusSource::OBJECT)
|
if(bonus->source != BonusSource::ARMY && bonus->source != BonusSource::OBJECT_INSTANCE && bonus->source != BonusSource::OBJECT_TYPE)
|
||||||
{
|
{
|
||||||
newArmyInstance.addNewBonus(std::make_shared<Bonus>(*bonus));
|
newArmyInstance.addNewBonus(std::make_shared<Bonus>(*bonus));
|
||||||
}
|
}
|
||||||
@ -225,7 +225,8 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
|
|||||||
|
|
||||||
if(weakest->count == 1)
|
if(weakest->count == 1)
|
||||||
{
|
{
|
||||||
assert(resultingArmy.size() > 1);
|
if (resultingArmy.size() == 1)
|
||||||
|
logAi->warn("Unexpected resulting army size!");
|
||||||
|
|
||||||
resultingArmy.erase(weakest);
|
resultingArmy.erase(weakest);
|
||||||
}
|
}
|
||||||
@ -255,7 +256,7 @@ std::shared_ptr<CCreatureSet> ArmyManager::getArmyAvailableToBuyAsCCreatureSet(
|
|||||||
{
|
{
|
||||||
auto ci = infoFromDC(dwelling->creatures[i]);
|
auto ci = infoFromDC(dwelling->creatures[i]);
|
||||||
|
|
||||||
if(!ci.count || ci.creID == -1)
|
if(!ci.count || ci.creID == CreatureID::NONE)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
vstd::amin(ci.count, availableRes / ci.cre->getFullRecruitCost()); //max count we can afford
|
vstd::amin(ci.count, availableRes / ci.cre->getFullRecruitCost()); //max count we can afford
|
||||||
@ -315,7 +316,7 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
|
|||||||
{
|
{
|
||||||
auto ci = infoFromDC(dwelling->creatures[i]);
|
auto ci = infoFromDC(dwelling->creatures[i]);
|
||||||
|
|
||||||
if(ci.creID == -1) continue;
|
if(ci.creID == CreatureID::NONE) continue;
|
||||||
|
|
||||||
if(i < GameConstants::CREATURES_PER_TOWN && countGrowth)
|
if(i < GameConstants::CREATURES_PER_TOWN && countGrowth)
|
||||||
{
|
{
|
||||||
@ -392,7 +393,7 @@ void ArmyManager::update()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(auto army : totalArmy)
|
for(auto & army : totalArmy)
|
||||||
{
|
{
|
||||||
army.second.creature = army.first.toCreature();
|
army.second.creature = army.first.toCreature();
|
||||||
army.second.power = evaluateStackPower(army.second.creature, army.second.count);
|
army.second.power = evaluateStackPower(army.second.creature, army.second.count);
|
||||||
|
@ -24,7 +24,7 @@ void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
|
|||||||
|
|
||||||
for(auto &pair : townInfo->buildings)
|
for(auto &pair : townInfo->buildings)
|
||||||
{
|
{
|
||||||
if(pair.second->upgrade != -1)
|
if(pair.second->upgrade != BuildingID::NONE)
|
||||||
{
|
{
|
||||||
parentMap[pair.second->upgrade] = pair.first;
|
parentMap[pair.second->upgrade] = pair.first;
|
||||||
}
|
}
|
||||||
@ -160,7 +160,7 @@ void BuildAnalyzer::update()
|
|||||||
|
|
||||||
updateDailyIncome();
|
updateDailyIncome();
|
||||||
|
|
||||||
if(ai->cb->getDate(Date::EDateType::DAY) == 1)
|
if(ai->cb->getDate(Date::DAY) == 1)
|
||||||
{
|
{
|
||||||
goldPreasure = 1;
|
goldPreasure = 1;
|
||||||
}
|
}
|
||||||
@ -256,7 +256,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
|||||||
{
|
{
|
||||||
logAi->trace("cant build. Need other dwelling");
|
logAi->trace("cant build. Need other dwelling");
|
||||||
}
|
}
|
||||||
else
|
else if(missingBuildings[0] != toBuild)
|
||||||
{
|
{
|
||||||
logAi->trace("cant build. Need %d", missingBuildings[0].num);
|
logAi->trace("cant build. Need %d", missingBuildings[0].num);
|
||||||
|
|
||||||
@ -274,6 +274,12 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
|||||||
|
|
||||||
return prerequisite;
|
return prerequisite;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logAi->trace("Cant build. The building requires itself as prerequisite");
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -72,7 +72,13 @@ void DangerHitMapAnalyzer::updateHitMap()
|
|||||||
if(ai->cb->getPlayerRelations(ai->playerID, pair.first) != PlayerRelations::ENEMIES)
|
if(ai->cb->getPlayerRelations(ai->playerID, pair.first) != PlayerRelations::ENEMIES)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ai->pathfinder->updatePaths(pair.second, PathfinderSettings());
|
PathfinderSettings ps;
|
||||||
|
|
||||||
|
ps.mainTurnDistanceLimit = 10;
|
||||||
|
ps.scoutTurnDistanceLimit = 10;
|
||||||
|
ps.useHeroChain = false;
|
||||||
|
|
||||||
|
ai->pathfinder->updatePaths(pair.second, ps);
|
||||||
|
|
||||||
boost::this_thread::interruption_point();
|
boost::this_thread::interruption_point();
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance *
|
|||||||
|
|
||||||
float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
|
float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
|
||||||
{
|
{
|
||||||
auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, hero->type->getIndex());
|
auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, BonusSourceID(hero->type->getId()));
|
||||||
auto secondarySkillBonus = Selector::targetSourceType()(BonusSource::SECONDARY_SKILL);
|
auto secondarySkillBonus = Selector::targetSourceType()(BonusSource::SECONDARY_SKILL);
|
||||||
auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus));
|
auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus));
|
||||||
auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(BonusSource::SECONDARY_SKILL));
|
auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(BonusSource::SECONDARY_SKILL));
|
||||||
@ -83,7 +83,7 @@ float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
|
|||||||
|
|
||||||
if(hasBonus)
|
if(hasBonus)
|
||||||
{
|
{
|
||||||
SecondarySkill bonusSkill = SecondarySkill(bonus->sid);
|
SecondarySkill bonusSkill = bonus->sid.as<SecondarySkill>();
|
||||||
float bonusScore = wariorSkillsScores.evaluateSecSkill(hero, bonusSkill);
|
float bonusScore = wariorSkillsScores.evaluateSecSkill(hero, bonusSkill);
|
||||||
|
|
||||||
if(bonusScore > 0)
|
if(bonusScore > 0)
|
||||||
@ -190,6 +190,41 @@ bool HeroManager::heroCapReached() const
|
|||||||
|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP);
|
|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float HeroManager::getMagicStrength(const CGHeroInstance * hero) const
|
||||||
|
{
|
||||||
|
auto hasFly = hero->spellbookContainsSpell(SpellID::FLY);
|
||||||
|
auto hasTownPortal = hero->spellbookContainsSpell(SpellID::TOWN_PORTAL);
|
||||||
|
auto manaLimit = hero->manaLimit();
|
||||||
|
auto spellPower = hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
|
||||||
|
auto hasEarth = hero->getSpellSchoolLevel(SpellID(SpellID::TOWN_PORTAL).toSpell()) > 0;
|
||||||
|
|
||||||
|
auto score = 0.0f;
|
||||||
|
|
||||||
|
for(auto spellId : hero->getSpellsInSpellbook())
|
||||||
|
{
|
||||||
|
auto spell = spellId.toSpell();
|
||||||
|
auto schoolLevel = hero->getSpellSchoolLevel(spell);
|
||||||
|
|
||||||
|
score += (spell->getLevel() + 1) * (schoolLevel + 1) * 0.05f;
|
||||||
|
}
|
||||||
|
|
||||||
|
vstd::amin(score, 1);
|
||||||
|
|
||||||
|
score *= std::min(1.0f, spellPower / 10.0f);
|
||||||
|
|
||||||
|
if(hasFly)
|
||||||
|
score += 0.3f;
|
||||||
|
|
||||||
|
if(hasTownPortal && hasEarth)
|
||||||
|
score += 0.6f;
|
||||||
|
|
||||||
|
vstd::amin(score, 1);
|
||||||
|
|
||||||
|
score *= std::min(1.0f, manaLimit / 100.0f);
|
||||||
|
|
||||||
|
return std::min(score, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
bool HeroManager::canRecruitHero(const CGTownInstance * town) const
|
bool HeroManager::canRecruitHero(const CGTownInstance * town) const
|
||||||
{
|
{
|
||||||
if(!town)
|
if(!town)
|
||||||
@ -278,7 +313,7 @@ void ExistingSkillRule::evaluateScore(const CGHeroInstance * hero, SecondarySkil
|
|||||||
if(heroSkill.first == skill)
|
if(heroSkill.first == skill)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
upgradesLeft += SecSkillLevel::EXPERT - heroSkill.second;
|
upgradesLeft += MasteryLevel::EXPERT - heroSkill.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(score >= 2 || (score >= 1 && upgradesLeft <= 1))
|
if(score >= 2 || (score >= 1 && upgradesLeft <= 1))
|
||||||
@ -292,7 +327,7 @@ void WisdomRule::evaluateScore(const CGHeroInstance * hero, SecondarySkill skill
|
|||||||
|
|
||||||
auto wisdomLevel = hero->getSecSkillLevel(SecondarySkill::WISDOM);
|
auto wisdomLevel = hero->getSecSkillLevel(SecondarySkill::WISDOM);
|
||||||
|
|
||||||
if(hero->level > 10 && wisdomLevel == SecSkillLevel::NONE)
|
if(hero->level > 10 && wisdomLevel == MasteryLevel::NONE)
|
||||||
score += 1.5;
|
score += 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,7 +345,7 @@ void AtLeastOneMagicRule::evaluateScore(const CGHeroInstance * hero, SecondarySk
|
|||||||
|
|
||||||
bool heroHasAnyMagic = vstd::contains_if(magicSchools, [&](SecondarySkill skill) -> bool
|
bool heroHasAnyMagic = vstd::contains_if(magicSchools, [&](SecondarySkill skill) -> bool
|
||||||
{
|
{
|
||||||
return hero->getSecSkillLevel(skill) > SecSkillLevel::NONE;
|
return hero->getSecSkillLevel(skill) > MasteryLevel::NONE;
|
||||||
});
|
});
|
||||||
|
|
||||||
if(!heroHasAnyMagic)
|
if(!heroHasAnyMagic)
|
||||||
|
@ -34,6 +34,7 @@ public:
|
|||||||
virtual bool heroCapReached() const = 0;
|
virtual bool heroCapReached() const = 0;
|
||||||
virtual const CGHeroInstance * findHeroWithGrail() const = 0;
|
virtual const CGHeroInstance * findHeroWithGrail() const = 0;
|
||||||
virtual const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit) const = 0;
|
virtual const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit) const = 0;
|
||||||
|
virtual float getMagicStrength(const CGHeroInstance * hero) const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DLL_EXPORT ISecondarySkillRule
|
class DLL_EXPORT ISecondarySkillRule
|
||||||
@ -76,6 +77,7 @@ public:
|
|||||||
bool heroCapReached() const override;
|
bool heroCapReached() const override;
|
||||||
const CGHeroInstance * findHeroWithGrail() const override;
|
const CGHeroInstance * findHeroWithGrail() const override;
|
||||||
const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit) const override;
|
const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit) const override;
|
||||||
|
float getMagicStrength(const CGHeroInstance * hero) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
float evaluateFightingStrength(const CGHeroInstance * hero) const;
|
float evaluateFightingStrength(const CGHeroInstance * hero) const;
|
||||||
|
@ -93,9 +93,14 @@ std::vector<std::shared_ptr<ObjectCluster>> ObjectClusterizer::getLockedClusters
|
|||||||
const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) const
|
const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) const
|
||||||
{
|
{
|
||||||
for(auto node = path.nodes.rbegin(); node != path.nodes.rend(); node++)
|
for(auto node = path.nodes.rbegin(); node != path.nodes.rend(); node++)
|
||||||
|
{
|
||||||
|
std::vector<const CGObjectInstance *> blockers = {};
|
||||||
|
|
||||||
|
if(node->layer == EPathfindingLayer::LAND || node->layer == EPathfindingLayer::SAIL)
|
||||||
{
|
{
|
||||||
auto guardPos = ai->cb->getGuardingCreaturePosition(node->coord);
|
auto guardPos = ai->cb->getGuardingCreaturePosition(node->coord);
|
||||||
auto blockers = ai->cb->getVisitableObjs(node->coord);
|
|
||||||
|
blockers = ai->cb->getVisitableObjs(node->coord);
|
||||||
|
|
||||||
if(guardPos.valid())
|
if(guardPos.valid())
|
||||||
{
|
{
|
||||||
@ -106,6 +111,7 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
|
|||||||
blockers.insert(blockers.begin(), guard);
|
blockers.insert(blockers.begin(), guard);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(node->specialAction && node->actionIsBlocked)
|
if(node->specialAction && node->actionIsBlocked)
|
||||||
{
|
{
|
||||||
|
@ -20,9 +20,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
std::string BuildingBehavior::toString() const
|
std::string BuildingBehavior::toString() const
|
||||||
|
@ -17,9 +17,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
std::string BuyArmyBehavior::toString() const
|
std::string BuyArmyBehavior::toString() const
|
||||||
|
@ -19,9 +19,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -19,9 +19,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
std::string ClusterBehavior::toString() const
|
std::string ClusterBehavior::toString() const
|
||||||
|
@ -25,9 +25,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
const float TREAT_IGNORE_RATIO = 2;
|
const float TREAT_IGNORE_RATIO = 2;
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
@ -114,7 +111,7 @@ bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoa
|
|||||||
if(ai->nullkiller->isHeroLocked(town->garrisonHero.get()))
|
if(ai->nullkiller->isHeroLocked(town->garrisonHero.get()))
|
||||||
{
|
{
|
||||||
logAi->trace(
|
logAi->trace(
|
||||||
"Hero %s in garrison of town %s is suposed to defend the town",
|
"Hero %s in garrison of town %s is supposed to defend the town",
|
||||||
town->garrisonHero->getNameTranslated(),
|
town->garrisonHero->getNameTranslated(),
|
||||||
town->getNameTranslated());
|
town->getNameTranslated());
|
||||||
|
|
||||||
|
@ -23,9 +23,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
std::string GatherArmyBehavior::toString() const
|
std::string GatherArmyBehavior::toString() const
|
||||||
|
@ -17,9 +17,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
std::string RecruitHeroBehavior::toString() const
|
std::string RecruitHeroBehavior::toString() const
|
||||||
@ -84,7 +81,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(treasureSourcesCount < 5)
|
if(treasureSourcesCount < 5 && (town->garrisonHero || town->getUpperArmy()->getArmyStrength() < 10000))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1
|
if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1
|
||||||
|
@ -21,9 +21,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
std::string StartupBehavior::toString() const
|
std::string StartupBehavior::toString() const
|
||||||
|
70
AI/Nullkiller/Behaviors/StayAtTownBehavior.cpp
Normal file
70
AI/Nullkiller/Behaviors/StayAtTownBehavior.cpp
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* StartupBehavior.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 "StayAtTownBehavior.h"
|
||||||
|
#include "../AIGateway.h"
|
||||||
|
#include "../AIUtility.h"
|
||||||
|
#include "../Goals/StayAtTown.h"
|
||||||
|
#include "../Goals/Composition.h"
|
||||||
|
#include "../Goals/ExecuteHeroChain.h"
|
||||||
|
#include "lib/mapObjects/MapObjects.h" //for victory conditions
|
||||||
|
#include "../Engine/Nullkiller.h"
|
||||||
|
|
||||||
|
namespace NKAI
|
||||||
|
{
|
||||||
|
|
||||||
|
using namespace Goals;
|
||||||
|
|
||||||
|
std::string StayAtTownBehavior::toString() const
|
||||||
|
{
|
||||||
|
return "StayAtTownBehavior";
|
||||||
|
}
|
||||||
|
|
||||||
|
Goals::TGoalVec StayAtTownBehavior::decompose() const
|
||||||
|
{
|
||||||
|
Goals::TGoalVec tasks;
|
||||||
|
auto towns = cb->getTownsInfo();
|
||||||
|
|
||||||
|
if(!towns.size())
|
||||||
|
return tasks;
|
||||||
|
|
||||||
|
for(auto town : towns)
|
||||||
|
{
|
||||||
|
if(!town->hasBuilt(BuildingID::MAGES_GUILD_1))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto paths = ai->nullkiller->pathfinder->getPathInfo(town->visitablePos());
|
||||||
|
|
||||||
|
for(auto & path : paths)
|
||||||
|
{
|
||||||
|
if(town->visitingHero && town->visitingHero.get() != path.targetHero)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(path.turn() == 0 && !path.getFirstBlockedAction() && path.exchangeCount <= 1)
|
||||||
|
{
|
||||||
|
if(path.targetHero->mana == path.targetHero->manaLimit())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Composition stayAtTown;
|
||||||
|
|
||||||
|
stayAtTown.addNextSequence({
|
||||||
|
sptr(ExecuteHeroChain(path)),
|
||||||
|
sptr(StayAtTown(town, path))
|
||||||
|
});
|
||||||
|
|
||||||
|
tasks.push_back(sptr(stayAtTown));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
39
AI/Nullkiller/Behaviors/StayAtTownBehavior.h
Normal file
39
AI/Nullkiller/Behaviors/StayAtTownBehavior.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* StayAtTownBehavior.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/VCMI_Lib.h"
|
||||||
|
#include "../Goals/CGoal.h"
|
||||||
|
#include "../AIUtility.h"
|
||||||
|
|
||||||
|
namespace NKAI
|
||||||
|
{
|
||||||
|
namespace Goals
|
||||||
|
{
|
||||||
|
class StayAtTownBehavior : public CGoal<StayAtTownBehavior>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
StayAtTownBehavior()
|
||||||
|
:CGoal(STAY_AT_TOWN_BEHAVIOR)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual TGoalVec decompose() const override;
|
||||||
|
virtual std::string toString() const override;
|
||||||
|
|
||||||
|
virtual bool operator==(const StayAtTownBehavior & other) const override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -9,6 +9,7 @@ set(Nullkiller_SRCS
|
|||||||
Pathfinding/Actions/BuyArmyAction.cpp
|
Pathfinding/Actions/BuyArmyAction.cpp
|
||||||
Pathfinding/Actions/BoatActions.cpp
|
Pathfinding/Actions/BoatActions.cpp
|
||||||
Pathfinding/Actions/TownPortalAction.cpp
|
Pathfinding/Actions/TownPortalAction.cpp
|
||||||
|
Pathfinding/Actions/AdventureSpellCastMovementActions.cpp
|
||||||
Pathfinding/Rules/AILayerTransitionRule.cpp
|
Pathfinding/Rules/AILayerTransitionRule.cpp
|
||||||
Pathfinding/Rules/AIMovementAfterDestinationRule.cpp
|
Pathfinding/Rules/AIMovementAfterDestinationRule.cpp
|
||||||
Pathfinding/Rules/AIMovementToDestinationRule.cpp
|
Pathfinding/Rules/AIMovementToDestinationRule.cpp
|
||||||
@ -34,6 +35,7 @@ set(Nullkiller_SRCS
|
|||||||
Goals/ExecuteHeroChain.cpp
|
Goals/ExecuteHeroChain.cpp
|
||||||
Goals/ExchangeSwapTownHeroes.cpp
|
Goals/ExchangeSwapTownHeroes.cpp
|
||||||
Goals/CompleteQuest.cpp
|
Goals/CompleteQuest.cpp
|
||||||
|
Goals/StayAtTown.cpp
|
||||||
Markers/ArmyUpgrade.cpp
|
Markers/ArmyUpgrade.cpp
|
||||||
Markers/HeroExchange.cpp
|
Markers/HeroExchange.cpp
|
||||||
Markers/UnlockCluster.cpp
|
Markers/UnlockCluster.cpp
|
||||||
@ -52,6 +54,7 @@ set(Nullkiller_SRCS
|
|||||||
Behaviors/BuildingBehavior.cpp
|
Behaviors/BuildingBehavior.cpp
|
||||||
Behaviors/GatherArmyBehavior.cpp
|
Behaviors/GatherArmyBehavior.cpp
|
||||||
Behaviors/ClusterBehavior.cpp
|
Behaviors/ClusterBehavior.cpp
|
||||||
|
Behaviors/StayAtTownBehavior.cpp
|
||||||
Helpers/ArmyFormation.cpp
|
Helpers/ArmyFormation.cpp
|
||||||
AIGateway.cpp
|
AIGateway.cpp
|
||||||
)
|
)
|
||||||
@ -69,6 +72,7 @@ set(Nullkiller_HEADERS
|
|||||||
Pathfinding/Actions/BuyArmyAction.h
|
Pathfinding/Actions/BuyArmyAction.h
|
||||||
Pathfinding/Actions/BoatActions.h
|
Pathfinding/Actions/BoatActions.h
|
||||||
Pathfinding/Actions/TownPortalAction.h
|
Pathfinding/Actions/TownPortalAction.h
|
||||||
|
Pathfinding/Actions/AdventureSpellCastMovementActions.h
|
||||||
Pathfinding/Rules/AILayerTransitionRule.h
|
Pathfinding/Rules/AILayerTransitionRule.h
|
||||||
Pathfinding/Rules/AIMovementAfterDestinationRule.h
|
Pathfinding/Rules/AIMovementAfterDestinationRule.h
|
||||||
Pathfinding/Rules/AIMovementToDestinationRule.h
|
Pathfinding/Rules/AIMovementToDestinationRule.h
|
||||||
@ -97,6 +101,7 @@ set(Nullkiller_HEADERS
|
|||||||
Goals/ExchangeSwapTownHeroes.h
|
Goals/ExchangeSwapTownHeroes.h
|
||||||
Goals/CompleteQuest.h
|
Goals/CompleteQuest.h
|
||||||
Goals/Goals.h
|
Goals/Goals.h
|
||||||
|
Goals/StayAtTown.h
|
||||||
Markers/ArmyUpgrade.h
|
Markers/ArmyUpgrade.h
|
||||||
Markers/HeroExchange.h
|
Markers/HeroExchange.h
|
||||||
Markers/UnlockCluster.h
|
Markers/UnlockCluster.h
|
||||||
@ -115,6 +120,7 @@ set(Nullkiller_HEADERS
|
|||||||
Behaviors/BuildingBehavior.h
|
Behaviors/BuildingBehavior.h
|
||||||
Behaviors/GatherArmyBehavior.h
|
Behaviors/GatherArmyBehavior.h
|
||||||
Behaviors/ClusterBehavior.h
|
Behaviors/ClusterBehavior.h
|
||||||
|
Behaviors/StayAtTownBehavior.h
|
||||||
Helpers/ArmyFormation.h
|
Helpers/ArmyFormation.h
|
||||||
AIGateway.h
|
AIGateway.h
|
||||||
)
|
)
|
||||||
|
@ -24,9 +24,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
void DeepDecomposer::reset()
|
void DeepDecomposer::reset()
|
||||||
|
@ -20,8 +20,6 @@ namespace NKAI
|
|||||||
#define MIN_AI_STRENGTH (0.5f) //lower when combat AI gets smarter
|
#define MIN_AI_STRENGTH (0.5f) //lower when combat AI gets smarter
|
||||||
#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
|
#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
engineBase::engineBase()
|
engineBase::engineBase()
|
||||||
{
|
{
|
||||||
rules = new fl::RuleBlock();
|
rules = new fl::RuleBlock();
|
||||||
|
@ -8,7 +8,11 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <fl/Headers.h>
|
#if __has_include(<fuzzylite/Headers.h>)
|
||||||
|
# include <fuzzylite/Headers.h>
|
||||||
|
#else
|
||||||
|
# include <fl/Headers.h>
|
||||||
|
#endif
|
||||||
#include "../Goals/AbstractGoal.h"
|
#include "../Goals/AbstractGoal.h"
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
@ -111,7 +111,7 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
|
|||||||
{
|
{
|
||||||
auto cb = ai->cb.get();
|
auto cb = ai->cb.get();
|
||||||
|
|
||||||
if(obj->tempOwner < PlayerColor::PLAYER_LIMIT && cb->getPlayerRelations(obj->tempOwner, ai->playerID) != PlayerRelations::ENEMIES) //owned or allied objects don't pose any threat
|
if(obj->tempOwner.isValidPlayer() && cb->getPlayerRelations(obj->tempOwner, ai->playerID) != PlayerRelations::ENEMIES) //owned or allied objects don't pose any threat
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
switch(obj->ID)
|
switch(obj->ID)
|
||||||
|
@ -18,15 +18,13 @@
|
|||||||
#include "../Behaviors/BuildingBehavior.h"
|
#include "../Behaviors/BuildingBehavior.h"
|
||||||
#include "../Behaviors/GatherArmyBehavior.h"
|
#include "../Behaviors/GatherArmyBehavior.h"
|
||||||
#include "../Behaviors/ClusterBehavior.h"
|
#include "../Behaviors/ClusterBehavior.h"
|
||||||
|
#include "../Behaviors/StayAtTownBehavior.h"
|
||||||
#include "../Goals/Invalid.h"
|
#include "../Goals/Invalid.h"
|
||||||
#include "../Goals/Composition.h"
|
#include "../Goals/Composition.h"
|
||||||
|
|
||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
@ -141,8 +139,8 @@ void Nullkiller::updateAiState(int pass, bool fast)
|
|||||||
{
|
{
|
||||||
memory->removeInvisibleObjects(cb.get());
|
memory->removeInvisibleObjects(cb.get());
|
||||||
|
|
||||||
dangerHitMap->calculateTileOwners();
|
|
||||||
dangerHitMap->updateHitMap();
|
dangerHitMap->updateHitMap();
|
||||||
|
dangerHitMap->calculateTileOwners();
|
||||||
|
|
||||||
boost::this_thread::interruption_point();
|
boost::this_thread::interruption_point();
|
||||||
|
|
||||||
@ -265,7 +263,8 @@ void Nullkiller::makeTurn()
|
|||||||
choseBestTask(sptr(CaptureObjectsBehavior()), 1),
|
choseBestTask(sptr(CaptureObjectsBehavior()), 1),
|
||||||
choseBestTask(sptr(ClusterBehavior()), MAX_DEPTH),
|
choseBestTask(sptr(ClusterBehavior()), MAX_DEPTH),
|
||||||
choseBestTask(sptr(DefenceBehavior()), MAX_DEPTH),
|
choseBestTask(sptr(DefenceBehavior()), MAX_DEPTH),
|
||||||
choseBestTask(sptr(GatherArmyBehavior()), MAX_DEPTH)
|
choseBestTask(sptr(GatherArmyBehavior()), MAX_DEPTH),
|
||||||
|
choseBestTask(sptr(StayAtTownBehavior()), MAX_DEPTH)
|
||||||
};
|
};
|
||||||
|
|
||||||
if(cb->getDate(Date::DAY) == 1)
|
if(cb->getDate(Date::DAY) == 1)
|
||||||
@ -341,7 +340,7 @@ void Nullkiller::executeTask(Goals::TTask task)
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
task->accept(ai.get());
|
task->accept(ai);
|
||||||
logAi->trace("Task %s completed in %lld", taskDescr, timeElapsed(start));
|
logAi->trace("Task %s completed in %lld", taskDescr, timeElapsed(start));
|
||||||
}
|
}
|
||||||
catch(goalFulfilledException &)
|
catch(goalFulfilledException &)
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "../../../lib/filesystem/Filesystem.h"
|
#include "../../../lib/filesystem/Filesystem.h"
|
||||||
#include "../Goals/ExecuteHeroChain.h"
|
#include "../Goals/ExecuteHeroChain.h"
|
||||||
#include "../Goals/BuildThis.h"
|
#include "../Goals/BuildThis.h"
|
||||||
|
#include "../Goals/StayAtTown.h"
|
||||||
#include "../Goals/ExchangeSwapTownHeroes.h"
|
#include "../Goals/ExchangeSwapTownHeroes.h"
|
||||||
#include "../Goals/DismissHero.h"
|
#include "../Goals/DismissHero.h"
|
||||||
#include "../Markers/UnlockCluster.h"
|
#include "../Markers/UnlockCluster.h"
|
||||||
@ -68,7 +69,7 @@ PriorityEvaluator::~PriorityEvaluator()
|
|||||||
|
|
||||||
void PriorityEvaluator::initVisitTile()
|
void PriorityEvaluator::initVisitTile()
|
||||||
{
|
{
|
||||||
auto file = CResourceHandler::get()->load(ResourceID("config/ai/object-priorities.txt"))->readAll();
|
auto file = CResourceHandler::get()->load(ResourcePath("config/ai/object-priorities.txt"))->readAll();
|
||||||
std::string str = std::string((char *)file.first.get(), file.second);
|
std::string str = std::string((char *)file.first.get(), file.second);
|
||||||
engine = fl::FllImporter().fromString(str);
|
engine = fl::FllImporter().fromString(str);
|
||||||
armyLossPersentageVariable = engine->getInputVariable("armyLoss");
|
armyLossPersentageVariable = engine->getInputVariable("armyLoss");
|
||||||
@ -241,13 +242,13 @@ uint64_t evaluateArtifactArmyValue(CArtifactInstance * art)
|
|||||||
return 1500;
|
return 1500;
|
||||||
|
|
||||||
auto statsValue =
|
auto statsValue =
|
||||||
10 * art->valOfBonuses(BonusType::MOVEMENT, 1)
|
10 * art->valOfBonuses(BonusType::MOVEMENT, BonusCustomSubtype::heroMovementLand)
|
||||||
+ 1200 * art->valOfBonuses(BonusType::STACKS_SPEED)
|
+ 1200 * art->valOfBonuses(BonusType::STACKS_SPEED)
|
||||||
+ 700 * art->valOfBonuses(BonusType::MORALE)
|
+ 700 * art->valOfBonuses(BonusType::MORALE)
|
||||||
+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::ATTACK)
|
+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK))
|
||||||
+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::DEFENSE)
|
+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE))
|
||||||
+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::KNOWLEDGE)
|
+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::KNOWLEDGE))
|
||||||
+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::SPELL_POWER)
|
+ 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::SPELL_POWER))
|
||||||
+ 500 * art->valOfBonuses(BonusType::LUCK);
|
+ 500 * art->valOfBonuses(BonusType::LUCK);
|
||||||
|
|
||||||
auto classValue = 0;
|
auto classValue = 0;
|
||||||
@ -309,6 +310,9 @@ uint64_t RewardEvaluator::getArmyReward(
|
|||||||
: 0;
|
: 0;
|
||||||
case Obj::PANDORAS_BOX:
|
case Obj::PANDORAS_BOX:
|
||||||
return 5000;
|
return 5000;
|
||||||
|
case Obj::MAGIC_WELL:
|
||||||
|
case Obj::MAGIC_SPRING:
|
||||||
|
return getManaRecoveryArmyReward(hero);
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -450,6 +454,11 @@ uint64_t RewardEvaluator::townArmyGrowth(const CGTownInstance * town) const
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t RewardEvaluator::getManaRecoveryArmyReward(const CGHeroInstance * hero) const
|
||||||
|
{
|
||||||
|
return ai->heroManager->getMagicStrength(hero) * 10000 * (1.0f - std::sqrt(static_cast<float>(hero->mana) / hero->manaLimit()));
|
||||||
|
}
|
||||||
|
|
||||||
float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) const
|
float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) const
|
||||||
{
|
{
|
||||||
if(!target)
|
if(!target)
|
||||||
@ -519,14 +528,17 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float RewardEvaluator::evaluateWitchHutSkillScore(const CGWitchHut * hut, const CGHeroInstance * hero, HeroRole role) const
|
float RewardEvaluator::evaluateWitchHutSkillScore(const CGObjectInstance * hut, const CGHeroInstance * hero, HeroRole role) const
|
||||||
{
|
{
|
||||||
|
auto rewardable = dynamic_cast<const CRewardableObject *>(hut);
|
||||||
|
assert(rewardable);
|
||||||
|
|
||||||
|
auto skill = SecondarySkill(*rewardable->configuration.getVariable("secondarySkill", "gainedSkill"));
|
||||||
|
|
||||||
if(!hut->wasVisited(hero->tempOwner))
|
if(!hut->wasVisited(hero->tempOwner))
|
||||||
return role == HeroRole::SCOUT ? 2 : 0;
|
return role == HeroRole::SCOUT ? 2 : 0;
|
||||||
|
|
||||||
auto skill = SecondarySkill(hut->ability);
|
if(hero->getSecSkillLevel(skill) != MasteryLevel::NONE
|
||||||
|
|
||||||
if(hero->getSecSkillLevel(skill) != SecSkillLevel::NONE
|
|
||||||
|| hero->secSkills.size() >= GameConstants::SKILL_PER_HERO)
|
|| hero->secSkills.size() >= GameConstants::SKILL_PER_HERO)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
@ -566,7 +578,7 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
|
|||||||
case Obj::LIBRARY_OF_ENLIGHTENMENT:
|
case Obj::LIBRARY_OF_ENLIGHTENMENT:
|
||||||
return 8;
|
return 8;
|
||||||
case Obj::WITCH_HUT:
|
case Obj::WITCH_HUT:
|
||||||
return evaluateWitchHutSkillScore(dynamic_cast<const CGWitchHut *>(target), hero, role);
|
return evaluateWitchHutSkillScore(target, hero, role);
|
||||||
case Obj::PANDORAS_BOX:
|
case Obj::PANDORAS_BOX:
|
||||||
//Can contains experience, spells, or skills (only on custom maps)
|
//Can contains experience, spells, or skills (only on custom maps)
|
||||||
return 2.5f;
|
return 2.5f;
|
||||||
@ -693,6 +705,22 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class StayAtTownManaRecoveryEvaluator : public IEvaluationContextBuilder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||||
|
{
|
||||||
|
if(task->goalType != Goals::STAY_AT_TOWN)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Goals::StayAtTown & stayAtTown = dynamic_cast<Goals::StayAtTown &>(*task);
|
||||||
|
|
||||||
|
evaluationContext.armyReward += evaluationContext.evaluator.getManaRecoveryArmyReward(stayAtTown.getHero().get());
|
||||||
|
evaluationContext.movementCostByRole[evaluationContext.heroRole] += stayAtTown.getMovementWasted();
|
||||||
|
evaluationContext.movementCost += stayAtTown.getMovementWasted();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uint8_t turn, uint64_t ourStrength)
|
void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uint8_t turn, uint64_t ourStrength)
|
||||||
{
|
{
|
||||||
HitMapInfo enemyDanger = evaluationContext.evaluator.getEnemyHeroDanger(tile, turn);
|
HitMapInfo enemyDanger = evaluationContext.evaluator.getEnemyHeroDanger(tile, turn);
|
||||||
@ -998,6 +1026,7 @@ PriorityEvaluator::PriorityEvaluator(const Nullkiller * ai)
|
|||||||
evaluationContextBuilders.push_back(std::make_shared<DefendTownEvaluator>());
|
evaluationContextBuilders.push_back(std::make_shared<DefendTownEvaluator>());
|
||||||
evaluationContextBuilders.push_back(std::make_shared<ExchangeSwapTownHeroesContextBuilder>());
|
evaluationContextBuilders.push_back(std::make_shared<ExchangeSwapTownHeroesContextBuilder>());
|
||||||
evaluationContextBuilders.push_back(std::make_shared<DismissHeroContextBuilder>(ai));
|
evaluationContextBuilders.push_back(std::make_shared<DismissHeroContextBuilder>(ai));
|
||||||
|
evaluationContextBuilders.push_back(std::make_shared<StayAtTownManaRecoveryEvaluator>());
|
||||||
}
|
}
|
||||||
|
|
||||||
EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal) const
|
EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal) const
|
||||||
|
@ -8,14 +8,16 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "fl/Headers.h"
|
#if __has_include(<fuzzylite/Headers.h>)
|
||||||
|
# include <fuzzylite/Headers.h>
|
||||||
|
#else
|
||||||
|
# include <fl/Headers.h>
|
||||||
|
#endif
|
||||||
#include "../Goals/CGoal.h"
|
#include "../Goals/CGoal.h"
|
||||||
#include "../Pathfinding/AIPathfinder.h"
|
#include "../Pathfinding/AIPathfinder.h"
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
class CGWitchHut;
|
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_END
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
|
||||||
namespace NKAI
|
namespace NKAI
|
||||||
@ -39,12 +41,13 @@ public:
|
|||||||
float getResourceRequirementStrength(int resType) const;
|
float getResourceRequirementStrength(int resType) const;
|
||||||
float getStrategicalValue(const CGObjectInstance * target) const;
|
float getStrategicalValue(const CGObjectInstance * target) const;
|
||||||
float getTotalResourceRequirementStrength(int resType) const;
|
float getTotalResourceRequirementStrength(int resType) const;
|
||||||
float evaluateWitchHutSkillScore(const CGWitchHut * hut, const CGHeroInstance * hero, HeroRole role) const;
|
float evaluateWitchHutSkillScore(const CGObjectInstance * hut, const CGHeroInstance * hero, HeroRole role) const;
|
||||||
float getSkillReward(const CGObjectInstance * target, const CGHeroInstance * hero, HeroRole role) const;
|
float getSkillReward(const CGObjectInstance * target, const CGHeroInstance * hero, HeroRole role) const;
|
||||||
int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const;
|
int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const;
|
||||||
uint64_t getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const;
|
uint64_t getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const;
|
||||||
const HitMapInfo & getEnemyHeroDanger(const int3 & tile, uint8_t turn) const;
|
const HitMapInfo & getEnemyHeroDanger(const int3 & tile, uint8_t turn) const;
|
||||||
uint64_t townArmyGrowth(const CGTownInstance * town) const;
|
uint64_t townArmyGrowth(const CGTownInstance * town) const;
|
||||||
|
uint64_t getManaRecoveryArmyReward(const CGHeroInstance * hero) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DLL_EXPORT EvaluationContext
|
struct DLL_EXPORT EvaluationContext
|
||||||
|
@ -10,14 +10,11 @@
|
|||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
#include "AbstractGoal.h"
|
#include "AbstractGoal.h"
|
||||||
#include "../AIGateway.h"
|
#include "../AIGateway.h"
|
||||||
#include "../../../lib/StringConstants.h"
|
#include "../../../lib/constants/StringConstants.h"
|
||||||
|
|
||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
TSubgoal Goals::sptr(const AbstractGoal & tmp)
|
TSubgoal Goals::sptr(const AbstractGoal & tmp)
|
||||||
|
@ -71,7 +71,9 @@ namespace Goals
|
|||||||
ARMY_UPGRADE,
|
ARMY_UPGRADE,
|
||||||
DEFEND_TOWN,
|
DEFEND_TOWN,
|
||||||
CAPTURE_OBJECT,
|
CAPTURE_OBJECT,
|
||||||
SAVE_RESOURCES
|
SAVE_RESOURCES,
|
||||||
|
STAY_AT_TOWN_BEHAVIOR,
|
||||||
|
STAY_AT_TOWN
|
||||||
};
|
};
|
||||||
|
|
||||||
class DLL_EXPORT TSubgoal : public std::shared_ptr<AbstractGoal>
|
class DLL_EXPORT TSubgoal : public std::shared_ptr<AbstractGoal>
|
||||||
|
@ -14,9 +14,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
bool AdventureSpellCast::operator==(const AdventureSpellCast & other) const
|
bool AdventureSpellCast::operator==(const AdventureSpellCast & other) const
|
||||||
|
@ -15,9 +15,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
bool BuildBoat::operator==(const BuildBoat & other) const
|
bool BuildBoat::operator==(const BuildBoat & other) const
|
||||||
|
@ -11,18 +11,14 @@
|
|||||||
#include "BuildThis.h"
|
#include "BuildThis.h"
|
||||||
#include "../AIGateway.h"
|
#include "../AIGateway.h"
|
||||||
#include "../AIUtility.h"
|
#include "../AIUtility.h"
|
||||||
#include "../../../lib/StringConstants.h"
|
#include "../../../lib/constants/StringConstants.h"
|
||||||
|
|
||||||
|
|
||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
|
|
||||||
BuildThis::BuildThis(BuildingID Bid, const CGTownInstance * tid)
|
BuildThis::BuildThis(BuildingID Bid, const CGTownInstance * tid)
|
||||||
: ElementarGoal(Goals::BUILD_STRUCTURE)
|
: ElementarGoal(Goals::BUILD_STRUCTURE)
|
||||||
{
|
{
|
||||||
|
@ -17,9 +17,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
bool BuyArmy::operator==(const BuyArmy & other) const
|
bool BuyArmy::operator==(const BuyArmy & other) const
|
||||||
@ -54,7 +51,7 @@ void BuyArmy::accept(AIGateway * ai)
|
|||||||
auto res = cb->getResourceAmount();
|
auto res = cb->getResourceAmount();
|
||||||
auto & ci = armyToBuy[i];
|
auto & ci = armyToBuy[i];
|
||||||
|
|
||||||
if(objid != -1 && ci.creID != objid)
|
if(objid != CreatureID::NONE && ci.creID.getNum() != objid)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
vstd::amin(ci.count, res / ci.cre->getFullRecruitCost());
|
vstd::amin(ci.count, res / ci.cre->getFullRecruitCost());
|
||||||
|
@ -18,8 +18,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
bool CaptureObject::operator==(const CaptureObject & other) const
|
bool CaptureObject::operator==(const CaptureObject & other) const
|
||||||
|
@ -17,9 +17,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
bool isKeyMaster(const QuestInfo & q)
|
bool isKeyMaster(const QuestInfo & q)
|
||||||
@ -41,41 +38,28 @@ TGoalVec CompleteQuest::decompose() const
|
|||||||
|
|
||||||
logAi->debug("Trying to realize quest: %s", questToString());
|
logAi->debug("Trying to realize quest: %s", questToString());
|
||||||
|
|
||||||
switch(q.quest->missionType)
|
if(!q.quest->mission.artifacts.empty())
|
||||||
{
|
|
||||||
case CQuest::MISSION_ART:
|
|
||||||
return missionArt();
|
return missionArt();
|
||||||
|
|
||||||
case CQuest::MISSION_HERO:
|
if(!q.quest->mission.heroes.empty())
|
||||||
return missionHero();
|
return missionHero();
|
||||||
|
|
||||||
case CQuest::MISSION_ARMY:
|
if(!q.quest->mission.creatures.empty())
|
||||||
return missionArmy();
|
return missionArmy();
|
||||||
|
|
||||||
case CQuest::MISSION_RESOURCES:
|
if(q.quest->mission.resources.nonZero())
|
||||||
return missionResources();
|
return missionResources();
|
||||||
|
|
||||||
case CQuest::MISSION_KILL_HERO:
|
if(q.quest->killTarget != ObjectInstanceID::NONE)
|
||||||
case CQuest::MISSION_KILL_CREATURE:
|
|
||||||
return missionDestroyObj();
|
return missionDestroyObj();
|
||||||
|
|
||||||
case CQuest::MISSION_PRIMARY_STAT:
|
for(auto & s : q.quest->mission.primary)
|
||||||
|
if(s)
|
||||||
return missionIncreasePrimaryStat();
|
return missionIncreasePrimaryStat();
|
||||||
|
|
||||||
case CQuest::MISSION_LEVEL:
|
if(q.quest->mission.heroLevel > 0)
|
||||||
return missionLevel();
|
return missionLevel();
|
||||||
|
|
||||||
case CQuest::MISSION_PLAYER:
|
|
||||||
if(ai->playerID.getNum() != q.quest->m13489val)
|
|
||||||
logAi->debug("Can't be player of color %d", q.quest->m13489val);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CQuest::MISSION_KEYMASTER:
|
|
||||||
return missionKeymaster();
|
|
||||||
|
|
||||||
} //end of switch
|
|
||||||
|
|
||||||
return TGoalVec();
|
return TGoalVec();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +94,7 @@ std::string CompleteQuest::questToString() const
|
|||||||
return "find " + VLC->generaltexth->tentColors[q.obj->subID] + " keymaster tent";
|
return "find " + VLC->generaltexth->tentColors[q.obj->subID] + " keymaster tent";
|
||||||
}
|
}
|
||||||
|
|
||||||
if(q.quest->missionType == CQuest::MISSION_NONE)
|
if(q.quest->questName == CQuest::missionName(0))
|
||||||
return "inactive quest";
|
return "inactive quest";
|
||||||
|
|
||||||
MetaString ms;
|
MetaString ms;
|
||||||
@ -140,7 +124,7 @@ TGoalVec CompleteQuest::missionArt() const
|
|||||||
|
|
||||||
CaptureObjectsBehavior findArts;
|
CaptureObjectsBehavior findArts;
|
||||||
|
|
||||||
for(auto art : q.quest->m5arts)
|
for(auto art : q.quest->mission.artifacts)
|
||||||
{
|
{
|
||||||
solutions.push_back(sptr(CaptureObjectsBehavior().ofType(Obj::ARTIFACT, art)));
|
solutions.push_back(sptr(CaptureObjectsBehavior().ofType(Obj::ARTIFACT, art)));
|
||||||
}
|
}
|
||||||
@ -226,7 +210,7 @@ TGoalVec CompleteQuest::missionResources() const
|
|||||||
|
|
||||||
TGoalVec CompleteQuest::missionDestroyObj() const
|
TGoalVec CompleteQuest::missionDestroyObj() const
|
||||||
{
|
{
|
||||||
auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val);
|
auto obj = cb->getObjByQuestIdentifier(q.quest->killTarget);
|
||||||
|
|
||||||
if(!obj)
|
if(!obj)
|
||||||
return CaptureObjectsBehavior(q.obj).decompose();
|
return CaptureObjectsBehavior(q.obj).decompose();
|
||||||
|
@ -11,15 +11,12 @@
|
|||||||
#include "Composition.h"
|
#include "Composition.h"
|
||||||
#include "../AIGateway.h"
|
#include "../AIGateway.h"
|
||||||
#include "../AIUtility.h"
|
#include "../AIUtility.h"
|
||||||
#include "../../../lib/StringConstants.h"
|
#include "../../../lib/constants/StringConstants.h"
|
||||||
|
|
||||||
|
|
||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
bool Composition::operator==(const Composition & other) const
|
bool Composition::operator==(const Composition & other) const
|
||||||
|
@ -16,9 +16,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
bool DigAtTile::operator==(const DigAtTile & other) const
|
bool DigAtTile::operator==(const DigAtTile & other) const
|
||||||
|
@ -14,9 +14,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
bool DismissHero::operator==(const DismissHero & other) const
|
bool DismissHero::operator==(const DismissHero & other) const
|
||||||
|
@ -16,9 +16,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
ExchangeSwapTownHeroes::ExchangeSwapTownHeroes(
|
ExchangeSwapTownHeroes::ExchangeSwapTownHeroes(
|
||||||
|
@ -15,9 +15,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
ExecuteHeroChain::ExecuteHeroChain(const AIPath & path, const CGObjectInstance * obj)
|
ExecuteHeroChain::ExecuteHeroChain(const AIPath & path, const CGObjectInstance * obj)
|
||||||
|
@ -11,15 +11,12 @@
|
|||||||
#include "Goals.h"
|
#include "Goals.h"
|
||||||
#include "../AIGateway.h"
|
#include "../AIGateway.h"
|
||||||
#include "../AIUtility.h"
|
#include "../AIUtility.h"
|
||||||
#include "../../../lib/StringConstants.h"
|
#include "../../../lib/constants/StringConstants.h"
|
||||||
|
|
||||||
|
|
||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
std::string RecruitHero::toString() const
|
std::string RecruitHero::toString() const
|
||||||
|
@ -15,9 +15,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
bool SaveResources::operator==(const SaveResources & other) const
|
bool SaveResources::operator==(const SaveResources & other) const
|
||||||
|
52
AI/Nullkiller/Goals/StayAtTown.cpp
Normal file
52
AI/Nullkiller/Goals/StayAtTown.cpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* ArmyUpgrade.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 "StayAtTown.h"
|
||||||
|
#include "../AIGateway.h"
|
||||||
|
#include "../Engine/Nullkiller.h"
|
||||||
|
#include "../AIUtility.h"
|
||||||
|
|
||||||
|
namespace NKAI
|
||||||
|
{
|
||||||
|
|
||||||
|
using namespace Goals;
|
||||||
|
|
||||||
|
StayAtTown::StayAtTown(const CGTownInstance * town, AIPath & path)
|
||||||
|
: ElementarGoal(Goals::STAY_AT_TOWN)
|
||||||
|
{
|
||||||
|
sethero(path.targetHero);
|
||||||
|
settown(town);
|
||||||
|
movementWasted = static_cast<float>(hero->movementPointsRemaining()) / hero->movementPointsLimit(!hero->boat) - path.movementCost();
|
||||||
|
vstd::amax(movementWasted, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StayAtTown::operator==(const StayAtTown & other) const
|
||||||
|
{
|
||||||
|
return hero == other.hero && town == other.town;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string StayAtTown::toString() const
|
||||||
|
{
|
||||||
|
return "Stay at town " + town->getNameTranslated()
|
||||||
|
+ " hero " + hero->getNameTranslated()
|
||||||
|
+ ", mana: " + std::to_string(hero->mana);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StayAtTown::accept(AIGateway * ai)
|
||||||
|
{
|
||||||
|
if(hero->visitedTown != town)
|
||||||
|
{
|
||||||
|
logAi->error("Hero %s expected visiting town %s", hero->getNameTranslated(), town->getNameTranslated());
|
||||||
|
}
|
||||||
|
|
||||||
|
ai->nullkiller->lockHero(hero.get(), HeroLockedReason::DEFENCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
36
AI/Nullkiller/Goals/StayAtTown.h
Normal file
36
AI/Nullkiller/Goals/StayAtTown.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* ArmyUpgrade.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 "../Goals/CGoal.h"
|
||||||
|
#include "../Pathfinding/AINodeStorage.h"
|
||||||
|
#include "../Analyzers/ArmyManager.h"
|
||||||
|
#include "../Analyzers/DangerHitMapAnalyzer.h"
|
||||||
|
|
||||||
|
namespace NKAI
|
||||||
|
{
|
||||||
|
namespace Goals
|
||||||
|
{
|
||||||
|
class DLL_EXPORT StayAtTown : public ElementarGoal<StayAtTown>
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
float movementWasted;
|
||||||
|
|
||||||
|
public:
|
||||||
|
StayAtTown(const CGTownInstance * town, AIPath & path);
|
||||||
|
|
||||||
|
virtual bool operator==(const StayAtTown & other) const override;
|
||||||
|
virtual std::string toString() const override;
|
||||||
|
void accept(AIGateway * ai) override;
|
||||||
|
float getMovementWasted() const { return movementWasted; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -16,9 +16,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
ArmyUpgrade::ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade)
|
ArmyUpgrade::ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade)
|
||||||
|
@ -17,9 +17,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
bool HeroExchange::operator==(const HeroExchange & other) const
|
bool HeroExchange::operator==(const HeroExchange & other) const
|
||||||
|
@ -16,9 +16,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
bool UnlockCluster::operator==(const UnlockCluster & other) const
|
bool UnlockCluster::operator==(const UnlockCluster & other) const
|
||||||
|
@ -279,9 +279,10 @@ void AINodeStorage::commit(
|
|||||||
|
|
||||||
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
||||||
logAi->trace(
|
logAi->trace(
|
||||||
"Commited %s -> %s, cost: %f, turn: %s, mp: %d, hero: %s, mask: %x, army: %lld",
|
"Commited %s -> %s, layer: %d, cost: %f, turn: %s, mp: %d, hero: %s, mask: %x, army: %lld",
|
||||||
source->coord.toString(),
|
source->coord.toString(),
|
||||||
destination->coord.toString(),
|
destination->coord.toString(),
|
||||||
|
destination->layer,
|
||||||
destination->getCost(),
|
destination->getCost(),
|
||||||
std::to_string(destination->turns),
|
std::to_string(destination->turns),
|
||||||
destination->moveRemains,
|
destination->moveRemains,
|
||||||
@ -983,7 +984,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
|
|||||||
struct TowmPortalFinder
|
struct TowmPortalFinder
|
||||||
{
|
{
|
||||||
const std::vector<CGPathNode *> & initialNodes;
|
const std::vector<CGPathNode *> & initialNodes;
|
||||||
SecSkillLevel::SecSkillLevel townPortalSkillLevel;
|
MasteryLevel::Type townPortalSkillLevel;
|
||||||
uint64_t movementNeeded;
|
uint64_t movementNeeded;
|
||||||
const ChainActor * actor;
|
const ChainActor * actor;
|
||||||
const CGHeroInstance * hero;
|
const CGHeroInstance * hero;
|
||||||
@ -1005,8 +1006,8 @@ struct TowmPortalFinder
|
|||||||
townPortal = spellID.toSpell();
|
townPortal = spellID.toSpell();
|
||||||
|
|
||||||
// TODO: Copy/Paste from TownPortalMechanics
|
// TODO: Copy/Paste from TownPortalMechanics
|
||||||
townPortalSkillLevel = SecSkillLevel::SecSkillLevel(hero->getSpellSchoolLevel(townPortal));
|
townPortalSkillLevel = MasteryLevel::Type(hero->getSpellSchoolLevel(townPortal));
|
||||||
movementNeeded = GameConstants::BASE_MOVEMENT_COST * (townPortalSkillLevel >= SecSkillLevel::EXPERT ? 2 : 3);
|
movementNeeded = GameConstants::BASE_MOVEMENT_COST * (townPortalSkillLevel >= MasteryLevel::EXPERT ? 2 : 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool actorCanCastTownPortal()
|
bool actorCanCastTownPortal()
|
||||||
@ -1027,7 +1028,7 @@ struct TowmPortalFinder
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(townPortalSkillLevel < SecSkillLevel::ADVANCED)
|
if(townPortalSkillLevel < MasteryLevel::ADVANCED)
|
||||||
{
|
{
|
||||||
const CGTownInstance * nearestTown = *vstd::minElementByFun(targetTowns, [&](const CGTownInstance * t) -> int
|
const CGTownInstance * nearestTown = *vstd::minElementByFun(targetTowns, [&](const CGTownInstance * t) -> int
|
||||||
{
|
{
|
||||||
@ -1208,7 +1209,7 @@ bool AINodeStorage::hasBetterChain(
|
|||||||
"Block ineficient battle move %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
"Block ineficient battle move %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||||
source->coord.toString(),
|
source->coord.toString(),
|
||||||
candidateNode->coord.toString(),
|
candidateNode->coord.toString(),
|
||||||
candidateNode->actor->hero->name,
|
candidateNode->actor->hero->getNameTranslated(),
|
||||||
candidateNode->actor->chainMask,
|
candidateNode->actor->chainMask,
|
||||||
candidateNode->actor->armyValue,
|
candidateNode->actor->armyValue,
|
||||||
node.moveRemains - candidateNode->moveRemains);
|
node.moveRemains - candidateNode->moveRemains);
|
||||||
@ -1232,7 +1233,7 @@ bool AINodeStorage::hasBetterChain(
|
|||||||
"Block ineficient move because of stronger army %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
"Block ineficient move because of stronger army %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||||
source->coord.toString(),
|
source->coord.toString(),
|
||||||
candidateNode->coord.toString(),
|
candidateNode->coord.toString(),
|
||||||
candidateNode->actor->hero->name,
|
candidateNode->actor->hero->getNameTranslated(),
|
||||||
candidateNode->actor->chainMask,
|
candidateNode->actor->chainMask,
|
||||||
candidateNode->actor->armyValue,
|
candidateNode->actor->armyValue,
|
||||||
node.moveRemains - candidateNode->moveRemains);
|
node.moveRemains - candidateNode->moveRemains);
|
||||||
@ -1258,7 +1259,7 @@ bool AINodeStorage::hasBetterChain(
|
|||||||
"Block ineficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
"Block ineficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||||
source->coord.toString(),
|
source->coord.toString(),
|
||||||
candidateNode->coord.toString(),
|
candidateNode->coord.toString(),
|
||||||
candidateNode->actor->hero->name,
|
candidateNode->actor->hero->getNameTranslated(),
|
||||||
candidateNode->actor->chainMask,
|
candidateNode->actor->chainMask,
|
||||||
candidateNode->actor->armyValue,
|
candidateNode->actor->armyValue,
|
||||||
node.moveRemains - candidateNode->moveRemains);
|
node.moveRemains - candidateNode->moveRemains);
|
||||||
@ -1343,6 +1344,7 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int pa
|
|||||||
pathNode.coord = node->coord;
|
pathNode.coord = node->coord;
|
||||||
pathNode.parentIndex = parentIndex;
|
pathNode.parentIndex = parentIndex;
|
||||||
pathNode.actionIsBlocked = false;
|
pathNode.actionIsBlocked = false;
|
||||||
|
pathNode.layer = node->layer;
|
||||||
|
|
||||||
if(pathNode.specialAction)
|
if(pathNode.specialAction)
|
||||||
{
|
{
|
||||||
|
@ -45,7 +45,7 @@ struct AIPathNode : public CGPathNode
|
|||||||
{
|
{
|
||||||
uint64_t danger;
|
uint64_t danger;
|
||||||
uint64_t armyLoss;
|
uint64_t armyLoss;
|
||||||
uint32_t manaCost;
|
int32_t manaCost;
|
||||||
const AIPathNode * chainOther;
|
const AIPathNode * chainOther;
|
||||||
std::shared_ptr<const SpecialAction> specialAction;
|
std::shared_ptr<const SpecialAction> specialAction;
|
||||||
const ChainActor * actor;
|
const ChainActor * actor;
|
||||||
@ -65,6 +65,7 @@ struct AIPathNodeInfo
|
|||||||
float cost;
|
float cost;
|
||||||
uint8_t turns;
|
uint8_t turns;
|
||||||
int3 coord;
|
int3 coord;
|
||||||
|
EPathfindingLayer layer;
|
||||||
uint64_t danger;
|
uint64_t danger;
|
||||||
const CGHeroInstance * targetHero;
|
const CGHeroInstance * targetHero;
|
||||||
int parentIndex;
|
int parentIndex;
|
||||||
|
@ -44,6 +44,7 @@ namespace AIPathfinding
|
|||||||
std::shared_ptr<AINodeStorage> nodeStorage)
|
std::shared_ptr<AINodeStorage> nodeStorage)
|
||||||
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), aiNodeStorage(nodeStorage)
|
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), aiNodeStorage(nodeStorage)
|
||||||
{
|
{
|
||||||
|
options.canUseCast = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
AIPathfinderConfig::~AIPathfinderConfig() = default;
|
AIPathfinderConfig::~AIPathfinderConfig() = default;
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* AdventureSpellCastMovementActions.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 "../../AIGateway.h"
|
||||||
|
#include "../../Goals/AdventureSpellCast.h"
|
||||||
|
#include "../../Goals/CaptureObject.h"
|
||||||
|
#include "../../Goals/Invalid.h"
|
||||||
|
#include "../../Goals/BuildBoat.h"
|
||||||
|
#include "../../../../lib/mapObjects/MapObjects.h"
|
||||||
|
#include "AdventureSpellCastMovementActions.h"
|
||||||
|
|
||||||
|
namespace NKAI
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace AIPathfinding
|
||||||
|
{
|
||||||
|
AdventureCastAction::AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero)
|
||||||
|
:spellToCast(spellToCast), hero(hero)
|
||||||
|
{
|
||||||
|
manaCost = hero->getSpellCost(spellToCast.toSpell());
|
||||||
|
}
|
||||||
|
|
||||||
|
WaterWalkingAction::WaterWalkingAction(const CGHeroInstance * hero)
|
||||||
|
:AdventureCastAction(SpellID::WATER_WALK, hero)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
AirWalkingAction::AirWalkingAction(const CGHeroInstance * hero)
|
||||||
|
: AdventureCastAction(SpellID::FLY, hero)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AdventureCastAction::applyOnDestination(
|
||||||
|
const CGHeroInstance * hero,
|
||||||
|
CDestinationNodeInfo & destination,
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
AIPathNode * dstMode,
|
||||||
|
const AIPathNode * srcNode) const
|
||||||
|
{
|
||||||
|
dstMode->manaCost = srcNode->manaCost + manaCost;
|
||||||
|
dstMode->theNodeBefore = source.node;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AdventureCastAction::execute(const CGHeroInstance * hero) const
|
||||||
|
{
|
||||||
|
assert(hero == this->hero);
|
||||||
|
|
||||||
|
Goals::AdventureSpellCast(hero, spellToCast).accept(ai);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AdventureCastAction::canAct(const AIPathNode * source) const
|
||||||
|
{
|
||||||
|
assert(hero == this->hero);
|
||||||
|
|
||||||
|
auto hero = source->actor->hero;
|
||||||
|
|
||||||
|
#ifdef VCMI_TRACE_PATHFINDER
|
||||||
|
logAi->trace(
|
||||||
|
"Hero %s has %d mana and needed %d and already spent %d",
|
||||||
|
hero->name,
|
||||||
|
hero->mana,
|
||||||
|
getManaCost(hero),
|
||||||
|
source->manaCost);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return hero->mana >= source->manaCost + manaCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AdventureCastAction::toString() const
|
||||||
|
{
|
||||||
|
return "Cast " + spellToCast.toSpell()->getNameTranslated() + " by " + hero->getNameTranslated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* AdventureSpellCastMovementActions.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 "SpecialAction.h"
|
||||||
|
#include "../../../../lib/mapObjects/MapObjects.h"
|
||||||
|
|
||||||
|
namespace NKAI
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace AIPathfinding
|
||||||
|
{
|
||||||
|
class AdventureCastAction : public SpecialAction
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
SpellID spellToCast;
|
||||||
|
const CGHeroInstance * hero;
|
||||||
|
int manaCost;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero);
|
||||||
|
|
||||||
|
virtual void execute(const CGHeroInstance * hero) const override;
|
||||||
|
|
||||||
|
virtual void applyOnDestination(
|
||||||
|
const CGHeroInstance * hero,
|
||||||
|
CDestinationNodeInfo & destination,
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
AIPathNode * dstMode,
|
||||||
|
const AIPathNode * srcNode) const override;
|
||||||
|
|
||||||
|
virtual bool canAct(const AIPathNode * source) const override;
|
||||||
|
|
||||||
|
virtual std::string toString() const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class WaterWalkingAction : public AdventureCastAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WaterWalkingAction(const CGHeroInstance * hero);
|
||||||
|
};
|
||||||
|
|
||||||
|
class AirWalkingAction : public AdventureCastAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AirWalkingAction(const CGHeroInstance * hero);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -16,9 +16,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
namespace AIPathfinding
|
namespace AIPathfinding
|
||||||
{
|
{
|
||||||
void BattleAction::execute(const CGHeroInstance * hero) const
|
void BattleAction::execute(const CGHeroInstance * hero) const
|
||||||
|
@ -20,14 +20,11 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
namespace AIPathfinding
|
namespace AIPathfinding
|
||||||
{
|
{
|
||||||
void BuildBoatAction::execute(const CGHeroInstance * hero) const
|
void BuildBoatAction::execute(const CGHeroInstance * hero) const
|
||||||
{
|
{
|
||||||
return Goals::BuildBoat(shipyard).accept(ai.get());
|
return Goals::BuildBoat(shipyard).accept(ai);
|
||||||
}
|
}
|
||||||
|
|
||||||
Goals::TSubgoal BuildBoatAction::decompose(const CGHeroInstance * hero) const
|
Goals::TSubgoal BuildBoatAction::decompose(const CGHeroInstance * hero) const
|
||||||
@ -80,7 +77,7 @@ namespace AIPathfinding
|
|||||||
|
|
||||||
void SummonBoatAction::execute(const CGHeroInstance * hero) const
|
void SummonBoatAction::execute(const CGHeroInstance * hero) const
|
||||||
{
|
{
|
||||||
Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT).accept(ai.get());
|
Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT).accept(ai);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChainActor * SummonBoatAction::getActor(const ChainActor * sourceActor) const
|
const ChainActor * SummonBoatAction::getActor(const ChainActor * sourceActor) const
|
||||||
@ -117,7 +114,7 @@ namespace AIPathfinding
|
|||||||
source->manaCost);
|
source->manaCost);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return hero->mana >= (si32)(source->manaCost + getManaCost(hero));
|
return hero->mana >= source->manaCost + getManaCost(hero);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string SummonBoatAction::toString() const
|
std::string SummonBoatAction::toString() const
|
||||||
@ -125,7 +122,7 @@ namespace AIPathfinding
|
|||||||
return "Summon Boat";
|
return "Summon Boat";
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t SummonBoatAction::getManaCost(const CGHeroInstance * hero) const
|
int32_t SummonBoatAction::getManaCost(const CGHeroInstance * hero) const
|
||||||
{
|
{
|
||||||
SpellID summonBoat = SpellID::SUMMON_BOAT;
|
SpellID summonBoat = SpellID::SUMMON_BOAT;
|
||||||
|
|
||||||
|
@ -20,8 +20,6 @@ namespace AIPathfinding
|
|||||||
{
|
{
|
||||||
class VirtualBoatAction : public SpecialAction
|
class VirtualBoatAction : public SpecialAction
|
||||||
{
|
{
|
||||||
public:
|
|
||||||
virtual const ChainActor * getActor(const ChainActor * sourceActor) const = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class SummonBoatAction : public VirtualBoatAction
|
class SummonBoatAction : public VirtualBoatAction
|
||||||
@ -43,7 +41,7 @@ namespace AIPathfinding
|
|||||||
virtual std::string toString() const override;
|
virtual std::string toString() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t getManaCost(const CGHeroInstance * hero) const;
|
int32_t getManaCost(const CGHeroInstance * hero) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BuildBoatAction : public VirtualBoatAction
|
class BuildBoatAction : public VirtualBoatAction
|
||||||
|
@ -16,9 +16,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
namespace AIPathfinding
|
namespace AIPathfinding
|
||||||
{
|
{
|
||||||
void BuyArmyAction::execute(const CGHeroInstance * hero) const
|
void BuyArmyAction::execute(const CGHeroInstance * hero) const
|
||||||
|
@ -16,9 +16,6 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
namespace AIPathfinding
|
namespace AIPathfinding
|
||||||
{
|
{
|
||||||
bool QuestAction::canAct(const AIPathNode * node) const
|
bool QuestAction::canAct(const AIPathNode * node) const
|
||||||
@ -28,7 +25,7 @@ namespace AIPathfinding
|
|||||||
return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(node->actor->hero);
|
return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(node->actor->hero);
|
||||||
}
|
}
|
||||||
|
|
||||||
return questInfo.quest->progress == CQuest::NOT_ACTIVE
|
return questInfo.quest->activeForPlayers.count(node->actor->hero->getOwner())
|
||||||
|| questInfo.quest->checkQuest(node->actor->hero);
|
|| questInfo.quest->checkQuest(node->actor->hero);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ namespace NKAI
|
|||||||
{
|
{
|
||||||
|
|
||||||
struct AIPathNode;
|
struct AIPathNode;
|
||||||
|
class ChainActor;
|
||||||
|
|
||||||
class SpecialAction
|
class SpecialAction
|
||||||
{
|
{
|
||||||
@ -54,6 +55,11 @@ public:
|
|||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual const ChainActor * getActor(const ChainActor * sourceActor) const
|
||||||
|
{
|
||||||
|
return sourceActor;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CompositeAction : public SpecialAction
|
class CompositeAction : public SpecialAction
|
||||||
|
@ -18,9 +18,6 @@ namespace NKAI
|
|||||||
|
|
||||||
using namespace AIPathfinding;
|
using namespace AIPathfinding;
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
|
||||||
|
|
||||||
void TownPortalAction::execute(const CGHeroInstance * hero) const
|
void TownPortalAction::execute(const CGHeroInstance * hero) const
|
||||||
{
|
{
|
||||||
auto goal = Goals::AdventureSpellCast(hero, SpellID::TOWN_PORTAL);
|
auto goal = Goals::AdventureSpellCast(hero, SpellID::TOWN_PORTAL);
|
||||||
@ -28,7 +25,7 @@ void TownPortalAction::execute(const CGHeroInstance * hero) const
|
|||||||
goal.town = target;
|
goal.town = target;
|
||||||
goal.tile = target->visitablePos();
|
goal.tile = target->visitablePos();
|
||||||
|
|
||||||
goal.accept(ai.get());
|
goal.accept(ai);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string TownPortalAction::toString() const
|
std::string TownPortalAction::toString() const
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
#include "AILayerTransitionRule.h"
|
#include "AILayerTransitionRule.h"
|
||||||
#include "../../Engine/Nullkiller.h"
|
#include "../../Engine/Nullkiller.h"
|
||||||
|
#include "../../../../lib/pathfinder/CPathfinder.h"
|
||||||
|
#include "../../../../lib/pathfinder/TurnInfo.h"
|
||||||
|
|
||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
@ -31,23 +33,79 @@ namespace AIPathfinding
|
|||||||
|
|
||||||
if(!destination.blocked)
|
if(!destination.blocked)
|
||||||
{
|
{
|
||||||
|
if(source.node->layer == EPathfindingLayer::LAND
|
||||||
|
&& (destination.node->layer == EPathfindingLayer::AIR || destination.node->layer == EPathfindingLayer::WATER))
|
||||||
|
{
|
||||||
|
if(pathfinderHelper->getTurnInfo()->isLayerAvailable(destination.node->layer))
|
||||||
return;
|
return;
|
||||||
|
else
|
||||||
|
destination.blocked = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::SAIL)
|
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::SAIL)
|
||||||
{
|
{
|
||||||
std::shared_ptr<const VirtualBoatAction> virtualBoat = findVirtualBoat(destination, source);
|
std::shared_ptr<const VirtualBoatAction> virtualBoat = findVirtualBoat(destination, source);
|
||||||
|
|
||||||
if(virtualBoat && tryEmbarkVirtualBoat(destination, source, virtualBoat))
|
if(virtualBoat && tryUseSpecialAction(destination, source, virtualBoat, EPathNodeAction::EMBARK))
|
||||||
{
|
{
|
||||||
#if NKAI_PATHFINDER_TRACE_LEVEL >= 1
|
#if NKAI_PATHFINDER_TRACE_LEVEL >= 1
|
||||||
logAi->trace("Embarking to virtual boat while moving %s -> %s!", source.coord.toString(), destination.coord.toString());
|
logAi->trace("Embarking to virtual boat while moving %s -> %s!", source.coord.toString(), destination.coord.toString());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::WATER)
|
||||||
|
{
|
||||||
|
auto action = waterWalkingActions.find(nodeStorage->getHero(source.node));
|
||||||
|
|
||||||
|
if(action != waterWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL))
|
||||||
|
{
|
||||||
|
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
||||||
|
logAi->trace("Casting water walk while moving %s -> %s!", source.coord.toString(), destination.coord.toString());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::AIR)
|
||||||
|
{
|
||||||
|
auto action = airWalkingActions.find(nodeStorage->getHero(source.node));
|
||||||
|
|
||||||
|
if(action != airWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL))
|
||||||
|
{
|
||||||
|
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
||||||
|
logAi->trace("Casting fly while moving %s -> %s!", source.coord.toString(), destination.coord.toString());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AILayerTransitionRule::setup()
|
void AILayerTransitionRule::setup()
|
||||||
|
{
|
||||||
|
SpellID waterWalk = SpellID::WATER_WALK;
|
||||||
|
SpellID airWalk = SpellID::FLY;
|
||||||
|
|
||||||
|
for(const CGHeroInstance * hero : nodeStorage->getAllHeroes())
|
||||||
|
{
|
||||||
|
if(hero->canCastThisSpell(waterWalk.toSpell()))
|
||||||
|
{
|
||||||
|
waterWalkingActions[hero] = std::make_shared<WaterWalkingAction>(hero);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hero->canCastThisSpell(airWalk.toSpell()))
|
||||||
|
{
|
||||||
|
airWalkingActions[hero] = std::make_shared<AirWalkingAction>(hero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collectVirtualBoats();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AILayerTransitionRule::collectVirtualBoats()
|
||||||
{
|
{
|
||||||
std::vector<const IShipyard *> shipyards;
|
std::vector<const IShipyard *> shipyards;
|
||||||
|
|
||||||
@ -81,7 +139,7 @@ namespace AIPathfinding
|
|||||||
auto summonBoatSpell = SpellID(SpellID::SUMMON_BOAT).toSpell();
|
auto summonBoatSpell = SpellID(SpellID::SUMMON_BOAT).toSpell();
|
||||||
|
|
||||||
if(hero->canCastThisSpell(summonBoatSpell)
|
if(hero->canCastThisSpell(summonBoatSpell)
|
||||||
&& hero->getSpellSchoolLevel(summonBoatSpell) >= SecSkillLevel::ADVANCED)
|
&& hero->getSpellSchoolLevel(summonBoatSpell) >= MasteryLevel::ADVANCED)
|
||||||
{
|
{
|
||||||
// TODO: For lower school level we might need to check the existance of some boat
|
// TODO: For lower school level we might need to check the existance of some boat
|
||||||
summonableVirtualBoats[hero] = std::make_shared<SummonBoatAction>();
|
summonableVirtualBoats[hero] = std::make_shared<SummonBoatAction>();
|
||||||
@ -113,30 +171,36 @@ namespace AIPathfinding
|
|||||||
return virtualBoat;
|
return virtualBoat;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AILayerTransitionRule::tryEmbarkVirtualBoat(
|
bool AILayerTransitionRule::tryUseSpecialAction(
|
||||||
CDestinationNodeInfo & destination,
|
CDestinationNodeInfo & destination,
|
||||||
const PathNodeInfo & source,
|
const PathNodeInfo & source,
|
||||||
std::shared_ptr<const VirtualBoatAction> virtualBoat) const
|
std::shared_ptr<const SpecialAction> specialAction,
|
||||||
|
EPathNodeAction targetAction) const
|
||||||
{
|
{
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
|
||||||
|
if(!specialAction->canAct(nodeStorage->getAINode(source.node)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
|
nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
|
||||||
{
|
{
|
||||||
auto boatNodeOptional = nodeStorage->getOrCreateNode(
|
auto castNodeOptional = nodeStorage->getOrCreateNode(
|
||||||
node->coord,
|
node->coord,
|
||||||
node->layer,
|
node->layer,
|
||||||
virtualBoat->getActor(node->actor));
|
specialAction->getActor(node->actor));
|
||||||
|
|
||||||
if(boatNodeOptional)
|
if(castNodeOptional)
|
||||||
{
|
{
|
||||||
AIPathNode * boatNode = boatNodeOptional.value();
|
AIPathNode * castNode = castNodeOptional.value();
|
||||||
|
|
||||||
if(boatNode->action == EPathNodeAction::UNKNOWN)
|
if(castNode->action == EPathNodeAction::UNKNOWN)
|
||||||
{
|
{
|
||||||
boatNode->addSpecialAction(virtualBoat);
|
castNode->addSpecialAction(specialAction);
|
||||||
destination.blocked = false;
|
destination.blocked = false;
|
||||||
destination.action = EPathNodeAction::EMBARK;
|
destination.action = targetAction;
|
||||||
destination.node = boatNode;
|
destination.node = castNode;
|
||||||
result = true;
|
result = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include "../AINodeStorage.h"
|
#include "../AINodeStorage.h"
|
||||||
#include "../../AIGateway.h"
|
#include "../../AIGateway.h"
|
||||||
#include "../Actions/BoatActions.h"
|
#include "../Actions/BoatActions.h"
|
||||||
|
#include "../Actions/AdventureSpellCastMovementActions.h"
|
||||||
#include "../../../../CCallback.h"
|
#include "../../../../CCallback.h"
|
||||||
#include "../../../../lib/mapObjects/MapObjects.h"
|
#include "../../../../lib/mapObjects/MapObjects.h"
|
||||||
#include "../../../../lib/pathfinder/PathfindingRules.h"
|
#include "../../../../lib/pathfinder/PathfindingRules.h"
|
||||||
@ -29,6 +30,8 @@ namespace AIPathfinding
|
|||||||
std::map<int3, std::shared_ptr<const BuildBoatAction>> virtualBoats;
|
std::map<int3, std::shared_ptr<const BuildBoatAction>> virtualBoats;
|
||||||
std::shared_ptr<AINodeStorage> nodeStorage;
|
std::shared_ptr<AINodeStorage> nodeStorage;
|
||||||
std::map<const CGHeroInstance *, std::shared_ptr<const SummonBoatAction>> summonableVirtualBoats;
|
std::map<const CGHeroInstance *, std::shared_ptr<const SummonBoatAction>> summonableVirtualBoats;
|
||||||
|
std::map<const CGHeroInstance *, std::shared_ptr<const WaterWalkingAction>> waterWalkingActions;
|
||||||
|
std::map<const CGHeroInstance *, std::shared_ptr<const AirWalkingAction>> airWalkingActions;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, Nullkiller * ai, std::shared_ptr<AINodeStorage> nodeStorage);
|
AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, Nullkiller * ai, std::shared_ptr<AINodeStorage> nodeStorage);
|
||||||
@ -41,15 +44,17 @@ namespace AIPathfinding
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void setup();
|
void setup();
|
||||||
|
void collectVirtualBoats();
|
||||||
|
|
||||||
std::shared_ptr<const VirtualBoatAction> findVirtualBoat(
|
std::shared_ptr<const VirtualBoatAction> findVirtualBoat(
|
||||||
CDestinationNodeInfo & destination,
|
CDestinationNodeInfo & destination,
|
||||||
const PathNodeInfo & source) const;
|
const PathNodeInfo & source) const;
|
||||||
|
|
||||||
bool tryEmbarkVirtualBoat(
|
bool tryUseSpecialAction(
|
||||||
CDestinationNodeInfo & destination,
|
CDestinationNodeInfo & destination,
|
||||||
const PathNodeInfo & source,
|
const PathNodeInfo & source,
|
||||||
std::shared_ptr<const VirtualBoatAction> virtualBoat) const;
|
std::shared_ptr<const SpecialAction> specialAction,
|
||||||
|
EPathNodeAction targetAction) const;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,7 +130,9 @@ namespace AIPathfinding
|
|||||||
auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord);
|
auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord);
|
||||||
QuestAction questAction(questInfo);
|
QuestAction questAction(questInfo);
|
||||||
|
|
||||||
if(destination.nodeObject->ID == Obj::QUEST_GUARD && questObj->quest->missionType == CQuest::MISSION_NONE)
|
if(destination.nodeObject->ID == Obj::QUEST_GUARD
|
||||||
|
&& questObj->quest->mission == Rewardable::Limiter{}
|
||||||
|
&& questObj->quest->killTarget == ObjectInstanceID::NONE)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
#include "../../lib/CStack.h"
|
#include "../../lib/CStack.h"
|
||||||
#include "../../CCallback.h"
|
#include "../../CCallback.h"
|
||||||
#include "../../lib/CCreatureHandler.h"
|
#include "../../lib/CCreatureHandler.h"
|
||||||
|
#include "../../lib/battle/BattleAction.h"
|
||||||
|
#include "../../lib/battle/BattleInfo.h"
|
||||||
|
|
||||||
static std::shared_ptr<CBattleCallback> cbc;
|
static std::shared_ptr<CBattleCallback> cbc;
|
||||||
|
|
||||||
@ -47,12 +49,17 @@ void CStupidAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
|
|||||||
CB->unlockGsWhenWaiting = false;
|
CB->unlockGsWhenWaiting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStupidAI::actionFinished(const BattleAction &action)
|
void CStupidAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB, AutocombatPreferences autocombatPreferences)
|
||||||
|
{
|
||||||
|
initBattleInterface(ENV, CB);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CStupidAI::actionFinished(const BattleID & battleID, const BattleAction &action)
|
||||||
{
|
{
|
||||||
print("actionFinished called");
|
print("actionFinished called");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStupidAI::actionStarted(const BattleAction &action)
|
void CStupidAI::actionStarted(const BattleID & battleID, const BattleAction &action)
|
||||||
{
|
{
|
||||||
print("actionStarted called");
|
print("actionStarted called");
|
||||||
}
|
}
|
||||||
@ -65,11 +72,11 @@ public:
|
|||||||
std::vector<BattleHex> attackFrom; //for melee fight
|
std::vector<BattleHex> attackFrom; //for melee fight
|
||||||
EnemyInfo(const CStack * _s) : s(_s), adi(0), adr(0)
|
EnemyInfo(const CStack * _s) : s(_s), adi(0), adr(0)
|
||||||
{}
|
{}
|
||||||
void calcDmg(const CStack * ourStack)
|
void calcDmg(const BattleID & battleID, const CStack * ourStack)
|
||||||
{
|
{
|
||||||
// FIXME: provide distance info for Jousting bonus
|
// FIXME: provide distance info for Jousting bonus
|
||||||
DamageEstimation retal;
|
DamageEstimation retal;
|
||||||
DamageEstimation dmg = cbc->battleEstimateDamage(ourStack, s, 0, &retal);
|
DamageEstimation dmg = cbc->getBattle(battleID)->battleEstimateDamage(ourStack, s, 0, &retal);
|
||||||
adi = static_cast<int>((dmg.damage.min + dmg.damage.max) / 2);
|
adi = static_cast<int>((dmg.damage.min + dmg.damage.max) / 2);
|
||||||
adr = static_cast<int>((retal.damage.min + retal.damage.max) / 2);
|
adr = static_cast<int>((retal.damage.min + retal.damage.max) / 2);
|
||||||
}
|
}
|
||||||
@ -85,14 +92,14 @@ bool isMoreProfitable(const EnemyInfo &ei1, const EnemyInfo& ei2)
|
|||||||
return (ei1.adi-ei1.adr) < (ei2.adi - ei2.adr);
|
return (ei1.adi-ei1.adr) < (ei2.adi - ei2.adr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const BattleHex &h2)
|
static bool willSecondHexBlockMoreEnemyShooters(const BattleID & battleID, const BattleHex &h1, const BattleHex &h2)
|
||||||
{
|
{
|
||||||
int shooters[2] = {0}; //count of shooters on hexes
|
int shooters[2] = {0}; //count of shooters on hexes
|
||||||
|
|
||||||
for(int i = 0; i < 2; i++)
|
for(int i = 0; i < 2; i++)
|
||||||
{
|
{
|
||||||
for (auto & neighbour : (i ? h2 : h1).neighbouringTiles())
|
for (auto & neighbour : (i ? h2 : h1).neighbouringTiles())
|
||||||
if(const auto * s = cbc->battleGetUnitByPos(neighbour))
|
if(const auto * s = cbc->getBattle(battleID)->battleGetUnitByPos(neighbour))
|
||||||
if(s->isShooter())
|
if(s->isShooter())
|
||||||
shooters[i]++;
|
shooters[i]++;
|
||||||
}
|
}
|
||||||
@ -100,16 +107,16 @@ static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const Battl
|
|||||||
return shooters[0] < shooters[1];
|
return shooters[0] < shooters[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStupidAI::yourTacticPhase(int distance)
|
void CStupidAI::yourTacticPhase(const BattleID & battleID, int distance)
|
||||||
{
|
{
|
||||||
cb->battleMakeTacticAction(BattleAction::makeEndOFTacticPhase(cb->battleGetTacticsSide()));
|
cb->battleMakeTacticAction(battleID, BattleAction::makeEndOFTacticPhase(cb->getBattle(battleID)->battleGetTacticsSide()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStupidAI::activeStack( const CStack * stack )
|
void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
|
||||||
{
|
{
|
||||||
//boost::this_thread::sleep(boost::posix_time::seconds(2));
|
//boost::this_thread::sleep_for(boost::chrono::seconds(2));
|
||||||
print("activeStack called for " + stack->nodeName());
|
print("activeStack called for " + stack->nodeName());
|
||||||
ReachabilityInfo dists = cb->getReachability(stack);
|
ReachabilityInfo dists = cb->getBattle(battleID)->getReachability(stack);
|
||||||
std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
|
std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
|
||||||
|
|
||||||
if(stack->creatureId() == CreatureID::CATAPULT)
|
if(stack->creatureId() == CreatureID::CATAPULT)
|
||||||
@ -122,24 +129,24 @@ void CStupidAI::activeStack( const CStack * stack )
|
|||||||
attack.side = side;
|
attack.side = side;
|
||||||
attack.stackNumber = stack->unitId();
|
attack.stackNumber = stack->unitId();
|
||||||
|
|
||||||
cb->battleMakeUnitAction(attack);
|
cb->battleMakeUnitAction(battleID, attack);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if(stack->hasBonusOfType(BonusType::SIEGE_WEAPON))
|
else if(stack->hasBonusOfType(BonusType::SIEGE_WEAPON))
|
||||||
{
|
{
|
||||||
cb->battleMakeUnitAction(BattleAction::makeDefend(stack));
|
cb->battleMakeUnitAction(battleID, BattleAction::makeDefend(stack));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const CStack *s : cb->battleGetStacks(CBattleCallback::ONLY_ENEMY))
|
for (const CStack *s : cb->getBattle(battleID)->battleGetStacks(CBattleInfoEssentials::ONLY_ENEMY))
|
||||||
{
|
{
|
||||||
if(cb->battleCanShoot(stack, s->getPosition()))
|
if(cb->getBattle(battleID)->battleCanShoot(stack, s->getPosition()))
|
||||||
{
|
{
|
||||||
enemiesShootable.push_back(s);
|
enemiesShootable.push_back(s);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack, false);
|
std::vector<BattleHex> avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(stack, false);
|
||||||
|
|
||||||
for (BattleHex hex : avHexes)
|
for (BattleHex hex : avHexes)
|
||||||
{
|
{
|
||||||
@ -162,21 +169,23 @@ void CStupidAI::activeStack( const CStack * stack )
|
|||||||
}
|
}
|
||||||
|
|
||||||
for ( auto & enemy : enemiesReachable )
|
for ( auto & enemy : enemiesReachable )
|
||||||
enemy.calcDmg( stack );
|
enemy.calcDmg(battleID, stack);
|
||||||
|
|
||||||
for ( auto & enemy : enemiesShootable )
|
for ( auto & enemy : enemiesShootable )
|
||||||
enemy.calcDmg( stack );
|
enemy.calcDmg(battleID, stack);
|
||||||
|
|
||||||
if(enemiesShootable.size())
|
if(enemiesShootable.size())
|
||||||
{
|
{
|
||||||
const EnemyInfo &ei= *std::max_element(enemiesShootable.begin(), enemiesShootable.end(), isMoreProfitable);
|
const EnemyInfo &ei= *std::max_element(enemiesShootable.begin(), enemiesShootable.end(), isMoreProfitable);
|
||||||
cb->battleMakeUnitAction(BattleAction::makeShotAttack(stack, ei.s));
|
cb->battleMakeUnitAction(battleID, BattleAction::makeShotAttack(stack, ei.s));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if(enemiesReachable.size())
|
else if(enemiesReachable.size())
|
||||||
{
|
{
|
||||||
const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable);
|
const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable);
|
||||||
cb->battleMakeUnitAction(BattleAction::makeMeleeAttack(stack, ei.s->getPosition(), *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), &willSecondHexBlockMoreEnemyShooters)));
|
BattleHex targetHex = *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), [&](auto a, auto b) { return willSecondHexBlockMoreEnemyShooters(battleID, a, b);});
|
||||||
|
|
||||||
|
cb->battleMakeUnitAction(battleID, BattleAction::makeMeleeAttack(stack, ei.s->getPosition(), targetHex));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if(enemiesUnreachable.size()) //due to #955 - a buggy battle may occur when there are no enemies
|
else if(enemiesUnreachable.size()) //due to #955 - a buggy battle may occur when there are no enemies
|
||||||
@ -188,26 +197,26 @@ void CStupidAI::activeStack( const CStack * stack )
|
|||||||
|
|
||||||
if(dists.distToNearestNeighbour(stack, closestEnemy->s) < GameConstants::BFIELD_SIZE)
|
if(dists.distToNearestNeighbour(stack, closestEnemy->s) < GameConstants::BFIELD_SIZE)
|
||||||
{
|
{
|
||||||
cb->battleMakeUnitAction(goTowards(stack, closestEnemy->s->getAttackableHexes(stack)));
|
cb->battleMakeUnitAction(battleID, goTowards(battleID, stack, closestEnemy->s->getAttackableHexes(stack)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cb->battleMakeUnitAction(BattleAction::makeDefend(stack));
|
cb->battleMakeUnitAction(battleID, BattleAction::makeDefend(stack));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStupidAI::battleAttack(const BattleAttack *ba)
|
void CStupidAI::battleAttack(const BattleID & battleID, const BattleAttack *ba)
|
||||||
{
|
{
|
||||||
print("battleAttack called");
|
print("battleAttack called");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStupidAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged)
|
void CStupidAI::battleStacksAttacked(const BattleID & battleID, const std::vector<BattleStackAttacked> & bsa, bool ranged)
|
||||||
{
|
{
|
||||||
print("battleStacksAttacked called");
|
print("battleStacksAttacked called");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStupidAI::battleEnd(const BattleResult *br, QueryID queryID)
|
void CStupidAI::battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID)
|
||||||
{
|
{
|
||||||
print("battleEnd called");
|
print("battleEnd called");
|
||||||
}
|
}
|
||||||
@ -217,38 +226,38 @@ void CStupidAI::battleEnd(const BattleResult *br, QueryID queryID)
|
|||||||
// print("battleResultsApplied called");
|
// print("battleResultsApplied called");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
void CStupidAI::battleNewRoundFirst(int round)
|
void CStupidAI::battleNewRoundFirst(const BattleID & battleID)
|
||||||
{
|
{
|
||||||
print("battleNewRoundFirst called");
|
print("battleNewRoundFirst called");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStupidAI::battleNewRound(int round)
|
void CStupidAI::battleNewRound(const BattleID & battleID)
|
||||||
{
|
{
|
||||||
print("battleNewRound called");
|
print("battleNewRound called");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStupidAI::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport)
|
void CStupidAI::battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport)
|
||||||
{
|
{
|
||||||
print("battleStackMoved called");
|
print("battleStackMoved called");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStupidAI::battleSpellCast(const BattleSpellCast *sc)
|
void CStupidAI::battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc)
|
||||||
{
|
{
|
||||||
print("battleSpellCast called");
|
print("battleSpellCast called");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStupidAI::battleStacksEffectsSet(const SetStackEffect & sse)
|
void CStupidAI::battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse)
|
||||||
{
|
{
|
||||||
print("battleStacksEffectsSet called");
|
print("battleStacksEffectsSet called");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStupidAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side, bool replayAllowed)
|
void CStupidAI::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side, bool replayAllowed)
|
||||||
{
|
{
|
||||||
print("battleStart called");
|
print("battleStart called");
|
||||||
side = Side;
|
side = Side;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStupidAI::battleCatapultAttacked(const CatapultAttack & ca)
|
void CStupidAI::battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca)
|
||||||
{
|
{
|
||||||
print("battleCatapultAttacked called");
|
print("battleCatapultAttacked called");
|
||||||
}
|
}
|
||||||
@ -258,10 +267,10 @@ void CStupidAI::print(const std::string &text) const
|
|||||||
logAi->trace("CStupidAI [%p]: %s", this, text);
|
logAi->trace("CStupidAI [%p]: %s", this, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
BattleAction CStupidAI::goTowards(const CStack * stack, std::vector<BattleHex> hexes) const
|
BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> hexes) const
|
||||||
{
|
{
|
||||||
auto reachability = cb->getReachability(stack);
|
auto reachability = cb->getBattle(battleID)->getReachability(stack);
|
||||||
auto avHexes = cb->battleGetAvailableHexes(reachability, stack, false);
|
auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
|
||||||
|
|
||||||
if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
|
if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
|
||||||
{
|
{
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
#include "../../lib/battle/BattleHex.h"
|
#include "../../lib/battle/BattleHex.h"
|
||||||
#include "../../lib/battle/ReachabilityInfo.h"
|
#include "../../lib/battle/ReachabilityInfo.h"
|
||||||
|
#include "../../lib/CGameInterface.h"
|
||||||
|
|
||||||
class EnemyInfo;
|
class EnemyInfo;
|
||||||
|
|
||||||
@ -29,25 +30,27 @@ public:
|
|||||||
~CStupidAI();
|
~CStupidAI();
|
||||||
|
|
||||||
void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override;
|
void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override;
|
||||||
void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
|
void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB, AutocombatPreferences autocombatPreferences) override;
|
||||||
void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
|
|
||||||
void activeStack(const CStack * stack) override; //called when it's turn of that stack
|
|
||||||
void yourTacticPhase(int distance) override;
|
|
||||||
|
|
||||||
void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
|
void actionFinished(const BattleID & battleID, const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
|
||||||
void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged) override; //called when stack receives damage (after battleAttack())
|
void actionStarted(const BattleID & battleID, const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
|
||||||
void battleEnd(const BattleResult *br, QueryID queryID) override;
|
void activeStack(const BattleID & battleID, const CStack * stack) override; //called when it's turn of that stack
|
||||||
|
void yourTacticPhase(const BattleID & battleID, int distance) override;
|
||||||
|
|
||||||
|
void battleAttack(const BattleID & battleID, const BattleAttack *ba) override; //called when stack is performing attack
|
||||||
|
void battleStacksAttacked(const BattleID & battleID, const std::vector<BattleStackAttacked> & bsa, bool ranged) override; //called when stack receives damage (after battleAttack())
|
||||||
|
void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override;
|
||||||
//void battleResultsApplied() override; //called when all effects of last battle are applied
|
//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 battleNewRoundFirst(const BattleID & battleID) 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 battleNewRound(const BattleID & battleID) 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<BattleHex> dest, int distance, bool teleport) override;
|
void battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport) override;
|
||||||
void battleSpellCast(const BattleSpellCast *sc) override;
|
void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override;
|
||||||
void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks
|
void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override;//called when a specific effect is set to stacks
|
||||||
//void battleTriggerEffect(const BattleTriggerEffect & bte) override;
|
//void battleTriggerEffect(const BattleTriggerEffect & bte) override;
|
||||||
void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right
|
void battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right
|
||||||
void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
|
void battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca) override; //called when catapult makes an attack
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BattleAction goTowards(const CStack * stack, std::vector<BattleHex> hexes) const;
|
BattleAction goTowards(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> hexes) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,12 +21,8 @@
|
|||||||
#include "../../lib/mapObjects/CQuest.h"
|
#include "../../lib/mapObjects/CQuest.h"
|
||||||
#include "../../lib/mapping/CMapDefines.h"
|
#include "../../lib/mapping/CMapDefines.h"
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<VCAI> ai;
|
|
||||||
extern FuzzyHelper * fh;
|
extern FuzzyHelper * fh;
|
||||||
|
|
||||||
//extern static const int3 dirs[8];
|
|
||||||
|
|
||||||
const CGObjectInstance * ObjectIdRef::operator->() const
|
const CGObjectInstance * ObjectIdRef::operator->() const
|
||||||
{
|
{
|
||||||
return cb->getObj(id, false);
|
return cb->getObj(id, false);
|
||||||
@ -228,7 +224,7 @@ creInfo infoFromDC(const dwellingContent & dc)
|
|||||||
creInfo ci;
|
creInfo ci;
|
||||||
ci.count = dc.first;
|
ci.count = dc.first;
|
||||||
ci.creID = dc.second.size() ? dc.second.back() : CreatureID(-1); //should never be accessed
|
ci.creID = dc.second.size() ? dc.second.back() : CreatureID(-1); //should never be accessed
|
||||||
if (ci.creID != -1)
|
if (ci.creID != CreatureID::NONE)
|
||||||
{
|
{
|
||||||
ci.cre = VLC->creatures()->getById(ci.creID);
|
ci.cre = VLC->creatures()->getById(ci.creID);
|
||||||
ci.level = ci.cre->getLevel(); //this is creature tier, while tryRealize expects dwelling level. Ignore.
|
ci.level = ci.cre->getLevel(); //this is creature tier, while tryRealize expects dwelling level. Ignore.
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||||
#include "../../CCallback.h"
|
#include "../../CCallback.h"
|
||||||
|
|
||||||
|
class VCAI;
|
||||||
class CCallback;
|
class CCallback;
|
||||||
struct creInfo;
|
struct creInfo;
|
||||||
|
|
||||||
@ -33,7 +34,8 @@ const int ALLOWED_ROAMING_HEROES = 8;
|
|||||||
extern const double SAFE_ATTACK_CONSTANT;
|
extern const double SAFE_ATTACK_CONSTANT;
|
||||||
extern const int GOLD_RESERVE;
|
extern const int GOLD_RESERVE;
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
extern thread_local CCallback * cb;
|
||||||
|
extern thread_local VCAI * ai;
|
||||||
|
|
||||||
//provisional class for AI to store a reference to an owned hero object
|
//provisional class for AI to store a reference to an owned hero object
|
||||||
//checks if it's valid on access, should be used in place of const CGHeroInstance*
|
//checks if it's valid on access, should be used in place of const CGHeroInstance*
|
||||||
@ -140,7 +142,7 @@ class ObjsVector : public std::vector<ObjectIdRef>
|
|||||||
{
|
{
|
||||||
};
|
};
|
||||||
|
|
||||||
template<int id>
|
template<Obj::Type id>
|
||||||
bool objWithID(const CGObjectInstance * obj)
|
bool objWithID(const CGObjectInstance * obj)
|
||||||
{
|
{
|
||||||
return obj->ID == id;
|
return obj->ID == id;
|
||||||
@ -192,7 +194,7 @@ void foreach_tile_pos(CCallback * cbp, const Func & foo) // avoid costly retriev
|
|||||||
template<class Func>
|
template<class Func>
|
||||||
void foreach_neighbour(const int3 & pos, const Func & foo)
|
void foreach_neighbour(const int3 & pos, const Func & foo)
|
||||||
{
|
{
|
||||||
CCallback * cbp = cb.get(); // avoid costly retrieval of thread-specific pointer
|
CCallback * cbp = cb; // avoid costly retrieval of thread-specific pointer
|
||||||
for(const int3 & dir : int3::getDirs())
|
for(const int3 & dir : int3::getDirs())
|
||||||
{
|
{
|
||||||
const int3 n = pos + dir;
|
const int3 n = pos + dir;
|
||||||
|
@ -118,12 +118,12 @@ ui64 ArmyManager::howManyReinforcementsCanBuy(const CCreatureSet * h, const CGDw
|
|||||||
{
|
{
|
||||||
creInfo ci = infoFromDC(dc);
|
creInfo ci = infoFromDC(dc);
|
||||||
|
|
||||||
if(!ci.count || ci.creID == -1)
|
if(!ci.count || ci.creID == CreatureID::NONE)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
vstd::amin(ci.count, availableRes / ci.cre->getFullRecruitCost()); //max count we can afford
|
vstd::amin(ci.count, availableRes / ci.cre->getFullRecruitCost()); //max count we can afford
|
||||||
|
|
||||||
if(ci.count && ci.creID != -1) //valid creature at this level
|
if(ci.count && ci.creID != CreatureID::NONE) //valid creature at this level
|
||||||
{
|
{
|
||||||
//can be merged with another stack?
|
//can be merged with another stack?
|
||||||
SlotID dst = h->getSlotFor(ci.creID);
|
SlotID dst = h->getSlotFor(ci.creID);
|
||||||
|
@ -38,7 +38,7 @@ bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID
|
|||||||
|
|
||||||
for (BuildingID buildID : toBuild)
|
for (BuildingID buildID : toBuild)
|
||||||
{
|
{
|
||||||
EBuildingState::EBuildingState canBuild = cb->canBuildStructure(t, buildID);
|
EBuildingState canBuild = cb->canBuildStructure(t, buildID);
|
||||||
if (canBuild == EBuildingState::HAVE_CAPITAL || canBuild == EBuildingState::FORBIDDEN || canBuild == EBuildingState::NO_WATER)
|
if (canBuild == EBuildingState::HAVE_CAPITAL || canBuild == EBuildingState::FORBIDDEN || canBuild == EBuildingState::NO_WATER)
|
||||||
return false; //we won't be able to build this
|
return false; //we won't be able to build this
|
||||||
}
|
}
|
||||||
@ -52,7 +52,7 @@ bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID
|
|||||||
{
|
{
|
||||||
const CBuilding * b = t->town->buildings.at(buildID);
|
const CBuilding * b = t->town->buildings.at(buildID);
|
||||||
|
|
||||||
EBuildingState::EBuildingState canBuild = cb->canBuildStructure(t, buildID);
|
EBuildingState canBuild = cb->canBuildStructure(t, buildID);
|
||||||
if (canBuild == EBuildingState::ALLOWED)
|
if (canBuild == EBuildingState::ALLOWED)
|
||||||
{
|
{
|
||||||
PotentialBuilding pb;
|
PotentialBuilding pb;
|
||||||
@ -222,7 +222,7 @@ bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
|
|||||||
std::vector<BuildingID> extraBuildings;
|
std::vector<BuildingID> extraBuildings;
|
||||||
for (auto buildingInfo : t->town->buildings)
|
for (auto buildingInfo : t->town->buildings)
|
||||||
{
|
{
|
||||||
if (buildingInfo.first > 43)
|
if (buildingInfo.first > BuildingID::DWELL_UP2_FIRST)
|
||||||
extraBuildings.push_back(buildingInfo.first);
|
extraBuildings.push_back(buildingInfo.first);
|
||||||
}
|
}
|
||||||
return tryBuildAnyStructure(t, extraBuildings);
|
return tryBuildAnyStructure(t, extraBuildings);
|
||||||
|
@ -18,9 +18,6 @@
|
|||||||
#define MIN_AI_STRENGTH (0.5f) //lower when combat AI gets smarter
|
#define MIN_AI_STRENGTH (0.5f) //lower when combat AI gets smarter
|
||||||
#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
|
#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<VCAI> ai;
|
|
||||||
extern FuzzyHelper * fh;
|
|
||||||
|
|
||||||
engineBase::engineBase()
|
engineBase::engineBase()
|
||||||
{
|
{
|
||||||
rules = new fl::RuleBlock();
|
rules = new fl::RuleBlock();
|
||||||
|
@ -8,7 +8,11 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <fl/Headers.h>
|
#if __has_include(<fuzzylite/Headers.h>)
|
||||||
|
# include <fuzzylite/Headers.h>
|
||||||
|
#else
|
||||||
|
# include <fl/Headers.h>
|
||||||
|
#endif
|
||||||
#include "Goals/AbstractGoal.h"
|
#include "Goals/AbstractGoal.h"
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
@ -23,9 +23,6 @@
|
|||||||
|
|
||||||
FuzzyHelper * fh;
|
FuzzyHelper * fh;
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<VCAI> ai;
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
|
|
||||||
Goals::TSubgoal FuzzyHelper::chooseSolution(Goals::TGoalVec vec)
|
Goals::TSubgoal FuzzyHelper::chooseSolution(Goals::TGoalVec vec)
|
||||||
{
|
{
|
||||||
if(vec.empty())
|
if(vec.empty())
|
||||||
@ -216,7 +213,7 @@ void FuzzyHelper::setPriority(Goals::TSubgoal & g) //calls evaluate - Visitor pa
|
|||||||
|
|
||||||
ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor)
|
ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor)
|
||||||
{
|
{
|
||||||
return evaluateDanger(tile, visitor, ai.get());
|
return evaluateDanger(tile, visitor, ai);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor, const VCAI * ai)
|
ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor, const VCAI * ai)
|
||||||
@ -285,7 +282,7 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj, const VCAI * ai)
|
|||||||
{
|
{
|
||||||
auto cb = ai->myCb;
|
auto cb = ai->myCb;
|
||||||
|
|
||||||
if(obj->tempOwner < PlayerColor::PLAYER_LIMIT && cb->getPlayerRelations(obj->tempOwner, ai->playerID) != PlayerRelations::ENEMIES) //owned or allied objects don't pose any threat
|
if(obj->tempOwner.isValidPlayer() && cb->getPlayerRelations(obj->tempOwner, ai->playerID) != PlayerRelations::ENEMIES) //owned or allied objects don't pose any threat
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
switch(obj->ID)
|
switch(obj->ID)
|
||||||
|
@ -51,3 +51,5 @@ public:
|
|||||||
ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor, const VCAI * ai);
|
ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor, const VCAI * ai);
|
||||||
ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor);
|
ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern FuzzyHelper * fh;
|
||||||
|
@ -14,11 +14,7 @@
|
|||||||
#include "../FuzzyHelper.h"
|
#include "../FuzzyHelper.h"
|
||||||
#include "../ResourceManager.h"
|
#include "../ResourceManager.h"
|
||||||
#include "../BuildingManager.h"
|
#include "../BuildingManager.h"
|
||||||
#include "../../../lib/StringConstants.h"
|
#include "../../../lib/constants/StringConstants.h"
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<VCAI> ai;
|
|
||||||
extern FuzzyHelper * fh;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
|
@ -14,10 +14,6 @@
|
|||||||
#include "../AIhelper.h"
|
#include "../AIhelper.h"
|
||||||
#include "../../../lib/mapObjects/CGTownInstance.h"
|
#include "../../../lib/mapObjects/CGTownInstance.h"
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<VCAI> ai;
|
|
||||||
extern FuzzyHelper * fh;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
bool AdventureSpellCast::operator==(const AdventureSpellCast & other) const
|
bool AdventureSpellCast::operator==(const AdventureSpellCast & other) const
|
||||||
|
@ -17,12 +17,7 @@
|
|||||||
#include "../ResourceManager.h"
|
#include "../ResourceManager.h"
|
||||||
#include "../BuildingManager.h"
|
#include "../BuildingManager.h"
|
||||||
#include "../../../lib/mapObjects/CGTownInstance.h"
|
#include "../../../lib/mapObjects/CGTownInstance.h"
|
||||||
#include "../../../lib/StringConstants.h"
|
#include "../../../lib/constants/StringConstants.h"
|
||||||
|
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<VCAI> ai;
|
|
||||||
extern FuzzyHelper * fh;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
|
@ -13,10 +13,6 @@
|
|||||||
#include "../FuzzyHelper.h"
|
#include "../FuzzyHelper.h"
|
||||||
#include "../AIhelper.h"
|
#include "../AIhelper.h"
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<VCAI> ai;
|
|
||||||
extern FuzzyHelper * fh;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
bool BuildBoat::operator==(const BuildBoat & other) const
|
bool BuildBoat::operator==(const BuildBoat & other) const
|
||||||
|
@ -16,12 +16,7 @@
|
|||||||
#include "../ResourceManager.h"
|
#include "../ResourceManager.h"
|
||||||
#include "../BuildingManager.h"
|
#include "../BuildingManager.h"
|
||||||
#include "../../../lib/mapObjects/CGTownInstance.h"
|
#include "../../../lib/mapObjects/CGTownInstance.h"
|
||||||
#include "../../../lib/StringConstants.h"
|
#include "../../../lib/constants/StringConstants.h"
|
||||||
|
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<VCAI> ai;
|
|
||||||
extern FuzzyHelper * fh;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
|
@ -13,11 +13,6 @@
|
|||||||
#include "../AIhelper.h"
|
#include "../AIhelper.h"
|
||||||
#include "../../../lib/mapObjects/CGTownInstance.h"
|
#include "../../../lib/mapObjects/CGTownInstance.h"
|
||||||
|
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<VCAI> ai;
|
|
||||||
extern FuzzyHelper * fh;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
bool BuyArmy::operator==(const BuyArmy & other) const
|
bool BuyArmy::operator==(const BuyArmy & other) const
|
||||||
|
@ -16,11 +16,6 @@
|
|||||||
#include "../FuzzyHelper.h"
|
#include "../FuzzyHelper.h"
|
||||||
#include "../AIhelper.h"
|
#include "../AIhelper.h"
|
||||||
|
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<VCAI> ai;
|
|
||||||
extern FuzzyHelper * fh;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
bool ClearWayTo::operator==(const ClearWayTo & other) const
|
bool ClearWayTo::operator==(const ClearWayTo & other) const
|
||||||
|
@ -16,12 +16,7 @@
|
|||||||
#include "../ResourceManager.h"
|
#include "../ResourceManager.h"
|
||||||
#include "../BuildingManager.h"
|
#include "../BuildingManager.h"
|
||||||
#include "../../../lib/mapObjects/CGMarket.h"
|
#include "../../../lib/mapObjects/CGMarket.h"
|
||||||
#include "../../../lib/StringConstants.h"
|
#include "../../../lib/constants/StringConstants.h"
|
||||||
|
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<VCAI> ai;
|
|
||||||
extern FuzzyHelper * fh;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
@ -65,14 +60,11 @@ TGoalVec CollectRes::getAllPossibleSubgoals()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Obj::WATER_WHEEL:
|
|
||||||
if (resID != GameResID(EGameResID::GOLD))
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
case Obj::MYSTICAL_GARDEN:
|
case Obj::MYSTICAL_GARDEN:
|
||||||
if ((resID != GameResID(EGameResID::GOLD)) && (resID != GameResID(EGameResID::GEMS)))
|
if ((resID != GameResID(EGameResID::GOLD)) && (resID != GameResID(EGameResID::GEMS)))
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
|
case Obj::WATER_WHEEL:
|
||||||
case Obj::LEAN_TO:
|
case Obj::LEAN_TO:
|
||||||
case Obj::WAGON:
|
case Obj::WAGON:
|
||||||
if (resID != GameResID(EGameResID::GOLD))
|
if (resID != GameResID(EGameResID::GOLD))
|
||||||
@ -175,10 +167,10 @@ TSubgoal CollectRes::whatToDoToTrade()
|
|||||||
int howManyCanWeBuy = 0;
|
int howManyCanWeBuy = 0;
|
||||||
for (GameResID i = EGameResID::WOOD; i <= EGameResID::GOLD; ++i)
|
for (GameResID i = EGameResID::WOOD; i <= EGameResID::GOLD; ++i)
|
||||||
{
|
{
|
||||||
if (GameResID(i) == resID)
|
if (i.getNum() == resID)
|
||||||
continue;
|
continue;
|
||||||
int toGive = -1, toReceive = -1;
|
int toGive = -1, toReceive = -1;
|
||||||
m->getOffer(GameResID(i), resID, toGive, toReceive, EMarketMode::RESOURCE_RESOURCE);
|
m->getOffer(i, resID, toGive, toReceive, EMarketMode::RESOURCE_RESOURCE);
|
||||||
assert(toGive > 0 && toReceive > 0);
|
assert(toGive > 0 && toReceive > 0);
|
||||||
howManyCanWeBuy += toReceive * (ai->ah->freeResources()[i] / toGive);
|
howManyCanWeBuy += toReceive * (ai->ah->freeResources()[i] / toGive);
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,6 @@
|
|||||||
#include "../AIhelper.h"
|
#include "../AIhelper.h"
|
||||||
#include "../../../lib/mapObjects/CQuest.h"
|
#include "../../../lib/mapObjects/CQuest.h"
|
||||||
|
|
||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
|
||||||
extern boost::thread_specific_ptr<VCAI> ai;
|
|
||||||
extern FuzzyHelper * fh;
|
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
bool CompleteQuest::operator==(const CompleteQuest & other) const
|
bool CompleteQuest::operator==(const CompleteQuest & other) const
|
||||||
@ -25,48 +21,43 @@ bool CompleteQuest::operator==(const CompleteQuest & other) const
|
|||||||
return q.quest->qid == other.q.quest->qid;
|
return q.quest->qid == other.q.quest->qid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isKeyMaster(const QuestInfo & q)
|
||||||
|
{
|
||||||
|
return q.obj && (q.obj->ID == Obj::BORDER_GATE || q.obj->ID == Obj::BORDERGUARD);
|
||||||
|
}
|
||||||
|
|
||||||
TGoalVec CompleteQuest::getAllPossibleSubgoals()
|
TGoalVec CompleteQuest::getAllPossibleSubgoals()
|
||||||
{
|
{
|
||||||
TGoalVec solutions;
|
TGoalVec solutions;
|
||||||
|
|
||||||
if(q.quest->missionType && q.quest->progress != CQuest::COMPLETE)
|
if(!q.quest->isCompleted)
|
||||||
{
|
{
|
||||||
logAi->debug("Trying to realize quest: %s", questToString());
|
logAi->debug("Trying to realize quest: %s", questToString());
|
||||||
|
|
||||||
switch(q.quest->missionType)
|
if(isKeyMaster(q))
|
||||||
{
|
|
||||||
case CQuest::MISSION_ART:
|
|
||||||
return missionArt();
|
|
||||||
|
|
||||||
case CQuest::MISSION_HERO:
|
|
||||||
return missionHero();
|
|
||||||
|
|
||||||
case CQuest::MISSION_ARMY:
|
|
||||||
return missionArmy();
|
|
||||||
|
|
||||||
case CQuest::MISSION_RESOURCES:
|
|
||||||
return missionResources();
|
|
||||||
|
|
||||||
case CQuest::MISSION_KILL_HERO:
|
|
||||||
case CQuest::MISSION_KILL_CREATURE:
|
|
||||||
return missionDestroyObj();
|
|
||||||
|
|
||||||
case CQuest::MISSION_PRIMARY_STAT:
|
|
||||||
return missionIncreasePrimaryStat();
|
|
||||||
|
|
||||||
case CQuest::MISSION_LEVEL:
|
|
||||||
return missionLevel();
|
|
||||||
|
|
||||||
case CQuest::MISSION_PLAYER:
|
|
||||||
if(ai->playerID.getNum() != q.quest->m13489val)
|
|
||||||
logAi->debug("Can't be player of color %d", q.quest->m13489val);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CQuest::MISSION_KEYMASTER:
|
|
||||||
return missionKeymaster();
|
return missionKeymaster();
|
||||||
|
|
||||||
} //end of switch
|
if(!q.quest->mission.artifacts.empty())
|
||||||
|
return missionArt();
|
||||||
|
|
||||||
|
if(!q.quest->mission.heroes.empty())
|
||||||
|
return missionHero();
|
||||||
|
|
||||||
|
if(!q.quest->mission.creatures.empty())
|
||||||
|
return missionArmy();
|
||||||
|
|
||||||
|
if(q.quest->mission.resources.nonZero())
|
||||||
|
return missionResources();
|
||||||
|
|
||||||
|
if(q.quest->killTarget != ObjectInstanceID::NONE)
|
||||||
|
return missionDestroyObj();
|
||||||
|
|
||||||
|
for(auto & s : q.quest->mission.primary)
|
||||||
|
if(s)
|
||||||
|
return missionIncreasePrimaryStat();
|
||||||
|
|
||||||
|
if(q.quest->mission.heroLevel > 0)
|
||||||
|
return missionLevel();
|
||||||
}
|
}
|
||||||
|
|
||||||
return TGoalVec();
|
return TGoalVec();
|
||||||
@ -74,7 +65,7 @@ TGoalVec CompleteQuest::getAllPossibleSubgoals()
|
|||||||
|
|
||||||
TSubgoal CompleteQuest::whatToDoToAchieve()
|
TSubgoal CompleteQuest::whatToDoToAchieve()
|
||||||
{
|
{
|
||||||
if(q.quest->missionType == CQuest::MISSION_NONE)
|
if(q.quest->mission == Rewardable::Limiter{})
|
||||||
{
|
{
|
||||||
throw cannotFulfillGoalException("Can not complete inactive quest");
|
throw cannotFulfillGoalException("Can not complete inactive quest");
|
||||||
}
|
}
|
||||||
@ -108,7 +99,7 @@ std::string CompleteQuest::completeMessage() const
|
|||||||
|
|
||||||
std::string CompleteQuest::questToString() const
|
std::string CompleteQuest::questToString() const
|
||||||
{
|
{
|
||||||
if(q.quest->missionType == CQuest::MISSION_NONE)
|
if(q.quest->questName == CQuest::missionName(0))
|
||||||
return "inactive quest";
|
return "inactive quest";
|
||||||
|
|
||||||
MetaString ms;
|
MetaString ms;
|
||||||
@ -141,7 +132,7 @@ TGoalVec CompleteQuest::missionArt() const
|
|||||||
if(!solutions.empty())
|
if(!solutions.empty())
|
||||||
return solutions;
|
return solutions;
|
||||||
|
|
||||||
for(auto art : q.quest->m5arts)
|
for(auto art : q.quest->mission.artifacts)
|
||||||
{
|
{
|
||||||
solutions.push_back(sptr(GetArtOfType(art))); //TODO: transport?
|
solutions.push_back(sptr(GetArtOfType(art))); //TODO: transport?
|
||||||
}
|
}
|
||||||
@ -169,7 +160,7 @@ TGoalVec CompleteQuest::missionArmy() const
|
|||||||
if(!solutions.empty())
|
if(!solutions.empty())
|
||||||
return solutions;
|
return solutions;
|
||||||
|
|
||||||
for(auto creature : q.quest->m6creatures)
|
for(auto creature : q.quest->mission.creatures)
|
||||||
{
|
{
|
||||||
solutions.push_back(sptr(GatherTroops(creature.type->getId(), creature.count)));
|
solutions.push_back(sptr(GatherTroops(creature.type->getId(), creature.count)));
|
||||||
}
|
}
|
||||||
@ -183,7 +174,7 @@ TGoalVec CompleteQuest::missionIncreasePrimaryStat() const
|
|||||||
|
|
||||||
if(solutions.empty())
|
if(solutions.empty())
|
||||||
{
|
{
|
||||||
for(int i = 0; i < q.quest->m2stats.size(); ++i)
|
for(int i = 0; i < q.quest->mission.primary.size(); ++i)
|
||||||
{
|
{
|
||||||
// TODO: library, school and other boost objects
|
// TODO: library, school and other boost objects
|
||||||
logAi->debug("Don't know how to increase primary stat %d", i);
|
logAi->debug("Don't know how to increase primary stat %d", i);
|
||||||
@ -199,7 +190,7 @@ TGoalVec CompleteQuest::missionLevel() const
|
|||||||
|
|
||||||
if(solutions.empty())
|
if(solutions.empty())
|
||||||
{
|
{
|
||||||
logAi->debug("Don't know how to reach hero level %d", q.quest->m13489val);
|
logAi->debug("Don't know how to reach hero level %d", q.quest->mission.heroLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
return solutions;
|
return solutions;
|
||||||
@ -231,10 +222,10 @@ TGoalVec CompleteQuest::missionResources() const
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for(int i = 0; i < q.quest->m7resources.size(); ++i)
|
for(int i = 0; i < q.quest->mission.resources.size(); ++i)
|
||||||
{
|
{
|
||||||
if(q.quest->m7resources[i])
|
if(q.quest->mission.resources[i])
|
||||||
solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.quest->m7resources[i])));
|
solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.quest->mission.resources[i])));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -250,7 +241,7 @@ TGoalVec CompleteQuest::missionDestroyObj() const
|
|||||||
{
|
{
|
||||||
TGoalVec solutions;
|
TGoalVec solutions;
|
||||||
|
|
||||||
auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val);
|
auto obj = cb->getObjByQuestIdentifier(q.quest->killTarget);
|
||||||
|
|
||||||
if(!obj)
|
if(!obj)
|
||||||
return ai->ah->howToVisitObj(q.obj);
|
return ai->ah->howToVisitObj(q.obj);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user