1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-28 08:48:48 +02:00

Merge remote-tracking branch 'origin/develop' into reward_select_all

# Conflicts:
#	lib/mapObjects/CRewardableObject.cpp
This commit is contained in:
Tomasz Zieliński 2024-03-09 06:50:56 +01:00
commit fe8bcc5758
1169 changed files with 48107 additions and 28680 deletions

11
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,11 @@
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot#example-dependabotyml-file-for-github-actions
# Set update schedule for GitHub Actions
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every day
interval: "daily"

View File

@ -6,9 +6,8 @@ on:
- features/*
- beta
- master
- develop
pull_request:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
env:
@ -16,55 +15,7 @@ env:
BUILD_TYPE: Release
jobs:
check_last_build:
if: github.event.schedule != ''
runs-on: ubuntu-latest
outputs:
skip_build: ${{ steps.check_if_built.outputs.skip_build }}
defaults:
run:
shell: bash
steps:
- name: Get repo name
id: get_repo_name
run: echo "::set-output name=value::${GITHUB_REPOSITORY#*/}"
- name: Get last successful build for ${{ github.sha }}
uses: octokit/request-action@v2.1.0
id: get_last_scheduled_run
with:
route: GET /repos/{owner}/{repo}/actions/runs
owner: ${{ github.repository_owner }}
repo: ${{ steps.get_repo_name.outputs.value }}
status: success
per_page: 1
head_sha: ${{ github.sha }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check if successful build of the current commit exists
id: check_if_built
run: |
if [ ${{ fromJson(steps.get_last_scheduled_run.outputs.data).total_count }} -gt 0 ]; then
echo '::set-output name=skip_build::1'
else
echo '::set-output name=skip_build::0'
fi
- name: Cancel current run
if: steps.check_if_built.outputs.skip_build == 1
uses: octokit/request-action@v2.1.0
with:
route: POST /repos/{owner}/{repo}/actions/runs/{run_id}/cancel
owner: ${{ github.repository_owner }}
repo: ${{ steps.get_repo_name.outputs.value }}
run_id: ${{ github.run_id }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Wait for the run to be cancelled
if: steps.check_if_built.outputs.skip_build == 1
run: sleep 60
build:
needs: check_last_build
if: always() && needs.check_last_build.skip_build != 1
strategy:
matrix:
include:
@ -73,8 +24,8 @@ jobs:
test: 0
preset: linux-clang-test
- platform: linux
os: ubuntu-20.04
test: 0
os: ubuntu-22.04
test: 1
preset: linux-gcc-test
- platform: linux
os: ubuntu-20.04
@ -84,6 +35,7 @@ jobs:
os: macos-12
test: 0
pack: 1
pack_type: Release
extension: dmg
preset: macos-conan-ninja-release
conan_profile: macos-intel
@ -93,6 +45,7 @@ jobs:
os: macos-12
test: 0
pack: 1
pack_type: Release
extension: dmg
preset: macos-arm-conan-ninja-release
conan_profile: macos-arm
@ -102,6 +55,7 @@ jobs:
os: macos-12
test: 0
pack: 1
pack_type: Release
extension: ipa
preset: ios-release-conan-ccache
conan_profile: ios-arm64
@ -110,17 +64,29 @@ jobs:
os: windows-latest
test: 0
pack: 1
pack_type: RelWithDebInfo
extension: exe
preset: windows-msvc-release-ccache
- platform: mingw-ubuntu
- platform: mingw
os: ubuntu-22.04
test: 0
pack: 1
pack_type: Release
extension: exe
cpack_args: -D CPACK_NSIS_EXECUTABLE=`which makensis`
cmake_args: -G Ninja
preset: windows-mingw-conan-linux
conan_profile: mingw64-linux.jinja
- platform: mingw-32
os: ubuntu-22.04
test: 0
pack: 1
pack_type: Release
extension: exe
cpack_args: -D CPACK_NSIS_EXECUTABLE=`which makensis`
cmake_args: -G Ninja
preset: windows-mingw-conan-linux
conan_profile: mingw32-linux.jinja
- platform: android-32
os: ubuntu-22.04
extension: apk
@ -141,7 +107,7 @@ jobs:
shell: bash
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: recursive
@ -150,7 +116,7 @@ jobs:
run: |
find . -path ./.git -prune -o -path ./AI/FuzzyLite -prune -o -path ./test/googletest \
-o -path ./osx -prune -o -type f \
-not -name '*.png' -and -not -name '*.vcxproj*' -and -not -name '*.props' -and -not -name '*.wav' -and -not -name '*.ico' -and -not -name '*.bat' -print0 | \
-not -name '*.png' -and -not -name '*.vcxproj*' -and -not -name '*.props' -and -not -name '*.wav' -and -not -name '*.webm' -and -not -name '*.ico' -and -not -name '*.bat' -print0 | \
{ ! xargs -0 grep -l -z -P '\r\n'; }
- name: Validate JSON
@ -158,7 +124,7 @@ jobs:
# 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
pip3 install jstyleson
python3 CI/linux-qt6/validate_json.py
- name: Dependencies
@ -168,7 +134,7 @@ jobs:
# ensure the ccache for each PR is separate so they don't interfere with each other
# fall back to ccache of the vcmi/vcmi repo if no PR-specific ccache is found
- name: Ccache for PRs
- name: ccache for PRs
uses: hendrikmuhs/ccache-action@v1.2
if: ${{ github.event.number != '' }}
with:
@ -180,9 +146,9 @@ jobs:
max-size: "5G"
verbose: 2
- name: Ccache for everything but PRs
- name: ccache for everything but PRs
uses: hendrikmuhs/ccache-action@v1.2
if: ${{ github.event.number == '' }}
if: ${{ (github.repository == 'vcmi/vcmi' && github.event.number == '' && github.ref == 'refs/heads/develop') || github.repository != 'vcmi/vcmi' }}
with:
key: ${{ matrix.preset }}-no-PR
restore-keys: |
@ -191,7 +157,17 @@ jobs:
max-size: "5G"
verbose: 2
- uses: actions/setup-python@v4
- name: Prepare Heroes 3 data
env:
HEROES_3_DATA_PASSWORD: ${{ secrets.HEROES_3_DATA_PASSWORD }}
if: ${{ env.HEROES_3_DATA_PASSWORD != '' && matrix.test == 1 }}
run: |
wget --progress=dot:giga https://github.com/vcmi-mods/vcmi-test-data/releases/download/v1.0/h3_assets.zip
7za x h3_assets.zip -p$HEROES_3_DATA_PASSWORD
mkdir -p ~/.local/share/vcmi/
mv h3_assets/* ~/.local/share/vcmi/
- uses: actions/setup-python@v5
if: "${{ matrix.conan_profile != '' }}"
with:
python-version: '3.10'
@ -211,10 +187,6 @@ jobs:
env:
GENERATE_ONLY_BUILT_CONFIG: 1
- name: Git branch name
id: git-branch-name
uses: EthanSK/git-branch-name-action@v1
- name: Build Number
run: |
source '${{github.workspace}}/CI/get_package_name.sh'
@ -227,31 +199,42 @@ jobs:
env:
PULL_REQUEST: ${{ github.event.pull_request.number }}
- name: CMake Preset with ccache
- name: Configure
run: |
cmake -DCMAKE_CXX_COMPILER_LAUNCHER=ccache --preset ${{ matrix.preset }}
if [[ ${{matrix.preset}} == linux-gcc-test ]]; then GCC13=1; fi
cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }} ${GCC13:+-DCMAKE_C_COMPILER=gcc-13 -DCMAKE_CXX_COMPILER=g++-13}
- name: Build Preset
- name: Build
run: |
cmake --build --preset ${{matrix.preset}}
- name: Test
if: ${{ matrix.test == 1 }}
env:
HEROES_3_DATA_PASSWORD: ${{ secrets.HEROES_3_DATA_PASSWORD }}
if: ${{ env.HEROES_3_DATA_PASSWORD != '' && matrix.test == 1 }}
run: |
ctest --preset ${{matrix.preset}}
- name: Kill XProtect to work around CPack issue on macOS
if: ${{ startsWith(matrix.platform, 'mac') }}
run: |
# Cf. https://github.com/actions/runner-images/issues/7522#issuecomment-1556766641
echo Killing...; sudo pkill -9 XProtect >/dev/null || true;
echo "Waiting..."; counter=0; while pgrep XProtect && ((counter < 20)); do sleep 3; ((counter++)); done
pgrep XProtect || true
- name: Pack
id: cpack
if: ${{ matrix.pack == 1 }}
run: |
cd '${{github.workspace}}/out/build/${{matrix.preset}}'
CPACK_PATH=`which -a cpack | grep -m1 -v -i chocolatey`
"$CPACK_PATH" -C ${{env.BUILD_TYPE}} ${{ matrix.cpack_args }}
counter=0; until "$CPACK_PATH" -C ${{matrix.pack_type}} ${{ matrix.cpack_args }} || ((counter > 20)); do sleep 3; ((counter++)); done
test -f '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' \
&& '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' '${{github.workspace}}' "$(ls '${{ env.VCMI_PACKAGE_FILE_NAME }}'.*)"
rm -rf _CPack_Packages
- name: Create android package
- name: Create Android package
if: ${{ startsWith(matrix.platform, 'android') }}
run: |
cd android
@ -266,7 +249,7 @@ jobs:
- name: Artifacts
if: ${{ matrix.pack == 1 }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
path: |
@ -274,22 +257,30 @@ jobs:
- name: Android artifacts
if: ${{ startsWith(matrix.platform, 'android') }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
path: |
${{ env.ANDROID_APK_PATH }}
- name: Symbols
if: ${{ matrix.platform == 'msvc' }}
uses: actions/upload-artifact@v4
with:
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} - symbols
path: |
${{github.workspace}}/**/*.pdb
- name: Android JNI ${{matrix.platform}}
if: ${{ startsWith(matrix.platform, 'android') && github.ref == 'refs/heads/master' }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Android JNI ${{matrix.platform}}
path: |
${{ github.workspace }}/android/vcmi-app/src/main/jniLibs
- name: Upload build
if: ${{ (matrix.pack == 1 || startsWith(matrix.platform, 'android')) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc' }}
if: ${{ (matrix.pack == 1 || startsWith(matrix.platform, 'android')) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc' && matrix.platform != 'mingw-32' }}
continue-on-error: true
run: |
if cd '${{github.workspace}}/android/vcmi-app/build/outputs/apk/daily' ; then
@ -301,14 +292,6 @@ jobs:
env:
DEPLOY_RSA: ${{ secrets.DEPLOY_RSA }}
PACKAGE_EXTENSION: ${{ matrix.extension }}
- uses: act10ns/slack@v1
with:
status: ${{ job.status }}
channel: '#notifications'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: always()
# copy-pasted mostly
bundle_release:
@ -331,7 +314,7 @@ jobs:
shell: bash
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: recursive
@ -340,10 +323,11 @@ jobs:
env:
VCMI_BUILD_PLATFORM: x64
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
if: "${{ matrix.conan_profile != '' }}"
with:
python-version: '3.10'
- name: Conan setup
if: "${{ matrix.conan_profile != '' }}"
run: |
@ -359,10 +343,6 @@ jobs:
env:
GENERATE_ONLY_BUILT_CONFIG: 1
- name: Git branch name
id: git-branch-name
uses: EthanSK/git-branch-name-action@v1
- name: Build Number
run: |
source '${{github.workspace}}/CI/get_package_name.sh'
@ -384,12 +364,12 @@ jobs:
cmake --build --preset ${{matrix.preset}}
- name: Download libs x64
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: Android JNI android-64
path: ${{ github.workspace }}/android/vcmi-app/src/main/jniLibs/
- name: Create android package
- name: Create Android package
run: |
cd android
./gradlew bundleRelease --info
@ -399,16 +379,8 @@ jobs:
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
- name: Android artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ env.VCMI_PACKAGE_FILE_NAME }}
path: |
${{ env.ANDROID_APK_PATH }}
- uses: act10ns/slack@v1
with:
status: ${{ job.status }}
channel: '#notifications'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: always()

8
.gitignore vendored
View File

@ -1,12 +1,15 @@
/client/vcmiclient
/server/vcmiserver
/launcher/.lupdate
/launcher/vcmilauncher
/mapeditor/.lupdate
/launcher/vcmilauncher_automoc.cpp
/conan-*
build/
.cache/*
out/
/.qt
*.dll
*.exe
*.depend
@ -41,6 +44,8 @@ VCMI_VS11.sdf
VCMI_VS11.opensdf
.DS_Store
CMakeUserPresets.json
compile_commands.json
fuzzylite.pc
# Visual Studio
*.suo
@ -61,5 +66,8 @@ CMakeUserPresets.json
/deps
.vs/
# Visual Studio Code
/.vscode/
# CLion
.idea/

View File

@ -33,7 +33,8 @@ void DamageCache::buildDamageCache(std::shared_ptr<HypotheticBattle> hb, int sid
return u->isValidTarget();
});
std::vector<const battle::Unit *> ourUnits, enemyUnits;
std::vector<const battle::Unit *> ourUnits;
std::vector<const battle::Unit *> enemyUnits;
for(auto stack : stacks)
{
@ -61,16 +62,12 @@ void DamageCache::buildDamageCache(std::shared_ptr<HypotheticBattle> hb, int sid
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();
bool wasComputedBefore = damageCache[attacker->unitId()].count(defender->unitId());
if(damage == 0)
{
if (!wasComputedBefore)
cacheDamage(attacker, defender, hb);
damage = damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount();
}
return static_cast<int64_t>(damage);
return damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount();
}
int64_t DamageCache::getOriginalDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb)
@ -295,8 +292,10 @@ AttackPossibility AttackPossibility::evaluate(
for(int i = 0; i < totalAttacks; i++)
{
int64_t damageDealt, damageReceived;
float defenderDamageReduce, attackerDamageReduce;
int64_t damageDealt;
int64_t damageReceived;
float defenderDamageReduce;
float attackerDamageReduce;
DamageEstimation retaliation;
auto attackDmg = state->battleEstimateDamage(ap.attack, &retaliation);

View File

@ -10,7 +10,6 @@
#pragma once
#include "../../lib/battle/CUnitState.h"
#include "../../CCallback.h"
#include "common.h"
#include "StackWithBonuses.h"
#define BATTLE_TRACE_LEVEL 0

View File

@ -49,7 +49,6 @@ CBattleAI::~CBattleAI()
void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB)
{
setCbc(CB);
env = ENV;
cb = CB;
playerID = *CB->getPlayerID();
@ -90,7 +89,8 @@ void CBattleAI::yourTacticPhase(const BattleID & battleID, int distance)
static float getStrengthRatio(std::shared_ptr<CBattleInfoCallback> cb, int side)
{
auto stacks = cb->battleGetAllStacks();
auto our = 0, enemy = 0;
auto our = 0;
auto enemy = 0;
for(auto stack : stacks)
{
@ -120,7 +120,6 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * 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)
auto start = std::chrono::high_resolution_clock::now();
@ -145,7 +144,7 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
result = evaluator.selectStackAction(stack);
if(!skipCastUntilNextBattle && evaluator.canCastSpell())
if(autobattlePreferences.enableSpellsUsage && !skipCastUntilNextBattle && evaluator.canCastSpell())
{
auto spelCasted = evaluator.attemptCastingSpell(stack);

View File

@ -22,6 +22,8 @@
#include "../../lib/battle/BattleStateInfoForRetreat.h"
#include "../../lib/battle/CObstacleInstance.h"
#include "../../lib/battle/BattleAction.h"
#include "../../lib/CRandomGenerator.h"
// TODO: remove
// Eventually only IBattleInfoCallback and battle::Unit should be used,
@ -70,8 +72,9 @@ std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
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)
SpellID creatureSpellToCast = cb->getBattle(battleID)->getRandomCastedSpell(CRandomGenerator::getDefault(), stack);
if(stack->canCast() && creatureSpellToCast != SpellID::NONE)
{
const CSpell * spell = creatureSpellToCast.toSpell();
@ -146,7 +149,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
(int)bestAttack.from,
(int)bestAttack.attack.attacker->getPosition().hex,
bestAttack.attack.chargeDistance,
bestAttack.attack.attacker->speed(0, true),
bestAttack.attack.attacker->getMovementRange(0),
bestAttack.defenderDamageReduce,
bestAttack.attackerDamageReduce,
score
@ -224,7 +227,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
}
}
return BattleAction::makeDefend(stack);
return stack->waited() ? BattleAction::makeDefend(stack) : BattleAction::makeWait(stack);
}
uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock> start)
@ -349,10 +352,11 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
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);
});
for (auto const & s : VLC->spellh->objects)
if (s->canBeCast(cb->getBattle(battleID).get(), spells::Mode::HERO, hero))
possibleSpells.push_back(s.get());
LOGFL("I can cast %d spells.", possibleSpells.size());
vstd::erase_if(possibleSpells, [](const CSpell *s)
@ -427,33 +431,36 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
state->nextTurn(unit->unitId());
PotentialTargets pt(unit, damageCache, state);
PotentialTargets potentialTargets(unit, damageCache, state);
if(!pt.possibleAttacks.empty())
if(!potentialTargets.possibleAttacks.empty())
{
AttackPossibility ap = pt.bestAction();
AttackPossibility attackPossibility = potentialTargets.bestAction();
auto swb = state->getForUpdate(unit->unitId());
*swb = *ap.attackerState;
auto stackWithBonuses = state->getForUpdate(unit->unitId());
*stackWithBonuses = *attackPossibility.attackerState;
if(ap.defenderDamageReduce > 0)
swb->removeUnitBonus(Bonus::UntilAttack);
if(ap.attackerDamageReduce > 0)
swb->removeUnitBonus(Bonus::UntilBeingAttacked);
for(auto affected : ap.affectedUnits)
if(attackPossibility.defenderDamageReduce > 0)
{
swb = state->getForUpdate(affected->unitId());
*swb = *affected;
stackWithBonuses->removeUnitBonus(Bonus::UntilAttack);
stackWithBonuses->removeUnitBonus(Bonus::UntilOwnAttack);
}
if(attackPossibility.attackerDamageReduce > 0)
stackWithBonuses->removeUnitBonus(Bonus::UntilBeingAttacked);
if(ap.defenderDamageReduce > 0)
swb->removeUnitBonus(Bonus::UntilBeingAttacked);
if(ap.attackerDamageReduce > 0 && ap.attack.defender->unitId() == affected->unitId())
swb->removeUnitBonus(Bonus::UntilAttack);
for(auto affected : attackPossibility.affectedUnits)
{
stackWithBonuses = state->getForUpdate(affected->unitId());
*stackWithBonuses = *affected;
if(attackPossibility.defenderDamageReduce > 0)
stackWithBonuses->removeUnitBonus(Bonus::UntilBeingAttacked);
if(attackPossibility.attackerDamageReduce > 0 && attackPossibility.attack.defender->unitId() == affected->unitId())
stackWithBonuses->removeUnitBonus(Bonus::UntilAttack);
}
}
auto bav = pt.bestActionValue();
auto bav = potentialTargets.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)
@ -549,7 +556,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
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();
return !original || u->getMovementRange() != original->getMovementRange();
});
DamageCache safeCopy = damageCache;
@ -605,7 +612,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
if(ourUnit * goodEffect == 1)
{
if(ourUnit && goodEffect && (unit->isClone() || unit->isGhost() || !unit->unitSlot().validSlot()))
if(ourUnit && goodEffect && (unit->isClone() || unit->isGhost()))
continue;
ps.value += dpsReduce * scoreEvaluator.getPositiveEffectMultiplier();

View File

@ -258,7 +258,9 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
updateReachabilityMap(hb);
if(result.bestAttack.attack.shooting && hb->battleHasShootingPenalty(activeStack, result.bestAttack.dest))
if(result.bestAttack.attack.shooting
&& !activeStack->waited()
&& hb->battleHasShootingPenalty(activeStack, result.bestAttack.dest))
{
if(!canBeHitThisTurn(result.bestAttack))
return result; // lets wait
@ -268,7 +270,7 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
{
float score = evaluateExchange(ap, 0, targets, damageCache, hb);
if(score > result.score || (score == result.score && result.wait))
if(score > result.score || (vstd::isAlmostEqual(score, result.score) && result.wait))
{
result.score = score;
result.bestAttack = ap;
@ -295,7 +297,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
if(targets.unreachableEnemies.empty())
return result;
auto speed = activeStack->speed();
auto speed = activeStack->getMovementRange();
if(speed == 0)
return result;
@ -322,7 +324,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
auto turnsToRich = (distance - 1) / speed + 1;
auto hexes = closestStack->getSurroundingHexes();
auto enemySpeed = closestStack->speed();
auto enemySpeed = closestStack->getMovementRange();
auto speedRatio = speed / static_cast<float>(enemySpeed);
auto multiplier = speedRatio > 1 ? 1 : speedRatio;
@ -481,11 +483,6 @@ float BattleExchangeEvaluator::evaluateExchange(
DamageCache & damageCache,
std::shared_ptr<HypotheticBattle> hb)
{
if(ap.from.hex == 127)
{
logAi->trace("x");
}
BattleScore score = calculateExchange(ap, turn, targets, damageCache, hb);
#if BATTLE_TRACE_LEVEL >= 1
@ -687,11 +684,6 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
for(auto hex : hexes)
reachabilityMap[hex] = getOneTurnReachableUnits(turn, hex);
if(!ap.attack.shooting)
{
v.adjustPositions(melleeAttackers, ap, reachabilityMap);
}
#if BATTLE_TRACE_LEVEL>=1
logAi->trace("Exchange score: enemy: %2f, our -%2f", v.getScore().enemyDamageReduce, v.getScore().ourDamageReduce);
#endif
@ -699,69 +691,6 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
return v.getScore();
}
void BattleExchangeVariant::adjustPositions(
std::vector<const battle::Unit*> attackers,
const AttackPossibility & ap,
std::map<BattleHex, battle::Units> & reachabilityMap)
{
auto hexes = ap.attack.defender->getSurroundingHexes();
boost::sort(attackers, [&](const battle::Unit * u1, const battle::Unit * u2) -> bool
{
if(attackerValue[u1->unitId()].isRetalitated && !attackerValue[u2->unitId()].isRetalitated)
return true;
if(attackerValue[u2->unitId()].isRetalitated && !attackerValue[u1->unitId()].isRetalitated)
return false;
return attackerValue[u1->unitId()].value > attackerValue[u2->unitId()].value;
});
vstd::erase_if_present(hexes, ap.from);
vstd::erase_if_present(hexes, ap.attack.attacker->occupiedHex(ap.attack.attackerPos));
float notRealizedDamage = 0;
for(auto unit : attackers)
{
if(unit->unitId() == ap.attack.attacker->unitId())
continue;
if(!vstd::contains_if(hexes, [&](BattleHex h) -> bool
{
return vstd::contains(reachabilityMap[h], unit);
}))
{
notRealizedDamage += attackerValue[unit->unitId()].value;
continue;
}
auto desiredPosition = vstd::minElementByFun(hexes, [&](BattleHex h) -> float
{
auto score = vstd::contains(reachabilityMap[h], unit)
? reachabilityMap[h].size()
: 0;
if(unit->doubleWide())
{
auto backHex = unit->occupiedHex(h);
if(vstd::contains(hexes, backHex))
score += reachabilityMap[backHex].size();
}
return score;
});
hexes.erase(desiredPosition);
}
if(notRealizedDamage > ap.attackValue() && notRealizedDamage > attackerValue[ap.attack.attacker->unitId()].value)
{
dpsScore = BattleScore(EvaluationResult::INEFFECTIVE_SCORE, 0);
}
}
bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap)
{
for(auto pos : ap.attack.attacker->getSurroundingHexes())
@ -824,7 +753,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
continue;
}
auto unitSpeed = unit->speed(turn);
auto unitSpeed = unit->getMovementRange(turn);
auto radius = unitSpeed * (turn + 1);
ReachabilityInfo unitReachability = vstd::getOrCompute(
@ -887,14 +816,15 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
continue;
auto blockedUnitDamage = unit->getMinDamage(hb.battleCanShoot(unit)) * unit->getCount();
auto ratio = blockedUnitDamage / (blockedUnitDamage + activeUnitDamage);
float ratio = blockedUnitDamage / (float)(blockedUnitDamage + activeUnitDamage + 0.01);
auto unitReachability = turnBattle.getReachability(unit);
auto unitSpeed = unit->getMovementRange(turn); // Cached value, to avoid performance hit
for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
{
bool enemyUnit = false;
bool reachable = unitReachability.distances[hex] <= unit->speed(turn);
bool reachable = unitReachability.distances[hex] <= unitSpeed;
if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
{
@ -906,14 +836,14 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
for(BattleHex neighbor : hex.neighbouringTiles())
{
reachable = unitReachability.distances[neighbor] <= unit->speed(turn);
reachable = unitReachability.distances[neighbor] <= unitSpeed;
if(reachable) break;
}
}
}
if(!reachable && vstd::contains(reachabilityMap[hex], unit))
if(!reachable && std::count(reachabilityMap[hex].begin(), reachabilityMap[hex].end(), unit) > 1)
{
blockingScore += ratio * (enemyUnit ? BLOCKING_OWN_ATTACK_PENALTY : BLOCKING_OWN_MOVE_PENALTY);
}

View File

@ -106,11 +106,6 @@ public:
const BattleScore & getScore() const { return dpsScore; }
void adjustPositions(
std::vector<const battle::Unit *> attackers,
const AttackPossibility & ap,
std::map<BattleHex, battle::Units> & reachabilityMap);
private:
BattleScore dpsScore;
std::map<uint32_t, AttackerValue> attackerValue;

View File

@ -2,7 +2,6 @@ set(battleAI_SRCS
AttackPossibility.cpp
BattleAI.cpp
BattleEvaluator.cpp
common.cpp
EnemyInfo.cpp
PossibleSpellcast.cpp
PotentialTargets.cpp
@ -17,7 +16,6 @@ set(battleAI_HEADERS
AttackPossibility.h
BattleAI.h
BattleEvaluator.h
common.h
EnemyInfo.h
PotentialTargets.h
PossibleSpellcast.h
@ -26,12 +24,12 @@ set(battleAI_HEADERS
BattleExchangeVariant.h
)
if(NOT ENABLE_STATIC_AI_LIBS)
if(NOT ENABLE_STATIC_LIBS)
list(APPEND battleAI_SRCS main.cpp StdInc.cpp)
endif()
assign_source_group(${battleAI_SRCS} ${battleAI_HEADERS})
if(ENABLE_STATIC_AI_LIBS)
if(ENABLE_STATIC_LIBS)
add_library(BattleAI STATIC ${battleAI_SRCS} ${battleAI_HEADERS})
else()
add_library(BattleAI SHARED ${battleAI_SRCS} ${battleAI_HEADERS})
@ -39,7 +37,7 @@ else()
endif()
target_include_directories(BattleAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(BattleAI PRIVATE ${VCMI_LIB_TARGET} TBB::tbb)
target_link_libraries(BattleAI PRIVATE vcmi TBB::tbb)
vcmi_set_output_dir(BattleAI "AI")
enable_pch(BattleAI)

View File

@ -134,7 +134,7 @@ SlotID StackWithBonuses::unitSlot() const
TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const CSelector & limit,
const CBonusSystemNode * root, const std::string & cachingStr) const
{
TBonusListPtr ret = std::make_shared<BonusList>();
auto ret = std::make_shared<BonusList>();
TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, root, cachingStr);
vstd::copy_if(*originalList, std::back_inserter(*ret), [this](const std::shared_ptr<Bonus> & b)
@ -298,7 +298,7 @@ std::shared_ptr<StackWithBonuses> HypotheticBattle::getForUpdate(uint32_t id)
}
}
battle::Units HypotheticBattle::getUnitsIf(battle::UnitFilter predicate) const
battle::Units HypotheticBattle::getUnitsIf(const battle::UnitFilter & predicate) const
{
battle::Units proxyed = BattleProxy::getUnitsIf(predicate);
@ -356,7 +356,7 @@ void HypotheticBattle::addUnit(uint32_t id, const JsonNode & data)
{
battle::UnitInfo info;
info.load(id, data);
std::shared_ptr<StackWithBonuses> newUnit = std::make_shared<StackWithBonuses>(this, info);
auto newUnit = std::make_shared<StackWithBonuses>(this, info);
stackStates[newUnit->unitId()] = newUnit;
}

View File

@ -114,7 +114,7 @@ public:
int32_t getActiveStackID() const override;
battle::Units getUnitsIf(battle::UnitFilter predicate) const override;
battle::Units getUnitsIf(const battle::UnitFilter & predicate) const override;
void nextRound() override;
void nextTurn(uint32_t unitId) override;

View File

@ -1,26 +0,0 @@
/*
* common.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
class CBattleCallback;
template<typename Key, typename Val, typename Val2>
const Val getValOr(const std::map<Key, Val> &Map, const Key &key, const Val2 defaultValue)
{
//returning references here won't work: defaultValue must be converted into Val, creating temporary
auto i = Map.find(key);
if(i != Map.end())
return i->second;
else
return defaultValue;
}
void setCbc(std::shared_ptr<CBattleCallback> cb);
std::shared_ptr<CBattleCallback> getCbc();

View File

@ -15,7 +15,7 @@
#define strcpy_s(a, b, c) strncpy(a, c, b)
#endif
static const char *g_cszAiName = "Battle AI";
static const char * const g_cszAiName = "Battle AI";
extern "C" DLL_EXPORT int GetGlobalAiVersion()
{

View File

@ -14,11 +14,11 @@
#include "../../lib/CStack.h"
#include "../../lib/battle/BattleAction.h"
void CEmptyAI::saveGame(BinarySerializer & h, const int version)
void CEmptyAI::saveGame(BinarySerializer & h)
{
}
void CEmptyAI::loadGame(BinaryDeserializer & h, const int version)
void CEmptyAI::loadGame(BinaryDeserializer & h)
{
}

View File

@ -19,8 +19,8 @@ class CEmptyAI : public CGlobalAI
std::shared_ptr<CCallback> cb;
public:
virtual void saveGame(BinarySerializer & h, const int version) override;
virtual void loadGame(BinaryDeserializer & h, const int version) override;
void saveGame(BinarySerializer & h) override;
void loadGame(BinaryDeserializer & h) override;
void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
void yourTurn(QueryID queryID) override;

View File

@ -8,12 +8,12 @@ set(emptyAI_HEADERS
CEmptyAI.h
)
if(NOT ENABLE_STATIC_AI_LIBS)
if(NOT ENABLE_STATIC_LIBS)
list(APPEND emptyAI_SRCS main.cpp StdInc.cpp)
endif()
assign_source_group(${emptyAI_SRCS} ${emptyAI_HEADERS})
if(ENABLE_STATIC_AI_LIBS)
if(ENABLE_STATIC_LIBS)
add_library(EmptyAI STATIC ${emptyAI_SRCS} ${emptyAI_HEADERS})
else()
add_library(EmptyAI SHARED ${emptyAI_SRCS} ${emptyAI_HEADERS})
@ -21,7 +21,7 @@ else()
endif()
target_include_directories(EmptyAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(EmptyAI PRIVATE ${VCMI_LIB_TARGET})
target_link_libraries(EmptyAI PRIVATE vcmi)
vcmi_set_output_dir(EmptyAI "AI")
enable_pch(EmptyAI)

View File

@ -11,7 +11,6 @@
#include "CEmptyAI.h"
std::set<CGlobalAI*> ais;
extern "C" DLL_EXPORT int GetGlobalAiVersion()
{
return AI_INTERFACE_VER;

View File

@ -9,6 +9,7 @@
*/
#include "StdInc.h"
#include "../../lib/ArtifactUtils.h"
#include "../../lib/UnlockGuard.h"
#include "../../lib/mapObjects/MapObjects.h"
#include "../../lib/mapObjects/ObjectTemplate.h"
@ -63,7 +64,7 @@ struct SetGlobalState
};
#define SET_GLOBAL_STATE(ai) SetGlobalState _hlpSetState(ai);
#define SET_GLOBAL_STATE(ai) SetGlobalState _hlpSetState(ai)
#define NET_EVENT_HANDLER SET_GLOBAL_STATE(this)
#define MAKING_TURN SET_GLOBAL_STATE(this)
@ -100,7 +101,7 @@ void AIGateway::heroMoved(const TryMoveHero & details, bool verbose)
if(!hero)
validateObject(details.id); //enemy hero may have left visible area
const int3 from = hero ? hero->convertToVisitablePos(details.start) : (details.start - int3(0,1,0));;
const int3 from = hero ? hero->convertToVisitablePos(details.start) : (details.start - int3(0,1,0));
const int3 to = hero ? hero->convertToVisitablePos(details.end) : (details.end - int3(0,1,0));
const CGObjectInstance * o1 = vstd::frontOrNull(cb->getVisitableObjs(from, verbose));
@ -420,14 +421,14 @@ void AIGateway::requestRealized(PackageApplied * pa)
NET_EVENT_HANDLER;
if(status.haveTurn())
{
if(pa->packType == typeList.getTypeID<EndTurn>())
if(pa->packType == CTypeList::getInstance().getTypeID<EndTurn>(nullptr))
{
if(pa->result)
status.madeTurn();
}
}
if(pa->packType == typeList.getTypeID<QueryReply>())
if(pa->packType == CTypeList::getInstance().getTypeID<QueryReply>(nullptr))
{
status.receivedAnswerConfirmation(pa->requestID, pa->result);
}
@ -480,7 +481,7 @@ void AIGateway::objectPropertyChanged(const SetObjectProperty * sop)
NET_EVENT_HANDLER;
if(sop->what == ObjProperty::OWNER)
{
auto relations = myCb->getPlayerRelations(playerID, (PlayerColor)sop->val);
auto relations = myCb->getPlayerRelations(playerID, sop->identifier.as<PlayerColor>());
auto obj = myCb->getObj(sop->id, false);
if(!nullkiller) // crash protection
@ -585,11 +586,18 @@ void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, s
requestActionASAP([=]()
{
int sel = 0;
if(hPtr.validAndSet())
{
std::unique_lock<std::mutex> lockGuard(nullkiller->aiStateMutex);
nullkiller->heroManager->update();
answerQuery(queryID, nullkiller->heroManager->selectBestSkill(hPtr, skills));
sel = nullkiller->heroManager->selectBestSkill(hPtr, skills);
}
answerQuery(queryID, sel);
});
}
@ -624,7 +632,8 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
auto topObj = objects.front()->id == hero->id ? objects.back() : objects.front();
auto objType = topObj->ID; // top object should be our hero
auto goalObjectID = nullkiller->getTargetObject();
auto ratio = (float)nullkiller->dangerEvaluator->evaluateDanger(target, hero.get()) / (float)hero->getTotalStrength();
auto danger = nullkiller->dangerEvaluator->evaluateDanger(target, hero.get());
auto ratio = static_cast<float>(danger) / hero->getTotalStrength();
answer = topObj->id == goalObjectID; // no if we do not aim to visit this object
logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), hero.name, ratio);
@ -640,7 +649,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
}
else if(objType == Obj::ARTIFACT || objType == Obj::RESOURCE)
{
bool dangerUnknown = ratio == 0;
bool dangerUnknown = danger == 0;
bool dangerTooHigh = ratio > (1 / SAFE_ATTACK_CONSTANT);
answer = !dangerUnknown && !dangerTooHigh;
@ -660,14 +669,18 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
if(selection) //select from multiple components -> take the last one (they're indexed [1-size])
sel = components.size();
// TODO: Find better way to understand it is Chest of Treasures
if(hero.validAndSet()
&& components.size() == 2
&& components.front().id == Component::EComponentType::RESOURCE
&& (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN
|| nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE))
{
sel = 1; // for now lets pick gold from a chest.
std::unique_lock<std::mutex> mxLock(nullkiller->aiStateMutex);
// TODO: Find better way to understand it is Chest of Treasures
if(hero.validAndSet()
&& components.size() == 2
&& components.front().type == ComponentType::RESOURCE
&& (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN
|| nullkiller->buildAnalyzer->isGoldPreasureHigh()))
{
sel = 1;
}
}
answerQuery(askID, sel);
@ -746,27 +759,25 @@ void AIGateway::showMapObjectSelectDialog(QueryID askID, const Component & icon,
requestActionASAP([=](){ answerQuery(askID, selectedObject.getNum()); });
}
void AIGateway::saveGame(BinarySerializer & h, const int version)
void AIGateway::saveGame(BinarySerializer & h)
{
LOG_TRACE_PARAMS(logAi, "version '%i'", version);
NET_EVENT_HANDLER;
nullkiller->memory->removeInvisibleObjects(myCb.get());
CAdventureAI::saveGame(h, version);
serializeInternal(h, version);
CAdventureAI::saveGame(h);
serializeInternal(h);
}
void AIGateway::loadGame(BinaryDeserializer & h, const int version)
void AIGateway::loadGame(BinaryDeserializer & h)
{
LOG_TRACE_PARAMS(logAi, "version '%i'", version);
//NET_EVENT_HANDLER;
#if 0
//disabled due to issue 2890
registerGoals(h);
#endif // 0
CAdventureAI::loadGame(h, version);
serializeInternal(h, version);
CAdventureAI::loadGame(h);
serializeInternal(h);
}
bool AIGateway::makePossibleUpgrades(const CArmedInstance * obj)
@ -858,6 +869,8 @@ void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h
{
makePossibleUpgrades(h.get());
std::unique_lock<std::mutex> lockGuard(nullkiller->aiStateMutex);
if(!h->visitedTown->garrisonHero || !nullkiller->isHeroLocked(h->visitedTown->garrisonHero))
moveCreaturesToHero(h->visitedTown);
@ -995,21 +1008,21 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
for(auto p : h->artifactsWorn)
{
if(p.second.artifact)
allArtifacts.push_back(ArtifactLocation(h, p.first));
allArtifacts.push_back(ArtifactLocation(h->id, p.first));
}
}
for(auto slot : h->artifactsInBackpack)
allArtifacts.push_back(ArtifactLocation(h, h->getArtPos(slot.artifact)));
allArtifacts.push_back(ArtifactLocation(h->id, h->getArtPos(slot.artifact)));
if(otherh)
{
for(auto p : otherh->artifactsWorn)
{
if(p.second.artifact)
allArtifacts.push_back(ArtifactLocation(otherh, p.first));
allArtifacts.push_back(ArtifactLocation(otherh->id, p.first));
}
for(auto slot : otherh->artifactsInBackpack)
allArtifacts.push_back(ArtifactLocation(otherh, otherh->getArtPos(slot.artifact)));
allArtifacts.push_back(ArtifactLocation(otherh->id, otherh->getArtPos(slot.artifact)));
}
//we give stuff to one hero or another, depending on giveStuffToFirstHero
@ -1021,13 +1034,13 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
for(auto location : allArtifacts)
{
if(location.relatedObj() == target && location.slot < ArtifactPosition::AFTER_LAST)
if(location.artHolder == target->id && ArtifactUtils::isSlotEquipment(location.slot))
continue; //don't reequip artifact we already wear
if(location.slot == ArtifactPosition::MACH4) // don't attempt to move catapult
continue;
auto s = location.getSlot();
auto s = cb->getHero(location.artHolder)->getSlot(location.slot);
if(!s || s->locked) //we can't move locks
continue;
auto artifact = s->artifact;
@ -1038,9 +1051,9 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
bool emptySlotFound = false;
for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
{
ArtifactLocation destLocation(target, slot);
if(target->isPositionFree(slot) && artifact->canBePutAt(destLocation, true)) //combined artifacts are not always allowed to move
if(target->isPositionFree(slot) && artifact->canBePutAt(target, slot, true)) //combined artifacts are not always allowed to move
{
ArtifactLocation destLocation(target->id, slot);
cb->swapArtifacts(location, destLocation); //just put into empty slot
emptySlotFound = true;
changeMade = true;
@ -1054,11 +1067,11 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
auto otherSlot = target->getSlot(slot);
if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one
{
ArtifactLocation destLocation(target, slot);
//if that artifact is better than what we have, pick it
if(compareArtifacts(artifact, otherSlot->artifact) && artifact->canBePutAt(destLocation, true)) //combined artifacts are not always allowed to move
if(compareArtifacts(artifact, otherSlot->artifact) && artifact->canBePutAt(target, slot, true)) //combined artifacts are not always allowed to move
{
cb->swapArtifacts(location, ArtifactLocation(target, target->getArtPos(otherSlot->artifact)));
ArtifactLocation destLocation(target->id, slot);
cb->swapArtifacts(location, ArtifactLocation(target->id, target->getArtPos(otherSlot->artifact)));
changeMade = true;
break;
}
@ -1119,15 +1132,6 @@ void AIGateway::battleEnd(const BattleID & battleID, const BattleResult * br, Qu
logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.toString(), (won ? "won" : "lost"), battlename);
battlename.clear();
if (queryID != QueryID::NONE)
{
status.addQuery(queryID, "Combat result dialog");
const int confirmAction = 0;
requestActionASAP([=]()
{
answerQuery(queryID, confirmAction);
});
}
CAdventureAI::battleEnd(battleID, br, queryID);
}
@ -1403,7 +1407,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
int accquiredResources = 0;
if(const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(g.objid), false))
{
if(const IMarket * m = IMarket::castFrom(obj, false))
if(const auto * m = dynamic_cast<const IMarket*>(obj))
{
auto freeRes = cb->getResourceAmount(); //trade only resources which are not reserved
for(auto it = ResourceSet::nziterator(freeRes); it.valid(); it++)
@ -1412,13 +1416,14 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
if(res.getNum() == g.resID) //sell any other resource
continue;
int toGive, toGet;
int toGive;
int toGet;
m->getOffer(res, g.resID, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
toGive = static_cast<int>(toGive * (it->resVal / toGive)); //round down
//TODO trade only as much as needed
if (toGive) //don't try to sell 0 resources
{
cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, g.resID, toGive);
cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
accquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, accquiredResources, g.resID, obj->getObjectName());
}

View File

@ -62,7 +62,7 @@ public:
void heroVisit(const CGObjectInstance * obj, bool started);
template<typename Handler> void serialize(Handler & h, const int version)
template<typename Handler> void serialize(Handler & h)
{
h & battle;
h & remainingQueries;
@ -119,8 +119,8 @@ public:
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(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 saveGame(BinarySerializer & h, const int version) override; //saving
void loadGame(BinaryDeserializer & h, const int version) override; //loading
void saveGame(BinarySerializer & h) override; //saving
void loadGame(BinaryDeserializer & h) override; //loading
void finish() override;
void availableCreaturesChanged(const CGDwelling * town) override;
@ -203,7 +203,7 @@ public:
//special function that can be called ONLY from game events handling thread and will send request ASAP
void requestActionASAP(std::function<void()> whatToDo);
template<typename Handler> void serializeInternal(Handler & h, const int version)
template<typename Handler> void serializeInternal(Handler & h)
{
h & nullkiller->memory->knownTeleportChannels;
h & nullkiller->memory->knownSubterraneanGates;

View File

@ -276,12 +276,10 @@ creInfo infoFromDC(const dwellingContent & dc)
ci.creID = dc.second.size() ? dc.second.back() : CreatureID(-1); //should never be accessed
if (ci.creID != CreatureID::NONE)
{
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.creID.toCreature()->getLevel(); //this is creature tier, while tryRealize expects dwelling level. Ignore.
}
else
{
ci.cre = nullptr;
ci.level = 0;
}
return ci;
@ -439,7 +437,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
case Obj::MAGIC_WELL:
return h->mana < h->manaLimit();
case Obj::PRISON:
return ai->cb->getHeroesInfo().size() < VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP);
return !ai->heroManager->heroCapReached();
case Obj::TAVERN:
case Obj::EYE_OF_MAGI:
case Obj::BOAT:

View File

@ -60,7 +60,9 @@ struct creInfo;
class AIGateway;
class Nullkiller;
const int GOLD_MINE_PRODUCTION = 1000, WOOD_ORE_MINE_PRODUCTION = 2, RESOURCE_MINE_PRODUCTION = 1;
const int GOLD_MINE_PRODUCTION = 1000;
const int WOOD_ORE_MINE_PRODUCTION = 2;
const int RESOURCE_MINE_PRODUCTION = 1;
const int ACTUAL_RESOURCE_COUNT = 7;
const int ALLOWED_ROAMING_HEROES = 8;
@ -113,7 +115,7 @@ public:
bool validAndSet() const;
template<typename Handler> void serialize(Handler & h, const int version)
template<typename Handler> void serialize(Handler & h)
{
h & this->h;
h & hid;
@ -145,7 +147,7 @@ struct ObjectIdRef
bool operator<(const ObjectIdRef & rhs) const;
template<typename Handler> void serialize(Handler & h, const int version)
template<typename Handler> void serialize(Handler & h)
{
h & id;
}
@ -161,7 +163,6 @@ struct creInfo
{
int count;
CreatureID creID;
const Creature * cre;
int level;
};
creInfo infoFromDC(const dwellingContent & dc);

View File

@ -63,9 +63,9 @@ std::vector<SlotInfo> ArmyManager::toSlotInfo(std::vector<creInfo> army) const
{
SlotInfo slot;
slot.creature = VLC->creh->objects[i.cre->getId()];
slot.creature = i.creID.toCreature();
slot.count = i.count;
slot.power = evaluateStackPower(i.cre, i.count);
slot.power = evaluateStackPower(i.creID.toCreature(), i.count);
result.push_back(slot);
}
@ -117,7 +117,7 @@ std::vector<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<Slot
if(left.creature->getLevel() != right.creature->getLevel())
return left.creature->getLevel() < right.creature->getLevel();
return left.creature->speed() > right.creature->speed();
return left.creature->getMovementRange() > right.creature->getMovementRange();
});
return weakest;
@ -128,7 +128,7 @@ class TemporaryArmy : public CArmedInstance
public:
void armyChanged() override {}
TemporaryArmy()
:CArmedInstance(true)
:CArmedInstance(nullptr, true)
{
}
};
@ -259,7 +259,7 @@ std::shared_ptr<CCreatureSet> ArmyManager::getArmyAvailableToBuyAsCCreatureSet(
if(!ci.count || ci.creID == CreatureID::NONE)
continue;
vstd::amin(ci.count, availableRes / ci.cre->getFullRecruitCost()); //max count we can afford
vstd::amin(ci.count, availableRes / ci.creID.toCreature()->getFullRecruitCost()); //max count we can afford
if(!ci.count)
continue;
@ -270,7 +270,7 @@ std::shared_ptr<CCreatureSet> ArmyManager::getArmyAvailableToBuyAsCCreatureSet(
break;
army->setCreature(dst, ci.creID, ci.count);
availableRes -= ci.cre->getFullRecruitCost() * ci.count;
availableRes -= ci.creID.toCreature()->getFullRecruitCost() * ci.count;
}
return army;
@ -287,7 +287,7 @@ ui64 ArmyManager::howManyReinforcementsCanBuy(
for(const creInfo & ci : army)
{
aivalue += ci.count * ci.cre->getAIValue();
aivalue += ci.count * ci.creID.toCreature()->getAIValue();
}
return aivalue;
@ -320,7 +320,7 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
if(i < GameConstants::CREATURES_PER_TOWN && countGrowth)
{
ci.count += town ? town->creatureGrowth(i) : ci.cre->getGrowth();
ci.count += town ? town->creatureGrowth(i) : ci.creID.toCreature()->getGrowth();
}
if(!ci.count) continue;
@ -334,13 +334,13 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
freeHeroSlots--; //new slot will be occupied
}
vstd::amin(ci.count, availableRes / ci.cre->getFullRecruitCost()); //max count we can afford
vstd::amin(ci.count, availableRes / ci.creID.toCreature()->getFullRecruitCost()); //max count we can afford
if(!ci.count) continue;
ci.level = i; //this is important for Dungeon Summoning Portal
creaturesInDwellings.push_back(ci);
availableRes -= ci.cre->getFullRecruitCost() * ci.count;
availableRes -= ci.creID.toCreature()->getFullRecruitCost() * ci.count;
}
return creaturesInDwellings;

View File

@ -120,6 +120,11 @@ TResources BuildAnalyzer::getTotalResourcesRequired() const
return result;
}
bool BuildAnalyzer::isGoldPreasureHigh() const
{
return goldPreasure > ai->settings->getMaxGoldPreasure();
}
void BuildAnalyzer::update()
{
logAi->trace("Start analysing build");
@ -318,7 +323,7 @@ bool BuildAnalyzer::hasAnyBuilding(int32_t alignment, BuildingID bid) const
{
for(auto tdi : developmentInfos)
{
if(tdi.town->subID == alignment && tdi.town->hasBuilt(bid))
if(tdi.town->getFaction() == alignment && tdi.town->hasBuilt(bid))
return true;
}

View File

@ -96,6 +96,7 @@ public:
const std::vector<TownDevelopmentInfo> & getDevelopmentInfo() const { return developmentInfos; }
TResources getDailyIncome() const { return dailyIncome; }
float getGoldPreasure() const { return goldPreasure; }
bool isGoldPreasureHigh() const;
bool hasAnyBuilding(int32_t alignment, BuildingID bid) const;
private:

View File

@ -11,11 +11,12 @@
#include "DangerHitMapAnalyzer.h"
#include "../Engine/Nullkiller.h"
#include "../../../lib/CRandomGenerator.h"
namespace NKAI
{
HitMapInfo HitMapInfo::NoThreat;
const HitMapInfo HitMapInfo::NoThreat;
double HitMapInfo::value() const
{
@ -165,7 +166,7 @@ void DangerHitMapAnalyzer::calculateTileOwners()
auto addTownHero = [&](const CGTownInstance * town)
{
auto townHero = new CGHeroInstance();
auto townHero = new CGHeroInstance(town->cb);
CRandomGenerator rng;
auto visitablePos = town->visitablePos();
@ -225,7 +226,7 @@ void DangerHitMapAnalyzer::calculateTileOwners()
}
}
if(ourDistance == enemyDistance)
if(vstd::isAlmostEqual(ourDistance, enemyDistance))
{
hitMap[pos.x][pos.y][pos.z].closestTown = nullptr;
}
@ -265,8 +266,9 @@ uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath &
{
int3 tile = path.targetTile();
int turn = path.turn();
const HitMapNode & info = hitMap[tile.x][tile.y][tile.z];
const auto& info = getTileThreat(tile);
return (info.fastestDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.fastestDanger.danger))
|| (info.maximumDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.maximumDanger.danger));
}
@ -280,13 +282,9 @@ const HitMapNode & DangerHitMapAnalyzer::getObjectThreat(const CGObjectInstance
const HitMapNode & DangerHitMapAnalyzer::getTileThreat(const int3 & tile) const
{
const HitMapNode & info = hitMap[tile.x][tile.y][tile.z];
return info;
return hitMap[tile.x][tile.y][tile.z];
}
const std::set<const CGObjectInstance *> empty = {};
std::set<const CGObjectInstance *> DangerHitMapAnalyzer::getOneTurnAccessibleObjects(const CGHeroInstance * enemy) const
{
std::set<const CGObjectInstance *> result;

View File

@ -18,7 +18,7 @@ struct AIPath;
struct HitMapInfo
{
static HitMapInfo NoThreat;
static const HitMapInfo NoThreat;
uint64_t danger;
uint8_t turn;

View File

@ -17,7 +17,7 @@
namespace NKAI
{
SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluator(
const SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluator(
{
std::make_shared<SecondarySkillScoreMap>(
std::map<SecondarySkill, float>
@ -46,7 +46,7 @@ SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluato
std::make_shared<AtLeastOneMagicRule>()
});
SecondarySkillEvaluator HeroManager::scountSkillsScores = SecondarySkillEvaluator(
const SecondarySkillEvaluator HeroManager::scountSkillsScores = SecondarySkillEvaluator(
{
std::make_shared<SecondarySkillScoreMap>(
std::map<SecondarySkill, float>
@ -187,7 +187,9 @@ bool HeroManager::heroCapReached() const
int heroCount = cb->getHeroCount(ai->playerID, includeGarnisoned);
return heroCount >= ALLOWED_ROAMING_HEROES
|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP);
|| heroCount >= ai->settings->getMaxRoamingHeroes()
|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)
|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP);
}
float HeroManager::getMagicStrength(const CGHeroInstance * hero) const
@ -331,7 +333,7 @@ void WisdomRule::evaluateScore(const CGHeroInstance * hero, SecondarySkill skill
score += 1.5;
}
std::vector<SecondarySkill> AtLeastOneMagicRule::magicSchools = {
const std::vector<SecondarySkill> AtLeastOneMagicRule::magicSchools = {
SecondarySkill::AIR_MAGIC,
SecondarySkill::EARTH_MAGIC,
SecondarySkill::FIRE_MAGIC,

View File

@ -58,8 +58,8 @@ public:
class DLL_EXPORT HeroManager : public IHeroManager
{
private:
static SecondarySkillEvaluator wariorSkillsScores;
static SecondarySkillEvaluator scountSkillsScores;
static const SecondarySkillEvaluator wariorSkillsScores;
static const SecondarySkillEvaluator scountSkillsScores;
CCallback * cb; //this is enough, but we downcast from CCallback
const Nullkiller * ai;
@ -114,7 +114,7 @@ public:
class AtLeastOneMagicRule : public ISecondarySkillRule
{
private:
static std::vector<SecondarySkill> magicSchools;
static const std::vector<SecondarySkill> magicSchools;
public:
void evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const override;

View File

@ -47,13 +47,13 @@ Goals::TGoalVec BuildingBehavior::decompose() const
totalDevelopmentCost.toString());
auto & developmentInfos = ai->nullkiller->buildAnalyzer->getDevelopmentInfo();
auto goldPreasure = ai->nullkiller->buildAnalyzer->getGoldPreasure();
auto isGoldPreasureLow = !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh();
for(auto & developmentInfo : developmentInfos)
{
for(auto & buildingInfo : developmentInfo.toBuild)
{
if(goldPreasure < MAX_GOLD_PEASURE || buildingInfo.dailyIncome[EGameResID::GOLD] > 0)
if(isGoldPreasureLow || buildingInfo.dailyIncome[EGameResID::GOLD] > 0)
{
if(buildingInfo.notEnoughRes)
{

View File

@ -25,9 +25,9 @@ namespace Goals
{
}
virtual Goals::TGoalVec decompose() const override;
virtual std::string toString() const override;
virtual bool operator==(const BuildingBehavior & other) const override
Goals::TGoalVec decompose() const override;
std::string toString() const override;
bool operator==(const BuildingBehavior & other) const override
{
return true;
}

View File

@ -46,8 +46,7 @@ Goals::TGoalVec BuyArmyBehavior::decompose() const
for(const CGHeroInstance * targetHero : heroes)
{
if(ai->nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE
&& !town->hasBuilt(BuildingID::CITY_HALL))
if(ai->nullkiller->buildAnalyzer->isGoldPreasureHigh() && !town->hasBuilt(BuildingID::CITY_HALL))
{
continue;
}

View File

@ -24,9 +24,9 @@ namespace Goals
{
}
virtual Goals::TGoalVec decompose() const override;
virtual std::string toString() const override;
virtual bool operator==(const BuyArmyBehavior & other) const override
Goals::TGoalVec decompose() const override;
std::string toString() const override;
bool operator==(const BuyArmyBehavior & other) const override
{
return true;
}

View File

@ -48,8 +48,8 @@ namespace Goals
specificObjects = true;
}
virtual Goals::TGoalVec decompose() const override;
virtual std::string toString() const override;
Goals::TGoalVec decompose() const override;
std::string toString() const override;
CaptureObjectsBehavior & ofType(int type)
{
@ -65,7 +65,7 @@ namespace Goals
return *this;
}
virtual bool operator==(const CaptureObjectsBehavior & other) const override;
bool operator==(const CaptureObjectsBehavior & other) const override;
static Goals::TGoalVec getVisitGoals(const std::vector<AIPath> & paths, const CGObjectInstance * objToVisit = nullptr);

View File

@ -28,10 +28,10 @@ namespace Goals
{
}
virtual TGoalVec decompose() const override;
virtual std::string toString() const override;
TGoalVec decompose() const override;
std::string toString() const override;
virtual bool operator==(const ClusterBehavior & other) const override
bool operator==(const ClusterBehavior & other) const override
{
return true;
}

View File

@ -29,10 +29,10 @@ namespace Goals
{
}
virtual Goals::TGoalVec decompose() const override;
virtual std::string toString() const override;
Goals::TGoalVec decompose() const override;
std::string toString() const override;
virtual bool operator==(const DefenceBehavior & other) const override
bool operator==(const DefenceBehavior & other) const override
{
return true;
}

View File

@ -246,7 +246,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
{
auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero);
if(heroRole == HeroRole::MAIN && path.turn() < SCOUT_TURN_DISTANCE_LIMIT)
if(heroRole == HeroRole::MAIN && path.turn() < ai->nullkiller->settings->getScoutHeroTurnDistanceLimit())
hasMainAround = true;
}
@ -335,7 +335,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
if(!upgrade.upgradeValue
&& armyToGetOrBuy.upgradeValue > 20000
&& ai->nullkiller->heroManager->canRecruitHero(town)
&& path.turn() < SCOUT_TURN_DISTANCE_LIMIT)
&& path.turn() < ai->nullkiller->settings->getScoutHeroTurnDistanceLimit())
{
for(auto hero : cb->getAvailableHeroes(town))
{
@ -344,7 +344,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
if(scoutReinforcement >= armyToGetOrBuy.upgradeValue
&& ai->nullkiller->getFreeGold() >20000
&& ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE)
&& !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh())
{
Composition recruitHero;

View File

@ -25,10 +25,10 @@ namespace Goals
{
}
virtual TGoalVec decompose() const override;
virtual std::string toString() const override;
TGoalVec decompose() const override;
std::string toString() const override;
virtual bool operator==(const GatherArmyBehavior & other) const override
bool operator==(const GatherArmyBehavior & other) const override
{
return true;
}

View File

@ -85,8 +85,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const
continue;
if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1
|| (ai->nullkiller->getFreeResources()[EGameResID::GOLD] > 10000
&& ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE))
|| (ai->nullkiller->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh()))
{
tasks.push_back(Goals::sptr(Goals::RecruitHero(town).setpriority(3)));
}

View File

@ -25,10 +25,10 @@ namespace Goals
{
}
virtual TGoalVec decompose() const override;
virtual std::string toString() const override;
TGoalVec decompose() const override;
std::string toString() const override;
virtual bool operator==(const RecruitHeroBehavior & other) const override
bool operator==(const RecruitHeroBehavior & other) const override
{
return true;
}

View File

@ -71,7 +71,7 @@ bool needToRecruitHero(const CGTownInstance * startupTown)
for(auto obj : ai->nullkiller->objectClusterizer->getNearbyObjects())
{
if((obj->ID == Obj::RESOURCE && obj->subID == GameResID(EGameResID::GOLD))
if((obj->ID == Obj::RESOURCE && dynamic_cast<const CGResource *>(obj)->resourceID() == EGameResID::GOLD)
|| obj->ID == Obj::TREASURE_CHEST
|| obj->ID == Obj::CAMPFIRE
|| obj->ID == Obj::WATER_WHEEL)

View File

@ -25,10 +25,10 @@ namespace Goals
{
}
virtual TGoalVec decompose() const override;
virtual std::string toString() const override;
TGoalVec decompose() const override;
std::string toString() const override;
virtual bool operator==(const StartupBehavior & other) const override
bool operator==(const StartupBehavior & other) const override
{
return true;
}

View File

@ -25,10 +25,10 @@ namespace Goals
{
}
virtual TGoalVec decompose() const override;
virtual std::string toString() const override;
TGoalVec decompose() const override;
std::string toString() const override;
virtual bool operator==(const StayAtTownBehavior & other) const override
bool operator==(const StayAtTownBehavior & other) const override
{
return true;
}

View File

@ -17,6 +17,7 @@ set(Nullkiller_SRCS
AIUtility.cpp
Analyzers/ArmyManager.cpp
Analyzers/HeroManager.cpp
Engine/Settings.cpp
Engine/FuzzyEngines.cpp
Engine/FuzzyHelper.cpp
Engine/AIMemory.cpp
@ -80,6 +81,7 @@ set(Nullkiller_HEADERS
AIUtility.h
Analyzers/ArmyManager.h
Analyzers/HeroManager.h
Engine/Settings.h
Engine/FuzzyEngines.h
Engine/FuzzyHelper.h
Engine/AIMemory.h
@ -125,12 +127,12 @@ set(Nullkiller_HEADERS
AIGateway.h
)
if(NOT ENABLE_STATIC_AI_LIBS)
if(NOT ENABLE_STATIC_LIBS)
list(APPEND Nullkiller_SRCS main.cpp StdInc.cpp)
endif()
assign_source_group(${Nullkiller_SRCS} ${Nullkiller_HEADERS})
if(ENABLE_STATIC_AI_LIBS)
if(ENABLE_STATIC_LIBS)
add_library(Nullkiller STATIC ${Nullkiller_SRCS} ${Nullkiller_HEADERS})
else()
add_library(Nullkiller SHARED ${Nullkiller_SRCS} ${Nullkiller_HEADERS})
@ -138,7 +140,7 @@ else()
endif()
target_include_directories(Nullkiller PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(Nullkiller PUBLIC ${VCMI_LIB_TARGET} fuzzylite::fuzzylite TBB::tbb)
target_link_libraries(Nullkiller PUBLIC vcmi fuzzylite::fuzzylite TBB::tbb)
vcmi_set_output_dir(Nullkiller "AI")
enable_pch(Nullkiller)

View File

@ -39,7 +39,9 @@ void engineBase::addRule(const std::string & txt)
struct armyStructure
{
float walkers, shooters, flyers;
float walkers;
float shooters;
float flyers;
ui32 maxSpeed;
};

View File

@ -41,8 +41,14 @@ public:
TacticalAdvantageEngine();
float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us
private:
fl::InputVariable * ourWalkers, *ourShooters, *ourFlyers, *enemyWalkers, *enemyShooters, *enemyFlyers;
fl::InputVariable * ourSpeed, *enemySpeed;
fl::InputVariable * ourWalkers;
fl::InputVariable * ourShooters;
fl::InputVariable * ourFlyers;
fl::InputVariable * enemyWalkers;
fl::InputVariable * enemyShooters;
fl::InputVariable * enemyFlyers;
fl::InputVariable * ourSpeed;
fl::InputVariable * enemySpeed;
fl::InputVariable * bankPresent;
fl::InputVariable * castleWalls;
fl::OutputVariable * threat;

View File

@ -24,13 +24,13 @@ ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
{
//this one is not fuzzy anymore, just calculate weighted average
auto objectInfo = VLC->objtypeh->getHandlerFor(bank->ID, bank->subID)->getObjectInfo(bank->appearance);
auto objectInfo = bank->getObjectHandler()->getObjectInfo(bank->appearance);
CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
ui64 totalStrength = 0;
ui8 totalChance = 0;
for(auto config : bankInfo->getPossibleGuards())
for(auto config : bankInfo->getPossibleGuards(bank->cb))
{
totalStrength += config.second.totalStrength * config.first;
totalChance += config.first;
@ -161,10 +161,7 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
}
case Obj::PYRAMID:
{
if(obj->subID == 0)
return estimateBankDanger(dynamic_cast<const CBank *>(obj));
else
return 0;
return estimateBankDanger(dynamic_cast<const CBank *>(obj));
}
default:
return 0;

View File

@ -27,15 +27,11 @@ namespace NKAI
using namespace Goals;
#if NKAI_TRACE_LEVEL >= 1
#define MAXPASS 1000000
#else
#define MAXPASS 30
#endif
Nullkiller::Nullkiller()
:activeHero(nullptr), scanDepth(ScanDepth::MAIN_FULL), useHeroChain(true)
{
memory.reset(new AIMemory());
memory = std::make_unique<AIMemory>();
settings = std::make_unique<Settings>();
}
void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID)
@ -115,6 +111,8 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositi
void Nullkiller::resetAiState()
{
std::unique_lock<std::mutex> lockGuard(aiStateMutex);
lockedResources = TResources();
scanDepth = ScanDepth::MAIN_FULL;
playerID = ai->playerID;
@ -127,6 +125,8 @@ void Nullkiller::updateAiState(int pass, bool fast)
{
boost::this_thread::interruption_point();
std::unique_lock<std::mutex> lockGuard(aiStateMutex);
auto start = std::chrono::high_resolution_clock::now();
activeHero = nullptr;
@ -162,12 +162,12 @@ void Nullkiller::updateAiState(int pass, bool fast)
if(scanDepth == ScanDepth::SMALL)
{
cfg.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT;
cfg.mainTurnDistanceLimit = ai->nullkiller->settings->getMainHeroTurnDistanceLimit();
}
if(scanDepth != ScanDepth::ALL_FULL)
{
cfg.scoutTurnDistanceLimit = SCOUT_TURN_DISTANCE_LIMIT;
cfg.scoutTurnDistanceLimit = ai->nullkiller->settings->getScoutHeroTurnDistanceLimit();
}
boost::this_thread::interruption_point();
@ -231,13 +231,13 @@ void Nullkiller::makeTurn()
resetAiState();
for(int i = 1; i <= MAXPASS; i++)
for(int i = 1; i <= settings->getMaxPass(); i++)
{
updateAiState(i);
Goals::TTask bestTask = taskptr(Goals::Invalid());
for(;i <= MAXPASS; i++)
for(;i <= settings->getMaxPass(); i++)
{
Goals::TTaskVec fastTasks = {
choseBestTask(sptr(BuyArmyBehavior()), 1),
@ -324,9 +324,9 @@ void Nullkiller::makeTurn()
executeTask(bestTask);
if(i == MAXPASS)
if(i == settings->getMaxPass())
{
logAi->error("Goal %s exceeded maxpass. Terminating AI turn.", taskDescription);
logAi->warn("Goal %s exceeded maxpass. Terminating AI turn.", taskDescription);
}
}
}

View File

@ -11,6 +11,7 @@
#include "PriorityEvaluator.h"
#include "FuzzyHelper.h"
#include "Settings.h"
#include "AIMemory.h"
#include "DeepDecomposer.h"
#include "../Analyzers/DangerHitMapAnalyzer.h"
@ -23,7 +24,6 @@
namespace NKAI
{
const float MAX_GOLD_PEASURE = 0.3f;
const float MIN_PRIORITY = 0.01f;
const float SMALL_SCAN_MIN_PRIORITY = 0.4f;
@ -71,8 +71,10 @@ public:
std::unique_ptr<FuzzyHelper> dangerEvaluator;
std::unique_ptr<DeepDecomposer> decomposer;
std::unique_ptr<ArmyFormation> armyFormation;
std::unique_ptr<Settings> settings;
PlayerColor playerID;
std::shared_ptr<CCallback> cb;
std::mutex aiStateMutex;
Nullkiller();
void init(std::shared_ptr<CCallback> cb, PlayerColor playerID);

View File

@ -69,7 +69,7 @@ PriorityEvaluator::~PriorityEvaluator()
void PriorityEvaluator::initVisitTile()
{
auto file = CResourceHandler::get()->load(ResourcePath("config/ai/object-priorities.txt"))->readAll();
auto file = CResourceHandler::get()->load(ResourcePath("config/ai/nkai/object-priorities.txt"))->readAll();
std::string str = std::string((char *)file.first.get(), file.second);
engine = fl::FllImporter().fromString(str);
armyLossPersentageVariable = engine->getInputVariable("armyLoss");
@ -122,7 +122,7 @@ TResources getCreatureBankResources(const CGObjectInstance * target, const CGHer
{
//Fixme: unused variable hero
auto objectInfo = VLC->objtypeh->getHandlerFor(target->ID, target->subID)->getObjectInfo(target->appearance);
auto objectInfo = target->getObjectHandler()->getObjectInfo(target->appearance);
CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
auto resources = bankInfo->getPossibleResourcesReward();
TResources result = TResources();
@ -137,11 +137,23 @@ TResources getCreatureBankResources(const CGObjectInstance * target, const CGHer
return sum > 1 ? result / sum : result;
}
uint64_t getResourcesGoldReward(const TResources & res)
{
int nonGoldResources = res[EGameResID::GEMS]
+ res[EGameResID::SULFUR]
+ res[EGameResID::WOOD]
+ res[EGameResID::ORE]
+ res[EGameResID::CRYSTAL]
+ res[EGameResID::MERCURY];
return res[EGameResID::GOLD] + 100 * nonGoldResources;
}
uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
{
auto objectInfo = VLC->objtypeh->getHandlerFor(target->ID, target->subID)->getObjectInfo(target->appearance);
auto objectInfo = target->getObjectHandler()->getObjectInfo(target->appearance);
CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
auto creatures = bankInfo->getPossibleCreaturesReward();
auto creatures = bankInfo->getPossibleCreaturesReward(target->cb);
uint64_t result = 0;
const auto& slots = hero->Slots();
@ -236,7 +248,7 @@ int getDwellingArmyCost(const CGObjectInstance * target)
return cost;
}
uint64_t evaluateArtifactArmyValue(CArtifactInstance * art)
static uint64_t evaluateArtifactArmyValue(const CArtifactInstance * art)
{
if(art->artType->getId() == ArtifactID::SPELL_SCROLL)
return 1500;
@ -467,14 +479,20 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
switch(target->ID)
{
case Obj::MINE:
return target->subID == GameResID(EGameResID::GOLD)
{
auto mine = dynamic_cast<const CGMine *>(target);
return mine->producedResource == EGameResID::GOLD
? 0.5f
: 0.4f * getTotalResourceRequirementStrength(target->subID) + 0.1f * getResourceRequirementStrength(target->subID);
: 0.4f * getTotalResourceRequirementStrength(mine->producedResource) + 0.1f * getResourceRequirementStrength(mine->producedResource);
}
case Obj::RESOURCE:
return target->subID == GameResID(EGameResID::GOLD)
{
auto resource = dynamic_cast<const CGResource *>(target);
return resource->resourceID() == EGameResID::GOLD
? 0
: 0.2f * getTotalResourceRequirementStrength(target->subID) + 0.4f * getResourceRequirementStrength(target->subID);
: 0.2f * getTotalResourceRequirementStrength(resource->resourceID()) + 0.4f * getResourceRequirementStrength(resource->resourceID());
}
case Obj::CREATURE_BANK:
{
@ -485,7 +503,7 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
//Evaluate resources used for construction. Gold is evaluated separately.
if (it->resType != EGameResID::GOLD)
{
sum += 0.1f * getResourceRequirementStrength(it->resType);
sum += 0.1f * it->resVal * getResourceRequirementStrength(it->resType);
}
}
return sum;
@ -523,6 +541,9 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance *>(target))
: 0;
case Obj::KEYMASTER:
return 0.6f;
default:
return 0;
}
@ -582,6 +603,8 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
case Obj::PANDORAS_BOX:
//Can contains experience, spells, or skills (only on custom maps)
return 2.5f;
case Obj::PYRAMID:
return 3.0f;
case Obj::HERO:
return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
? enemyHeroEliminationSkillRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->level
@ -626,12 +649,14 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
const int dailyIncomeMultiplier = 5;
const float enemyArmyEliminationGoldRewardRatio = 0.2f;
const int32_t heroEliminationBonus = GameConstants::HERO_GOLD_COST / 2;
auto isGold = target->subID == GameResID(EGameResID::GOLD); // TODO: other resorces could be sold but need to evaluate market power
switch(target->ID)
{
case Obj::RESOURCE:
return isGold ? 600 : 100;
{
auto * res = dynamic_cast<const CGResource*>(target);
return res->resourceID() == GameResID::GOLD ? 600 : 100;
}
case Obj::TREASURE_CHEST:
return 1500;
case Obj::WATER_WHEEL:
@ -640,7 +665,10 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
return dailyIncomeMultiplier * estimateTownIncome(ai->cb.get(), target, hero);
case Obj::MINE:
case Obj::ABANDONED_MINE:
return dailyIncomeMultiplier * (isGold ? 1000 : 75);
{
auto * mine = dynamic_cast<const CGMine*>(target);
return dailyIncomeMultiplier * (mine->producedResource == GameResID::GOLD ? 1000 : 75);
}
case Obj::MYSTICAL_GARDEN:
case Obj::WINDMILL:
return 100;
@ -649,7 +677,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
case Obj::WAGON:
return 100;
case Obj::CREATURE_BANK:
return getCreatureBankResources(target, hero)[EGameResID::GOLD];
return getResourcesGoldReward(getCreatureBankResources(target, hero));
case Obj::CRYPT:
case Obj::DERELICT_SHIP:
return 3000;
@ -674,7 +702,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
class HeroExchangeEvaluator : public IEvaluationContextBuilder
{
public:
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{
if(task->goalType != Goals::HERO_EXCHANGE)
return;
@ -691,7 +719,7 @@ public:
class ArmyUpgradeEvaluator : public IEvaluationContextBuilder
{
public:
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{
if(task->goalType != Goals::ARMY_UPGRADE)
return;
@ -708,7 +736,7 @@ public:
class StayAtTownManaRecoveryEvaluator : public IEvaluationContextBuilder
{
public:
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{
if(task->goalType != Goals::STAY_AT_TOWN)
return;
@ -743,7 +771,7 @@ void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uin
class DefendTownEvaluator : public IEvaluationContextBuilder
{
public:
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{
if(task->goalType != Goals::DEFEND_TOWN)
return;
@ -793,7 +821,7 @@ private:
public:
ExecuteHeroChainEvaluationContextBuilder(const Nullkiller * ai) : ai(ai) {}
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{
if(task->goalType != Goals::EXECUTE_HERO_CHAIN)
return;
@ -851,7 +879,7 @@ class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder
public:
ClusterEvaluationContextBuilder(const Nullkiller * ai) {}
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{
if(task->goalType != Goals::UNLOCK_CLUSTER)
return;
@ -898,7 +926,7 @@ public:
class ExchangeSwapTownHeroesContextBuilder : public IEvaluationContextBuilder
{
public:
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{
if(task->goalType != Goals::EXCHANGE_SWAP_TOWN_HEROES)
return;
@ -926,7 +954,7 @@ private:
public:
DismissHeroContextBuilder(const Nullkiller * ai) : ai(ai) {}
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{
if(task->goalType != Goals::DISMISS_HERO)
return;
@ -946,7 +974,7 @@ public:
class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder
{
public:
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{
if(task->goalType != Goals::BUILD_STRUCTURE)
return;
@ -1005,7 +1033,7 @@ public:
uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const
{
if(ai->buildAnalyzer->hasAnyBuilding(town->subID, bi.id))
if(ai->buildAnalyzer->hasAnyBuilding(town->getFaction(), bi.id))
return 0;
auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);

View File

@ -0,0 +1,78 @@
/*
* Settings.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 <limits>
#include "Settings.h"
#include "../../../lib/mapObjectConstructors/AObjectTypeHandler.h"
#include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h"
#include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
#include "../../../lib/mapObjects/MapObjects.h"
#include "../../../lib/modding/CModHandler.h"
#include "../../../lib/VCMI_Lib.h"
#include "../../../lib/filesystem/Filesystem.h"
#include "../../../lib/json/JsonNode.h"
namespace NKAI
{
Settings::Settings()
: maxRoamingHeroes(8),
mainHeroTurnDistanceLimit(10),
scoutHeroTurnDistanceLimit(5),
maxGoldPreasure(0.3f),
maxpass(30)
{
ResourcePath resource("config/ai/nkai/nkai-settings", EResType::JSON);
loadFromMod("core", resource);
for(const auto & modName : VLC->modh->getActiveMods())
{
if(CResourceHandler::get(modName)->existsResource(resource))
loadFromMod(modName, resource);
}
}
void Settings::loadFromMod(const std::string & modName, const ResourcePath & resource)
{
if(!CResourceHandler::get(modName)->existsResource(resource))
{
logGlobal->error("Failed to load font %s from mod %s", resource.getName(), modName);
return;
}
JsonNode node(JsonPath::fromResource(resource), modName);
if(node.Struct()["maxRoamingHeroes"].isNumber())
{
maxRoamingHeroes = node.Struct()["maxRoamingHeroes"].Integer();
}
if(node.Struct()["mainHeroTurnDistanceLimit"].isNumber())
{
mainHeroTurnDistanceLimit = node.Struct()["mainHeroTurnDistanceLimit"].Integer();
}
if(node.Struct()["scoutHeroTurnDistanceLimit"].isNumber())
{
scoutHeroTurnDistanceLimit = node.Struct()["scoutHeroTurnDistanceLimit"].Integer();
}
if(node.Struct()["maxpass"].isNumber())
{
maxpass = node.Struct()["maxpass"].Integer();
}
if(node.Struct()["maxGoldPreasure"].isNumber())
{
maxGoldPreasure = node.Struct()["maxGoldPreasure"].Float();
}
}
}

View File

@ -0,0 +1,42 @@
/*
* Settings.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
VCMI_LIB_NAMESPACE_BEGIN
class JsonNode;
class ResourcePath;
VCMI_LIB_NAMESPACE_END
namespace NKAI
{
class Settings
{
private:
int maxRoamingHeroes;
int mainHeroTurnDistanceLimit;
int scoutHeroTurnDistanceLimit;
int maxpass;
float maxGoldPreasure;
public:
Settings();
int getMaxPass() const { return maxpass; }
float getMaxGoldPreasure() const { return maxGoldPreasure; }
int getMaxRoamingHeroes() const { return maxRoamingHeroes; }
int getMainHeroTurnDistanceLimit() const { return mainHeroTurnDistanceLimit; }
int getScoutHeroTurnDistanceLimit() const { return scoutHeroTurnDistanceLimit; }
private:
void loadFromMod(const std::string & modName, const ResourcePath & resource);
};
}

View File

@ -180,11 +180,7 @@ public:
{
}
virtual ~cannotFulfillGoalException() throw ()
{
};
const char * what() const throw () override
const char * what() const noexcept override
{
return msg.c_str();
}
@ -203,11 +199,7 @@ public:
msg = goal->toString();
}
virtual ~goalFulfilledException() throw ()
{
};
const char * what() const throw () override
const char * what() const noexcept override
{
return msg.c_str();
}

View File

@ -35,7 +35,7 @@ namespace Goals
void accept(AIGateway * ai) override;
std::string toString() const override;
virtual bool operator==(const AdventureSpellCast & other) const override;
bool operator==(const AdventureSpellCast & other) const override;
};
}

View File

@ -32,7 +32,7 @@ namespace Goals
TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const Build & other) const override
bool operator==(const Build & other) const override
{
return true;
}

View File

@ -29,7 +29,7 @@ namespace Goals
void accept(AIGateway * ai) override;
std::string toString() const override;
virtual bool operator==(const BuildBoat & other) const override;
bool operator==(const BuildBoat & other) const override;
};
}

View File

@ -39,8 +39,8 @@ namespace Goals
}
BuildThis(BuildingID Bid, const CGTownInstance * tid);
virtual bool operator==(const BuildThis & other) const override;
virtual std::string toString() const override;
bool operator==(const BuildThis & other) const override;
std::string toString() const override;
void accept(AIGateway * ai) override;
};
}

View File

@ -54,12 +54,12 @@ void BuyArmy::accept(AIGateway * ai)
if(objid != CreatureID::NONE && ci.creID.getNum() != objid)
continue;
vstd::amin(ci.count, res / ci.cre->getFullRecruitCost());
vstd::amin(ci.count, res / ci.creID.toCreature()->getFullRecruitCost());
if(ci.count)
{
cb->recruitCreatures(town, town->getUpperArmy(), ci.creID, ci.count, ci.level);
valueBought += ci.count * ci.cre->getAIValue();
valueBought += ci.count * ci.creID.toCreature()->getAIValue();
}
}

View File

@ -36,11 +36,11 @@ namespace Goals
priority = 3;//TODO: evaluate?
}
virtual bool operator==(const BuyArmy & other) const override;
bool operator==(const BuyArmy & other) const override;
virtual std::string toString() const override;
std::string toString() const override;
virtual void accept(AIGateway * ai) override;
void accept(AIGateway * ai) override;
};
}

View File

@ -37,14 +37,14 @@ namespace Goals
{
return new T(static_cast<T const &>(*this)); //casting enforces template instantiation
}
template<typename Handler> void serialize(Handler & h, const int version)
template<typename Handler> void serialize(Handler & h)
{
h & static_cast<AbstractGoal &>(*this);
//h & goalType & isElementar & isAbstract & priority;
//h & value & resID & objid & aid & tile & hero & town & bid;
}
virtual bool operator==(const AbstractGoal & g) const override
bool operator==(const AbstractGoal & g) const override
{
if(goalType != g.goalType)
return false;
@ -54,7 +54,7 @@ namespace Goals
virtual bool operator==(const T & other) const = 0;
virtual TGoalVec decompose() const override
TGoalVec decompose() const override
{
TSubgoal single = decomposeSingle();
@ -90,11 +90,11 @@ namespace Goals
return *((T *)this);
}
virtual bool isElementar() const override { return true; }
bool isElementar() const override { return true; }
virtual HeroPtr getHero() const override { return AbstractGoal::hero; }
HeroPtr getHero() const override { return AbstractGoal::hero; }
virtual int getHeroExchangeCount() const override { return 0; }
int getHeroExchangeCount() const override { return 0; }
};
}

View File

@ -34,11 +34,11 @@ namespace Goals
name = obj->getObjectName();
}
virtual bool operator==(const CaptureObject & other) const override;
virtual Goals::TGoalVec decompose() const override;
virtual std::string toString() const override;
virtual bool hasHash() const override { return true; }
virtual uint64_t getHash() const override;
bool operator==(const CaptureObject & other) const override;
Goals::TGoalVec decompose() const override;
std::string toString() const override;
bool hasHash() const override { return true; }
uint64_t getHash() const override;
};
}

View File

@ -98,7 +98,7 @@ std::string CompleteQuest::questToString() const
return "inactive quest";
MetaString ms;
q.quest->getRolloverText(ms, false);
q.quest->getRolloverText(q.obj->cb, ms, false);
return ms.toString();
}
@ -210,7 +210,7 @@ TGoalVec CompleteQuest::missionResources() const
TGoalVec CompleteQuest::missionDestroyObj() const
{
auto obj = cb->getObjByQuestIdentifier(q.quest->killTarget);
auto obj = cb->getObj(q.quest->killTarget);
if(!obj)
return CaptureObjectsBehavior(q.obj).decompose();

View File

@ -29,12 +29,12 @@ namespace Goals
{
}
virtual Goals::TGoalVec decompose() const override;
virtual std::string toString() const override;
virtual bool hasHash() const override { return true; }
virtual uint64_t getHash() const override;
Goals::TGoalVec decompose() const override;
std::string toString() const override;
bool hasHash() const override { return true; }
uint64_t getHash() const override;
virtual bool operator==(const CompleteQuest & other) const override;
bool operator==(const CompleteQuest & other) const override;
private:
TGoalVec tryCompleteQuest() const;

View File

@ -26,15 +26,15 @@ namespace Goals
{
}
virtual bool operator==(const Composition & other) const override;
virtual std::string toString() const override;
bool operator==(const Composition & other) const override;
std::string toString() const override;
void accept(AIGateway * ai) override;
Composition & addNext(const AbstractGoal & goal);
Composition & addNext(TSubgoal goal);
Composition & addNextSequence(const TGoalVec & taskSequence);
virtual TGoalVec decompose() const override;
virtual bool isElementar() const override;
virtual int getHeroExchangeCount() const override;
TGoalVec decompose() const override;
bool isElementar() const override;
int getHeroExchangeCount() const override;
};
}

View File

@ -33,7 +33,7 @@ namespace Goals
{
tile = Tile;
}
virtual bool operator==(const DigAtTile & other) const override;
bool operator==(const DigAtTile & other) const override;
private:
//TSubgoal decomposeSingle() const override;

View File

@ -26,7 +26,7 @@ namespace Goals
void accept(AIGateway * ai) override;
std::string toString() const override;
virtual bool operator==(const DismissHero & other) const override;
bool operator==(const DismissHero & other) const override;
};
}

View File

@ -31,7 +31,7 @@ namespace Goals
void accept(AIGateway * ai) override;
std::string toString() const override;
virtual bool operator==(const ExchangeSwapTownHeroes & other) const override;
bool operator==(const ExchangeSwapTownHeroes & other) const override;
const CGHeroInstance * getGarrisonHero() const { return garrisonHero; }
HeroLockedReason getLockingReason() const { return lockingReason; }

View File

@ -30,10 +30,10 @@ namespace Goals
void accept(AIGateway * ai) override;
std::string toString() const override;
virtual bool operator==(const ExecuteHeroChain & other) const override;
bool operator==(const ExecuteHeroChain & other) const override;
const AIPath & getPath() const { return chainPath; }
virtual int getHeroExchangeCount() const override { return chainPath.exchangeCount; }
int getHeroExchangeCount() const override { return chainPath.exchangeCount; }
private:
bool moveHeroToTile(const CGHeroInstance * hero, const int3 & tile);

View File

@ -36,7 +36,7 @@ namespace Goals
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
std::string completeMessage() const override;
virtual bool operator==(const GatherArmy & other) const override;
bool operator==(const GatherArmy & other) const override;
};
}

View File

@ -32,17 +32,17 @@ namespace Goals
return TGoalVec();
}
virtual bool operator==(const Invalid & other) const override
bool operator==(const Invalid & other) const override
{
return true;
}
virtual std::string toString() const override
std::string toString() const override
{
return "Invalid";
}
virtual void accept(AIGateway * ai) override
void accept(AIGateway * ai) override
{
throw cannotFulfillGoalException("Can not fulfill Invalid goal!");
}

View File

@ -38,12 +38,12 @@ namespace Goals
{
}
virtual bool operator==(const RecruitHero & other) const override
bool operator==(const RecruitHero & other) const override
{
return true;
}
virtual std::string toString() const override;
std::string toString() const override;
void accept(AIGateway * ai) override;
};
}

View File

@ -28,7 +28,7 @@ namespace Goals
void accept(AIGateway * ai) override;
std::string toString() const override;
virtual bool operator==(const SaveResources & other) const override;
bool operator==(const SaveResources & other) const override;
};
}

View File

@ -26,8 +26,8 @@ namespace Goals
public:
StayAtTown(const CGTownInstance * town, AIPath & path);
virtual bool operator==(const StayAtTown & other) const override;
virtual std::string toString() const override;
bool operator==(const StayAtTown & other) const override;
std::string toString() const override;
void accept(AIGateway * ai) override;
float getMovementWasted() const { return movementWasted; }
};

View File

@ -34,7 +34,7 @@ namespace Goals
value = val;
objid = Objid;
}
virtual bool operator==(const Trade & other) const override;
bool operator==(const Trade & other) const override;
};
}

View File

@ -29,8 +29,8 @@ namespace Goals
ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade);
ArmyUpgrade(const CGHeroInstance * targetMain, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade);
virtual bool operator==(const ArmyUpgrade & other) const override;
virtual std::string toString() const override;
bool operator==(const ArmyUpgrade & other) const override;
std::string toString() const override;
uint64_t getUpgradeValue() const { return upgradeValue; }
uint64_t getInitialArmyValue() const { return initialValue; }

View File

@ -30,8 +30,8 @@ namespace Goals
DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath, bool isCounterAttack = false);
DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const CGHeroInstance * defender);
virtual bool operator==(const DefendTown & other) const override;
virtual std::string toString() const override;
bool operator==(const DefendTown & other) const override;
std::string toString() const override;
const HitMapInfo & getTreat() const { return treat; }

View File

@ -28,8 +28,8 @@ namespace Goals
sethero(targetHero);
}
virtual bool operator==(const HeroExchange & other) const override;
virtual std::string toString() const override;
bool operator==(const HeroExchange & other) const override;
std::string toString() const override;
uint64_t getReinforcementArmyStrength() const;
};

View File

@ -36,8 +36,8 @@ namespace Goals
sethero(pathToCenter.targetHero);
}
virtual bool operator==(const UnlockCluster & other) const override;
virtual std::string toString() const override;
bool operator==(const UnlockCluster & other) const override;
std::string toString() const override;
std::shared_ptr<ObjectCluster> getCluster() const { return cluster; }
const AIPath & getPathToCenter() { return pathToCenter; }
};

View File

@ -35,6 +35,8 @@ const uint64_t MIN_ARMY_STRENGTH_FOR_CHAIN = 5000;
const uint64_t MIN_ARMY_STRENGTH_FOR_NEXT_ACTOR = 1000;
const uint64_t CHAIN_MAX_DEPTH = 4;
const bool DO_NOT_SAVE_TO_COMMITED_TILES = false;
AISharedStorage::AISharedStorage(int3 sizes)
{
if(!shared){
@ -90,7 +92,7 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
const PlayerColor fowPlayer = ai->playerID;
const auto fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap;
const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap;
const int3 sizes = gs->getMapSize();
//Each thread gets different x, but an array of y located next to each other in memory
@ -234,6 +236,7 @@ void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, EPath
heroNode.specialAction.reset();
heroNode.armyLoss = 0;
heroNode.chainOther = nullptr;
heroNode.dayFlags = DayFlags::NONE;
heroNode.update(coord, layer, accessibility);
}
}
@ -265,7 +268,8 @@ void AINodeStorage::commit(
EPathNodeAction action,
int turn,
int movementLeft,
float cost) const
float cost,
bool saveToCommited) const
{
destination->action = action;
destination->setCost(cost);
@ -291,10 +295,15 @@ void AINodeStorage::commit(
destination->actor->armyValue);
#endif
if(destination->turns <= heroChainTurn)
if(saveToCommited && destination->turns <= heroChainTurn)
{
commitedTiles.insert(destination->coord);
}
if(destination->turns == source->turns)
{
destination->dayFlags = source->dayFlags;
}
}
std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
@ -323,7 +332,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
return neighbours;
}
EPathfindingLayer phisycalLayers[2] = {EPathfindingLayer::LAND, EPathfindingLayer::SAIL};
constexpr std::array phisycalLayers = {EPathfindingLayer::LAND, EPathfindingLayer::SAIL};
bool AINodeStorage::increaseHeroChainTurnLimit()
{
@ -778,7 +787,14 @@ void HeroChainCalculationTask::addHeroChain(const std::vector<ExchangeCandidate>
continue;
}
storage.commit(exchangeNode, carrier, carrier->action, chainInfo.turns, chainInfo.moveRemains, chainInfo.getCost());
storage.commit(
exchangeNode,
carrier,
carrier->action,
chainInfo.turns,
chainInfo.moveRemains,
chainInfo.getCost(),
DO_NOT_SAVE_TO_COMMITED_TILES);
if(carrier->specialAction || carrier->chainOther)
{
@ -827,6 +843,7 @@ ExchangeCandidate HeroChainCalculationTask::calculateExchange(
candidate.turns = carrierParentNode->turns;
candidate.setCost(carrierParentNode->getCost() + otherParentNode->getCost() / 1000.0);
candidate.moveRemains = carrierParentNode->moveRemains;
candidate.danger = carrierParentNode->danger;
if(carrierParentNode->turns < otherParentNode->turns)
{
@ -1070,7 +1087,8 @@ struct TowmPortalFinder
EPathNodeAction::TELEPORT_NORMAL,
bestNode->turns,
bestNode->moveRemains - movementNeeded,
movementCost);
movementCost,
DO_NOT_SAVE_TO_COMMITED_TILES);
node->theNodeBefore = bestNode;
node->addSpecialAction(std::make_shared<AIPathfinding::TownPortalAction>(targetTown));
@ -1247,8 +1265,8 @@ bool AINodeStorage::hasBetterChain(
&& nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength
&& node.getCost() <= candidateNode->getCost())
{
if(nodeActor->heroFightingStrength == candidateActor->heroFightingStrength
&& node.getCost() == candidateNode->getCost()
if(vstd::isAlmostEqual(nodeActor->heroFightingStrength, candidateActor->heroFightingStrength)
&& vstd::isAlmostEqual(node.getCost(), candidateNode->getCost())
&& &node < candidateNode)
{
continue;

View File

@ -24,9 +24,6 @@
namespace NKAI
{
const int SCOUT_TURN_DISTANCE_LIMIT = 5;
const int MAIN_TURN_DISTANCE_LIMIT = 10;
namespace AIPathfinding
{
#ifdef ENVIRONMENT64
@ -41,11 +38,19 @@ namespace AIPathfinding
const int CHAIN_MAX_DEPTH = 4;
}
enum DayFlags : ui8
{
NONE = 0,
FLY_CAST = 1,
WATER_WALK_CAST = 2
};
struct AIPathNode : public CGPathNode
{
uint64_t danger;
uint64_t armyLoss;
int32_t manaCost;
int16_t manaCost;
DayFlags dayFlags;
const AIPathNode * chainOther;
std::shared_ptr<const SpecialAction> specialAction;
const ChainActor * actor;
@ -180,7 +185,7 @@ public:
bool selectFirstActor();
bool selectNextActor();
virtual std::vector<CGPathNode *> getInitialNodes() override;
std::vector<CGPathNode *> getInitialNodes() override;
virtual std::vector<CGPathNode *> calculateNeighbours(
const PathNodeInfo & source,
@ -192,7 +197,7 @@ public:
const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) override;
virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override;
void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override;
void commit(
AIPathNode * destination,
@ -200,7 +205,8 @@ public:
EPathNodeAction action,
int turn,
int movementLeft,
float cost) const;
float cost,
bool saveToCommited = true) const;
inline const AIPathNode * getAINode(const CGPathNode * node) const
{

View File

@ -34,7 +34,7 @@ namespace AIPathfinding
~AIPathfinderConfig();
virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
};
}

View File

@ -22,18 +22,18 @@ namespace NKAI
namespace AIPathfinding
{
AdventureCastAction::AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero)
:spellToCast(spellToCast), hero(hero)
AdventureCastAction::AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero, DayFlags flagsToAdd)
:spellToCast(spellToCast), hero(hero), flagsToAdd(flagsToAdd)
{
manaCost = hero->getSpellCost(spellToCast.toSpell());
}
WaterWalkingAction::WaterWalkingAction(const CGHeroInstance * hero)
:AdventureCastAction(SpellID::WATER_WALK, hero)
:AdventureCastAction(SpellID::WATER_WALK, hero, DayFlags::WATER_WALK_CAST)
{ }
AirWalkingAction::AirWalkingAction(const CGHeroInstance * hero)
: AdventureCastAction(SpellID::FLY, hero)
: AdventureCastAction(SpellID::FLY, hero, DayFlags::FLY_CAST)
{
}
@ -41,11 +41,12 @@ namespace AIPathfinding
const CGHeroInstance * hero,
CDestinationNodeInfo & destination,
const PathNodeInfo & source,
AIPathNode * dstMode,
AIPathNode * dstNode,
const AIPathNode * srcNode) const
{
dstMode->manaCost = srcNode->manaCost + manaCost;
dstMode->theNodeBefore = source.node;
dstNode->manaCost = srcNode->manaCost + manaCost;
dstNode->theNodeBefore = source.node;
dstNode->dayFlags = static_cast<DayFlags>(dstNode->dayFlags | flagsToAdd);
}
void AdventureCastAction::execute(const CGHeroInstance * hero) const

View File

@ -24,11 +24,12 @@ namespace AIPathfinding
SpellID spellToCast;
const CGHeroInstance * hero;
int manaCost;
DayFlags flagsToAdd;
public:
AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero);
AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero, DayFlags flagsToAdd = DayFlags::NONE);
virtual void execute(const CGHeroInstance * hero) const override;
void execute(const CGHeroInstance * hero) const override;
virtual void applyOnDestination(
const CGHeroInstance * hero,
@ -37,9 +38,9 @@ namespace AIPathfinding
AIPathNode * dstMode,
const AIPathNode * srcNode) const override;
virtual bool canAct(const AIPathNode * source) const override;
bool canAct(const AIPathNode * source) const override;
virtual std::string toString() const override;
std::string toString() const override;
};
class WaterWalkingAction : public AdventureCastAction

View File

@ -28,9 +28,9 @@ namespace AIPathfinding
{
}
virtual void execute(const CGHeroInstance * hero) const override;
void execute(const CGHeroInstance * hero) const override;
virtual std::string toString() const override;
std::string toString() const override;
};
}

View File

@ -25,7 +25,7 @@ namespace AIPathfinding
class SummonBoatAction : public VirtualBoatAction
{
public:
virtual void execute(const CGHeroInstance * hero) const override;
void execute(const CGHeroInstance * hero) const override;
virtual void applyOnDestination(
const CGHeroInstance * hero,
@ -34,11 +34,11 @@ namespace AIPathfinding
AIPathNode * dstMode,
const AIPathNode * srcNode) const override;
virtual bool canAct(const AIPathNode * source) const override;
bool canAct(const AIPathNode * source) const override;
virtual const ChainActor * getActor(const ChainActor * sourceActor) const override;
const ChainActor * getActor(const ChainActor * sourceActor) const override;
virtual std::string toString() const override;
std::string toString() const override;
private:
int32_t getManaCost(const CGHeroInstance * hero) const;
@ -56,17 +56,17 @@ namespace AIPathfinding
{
}
virtual bool canAct(const AIPathNode * source) const override;
bool canAct(const AIPathNode * source) const override;
virtual void execute(const CGHeroInstance * hero) const override;
void execute(const CGHeroInstance * hero) const override;
virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
virtual const ChainActor * getActor(const ChainActor * sourceActor) const override;
const ChainActor * getActor(const ChainActor * sourceActor) const override;
virtual std::string toString() const override;
std::string toString() const override;
virtual const CGObjectInstance * targetObject() const override;
const CGObjectInstance * targetObject() const override;
};
}

View File

@ -28,13 +28,13 @@ namespace AIPathfinding
{
}
virtual bool canAct(const AIPathNode * node) const override;
bool canAct(const AIPathNode * node) const override;
virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
virtual void execute(const CGHeroInstance * hero) const override;
void execute(const CGHeroInstance * hero) const override;
virtual std::string toString() const override;
std::string toString() const override;
};
}

View File

@ -29,9 +29,9 @@ namespace AIPathfinding
{
}
virtual void execute(const CGHeroInstance * hero) const override;
void execute(const CGHeroInstance * hero) const override;
virtual std::string toString() const override;
std::string toString() const override;
};
}

View File

@ -18,7 +18,7 @@
using namespace NKAI;
CCreatureSet emptyArmy;
const CCreatureSet emptyArmy;
bool HeroExchangeArmy::needsLastStack() const
{
@ -327,7 +327,7 @@ ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other)
return result;
}
HeroActor * exchanged = new HeroActor(actor, other, newArmy, ai);
auto * exchanged = new HeroActor(actor, other, newArmy, ai);
exchanged->armyCost += newArmy->armyCost;
result.actor = exchanged;
@ -342,7 +342,7 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
const CGObjectInstance * upgrader,
TResources resources) const
{
HeroExchangeArmy * target = new HeroExchangeArmy();
auto * target = new HeroExchangeArmy();
auto upgradeInfo = ai->armyManager->calculateCreaturesUpgrade(army, upgrader, resources);
if(upgradeInfo.upgradeValue)
@ -373,10 +373,10 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
for(auto & creatureToBuy : buyArmy)
{
auto targetSlot = target->getSlotFor(dynamic_cast<const CCreature*>(creatureToBuy.cre));
auto targetSlot = target->getSlotFor(creatureToBuy.creID.toCreature());
target->addToSlot(targetSlot, creatureToBuy.creID, creatureToBuy.count);
target->armyCost += creatureToBuy.cre->getFullRecruitCost() * creatureToBuy.count;
target->armyCost += creatureToBuy.creID.toCreature()->getFullRecruitCost() * creatureToBuy.count;
target->requireBuyArmy = true;
}
}
@ -393,7 +393,7 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
HeroExchangeArmy * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const
{
HeroExchangeArmy * target = new HeroExchangeArmy();
auto * target = new HeroExchangeArmy();
auto bestArmy = ai->armyManager->getBestArmy(actor->hero, army1, army2);
for(auto & slotInfo : bestArmy)
@ -445,7 +445,7 @@ std::string DwellingActor::toString() const
CCreatureSet * DwellingActor::getDwellingCreatures(const CGDwelling * dwelling, bool waitForGrowth)
{
CCreatureSet * dwellingCreatures = new CCreatureSet();
auto * dwellingCreatures = new CCreatureSet();
for(auto & creatureInfo : dwelling->creatures)
{

View File

@ -28,10 +28,10 @@ class HeroExchangeArmy : public CArmedInstance
public:
TResources armyCost;
bool requireBuyArmy;
virtual bool needsLastStack() const override;
bool needsLastStack() const override;
std::shared_ptr<SpecialAction> getActorAction() const;
HeroExchangeArmy(): CArmedInstance(true), requireBuyArmy(false) {}
HeroExchangeArmy(): CArmedInstance(nullptr, true), requireBuyArmy(false) {}
};
struct ExchangeResult
@ -51,24 +51,24 @@ protected:
public:
uint64_t chainMask;
bool isMovable;
bool allowUseResources;
bool allowBattle;
bool allowSpellCast;
bool isMovable = false;
bool allowUseResources = false;
bool allowBattle = false;
bool allowSpellCast = false;
std::shared_ptr<SpecialAction> actorAction;
const CGHeroInstance * hero;
HeroRole heroRole;
const CCreatureSet * creatureSet;
const ChainActor * battleActor;
const ChainActor * castActor;
const ChainActor * resourceActor;
const ChainActor * carrierParent;
const ChainActor * otherParent;
const ChainActor * baseActor;
const CCreatureSet * creatureSet = nullptr;
const ChainActor * battleActor = nullptr;
const ChainActor * castActor = nullptr;
const ChainActor * resourceActor = nullptr;
const ChainActor * carrierParent = nullptr;
const ChainActor * otherParent = nullptr;
const ChainActor * baseActor = nullptr;
int3 initialPosition;
EPathfindingLayer layer;
uint32_t initialMovement;
uint32_t initialTurn;
uint32_t initialMovement = 0;
uint32_t initialTurn = 0;
uint64_t armyValue;
float heroFightingStrength;
uint8_t actorExchangeCount;
@ -126,7 +126,7 @@ public:
HeroActor(const ChainActor * carrier, const ChainActor * other, const HeroExchangeArmy * army, const Nullkiller * ai);
protected:
virtual ExchangeResult tryExchangeNoLock(const ChainActor * specialActor, const ChainActor * other) const override;
ExchangeResult tryExchangeNoLock(const ChainActor * specialActor, const ChainActor * other) const override;
};
class ObjectActor : public ChainActor
@ -136,7 +136,7 @@ private:
public:
ObjectActor(const CGObjectInstance * obj, const CCreatureSet * army, uint64_t chainMask, int initialTurn);
virtual std::string toString() const override;
std::string toString() const override;
const CGObjectInstance * getActorObject() const override;
};
@ -154,7 +154,7 @@ private:
public:
DwellingActor(const CGDwelling * dwelling, uint64_t chainMask, bool waitForGrowth, int dayOfWeek);
~DwellingActor();
virtual std::string toString() const override;
std::string toString() const override;
protected:
int getInitialTurn(bool waitForGrowth, int dayOfWeek);
@ -168,7 +168,7 @@ private:
public:
TownGarrisonActor(const CGTownInstance * town, uint64_t chainMask);
virtual std::string toString() const override;
std::string toString() const override;
};
}

View File

@ -61,6 +61,12 @@ namespace AIPathfinding
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::WATER)
{
if(nodeStorage->getAINode(source.node)->dayFlags & DayFlags::WATER_WALK_CAST)
{
destination.blocked = false;
return;
}
auto action = waterWalkingActions.find(nodeStorage->getHero(source.node));
if(action != waterWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL))
@ -73,6 +79,12 @@ namespace AIPathfinding
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::AIR)
{
if(nodeStorage->getAINode(source.node)->dayFlags & DayFlags::FLY_CAST)
{
destination.blocked = false;
return;
}
auto action = airWalkingActions.find(nodeStorage->getHero(source.node));
if(action != airWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL))
@ -91,12 +103,12 @@ namespace AIPathfinding
for(const CGHeroInstance * hero : nodeStorage->getAllHeroes())
{
if(hero->canCastThisSpell(waterWalk.toSpell()))
if(hero->canCastThisSpell(waterWalk.toSpell()) && hero->mana >= hero->getSpellCost(waterWalk.toSpell()))
{
waterWalkingActions[hero] = std::make_shared<WaterWalkingAction>(hero);
}
if(hero->canCastThisSpell(airWalk.toSpell()))
if(hero->canCastThisSpell(airWalk.toSpell()) && hero->mana >= hero->getSpellCost(airWalk.toSpell()))
{
airWalkingActions[hero] = std::make_shared<AirWalkingAction>(hero);
}
@ -119,7 +131,7 @@ namespace AIPathfinding
{
if(obj->ID != Obj::TOWN) //towns were handled in the previous loop
{
if(const IShipyard * shipyard = IShipyard::castFrom(obj))
if(const auto * shipyard = dynamic_cast<const IShipyard *>(obj))
shipyards.push_back(shipyard);
}
}
@ -179,11 +191,6 @@ namespace AIPathfinding
{
bool result = false;
if(!specialAction->canAct(nodeStorage->getAINode(source.node)))
{
return false;
}
nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
{
auto castNodeOptional = nodeStorage->getOrCreateNode(

View File

@ -14,7 +14,7 @@
#define strcpy_s(a, b, c) strncpy(a, c, b)
#endif
static const char * g_cszAiName = "Nullkiller";
static const char * const g_cszAiName = "Nullkiller";
extern "C" DLL_EXPORT int GetGlobalAiVersion()
{

View File

@ -8,19 +8,19 @@ set(stupidAI_HEADERS
StupidAI.h
)
if(NOT ENABLE_STATIC_AI_LIBS)
if(NOT ENABLE_STATIC_LIBS)
list(APPEND stupidAI_SRCS main.cpp StdInc.cpp)
endif()
assign_source_group(${stupidAI_SRCS} ${stupidAI_HEADERS})
if(ENABLE_STATIC_AI_LIBS)
if(ENABLE_STATIC_LIBS)
add_library(StupidAI STATIC ${stupidAI_SRCS} ${stupidAI_HEADERS})
else()
add_library(StupidAI SHARED ${stupidAI_SRCS} ${stupidAI_HEADERS})
install(TARGETS StupidAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR})
endif()
target_link_libraries(StupidAI PRIVATE ${VCMI_LIB_TARGET})
target_link_libraries(StupidAI PRIVATE vcmi)
target_include_directories(StupidAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
vcmi_set_output_dir(StupidAI "AI")

View File

@ -15,8 +15,7 @@
#include "../../lib/CCreatureHandler.h"
#include "../../lib/battle/BattleAction.h"
#include "../../lib/battle/BattleInfo.h"
static std::shared_ptr<CBattleCallback> cbc;
#include "../../lib/CRandomGenerator.h"
CStupidAI::CStupidAI()
: side(-1)
@ -41,7 +40,7 @@ void CStupidAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
{
print("init called, saving ptr to IBattleCallback");
env = ENV;
cbc = cb = CB;
cb = CB;
wasWaitingForRealize = CB->waitTillRealize;
wasUnlockingGs = CB->unlockGsWhenWaiting;
@ -68,15 +67,16 @@ class EnemyInfo
{
public:
const CStack * s;
int adi, adr;
int adi;
int adr;
std::vector<BattleHex> attackFrom; //for melee fight
EnemyInfo(const CStack * _s) : s(_s), adi(0), adr(0)
{}
void calcDmg(const BattleID & battleID, const CStack * ourStack)
void calcDmg(std::shared_ptr<CBattleCallback> cb, const BattleID & battleID, const CStack * ourStack)
{
// FIXME: provide distance info for Jousting bonus
DamageEstimation retal;
DamageEstimation dmg = cbc->getBattle(battleID)->battleEstimateDamage(ourStack, s, 0, &retal);
DamageEstimation dmg = cb->getBattle(battleID)->battleEstimateDamage(ourStack, s, 0, &retal);
adi = static_cast<int>((dmg.damage.min + dmg.damage.max) / 2);
adr = static_cast<int>((retal.damage.min + retal.damage.max) / 2);
}
@ -92,14 +92,14 @@ bool isMoreProfitable(const EnemyInfo &ei1, const EnemyInfo& ei2)
return (ei1.adi-ei1.adr) < (ei2.adi - ei2.adr);
}
static bool willSecondHexBlockMoreEnemyShooters(const BattleID & battleID, const BattleHex &h1, const BattleHex &h2)
static bool willSecondHexBlockMoreEnemyShooters(std::shared_ptr<CBattleCallback> cb, const BattleID & battleID, const BattleHex &h1, const BattleHex &h2)
{
int shooters[2] = {0}; //count of shooters on hexes
for(int i = 0; i < 2; i++)
{
for (auto & neighbour : (i ? h2 : h1).neighbouringTiles())
if(const auto * s = cbc->getBattle(battleID)->battleGetUnitByPos(neighbour))
if(const auto * s = cb->getBattle(battleID)->battleGetUnitByPos(neighbour))
if(s->isShooter())
shooters[i]++;
}
@ -117,7 +117,9 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
//boost::this_thread::sleep_for(boost::chrono::seconds(2));
print("activeStack called for " + stack->nodeName());
ReachabilityInfo dists = cb->getBattle(battleID)->getReachability(stack);
std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
std::vector<EnemyInfo> enemiesShootable;
std::vector<EnemyInfo> enemiesReachable;
std::vector<EnemyInfo> enemiesUnreachable;
if(stack->creatureId() == CreatureID::CATAPULT)
{
@ -152,7 +154,7 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
{
if(CStack::isMeleeAttackPossible(stack, s, hex))
{
std::vector<EnemyInfo>::iterator i = std::find(enemiesReachable.begin(), enemiesReachable.end(), s);
auto i = std::find(enemiesReachable.begin(), enemiesReachable.end(), s);
if(i == enemiesReachable.end())
{
enemiesReachable.push_back(s);
@ -169,10 +171,10 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
}
for ( auto & enemy : enemiesReachable )
enemy.calcDmg(battleID, stack);
enemy.calcDmg(cb, battleID, stack);
for ( auto & enemy : enemiesShootable )
enemy.calcDmg(battleID, stack);
enemy.calcDmg(cb, battleID, stack);
if(enemiesShootable.size())
{
@ -183,7 +185,7 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
else if(enemiesReachable.size())
{
const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable);
BattleHex targetHex = *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), [&](auto a, auto b) { return willSecondHexBlockMoreEnemyShooters(battleID, a, b);});
BattleHex targetHex = *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), [&](auto a, auto b) { return willSecondHexBlockMoreEnemyShooters(cb, battleID, a, b);});
cb->battleMakeUnitAction(battleID, BattleAction::makeMeleeAttack(stack, ei.s->getPosition(), targetHex));
return;

View File

@ -16,7 +16,7 @@
#define strcpy_s(a, b, c) strncpy(a, c, b)
#endif
static const char *g_cszAiName = "Stupid AI 0.1";
static const char * const g_cszAiName = "Stupid AI 0.1";
extern "C" DLL_EXPORT int GetGlobalAiVersion()
{

View File

@ -26,7 +26,6 @@ using crint3 = const int3 &;
using crstring = const std::string &;
using dwellingContent = std::pair<ui32, std::vector<CreatureID>>;
const int GOLD_MINE_PRODUCTION = 1000, WOOD_ORE_MINE_PRODUCTION = 2, RESOURCE_MINE_PRODUCTION = 1;
const int ACTUAL_RESOURCE_COUNT = 7;
const int ALLOWED_ROAMING_HEROES = 8;
@ -71,7 +70,7 @@ public:
bool validAndSet() const;
template<typename Handler> void serialize(Handler & h, const int version)
template<typename Handler> void serialize(Handler & h)
{
h & this->h;
h & hid;
@ -103,7 +102,7 @@ struct ObjectIdRef
bool operator<(const ObjectIdRef & rhs) const;
template<typename Handler> void serialize(Handler & h, const int version)
template<typename Handler> void serialize(Handler & h)
{
h & id;
}

View File

@ -63,7 +63,7 @@ std::vector<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<Slot
if(left.creature->getLevel() != right.creature->getLevel())
return left.creature->getLevel() < right.creature->getLevel();
return left.creature->speed() > right.creature->speed();
return left.creature->getMovementRange() > right.creature->getMovementRange();
});
return weakest;

View File

@ -139,17 +139,17 @@ void BuildingManager::setAI(VCAI * AI)
//Set of buildings for different goals. Does not include any prerequisites.
static const std::vector<BuildingID> essential = { BuildingID::TAVERN, BuildingID::TOWN_HALL };
static const std::vector<BuildingID> basicGoldSource = { BuildingID::TOWN_HALL, BuildingID::CITY_HALL };
static const std::vector<BuildingID> defence = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE };
static const std::vector<BuildingID> capitolAndRequirements = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::CAPITOL };
static const std::vector<BuildingID> unitsSource = { BuildingID::DWELL_LVL_1, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3,
BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7 };
static const std::vector<BuildingID> unitsUpgrade = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP,
BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP };
static const std::vector<BuildingID> unitGrowth = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1,
BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR };
static const std::vector<BuildingID> unitGrowth = { BuildingID::HORDE_1, BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR };
static const std::vector<BuildingID> _spells = { BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5 };
static const std::vector<BuildingID> extra = { BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3,
BuildingID::SPECIAL_4, BuildingID::SHIPYARD }; // all remaining buildings
static const std::vector<BuildingID> extra = { BuildingID::MARKETPLACE, BuildingID::BLACKSMITH, BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2,
BuildingID::SPECIAL_3, BuildingID::SPECIAL_4, BuildingID::SHIPYARD }; // all remaining buildings
bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
{
@ -172,8 +172,26 @@ bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
if(tryBuildAnyStructure(t, essential))
return true;
//the more gold the better and less problems later //TODO: what about building mage guild / marketplace etc. with city hall disabled in editor?
if(tryBuildNextStructure(t, basicGoldSource))
if (cb->getDate(Date::DAY_OF_WEEK) < 5) // first 4 days of week - try to focus on dwellings
{
if (tryBuildNextStructure(t, unitsSource, 4))
return true;
}
if (cb->getDate(Date::DAY_OF_WEEK) > 4) // last 3 days of week - try to focus on growth by building Fort/Citadel/Castle
{
if (tryBuildNextStructure(t, defence, 3))
return true;
}
if (t->hasBuilt(BuildingID::CASTLE))
{
if (tryBuildAnyStructure(t, unitGrowth))
return true;
}
//try to make City Hall
if (tryBuildNextStructure(t, basicGoldSource))
return true;
//workaround for mantis #2696 - build capitol with separate algorithm if it is available
@ -183,25 +201,6 @@ bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
return true;
}
if(!t->hasBuilt(BuildingID::FORT)) //in vast majority of situations fort is top priority building if we already have city hall, TODO: unite with unitGrowth building chain
if(tryBuildThisStructure(t, BuildingID::FORT))
return true;
if (cb->getDate(Date::DAY_OF_WEEK) > 6) // last 2 days of week - try to focus on growth
{
if (tryBuildNextStructure(t, unitGrowth, 2))
return true;
}
//try building dwellings
if (t->hasBuilt(BuildingID::FORT))
{
if (tryBuildAnyStructure(t, unitsSource, 8 - cb->getDate(Date::DAY_OF_WEEK)))
return true;
}
//try to upgrade dwelling
for (int i = 0; i < unitsUpgrade.size(); i++)
{

View File

@ -94,12 +94,12 @@ set(VCAI_HEADERS
VCAI.h
)
if(NOT ENABLE_STATIC_AI_LIBS)
if(NOT ENABLE_STATIC_LIBS)
list(APPEND VCAI_SRCS main.cpp StdInc.cpp)
endif()
assign_source_group(${VCAI_SRCS} ${VCAI_HEADERS})
if(ENABLE_STATIC_AI_LIBS)
if(ENABLE_STATIC_LIBS)
add_library(VCAI STATIC ${VCAI_SRCS} ${VCAI_HEADERS})
else()
add_library(VCAI SHARED ${VCAI_SRCS} ${VCAI_HEADERS})
@ -107,7 +107,7 @@ else()
endif()
target_include_directories(VCAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(VCAI PUBLIC ${VCMI_LIB_TARGET} fuzzylite::fuzzylite)
target_link_libraries(VCAI PUBLIC vcmi fuzzylite::fuzzylite)
vcmi_set_output_dir(VCAI "AI")
enable_pch(VCAI)

View File

@ -37,7 +37,9 @@ void engineBase::addRule(const std::string & txt)
struct armyStructure
{
float walkers, shooters, flyers;
float walkers;
float shooters;
float flyers;
ui32 maxSpeed;
};
@ -406,7 +408,7 @@ float VisitObjEngine::evaluate(Goals::VisitObj & goal)
else
{
MapObjectsEvaluator::getInstance().addObjectData(obj->ID, obj->subID, 0);
logGlobal->error("AI met object type it doesn't know - ID: " + std::to_string(obj->ID) + ", subID: " + std::to_string(obj->subID) + " - adding to database with value " + std::to_string(objValue));
logGlobal->error("AI met object type it doesn't know - ID: %d, subID: %d - adding to database with value %d ", obj->ID, obj->subID, objValue);
}
setSharedFuzzyVariables(goal);

View File

@ -38,8 +38,14 @@ public:
TacticalAdvantageEngine();
float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us
private:
fl::InputVariable * ourWalkers, *ourShooters, *ourFlyers, *enemyWalkers, *enemyShooters, *enemyFlyers;
fl::InputVariable * ourSpeed, *enemySpeed;
fl::InputVariable * ourWalkers;
fl::InputVariable * ourShooters;
fl::InputVariable * ourFlyers;
fl::InputVariable * enemyWalkers;
fl::InputVariable * enemyShooters;
fl::InputVariable * enemyFlyers;
fl::InputVariable * ourSpeed;
fl::InputVariable * enemySpeed;
fl::InputVariable * bankPresent;
fl::InputVariable * castleWalls;
fl::OutputVariable * threat;

Some files were not shown because too many files have changed in this diff Show More