mirror of
https://github.com/vcmi/vcmi.git
synced 2025-03-17 20:58:07 +02:00
commit
1cbe1229ee
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -15,6 +15,7 @@ Please attach game logs: `VCMI_client.txt`, `VCMI_server.txt` etc.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
@ -24,7 +25,7 @@ Steps to reproduce the behavior:
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Actual behavior**
|
||||
A clear description what is currently happening
|
||||
A clear description what is currently happening
|
||||
|
||||
**Did it work earlier?**
|
||||
If this something which worked well some time ago, please let us know about version where it works or at date when it worked.
|
||||
@ -33,8 +34,9 @@ If this something which worked well some time ago, please let us know about vers
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Version**
|
||||
- OS: [e.g. Windows, macOS Intel, macOS ARM, Android, Linux, iOS]
|
||||
- Version: [VCMI version]
|
||||
|
||||
- OS: [e.g. Windows, macOS Intel, macOS ARM, Android, Linux, iOS]
|
||||
- Version: [VCMI version]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
145
.github/workflows/github.yml
vendored
145
.github/workflows/github.yml
vendored
@ -3,7 +3,6 @@ name: VCMI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- features/*
|
||||
- beta
|
||||
- master
|
||||
- develop
|
||||
@ -22,84 +21,112 @@ jobs:
|
||||
- platform: linux-qt6
|
||||
os: ubuntu-24.04
|
||||
test: 0
|
||||
before_install: linux_qt6.sh
|
||||
preset: linux-clang-test
|
||||
- platform: linux
|
||||
os: ubuntu-24.04
|
||||
test: 1
|
||||
before_install: linux_qt5.sh
|
||||
preset: linux-gcc-test
|
||||
- platform: linux
|
||||
os: ubuntu-20.04
|
||||
test: 0
|
||||
before_install: linux_qt5.sh
|
||||
preset: linux-gcc-debug
|
||||
- platform: mac-intel
|
||||
os: macos-13
|
||||
test: 0
|
||||
pack: 1
|
||||
upload: 1
|
||||
pack_type: Release
|
||||
extension: dmg
|
||||
before_install: macos.sh
|
||||
preset: macos-conan-ninja-release
|
||||
conan_profile: macos-intel
|
||||
conan_prebuilts: dependencies-mac-intel
|
||||
conan_options: --options with_apple_system_libs=True
|
||||
artifact_platform: intel
|
||||
- platform: mac-arm
|
||||
os: macos-13
|
||||
test: 0
|
||||
pack: 1
|
||||
upload: 1
|
||||
pack_type: Release
|
||||
extension: dmg
|
||||
before_install: macos.sh
|
||||
preset: macos-arm-conan-ninja-release
|
||||
conan_profile: macos-arm
|
||||
conan_prebuilts: dependencies-mac-arm
|
||||
conan_options: --options with_apple_system_libs=True
|
||||
artifact_platform: arm
|
||||
- platform: ios
|
||||
os: macos-13
|
||||
test: 0
|
||||
pack: 1
|
||||
upload: 1
|
||||
pack_type: Release
|
||||
extension: ipa
|
||||
before_install: macos.sh
|
||||
preset: ios-release-conan-ccache
|
||||
conan_profile: ios-arm64
|
||||
conan_prebuilts: dependencies-ios
|
||||
conan_options: --options with_apple_system_libs=True
|
||||
- platform: msvc
|
||||
- platform: msvc-x64
|
||||
os: windows-latest
|
||||
test: 0
|
||||
pack: 1
|
||||
upload: 1
|
||||
pack_type: RelWithDebInfo
|
||||
extension: exe
|
||||
before_install: msvc.sh
|
||||
preset: windows-msvc-release
|
||||
- platform: msvc-x86
|
||||
os: windows-latest
|
||||
test: 0
|
||||
pack: 1
|
||||
pack_type: RelWithDebInfo
|
||||
extension: exe
|
||||
preset: windows-msvc-release
|
||||
- platform: mingw
|
||||
os: ubuntu-22.04
|
||||
before_install: msvc.sh
|
||||
preset: windows-msvc-release-x86
|
||||
- platform: mingw_x86_64
|
||||
os: ubuntu-24.04
|
||||
test: 0
|
||||
pack: 1
|
||||
pack_type: Release
|
||||
extension: exe
|
||||
cpack_args: -D CPACK_NSIS_EXECUTABLE=`which makensis`
|
||||
cmake_args: -G Ninja
|
||||
before_install: mingw.sh
|
||||
preset: windows-mingw-conan-linux
|
||||
conan_profile: mingw64-linux.jinja
|
||||
- platform: mingw-32
|
||||
os: ubuntu-22.04
|
||||
conan_prebuilts: dependencies-mingw-x86-64
|
||||
- platform: mingw_x86
|
||||
os: ubuntu-24.04
|
||||
test: 0
|
||||
pack: 1
|
||||
pack_type: Release
|
||||
extension: exe
|
||||
cpack_args: -D CPACK_NSIS_EXECUTABLE=`which makensis`
|
||||
cmake_args: -G Ninja
|
||||
before_install: mingw.sh
|
||||
preset: windows-mingw-conan-linux
|
||||
conan_profile: mingw32-linux.jinja
|
||||
conan_prebuilts: dependencies-mingw-x86
|
||||
- platform: android-32
|
||||
os: macos-14
|
||||
os: ubuntu-24.04
|
||||
upload: 1
|
||||
extension: apk
|
||||
preset: android-conan-ninja-release
|
||||
conan_profile: android-32
|
||||
conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT
|
||||
before_install: android.sh
|
||||
conan_profile: android-32-ndk
|
||||
conan_prebuilts: dependencies-android-armeabi-v7a
|
||||
artifact_platform: armeabi-v7a
|
||||
- platform: android-64
|
||||
os: macos-14
|
||||
os: ubuntu-24.04
|
||||
upload: 1
|
||||
extension: apk
|
||||
preset: android-conan-ninja-release
|
||||
conan_profile: android-64
|
||||
conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT
|
||||
before_install: android.sh
|
||||
conan_profile: android-64-ndk
|
||||
conan_prebuilts: dependencies-android-arm64-v8a
|
||||
artifact_platform: arm64-v8a
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
@ -107,15 +134,25 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Dependencies
|
||||
run: source '${{github.workspace}}/CI/${{matrix.platform}}/before_install.sh'
|
||||
- name: Prepare CI
|
||||
if: "${{ matrix.before_install != '' }}"
|
||||
run: source '${{github.workspace}}/CI/before_install/${{matrix.before_install}}'
|
||||
env:
|
||||
VCMI_BUILD_PLATFORM: x64
|
||||
|
||||
- name: Install Conan Dependencies
|
||||
if: "${{ matrix.conan_prebuilts != '' }}"
|
||||
run: source '${{github.workspace}}/CI/install_conan_dependencies.sh' '${{matrix.conan_prebuilts}}'
|
||||
|
||||
- name: Install vcpkg Dependencies
|
||||
if: ${{ startsWith(matrix.platform, 'msvc') }}
|
||||
run: source '${{github.workspace}}/CI/install_vcpkg_dependencies.sh' '${{matrix.platform}}'
|
||||
|
||||
# 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
|
||||
@ -146,20 +183,24 @@ jobs:
|
||||
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
|
||||
if [[ ${{github.repository_owner}} == vcmi ]]
|
||||
then
|
||||
data_url="https://github.com/vcmi-mods/vcmi-test-data/releases/download/v1.0/h3_assets.zip"
|
||||
else
|
||||
data_url="https://github.com/${{github.repository_owner}}/vcmi-test-data/releases/download/v1.0/h3_assets.zip"
|
||||
fi
|
||||
wget --progress=dot:giga "$data_url" -O 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
|
||||
- name: Install Conan
|
||||
if: "${{ matrix.conan_profile != '' }}"
|
||||
with:
|
||||
python-version: '3.10'
|
||||
run: pipx install 'conan<2.0'
|
||||
|
||||
- name: Conan setup
|
||||
- name: Install Conan profile
|
||||
if: "${{ matrix.conan_profile != '' }}"
|
||||
run: |
|
||||
pip3 install 'conan<2.0'
|
||||
conan profile new default --detect
|
||||
conan install . \
|
||||
--install-folder=conan-generated \
|
||||
@ -171,7 +212,13 @@ jobs:
|
||||
env:
|
||||
GENERATE_ONLY_BUILT_CONFIG: 1
|
||||
|
||||
- uses: actions/setup-java@v4
|
||||
# Workaround for gradle not discovering SDK that was installed via conan
|
||||
- name: Find Android NDK
|
||||
if: ${{ startsWith(matrix.platform, 'android') }}
|
||||
run: sudo ln -s -T /home/runner/.conan/data/android-ndk/r25c/_/_/package/4db1be536558d833e52e862fd84d64d75c2b3656/bin /usr/local/lib/android/sdk/ndk/25.2.9519653
|
||||
|
||||
- name: Install Java
|
||||
uses: actions/setup-java@v4
|
||||
if: ${{ startsWith(matrix.platform, 'android') }}
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
@ -202,11 +249,11 @@ jobs:
|
||||
elif [[ (${{matrix.preset}} == android-conan-ninja-release) && (${{github.ref}} != 'refs/heads/master') ]]
|
||||
then
|
||||
cmake -DENABLE_CCACHE:BOOL=ON -DANDROID_GRADLE_PROPERTIES="applicationIdSuffix=.daily;signingConfig=dailySigning;applicationLabel=VCMI daily" --preset ${{ matrix.preset }}
|
||||
elif [[ ${{matrix.platform}} != msvc ]]
|
||||
elif [[ ${{startsWith(matrix.platform, 'msvc') }} ]]
|
||||
then
|
||||
cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }}
|
||||
else
|
||||
cmake --preset ${{ matrix.preset }}
|
||||
else
|
||||
cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }}
|
||||
fi
|
||||
|
||||
- name: Build
|
||||
@ -236,10 +283,13 @@ jobs:
|
||||
if: ${{ matrix.pack == 1 }}
|
||||
run: |
|
||||
cd '${{github.workspace}}/out/build/${{matrix.preset}}'
|
||||
CPACK_PATH=`which -a cpack | grep -m1 -v -i chocolatey`
|
||||
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 }}'.*)"
|
||||
|
||||
# Workaround for CPack bug on macOS 13
|
||||
counter=0
|
||||
until cpack -C ${{matrix.pack_type}} || ((counter > 20)); do
|
||||
sleep 3
|
||||
((counter++))
|
||||
done
|
||||
rm -rf _CPack_Packages
|
||||
|
||||
- name: Artifacts
|
||||
@ -247,6 +297,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
|
||||
compression-level: 0
|
||||
path: |
|
||||
${{github.workspace}}/out/build/${{matrix.preset}}/${{ env.VCMI_PACKAGE_FILE_NAME }}.${{ matrix.extension }}
|
||||
|
||||
@ -262,32 +313,35 @@ jobs:
|
||||
echo "ANDROID_APK_PATH=$ANDROID_APK_PATH" >> $GITHUB_ENV
|
||||
echo "ANDROID_AAB_PATH=$ANDROID_AAB_PATH" >> $GITHUB_ENV
|
||||
|
||||
- name: Android apk artifacts
|
||||
- name: Upload android apk artifacts
|
||||
if: ${{ startsWith(matrix.platform, 'android') }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
|
||||
compression-level: 0
|
||||
path: |
|
||||
${{ env.ANDROID_APK_PATH }}
|
||||
|
||||
- name: Android aab artifacts
|
||||
if: ${{ startsWith(matrix.platform, 'android') }}
|
||||
- name: Upload Android aab artifacts
|
||||
if: ${{ startsWith(matrix.platform, 'android') && github.ref == 'refs/heads/master' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} - aab
|
||||
compression-level: 0
|
||||
path: |
|
||||
${{ env.ANDROID_AAB_PATH }}
|
||||
|
||||
- name: Symbols
|
||||
if: ${{ matrix.platform == 'msvc' }}
|
||||
- name: Upload debug symbols
|
||||
if: ${{ startsWith(matrix.platform, 'msvc') }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} - symbols
|
||||
compression-level: 9
|
||||
path: |
|
||||
${{github.workspace}}/**/*.pdb
|
||||
|
||||
- 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' && matrix.platform != 'mingw-32' }}
|
||||
if: ${{ (matrix.upload == 1) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master') }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
if [ -z '${{ env.ANDROID_APK_PATH }}' ] ; then
|
||||
@ -337,19 +391,20 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
if: "${{ matrix.conan_profile != '' }}"
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Ensure LF line endings
|
||||
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 '*.webm' -and -not -name '*.ico' -and -not -name '*.bat' -print0 | \
|
||||
-not -name '*.png' -and -not -name '*.ttf' -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
|
||||
run: |
|
||||
sudo apt install python3-jstyleson
|
||||
python3 CI/linux-qt6/validate_json.py
|
||||
python3 CI/validate_json.py
|
||||
|
||||
- name: Validate Markdown
|
||||
uses: DavidAnson/markdownlint-cli2-action@v18
|
||||
with:
|
||||
config: 'CI/example.markdownlint-cli2.jsonc'
|
||||
globs: '**/*.md'
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -43,6 +43,7 @@ VCMI_VS11.sdf
|
||||
*.ipch
|
||||
VCMI_VS11.opensdf
|
||||
.DS_Store
|
||||
.directory
|
||||
CMakeUserPresets.json
|
||||
compile_commands.json
|
||||
fuzzylite.pc
|
||||
|
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -1,7 +1,7 @@
|
||||
[submodule "test/googletest"]
|
||||
path = test/googletest
|
||||
url = https://github.com/google/googletest
|
||||
branch = v1.13.x
|
||||
branch = v1.15.x
|
||||
[submodule "AI/FuzzyLite"]
|
||||
path = AI/FuzzyLite
|
||||
url = https://github.com/fuzzylite/fuzzylite.git
|
||||
|
@ -12,6 +12,10 @@
|
||||
#include "../../lib/CStack.h" // TODO: remove
|
||||
// Eventually only IBattleInfoCallback and battle::Unit should be used,
|
||||
// CUnitState should be private and CStack should be removed completely
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
#include "../../lib/spells/ISpellMechanics.h"
|
||||
#include "../../lib/spells/ObstacleCasterProxy.h"
|
||||
#include "../../lib/battle/CObstacleInstance.h"
|
||||
|
||||
uint64_t averageDmg(const DamageRange & range)
|
||||
{
|
||||
@ -25,9 +29,64 @@ void DamageCache::cacheDamage(const battle::Unit * attacker, const battle::Unit
|
||||
damageCache[attacker->unitId()][defender->unitId()] = static_cast<float>(damage) / attacker->getCount();
|
||||
}
|
||||
|
||||
|
||||
void DamageCache::buildDamageCache(std::shared_ptr<HypotheticBattle> hb, int side)
|
||||
void DamageCache::buildObstacleDamageCache(std::shared_ptr<HypotheticBattle> hb, BattleSide side)
|
||||
{
|
||||
for(const auto & obst : hb->battleGetAllObstacles(side))
|
||||
{
|
||||
auto spellObstacle = dynamic_cast<const SpellCreatedObstacle *>(obst.get());
|
||||
|
||||
if(!spellObstacle || !obst->triggersEffects())
|
||||
continue;
|
||||
|
||||
auto triggerAbility = VLC->spells()->getById(obst->getTrigger());
|
||||
auto triggerIsNegative = triggerAbility->isNegative() || triggerAbility->isDamage();
|
||||
|
||||
if(!triggerIsNegative)
|
||||
continue;
|
||||
|
||||
std::unique_ptr<spells::BattleCast> cast = nullptr;
|
||||
std::unique_ptr<spells::ObstacleCasterProxy> caster = nullptr;
|
||||
if(spellObstacle->obstacleType == SpellCreatedObstacle::EObstacleType::SPELL_CREATED)
|
||||
{
|
||||
const auto * hero = hb->battleGetFightingHero(spellObstacle->casterSide);
|
||||
caster = std::make_unique<spells::ObstacleCasterProxy>(hb->getSidePlayer(spellObstacle->casterSide), hero, *spellObstacle);
|
||||
cast = std::make_unique<spells::BattleCast>(spells::BattleCast(hb.get(), caster.get(), spells::Mode::PASSIVE, obst->getTrigger().toSpell()));
|
||||
}
|
||||
|
||||
auto affectedHexes = obst->getAffectedTiles();
|
||||
auto stacks = hb->battleGetUnitsIf([](const battle::Unit * u) -> bool {
|
||||
return u->alive() && !u->isTurret() && u->getPosition().isValid();
|
||||
});
|
||||
|
||||
auto inner = std::make_shared<HypotheticBattle>(hb->env, hb);
|
||||
|
||||
for(auto stack : stacks)
|
||||
{
|
||||
auto updated = inner->getForUpdate(stack->unitId());
|
||||
|
||||
spells::Target target;
|
||||
target.push_back(spells::Destination(updated.get()));
|
||||
|
||||
if(cast)
|
||||
cast->castEval(inner->getServerCallback(), target);
|
||||
|
||||
auto damageDealt = stack->getAvailableHealth() - updated->getAvailableHealth();
|
||||
|
||||
for(auto hex : affectedHexes)
|
||||
{
|
||||
obstacleDamage[hex][stack->unitId()] = damageDealt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DamageCache::buildDamageCache(std::shared_ptr<HypotheticBattle> hb, BattleSide side)
|
||||
{
|
||||
if(parent == nullptr)
|
||||
{
|
||||
buildObstacleDamageCache(hb, side);
|
||||
}
|
||||
|
||||
auto stacks = hb->battleGetUnitsIf([=](const battle::Unit * u) -> bool
|
||||
{
|
||||
return u->isValidTarget();
|
||||
@ -70,6 +129,23 @@ int64_t DamageCache::getDamage(const battle::Unit * attacker, const battle::Unit
|
||||
return damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount();
|
||||
}
|
||||
|
||||
int64_t DamageCache::getObstacleDamage(BattleHex hex, const battle::Unit * defender)
|
||||
{
|
||||
if(parent)
|
||||
return parent->getObstacleDamage(hex, defender);
|
||||
|
||||
auto damages = obstacleDamage.find(hex);
|
||||
|
||||
if(damages == obstacleDamage.end())
|
||||
return 0;
|
||||
|
||||
auto damage = damages->second.find(defender->unitId());
|
||||
|
||||
return damage == damages->second.end()
|
||||
? 0
|
||||
: damage->second;
|
||||
}
|
||||
|
||||
int64_t DamageCache::getOriginalDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb)
|
||||
{
|
||||
if(parent)
|
||||
@ -93,6 +169,8 @@ int64_t DamageCache::getOriginalDamage(const battle::Unit * attacker, const batt
|
||||
AttackPossibility::AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack)
|
||||
: from(from), dest(dest), attack(attack)
|
||||
{
|
||||
this->attack.attackerPos = from;
|
||||
this->attack.defenderPos = dest;
|
||||
}
|
||||
|
||||
float AttackPossibility::damageDiff() const
|
||||
@ -199,6 +277,8 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(
|
||||
if(attackInfo.shooting)
|
||||
return 0;
|
||||
|
||||
std::set<uint32_t> checkedUnits;
|
||||
|
||||
auto attacker = attackInfo.attacker;
|
||||
auto hexes = attacker->getSurroundingHexes(hex);
|
||||
for(BattleHex tile : hexes)
|
||||
@ -206,9 +286,13 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(
|
||||
auto st = state->battleGetUnitByPos(tile, true);
|
||||
if(!st || !state->battleMatchOwner(st, attacker))
|
||||
continue;
|
||||
if(vstd::contains(checkedUnits, st->unitId()))
|
||||
continue;
|
||||
if(!state->battleCanShoot(st))
|
||||
continue;
|
||||
|
||||
checkedUnits.insert(st->unitId());
|
||||
|
||||
// FIXME: provide distance info for Jousting bonus
|
||||
BattleAttackInfo rangeAttackInfo(st, attacker, 0, true);
|
||||
rangeAttackInfo.defenderPos = hex;
|
||||
@ -218,9 +302,10 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(
|
||||
|
||||
auto rangeDmg = state->battleEstimateDamage(rangeAttackInfo);
|
||||
auto meleeDmg = state->battleEstimateDamage(meleeAttackInfo);
|
||||
auto cachedDmg = damageCache.getOriginalDamage(st, attacker, state);
|
||||
|
||||
int64_t gain = averageDmg(rangeDmg.damage) - averageDmg(meleeDmg.damage) + 1;
|
||||
res += gain;
|
||||
res += gain * cachedDmg / std::max<uint64_t>(1, averageDmg(rangeDmg.damage));
|
||||
}
|
||||
|
||||
return res;
|
||||
@ -243,7 +328,7 @@ AttackPossibility AttackPossibility::evaluate(
|
||||
|
||||
std::vector<BattleHex> defenderHex;
|
||||
if(attackInfo.shooting)
|
||||
defenderHex = defender->getHexes();
|
||||
defenderHex.push_back(defender->getPosition());
|
||||
else
|
||||
defenderHex = CStack::meleeAttackHexes(attacker, defender, hex);
|
||||
|
||||
@ -261,63 +346,114 @@ AttackPossibility AttackPossibility::evaluate(
|
||||
if (!attackInfo.shooting)
|
||||
ap.attackerState->setPosition(hex);
|
||||
|
||||
std::vector<const battle::Unit*> units;
|
||||
std::vector<const battle::Unit *> defenderUnits;
|
||||
std::vector<const battle::Unit *> retaliatedUnits = {attacker};
|
||||
std::vector<const battle::Unit *> affectedUnits;
|
||||
|
||||
if (attackInfo.shooting)
|
||||
units = state->getAttackedBattleUnits(attacker, defHex, true, BattleHex::INVALID);
|
||||
defenderUnits = state->getAttackedBattleUnits(attacker, defender, defHex, true, hex, defender->getPosition());
|
||||
else
|
||||
units = state->getAttackedBattleUnits(attacker, defHex, false, hex);
|
||||
|
||||
// ensure the defender is also affected
|
||||
bool addDefender = true;
|
||||
for(auto unit : units)
|
||||
{
|
||||
if (unit->unitId() == defender->unitId())
|
||||
defenderUnits = state->getAttackedBattleUnits(attacker, defender, defHex, false, hex, defender->getPosition());
|
||||
retaliatedUnits = state->getAttackedBattleUnits(defender, attacker, hex, false, defender->getPosition(), hex);
|
||||
|
||||
// attacker can not melle-attack itself but still can hit that place where it was before moving
|
||||
vstd::erase_if(defenderUnits, [attacker](const battle::Unit * u) -> bool { return u->unitId() == attacker->unitId(); });
|
||||
|
||||
if(!vstd::contains_if(retaliatedUnits, [attacker](const battle::Unit * u) -> bool { return u->unitId() == attacker->unitId(); }))
|
||||
{
|
||||
addDefender = false;
|
||||
break;
|
||||
retaliatedUnits.push_back(attacker);
|
||||
}
|
||||
|
||||
auto obstacleDamage = damageCache.getObstacleDamage(hex, attacker);
|
||||
|
||||
if(obstacleDamage > 0)
|
||||
{
|
||||
ap.attackerDamageReduce += calculateDamageReduce(nullptr, attacker, obstacleDamage, damageCache, state);
|
||||
|
||||
ap.attackerState->damage(obstacleDamage);
|
||||
}
|
||||
}
|
||||
|
||||
if(addDefender)
|
||||
units.push_back(defender);
|
||||
|
||||
for(auto u : units)
|
||||
// ensure the defender is also affected
|
||||
if(!vstd::contains_if(defenderUnits, [defender](const battle::Unit * u) -> bool { return u->unitId() == defender->unitId(); }))
|
||||
{
|
||||
if(!ap.attackerState->alive())
|
||||
break;
|
||||
defenderUnits.push_back(defender);
|
||||
}
|
||||
|
||||
affectedUnits = defenderUnits;
|
||||
vstd::concatenate(affectedUnits, retaliatedUnits);
|
||||
|
||||
logAi->trace("Attacked battle units count %d, %d->%d", affectedUnits.size(), hex.hex, defHex.hex);
|
||||
|
||||
std::map<uint32_t, std::shared_ptr<battle::CUnitState>> defenderStates;
|
||||
|
||||
for(auto u : affectedUnits)
|
||||
{
|
||||
if(u->unitId() == attacker->unitId())
|
||||
continue;
|
||||
|
||||
auto defenderState = u->acquireState();
|
||||
ap.affectedUnits.push_back(defenderState);
|
||||
|
||||
for(int i = 0; i < totalAttacks; i++)
|
||||
ap.affectedUnits.push_back(defenderState);
|
||||
defenderStates[u->unitId()] = defenderState;
|
||||
}
|
||||
|
||||
for(int i = 0; i < totalAttacks; i++)
|
||||
{
|
||||
if(!ap.attackerState->alive() || !defenderStates[defender->unitId()]->alive())
|
||||
break;
|
||||
|
||||
for(auto u : defenderUnits)
|
||||
{
|
||||
auto defenderState = defenderStates.at(u->unitId());
|
||||
|
||||
int64_t damageDealt;
|
||||
int64_t damageReceived;
|
||||
float defenderDamageReduce;
|
||||
float attackerDamageReduce;
|
||||
|
||||
DamageEstimation retaliation;
|
||||
auto attackDmg = state->battleEstimateDamage(ap.attack, &retaliation);
|
||||
|
||||
vstd::amin(attackDmg.damage.min, defenderState->getAvailableHealth());
|
||||
vstd::amin(attackDmg.damage.max, defenderState->getAvailableHealth());
|
||||
|
||||
vstd::amin(retaliation.damage.min, ap.attackerState->getAvailableHealth());
|
||||
vstd::amin(retaliation.damage.max, ap.attackerState->getAvailableHealth());
|
||||
|
||||
damageDealt = averageDmg(attackDmg.damage);
|
||||
defenderDamageReduce = calculateDamageReduce(attacker, defender, damageDealt, damageCache, state);
|
||||
vstd::amin(damageDealt, defenderState->getAvailableHealth());
|
||||
|
||||
defenderDamageReduce = calculateDamageReduce(attacker, u, damageDealt, damageCache, state);
|
||||
ap.attackerState->afterAttack(attackInfo.shooting, false);
|
||||
|
||||
//FIXME: use ranged retaliation
|
||||
damageReceived = 0;
|
||||
attackerDamageReduce = 0;
|
||||
|
||||
if (!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked)
|
||||
if (!attackInfo.shooting && u->unitId() == defender->unitId() && defenderState->ableToRetaliate() && !counterAttacksBlocked)
|
||||
{
|
||||
damageReceived = averageDmg(retaliation.damage);
|
||||
attackerDamageReduce = calculateDamageReduce(defender, attacker, damageReceived, damageCache, state);
|
||||
for(auto retaliated : retaliatedUnits)
|
||||
{
|
||||
if(retaliated->unitId() == attacker->unitId())
|
||||
{
|
||||
int64_t damageReceived = averageDmg(retaliation.damage);
|
||||
|
||||
vstd::amin(damageReceived, ap.attackerState->getAvailableHealth());
|
||||
|
||||
attackerDamageReduce = calculateDamageReduce(defender, retaliated, damageReceived, damageCache, state);
|
||||
ap.attackerState->damage(damageReceived);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto retaliationCollateral = state->battleEstimateDamage(defender, retaliated, 0);
|
||||
int64_t damageReceived = averageDmg(retaliationCollateral.damage);
|
||||
|
||||
vstd::amin(damageReceived, retaliated->getAvailableHealth());
|
||||
|
||||
if(defender->unitSide() == retaliated->unitSide())
|
||||
defenderDamageReduce += calculateDamageReduce(defender, retaliated, damageReceived, damageCache, state);
|
||||
else
|
||||
ap.collateralDamageReduce += calculateDamageReduce(defender, retaliated, damageReceived, damageCache, state);
|
||||
|
||||
defenderStates.at(retaliated->unitId())->damage(damageReceived);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
defenderState->afterAttack(attackInfo.shooting, true);
|
||||
}
|
||||
|
||||
@ -331,21 +467,30 @@ AttackPossibility AttackPossibility::evaluate(
|
||||
if(attackerSide == u->unitSide())
|
||||
ap.collateralDamageReduce += defenderDamageReduce;
|
||||
|
||||
if(u->unitId() == defender->unitId() ||
|
||||
(!attackInfo.shooting && CStack::isMeleeAttackPossible(u, attacker, hex)))
|
||||
if(u->unitId() == defender->unitId()
|
||||
|| (!attackInfo.shooting && CStack::isMeleeAttackPossible(u, attacker, hex)))
|
||||
{
|
||||
//FIXME: handle RANGED_RETALIATION ?
|
||||
ap.attackerDamageReduce += attackerDamageReduce;
|
||||
}
|
||||
|
||||
ap.attackerState->damage(damageReceived);
|
||||
defenderState->damage(damageDealt);
|
||||
|
||||
if (!ap.attackerState->alive() || !defenderState->alive())
|
||||
break;
|
||||
if(u->unitId() == defender->unitId())
|
||||
{
|
||||
ap.defenderDead = !defenderState->alive();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=2
|
||||
logAi->trace("BattleAI AP: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld",
|
||||
attackInfo.attacker->unitType()->getJsonKey(),
|
||||
attackInfo.defender->unitType()->getJsonKey(),
|
||||
(int)ap.dest, (int)ap.from, (int)ap.affectedUnits.size(),
|
||||
ap.defenderDamageReduce, ap.attackerDamageReduce, ap.collateralDamageReduce, ap.shootersBlockedDmg);
|
||||
#endif
|
||||
|
||||
if(!bestAp.dest.isValid() || ap.attackValue() > bestAp.attackValue())
|
||||
bestAp = ap;
|
||||
}
|
||||
|
@ -18,16 +18,20 @@ class DamageCache
|
||||
{
|
||||
private:
|
||||
std::unordered_map<uint32_t, std::unordered_map<uint32_t, float>> damageCache;
|
||||
std::map<BattleHex, std::unordered_map<uint32_t, int64_t>> obstacleDamage;
|
||||
DamageCache * parent;
|
||||
|
||||
void buildObstacleDamageCache(std::shared_ptr<HypotheticBattle> hb, BattleSide side);
|
||||
|
||||
public:
|
||||
DamageCache() : parent(nullptr) {}
|
||||
DamageCache(DamageCache * parent) : parent(parent) {}
|
||||
|
||||
void cacheDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb);
|
||||
int64_t getDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb);
|
||||
int64_t getObstacleDamage(BattleHex hex, const battle::Unit * defender);
|
||||
int64_t getOriginalDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb);
|
||||
void buildDamageCache(std::shared_ptr<HypotheticBattle> hb, int side);
|
||||
void buildDamageCache(std::shared_ptr<HypotheticBattle> hb, BattleSide side);
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@ -49,6 +53,7 @@ public:
|
||||
float attackerDamageReduce = 0; //usually by counter-attack
|
||||
float collateralDamageReduce = 0; // friendly fire (usually by two-hex attacks)
|
||||
int64_t shootersBlockedDmg = 0;
|
||||
bool defenderDead = false;
|
||||
|
||||
AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack_);
|
||||
|
||||
|
@ -1,106 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<CodeBlocks_project_file>
|
||||
<FileVersion major="1" minor="6" />
|
||||
<Project>
|
||||
<Option title="BattleAI" />
|
||||
<Option pch_mode="2" />
|
||||
<Option compiler="gcc" />
|
||||
<Build>
|
||||
<Target title="Debug-win32">
|
||||
<Option platforms="Windows;" />
|
||||
<Option output="../BattleAI" imp_lib="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).a" def_file="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).def" prefix_auto="1" extension_auto="1" />
|
||||
<Option object_output="obj/Debug/x86/" />
|
||||
<Option type="3" />
|
||||
<Option compiler="gcc" />
|
||||
<Compiler>
|
||||
<Add option="-g" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add option="-lboost_thread$(#boost.libsuffix32)" />
|
||||
<Add option="-lboost_system$(#boost.libsuffix32)" />
|
||||
<Add option="-lVCMI_lib" />
|
||||
<Add directory="$(#boost.lib32)" />
|
||||
</Linker>
|
||||
</Target>
|
||||
<Target title="Release-win32">
|
||||
<Option platforms="Windows;" />
|
||||
<Option output="../BattleAI" imp_lib="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).a" def_file="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).def" prefix_auto="1" extension_auto="1" />
|
||||
<Option object_output="obj/Release/x86/" />
|
||||
<Option type="3" />
|
||||
<Option compiler="gcc" />
|
||||
<Compiler>
|
||||
<Add option="-O2" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add option="-s" />
|
||||
<Add option="-lboost_thread$(#boost.libsuffix32)" />
|
||||
<Add option="-lboost_system$(#boost.libsuffix32)" />
|
||||
<Add option="-lVCMI_lib" />
|
||||
<Add directory="$(#boost.lib32)" />
|
||||
</Linker>
|
||||
</Target>
|
||||
<Target title="Debug-win64">
|
||||
<Option platforms="Windows;" />
|
||||
<Option output="../BattleAI" imp_lib="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).a" def_file="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).def" prefix_auto="1" extension_auto="1" />
|
||||
<Option object_output="obj/Debug/x64/" />
|
||||
<Option type="3" />
|
||||
<Option compiler="gnu_gcc_compiler_x64" />
|
||||
<Compiler>
|
||||
<Add option="-g" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add option="-lboost_thread$(#boost.libsuffix64)" />
|
||||
<Add option="-lboost_system$(#boost.libsuffix64)" />
|
||||
<Add option="-lVCMI_lib" />
|
||||
<Add directory="$(#boost.lib64)" />
|
||||
</Linker>
|
||||
</Target>
|
||||
</Build>
|
||||
<Compiler>
|
||||
<Add option="-pedantic" />
|
||||
<Add option="-Wextra" />
|
||||
<Add option="-Wall" />
|
||||
<Add option="-std=gnu++11" />
|
||||
<Add option="-fexceptions" />
|
||||
<Add option="-Wpointer-arith" />
|
||||
<Add option="-Wno-switch" />
|
||||
<Add option="-Wno-sign-compare" />
|
||||
<Add option="-Wno-unused-parameter" />
|
||||
<Add option="-Wno-overloaded-virtual" />
|
||||
<Add option="-DBOOST_ALL_DYN_LINK" />
|
||||
<Add option="-DBOOST_SYSTEM_NO_DEPRECATED" />
|
||||
<Add option="-D_WIN32_WINNT=0x0600" />
|
||||
<Add option="-D_WIN32" />
|
||||
<Add directory="$(#boost.include)" />
|
||||
<Add directory="../../include" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add directory="../.." />
|
||||
</Linker>
|
||||
<Unit filename="AttackPossibility.cpp" />
|
||||
<Unit filename="AttackPossibility.h" />
|
||||
<Unit filename="BattleAI.cpp" />
|
||||
<Unit filename="BattleAI.h" />
|
||||
<Unit filename="CMakeLists.txt" />
|
||||
<Unit filename="EnemyInfo.cpp" />
|
||||
<Unit filename="EnemyInfo.h" />
|
||||
<Unit filename="PossibleSpellcast.cpp" />
|
||||
<Unit filename="PossibleSpellcast.h" />
|
||||
<Unit filename="PotentialTargets.cpp" />
|
||||
<Unit filename="PotentialTargets.h" />
|
||||
<Unit filename="StackWithBonuses.cpp" />
|
||||
<Unit filename="StackWithBonuses.h" />
|
||||
<Unit filename="StdInc.h">
|
||||
<Option compile="1" />
|
||||
<Option weight="0" />
|
||||
</Unit>
|
||||
<Unit filename="ThreatMap.cpp" />
|
||||
<Unit filename="ThreatMap.h" />
|
||||
<Unit filename="common.cpp" />
|
||||
<Unit filename="common.h" />
|
||||
<Unit filename="main.cpp" />
|
||||
<Extensions>
|
||||
<lib_finder disable_auto="1" />
|
||||
</Extensions>
|
||||
</Project>
|
||||
</CodeBlocks_project_file>
|
@ -23,15 +23,17 @@
|
||||
#include "../../lib/battle/BattleAction.h"
|
||||
#include "../../lib/battle/BattleStateInfoForRetreat.h"
|
||||
#include "../../lib/battle/CObstacleInstance.h"
|
||||
#include "../../lib/StartInfo.h"
|
||||
#include "../../lib/CStack.h" // TODO: remove
|
||||
// Eventually only IBattleInfoCallback and battle::Unit should be used,
|
||||
// CUnitState should be private and CStack should be removed completely
|
||||
#include "../../lib/logging/VisualLogger.h"
|
||||
|
||||
#define LOGL(text) print(text)
|
||||
#define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
|
||||
|
||||
CBattleAI::CBattleAI()
|
||||
: side(-1),
|
||||
: side(BattleSide::NONE),
|
||||
wasWaitingForRealize(false),
|
||||
wasUnlockingGs(false)
|
||||
{
|
||||
@ -47,6 +49,17 @@ CBattleAI::~CBattleAI()
|
||||
}
|
||||
}
|
||||
|
||||
void logHexNumbers()
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logVisual->updateWithLock("hexes", [](IVisualLogBuilder & b)
|
||||
{
|
||||
for(BattleHex hex = BattleHex(0); hex < GameConstants::BFIELD_SIZE; hex = BattleHex(hex + 1))
|
||||
b.addText(hex, std::to_string(hex.hex));
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB)
|
||||
{
|
||||
env = ENV;
|
||||
@ -57,6 +70,8 @@ void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
|
||||
CB->waitTillRealize = false;
|
||||
CB->unlockGsWhenWaiting = false;
|
||||
movesSkippedByDefense = 0;
|
||||
|
||||
logHexNumbers();
|
||||
}
|
||||
|
||||
void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB, AutocombatPreferences autocombatPreferences)
|
||||
@ -86,7 +101,7 @@ void CBattleAI::yourTacticPhase(const BattleID & battleID, int distance)
|
||||
cb->battleMakeTacticAction(battleID, BattleAction::makeEndOFTacticPhase(cb->getBattle(battleID)->battleGetTacticsSide()));
|
||||
}
|
||||
|
||||
static float getStrengthRatio(std::shared_ptr<CBattleInfoCallback> cb, int side)
|
||||
static float getStrengthRatio(std::shared_ptr<CBattleInfoCallback> cb, BattleSide side)
|
||||
{
|
||||
auto stacks = cb->battleGetAllStacks();
|
||||
auto our = 0;
|
||||
@ -108,6 +123,11 @@ static float getStrengthRatio(std::shared_ptr<CBattleInfoCallback> cb, int side)
|
||||
return enemy == 0 ? 1.0f : static_cast<float>(our) / enemy;
|
||||
}
|
||||
|
||||
int getSimulationTurnsCount(const StartInfo * startInfo)
|
||||
{
|
||||
return startInfo->difficulty < 4 ? 2 : 10;
|
||||
}
|
||||
|
||||
void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
|
||||
{
|
||||
LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName());
|
||||
@ -140,18 +160,19 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
|
||||
logAi->trace("Build evaluator and targets");
|
||||
#endif
|
||||
|
||||
BattleEvaluator evaluator(env, cb, stack, playerID, battleID, side, getStrengthRatio(cb->getBattle(battleID), side));
|
||||
BattleEvaluator evaluator(
|
||||
env, cb, stack, playerID, battleID, side,
|
||||
getStrengthRatio(cb->getBattle(battleID), side),
|
||||
getSimulationTurnsCount(env->game()->getStartInfo()));
|
||||
|
||||
result = evaluator.selectStackAction(stack);
|
||||
|
||||
if(autobattlePreferences.enableSpellsUsage && !skipCastUntilNextBattle && evaluator.canCastSpell())
|
||||
if(autobattlePreferences.enableSpellsUsage && evaluator.canCastSpell())
|
||||
{
|
||||
auto spelCasted = evaluator.attemptCastingSpell(stack);
|
||||
|
||||
if(spelCasted)
|
||||
return;
|
||||
|
||||
skipCastUntilNextBattle = true;
|
||||
}
|
||||
|
||||
logAi->trace("Spellcast attempt completed in %lld", timeElapsed(start));
|
||||
@ -176,7 +197,7 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
|
||||
movesSkippedByDefense = 0;
|
||||
}
|
||||
|
||||
logAi->trace("BattleAI decission made in %lld", timeElapsed(start));
|
||||
logAi->trace("BattleAI decision made in %lld", timeElapsed(start));
|
||||
|
||||
cb->battleMakeUnitAction(battleID, result);
|
||||
}
|
||||
@ -206,7 +227,7 @@ BattleAction CBattleAI::useCatapult(const BattleID & battleID, const CStack * st
|
||||
{
|
||||
auto wallState = cb->getBattle(battleID)->battleGetWallState(wallPart);
|
||||
|
||||
if(wallState == EWallState::REINFORCED || wallState == EWallState::INTACT || wallState == EWallState::DAMAGED)
|
||||
if(wallState != EWallState::NONE && wallState != EWallState::DESTROYED)
|
||||
{
|
||||
targetHex = cb->getBattle(battleID)->wallPartToBattleHex(wallPart);
|
||||
break;
|
||||
@ -229,12 +250,10 @@ BattleAction CBattleAI::useCatapult(const BattleID & battleID, const CStack * st
|
||||
return attack;
|
||||
}
|
||||
|
||||
void CBattleAI::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side, bool replayAllowed)
|
||||
void CBattleAI::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide Side, bool replayAllowed)
|
||||
{
|
||||
LOG_TRACE(logAi);
|
||||
side = Side;
|
||||
|
||||
skipCastUntilNextBattle = false;
|
||||
}
|
||||
|
||||
void CBattleAI::print(const std::string &text) const
|
||||
|
@ -27,7 +27,7 @@ struct CurrentOffensivePotential
|
||||
std::map<const CStack *, PotentialTargets> ourAttacks;
|
||||
std::map<const CStack *, PotentialTargets> enemyAttacks;
|
||||
|
||||
CurrentOffensivePotential(ui8 side)
|
||||
CurrentOffensivePotential(BattleSide side)
|
||||
{
|
||||
for(auto stack : cbc->battleGetStacks())
|
||||
{
|
||||
@ -50,11 +50,11 @@ struct CurrentOffensivePotential
|
||||
return ourPotential - enemyPotential;
|
||||
}
|
||||
};
|
||||
*/ // These lines may be usefull but they are't used in the code.
|
||||
*/ // These lines may be useful but they are't used in the code.
|
||||
|
||||
class CBattleAI : public CBattleGameInterface
|
||||
{
|
||||
int side;
|
||||
BattleSide side;
|
||||
std::shared_ptr<CBattleCallback> cb;
|
||||
std::shared_ptr<Environment> env;
|
||||
|
||||
@ -62,7 +62,6 @@ class CBattleAI : public CBattleGameInterface
|
||||
bool wasWaitingForRealize;
|
||||
bool wasUnlockingGs;
|
||||
int movesSkippedByDefense;
|
||||
bool skipCastUntilNextBattle;
|
||||
|
||||
public:
|
||||
CBattleAI();
|
||||
@ -80,7 +79,7 @@ public:
|
||||
BattleAction useCatapult(const BattleID & battleID, const CStack *stack);
|
||||
BattleAction useHealingTent(const BattleID & battleID, const CStack *stack);
|
||||
|
||||
void battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool Side, bool replayAllowed) override;
|
||||
void battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, BattleSide side, bool replayAllowed) override;
|
||||
//void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
|
||||
//void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
|
||||
//void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
|
||||
@ -93,7 +92,7 @@ public:
|
||||
//void battleSpellCast(const BattleSpellCast *sc) override;
|
||||
//void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks
|
||||
//void battleTriggerEffect(const BattleTriggerEffect & bte) override;
|
||||
//void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right
|
||||
//void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide side) override; //called by engine when battle starts; side=0 - left, side=1 - right
|
||||
//void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
|
||||
AutocombatPreferences autobattlePreferences = AutocombatPreferences();
|
||||
};
|
||||
|
@ -1,168 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="RD|Win32">
|
||||
<Configuration>RD</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="RD|x64">
|
||||
<Configuration>RD</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{C0300513-E845-43B4-9A4F-E8817EAEF57C}</ProjectGuid>
|
||||
<RootNamespace>BattleAI</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<PlatformToolset>v140_xp</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<PlatformToolset>v140_xp</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<PlatformToolset>v140_xp</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\..\VCMI_global_debug.props" />
|
||||
<Import Project="..\..\VCMI_global.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\..\VCMI_global_debug.props" />
|
||||
<Import Project="..\..\VCMI_global.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\..\VCMI_global_release.props" />
|
||||
<Import Project="..\..\VCMI_global.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\..\VCMI_global_release.props" />
|
||||
<Import Project="..\..\VCMI_global.props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<OutDir>$(VCMI_Out)\AI\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<OutDir>$(VCMI_Out)\AI\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">
|
||||
<OutDir>$(VCMI_Out)/AI</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'">
|
||||
<OutDir>$(VCMI_Out)\AI\</OutDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
|
||||
<AdditionalOptions>/Zm159 %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>..\..\..\libs;..\..;..</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
|
||||
<AdditionalOptions>/Zm159 %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(VCMI_Out)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
|
||||
<AdditionalOptions>/Zm159 %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AttackPossibility.cpp" />
|
||||
<ClCompile Include="common.cpp" />
|
||||
<ClCompile Include="EnemyInfo.cpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="PossibleSpellcast.cpp" />
|
||||
<ClCompile Include="PotentialTargets.cpp" />
|
||||
<ClCompile Include="StackWithBonuses.cpp" />
|
||||
<ClCompile Include="StdInc.cpp">
|
||||
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">StdInc.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='RD|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="BattleAI.cpp" />
|
||||
<ClCompile Include="ThreatMap.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AttackPossibility.h" />
|
||||
<ClInclude Include="common.h" />
|
||||
<ClInclude Include="EnemyInfo.h" />
|
||||
<ClInclude Include="PossibleSpellcast.h" />
|
||||
<ClInclude Include="PotentialTargets.h" />
|
||||
<ClInclude Include="StackWithBonuses.h" />
|
||||
<ClInclude Include="StdInc.h" />
|
||||
<ClInclude Include="BattleAI.h" />
|
||||
<ClInclude Include="..\..\Global.h" />
|
||||
<ClInclude Include="ThreatMap.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
@ -17,6 +17,7 @@
|
||||
#include "../../lib/CStopWatch.h"
|
||||
#include "../../lib/CThreadHelper.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/entities/building/TownFortifications.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
#include "../../lib/spells/ISpellMechanics.h"
|
||||
#include "../../lib/battle/BattleStateInfoForRetreat.h"
|
||||
@ -49,6 +50,43 @@ SpellTypes spellType(const CSpell * spell)
|
||||
return SpellTypes::OTHER;
|
||||
}
|
||||
|
||||
BattleEvaluator::BattleEvaluator(
|
||||
std::shared_ptr<Environment> env,
|
||||
std::shared_ptr<CBattleCallback> cb,
|
||||
const battle::Unit * activeStack,
|
||||
PlayerColor playerID,
|
||||
BattleID battleID,
|
||||
BattleSide side,
|
||||
float strengthRatio,
|
||||
int simulationTurnsCount)
|
||||
:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio, simulationTurnsCount),
|
||||
cachedAttack(), playerID(playerID), side(side), env(env),
|
||||
cb(cb), strengthRatio(strengthRatio), battleID(battleID), simulationTurnsCount(simulationTurnsCount)
|
||||
{
|
||||
hb = std::make_shared<HypotheticBattle>(env.get(), cb->getBattle(battleID));
|
||||
damageCache.buildDamageCache(hb, side);
|
||||
|
||||
targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
|
||||
}
|
||||
|
||||
BattleEvaluator::BattleEvaluator(
|
||||
std::shared_ptr<Environment> env,
|
||||
std::shared_ptr<CBattleCallback> cb,
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
DamageCache & damageCache,
|
||||
const battle::Unit * activeStack,
|
||||
PlayerColor playerID,
|
||||
BattleID battleID,
|
||||
BattleSide side,
|
||||
float strengthRatio,
|
||||
int simulationTurnsCount)
|
||||
:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio, simulationTurnsCount),
|
||||
cachedAttack(), playerID(playerID), side(side), env(env), cb(cb), hb(hb),
|
||||
damageCache(damageCache), strengthRatio(strengthRatio), battleID(battleID), simulationTurnsCount(simulationTurnsCount)
|
||||
{
|
||||
targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
|
||||
}
|
||||
|
||||
std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
|
||||
{
|
||||
std::vector<BattleHex> result;
|
||||
@ -81,6 +119,14 @@ std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
|
||||
return result;
|
||||
}
|
||||
|
||||
bool BattleEvaluator::hasWorkingTowers() const
|
||||
{
|
||||
bool keepIntact = cb->getBattle(battleID)->battleGetWallState(EWallPart::KEEP) != EWallState::NONE && cb->getBattle(battleID)->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED;
|
||||
bool upperIntact = cb->getBattle(battleID)->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::NONE && cb->getBattle(battleID)->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED;
|
||||
bool bottomIntact = cb->getBattle(battleID)->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::NONE && cb->getBattle(battleID)->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED;
|
||||
return keepIntact || upperIntact || bottomIntact;
|
||||
}
|
||||
|
||||
std::optional<PossibleSpellcast> BattleEvaluator::findBestCreatureSpell(const CStack *stack)
|
||||
{
|
||||
//TODO: faerie dragon type spell should be selected by server
|
||||
@ -123,6 +169,14 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
|
||||
auto moveTarget = scoreEvaluator.findMoveTowardsUnreachable(stack, *targets, damageCache, hb);
|
||||
float score = EvaluationResult::INEFFECTIVE_SCORE;
|
||||
auto enemyMellee = hb->getUnitsIf([this](const battle::Unit* u) -> bool
|
||||
{
|
||||
return u->unitSide() == BattleSide::ATTACKER && !hb->battleCanShoot(u);
|
||||
});
|
||||
bool siegeDefense = stack->unitSide() == BattleSide::DEFENDER
|
||||
&& !stack->canShoot()
|
||||
&& hasWorkingTowers()
|
||||
&& !enemyMellee.empty();
|
||||
|
||||
if(targets->possibleAttacks.empty() && bestSpellcast.has_value())
|
||||
{
|
||||
@ -136,11 +190,13 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
logAi->trace("Evaluating attack for %s", stack->getDescription());
|
||||
#endif
|
||||
|
||||
auto evaluationResult = scoreEvaluator.findBestTarget(stack, *targets, damageCache, hb);
|
||||
auto evaluationResult = scoreEvaluator.findBestTarget(stack, *targets, damageCache, hb, siegeDefense);
|
||||
auto & bestAttack = evaluationResult.bestAttack;
|
||||
|
||||
cachedAttack = bestAttack;
|
||||
cachedScore = evaluationResult.score;
|
||||
cachedAttack.ap = bestAttack;
|
||||
cachedAttack.score = evaluationResult.score;
|
||||
cachedAttack.turn = 0;
|
||||
cachedAttack.waited = evaluationResult.wait;
|
||||
|
||||
//TODO: consider more complex spellcast evaluation, f.e. because "re-retaliation" during enemy move in same turn for melee attack etc.
|
||||
if(bestSpellcast.has_value() && bestSpellcast->value > bestAttack.damageDiff())
|
||||
@ -167,7 +223,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
score
|
||||
);
|
||||
|
||||
if (moveTarget.scorePerTurn <= score)
|
||||
if (moveTarget.score <= score)
|
||||
{
|
||||
if(evaluationResult.wait)
|
||||
{
|
||||
@ -186,37 +242,59 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
{
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
else
|
||||
|
||||
bool isTargetOutsideFort = !hb->battleIsInsideWalls(bestAttack.from);
|
||||
bool siegeDefense = stack->unitSide() == BattleSide::DEFENDER
|
||||
&& !bestAttack.attack.shooting
|
||||
&& hasWorkingTowers()
|
||||
&& !enemyMellee.empty()
|
||||
&& isTargetOutsideFort;
|
||||
|
||||
if(siegeDefense)
|
||||
{
|
||||
activeActionMade = true;
|
||||
return BattleAction::makeMeleeAttack(stack, bestAttack.attack.defender->getPosition(), bestAttack.from);
|
||||
logAi->trace("Evaluating exchange at %d self-defense", stack->getPosition().hex);
|
||||
|
||||
BattleAttackInfo bai(stack, stack, 0, false);
|
||||
AttackPossibility apDefend(stack->getPosition(), stack->getPosition(), bai);
|
||||
|
||||
float defenseValue = scoreEvaluator.evaluateExchange(apDefend, 0, *targets, damageCache, hb);
|
||||
|
||||
if((defenseValue > score && score <= 0) || (defenseValue > 2 * score && score > 0))
|
||||
{
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
}
|
||||
|
||||
activeActionMade = true;
|
||||
return BattleAction::makeMeleeAttack(stack, bestAttack.attack.defenderPos, bestAttack.from);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code.
|
||||
if(moveTarget.scorePerTurn > score)
|
||||
//ThreatMap threatsToUs(stack); // These lines may be useful but they are't used in the code.
|
||||
if(moveTarget.score > score)
|
||||
{
|
||||
score = moveTarget.score;
|
||||
cachedAttack = moveTarget.cachedAttack;
|
||||
cachedScore = score;
|
||||
cachedAttack.ap = moveTarget.cachedAttack;
|
||||
cachedAttack.score = score;
|
||||
cachedAttack.turn = moveTarget.turnsToRich;
|
||||
|
||||
if(stack->waited())
|
||||
{
|
||||
logAi->debug(
|
||||
"Moving %s towards hex %s[%d], score: %2f/%2f",
|
||||
"Moving %s towards hex %s[%d], score: %2f",
|
||||
stack->getDescription(),
|
||||
moveTarget.cachedAttack->attack.defender->getDescription(),
|
||||
moveTarget.cachedAttack->attack.defender->getPosition().hex,
|
||||
moveTarget.score,
|
||||
moveTarget.scorePerTurn);
|
||||
moveTarget.score);
|
||||
|
||||
return goTowardsNearest(stack, moveTarget.positions);
|
||||
return goTowardsNearest(stack, moveTarget.positions, *targets);
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedAttack.waited = true;
|
||||
|
||||
return BattleAction::makeWait(stack);
|
||||
}
|
||||
}
|
||||
@ -224,7 +302,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
if(score <= EvaluationResult::INEFFECTIVE_SCORE
|
||||
&& !stack->hasBonusOfType(BonusType::FLYING)
|
||||
&& stack->unitSide() == BattleSide::ATTACKER
|
||||
&& cb->getBattle(battleID)->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
|
||||
&& cb->getBattle(battleID)->battleGetFortifications().hasMoat)
|
||||
{
|
||||
auto brokenWallMoat = getBrokenWallMoatHexes();
|
||||
|
||||
@ -235,7 +313,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
if(stack->doubleWide() && vstd::contains(brokenWallMoat, stack->getPosition()))
|
||||
return BattleAction::makeMove(stack, stack->getPosition().cloneInDirection(BattleHex::RIGHT));
|
||||
else
|
||||
return goTowardsNearest(stack, brokenWallMoat);
|
||||
return goTowardsNearest(stack, brokenWallMoat, *targets);
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,11 +327,55 @@ uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock>
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
|
||||
}
|
||||
|
||||
BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes)
|
||||
BattleAction BattleEvaluator::moveOrAttack(const CStack * stack, BattleHex hex, const PotentialTargets & targets)
|
||||
{
|
||||
auto additionalScore = 0;
|
||||
std::optional<AttackPossibility> attackOnTheWay;
|
||||
|
||||
for(auto & target : targets.possibleAttacks)
|
||||
{
|
||||
if(!target.attack.shooting && target.from == hex && target.attackValue() > additionalScore)
|
||||
{
|
||||
additionalScore = target.attackValue();
|
||||
attackOnTheWay = target;
|
||||
}
|
||||
}
|
||||
|
||||
if(attackOnTheWay)
|
||||
{
|
||||
activeActionMade = true;
|
||||
return BattleAction::makeMeleeAttack(stack, attackOnTheWay->attack.defender->getPosition(), attackOnTheWay->from);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(stack->position == hex)
|
||||
return BattleAction::makeDefend(stack);
|
||||
else
|
||||
return BattleAction::makeMove(stack, hex);
|
||||
}
|
||||
}
|
||||
|
||||
BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes, const PotentialTargets & targets)
|
||||
{
|
||||
auto reachability = cb->getBattle(battleID)->getReachability(stack);
|
||||
auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
|
||||
|
||||
auto enemyMellee = hb->getUnitsIf([this](const battle::Unit* u) -> bool
|
||||
{
|
||||
return u->unitSide() == BattleSide::ATTACKER && !hb->battleCanShoot(u);
|
||||
});
|
||||
|
||||
bool siegeDefense = stack->unitSide() == BattleSide::DEFENDER
|
||||
&& hasWorkingTowers()
|
||||
&& !enemyMellee.empty();
|
||||
|
||||
if (siegeDefense)
|
||||
{
|
||||
vstd::erase_if(avHexes, [&](const BattleHex& hex) {
|
||||
return !cb->getBattle(battleID)->battleIsInsideWalls(hex);
|
||||
});
|
||||
}
|
||||
|
||||
if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
|
||||
{
|
||||
return BattleAction::makeDefend(stack);
|
||||
@ -261,56 +383,45 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
|
||||
|
||||
std::vector<BattleHex> targetHexes = hexes;
|
||||
|
||||
for(int i = 0; i < 5; i++)
|
||||
{
|
||||
std::sort(targetHexes.begin(), targetHexes.end(), [&](BattleHex h1, BattleHex h2) -> bool
|
||||
{
|
||||
return reachability.distances.at(h1) < reachability.distances.at(h2);
|
||||
});
|
||||
vstd::erase_if(targetHexes, [](const BattleHex & hex) { return !hex.isValid(); });
|
||||
|
||||
for(auto hex : targetHexes)
|
||||
std::sort(targetHexes.begin(), targetHexes.end(), [&](BattleHex h1, BattleHex h2) -> bool
|
||||
{
|
||||
if(vstd::contains(avHexes, hex))
|
||||
{
|
||||
return BattleAction::makeMove(stack, hex);
|
||||
}
|
||||
|
||||
if(stack->coversPos(hex))
|
||||
{
|
||||
logAi->warn("Warning: already standing on neighbouring tile!");
|
||||
//We shouldn't even be here...
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
}
|
||||
|
||||
if(reachability.distances.at(targetHexes.front()) <= GameConstants::BFIELD_SIZE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
std::vector<BattleHex> copy = targetHexes;
|
||||
|
||||
for(auto hex : copy)
|
||||
vstd::concatenate(targetHexes, hex.allNeighbouringTiles());
|
||||
|
||||
vstd::erase_if(targetHexes, [](const BattleHex & hex) {return !hex.isValid();});
|
||||
vstd::removeDuplicates(targetHexes);
|
||||
}
|
||||
return reachability.distances[h1] < reachability.distances[h2];
|
||||
});
|
||||
|
||||
BattleHex bestNeighbor = targetHexes.front();
|
||||
|
||||
if(reachability.distances.at(bestNeighbor) > GameConstants::BFIELD_SIZE)
|
||||
if(reachability.distances[bestNeighbor] > GameConstants::BFIELD_SIZE)
|
||||
{
|
||||
logAi->trace("No richable hexes.");
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
|
||||
// this turn
|
||||
for(auto hex : targetHexes)
|
||||
{
|
||||
if(vstd::contains(avHexes, hex))
|
||||
{
|
||||
return moveOrAttack(stack, hex, targets);
|
||||
}
|
||||
|
||||
if(stack->coversPos(hex))
|
||||
{
|
||||
logAi->warn("Warning: already standing on neighbouring hex!");
|
||||
//We shouldn't even be here...
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
}
|
||||
|
||||
// not this turn
|
||||
scoreEvaluator.updateReachabilityMap(hb);
|
||||
|
||||
if(stack->hasBonusOfType(BonusType::FLYING))
|
||||
{
|
||||
std::set<BattleHex> obstacleHexes;
|
||||
|
||||
auto insertAffected = [](const CObstacleInstance & spellObst, std::set<BattleHex> obstacleHexes) {
|
||||
auto insertAffected = [](const CObstacleInstance & spellObst, std::set<BattleHex> & obstacleHexes) {
|
||||
auto affectedHexes = spellObst.getAffectedTiles();
|
||||
obstacleHexes.insert(affectedHexes.cbegin(), affectedHexes.cend());
|
||||
};
|
||||
@ -343,7 +454,7 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
|
||||
return scoreEvaluator.checkPositionBlocksOurStacks(*hb, stack, hex) ? BLOCKED_STACK_PENALTY + distance : distance;
|
||||
});
|
||||
|
||||
return BattleAction::makeMove(stack, *nearestAvailableHex);
|
||||
return moveOrAttack(stack, *nearestAvailableHex, targets);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -357,11 +468,16 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
|
||||
|
||||
if(vstd::contains(avHexes, currentDest)
|
||||
&& !scoreEvaluator.checkPositionBlocksOurStacks(*hb, stack, currentDest))
|
||||
return BattleAction::makeMove(stack, currentDest);
|
||||
{
|
||||
return moveOrAttack(stack, currentDest, targets);
|
||||
}
|
||||
|
||||
currentDest = reachability.predecessors[currentDest];
|
||||
}
|
||||
}
|
||||
|
||||
logAi->error("We should either detect that hexes are unreachable or make a move!");
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
|
||||
bool BattleEvaluator::canCastSpell()
|
||||
@ -391,7 +507,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
|
||||
vstd::erase_if(possibleSpells, [](const CSpell *s)
|
||||
{
|
||||
return spellType(s) != SpellTypes::BATTLE || s->getTargetType() == spells::AimType::LOCATION;
|
||||
return spellType(s) != SpellTypes::BATTLE;
|
||||
});
|
||||
|
||||
LOGFL("I know how %d of them works.", possibleSpells.size());
|
||||
@ -402,9 +518,6 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
{
|
||||
spells::BattleCast temp(cb->getBattle(battleID).get(), hero, spells::Mode::HERO, spell);
|
||||
|
||||
if(spell->getTargetType() == spells::AimType::LOCATION)
|
||||
continue;
|
||||
|
||||
const bool FAST = true;
|
||||
|
||||
for(auto & target : temp.findPotentialTargets(FAST))
|
||||
@ -573,7 +686,15 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
auto & ps = possibleCasts[i];
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("Evaluating %s", ps.spell->getNameTranslated());
|
||||
if(ps.dest.empty())
|
||||
logAi->trace("Evaluating %s", ps.spell->getNameTranslated());
|
||||
else
|
||||
{
|
||||
auto psFirst = ps.dest.front();
|
||||
auto strWhere = psFirst.unitValue ? psFirst.unitValue->getDescription() : std::to_string(psFirst.hexValue.hex);
|
||||
|
||||
logAi->trace("Evaluating %s at %s", ps.spell->getNameTranslated(), strWhere);
|
||||
}
|
||||
#endif
|
||||
|
||||
auto state = std::make_shared<HypotheticBattle>(env.get(), cb->getBattle(battleID));
|
||||
@ -581,7 +702,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
spells::BattleCast cast(state.get(), hero, spells::Mode::HERO, ps.spell);
|
||||
cast.castEval(state->getServerCallback(), ps.dest);
|
||||
|
||||
auto allUnits = state->battleGetUnitsIf([](const battle::Unit * u) -> bool { return true; });
|
||||
auto allUnits = state->battleGetUnitsIf([](const battle::Unit * u) -> bool { return u->isValidTarget(); });
|
||||
|
||||
auto needFullEval = vstd::contains_if(allUnits, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
@ -591,40 +712,57 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
|
||||
DamageCache safeCopy = damageCache;
|
||||
DamageCache innerCache(&safeCopy);
|
||||
|
||||
innerCache.buildDamageCache(state, side);
|
||||
|
||||
if(needFullEval || !cachedAttack)
|
||||
if(cachedAttack.ap && cachedAttack.waited)
|
||||
{
|
||||
state->makeWait(activeStack);
|
||||
}
|
||||
|
||||
if(needFullEval || !cachedAttack.ap)
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("Full evaluation is started due to stack speed affected.");
|
||||
#endif
|
||||
|
||||
PotentialTargets innerTargets(activeStack, innerCache, state);
|
||||
BattleExchangeEvaluator innerEvaluator(state, env, strengthRatio);
|
||||
BattleExchangeEvaluator innerEvaluator(state, env, strengthRatio, simulationTurnsCount);
|
||||
|
||||
innerEvaluator.updateReachabilityMap(state);
|
||||
|
||||
auto moveTarget = innerEvaluator.findMoveTowardsUnreachable(activeStack, innerTargets, innerCache, state);
|
||||
|
||||
if(!innerTargets.possibleAttacks.empty())
|
||||
{
|
||||
innerEvaluator.updateReachabilityMap(state);
|
||||
|
||||
auto newStackAction = innerEvaluator.findBestTarget(activeStack, innerTargets, innerCache, state);
|
||||
|
||||
ps.value = newStackAction.score;
|
||||
ps.value = std::max(moveTarget.score, newStackAction.score);
|
||||
}
|
||||
else
|
||||
{
|
||||
ps.value = 0;
|
||||
ps.value = moveTarget.score;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ps.value = scoreEvaluator.evaluateExchange(*cachedAttack, 0, *targets, innerCache, state);
|
||||
}
|
||||
auto updatedAttacker = state->getForUpdate(cachedAttack.ap->attack.attacker->unitId());
|
||||
auto updatedDefender = state->getForUpdate(cachedAttack.ap->attack.defender->unitId());
|
||||
auto updatedBai = BattleAttackInfo(
|
||||
updatedAttacker.get(),
|
||||
updatedDefender.get(),
|
||||
cachedAttack.ap->attack.chargeDistance,
|
||||
cachedAttack.ap->attack.shooting);
|
||||
|
||||
auto updatedAttack = AttackPossibility::evaluate(updatedBai, cachedAttack.ap->from, innerCache, state);
|
||||
|
||||
ps.value = scoreEvaluator.evaluateExchange(updatedAttack, cachedAttack.turn, *targets, innerCache, state);
|
||||
}
|
||||
for(const auto & unit : allUnits)
|
||||
{
|
||||
if (!unit->isValidTarget())
|
||||
if(!unit->isValidTarget(true))
|
||||
continue;
|
||||
|
||||
|
||||
auto newHealth = unit->getAvailableHealth();
|
||||
auto oldHealth = vstd::find_or(healthOfStack, unit->unitId(), 0); // old health value may not exist for newly summoned units
|
||||
|
||||
@ -635,7 +773,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
|
||||
auto dpsReduce = AttackPossibility::calculateDamageReduce(
|
||||
nullptr,
|
||||
originalDefender && originalDefender->alive() ? originalDefender : unit,
|
||||
originalDefender && originalDefender->alive() ? originalDefender : unit,
|
||||
damage,
|
||||
innerCache,
|
||||
state);
|
||||
@ -645,23 +783,49 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
|
||||
if(ourUnit * goodEffect == 1)
|
||||
{
|
||||
if(ourUnit && goodEffect && (unit->isClone() || unit->isGhost()))
|
||||
auto isMagical = state->getForUpdate(unit->unitId())->summoned
|
||||
|| unit->isClone()
|
||||
|| unit->isGhost();
|
||||
|
||||
if(ourUnit && goodEffect && isMagical)
|
||||
continue;
|
||||
|
||||
ps.value += dpsReduce * scoreEvaluator.getPositiveEffectMultiplier();
|
||||
}
|
||||
else
|
||||
ps.value -= dpsReduce * scoreEvaluator.getNegativeEffectMultiplier();
|
||||
// discourage AI making collateral damage with spells
|
||||
ps.value -= 4 * dpsReduce * scoreEvaluator.getNegativeEffectMultiplier();
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace(
|
||||
"Spell affects %s (%d), dps: %2f",
|
||||
unit->creatureId().toCreature()->getNameSingularTranslated(),
|
||||
unit->getCount(),
|
||||
dpsReduce);
|
||||
// Ensure ps.dest is not empty before accessing the first element
|
||||
if (!ps.dest.empty())
|
||||
{
|
||||
logAi->trace(
|
||||
"Spell %s to %d affects %s (%d), dps: %2f oldHealth: %d newHealth: %d",
|
||||
ps.spell->getNameTranslated(),
|
||||
ps.dest.at(0).hexValue.hex, // Safe to access .at(0) now
|
||||
unit->creatureId().toCreature()->getNameSingularTranslated(),
|
||||
unit->getCount(),
|
||||
dpsReduce,
|
||||
oldHealth,
|
||||
newHealth);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle the case where ps.dest is empty
|
||||
logAi->trace(
|
||||
"Spell %s has no destination, affects %s (%d), dps: %2f oldHealth: %d newHealth: %d",
|
||||
ps.spell->getNameTranslated(),
|
||||
unit->creatureId().toCreature()->getNameSingularTranslated(),
|
||||
unit->getCount(),
|
||||
dpsReduce,
|
||||
oldHealth,
|
||||
newHealth);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("Total score: %2f", ps.value);
|
||||
#endif
|
||||
@ -672,13 +836,12 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
|
||||
LOGFL("Evaluation took %d ms", timer.getDiff());
|
||||
|
||||
auto pscValue = [](const PossibleSpellcast &ps) -> float
|
||||
{
|
||||
return ps.value;
|
||||
};
|
||||
auto castToPerform = *vstd::maxElementByFun(possibleCasts, pscValue);
|
||||
auto castToPerform = *vstd::maxElementByFun(possibleCasts, [](const PossibleSpellcast & ps) -> float
|
||||
{
|
||||
return ps.value;
|
||||
});
|
||||
|
||||
if(castToPerform.value > cachedScore)
|
||||
if(castToPerform.value > cachedAttack.score && !vstd::isAlmostEqual(castToPerform.value, cachedAttack.score))
|
||||
{
|
||||
LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->getNameTranslated() % castToPerform.value);
|
||||
BattleAction spellcast;
|
||||
@ -686,7 +849,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
spellcast.spell = castToPerform.spell->id;
|
||||
spellcast.setTarget(castToPerform.dest);
|
||||
spellcast.side = side;
|
||||
spellcast.stackNumber = (!side) ? -1 : -2;
|
||||
spellcast.stackNumber = -1;
|
||||
cb->battleMakeSpellAction(battleID, spellcast);
|
||||
activeActionMade = true;
|
||||
|
||||
|
@ -22,6 +22,14 @@ VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class EnemyInfo;
|
||||
|
||||
struct CachedAttack
|
||||
{
|
||||
std::optional<AttackPossibility> ap;
|
||||
float score = EvaluationResult::INEFFECTIVE_SCORE;
|
||||
uint8_t turn = 255;
|
||||
bool waited = false;
|
||||
};
|
||||
|
||||
class BattleEvaluator
|
||||
{
|
||||
std::unique_ptr<PotentialTargets> targets;
|
||||
@ -30,23 +38,25 @@ class BattleEvaluator
|
||||
std::shared_ptr<CBattleCallback> cb;
|
||||
std::shared_ptr<Environment> env;
|
||||
bool activeActionMade = false;
|
||||
std::optional<AttackPossibility> cachedAttack;
|
||||
CachedAttack cachedAttack;
|
||||
PlayerColor playerID;
|
||||
BattleID battleID;
|
||||
int side;
|
||||
float cachedScore;
|
||||
BattleSide side;
|
||||
DamageCache damageCache;
|
||||
float strengthRatio;
|
||||
int simulationTurnsCount;
|
||||
|
||||
public:
|
||||
BattleAction selectStackAction(const CStack * stack);
|
||||
bool attemptCastingSpell(const CStack * stack);
|
||||
bool canCastSpell();
|
||||
std::optional<PossibleSpellcast> findBestCreatureSpell(const CStack * stack);
|
||||
BattleAction goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes);
|
||||
BattleAction goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes, const PotentialTargets & targets);
|
||||
std::vector<BattleHex> getBrokenWallMoatHexes() const;
|
||||
bool hasWorkingTowers() const;
|
||||
void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
|
||||
void print(const std::string & text) const;
|
||||
BattleAction moveOrAttack(const CStack * stack, BattleHex hex, const PotentialTargets & targets);
|
||||
|
||||
BattleEvaluator(
|
||||
std::shared_ptr<Environment> env,
|
||||
@ -54,16 +64,9 @@ public:
|
||||
const battle::Unit * activeStack,
|
||||
PlayerColor playerID,
|
||||
BattleID battleID,
|
||||
int side,
|
||||
float strengthRatio)
|
||||
:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio), cachedAttack(), playerID(playerID), side(side), env(env), cb(cb), strengthRatio(strengthRatio), battleID(battleID)
|
||||
{
|
||||
hb = std::make_shared<HypotheticBattle>(env.get(), cb->getBattle(battleID));
|
||||
damageCache.buildDamageCache(hb, side);
|
||||
|
||||
targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
|
||||
cachedScore = EvaluationResult::INEFFECTIVE_SCORE;
|
||||
}
|
||||
BattleSide side,
|
||||
float strengthRatio,
|
||||
int simulationTurnsCount);
|
||||
|
||||
BattleEvaluator(
|
||||
std::shared_ptr<Environment> env,
|
||||
@ -73,11 +76,7 @@ public:
|
||||
const battle::Unit * activeStack,
|
||||
PlayerColor playerID,
|
||||
BattleID battleID,
|
||||
int side,
|
||||
float strengthRatio)
|
||||
:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio), cachedAttack(), playerID(playerID), side(side), env(env), cb(cb), hb(hb), damageCache(damageCache), strengthRatio(strengthRatio), battleID(battleID)
|
||||
{
|
||||
targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
|
||||
cachedScore = EvaluationResult::INEFFECTIVE_SCORE;
|
||||
}
|
||||
BattleSide side,
|
||||
float strengthRatio,
|
||||
int simulationTurnsCount);
|
||||
};
|
||||
|
@ -9,16 +9,17 @@
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "BattleExchangeVariant.h"
|
||||
#include "BattleEvaluator.h"
|
||||
#include "../../lib/CStack.h"
|
||||
|
||||
AttackerValue::AttackerValue()
|
||||
: value(0),
|
||||
isRetalitated(false)
|
||||
isRetaliated(false)
|
||||
{
|
||||
}
|
||||
|
||||
MoveTarget::MoveTarget()
|
||||
: positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE), scorePerTurn(EvaluationResult::INEFFECTIVE_SCORE)
|
||||
: positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE)
|
||||
{
|
||||
turnsToRich = 1;
|
||||
}
|
||||
@ -28,102 +29,97 @@ float BattleExchangeVariant::trackAttack(
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
DamageCache & damageCache)
|
||||
{
|
||||
if(!ap.attackerState)
|
||||
{
|
||||
logAi->trace("Skipping fake ap attack");
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto attacker = hb->getForUpdate(ap.attack.attacker->unitId());
|
||||
|
||||
const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
|
||||
static const auto selectorBlocksRetaliation = Selector::type()(BonusType::BLOCKS_RETALIATION);
|
||||
const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
|
||||
|
||||
float attackValue = 0;
|
||||
float attackValue = ap.attackValue();
|
||||
auto affectedUnits = ap.affectedUnits;
|
||||
|
||||
dpsScore.ourDamageReduce += ap.attackerDamageReduce + ap.collateralDamageReduce;
|
||||
dpsScore.enemyDamageReduce += ap.defenderDamageReduce + ap.shootersBlockedDmg;
|
||||
attackerValue[attacker->unitId()].value = attackValue;
|
||||
|
||||
affectedUnits.push_back(ap.attackerState);
|
||||
|
||||
for(auto affectedUnit : affectedUnits)
|
||||
{
|
||||
auto unitToUpdate = hb->getForUpdate(affectedUnit->unitId());
|
||||
auto damageDealt = unitToUpdate->getAvailableHealth() - affectedUnit->getAvailableHealth();
|
||||
|
||||
if(damageDealt > 0)
|
||||
{
|
||||
unitToUpdate->damage(damageDealt);
|
||||
}
|
||||
|
||||
if(unitToUpdate->unitSide() == attacker->unitSide())
|
||||
{
|
||||
if(unitToUpdate->unitId() == attacker->unitId())
|
||||
{
|
||||
auto defender = hb->getForUpdate(ap.attack.defender->unitId());
|
||||
|
||||
if(!defender->alive() || counterAttacksBlocked || ap.attack.shooting || !defender->ableToRetaliate())
|
||||
continue;
|
||||
|
||||
auto retaliationDamage = damageCache.getDamage(defender.get(), unitToUpdate.get(), hb);
|
||||
auto attackerDamageReduce = AttackPossibility::calculateDamageReduce(defender.get(), unitToUpdate.get(), retaliationDamage, damageCache, hb);
|
||||
|
||||
attackValue -= attackerDamageReduce;
|
||||
dpsScore.ourDamageReduce += attackerDamageReduce;
|
||||
attackerValue[unitToUpdate->unitId()].isRetalitated = true;
|
||||
|
||||
unitToUpdate->damage(retaliationDamage);
|
||||
defender->afterAttack(false, true);
|
||||
unitToUpdate->afterAttack(ap.attack.shooting, false);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace(
|
||||
"%s -> %s, ap retalitation, %s, dps: %2f, score: %2f",
|
||||
defender->getDescription(),
|
||||
unitToUpdate->getDescription(),
|
||||
"%s -> %s, ap retaliation, %s, dps: %lld",
|
||||
hb->getForUpdate(ap.attack.defender->unitId())->getDescription(),
|
||||
ap.attack.attacker->getDescription(),
|
||||
ap.attack.shooting ? "shot" : "mellee",
|
||||
retaliationDamage,
|
||||
attackerDamageReduce);
|
||||
damageDealt);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
auto collateralDamage = damageCache.getDamage(attacker.get(), unitToUpdate.get(), hb);
|
||||
auto collateralDamageReduce = AttackPossibility::calculateDamageReduce(attacker.get(), unitToUpdate.get(), collateralDamage, damageCache, hb);
|
||||
|
||||
attackValue -= collateralDamageReduce;
|
||||
dpsScore.ourDamageReduce += collateralDamageReduce;
|
||||
|
||||
unitToUpdate->damage(collateralDamage);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace(
|
||||
"%s -> %s, ap collateral, %s, dps: %2f, score: %2f",
|
||||
attacker->getDescription(),
|
||||
"%s, ap collateral, dps: %lld",
|
||||
unitToUpdate->getDescription(),
|
||||
ap.attack.shooting ? "shot" : "mellee",
|
||||
collateralDamage,
|
||||
collateralDamageReduce);
|
||||
damageDealt);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int64_t attackDamage = damageCache.getDamage(attacker.get(), unitToUpdate.get(), hb);
|
||||
float defenderDamageReduce = AttackPossibility::calculateDamageReduce(attacker.get(), unitToUpdate.get(), attackDamage, damageCache, hb);
|
||||
|
||||
attackValue += defenderDamageReduce;
|
||||
dpsScore.enemyDamageReduce += defenderDamageReduce;
|
||||
attackerValue[attacker->unitId()].value += defenderDamageReduce;
|
||||
|
||||
unitToUpdate->damage(attackDamage);
|
||||
if(unitToUpdate->unitId() == ap.attack.defender->unitId())
|
||||
{
|
||||
if(unitToUpdate->ableToRetaliate() && !affectedUnit->ableToRetaliate())
|
||||
{
|
||||
unitToUpdate->afterAttack(ap.attack.shooting, true);
|
||||
}
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace(
|
||||
"%s -> %s, ap attack, %s, dps: %2f, score: %2f",
|
||||
attacker->getDescription(),
|
||||
unitToUpdate->getDescription(),
|
||||
ap.attack.shooting ? "shot" : "mellee",
|
||||
attackDamage,
|
||||
defenderDamageReduce);
|
||||
logAi->trace(
|
||||
"%s -> %s, ap attack, %s, dps: %lld",
|
||||
attacker->getDescription(),
|
||||
ap.attack.defender->getDescription(),
|
||||
ap.attack.shooting ? "shot" : "mellee",
|
||||
damageDealt);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace(
|
||||
"%s, ap enemy collateral, dps: %lld",
|
||||
unitToUpdate->getDescription(),
|
||||
damageDealt);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("ap shooters blocking: %lld", ap.shootersBlockedDmg);
|
||||
logAi->trace(
|
||||
"ap score: our: %2f, enemy: %2f, collateral: %2f, blocked: %2f",
|
||||
ap.attackerDamageReduce,
|
||||
ap.defenderDamageReduce,
|
||||
ap.collateralDamageReduce,
|
||||
ap.shootersBlockedDmg);
|
||||
#endif
|
||||
|
||||
attackValue += ap.shootersBlockedDmg;
|
||||
dpsScore.enemyDamageReduce += ap.shootersBlockedDmg;
|
||||
attacker->afterAttack(ap.attack.shooting, false);
|
||||
|
||||
return attackValue;
|
||||
}
|
||||
|
||||
@ -185,7 +181,7 @@ float BattleExchangeVariant::trackAttack(
|
||||
if(isOurAttack)
|
||||
{
|
||||
dpsScore.ourDamageReduce += attackerDamageReduce;
|
||||
attackerValue[attacker->unitId()].isRetalitated = true;
|
||||
attackerValue[attacker->unitId()].isRetaliated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -218,7 +214,8 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
const battle::Unit * activeStack,
|
||||
PotentialTargets & targets,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> hb)
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
bool siegeDefense)
|
||||
{
|
||||
EvaluationResult result(targets.bestAction());
|
||||
|
||||
@ -230,13 +227,15 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
|
||||
auto hbWaited = std::make_shared<HypotheticBattle>(env.get(), hb);
|
||||
|
||||
hbWaited->getForUpdate(activeStack->unitId())->waiting = true;
|
||||
hbWaited->getForUpdate(activeStack->unitId())->waitedThisTurn = true;
|
||||
hbWaited->makeWait(activeStack);
|
||||
|
||||
updateReachabilityMap(hbWaited);
|
||||
|
||||
for(auto & ap : targets.possibleAttacks)
|
||||
{
|
||||
if (siegeDefense && !hb->battleIsInsideWalls(ap.from))
|
||||
continue;
|
||||
|
||||
float score = evaluateExchange(ap, 0, targets, damageCache, hbWaited);
|
||||
|
||||
if(score > result.score)
|
||||
@ -259,6 +258,7 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
updateReachabilityMap(hb);
|
||||
|
||||
if(result.bestAttack.attack.shooting
|
||||
&& !result.bestAttack.defenderDead
|
||||
&& !activeStack->waited()
|
||||
&& hb->battleHasShootingPenalty(activeStack, result.bestAttack.dest))
|
||||
{
|
||||
@ -268,9 +268,13 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
|
||||
for(auto & ap : targets.possibleAttacks)
|
||||
{
|
||||
float score = evaluateExchange(ap, 0, targets, damageCache, hb);
|
||||
if (siegeDefense && !hb->battleIsInsideWalls(ap.from))
|
||||
continue;
|
||||
|
||||
if(score > result.score || (vstd::isAlmostEqual(score, result.score) && result.wait))
|
||||
float score = evaluateExchange(ap, 0, targets, damageCache, hb);
|
||||
bool sameScoreButWaited = vstd::isAlmostEqual(score, result.score) && result.wait;
|
||||
|
||||
if(score > result.score || sameScoreButWaited)
|
||||
{
|
||||
result.score = score;
|
||||
result.bestAttack = ap;
|
||||
@ -285,6 +289,36 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
return result;
|
||||
}
|
||||
|
||||
ReachabilityInfo getReachabilityWithEnemyBypass(
|
||||
const battle::Unit * activeStack,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> state)
|
||||
{
|
||||
ReachabilityInfo::Parameters params(activeStack, activeStack->getPosition());
|
||||
|
||||
if(!params.flying)
|
||||
{
|
||||
for(const auto * unit : state->battleAliveUnits())
|
||||
{
|
||||
if(unit->unitSide() == activeStack->unitSide())
|
||||
continue;
|
||||
|
||||
auto dmg = damageCache.getOriginalDamage(activeStack, unit, state);
|
||||
auto turnsToKill = unit->getAvailableHealth() / std::max(dmg, (int64_t)1);
|
||||
|
||||
vstd::amin(turnsToKill, 100);
|
||||
|
||||
for(auto & hex : unit->getHexes())
|
||||
if(hex.isAvailable()) //towers can have <0 pos; we don't also want to overwrite side columns
|
||||
params.destructibleEnemyTurns[hex] = turnsToKill * unit->getMovementRange();
|
||||
}
|
||||
|
||||
params.bypassEnemyStacks = true;
|
||||
}
|
||||
|
||||
return state->getReachability(params);
|
||||
}
|
||||
|
||||
MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
const battle::Unit * activeStack,
|
||||
PotentialTargets & targets,
|
||||
@ -294,6 +328,8 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
MoveTarget result;
|
||||
BattleExchangeVariant ev;
|
||||
|
||||
logAi->trace("Find move towards unreachable. Enemies count %d", targets.unreachableEnemies.size());
|
||||
|
||||
if(targets.unreachableEnemies.empty())
|
||||
return result;
|
||||
|
||||
@ -304,17 +340,17 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
|
||||
updateReachabilityMap(hb);
|
||||
|
||||
auto dists = cb->getReachability(activeStack);
|
||||
auto dists = getReachabilityWithEnemyBypass(activeStack, damageCache, hb);
|
||||
auto flying = activeStack->hasBonusOfType(BonusType::FLYING);
|
||||
|
||||
for(const battle::Unit * enemy : targets.unreachableEnemies)
|
||||
{
|
||||
std::vector<const battle::Unit *> adjacentStacks = getAdjacentUnits(enemy);
|
||||
auto closestStack = *vstd::minElementByFun(adjacentStacks, [&](const battle::Unit * u) -> int64_t
|
||||
{
|
||||
return dists.distToNearestNeighbour(activeStack, u) * 100000 - activeStack->getTotalHealth();
|
||||
});
|
||||
logAi->trace(
|
||||
"Checking movement towards %d of %s",
|
||||
enemy->getCount(),
|
||||
enemy->creatureId().toCreature()->getNameSingularTranslated());
|
||||
|
||||
auto distance = dists.distToNearestNeighbour(activeStack, closestStack);
|
||||
auto distance = dists.distToNearestNeighbour(activeStack, enemy);
|
||||
|
||||
if(distance >= GameConstants::BFIELD_SIZE)
|
||||
continue;
|
||||
@ -322,31 +358,109 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
if(distance <= speed)
|
||||
continue;
|
||||
|
||||
float penaltyMultiplier = 1.0f; // Default multiplier, no penalty
|
||||
float closestAllyDistance = std::numeric_limits<float>::max();
|
||||
|
||||
for (const battle::Unit* ally : hb->battleAliveUnits()) {
|
||||
if (ally == activeStack)
|
||||
continue;
|
||||
if (ally->unitSide() != activeStack->unitSide())
|
||||
continue;
|
||||
|
||||
float allyDistance = dists.distToNearestNeighbour(ally, enemy);
|
||||
if (allyDistance < closestAllyDistance)
|
||||
{
|
||||
closestAllyDistance = allyDistance;
|
||||
}
|
||||
}
|
||||
|
||||
// If an ally is closer to the enemy, compute the penaltyMultiplier
|
||||
if (closestAllyDistance < distance) {
|
||||
penaltyMultiplier = closestAllyDistance / distance; // Ratio of distances
|
||||
}
|
||||
|
||||
auto turnsToRich = (distance - 1) / speed + 1;
|
||||
auto hexes = closestStack->getSurroundingHexes();
|
||||
auto enemySpeed = closestStack->getMovementRange();
|
||||
auto hexes = enemy->getSurroundingHexes();
|
||||
auto enemySpeed = enemy->getMovementRange();
|
||||
auto speedRatio = speed / static_cast<float>(enemySpeed);
|
||||
auto multiplier = speedRatio > 1 ? 1 : speedRatio;
|
||||
auto multiplier = (speedRatio > 1 ? 1 : speedRatio) * penaltyMultiplier;
|
||||
|
||||
if(enemy->canShoot())
|
||||
multiplier *= 1.5f;
|
||||
|
||||
for(auto hex : hexes)
|
||||
for(auto & hex : hexes)
|
||||
{
|
||||
// FIXME: provide distance info for Jousting bonus
|
||||
auto bai = BattleAttackInfo(activeStack, closestStack, 0, cb->battleCanShoot(activeStack));
|
||||
auto bai = BattleAttackInfo(activeStack, enemy, 0, cb->battleCanShoot(activeStack));
|
||||
auto attack = AttackPossibility::evaluate(bai, hex, damageCache, hb);
|
||||
|
||||
attack.shootersBlockedDmg = 0; // we do not want to count on it, it is not for sure
|
||||
|
||||
auto score = calculateExchange(attack, turnsToRich, targets, damageCache, hb);
|
||||
auto scorePerTurn = BattleScore(score.enemyDamageReduce * std::sqrt(multiplier / turnsToRich), score.ourDamageReduce);
|
||||
|
||||
if(result.scorePerTurn < scoreValue(scorePerTurn))
|
||||
score.enemyDamageReduce *= multiplier;
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("Multiplier: %f, turns: %d, current score %f, new score %f", multiplier, turnsToRich, result.score, scoreValue(score));
|
||||
#endif
|
||||
|
||||
if(result.score < scoreValue(score)
|
||||
|| (result.turnsToRich > turnsToRich && vstd::isAlmostEqual(result.score, scoreValue(score))))
|
||||
{
|
||||
result.scorePerTurn = scoreValue(scorePerTurn);
|
||||
result.score = scoreValue(score);
|
||||
result.positions = closestStack->getAttackableHexes(activeStack);
|
||||
result.positions.clear();
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("New high score");
|
||||
#endif
|
||||
|
||||
for(const BattleHex & initialEnemyHex : enemy->getAttackableHexes(activeStack))
|
||||
{
|
||||
BattleHex enemyHex = initialEnemyHex;
|
||||
|
||||
while(!flying && dists.distances[enemyHex] > speed && dists.predecessors.at(enemyHex).isValid())
|
||||
{
|
||||
enemyHex = dists.predecessors.at(enemyHex);
|
||||
|
||||
if(dists.accessibility[enemyHex] == EAccessibility::ALIVE_STACK)
|
||||
{
|
||||
auto defenderToBypass = hb->battleGetUnitByPos(enemyHex);
|
||||
|
||||
if(defenderToBypass)
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("Found target to bypass at %d", enemyHex.hex);
|
||||
#endif
|
||||
|
||||
auto attackHex = dists.predecessors[enemyHex];
|
||||
auto baiBypass = BattleAttackInfo(activeStack, defenderToBypass, 0, cb->battleCanShoot(activeStack));
|
||||
auto attackBypass = AttackPossibility::evaluate(baiBypass, attackHex, damageCache, hb);
|
||||
|
||||
auto adjacentStacks = getAdjacentUnits(enemy);
|
||||
|
||||
adjacentStacks.push_back(defenderToBypass);
|
||||
vstd::removeDuplicates(adjacentStacks);
|
||||
|
||||
auto bypassScore = calculateExchange(
|
||||
attackBypass,
|
||||
dists.distances[attackHex],
|
||||
targets,
|
||||
damageCache,
|
||||
hb,
|
||||
adjacentStacks);
|
||||
|
||||
if(scoreValue(bypassScore) > result.score)
|
||||
{
|
||||
result.score = scoreValue(bypassScore);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("New high score after bypass %f", scoreValue(bypassScore));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.positions.push_back(enemyHex);
|
||||
}
|
||||
|
||||
result.cachedAttack = attack;
|
||||
result.turnsToRich = turnsToRich;
|
||||
}
|
||||
@ -390,7 +504,8 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
const AttackPossibility & ap,
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
std::shared_ptr<HypotheticBattle> hb) const
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits) const
|
||||
{
|
||||
ReachabilityData result;
|
||||
|
||||
@ -398,13 +513,29 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
|
||||
if(!ap.attack.shooting) hexes.push_back(ap.from);
|
||||
|
||||
std::vector<const battle::Unit *> allReachableUnits;
|
||||
|
||||
std::vector<const battle::Unit *> allReachableUnits = additionalUnits;
|
||||
|
||||
for(auto hex : hexes)
|
||||
{
|
||||
vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex));
|
||||
}
|
||||
|
||||
if(!ap.attack.attacker->isTurret())
|
||||
{
|
||||
for(auto hex : ap.attack.attacker->getHexes())
|
||||
{
|
||||
auto unitsReachingAttacker = turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex);
|
||||
for(auto unit : unitsReachingAttacker)
|
||||
{
|
||||
if(unit->unitSide() != ap.attack.attacker->unitSide())
|
||||
{
|
||||
allReachableUnits.push_back(unit);
|
||||
result.enemyUnitsReachingAttacker.insert(unit->unitId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vstd::removeDuplicates(allReachableUnits);
|
||||
|
||||
auto copy = allReachableUnits;
|
||||
@ -440,7 +571,7 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
|
||||
for(auto unit : allReachableUnits)
|
||||
{
|
||||
auto accessible = !unit->canShoot();
|
||||
auto accessible = !unit->canShoot() || vstd::contains(additionalUnits, unit);
|
||||
|
||||
if(!accessible)
|
||||
{
|
||||
@ -464,14 +595,14 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
for(auto unit : turnOrder[turn])
|
||||
{
|
||||
if(vstd::contains(allReachableUnits, unit))
|
||||
result.units.push_back(unit);
|
||||
result.units[turn].push_back(unit);
|
||||
}
|
||||
}
|
||||
|
||||
vstd::erase_if(result.units, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !hb->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
vstd::erase_if(result.units[turn], [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !hb->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -502,13 +633,14 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> hb) const
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits) const
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Battle exchange at %d", ap.attack.shooting ? ap.dest.hex : ap.from.hex);
|
||||
#endif
|
||||
|
||||
if(cb->battleGetMySide() == BattlePerspective::LEFT_SIDE
|
||||
if(cb->battleGetMySide() == BattleSide::LEFT_SIDE
|
||||
&& cb->battleGetGateState() == EGateState::BLOCKED
|
||||
&& ap.attack.defender->coversPos(BattleHex::GATE_BRIDGE))
|
||||
{
|
||||
@ -521,7 +653,7 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
if(hb->battleGetUnitByID(ap.attack.defender->unitId())->alive())
|
||||
enemyStacks.push_back(ap.attack.defender);
|
||||
|
||||
ReachabilityData exchangeUnits = getExchangeUnits(ap, turn, targets, hb);
|
||||
ReachabilityData exchangeUnits = getExchangeUnits(ap, turn, targets, hb, additionalUnits);
|
||||
|
||||
if(exchangeUnits.units.empty())
|
||||
{
|
||||
@ -531,22 +663,25 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
auto exchangeBattle = std::make_shared<HypotheticBattle>(env.get(), hb);
|
||||
BattleExchangeVariant v;
|
||||
|
||||
for(auto unit : exchangeUnits.units)
|
||||
for(int exchangeTurn = 0; exchangeTurn < exchangeUnits.units.size(); exchangeTurn++)
|
||||
{
|
||||
if(unit->isTurret())
|
||||
continue;
|
||||
|
||||
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, unit, true);
|
||||
auto & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||
auto u = exchangeBattle->getForUpdate(unit->unitId());
|
||||
|
||||
if(u->alive() && !vstd::contains(attackerQueue, unit))
|
||||
for(auto unit : exchangeUnits.units.at(exchangeTurn))
|
||||
{
|
||||
attackerQueue.push_back(unit);
|
||||
if(unit->isTurret())
|
||||
continue;
|
||||
|
||||
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, unit, true);
|
||||
auto & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||
auto u = exchangeBattle->getForUpdate(unit->unitId());
|
||||
|
||||
if(u->alive() && !vstd::contains(attackerQueue, unit))
|
||||
{
|
||||
attackerQueue.push_back(unit);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL
|
||||
logAi->trace("Exchanging: %s", u->getDescription());
|
||||
logAi->trace("Exchanging: %s", u->getDescription());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -560,122 +695,166 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
|
||||
bool canUseAp = true;
|
||||
|
||||
for(auto activeUnit : exchangeUnits.units)
|
||||
std::set<uint32_t> blockedShooters;
|
||||
|
||||
int totalTurnsCount = simulationTurnsCount >= turn + turnOrder.size()
|
||||
? simulationTurnsCount
|
||||
: turn + turnOrder.size();
|
||||
|
||||
for(int exchangeTurn = 0; exchangeTurn < simulationTurnsCount; exchangeTurn++)
|
||||
{
|
||||
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, activeUnit, true);
|
||||
battle::Units & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||
battle::Units & oppositeQueue = isOur ? enemyStacks : ourStacks;
|
||||
bool isMovingTurm = exchangeTurn < turn;
|
||||
int queueTurn = exchangeTurn >= exchangeUnits.units.size()
|
||||
? exchangeUnits.units.size() - 1
|
||||
: exchangeTurn;
|
||||
|
||||
auto attacker = exchangeBattle->getForUpdate(activeUnit->unitId());
|
||||
|
||||
if(!attacker->alive())
|
||||
for(auto activeUnit : exchangeUnits.units.at(queueTurn))
|
||||
{
|
||||
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, activeUnit, true);
|
||||
battle::Units & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||
battle::Units & oppositeQueue = isOur ? enemyStacks : ourStacks;
|
||||
|
||||
auto attacker = exchangeBattle->getForUpdate(activeUnit->unitId());
|
||||
auto shooting = exchangeBattle->battleCanShoot(attacker.get())
|
||||
&& !vstd::contains(blockedShooters, attacker->unitId());
|
||||
|
||||
if(!attacker->alive())
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace( "Attacker is dead");
|
||||
logAi->trace("Attacker is dead");
|
||||
#endif
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
auto targetUnit = ap.attack.defender;
|
||||
|
||||
if(!isOur || !exchangeBattle->battleGetUnitByID(targetUnit->unitId())->alive())
|
||||
{
|
||||
auto estimateAttack = [&](const battle::Unit * u) -> float
|
||||
{
|
||||
auto stackWithBonuses = exchangeBattle->getForUpdate(u->unitId());
|
||||
auto score = v.trackAttack(
|
||||
attacker,
|
||||
stackWithBonuses,
|
||||
exchangeBattle->battleCanShoot(stackWithBonuses.get()),
|
||||
isOur,
|
||||
damageCache,
|
||||
hb,
|
||||
true);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Best target selector %s->%s score = %2f", attacker->getDescription(), stackWithBonuses->getDescription(), score);
|
||||
#endif
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
auto unitsInOppositeQueueExceptInaccessible = oppositeQueue;
|
||||
|
||||
vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u)->bool
|
||||
{
|
||||
return vstd::contains(exchangeUnits.shooters, u);
|
||||
});
|
||||
|
||||
if(!unitsInOppositeQueueExceptInaccessible.empty())
|
||||
{
|
||||
targetUnit = *vstd::maxElementByFun(unitsInOppositeQueueExceptInaccessible, estimateAttack);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
if(isMovingTurm && !shooting
|
||||
&& !vstd::contains(exchangeUnits.enemyUnitsReachingAttacker, attacker->unitId()))
|
||||
{
|
||||
auto reachable = exchangeBattle->battleGetUnitsIf([this, &exchangeBattle, &attacker](const battle::Unit * u) -> bool
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Attacker is moving");
|
||||
#endif
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
auto targetUnit = ap.attack.defender;
|
||||
|
||||
if(!isOur || !exchangeBattle->battleGetUnitByID(targetUnit->unitId())->alive())
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=2
|
||||
logAi->trace("Best target selector for %s", attacker->getDescription());
|
||||
#endif
|
||||
auto estimateAttack = [&](const battle::Unit * u) -> float
|
||||
{
|
||||
auto stackWithBonuses = exchangeBattle->getForUpdate(u->unitId());
|
||||
auto score = v.trackAttack(
|
||||
attacker,
|
||||
stackWithBonuses,
|
||||
exchangeBattle->battleCanShoot(stackWithBonuses.get()),
|
||||
isOur,
|
||||
damageCache,
|
||||
hb,
|
||||
true);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=2
|
||||
logAi->trace("Best target selector %s->%s score = %2f", attacker->getDescription(), stackWithBonuses->getDescription(), score);
|
||||
#endif
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
auto unitsInOppositeQueueExceptInaccessible = oppositeQueue;
|
||||
|
||||
vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u)->bool
|
||||
{
|
||||
if(u->unitSide() == attacker->unitSide())
|
||||
return false;
|
||||
|
||||
if(!exchangeBattle->getForUpdate(u->unitId())->alive())
|
||||
return false;
|
||||
|
||||
if (!u->getPosition().isValid())
|
||||
return false; // e.g. tower shooters
|
||||
|
||||
return vstd::contains_if(reachabilityMap.at(u->getPosition()), [&attacker](const battle::Unit * other) -> bool
|
||||
{
|
||||
return attacker->unitId() == other->unitId();
|
||||
});
|
||||
return vstd::contains(exchangeUnits.shooters, u);
|
||||
});
|
||||
|
||||
if(!reachable.empty())
|
||||
if(!isOur
|
||||
&& exchangeTurn == 0
|
||||
&& exchangeUnits.units.at(exchangeTurn).at(0)->unitId() != ap.attack.attacker->unitId()
|
||||
&& !vstd::contains(exchangeUnits.enemyUnitsReachingAttacker, attacker->unitId()))
|
||||
{
|
||||
targetUnit = *vstd::maxElementByFun(reachable, estimateAttack);
|
||||
vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return u->unitId() == ap.attack.attacker->unitId();
|
||||
});
|
||||
}
|
||||
|
||||
if(!unitsInOppositeQueueExceptInaccessible.empty())
|
||||
{
|
||||
targetUnit = *vstd::maxElementByFun(unitsInOppositeQueueExceptInaccessible, estimateAttack);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto reachable = exchangeBattle->battleGetUnitsIf([this, &exchangeBattle, &attacker](const battle::Unit * u) -> bool
|
||||
{
|
||||
if(u->unitSide() == attacker->unitSide())
|
||||
return false;
|
||||
|
||||
if(!exchangeBattle->getForUpdate(u->unitId())->alive())
|
||||
return false;
|
||||
|
||||
if(!u->getPosition().isValid())
|
||||
return false; // e.g. tower shooters
|
||||
|
||||
return vstd::contains_if(reachabilityMap.at(u->getPosition()), [&attacker](const battle::Unit * other) -> bool
|
||||
{
|
||||
return attacker->unitId() == other->unitId();
|
||||
});
|
||||
});
|
||||
|
||||
if(!reachable.empty())
|
||||
{
|
||||
targetUnit = *vstd::maxElementByFun(reachable, estimateAttack);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Battle queue is empty and no reachable enemy.");
|
||||
logAi->trace("Battle queue is empty and no reachable enemy.");
|
||||
#endif
|
||||
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto defender = exchangeBattle->getForUpdate(targetUnit->unitId());
|
||||
auto shooting = exchangeBattle->battleCanShoot(attacker.get());
|
||||
const int totalAttacks = attacker->getTotalAttacks(shooting);
|
||||
auto defender = exchangeBattle->getForUpdate(targetUnit->unitId());
|
||||
const int totalAttacks = attacker->getTotalAttacks(shooting);
|
||||
|
||||
if(canUseAp && activeUnit->unitId() == ap.attack.attacker->unitId()
|
||||
&& targetUnit->unitId() == ap.attack.defender->unitId())
|
||||
{
|
||||
v.trackAttack(ap, exchangeBattle, damageCache);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int i = 0; i < totalAttacks; i++)
|
||||
if(canUseAp && activeUnit->unitId() == ap.attack.attacker->unitId()
|
||||
&& targetUnit->unitId() == ap.attack.defender->unitId())
|
||||
{
|
||||
v.trackAttack(attacker, defender, shooting, isOur, damageCache, exchangeBattle);
|
||||
|
||||
if(!attacker->alive() || !defender->alive())
|
||||
break;
|
||||
v.trackAttack(ap, exchangeBattle, damageCache);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int i = 0; i < totalAttacks; i++)
|
||||
{
|
||||
v.trackAttack(attacker, defender, shooting, isOur, damageCache, exchangeBattle);
|
||||
|
||||
if(!attacker->alive() || !defender->alive())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!shooting)
|
||||
blockedShooters.insert(defender->unitId());
|
||||
|
||||
canUseAp = false;
|
||||
|
||||
vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
|
||||
vstd::erase_if(oppositeQueue, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
}
|
||||
|
||||
canUseAp = false;
|
||||
|
||||
vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
|
||||
vstd::erase_if(oppositeQueue, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
exchangeBattle->nextRound();
|
||||
}
|
||||
|
||||
// avoid blocking path for stronger stack by weaker stack
|
||||
@ -687,11 +866,28 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
for(auto hex : hexes)
|
||||
reachabilityMap[hex] = getOneTurnReachableUnits(turn, hex);
|
||||
|
||||
auto score = v.getScore();
|
||||
|
||||
if(simulationTurnsCount < totalTurnsCount)
|
||||
{
|
||||
float scalingRatio = simulationTurnsCount / static_cast<float>(totalTurnsCount);
|
||||
|
||||
score.enemyDamageReduce *= scalingRatio;
|
||||
score.ourDamageReduce *= scalingRatio;
|
||||
}
|
||||
|
||||
if(turn > 0)
|
||||
{
|
||||
auto turnMultiplier = 1 - std::min(0.2, 0.05 * turn);
|
||||
|
||||
score.enemyDamageReduce *= turnMultiplier;
|
||||
}
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Exchange score: enemy: %2f, our -%2f", v.getScore().enemyDamageReduce, v.getScore().ourDamageReduce);
|
||||
logAi->trace("Exchange score: enemy: %2f, our -%2f", score.enemyDamageReduce, score.ourDamageReduce);
|
||||
#endif
|
||||
|
||||
return v.getScore();
|
||||
return score;
|
||||
}
|
||||
|
||||
bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap)
|
||||
|
@ -45,7 +45,7 @@ struct BattleScore
|
||||
struct AttackerValue
|
||||
{
|
||||
float value;
|
||||
bool isRetalitated;
|
||||
bool isRetaliated;
|
||||
BattleHex position;
|
||||
|
||||
AttackerValue();
|
||||
@ -54,7 +54,6 @@ struct AttackerValue
|
||||
struct MoveTarget
|
||||
{
|
||||
float score;
|
||||
float scorePerTurn;
|
||||
std::vector<BattleHex> positions;
|
||||
std::optional<AttackPossibility> cachedAttack;
|
||||
uint8_t turnsToRich;
|
||||
@ -64,7 +63,7 @@ struct MoveTarget
|
||||
|
||||
struct EvaluationResult
|
||||
{
|
||||
static const int64_t INEFFECTIVE_SCORE = -10000;
|
||||
static const int64_t INEFFECTIVE_SCORE = -100000000;
|
||||
|
||||
AttackPossibility bestAttack;
|
||||
MoveTarget bestMove;
|
||||
@ -113,13 +112,15 @@ private:
|
||||
|
||||
struct ReachabilityData
|
||||
{
|
||||
std::vector<const battle::Unit *> units;
|
||||
std::map<int, std::vector<const battle::Unit *>> units;
|
||||
|
||||
// shooters which are within mellee attack and mellee units
|
||||
std::vector<const battle::Unit *> melleeAccessible;
|
||||
|
||||
// far shooters
|
||||
std::vector<const battle::Unit *> shooters;
|
||||
|
||||
std::set<uint32_t> enemyUnitsReachingAttacker;
|
||||
};
|
||||
|
||||
class BattleExchangeEvaluator
|
||||
@ -131,6 +132,7 @@ private:
|
||||
std::map<BattleHex, std::vector<const battle::Unit *>> reachabilityMap;
|
||||
std::vector<battle::Units> turnOrder;
|
||||
float negativeEffectMultiplier;
|
||||
int simulationTurnsCount;
|
||||
|
||||
float scoreValue(const BattleScore & score) const;
|
||||
|
||||
@ -139,7 +141,8 @@ private:
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> hb) const;
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits = {}) const;
|
||||
|
||||
bool canBeHitThisTurn(const AttackPossibility & ap);
|
||||
|
||||
@ -147,15 +150,17 @@ public:
|
||||
BattleExchangeEvaluator(
|
||||
std::shared_ptr<CBattleInfoCallback> cb,
|
||||
std::shared_ptr<Environment> env,
|
||||
float strengthRatio): cb(cb), env(env) {
|
||||
negativeEffectMultiplier = strengthRatio >= 1 ? 1 : strengthRatio;
|
||||
float strengthRatio,
|
||||
int simulationTurnsCount): cb(cb), env(env), simulationTurnsCount(simulationTurnsCount){
|
||||
negativeEffectMultiplier = strengthRatio >= 1 ? 1 : strengthRatio * strengthRatio;
|
||||
}
|
||||
|
||||
EvaluationResult findBestTarget(
|
||||
const battle::Unit * activeStack,
|
||||
PotentialTargets & targets,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> hb);
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
bool siegeDefense = false);
|
||||
|
||||
float evaluateExchange(
|
||||
const AttackPossibility & ap,
|
||||
@ -171,7 +176,8 @@ public:
|
||||
const AttackPossibility & ap,
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
std::shared_ptr<HypotheticBattle> hb) const;
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits = {}) const;
|
||||
|
||||
bool checkPositionBlocksOurStacks(HypotheticBattle & hb, const battle::Unit * unit, BattleHex position);
|
||||
|
||||
|
@ -37,11 +37,7 @@ else()
|
||||
endif()
|
||||
|
||||
target_include_directories(BattleAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(BattleAI PRIVATE vcmi TBB::tbb)
|
||||
target_link_libraries(BattleAI PRIVATE vcmi)
|
||||
|
||||
vcmi_set_output_dir(BattleAI "AI")
|
||||
enable_pch(BattleAI)
|
||||
|
||||
if(APPLE_IOS AND NOT USING_CONAN)
|
||||
install(IMPORTED_RUNTIME_ARTIFACTS TBB::tbb LIBRARY DESTINATION ${LIB_DIR}) # CMake 3.21+
|
||||
endif()
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "StdInc.h"
|
||||
#include "PotentialTargets.h"
|
||||
#include "../../lib/CStack.h"//todo: remove
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
|
||||
PotentialTargets::PotentialTargets(
|
||||
const battle::Unit * attacker,
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
#include <vcmi/events/EventBus.h>
|
||||
|
||||
#include "../../lib/battle/BattleLayout.h"
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/ScriptHandler.h"
|
||||
#include "../../lib/networkPacks/PacksForClientBattle.h"
|
||||
@ -116,7 +117,7 @@ uint32_t StackWithBonuses::unitId() const
|
||||
return id;
|
||||
}
|
||||
|
||||
ui8 StackWithBonuses::unitSide() const
|
||||
BattleSide StackWithBonuses::unitSide() const
|
||||
{
|
||||
return side;
|
||||
}
|
||||
@ -132,10 +133,10 @@ SlotID StackWithBonuses::unitSlot() const
|
||||
}
|
||||
|
||||
TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const CSelector & limit,
|
||||
const CBonusSystemNode * root, const std::string & cachingStr) const
|
||||
const std::string & cachingStr) const
|
||||
{
|
||||
auto ret = std::make_shared<BonusList>();
|
||||
TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, root, cachingStr);
|
||||
TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, cachingStr);
|
||||
|
||||
vstd::copy_if(*originalList, std::back_inserter(*ret), [this](const std::shared_ptr<Bonus> & b)
|
||||
{
|
||||
@ -467,7 +468,7 @@ int64_t HypotheticBattle::getActualDamage(const DamageRange & damage, int32_t at
|
||||
return (damage.min + damage.max) / 2;
|
||||
}
|
||||
|
||||
std::vector<SpellID> HypotheticBattle::getUsedSpells(ui8 side) const
|
||||
std::vector<SpellID> HypotheticBattle::getUsedSpells(BattleSide side) const
|
||||
{
|
||||
// TODO
|
||||
return {};
|
||||
@ -479,10 +480,9 @@ int3 HypotheticBattle::getLocation() const
|
||||
return int3(-1, -1, -1);
|
||||
}
|
||||
|
||||
bool HypotheticBattle::isCreatureBank() const
|
||||
BattleLayout HypotheticBattle::getLayout() const
|
||||
{
|
||||
// TODO
|
||||
return false;
|
||||
return subject->getBattle()->getLayout();
|
||||
}
|
||||
|
||||
int64_t HypotheticBattle::getTreeVersion() const
|
||||
@ -502,10 +502,18 @@ ServerCallback * HypotheticBattle::getServerCallback()
|
||||
return serverCallback.get();
|
||||
}
|
||||
|
||||
void HypotheticBattle::makeWait(const battle::Unit * activeStack)
|
||||
{
|
||||
auto unit = getForUpdate(activeStack->unitId());
|
||||
|
||||
resetActiveUnit();
|
||||
unit->waiting = true;
|
||||
unit->waitedThisTurn = true;
|
||||
}
|
||||
|
||||
HypotheticBattle::HypotheticServerCallback::HypotheticServerCallback(HypotheticBattle * owner_)
|
||||
:owner(owner_)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::complain(const std::string & problem)
|
||||
@ -523,44 +531,44 @@ vstd::RNG * HypotheticBattle::HypotheticServerCallback::getRNG()
|
||||
return &rngStub;
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(CPackForClient * pack)
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(CPackForClient & pack)
|
||||
{
|
||||
logAi->error("Package of type %s is not allowed in battle evaluation", typeid(pack).name());
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(BattleLogMessage * pack)
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(BattleLogMessage & pack)
|
||||
{
|
||||
pack->applyBattle(owner);
|
||||
pack.applyBattle(owner);
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(BattleStackMoved * pack)
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(BattleStackMoved & pack)
|
||||
{
|
||||
pack->applyBattle(owner);
|
||||
pack.applyBattle(owner);
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(BattleUnitsChanged * pack)
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(BattleUnitsChanged & pack)
|
||||
{
|
||||
pack->applyBattle(owner);
|
||||
pack.applyBattle(owner);
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(SetStackEffect * pack)
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(SetStackEffect & pack)
|
||||
{
|
||||
pack->applyBattle(owner);
|
||||
pack.applyBattle(owner);
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(StacksInjured * pack)
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(StacksInjured & pack)
|
||||
{
|
||||
pack->applyBattle(owner);
|
||||
pack.applyBattle(owner);
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(BattleObstaclesChanged * pack)
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(BattleObstaclesChanged & pack)
|
||||
{
|
||||
pack->applyBattle(owner);
|
||||
pack.applyBattle(owner);
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(CatapultAttack * pack)
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(CatapultAttack & pack)
|
||||
{
|
||||
pack->applyBattle(owner);
|
||||
pack.applyBattle(owner);
|
||||
}
|
||||
|
||||
HypotheticBattle::HypotheticEnvironment::HypotheticEnvironment(HypotheticBattle * owner_, const Environment * upperEnvironment)
|
||||
|
@ -21,23 +21,43 @@
|
||||
class HypotheticBattle;
|
||||
|
||||
///Fake random generator, used by AI to evaluate random server behavior
|
||||
class RNGStub : public vstd::RNG
|
||||
class RNGStub final : public vstd::RNG
|
||||
{
|
||||
public:
|
||||
vstd::TRandI64 getInt64Range(int64_t lower, int64_t upper) override
|
||||
int nextInt() override
|
||||
{
|
||||
return [=]()->int64_t
|
||||
{
|
||||
return (lower + upper)/2;
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
|
||||
vstd::TRand getDoubleRange(double lower, double upper) override
|
||||
int nextBinomialInt(int coinsCount, double coinChance) override
|
||||
{
|
||||
return [=]()->double
|
||||
{
|
||||
return (lower + upper)/2;
|
||||
};
|
||||
return coinsCount * coinChance;
|
||||
}
|
||||
|
||||
int nextInt(int lower, int upper) override
|
||||
{
|
||||
return (lower + upper) / 2;
|
||||
}
|
||||
int64_t nextInt64(int64_t lower, int64_t upper) override
|
||||
{
|
||||
return (lower + upper) / 2;
|
||||
}
|
||||
double nextDouble(double lower, double upper) override
|
||||
{
|
||||
return (lower + upper) / 2;
|
||||
}
|
||||
|
||||
int nextInt(int upper) override
|
||||
{
|
||||
return upper / 2;
|
||||
}
|
||||
int64_t nextInt64(int64_t upper) override
|
||||
{
|
||||
return upper / 2;
|
||||
}
|
||||
double nextDouble(double upper) override
|
||||
{
|
||||
return upper / 2;
|
||||
}
|
||||
};
|
||||
|
||||
@ -65,13 +85,13 @@ public:
|
||||
int32_t unitBaseAmount() const override;
|
||||
|
||||
uint32_t unitId() const override;
|
||||
ui8 unitSide() const override;
|
||||
BattleSide unitSide() const override;
|
||||
PlayerColor unitOwner() const override;
|
||||
SlotID unitSlot() const override;
|
||||
|
||||
///IBonusBearer
|
||||
TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit,
|
||||
const CBonusSystemNode * root = nullptr, const std::string & cachingStr = "") const override;
|
||||
const std::string & cachingStr = "") const override;
|
||||
|
||||
int64_t getTreeVersion() const override;
|
||||
|
||||
@ -91,7 +111,7 @@ private:
|
||||
const CCreature * type;
|
||||
ui32 baseAmount;
|
||||
uint32_t id;
|
||||
ui8 side;
|
||||
BattleSide side;
|
||||
PlayerColor player;
|
||||
SlotID slot;
|
||||
};
|
||||
@ -138,12 +158,19 @@ public:
|
||||
uint32_t nextUnitId() const override;
|
||||
|
||||
int64_t getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const override;
|
||||
std::vector<SpellID> getUsedSpells(ui8 side) const override;
|
||||
std::vector<SpellID> getUsedSpells(BattleSide side) const override;
|
||||
int3 getLocation() const override;
|
||||
bool isCreatureBank() const override;
|
||||
BattleLayout getLayout() const override;
|
||||
|
||||
int64_t getTreeVersion() const;
|
||||
|
||||
void makeWait(const battle::Unit * activeStack);
|
||||
|
||||
void resetActiveUnit()
|
||||
{
|
||||
activeUnitId = -1;
|
||||
}
|
||||
|
||||
#if SCRIPTING_ENABLED
|
||||
scripting::Pool * getContextPool() const override;
|
||||
#endif
|
||||
@ -162,15 +189,15 @@ private:
|
||||
|
||||
vstd::RNG * getRNG() override;
|
||||
|
||||
void apply(CPackForClient * pack) override;
|
||||
void apply(CPackForClient & pack) override;
|
||||
|
||||
void apply(BattleLogMessage * pack) override;
|
||||
void apply(BattleStackMoved * pack) override;
|
||||
void apply(BattleUnitsChanged * pack) override;
|
||||
void apply(SetStackEffect * pack) override;
|
||||
void apply(StacksInjured * pack) override;
|
||||
void apply(BattleObstaclesChanged * pack) override;
|
||||
void apply(CatapultAttack * pack) override;
|
||||
void apply(BattleLogMessage & pack) override;
|
||||
void apply(BattleStackMoved & pack) override;
|
||||
void apply(BattleUnitsChanged & pack) override;
|
||||
void apply(SetStackEffect & pack) override;
|
||||
void apply(StacksInjured & pack) override;
|
||||
void apply(BattleObstaclesChanged & pack) override;
|
||||
void apply(CatapultAttack & pack) override;
|
||||
private:
|
||||
HypotheticBattle * owner;
|
||||
RNGStub rngStub;
|
||||
|
@ -70,4 +70,4 @@ ThreatMap::ThreatMap(const CStack *Endangered) : endangered(Endangered)
|
||||
});
|
||||
}
|
||||
}
|
||||
*/ // These lines may be usefull but they are't used in the code.
|
||||
*/ // These lines may be useful but they are't used in the code.
|
||||
|
@ -22,4 +22,4 @@ public:
|
||||
std::array<int, GameConstants::BFIELD_SIZE> sufferedDamage;
|
||||
|
||||
ThreatMap(const CStack *Endangered);
|
||||
};*/ // These lines may be usefull but they are't used in the code.
|
||||
};*/ // These lines may be useful but they are't used in the code.
|
||||
|
@ -8,10 +8,6 @@ else()
|
||||
option(FORCE_BUNDLED_FL "Force to use FuzzyLite included into VCMI's source tree" OFF)
|
||||
endif()
|
||||
|
||||
if(TBB_FOUND AND MSVC)
|
||||
install_vcpkg_imported_tgt(TBB::tbb)
|
||||
endif()
|
||||
|
||||
#FuzzyLite uses MSVC pragmas in headers, so, we need to disable -Wunknown-pragmas
|
||||
if(MINGW)
|
||||
add_compile_options(-Wno-unknown-pragmas)
|
||||
|
@ -14,14 +14,6 @@
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/battle/BattleAction.h"
|
||||
|
||||
void CEmptyAI::saveGame(BinarySerializer & h)
|
||||
{
|
||||
}
|
||||
|
||||
void CEmptyAI::loadGame(BinaryDeserializer & h)
|
||||
{
|
||||
}
|
||||
|
||||
void CEmptyAI::initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB)
|
||||
{
|
||||
cb = CB;
|
||||
|
@ -19,9 +19,6 @@ class CEmptyAI : public CGlobalAI
|
||||
std::shared_ptr<CCallback> cb;
|
||||
|
||||
public:
|
||||
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;
|
||||
void yourTacticPhase(const BattleID & battleID, int distance) override;
|
||||
|
@ -1,86 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<CodeBlocks_project_file>
|
||||
<FileVersion major="1" minor="6" />
|
||||
<Project>
|
||||
<Option title="EmptyAI" />
|
||||
<Option pch_mode="2" />
|
||||
<Option compiler="gcc" />
|
||||
<Build>
|
||||
<Target title="Debug-win32">
|
||||
<Option platforms="Windows;" />
|
||||
<Option output="../EmptyAI" imp_lib="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).a" def_file="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).def" prefix_auto="1" extension_auto="1" />
|
||||
<Option object_output="obj/Debug/x86/" />
|
||||
<Option type="3" />
|
||||
<Option compiler="gcc" />
|
||||
<Compiler>
|
||||
<Add option="-ggdb" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add option="-lboost_system$(#boost.libsuffix32)" />
|
||||
<Add option="-lVCMI_lib" />
|
||||
<Add directory="$(#boost.lib32)" />
|
||||
</Linker>
|
||||
</Target>
|
||||
<Target title="Release-win32">
|
||||
<Option platforms="Windows;" />
|
||||
<Option output="../EmptyAI" imp_lib="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).a" def_file="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).def" prefix_auto="1" extension_auto="1" />
|
||||
<Option object_output="obj/Release/x86/" />
|
||||
<Option type="3" />
|
||||
<Option compiler="gcc" />
|
||||
<Compiler>
|
||||
<Add option="-O2" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add option="-lboost_system$(#boost.libsuffix32)" />
|
||||
<Add option="-lVCMI_lib" />
|
||||
<Add directory="$(#boost.lib32)" />
|
||||
</Linker>
|
||||
</Target>
|
||||
<Target title="Debug-win64">
|
||||
<Option platforms="Windows;" />
|
||||
<Option output="../EmptyAI" imp_lib="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).a" def_file="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).def" prefix_auto="1" extension_auto="1" />
|
||||
<Option object_output="obj/Debug/x64/" />
|
||||
<Option type="3" />
|
||||
<Option compiler="gnu_gcc_compiler_x64" />
|
||||
<Compiler>
|
||||
<Add option="-ggdb" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add option="-lboost_system$(#boost.libsuffix64)" />
|
||||
<Add option="-lVCMI_lib" />
|
||||
<Add directory="$(#boost.lib64)" />
|
||||
</Linker>
|
||||
</Target>
|
||||
</Build>
|
||||
<Compiler>
|
||||
<Add option="-Wextra" />
|
||||
<Add option="-Wall" />
|
||||
<Add option="-std=gnu++11" />
|
||||
<Add option="-fexceptions" />
|
||||
<Add option="-Wpointer-arith" />
|
||||
<Add option="-Wno-switch" />
|
||||
<Add option="-Wno-sign-compare" />
|
||||
<Add option="-Wno-unused-parameter" />
|
||||
<Add option="-Wno-overloaded-virtual" />
|
||||
<Add option="-fpermissive" />
|
||||
<Add option="-D_WIN32_WINNT=0x0600" />
|
||||
<Add option="-D_WIN32" />
|
||||
<Add option="-DBOOST_ALL_DYN_LINK" />
|
||||
<Add directory="$(#boost.include)" />
|
||||
<Add directory="../../include" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add directory="../.." />
|
||||
</Linker>
|
||||
<Unit filename="CEmptyAI.cpp" />
|
||||
<Unit filename="CEmptyAI.h" />
|
||||
<Unit filename="StdInc.h">
|
||||
<Option compile="1" />
|
||||
<Option weight="0" />
|
||||
</Unit>
|
||||
<Unit filename="exp_funcs.cpp" />
|
||||
<Extensions>
|
||||
<lib_finder disable_auto="1" />
|
||||
</Extensions>
|
||||
</Project>
|
||||
</CodeBlocks_project_file>
|
@ -1,181 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="RD|Win32">
|
||||
<Configuration>RD</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="RD|x64">
|
||||
<Configuration>RD</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="CEmptyAI.cpp" />
|
||||
<ClCompile Include="exp_funcs.cpp" />
|
||||
<ClCompile Include="StdInc.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='RD|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="CEmptyAI.h" />
|
||||
<ClInclude Include="StdInc.h" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{C41C4EB6-6F74-4F37-9FB0-9FA6BF377837}</ProjectGuid>
|
||||
<RootNamespace>EmptyAI</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<PlatformToolset>v140_xp</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<PlatformToolset>v140_xp</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<PlatformToolset>v140_xp</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\..\VCMI_global_debug.props" />
|
||||
<Import Project="..\..\VCMI_global.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\..\VCMI_global_debug.props" />
|
||||
<Import Project="..\..\VCMI_global.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\..\VCMI_global_release.props" />
|
||||
<Import Project="..\..\VCMI_global.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\..\VCMI_global_release.props" />
|
||||
<Import Project="..\..\VCMI_global.props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<OutDir>$(VCMI_Out)\AI\</OutDir>
|
||||
<IncludePath>$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<OutDir>$(VCMI_Out)\AI\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">
|
||||
<OutDir>$(VCMI_Out)/AI</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'">
|
||||
<OutDir>$(VCMI_Out)\AI\</OutDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
|
||||
<PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalOptions>/Zm130 %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<OutputFile>$(OutDir)EmptyAI.dll</OutputFile>
|
||||
<AdditionalLibraryDirectories>..\..\..\libs;..\..</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
|
||||
<PreprocessorDefinitions>_WINDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<OutputFile>$(OutDir)EmptyAI.dll</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
|
||||
<PreprocessorDefinitions>_WINDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<OutputFile>$(OutDir)EmptyAI.dll</OutputFile>
|
||||
<AdditionalLibraryDirectories>$(VCMI_Out)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
|
||||
<PreprocessorDefinitions>_WINDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalOptions>/Zm130 %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<OutputFile>$(OutDir)EmptyAI.dll</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
285
AI/FuzzyLite.cbp
285
AI/FuzzyLite.cbp
@ -1,285 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<CodeBlocks_project_file>
|
||||
<FileVersion major="1" minor="6" />
|
||||
<Project>
|
||||
<Option title="FuzzyLite" />
|
||||
<Option pch_mode="2" />
|
||||
<Option compiler="gcc" />
|
||||
<Build>
|
||||
<Target title="Debug-win32">
|
||||
<Option platforms="Windows;" />
|
||||
<Option output="FuzzyLite" prefix_auto="1" extension_auto="1" />
|
||||
<Option working_dir="" />
|
||||
<Option object_output="../obj/FuzzyLite/Debug/x86" />
|
||||
<Option type="2" />
|
||||
<Option compiler="gcc" />
|
||||
<Option createDefFile="1" />
|
||||
<Compiler>
|
||||
<Add option="-Og" />
|
||||
<Add option="-g" />
|
||||
</Compiler>
|
||||
</Target>
|
||||
<Target title="Release-win32">
|
||||
<Option platforms="Windows;" />
|
||||
<Option output="FuzzyLite" prefix_auto="1" extension_auto="1" />
|
||||
<Option working_dir="" />
|
||||
<Option object_output="../obj/FuzzyLite/Release/x86" />
|
||||
<Option type="2" />
|
||||
<Option compiler="gcc" />
|
||||
<Option createDefFile="1" />
|
||||
<Compiler>
|
||||
<Add option="-fomit-frame-pointer" />
|
||||
<Add option="-O2" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add option="-s" />
|
||||
</Linker>
|
||||
</Target>
|
||||
<Target title="Debug-win64">
|
||||
<Option platforms="Windows;" />
|
||||
<Option output="FuzzyLite" prefix_auto="1" extension_auto="1" />
|
||||
<Option working_dir="" />
|
||||
<Option object_output="../obj/FuzzyLite/Debug/x64" />
|
||||
<Option type="2" />
|
||||
<Option compiler="gnu_gcc_compiler_x64" />
|
||||
<Option createDefFile="1" />
|
||||
<Compiler>
|
||||
<Add option="-Og" />
|
||||
<Add option="-g" />
|
||||
</Compiler>
|
||||
</Target>
|
||||
</Build>
|
||||
<Compiler>
|
||||
<Add option="-Wextra" />
|
||||
<Add option="-Wall" />
|
||||
<Add option="-std=gnu++11" />
|
||||
<Add option="-fexceptions" />
|
||||
<Add option="-Wpointer-arith" />
|
||||
<Add option="-Wno-switch" />
|
||||
<Add option="-Wno-sign-compare" />
|
||||
<Add option="-Wno-unused-parameter" />
|
||||
<Add option="-Wno-overloaded-virtual" />
|
||||
<Add option="-DFL_CPP11" />
|
||||
<Add directory="FuzzyLite/fuzzylite" />
|
||||
</Compiler>
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/Benchmark.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/Complexity.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/Console.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/Engine.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/Exception.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/Headers.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/Operation.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/activation/Activation.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/activation/First.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/activation/General.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/activation/Highest.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/activation/Last.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/activation/Lowest.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/activation/Proportional.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/activation/Threshold.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/defuzzifier/Bisector.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/defuzzifier/Centroid.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/defuzzifier/Defuzzifier.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/defuzzifier/IntegralDefuzzifier.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/defuzzifier/LargestOfMaximum.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/defuzzifier/MeanOfMaximum.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/defuzzifier/SmallestOfMaximum.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/defuzzifier/WeightedAverage.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/defuzzifier/WeightedAverageCustom.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/defuzzifier/WeightedDefuzzifier.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/defuzzifier/WeightedSum.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/defuzzifier/WeightedSumCustom.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/factory/ActivationFactory.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/factory/CloningFactory.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/factory/ConstructionFactory.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/factory/DefuzzifierFactory.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/factory/FactoryManager.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/factory/FunctionFactory.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/factory/HedgeFactory.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/factory/SNormFactory.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/factory/TNormFactory.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/factory/TermFactory.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/fuzzylite.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/hedge/Any.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/hedge/Extremely.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/hedge/Hedge.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/hedge/HedgeFunction.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/hedge/Not.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/hedge/Seldom.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/hedge/Somewhat.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/hedge/Very.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/imex/CppExporter.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/imex/Exporter.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/imex/FclExporter.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/imex/FclImporter.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/imex/FisExporter.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/imex/FisImporter.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/imex/FldExporter.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/imex/FllExporter.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/imex/FllImporter.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/imex/Importer.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/imex/JavaExporter.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/imex/RScriptExporter.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/Norm.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/SNorm.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/TNorm.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/s/AlgebraicSum.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/s/BoundedSum.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/s/DrasticSum.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/s/EinsteinSum.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/s/HamacherSum.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/s/Maximum.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/s/NilpotentMaximum.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/s/NormalizedSum.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/s/SNormFunction.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/s/UnboundedSum.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/t/AlgebraicProduct.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/t/BoundedDifference.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/t/DrasticProduct.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/t/EinsteinProduct.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/t/HamacherProduct.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/t/Minimum.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/t/NilpotentMinimum.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/norm/t/TNormFunction.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/rule/Antecedent.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/rule/Consequent.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/rule/Expression.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/rule/Rule.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/rule/RuleBlock.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Activated.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Aggregated.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Bell.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Binary.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Concave.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Constant.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Cosine.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Discrete.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Function.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Gaussian.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/GaussianProduct.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Linear.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/PiShape.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Ramp.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Rectangle.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/SShape.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Sigmoid.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/SigmoidDifference.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/SigmoidProduct.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Spike.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Term.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Trapezoid.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/Triangle.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/term/ZShape.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/variable/InputVariable.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/variable/OutputVariable.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/fl/variable/Variable.h" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/Benchmark.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/Complexity.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/Console.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/Engine.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/Exception.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/activation/First.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/activation/General.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/activation/Highest.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/activation/Last.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/activation/Lowest.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/activation/Proportional.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/activation/Threshold.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/defuzzifier/Bisector.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/defuzzifier/Centroid.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/defuzzifier/IntegralDefuzzifier.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/defuzzifier/LargestOfMaximum.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/defuzzifier/MeanOfMaximum.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/defuzzifier/SmallestOfMaximum.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/defuzzifier/WeightedAverage.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/defuzzifier/WeightedAverageCustom.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/defuzzifier/WeightedDefuzzifier.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/defuzzifier/WeightedSum.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/defuzzifier/WeightedSumCustom.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/factory/ActivationFactory.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/factory/DefuzzifierFactory.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/factory/FactoryManager.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/factory/FunctionFactory.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/factory/HedgeFactory.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/factory/SNormFactory.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/factory/TNormFactory.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/factory/TermFactory.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/fuzzylite.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/hedge/Any.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/hedge/Extremely.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/hedge/HedgeFunction.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/hedge/Not.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/hedge/Seldom.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/hedge/Somewhat.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/hedge/Very.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/imex/CppExporter.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/imex/Exporter.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/imex/FclExporter.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/imex/FclImporter.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/imex/FisExporter.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/imex/FisImporter.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/imex/FldExporter.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/imex/FllExporter.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/imex/FllImporter.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/imex/Importer.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/imex/JavaExporter.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/imex/RScriptExporter.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/main.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/s/AlgebraicSum.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/s/BoundedSum.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/s/DrasticSum.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/s/EinsteinSum.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/s/HamacherSum.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/s/Maximum.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/s/NilpotentMaximum.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/s/NormalizedSum.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/s/SNormFunction.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/s/UnboundedSum.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/t/AlgebraicProduct.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/t/BoundedDifference.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/t/DrasticProduct.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/t/EinsteinProduct.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/t/HamacherProduct.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/t/Minimum.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/t/NilpotentMinimum.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/norm/t/TNormFunction.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/rule/Antecedent.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/rule/Consequent.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/rule/Expression.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/rule/Rule.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/rule/RuleBlock.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Activated.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Aggregated.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Bell.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Binary.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Concave.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Constant.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Cosine.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Discrete.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Function.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Gaussian.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/GaussianProduct.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Linear.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/PiShape.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Ramp.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Rectangle.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/SShape.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Sigmoid.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/SigmoidDifference.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/SigmoidProduct.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Spike.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Term.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Trapezoid.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/Triangle.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/term/ZShape.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/variable/InputVariable.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/variable/OutputVariable.cpp" />
|
||||
<Unit filename="FuzzyLite/fuzzylite/src/variable/Variable.cpp" />
|
||||
<Extensions>
|
||||
<code_completion />
|
||||
<envvars />
|
||||
<debugger />
|
||||
<lib_finder disable_auto="1" />
|
||||
</Extensions>
|
||||
</Project>
|
||||
</CodeBlocks_project_file>
|
@ -12,22 +12,21 @@
|
||||
#include "../../lib/ArtifactUtils.h"
|
||||
#include "../../lib/UnlockGuard.h"
|
||||
#include "../../lib/StartInfo.h"
|
||||
#include "../../lib/entities/building/CBuilding.h"
|
||||
#include "../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../lib/mapObjects/ObjectTemplate.h"
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/GameSettings.h"
|
||||
#include "../../lib/IGameSettings.h"
|
||||
#include "../../lib/gameState/CGameState.h"
|
||||
#include "../../lib/serializer/CTypeList.h"
|
||||
#include "../../lib/serializer/BinarySerializer.h"
|
||||
#include "../../lib/serializer/BinaryDeserializer.h"
|
||||
#include "../../lib/networkPacks/PacksForClient.h"
|
||||
#include "../../lib/networkPacks/PacksForClientBattle.h"
|
||||
#include "../../lib/networkPacks/PacksForServer.h"
|
||||
#include "../../lib/networkPacks/StackLocation.h"
|
||||
#include "../../lib/battle/BattleStateInfoForRetreat.h"
|
||||
#include "../../lib/battle/BattleInfo.h"
|
||||
#include "../../lib/CPlayerState.h"
|
||||
|
||||
#include "AIGateway.h"
|
||||
#include "Goals/Goals.h"
|
||||
@ -35,11 +34,6 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
// our to enemy strength ratio constants
|
||||
const float SAFE_ATTACK_CONSTANT = 1.1f;
|
||||
const float RETREAT_THRESHOLD = 0.3f;
|
||||
const double RETREAT_ABSOLUTE_THRESHOLD = 10000.;
|
||||
|
||||
//one thread may be turn of AI and another will be handling a side effect for AI2
|
||||
thread_local CCallback * cb = nullptr;
|
||||
thread_local AIGateway * ai = nullptr;
|
||||
@ -287,6 +281,9 @@ void AIGateway::tileRevealed(const std::unordered_set<int3> & pos)
|
||||
for(const CGObjectInstance * obj : myCb->getVisitableObjs(tile))
|
||||
addVisitableObj(obj);
|
||||
}
|
||||
|
||||
if (nullkiller->settings->isUpdateHitmapOnTileReveal())
|
||||
nullkiller->dangerHitMap->reset();
|
||||
}
|
||||
|
||||
void AIGateway::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query)
|
||||
@ -500,7 +497,7 @@ void AIGateway::objectPropertyChanged(const SetObjectProperty * sop)
|
||||
if(relations == PlayerRelations::ENEMIES)
|
||||
{
|
||||
//we want to visit objects owned by oppponents
|
||||
//addVisitableObj(obj); // TODO: Remove once save compatability broken. In past owned objects were removed from this set
|
||||
//addVisitableObj(obj); // TODO: Remove once save compatibility broken. In past owned objects were removed from this set
|
||||
nullkiller->memory->markObjectUnvisited(obj);
|
||||
}
|
||||
else if(relations == PlayerRelations::SAME_PLAYER && obj->ID == Obj::TOWN)
|
||||
@ -554,7 +551,7 @@ std::optional<BattleAction> AIGateway::makeSurrenderRetreatDecision(const Battle
|
||||
double fightRatio = ourStrength / (double)battleState.getEnemyStrength();
|
||||
|
||||
// if we have no towns - things are already bad, so retreat is not an option.
|
||||
if(cb->getTownsInfo().size() && ourStrength < RETREAT_ABSOLUTE_THRESHOLD && fightRatio < RETREAT_THRESHOLD && battleState.canFlee)
|
||||
if(cb->getTownsInfo().size() && ourStrength < nullkiller->settings->getRetreatThresholdAbsolute() && fightRatio < nullkiller->settings->getRetreatThresholdRelative() && battleState.canFlee)
|
||||
{
|
||||
return BattleAction::makeRetreat(battleState.ourSide);
|
||||
}
|
||||
@ -568,6 +565,7 @@ void AIGateway::initGameInterface(std::shared_ptr<Environment> env, std::shared_
|
||||
LOG_TRACE(logAi);
|
||||
myCb = CB;
|
||||
cbc = CB;
|
||||
this->env = env;
|
||||
|
||||
NET_EVENT_HANDLER;
|
||||
playerID = *myCb->getPlayerID();
|
||||
@ -603,7 +601,7 @@ void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, s
|
||||
|
||||
if(hPtr.validAndSet())
|
||||
{
|
||||
std::unique_lock<std::mutex> lockGuard(nullkiller->aiStateMutex);
|
||||
std::unique_lock lockGuard(nullkiller->aiStateMutex);
|
||||
|
||||
nullkiller->heroManager->update();
|
||||
|
||||
@ -648,7 +646,14 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
|
||||
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
|
||||
answer = true;
|
||||
|
||||
if(topObj->id != goalObjectID && nullkiller->dangerEvaluator->evaluateDanger(topObj) > 0)
|
||||
{
|
||||
// no if we do not aim to visit this object
|
||||
answer = false;
|
||||
}
|
||||
|
||||
logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), hero.name(), ratio);
|
||||
|
||||
if(cb->getObj(goalObjectID, false))
|
||||
@ -663,7 +668,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
|
||||
else if(objType == Obj::ARTIFACT || objType == Obj::RESOURCE)
|
||||
{
|
||||
bool dangerUnknown = danger == 0;
|
||||
bool dangerTooHigh = ratio > (1 / SAFE_ATTACK_CONSTANT);
|
||||
bool dangerTooHigh = ratio * nullkiller->settings->getSafeAttackRatio() > 1;
|
||||
|
||||
answer = !dangerUnknown && !dangerTooHigh;
|
||||
}
|
||||
@ -683,7 +688,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
|
||||
sel = components.size();
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> mxLock(nullkiller->aiStateMutex);
|
||||
std::unique_lock mxLock(nullkiller->aiStateMutex);
|
||||
|
||||
// TODO: Find better way to understand it is Chest of Treasures
|
||||
if(hero.validAndSet()
|
||||
@ -705,7 +710,7 @@ void AIGateway::showTeleportDialog(const CGHeroInstance * hero, TeleportChannelI
|
||||
NET_EVENT_HANDLER;
|
||||
status.addQuery(askID, boost::str(boost::format("Teleport dialog query with %d exits") % exits.size()));
|
||||
|
||||
int choosenExit = -1;
|
||||
int chosenExit = -1;
|
||||
if(impassable)
|
||||
{
|
||||
nullkiller->memory->knownTeleportChannels[channel]->passability = TeleportChannel::IMPASSABLE;
|
||||
@ -714,14 +719,14 @@ void AIGateway::showTeleportDialog(const CGHeroInstance * hero, TeleportChannelI
|
||||
{
|
||||
auto neededExit = std::make_pair(destinationTeleport, destinationTeleportPos);
|
||||
if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, neededExit))
|
||||
choosenExit = vstd::find_pos(exits, neededExit);
|
||||
chosenExit = vstd::find_pos(exits, neededExit);
|
||||
}
|
||||
|
||||
for(auto exit : exits)
|
||||
{
|
||||
if(status.channelProbing() && exit.first == destinationTeleport)
|
||||
{
|
||||
choosenExit = vstd::find_pos(exits, exit);
|
||||
chosenExit = vstd::find_pos(exits, exit);
|
||||
break;
|
||||
}
|
||||
else
|
||||
@ -739,7 +744,7 @@ void AIGateway::showTeleportDialog(const CGHeroInstance * hero, TeleportChannelI
|
||||
|
||||
requestActionASAP([=]()
|
||||
{
|
||||
answerQuery(askID, choosenExit);
|
||||
answerQuery(askID, chosenExit);
|
||||
});
|
||||
}
|
||||
|
||||
@ -756,7 +761,7 @@ void AIGateway::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstan
|
||||
//you can't request action from action-response thread
|
||||
requestActionASAP([=]()
|
||||
{
|
||||
if(removableUnits && up->tempOwner == down->tempOwner && nullkiller->settings->isGarrisonTroopsUsageAllowed() && !cb->getStartInfo()->isSteadwickFallCampaignMission())
|
||||
if(removableUnits && up->tempOwner == down->tempOwner && nullkiller->settings->isGarrisonTroopsUsageAllowed() && !cb->getStartInfo()->isRestorationOfErathiaCampaign())
|
||||
{
|
||||
pickBestCreatures(down, up);
|
||||
}
|
||||
@ -772,27 +777,6 @@ void AIGateway::showMapObjectSelectDialog(QueryID askID, const Component & icon,
|
||||
requestActionASAP([=](){ answerQuery(askID, selectedObject.getNum()); });
|
||||
}
|
||||
|
||||
void AIGateway::saveGame(BinarySerializer & h)
|
||||
{
|
||||
NET_EVENT_HANDLER;
|
||||
nullkiller->memory->removeInvisibleObjects(myCb.get());
|
||||
|
||||
CAdventureAI::saveGame(h);
|
||||
serializeInternal(h);
|
||||
}
|
||||
|
||||
void AIGateway::loadGame(BinaryDeserializer & h)
|
||||
{
|
||||
//NET_EVENT_HANDLER;
|
||||
|
||||
#if 0
|
||||
//disabled due to issue 2890
|
||||
registerGoals(h);
|
||||
#endif // 0
|
||||
CAdventureAI::loadGame(h);
|
||||
serializeInternal(h);
|
||||
}
|
||||
|
||||
bool AIGateway::makePossibleUpgrades(const CArmedInstance * obj)
|
||||
{
|
||||
if(!obj)
|
||||
@ -825,7 +809,7 @@ void AIGateway::makeTurn()
|
||||
auto day = cb->getDate(Date::DAY);
|
||||
logAi->info("Player %d (%s) starting turn, day %d", playerID, playerID.toString(), day);
|
||||
|
||||
boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
|
||||
boost::shared_lock gsLock(CGameState::mutex);
|
||||
setThreadName("AIGateway::makeTurn");
|
||||
|
||||
if(nullkiller->isOpenMap())
|
||||
@ -877,7 +861,7 @@ void AIGateway::makeTurn()
|
||||
|
||||
void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
|
||||
{
|
||||
LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->pos.toString());
|
||||
LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->anchorPos().toString());
|
||||
switch(obj->ID)
|
||||
{
|
||||
case Obj::TOWN:
|
||||
@ -885,7 +869,7 @@ void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h
|
||||
{
|
||||
makePossibleUpgrades(h.get());
|
||||
|
||||
std::unique_lock<std::mutex> lockGuard(nullkiller->aiStateMutex);
|
||||
std::unique_lock lockGuard(nullkiller->aiStateMutex);
|
||||
|
||||
if(!h->visitedTown->garrisonHero || !nullkiller->isHeroLocked(h->visitedTown->garrisonHero))
|
||||
moveCreaturesToHero(h->visitedTown);
|
||||
@ -1069,7 +1053,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
|
||||
//FIXME: why are the above possible to be null?
|
||||
|
||||
bool emptySlotFound = false;
|
||||
for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
|
||||
for(auto slot : artifact->getType()->getPossibleSlots().at(target->bearerType()))
|
||||
{
|
||||
if(target->isPositionFree(slot) && artifact->canBePutAt(target, slot, true)) //combined artifacts are not always allowed to move
|
||||
{
|
||||
@ -1082,7 +1066,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
|
||||
}
|
||||
if(!emptySlotFound) //try to put that atifact in already occupied slot
|
||||
{
|
||||
for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
|
||||
for(auto slot : artifact->getType()->getPossibleSlots().at(target->bearerType()))
|
||||
{
|
||||
auto otherSlot = target->getSlot(slot);
|
||||
if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one
|
||||
@ -1093,8 +1077,8 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
|
||||
{
|
||||
logAi->trace(
|
||||
"Exchange artifacts %s <-> %s",
|
||||
artifact->artType->getNameTranslated(),
|
||||
otherSlot->artifact->artType->getNameTranslated());
|
||||
artifact->getType()->getNameTranslated(),
|
||||
otherSlot->artifact->getType()->getNameTranslated());
|
||||
|
||||
if(!otherSlot->artifact->canBePutAt(artHolder, location.slot, true))
|
||||
{
|
||||
@ -1143,10 +1127,10 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re
|
||||
{
|
||||
for(auto stack : recruiter->Slots())
|
||||
{
|
||||
if(!stack.second->type)
|
||||
if(!stack.second->getType())
|
||||
continue;
|
||||
|
||||
auto duplicatingSlot = recruiter->getSlotFor(stack.second->type);
|
||||
auto duplicatingSlot = recruiter->getSlotFor(stack.second->getCreature());
|
||||
|
||||
if(duplicatingSlot != stack.first)
|
||||
{
|
||||
@ -1167,7 +1151,7 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re
|
||||
}
|
||||
}
|
||||
|
||||
void AIGateway::battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed)
|
||||
void AIGateway::battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, BattleSide side, bool replayAllowed)
|
||||
{
|
||||
NET_EVENT_HANDLER;
|
||||
assert(!playerID.isValidPlayer() || status.getBattle() == UPCOMING_BATTLE);
|
||||
@ -1187,6 +1171,17 @@ void AIGateway::battleEnd(const BattleID & battleID, const BattleResult * br, Qu
|
||||
battlename.clear();
|
||||
|
||||
CAdventureAI::battleEnd(battleID, br, queryID);
|
||||
|
||||
// gosolo
|
||||
if(queryID != QueryID::NONE && myCb->getPlayerState(playerID)->isHuman())
|
||||
{
|
||||
status.addQuery(queryID, "Confirm battle query");
|
||||
|
||||
requestActionASAP([=]()
|
||||
{
|
||||
answerQuery(queryID, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void AIGateway::waitTillFree()
|
||||
@ -1319,6 +1314,11 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
|
||||
auto doTeleportMovement = [&](ObjectInstanceID exitId, int3 exitPos)
|
||||
{
|
||||
if(cb->getObj(exitId) && cb->getObj(exitId)->ID == Obj::WHIRLPOOL)
|
||||
{
|
||||
nullkiller->armyFormation->rearrangeArmyForWhirlpool(*h);
|
||||
}
|
||||
|
||||
destinationTeleport = exitId;
|
||||
if(exitPos.valid())
|
||||
destinationTeleportPos = exitPos;
|
||||
@ -1340,6 +1340,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
status.setChannelProbing(true);
|
||||
for(auto exit : teleportChannelProbingList)
|
||||
doTeleportMovement(exit, int3(-1));
|
||||
|
||||
teleportChannelProbingList.clear();
|
||||
status.setChannelProbing(false);
|
||||
|
||||
@ -1450,8 +1451,8 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
|
||||
void AIGateway::buildStructure(const CGTownInstance * t, BuildingID building)
|
||||
{
|
||||
auto name = t->town->buildings.at(building)->getNameTranslated();
|
||||
logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->pos.toString());
|
||||
auto name = t->getTown()->buildings.at(building)->getNameTranslated();
|
||||
logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->anchorPos().toString());
|
||||
cb->buildBuilding(t, building); //just do this;
|
||||
}
|
||||
|
||||
@ -1473,7 +1474,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
|
||||
if(cb->getResourceAmount(GameResID(g.resID)) >= g.value) //goal is already fulfilled. Why we need this check, anyway?
|
||||
throw goalFulfilledException(sptr(g));
|
||||
|
||||
int accquiredResources = 0;
|
||||
int acquiredResources = 0;
|
||||
if(const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(g.objid), false))
|
||||
{
|
||||
if(const auto * m = dynamic_cast<const IMarket*>(obj))
|
||||
@ -1492,9 +1493,9 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
|
||||
//TODO trade only as much as needed
|
||||
if (toGive) //don't try to sell 0 resources
|
||||
{
|
||||
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());
|
||||
cb->trade(m->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
|
||||
acquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
|
||||
logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, acquiredResources, g.resID, obj->getObjectName());
|
||||
}
|
||||
if (cb->getResourceAmount(GameResID(g.resID)))
|
||||
throw goalFulfilledException(sptr(g)); //we traded all we needed
|
||||
@ -1565,7 +1566,7 @@ void AIGateway::requestActionASAP(std::function<void()> whatToDo)
|
||||
{
|
||||
setThreadName("AIGateway::requestActionASAP::whatToDo");
|
||||
SET_GLOBAL_STATE(this);
|
||||
boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
|
||||
boost::shared_lock gsLock(CGameState::mutex);
|
||||
whatToDo();
|
||||
});
|
||||
|
||||
|
@ -16,9 +16,7 @@
|
||||
#include "../../lib/CThreadHelper.h"
|
||||
#include "../../lib/GameConstants.h"
|
||||
#include "../../lib/VCMI_Lib.h"
|
||||
#include "../../lib/CBuildingHandler.h"
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
#include "../../lib/CTownHandler.h"
|
||||
#include "../../lib/mapObjects/MiscObjects.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
#include "Pathfinding/AIPathfinder.h"
|
||||
@ -59,15 +57,6 @@ public:
|
||||
void attemptedAnsweringQuery(QueryID queryID, int answerRequestID);
|
||||
void receivedAnswerConfirmation(int answerRequestID, int result);
|
||||
void heroVisit(const CGObjectInstance * obj, bool started);
|
||||
|
||||
|
||||
template<typename Handler> void serialize(Handler & h)
|
||||
{
|
||||
h & battle;
|
||||
h & remainingQueries;
|
||||
h & requestToQueryID;
|
||||
h & havingTurn;
|
||||
}
|
||||
};
|
||||
|
||||
// The gateway is responsible for AI events handling. Copied from VCAI.h and refined a bit
|
||||
@ -104,7 +93,7 @@ public:
|
||||
AIGateway();
|
||||
virtual ~AIGateway();
|
||||
|
||||
//TODO: extract to apropriate goals
|
||||
//TODO: extract to appropriate goals
|
||||
void tryRealize(Goals::DigAtTile & g);
|
||||
void tryRealize(Goals::Trade & g);
|
||||
|
||||
@ -119,8 +108,6 @@ 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) override; //saving
|
||||
void loadGame(BinaryDeserializer & h) override; //loading
|
||||
void finish() override;
|
||||
|
||||
void availableCreaturesChanged(const CGDwelling * town) override;
|
||||
@ -169,7 +156,7 @@ public:
|
||||
void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
|
||||
std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState) override;
|
||||
|
||||
void battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed) override;
|
||||
void battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, BattleSide side, bool replayAllowed) override;
|
||||
void battleEnd(const BattleID & battleID, const BattleResult * br, QueryID queryID) override;
|
||||
|
||||
void makeTurn();
|
||||
@ -202,17 +189,6 @@ public:
|
||||
void answerQuery(QueryID queryID, int selection);
|
||||
//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)
|
||||
{
|
||||
h & nullkiller->memory->knownTeleportChannels;
|
||||
h & nullkiller->memory->knownSubterraneanGates;
|
||||
h & destinationTeleport;
|
||||
h & nullkiller->memory->visitableObjs;
|
||||
h & nullkiller->memory->alreadyVisited;
|
||||
h & status;
|
||||
h & battlename;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -14,11 +14,10 @@
|
||||
|
||||
#include "../../lib/UnlockGuard.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../lib/mapping/CMapDefines.h"
|
||||
#include "../../lib/gameState/QuestInfo.h"
|
||||
#include "../../lib/GameSettings.h"
|
||||
#include "../../lib/IGameSettings.h"
|
||||
|
||||
#include <vcmi/CreatureService.h>
|
||||
|
||||
@ -147,21 +146,21 @@ bool HeroPtr::operator==(const HeroPtr & rhs) const
|
||||
return h == rhs.get(true);
|
||||
}
|
||||
|
||||
bool isSafeToVisit(const CGHeroInstance * h, const CCreatureSet * heroArmy, uint64_t dangerStrength)
|
||||
bool isSafeToVisit(const CGHeroInstance * h, const CCreatureSet * heroArmy, uint64_t dangerStrength, float safeAttackRatio)
|
||||
{
|
||||
const ui64 heroStrength = h->getFightingStrength() * heroArmy->getArmyStrength();
|
||||
const ui64 heroStrength = h->getHeroStrength() * heroArmy->getArmyStrength();
|
||||
|
||||
if(dangerStrength)
|
||||
{
|
||||
return heroStrength / SAFE_ATTACK_CONSTANT > dangerStrength;
|
||||
return heroStrength > dangerStrength * safeAttackRatio;
|
||||
}
|
||||
|
||||
return true; //there's no danger
|
||||
}
|
||||
|
||||
bool isSafeToVisit(const CGHeroInstance * h, uint64_t dangerStrength)
|
||||
bool isSafeToVisit(const CGHeroInstance * h, uint64_t dangerStrength, float safeAttackRatio)
|
||||
{
|
||||
return isSafeToVisit(h, h, dangerStrength);
|
||||
return isSafeToVisit(h, h, dangerStrength, safeAttackRatio);
|
||||
}
|
||||
|
||||
bool isObjectRemovable(const CGObjectInstance * obj)
|
||||
@ -194,7 +193,7 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
|
||||
{
|
||||
// TODO: Such information should be provided by pathfinder
|
||||
// Tile must be free or with unoccupied boat
|
||||
if(!t->blocked)
|
||||
if(!t->blocked())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -268,8 +267,8 @@ bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2)
|
||||
|
||||
bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2)
|
||||
{
|
||||
auto art1 = a1->artType;
|
||||
auto art2 = a2->artType;
|
||||
auto art1 = a1->getType();
|
||||
auto art2 = a2->getType();
|
||||
|
||||
if(art1->getPrice() == art2->getPrice())
|
||||
return art1->valOfBonuses(BonusType::PRIMARY_SKILL) > art2->valOfBonuses(BonusType::PRIMARY_SKILL);
|
||||
@ -313,7 +312,7 @@ int getDuplicatingSlots(const CArmedInstance * army)
|
||||
|
||||
for(auto stack : army->Slots())
|
||||
{
|
||||
if(stack.second->type && army->getSlotFor(stack.second->type) != stack.first)
|
||||
if(stack.second->getCreature() && army->getSlotFor(stack.second->getCreature()) != stack.first)
|
||||
duplicatingSlots++;
|
||||
}
|
||||
|
||||
@ -388,7 +387,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
|
||||
{
|
||||
for(auto slot : h->Slots())
|
||||
{
|
||||
if(slot.second->type->hasUpgrades())
|
||||
if(slot.second->getType()->hasUpgrades())
|
||||
return true; //TODO: check price?
|
||||
}
|
||||
return false;
|
||||
@ -430,9 +429,16 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
|
||||
return false;
|
||||
}
|
||||
|
||||
if(obj->wasVisited(h)) //it must pointer to hero instance, heroPtr calls function wasVisited(ui8 player);
|
||||
if(obj->wasVisited(h))
|
||||
return false;
|
||||
|
||||
auto rewardable = dynamic_cast<const Rewardable::Interface *>(obj);
|
||||
|
||||
if(rewardable && rewardable->getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT).empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -441,9 +447,9 @@ bool townHasFreeTavern(const CGTownInstance * town)
|
||||
if(!town->hasBuilt(BuildingID::TAVERN)) return false;
|
||||
if(!town->visitingHero) return true;
|
||||
|
||||
bool canMoveVisitingHeroToGarnison = !town->getUpperArmy()->stacksCount();
|
||||
bool canMoveVisitingHeroToGarrison = !town->getUpperArmy()->stacksCount();
|
||||
|
||||
return canMoveVisitingHeroToGarnison;
|
||||
return canMoveVisitingHeroToGarrison;
|
||||
}
|
||||
|
||||
uint64_t getHeroArmyStrengthWithCommander(const CGHeroInstance * hero, const CCreatureSet * heroArmy)
|
||||
|
@ -40,9 +40,7 @@
|
||||
/*********************** TBB.h ********************/
|
||||
|
||||
#include "../../lib/VCMI_Lib.h"
|
||||
#include "../../lib/CBuildingHandler.h"
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
#include "../../lib/CTownHandler.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
#include "../../lib/CStopWatch.h"
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
@ -63,11 +61,6 @@ 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;
|
||||
|
||||
//implementation-dependent
|
||||
extern const float SAFE_ATTACK_CONSTANT;
|
||||
extern const int GOLD_RESERVE;
|
||||
|
||||
extern thread_local CCallback * cb;
|
||||
|
||||
@ -110,13 +103,6 @@ public:
|
||||
const CGHeroInstance * get(bool doWeExpectNull = false) const;
|
||||
const CGHeroInstance * get(const CPlayerSpecificInfoCallback * cb, bool doWeExpectNull = false) const;
|
||||
bool validAndSet() const;
|
||||
|
||||
|
||||
template<typename Handler> void serialize(Handler & handler)
|
||||
{
|
||||
handler & h;
|
||||
handler & hid;
|
||||
}
|
||||
};
|
||||
|
||||
enum BattleState
|
||||
@ -141,12 +127,6 @@ struct ObjectIdRef
|
||||
ObjectIdRef(const CGObjectInstance * obj);
|
||||
|
||||
bool operator<(const ObjectIdRef & rhs) const;
|
||||
|
||||
|
||||
template<typename Handler> void serialize(Handler & h)
|
||||
{
|
||||
h & id;
|
||||
}
|
||||
};
|
||||
|
||||
template<Obj::Type id>
|
||||
@ -228,8 +208,8 @@ bool isBlockVisitObj(const int3 & pos);
|
||||
bool isWeeklyRevisitable(const Nullkiller * ai, const CGObjectInstance * obj);
|
||||
|
||||
bool isObjectRemovable(const CGObjectInstance * obj); //FIXME FIXME: move logic to object property!
|
||||
bool isSafeToVisit(const CGHeroInstance * h, uint64_t dangerStrength);
|
||||
bool isSafeToVisit(const CGHeroInstance * h, const CCreatureSet *, uint64_t dangerStrength);
|
||||
bool isSafeToVisit(const CGHeroInstance * h, uint64_t dangerStrength, float safeAttackRatio);
|
||||
bool isSafeToVisit(const CGHeroInstance * h, const CCreatureSet *, uint64_t dangerStrength, float safeAttackRatio);
|
||||
|
||||
bool compareHeroStrength(const CGHeroInstance * h1, const CGHeroInstance * h2);
|
||||
bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2);
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../../../CCallback.h"
|
||||
#include "../../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../../lib/IGameSettings.h"
|
||||
#include "../../../lib/GameConstants.h"
|
||||
|
||||
namespace NKAI
|
||||
@ -90,7 +91,7 @@ std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, c
|
||||
{
|
||||
for(auto & i : armyPtr->Slots())
|
||||
{
|
||||
auto cre = dynamic_cast<const CCreature*>(i.second->type);
|
||||
auto cre = dynamic_cast<const CCreature*>(i.second->getType());
|
||||
auto & slotInfp = creToPower[cre];
|
||||
|
||||
slotInfp.creature = cre;
|
||||
@ -144,7 +145,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
|
||||
|
||||
for(auto & slot : sortedSlots)
|
||||
{
|
||||
alignmentMap[slot.creature->getFaction()] += slot.power;
|
||||
alignmentMap[slot.creature->getFactionID()] += slot.power;
|
||||
}
|
||||
|
||||
std::set<FactionID> allowedFactions;
|
||||
@ -152,16 +153,6 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
|
||||
uint64_t armyValue = 0;
|
||||
|
||||
TemporaryArmy newArmyInstance;
|
||||
auto bonusModifiers = armyCarrier->getBonuses(Selector::type()(BonusType::MORALE));
|
||||
|
||||
for(auto bonus : *bonusModifiers)
|
||||
{
|
||||
// army bonuses will change and object bonuses are temporary
|
||||
if(bonus->source != BonusSource::ARMY && bonus->source != BonusSource::OBJECT_INSTANCE && bonus->source != BonusSource::OBJECT_TYPE)
|
||||
{
|
||||
newArmyInstance.addNewBonus(std::make_shared<Bonus>(*bonus));
|
||||
}
|
||||
}
|
||||
|
||||
while(allowedFactions.size() < alignmentMap.size())
|
||||
{
|
||||
@ -178,7 +169,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
|
||||
|
||||
for(auto & slot : sortedSlots)
|
||||
{
|
||||
if(vstd::contains(allowedFactions, slot.creature->getFaction()))
|
||||
if(vstd::contains(allowedFactions, slot.creature->getFactionID()))
|
||||
{
|
||||
auto slotID = newArmyInstance.getSlotFor(slot.creature->getId());
|
||||
|
||||
@ -197,16 +188,18 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
|
||||
auto morale = slot.second->moraleVal();
|
||||
auto multiplier = 1.0f;
|
||||
|
||||
const float BadMoraleChance = 0.083f;
|
||||
const float HighMoraleChance = 0.04f;
|
||||
const auto & badMoraleDice = cb->getSettings().getVector(EGameSettings::COMBAT_BAD_MORALE_DICE);
|
||||
const auto & highMoraleDice = cb->getSettings().getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE);
|
||||
|
||||
if(morale < 0)
|
||||
if(morale < 0 && !badMoraleDice.empty())
|
||||
{
|
||||
multiplier += morale * BadMoraleChance;
|
||||
size_t diceIndex = std::min<size_t>(badMoraleDice.size(), -morale) - 1;
|
||||
multiplier -= 1.0 / badMoraleDice.at(diceIndex);
|
||||
}
|
||||
else if(morale > 0)
|
||||
else if(morale > 0 && !highMoraleDice.empty())
|
||||
{
|
||||
multiplier += morale * HighMoraleChance;
|
||||
size_t diceIndex = std::min<size_t>(highMoraleDice.size(), morale) - 1;
|
||||
multiplier += 1.0 / highMoraleDice.at(diceIndex);
|
||||
}
|
||||
|
||||
newValue += multiplier * slot.second->getPower();
|
||||
@ -316,6 +309,8 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
|
||||
? dynamic_cast<const CGTownInstance *>(dwelling)
|
||||
: nullptr;
|
||||
|
||||
std::set<SlotID> alreadyDisbanded;
|
||||
|
||||
for(int i = dwelling->creatures.size() - 1; i >= 0; i--)
|
||||
{
|
||||
auto ci = infoFromDC(dwelling->creatures[i]);
|
||||
@ -329,18 +324,71 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
|
||||
|
||||
if(!ci.count) continue;
|
||||
|
||||
// Calculate the market value of the new stack
|
||||
TResources newStackValue = ci.creID.toCreature()->getFullRecruitCost() * ci.count;
|
||||
|
||||
SlotID dst = hero->getSlotFor(ci.creID);
|
||||
|
||||
// Keep track of the least valuable slot in the hero's army
|
||||
SlotID leastValuableSlot;
|
||||
TResources leastValuableStackValue;
|
||||
leastValuableStackValue[6] = std::numeric_limits<int>::max();
|
||||
bool shouldDisband = false;
|
||||
if(!hero->hasStackAtSlot(dst)) //need another new slot for this stack
|
||||
{
|
||||
if(!freeHeroSlots) //no more place for stacks
|
||||
continue;
|
||||
if(!freeHeroSlots) // No free slots; consider replacing
|
||||
{
|
||||
// Check for the least valuable existing stack
|
||||
for (auto& slot : hero->Slots())
|
||||
{
|
||||
if (alreadyDisbanded.find(slot.first) != alreadyDisbanded.end())
|
||||
continue;
|
||||
|
||||
if(slot.second->getCreatureID() != CreatureID::NONE)
|
||||
{
|
||||
TResources currentStackValue = slot.second->getCreatureID().toCreature()->getFullRecruitCost() * slot.second->getCount();
|
||||
|
||||
if (town && slot.second->getCreatureID().toCreature()->getFactionID() == town->getFactionID())
|
||||
continue;
|
||||
|
||||
if(currentStackValue.marketValue() < leastValuableStackValue.marketValue())
|
||||
{
|
||||
leastValuableStackValue = currentStackValue;
|
||||
leastValuableSlot = slot.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decide whether to replace the least valuable stack
|
||||
if(newStackValue.marketValue() <= leastValuableStackValue.marketValue())
|
||||
{
|
||||
continue; // Skip if the new stack isn't worth replacing
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldDisband = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
freeHeroSlots--; //new slot will be occupied
|
||||
}
|
||||
}
|
||||
|
||||
vstd::amin(ci.count, availableRes / ci.creID.toCreature()->getFullRecruitCost()); //max count we can afford
|
||||
|
||||
if(!ci.count) continue;
|
||||
int disbandMalus = 0;
|
||||
|
||||
if (shouldDisband)
|
||||
{
|
||||
disbandMalus = leastValuableStackValue / ci.creID.toCreature()->getFullRecruitCost();
|
||||
alreadyDisbanded.insert(leastValuableSlot);
|
||||
}
|
||||
|
||||
ci.count -= disbandMalus;
|
||||
|
||||
if(ci.count <= 0)
|
||||
continue;
|
||||
|
||||
ci.level = i; //this is important for Dungeon Summoning Portal
|
||||
creaturesInDwellings.push_back(ci);
|
||||
|
@ -14,8 +14,6 @@
|
||||
|
||||
#include "../../../lib/GameConstants.h"
|
||||
#include "../../../lib/VCMI_Lib.h"
|
||||
#include "../../../lib/CTownHandler.h"
|
||||
#include "../../../lib/CBuildingHandler.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
@ -10,13 +10,14 @@
|
||||
#include "../StdInc.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../../../lib/entities/building/CBuilding.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
|
||||
{
|
||||
auto townInfo = developmentInfo.town->town;
|
||||
auto townInfo = developmentInfo.town->getTown();
|
||||
auto creatures = townInfo->creatures;
|
||||
auto buildings = townInfo->getAllBuildings();
|
||||
|
||||
@ -30,17 +31,14 @@ void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
|
||||
}
|
||||
}
|
||||
|
||||
BuildingID prefixes[] = {BuildingID::DWELL_UP_FIRST, BuildingID::DWELL_FIRST};
|
||||
|
||||
for(int level = 0; level < GameConstants::CREATURES_PER_TOWN; level++)
|
||||
for(int level = 0; level < developmentInfo.town->getTown()->creatures.size(); level++)
|
||||
{
|
||||
logAi->trace("Checking dwelling level %d", level);
|
||||
BuildingInfo nextToBuild = BuildingInfo();
|
||||
|
||||
for(BuildingID prefix : prefixes)
|
||||
for(int upgradeIndex : {1, 0})
|
||||
{
|
||||
BuildingID building = BuildingID(prefix + level);
|
||||
|
||||
BuildingID building = BuildingID(BuildingID::getDwellingFromLevel(level, upgradeIndex));
|
||||
if(!vstd::contains(buildings, building))
|
||||
continue; // no such building in town
|
||||
|
||||
@ -74,16 +72,23 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo)
|
||||
|
||||
if(developmentInfo.existingDwellings.size() >= 2 && ai->cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday)
|
||||
{
|
||||
otherBuildings.push_back({BuildingID::CITADEL, BuildingID::CASTLE});
|
||||
otherBuildings.push_back({BuildingID::HORDE_1});
|
||||
otherBuildings.push_back({BuildingID::HORDE_2});
|
||||
}
|
||||
|
||||
otherBuildings.push_back({ BuildingID::CITADEL, BuildingID::CASTLE });
|
||||
otherBuildings.push_back({ BuildingID::RESOURCE_SILO });
|
||||
otherBuildings.push_back({ BuildingID::SPECIAL_1 });
|
||||
otherBuildings.push_back({ BuildingID::SPECIAL_2 });
|
||||
otherBuildings.push_back({ BuildingID::SPECIAL_3 });
|
||||
otherBuildings.push_back({ BuildingID::SPECIAL_4 });
|
||||
otherBuildings.push_back({ BuildingID::MARKETPLACE });
|
||||
|
||||
for(auto & buildingSet : otherBuildings)
|
||||
{
|
||||
for(auto & buildingID : buildingSet)
|
||||
{
|
||||
if(!developmentInfo.town->hasBuilt(buildingID) && developmentInfo.town->town->buildings.count(buildingID))
|
||||
if(!developmentInfo.town->hasBuilt(buildingID) && developmentInfo.town->getTown()->buildings.count(buildingID))
|
||||
{
|
||||
developmentInfo.addBuildingToBuild(getBuildingOrPrerequisite(developmentInfo.town, buildingID));
|
||||
|
||||
@ -100,10 +105,17 @@ int32_t convertToGold(const TResources & res)
|
||||
+ 125 * (res[EGameResID::GEMS] + res[EGameResID::CRYSTAL] + res[EGameResID::MERCURY] + res[EGameResID::SULFUR]);
|
||||
}
|
||||
|
||||
TResources withoutGold(TResources other)
|
||||
{
|
||||
other[GameResID::GOLD] = 0;
|
||||
|
||||
return other;
|
||||
}
|
||||
|
||||
TResources BuildAnalyzer::getResourcesRequiredNow() const
|
||||
{
|
||||
auto resourcesAvailable = ai->getFreeResources();
|
||||
auto result = requiredResources - resourcesAvailable;
|
||||
auto result = withoutGold(armyCost) + requiredResources - resourcesAvailable;
|
||||
|
||||
result.positive();
|
||||
|
||||
@ -113,7 +125,7 @@ TResources BuildAnalyzer::getResourcesRequiredNow() const
|
||||
TResources BuildAnalyzer::getTotalResourcesRequired() const
|
||||
{
|
||||
auto resourcesAvailable = ai->getFreeResources();
|
||||
auto result = totalDevelopmentCost - resourcesAvailable;
|
||||
auto result = totalDevelopmentCost + withoutGold(armyCost) - resourcesAvailable;
|
||||
|
||||
result.positive();
|
||||
|
||||
@ -135,6 +147,8 @@ void BuildAnalyzer::update()
|
||||
|
||||
auto towns = ai->cb->getTownsInfo();
|
||||
|
||||
float economyDevelopmentCost = 0;
|
||||
|
||||
for(const CGTownInstance* town : towns)
|
||||
{
|
||||
logAi->trace("Checking town %s", town->getNameTranslated());
|
||||
@ -147,6 +161,11 @@ void BuildAnalyzer::update()
|
||||
|
||||
requiredResources += developmentInfo.requiredResources;
|
||||
totalDevelopmentCost += developmentInfo.townDevelopmentCost;
|
||||
for(auto building : developmentInfo.toBuild)
|
||||
{
|
||||
if (building.dailyIncome[EGameResID::GOLD] > 0)
|
||||
economyDevelopmentCost += building.buildCostWithPrerequisites[EGameResID::GOLD];
|
||||
}
|
||||
armyCost += developmentInfo.armyCost;
|
||||
|
||||
for(auto bi : developmentInfo.toBuild)
|
||||
@ -165,15 +184,7 @@ void BuildAnalyzer::update()
|
||||
|
||||
updateDailyIncome();
|
||||
|
||||
if(ai->cb->getDate(Date::DAY) == 1)
|
||||
{
|
||||
goldPressure = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
goldPressure = ai->getLockedResources()[EGameResID::GOLD] / 5000.0f
|
||||
+ (float)armyCost[EGameResID::GOLD] / (1 + 2 * ai->getFreeGold() + (float)dailyIncome[EGameResID::GOLD] * 7.0f);
|
||||
}
|
||||
goldPressure = (ai->getLockedResources()[EGameResID::GOLD] + (float)armyCost[EGameResID::GOLD] + economyDevelopmentCost) / (1 + 2 * ai->getFreeGold() + (float)dailyIncome[EGameResID::GOLD] * 7.0f);
|
||||
|
||||
logAi->trace("Gold pressure: %f", goldPressure);
|
||||
}
|
||||
@ -192,7 +203,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
bool excludeDwellingDependencies) const
|
||||
{
|
||||
BuildingID building = toBuild;
|
||||
auto townInfo = town->town;
|
||||
auto townInfo = town->getTown();
|
||||
|
||||
const CBuilding * buildPtr = townInfo->buildings.at(building);
|
||||
const CCreature * creature = nullptr;
|
||||
@ -203,8 +214,8 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
|
||||
if(BuildingID::DWELL_FIRST <= toBuild && toBuild <= BuildingID::DWELL_UP_LAST)
|
||||
{
|
||||
creatureLevel = (toBuild - BuildingID::DWELL_FIRST) % GameConstants::CREATURES_PER_TOWN;
|
||||
creatureUpgrade = (toBuild - BuildingID::DWELL_FIRST) / GameConstants::CREATURES_PER_TOWN;
|
||||
creatureLevel = BuildingID::getLevelFromDwelling(toBuild);
|
||||
creatureUpgrade = BuildingID::getUpgradedFromDwelling(toBuild);
|
||||
}
|
||||
else if(toBuild == BuildingID::HORDE_1 || toBuild == BuildingID::HORDE_1_UPGR)
|
||||
{
|
||||
@ -231,6 +242,12 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
logAi->trace("checking %s", info.name);
|
||||
logAi->trace("buildInfo %s", info.toString());
|
||||
|
||||
int highestFort = 0;
|
||||
for (auto twn : ai->cb->getTownsInfo())
|
||||
{
|
||||
highestFort = std::max(highestFort, (int)twn->fortLevel());
|
||||
}
|
||||
|
||||
if(!town->hasBuilt(building))
|
||||
{
|
||||
auto canBuild = ai->cb->canBuildStructure(town, building);
|
||||
@ -267,7 +284,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
|
||||
BuildingInfo prerequisite = getBuildingOrPrerequisite(town, missingBuildings[0], excludeDwellingDependencies);
|
||||
|
||||
prerequisite.buildCostWithPrerequisits += info.buildCost;
|
||||
prerequisite.buildCostWithPrerequisites += info.buildCost;
|
||||
prerequisite.creatureCost = info.creatureCost;
|
||||
prerequisite.creatureGrows = info.creatureGrows;
|
||||
prerequisite.creatureLevel = info.creatureLevel;
|
||||
@ -275,7 +292,15 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
prerequisite.baseCreatureID = info.baseCreatureID;
|
||||
prerequisite.prerequisitesCount++;
|
||||
prerequisite.armyCost = info.armyCost;
|
||||
prerequisite.dailyIncome = info.dailyIncome;
|
||||
bool haveSameOrBetterFort = false;
|
||||
if (prerequisite.id == BuildingID::FORT && highestFort >= CGTownInstance::EFortLevel::FORT)
|
||||
haveSameOrBetterFort = true;
|
||||
if (prerequisite.id == BuildingID::CITADEL && highestFort >= CGTownInstance::EFortLevel::CITADEL)
|
||||
haveSameOrBetterFort = true;
|
||||
if (prerequisite.id == BuildingID::CASTLE && highestFort >= CGTownInstance::EFortLevel::CASTLE)
|
||||
haveSameOrBetterFort = true;
|
||||
if(!haveSameOrBetterFort)
|
||||
prerequisite.dailyIncome = info.dailyIncome;
|
||||
|
||||
return prerequisite;
|
||||
}
|
||||
@ -308,9 +333,7 @@ void BuildAnalyzer::updateDailyIncome()
|
||||
const CGMine* mine = dynamic_cast<const CGMine*>(obj);
|
||||
|
||||
if(mine)
|
||||
{
|
||||
dailyIncome[mine->producedResource.getNum()] += mine->producedQuantity;
|
||||
}
|
||||
dailyIncome += mine->dailyIncome();
|
||||
}
|
||||
|
||||
for(const CGTownInstance* town : towns)
|
||||
@ -323,7 +346,7 @@ bool BuildAnalyzer::hasAnyBuilding(int32_t alignment, BuildingID bid) const
|
||||
{
|
||||
for(auto tdi : developmentInfos)
|
||||
{
|
||||
if(tdi.town->getFaction() == alignment && tdi.town->hasBuilt(bid))
|
||||
if(tdi.town->getFactionID() == alignment && tdi.town->hasBuilt(bid))
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -340,7 +363,8 @@ void TownDevelopmentInfo::addExistingDwelling(const BuildingInfo & existingDwell
|
||||
|
||||
void TownDevelopmentInfo::addBuildingToBuild(const BuildingInfo & nextToBuild)
|
||||
{
|
||||
townDevelopmentCost += nextToBuild.buildCostWithPrerequisits;
|
||||
townDevelopmentCost += nextToBuild.buildCostWithPrerequisites;
|
||||
townDevelopmentCost += withoutGold(nextToBuild.armyCost);
|
||||
|
||||
if(nextToBuild.canBuild)
|
||||
{
|
||||
@ -361,7 +385,7 @@ BuildingInfo::BuildingInfo()
|
||||
creatureGrows = 0;
|
||||
creatureID = CreatureID::NONE;
|
||||
buildCost = 0;
|
||||
buildCostWithPrerequisits = 0;
|
||||
buildCostWithPrerequisites = 0;
|
||||
prerequisitesCount = 0;
|
||||
name.clear();
|
||||
armyStrength = 0;
|
||||
@ -376,7 +400,7 @@ BuildingInfo::BuildingInfo(
|
||||
{
|
||||
id = building->bid;
|
||||
buildCost = building->resources;
|
||||
buildCostWithPrerequisits = building->resources;
|
||||
buildCostWithPrerequisites = building->resources;
|
||||
dailyIncome = building->produce;
|
||||
exists = town->hasBuilt(id);
|
||||
prerequisitesCount = 1;
|
||||
|
@ -22,7 +22,7 @@ class DLL_EXPORT BuildingInfo
|
||||
public:
|
||||
BuildingID id;
|
||||
TResources buildCost;
|
||||
TResources buildCostWithPrerequisits;
|
||||
TResources buildCostWithPrerequisites;
|
||||
int creatureGrows;
|
||||
uint8_t creatureLevel;
|
||||
TResources creatureCost;
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../pforeach.h"
|
||||
#include "../../../lib/CRandomGenerator.h"
|
||||
#include "../../../lib/logging/VisualLogger.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
@ -24,6 +25,41 @@ double HitMapInfo::value() const
|
||||
return danger / std::sqrt(turn / 3.0f + 1);
|
||||
}
|
||||
|
||||
void logHitmap(PlayerColor playerID, DangerHitMapAnalyzer & data)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logVisual->updateWithLock(playerID.toString() + ".danger.max", [&data](IVisualLogBuilder & b)
|
||||
{
|
||||
foreach_tile_pos([&b, &data](const int3 & pos)
|
||||
{
|
||||
auto & treat = data.getTileThreat(pos).maximumDanger;
|
||||
b.addText(pos, std::to_string(treat.danger));
|
||||
|
||||
if(treat.hero.validAndSet())
|
||||
{
|
||||
b.addText(pos, std::to_string(treat.turn));
|
||||
b.addText(pos, treat.hero->getNameTranslated());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
logVisual->updateWithLock(playerID.toString() + ".danger.fast", [&data](IVisualLogBuilder & b)
|
||||
{
|
||||
foreach_tile_pos([&b, &data](const int3 & pos)
|
||||
{
|
||||
auto & treat = data.getTileThreat(pos).fastestDanger;
|
||||
b.addText(pos, std::to_string(treat.danger));
|
||||
|
||||
if(treat.hero.validAndSet())
|
||||
{
|
||||
b.addText(pos, std::to_string(treat.turn));
|
||||
b.addText(pos, treat.hero->getNameTranslated());
|
||||
}
|
||||
});
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void DangerHitMapAnalyzer::updateHitMap()
|
||||
{
|
||||
if(hitMapUpToDate)
|
||||
@ -53,6 +89,13 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
|
||||
heroes[hero->tempOwner][hero] = HeroRole::MAIN;
|
||||
}
|
||||
if(obj->ID == Obj::TOWN)
|
||||
{
|
||||
auto town = dynamic_cast<const CGTownInstance *>(obj);
|
||||
|
||||
if(town->garrisonHero)
|
||||
heroes[town->garrisonHero->tempOwner][town->garrisonHero] = HeroRole::MAIN;
|
||||
}
|
||||
}
|
||||
|
||||
auto ourTowns = cb->getTownsInfo();
|
||||
@ -96,6 +139,7 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
|
||||
newThreat.hero = path.targetHero;
|
||||
newThreat.turn = path.turn();
|
||||
newThreat.threat = path.getHeroStrength() * (1 - path.movementCost() / 2.0);
|
||||
newThreat.danger = path.getHeroStrength();
|
||||
|
||||
if(newThreat.value() > node.maximumDanger.value())
|
||||
@ -144,6 +188,8 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
}
|
||||
|
||||
logAi->trace("Danger hit map updated in %ld", timeElapsed(start));
|
||||
|
||||
logHitmap(ai->playerID, *this);
|
||||
}
|
||||
|
||||
void DangerHitMapAnalyzer::calculateTileOwners()
|
||||
@ -270,8 +316,8 @@ uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath &
|
||||
|
||||
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));
|
||||
return (info.fastestDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.fastestDanger.danger, ai->settings->getSafeAttackRatio()))
|
||||
|| (info.maximumDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.maximumDanger.danger, ai->settings->getSafeAttackRatio()));
|
||||
}
|
||||
|
||||
const HitMapNode & DangerHitMapAnalyzer::getObjectThreat(const CGObjectInstance * obj) const
|
||||
@ -302,6 +348,7 @@ std::set<const CGObjectInstance *> DangerHitMapAnalyzer::getOneTurnAccessibleObj
|
||||
void DangerHitMapAnalyzer::reset()
|
||||
{
|
||||
hitMapUpToDate = false;
|
||||
tileOwnersUpToDate = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ struct HitMapInfo
|
||||
|
||||
uint64_t danger;
|
||||
uint8_t turn;
|
||||
float threat;
|
||||
HeroPtr hero;
|
||||
|
||||
HitMapInfo()
|
||||
@ -33,6 +34,7 @@ struct HitMapInfo
|
||||
{
|
||||
danger = 0;
|
||||
turn = 255;
|
||||
threat = 0;
|
||||
hero = HeroPtr();
|
||||
}
|
||||
|
||||
|
@ -11,8 +11,7 @@
|
||||
#include "../StdInc.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../../lib/CHeroHandler.h"
|
||||
#include "../../../lib/GameSettings.h"
|
||||
#include "../../../lib/IGameSettings.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
@ -71,7 +70,7 @@ float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance *
|
||||
|
||||
float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
|
||||
{
|
||||
auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, BonusSourceID(hero->type->getId()));
|
||||
auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, BonusSourceID(hero->getHeroTypeID()));
|
||||
auto secondarySkillBonus = Selector::targetSourceType()(BonusSource::SECONDARY_SKILL);
|
||||
auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus));
|
||||
auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(BonusSource::SECONDARY_SKILL));
|
||||
@ -96,7 +95,7 @@ float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
|
||||
|
||||
float HeroManager::evaluateFightingStrength(const CGHeroInstance * hero) const
|
||||
{
|
||||
return evaluateSpeciality(hero) + wariorSkillsScores.evaluateSecSkills(hero) + hero->level * 1.5f;
|
||||
return evaluateSpeciality(hero) + wariorSkillsScores.evaluateSecSkills(hero) + hero->getBasePrimarySkillValue(PrimarySkill::ATTACK) + hero->getBasePrimarySkillValue(PrimarySkill::DEFENSE) + hero->getBasePrimarySkillValue(PrimarySkill::SPELL_POWER) + hero->getBasePrimarySkillValue(PrimarySkill::KNOWLEDGE);
|
||||
}
|
||||
|
||||
void HeroManager::update()
|
||||
@ -109,7 +108,7 @@ void HeroManager::update()
|
||||
for(auto & hero : myHeroes)
|
||||
{
|
||||
scores[hero] = evaluateFightingStrength(hero);
|
||||
knownFightingStrength[hero->id] = hero->getFightingStrength();
|
||||
knownFightingStrength[hero->id] = hero->getHeroStrength();
|
||||
}
|
||||
|
||||
auto scoreSort = [&](const CGHeroInstance * h1, const CGHeroInstance * h2) -> bool
|
||||
@ -148,7 +147,10 @@ void HeroManager::update()
|
||||
|
||||
HeroRole HeroManager::getHeroRole(const HeroPtr & hero) const
|
||||
{
|
||||
return heroRoles.at(hero);
|
||||
if (heroRoles.find(hero) != heroRoles.end())
|
||||
return heroRoles.at(hero);
|
||||
else
|
||||
return HeroRole::SCOUT;
|
||||
}
|
||||
|
||||
const std::map<HeroPtr, HeroRole> & HeroManager::getHeroRoles() const
|
||||
@ -189,15 +191,13 @@ float HeroManager::evaluateHero(const CGHeroInstance * hero) const
|
||||
return evaluateFightingStrength(hero);
|
||||
}
|
||||
|
||||
bool HeroManager::heroCapReached() const
|
||||
bool HeroManager::heroCapReached(bool includeGarrisoned) const
|
||||
{
|
||||
const bool includeGarnisoned = true;
|
||||
int heroCount = cb->getHeroCount(ai->playerID, includeGarnisoned);
|
||||
int heroCount = cb->getHeroCount(ai->playerID, includeGarrisoned);
|
||||
|
||||
return heroCount >= ALLOWED_ROAMING_HEROES
|
||||
|| 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);
|
||||
return heroCount >= ai->settings->getMaxRoamingHeroes()
|
||||
|| heroCount >= cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)
|
||||
|| heroCount >= cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP);
|
||||
}
|
||||
|
||||
float HeroManager::getFightingStrengthCached(const CGHeroInstance * hero) const
|
||||
@ -205,7 +205,7 @@ float HeroManager::getFightingStrengthCached(const CGHeroInstance * hero) const
|
||||
auto cached = knownFightingStrength.find(hero->id);
|
||||
|
||||
//FIXME: fallback to hero->getFightingStrength() is VERY slow on higher difficulties (no object graph? map reveal?)
|
||||
return cached != knownFightingStrength.end() ? cached->second : hero->getFightingStrength();
|
||||
return cached != knownFightingStrength.end() ? cached->second : hero->getHeroStrength();
|
||||
}
|
||||
|
||||
float HeroManager::getMagicStrength(const CGHeroInstance * hero) const
|
||||
@ -282,7 +282,7 @@ const CGHeroInstance * HeroManager::findHeroWithGrail() const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const CGHeroInstance * HeroManager::findWeakHeroToDismiss(uint64_t armyLimit) const
|
||||
const CGHeroInstance * HeroManager::findWeakHeroToDismiss(uint64_t armyLimit, const CGTownInstance* townToSpare) const
|
||||
{
|
||||
const CGHeroInstance * weakestHero = nullptr;
|
||||
auto myHeroes = ai->cb->getHeroesInfo();
|
||||
@ -293,12 +293,13 @@ const CGHeroInstance * HeroManager::findWeakHeroToDismiss(uint64_t armyLimit) co
|
||||
|| existingHero->getArmyStrength() >armyLimit
|
||||
|| getHeroRole(existingHero) == HeroRole::MAIN
|
||||
|| existingHero->movementPointsRemaining()
|
||||
|| (townToSpare != nullptr && existingHero->visitedTown == townToSpare)
|
||||
|| existingHero->artifactsWorn.size() > (existingHero->hasSpellbook() ? 2 : 1))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!weakestHero || weakestHero->getFightingStrength() > existingHero->getFightingStrength())
|
||||
if(!weakestHero || weakestHero->getHeroStrength() > existingHero->getHeroStrength())
|
||||
{
|
||||
weakestHero = existingHero;
|
||||
}
|
||||
|
@ -14,8 +14,6 @@
|
||||
|
||||
#include "../../../lib/GameConstants.h"
|
||||
#include "../../../lib/VCMI_Lib.h"
|
||||
#include "../../../lib/CTownHandler.h"
|
||||
#include "../../../lib/CBuildingHandler.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
@ -58,9 +56,9 @@ public:
|
||||
float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const;
|
||||
float evaluateHero(const CGHeroInstance * hero) const;
|
||||
bool canRecruitHero(const CGTownInstance * t = nullptr) const;
|
||||
bool heroCapReached() const;
|
||||
bool heroCapReached(bool includeGarrisoned = true) const;
|
||||
const CGHeroInstance * findHeroWithGrail() const;
|
||||
const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit) const;
|
||||
const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit, const CGTownInstance * townToSpare = nullptr) const;
|
||||
float getMagicStrength(const CGHeroInstance * hero) const;
|
||||
float getFightingStrengthCached(const CGHeroInstance * hero) const;
|
||||
|
||||
|
@ -97,9 +97,10 @@ std::optional<const CGObjectInstance *> ObjectClusterizer::getBlocker(const AIPa
|
||||
{
|
||||
auto guardPos = ai->cb->getGuardingCreaturePosition(node.coord);
|
||||
|
||||
blockers = ai->cb->getVisitableObjs(node.coord);
|
||||
if (ai->cb->isVisible(node.coord))
|
||||
blockers = ai->cb->getVisitableObjs(node.coord);
|
||||
|
||||
if(guardPos.valid())
|
||||
if(guardPos.valid() && ai->cb->isVisible(guardPos))
|
||||
{
|
||||
auto guard = ai->cb->getTopObj(ai->cb->getGuardingCreaturePosition(node.coord));
|
||||
|
||||
@ -146,6 +147,13 @@ std::optional<const CGObjectInstance *> ObjectClusterizer::getBlocker(const AIPa
|
||||
return blocker;
|
||||
}
|
||||
|
||||
auto danger = ai->dangerEvaluator->evaluateDanger(blocker);
|
||||
|
||||
if(danger > 0 && blocker->isBlockedVisitable() && isObjectRemovable(blocker))
|
||||
{
|
||||
return blocker;
|
||||
}
|
||||
|
||||
return std::optional< const CGObjectInstance *>();
|
||||
}
|
||||
|
||||
@ -467,9 +475,11 @@ void ObjectClusterizer::clusterizeObject(
|
||||
|
||||
heroesProcessed.insert(path.targetHero);
|
||||
|
||||
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
|
||||
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), PriorityEvaluator::PriorityTier::HUNTER_GATHER);
|
||||
|
||||
if(priority < MIN_PRIORITY)
|
||||
if(ai->settings->isUseFuzzy() && priority < MIN_PRIORITY)
|
||||
continue;
|
||||
else if (priority <= 0)
|
||||
continue;
|
||||
|
||||
ClusterMap::accessor cluster;
|
||||
@ -488,12 +498,14 @@ void ObjectClusterizer::clusterizeObject(
|
||||
|
||||
heroesProcessed.insert(path.targetHero);
|
||||
|
||||
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
|
||||
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), PriorityEvaluator::PriorityTier::HUNTER_GATHER);
|
||||
|
||||
if(priority < MIN_PRIORITY)
|
||||
if (ai->settings->isUseFuzzy() && priority < MIN_PRIORITY)
|
||||
continue;
|
||||
else if (priority <= 0)
|
||||
continue;
|
||||
|
||||
bool interestingObject = path.turn() <= 2 || priority > 0.5f;
|
||||
bool interestingObject = path.turn() <= 2 || priority > (ai->settings->isUseFuzzy() ? 0.5f : 0);
|
||||
|
||||
if(interestingObject)
|
||||
{
|
||||
|
@ -49,26 +49,49 @@ Goals::TGoalVec BuildingBehavior::decompose(const Nullkiller * ai) const
|
||||
auto & developmentInfos = ai->buildAnalyzer->getDevelopmentInfo();
|
||||
auto isGoldPressureLow = !ai->buildAnalyzer->isGoldPressureHigh();
|
||||
|
||||
ai->dangerHitMap->updateHitMap();
|
||||
|
||||
for(auto & developmentInfo : developmentInfos)
|
||||
{
|
||||
for(auto & buildingInfo : developmentInfo.toBuild)
|
||||
bool emergencyDefense = false;
|
||||
uint8_t closestThreat = std::numeric_limits<uint8_t>::max();
|
||||
for (auto threat : ai->dangerHitMap->getTownThreats(developmentInfo.town))
|
||||
{
|
||||
if(isGoldPressureLow || buildingInfo.dailyIncome[EGameResID::GOLD] > 0)
|
||||
closestThreat = std::min(closestThreat, threat.turn);
|
||||
}
|
||||
for (auto& buildingInfo : developmentInfo.toBuild)
|
||||
{
|
||||
if (closestThreat <= 1 && developmentInfo.town->fortLevel() < CGTownInstance::EFortLevel::CASTLE && !buildingInfo.notEnoughRes)
|
||||
{
|
||||
if(buildingInfo.notEnoughRes)
|
||||
if (buildingInfo.id == BuildingID::CITADEL || buildingInfo.id == BuildingID::CASTLE)
|
||||
{
|
||||
if(ai->getLockedResources().canAfford(buildingInfo.buildCost))
|
||||
continue;
|
||||
|
||||
Composition composition;
|
||||
|
||||
composition.addNext(BuildThis(buildingInfo, developmentInfo));
|
||||
composition.addNext(SaveResources(buildingInfo.buildCost));
|
||||
|
||||
tasks.push_back(sptr(composition));
|
||||
}
|
||||
else
|
||||
tasks.push_back(sptr(BuildThis(buildingInfo, developmentInfo)));
|
||||
emergencyDefense = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!emergencyDefense)
|
||||
{
|
||||
for (auto& buildingInfo : developmentInfo.toBuild)
|
||||
{
|
||||
if (isGoldPressureLow || buildingInfo.dailyIncome[EGameResID::GOLD] > 0)
|
||||
{
|
||||
if (buildingInfo.notEnoughRes)
|
||||
{
|
||||
if (ai->getLockedResources().canAfford(buildingInfo.buildCost))
|
||||
continue;
|
||||
|
||||
Composition composition;
|
||||
|
||||
composition.addNext(BuildThis(buildingInfo, developmentInfo));
|
||||
composition.addNext(SaveResources(buildingInfo.buildCost));
|
||||
tasks.push_back(sptr(composition));
|
||||
}
|
||||
else
|
||||
{
|
||||
tasks.push_back(sptr(BuildThis(buildingInfo, developmentInfo)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,9 +28,6 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
|
||||
if(ai->cb->getDate(Date::DAY) == 1)
|
||||
return tasks;
|
||||
|
||||
auto heroes = cb->getHeroesInfo();
|
||||
|
||||
if(heroes.empty())
|
||||
@ -38,19 +35,23 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const
|
||||
return tasks;
|
||||
}
|
||||
|
||||
ai->dangerHitMap->updateHitMap();
|
||||
|
||||
for(auto town : cb->getTownsInfo())
|
||||
{
|
||||
uint8_t closestThreat = ai->dangerHitMap->getTileThreat(town->visitablePos()).fastestDanger.turn;
|
||||
|
||||
if (closestThreat >=2 && ai->buildAnalyzer->isGoldPressureHigh() && !town->hasBuilt(BuildingID::CITY_HALL) && cb->canBuildStructure(town, BuildingID::CITY_HALL) != EBuildingState::FORBIDDEN)
|
||||
{
|
||||
return tasks;
|
||||
}
|
||||
|
||||
auto townArmyAvailableToBuy = ai->armyManager->getArmyAvailableToBuyAsCCreatureSet(
|
||||
town,
|
||||
ai->getFreeResources());
|
||||
|
||||
for(const CGHeroInstance * targetHero : heroes)
|
||||
{
|
||||
if(ai->buildAnalyzer->isGoldPressureHigh() && !town->hasBuilt(BuildingID::CITY_HALL))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(ai->heroManager->getHeroRole(targetHero) == HeroRole::MAIN)
|
||||
{
|
||||
auto reinforcement = ai->armyManager->howManyReinforcementsCanGet(
|
||||
@ -63,7 +64,7 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const
|
||||
|
||||
if(reinforcement)
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(5)));
|
||||
tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(reinforcement)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -68,14 +68,6 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(
|
||||
logAi->trace("Path found %s", path.toString());
|
||||
#endif
|
||||
|
||||
if(nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.getHeroStrength());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
if(objToVisit && !force && !shouldVisit(nullkiller, path.targetHero, objToVisit))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
@ -87,6 +79,9 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(
|
||||
auto hero = path.targetHero;
|
||||
auto danger = path.getTotalDanger();
|
||||
|
||||
if (hero->getOwner() != nullkiller->playerID)
|
||||
continue;
|
||||
|
||||
if(nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT
|
||||
&& (path.getTotalDanger() == 0 || path.turn() > 0)
|
||||
&& path.exchangeCount > 1)
|
||||
@ -119,7 +114,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(
|
||||
continue;
|
||||
}
|
||||
|
||||
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
|
||||
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger, nullkiller->settings->getSafeAttackRatio());
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
@ -212,7 +207,7 @@ void CaptureObjectsBehavior::decomposeObjects(
|
||||
vstd::concatenate(tasksLocal, getVisitGoals(paths, nullkiller, objToVisit, specificObjects));
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(sync); // FIXME: consider using tbb::parallel_reduce instead to avoid mutex overhead
|
||||
std::lock_guard lock(sync); // FIXME: consider using tbb::parallel_reduce instead to avoid mutex overhead
|
||||
vstd::concatenate(result, tasksLocal);
|
||||
});
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoa
|
||||
|
||||
tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, nullptr).setpriority(5)));
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
else if(ai->heroManager->getHeroRole(town->garrisonHero.get()) == HeroRole::MAIN)
|
||||
{
|
||||
@ -141,7 +141,7 @@ bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoa
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::DismissHero(heroToDismiss).setpriority(5)));
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -158,11 +158,10 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
|
||||
threats.push_back(threatNode.fastestDanger); // no guarantee that fastest danger will be there
|
||||
|
||||
if(town->garrisonHero && handleGarrisonHeroFromPreviousTurn(town, tasks, ai))
|
||||
if (town->garrisonHero && handleGarrisonHeroFromPreviousTurn(town, tasks, ai))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!threatNode.fastestDanger.hero)
|
||||
{
|
||||
logAi->trace("No threat found for town %s", town->getNameTranslated());
|
||||
@ -240,7 +239,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
if(path.turn() <= threat.turn - 2)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Defer defence of %s by %s because he has enough time to reach the town next trun",
|
||||
logAi->trace("Defer defence of %s by %s because he has enough time to reach the town next turn",
|
||||
town->getObjectName(),
|
||||
path.targetHero->getObjectName());
|
||||
#endif
|
||||
@ -250,6 +249,16 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!path.targetHero->canBeMergedWith(*town))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Can't merge armies of hero %s and town %s",
|
||||
path.targetHero->getObjectName(),
|
||||
town->getObjectName());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
if(path.targetHero == town->visitingHero.get() && path.exchangeCount == 1)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
@ -261,6 +270,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
// dismiss creatures we are not able to pick to be able to hide in garrison
|
||||
if(town->garrisonHero
|
||||
|| town->getUpperArmy()->stacksCount() == 0
|
||||
|| path.targetHero->canBeMergedWith(*town)
|
||||
|| (town->getUpperArmy()->getArmyStrength() < 500 && town->fortLevel() >= CGTownInstance::CITADEL))
|
||||
{
|
||||
tasks.push_back(
|
||||
@ -292,7 +302,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
continue;
|
||||
}
|
||||
|
||||
if(threat.turn == 0 || (path.turn() <= threat.turn && path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= threat.danger))
|
||||
if(threat.turn == 0 || (path.turn() <= threat.turn && path.getHeroStrength() * ai->settings->getSafeAttackRatio() >= threat.danger))
|
||||
{
|
||||
if(ai->arePathHeroesLocked(path))
|
||||
{
|
||||
@ -343,23 +353,14 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
}
|
||||
else if(town->visitingHero && path.targetHero != town->visitingHero && !path.containsHero(town->visitingHero))
|
||||
{
|
||||
if(town->garrisonHero)
|
||||
if(town->garrisonHero && town->garrisonHero != path.targetHero)
|
||||
{
|
||||
if(ai->heroManager->getHeroRole(town->visitingHero.get()) == HeroRole::SCOUT
|
||||
&& town->visitingHero->getArmyStrength() < path.heroArmy->getArmyStrength() / 20)
|
||||
{
|
||||
if(path.turn() == 0)
|
||||
sequence.push_back(sptr(DismissHero(town->visitingHero.get())));
|
||||
}
|
||||
else
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Cancel moving %s to defend town %s as the town has garrison hero",
|
||||
path.targetHero->getObjectName(),
|
||||
town->getObjectName());
|
||||
logAi->trace("Cancel moving %s to defend town %s as the town has garrison hero",
|
||||
path.targetHero->getObjectName(),
|
||||
town->getObjectName());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else if(path.turn() == 0)
|
||||
{
|
||||
@ -405,6 +406,9 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
|
||||
void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitMapInfo & threat, const CGTownInstance * town, const Nullkiller * ai) const
|
||||
{
|
||||
if (threat.turn > 0 || town->garrisonHero || town->visitingHero)
|
||||
return;
|
||||
|
||||
if(town->hasBuilt(BuildingID::TAVERN)
|
||||
&& ai->cb->getResourceAmount(EGameResID::GOLD) > GameConstants::HERO_GOLD_COST)
|
||||
{
|
||||
@ -415,6 +419,21 @@ void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitM
|
||||
if(hero->getTotalStrength() < threat.danger)
|
||||
continue;
|
||||
|
||||
bool heroAlreadyHiredInOtherTown = false;
|
||||
for (const auto& task : tasks)
|
||||
{
|
||||
if (auto recruitGoal = dynamic_cast<Goals::RecruitHero*>(task.get()))
|
||||
{
|
||||
if (recruitGoal->getHero() == hero)
|
||||
{
|
||||
heroAlreadyHiredInOtherTown = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (heroAlreadyHiredInOtherTown)
|
||||
continue;
|
||||
|
||||
auto myHeroes = ai->cb->getHeroesInfo();
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
@ -451,7 +470,7 @@ void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitM
|
||||
}
|
||||
else if(ai->heroManager->heroCapReached())
|
||||
{
|
||||
heroToDismiss = ai->heroManager->findWeakHeroToDismiss(hero->getArmyStrength());
|
||||
heroToDismiss = ai->heroManager->findWeakHeroToDismiss(hero->getArmyStrength(), town);
|
||||
|
||||
if(!heroToDismiss)
|
||||
continue;
|
||||
|
@ -33,46 +33,32 @@ Goals::TGoalVec ExplorationBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
|
||||
for(auto obj : ai->memory->visitableObjs)
|
||||
for (auto obj : ai->memory->visitableObjs)
|
||||
{
|
||||
if(!vstd::contains(ai->memory->alreadyVisited, obj))
|
||||
switch (obj->ID.num)
|
||||
{
|
||||
switch(obj->ID.num)
|
||||
{
|
||||
case Obj::REDWOOD_OBSERVATORY:
|
||||
case Obj::PILLAR_OF_FIRE:
|
||||
tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 200)).addNext(CaptureObject(obj))));
|
||||
{
|
||||
auto rObj = dynamic_cast<const CRewardableObject*>(obj);
|
||||
if (!rObj->wasScouted(ai->playerID))
|
||||
tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 200)).addNext(CaptureObject(obj))));
|
||||
break;
|
||||
}
|
||||
case Obj::MONOLITH_ONE_WAY_ENTRANCE:
|
||||
case Obj::MONOLITH_TWO_WAY:
|
||||
case Obj::SUBTERRANEAN_GATE:
|
||||
auto tObj = dynamic_cast<const CGTeleport *>(obj);
|
||||
if(TeleportChannel::IMPASSABLE != ai->memory->knownTeleportChannels[tObj->channel]->passability)
|
||||
{
|
||||
tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 50)).addNext(CaptureObject(obj))));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(obj->ID.num)
|
||||
case Obj::WHIRLPOOL:
|
||||
{
|
||||
case Obj::MONOLITH_TWO_WAY:
|
||||
case Obj::SUBTERRANEAN_GATE:
|
||||
auto tObj = dynamic_cast<const CGTeleport *>(obj);
|
||||
if(TeleportChannel::IMPASSABLE == ai->memory->knownTeleportChannels[tObj->channel]->passability)
|
||||
break;
|
||||
for(auto exit : ai->memory->knownTeleportChannels[tObj->channel]->exits)
|
||||
auto tObj = dynamic_cast<const CGTeleport*>(obj);
|
||||
for (auto exit : cb->getTeleportChannelExits(tObj->channel))
|
||||
{
|
||||
if(!cb->getObj(exit))
|
||||
{
|
||||
// Always attempt to visit two-way teleports if one of channel exits is not visible
|
||||
tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 50)).addNext(CaptureObject(obj))));
|
||||
break;
|
||||
if (exit != tObj->id)
|
||||
{
|
||||
if (!cb->isVisible(cb->getObjInstance(exit)))
|
||||
tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 50)).addNext(CaptureObject(obj))));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +81,9 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const Nullkiller * ai, con
|
||||
logAi->trace("Path found %s, %s, %lld", path.toString(), path.targetHero->getObjectName(), path.heroArmy->getArmyStrength());
|
||||
#endif
|
||||
|
||||
if (path.targetHero->getOwner() != ai->playerID)
|
||||
continue;
|
||||
|
||||
if(path.containsHero(hero))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
@ -89,14 +92,6 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const Nullkiller * ai, con
|
||||
continue;
|
||||
}
|
||||
|
||||
if(path.turn() > 0 && ai->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
if(ai->arePathHeroesLocked(path))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
@ -150,7 +145,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const Nullkiller * ai, con
|
||||
}
|
||||
|
||||
auto danger = path.getTotalDanger();
|
||||
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
|
||||
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger, ai->settings->getSafeAttackRatio());
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
@ -292,17 +287,6 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
|
||||
continue;
|
||||
}
|
||||
|
||||
auto heroRole = ai->heroManager->getHeroRole(path.targetHero);
|
||||
|
||||
if(heroRole == HeroRole::SCOUT
|
||||
&& ai->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
auto upgrade = ai->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
|
||||
|
||||
if(!upgrader->garrisonHero
|
||||
@ -320,14 +304,6 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
|
||||
|
||||
armyToGetOrBuy.upgradeValue -= path.heroArmy->getArmyStrength();
|
||||
|
||||
armyToGetOrBuy.addArmyToBuy(
|
||||
ai->armyManager->toSlotInfo(
|
||||
ai->armyManager->getArmyAvailableToBuy(
|
||||
path.heroArmy,
|
||||
upgrader,
|
||||
ai->getFreeResources(),
|
||||
path.turn())));
|
||||
|
||||
upgrade.upgradeValue += armyToGetOrBuy.upgradeValue;
|
||||
upgrade.upgradeCost += armyToGetOrBuy.upgradeCost;
|
||||
vstd::concatenate(upgrade.resultingArmy, armyToGetOrBuy.resultingArmy);
|
||||
@ -339,8 +315,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
|
||||
{
|
||||
for(auto hero : cb->getAvailableHeroes(upgrader))
|
||||
{
|
||||
auto scoutReinforcement = ai->armyManager->howManyReinforcementsCanBuy(hero, upgrader)
|
||||
+ ai->armyManager->howManyReinforcementsCanGet(hero, upgrader);
|
||||
auto scoutReinforcement = ai->armyManager->howManyReinforcementsCanGet(hero, upgrader);
|
||||
|
||||
if(scoutReinforcement >= armyToGetOrBuy.upgradeValue
|
||||
&& ai->getFreeGold() >20000
|
||||
@ -366,7 +341,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
|
||||
|
||||
auto danger = path.getTotalDanger();
|
||||
|
||||
auto isSafe = isSafeToVisit(path.targetHero, path.heroArmy, danger);
|
||||
auto isSafe = isSafeToVisit(path.targetHero, path.heroArmy, danger, ai->settings->getSafeAttackRatio());
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
|
@ -31,9 +31,11 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const
|
||||
|
||||
auto ourHeroes = ai->heroManager->getHeroRoles();
|
||||
auto minScoreToHireMain = std::numeric_limits<float>::max();
|
||||
int currentArmyValue = 0;
|
||||
|
||||
for(auto hero : ourHeroes)
|
||||
{
|
||||
currentArmyValue += hero.first->getArmyCost();
|
||||
if(hero.second != HeroRole::MAIN)
|
||||
continue;
|
||||
|
||||
@ -45,51 +47,89 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const
|
||||
minScoreToHireMain = newScore;
|
||||
}
|
||||
}
|
||||
// If we don't have any heros we might want to lower our expectations.
|
||||
if (ourHeroes.empty())
|
||||
minScoreToHireMain = 0;
|
||||
|
||||
const CGHeroInstance* bestHeroToHire = nullptr;
|
||||
const CGTownInstance* bestTownToHireFrom = nullptr;
|
||||
float bestScore = 0;
|
||||
bool haveCapitol = false;
|
||||
|
||||
ai->dangerHitMap->updateHitMap();
|
||||
int treasureSourcesCount = 0;
|
||||
|
||||
for(auto town : towns)
|
||||
{
|
||||
uint8_t closestThreat = UINT8_MAX;
|
||||
for (auto threat : ai->dangerHitMap->getTownThreats(town))
|
||||
{
|
||||
closestThreat = std::min(closestThreat, threat.turn);
|
||||
}
|
||||
//Don't hire a hero where there already is one present
|
||||
if (town->visitingHero && town->garrisonHero)
|
||||
continue;
|
||||
float visitability = 0;
|
||||
for (auto checkHero : ourHeroes)
|
||||
{
|
||||
if (ai->dangerHitMap->getClosestTown(checkHero.first.get()->visitablePos()) == town)
|
||||
visitability++;
|
||||
}
|
||||
if(ai->heroManager->canRecruitHero(town))
|
||||
{
|
||||
auto availableHeroes = ai->cb->getAvailableHeroes(town);
|
||||
|
||||
for(auto hero : availableHeroes)
|
||||
|
||||
for (auto obj : ai->objectClusterizer->getNearbyObjects())
|
||||
{
|
||||
auto score = ai->heroManager->evaluateHero(hero);
|
||||
|
||||
if(score > minScoreToHireMain)
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero(town, hero).setpriority(200)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int treasureSourcesCount = 0;
|
||||
|
||||
for(auto obj : ai->objectClusterizer->getNearbyObjects())
|
||||
{
|
||||
if((obj->ID == Obj::RESOURCE)
|
||||
if ((obj->ID == Obj::RESOURCE)
|
||||
|| obj->ID == Obj::TREASURE_CHEST
|
||||
|| obj->ID == Obj::CAMPFIRE
|
||||
|| isWeeklyRevisitable(ai, obj)
|
||||
|| obj->ID ==Obj::ARTIFACT)
|
||||
|| obj->ID == Obj::ARTIFACT)
|
||||
{
|
||||
auto tile = obj->visitablePos();
|
||||
auto closestTown = ai->dangerHitMap->getClosestTown(tile);
|
||||
|
||||
if(town == closestTown)
|
||||
if (town == closestTown)
|
||||
treasureSourcesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if(treasureSourcesCount < 5 && (town->garrisonHero || town->getUpperArmy()->getArmyStrength() < 10000))
|
||||
continue;
|
||||
|
||||
if(ai->cb->getHeroesInfo().size() < ai->cb->getTownsInfo().size() + 1
|
||||
|| (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureHigh()))
|
||||
for(auto hero : availableHeroes)
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero(town).setpriority(3)));
|
||||
auto score = ai->heroManager->evaluateHero(hero);
|
||||
if(score > minScoreToHireMain)
|
||||
{
|
||||
score *= score / minScoreToHireMain;
|
||||
}
|
||||
score *= (hero->getArmyCost() + currentArmyValue);
|
||||
if (hero->getFactionID() == town->getFactionID())
|
||||
score *= 1.5;
|
||||
if (vstd::isAlmostZero(visitability))
|
||||
score *= 30 * town->getTownLevel();
|
||||
else
|
||||
score *= town->getTownLevel() / visitability;
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestHeroToHire = hero;
|
||||
bestTownToHireFrom = town;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (town->hasCapitol())
|
||||
haveCapitol = true;
|
||||
}
|
||||
if (bestHeroToHire && bestTownToHireFrom)
|
||||
{
|
||||
if (ai->cb->getHeroesInfo().size() == 0
|
||||
|| treasureSourcesCount > ai->cb->getHeroesInfo().size() * 5
|
||||
|| bestHeroToHire->getArmyCost() > GameConstants::HERO_GOLD_COST / 2.0
|
||||
|| (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureHigh() && haveCapitol)
|
||||
|| (ai->getFreeResources()[EGameResID::GOLD] > 30000 && !ai->buildAnalyzer->isGoldPressureHigh()))
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero(bestTownToHireFrom, bestHeroToHire).setpriority((float)3 / (ourHeroes.size() + 1))));
|
||||
}
|
||||
}
|
||||
|
||||
return tasks;
|
||||
|
@ -79,6 +79,15 @@ bool needToRecruitHero(const Nullkiller * ai, const CGTownInstance * startupTown
|
||||
bool isGoldPile = dynamic_cast<const CGResource *>(obj)
|
||||
&& dynamic_cast<const CGResource *>(obj)->resourceID() == EGameResID::GOLD;
|
||||
|
||||
auto rewardable = dynamic_cast<const Rewardable::Interface *>(obj);
|
||||
|
||||
if(rewardable)
|
||||
{
|
||||
for(auto & info : rewardable->configuration.info)
|
||||
if(info.reward.resources[EGameResID::GOLD] > 0)
|
||||
isGoldPile = true;
|
||||
}
|
||||
|
||||
if(isGoldPile
|
||||
|| obj->ID == Obj::TREASURE_CHEST
|
||||
|| obj->ID == Obj::CAMPFIRE
|
||||
|
@ -39,9 +39,6 @@ Goals::TGoalVec StayAtTownBehavior::decompose(const Nullkiller * ai) const
|
||||
|
||||
for(auto town : towns)
|
||||
{
|
||||
if(!town->hasBuilt(BuildingID::MAGES_GUILD_1))
|
||||
continue;
|
||||
|
||||
ai->pathfinder->calculatePathInfo(paths, town->visitablePos());
|
||||
|
||||
for(auto & path : paths)
|
||||
@ -49,14 +46,8 @@ Goals::TGoalVec StayAtTownBehavior::decompose(const Nullkiller * ai) const
|
||||
if(town->visitingHero && town->visitingHero.get() != path.targetHero)
|
||||
continue;
|
||||
|
||||
if(!path.targetHero->hasSpellbook() || path.targetHero->mana >= 0.75f * path.targetHero->manaLimit())
|
||||
continue;
|
||||
|
||||
if(path.turn() == 0 && !path.getFirstBlockedAction() && path.exchangeCount <= 1)
|
||||
if(!path.getFirstBlockedAction() && path.exchangeCount <= 1)
|
||||
{
|
||||
if(path.targetHero->mana == path.targetHero->manaLimit())
|
||||
continue;
|
||||
|
||||
Composition stayAtTown;
|
||||
|
||||
stayAtTown.addNextSequence({
|
||||
|
@ -8,6 +8,7 @@ set(Nullkiller_SRCS
|
||||
Pathfinding/Actions/QuestAction.cpp
|
||||
Pathfinding/Actions/BuyArmyAction.cpp
|
||||
Pathfinding/Actions/BoatActions.cpp
|
||||
Pathfinding/Actions/WhirlpoolAction.cpp
|
||||
Pathfinding/Actions/TownPortalAction.cpp
|
||||
Pathfinding/Actions/AdventureSpellCastMovementActions.cpp
|
||||
Pathfinding/Rules/AILayerTransitionRule.cpp
|
||||
@ -79,6 +80,7 @@ set(Nullkiller_HEADERS
|
||||
Pathfinding/Actions/QuestAction.h
|
||||
Pathfinding/Actions/BuyArmyAction.h
|
||||
Pathfinding/Actions/BoatActions.h
|
||||
Pathfinding/Actions/WhirlpoolAction.h
|
||||
Pathfinding/Actions/TownPortalAction.h
|
||||
Pathfinding/Actions/AdventureSpellCastMovementActions.h
|
||||
Pathfinding/Rules/AILayerTransitionRule.h
|
||||
@ -155,11 +157,7 @@ else()
|
||||
endif()
|
||||
|
||||
target_include_directories(Nullkiller PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(Nullkiller PUBLIC vcmi fuzzylite::fuzzylite TBB::tbb)
|
||||
target_link_libraries(Nullkiller PUBLIC vcmi fuzzylite::fuzzylite)
|
||||
|
||||
vcmi_set_output_dir(Nullkiller "AI")
|
||||
enable_pch(Nullkiller)
|
||||
|
||||
if(APPLE_IOS AND NOT USING_CONAN)
|
||||
install(IMPORTED_RUNTIME_ARTIFACTS TBB::tbb LIBRARY DESTINATION ${LIB_DIR}) # CMake 3.21+
|
||||
endif()
|
||||
|
@ -17,8 +17,7 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
#define MIN_AI_STRENGTH (0.5f) //lower when combat AI gets smarter
|
||||
#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
|
||||
constexpr float MIN_AI_STRENGTH = 0.5f; //lower when combat AI gets smarter
|
||||
|
||||
engineBase::engineBase()
|
||||
{
|
||||
@ -208,12 +207,6 @@ float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, c
|
||||
enemyFlyers->setValue(enemyStructure.flyers);
|
||||
enemySpeed->setValue(enemyStructure.maxSpeed);
|
||||
|
||||
bool bank = dynamic_cast<const CBank *>(enemy);
|
||||
if(bank)
|
||||
bankPresent->setValue(1);
|
||||
else
|
||||
bankPresent->setValue(0);
|
||||
|
||||
const CGTownInstance * fort = dynamic_cast<const CGTownInstance *>(enemy);
|
||||
if(fort)
|
||||
castleWalls->setValue(fort->fortLevel());
|
||||
|
@ -20,25 +20,6 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
|
||||
{
|
||||
//this one is not fuzzy anymore, just calculate weighted average
|
||||
|
||||
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(bank->cb))
|
||||
{
|
||||
totalStrength += config.second.totalStrength * config.first;
|
||||
totalChance += config.first;
|
||||
}
|
||||
return totalStrength / std::max<ui8>(totalChance, 1); //avoid division by zero
|
||||
|
||||
}
|
||||
|
||||
ui64 FuzzyHelper::evaluateDanger(const int3 & tile, const CGHeroInstance * visitor, bool checkGuards)
|
||||
{
|
||||
auto cb = ai->cb.get();
|
||||
@ -71,6 +52,15 @@ ui64 FuzzyHelper::evaluateDanger(const int3 & tile, const CGHeroInstance * visit
|
||||
{
|
||||
objectDanger += evaluateDanger(hero->visitedTown.get());
|
||||
}
|
||||
objectDanger *= ai->heroManager->getFightingStrengthCached(hero);
|
||||
}
|
||||
if (objWithID<Obj::TOWN>(dangerousObject))
|
||||
{
|
||||
auto town = dynamic_cast<const CGTownInstance*>(dangerousObject);
|
||||
auto hero = town->garrisonHero;
|
||||
|
||||
if (hero)
|
||||
objectDanger *= ai->heroManager->getFightingStrengthCached(hero);
|
||||
}
|
||||
|
||||
if(objectDanger)
|
||||
@ -136,10 +126,10 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
|
||||
{
|
||||
auto fortLevel = town->fortLevel();
|
||||
|
||||
if(fortLevel == CGTownInstance::EFortLevel::CASTLE)
|
||||
danger += 10000;
|
||||
if (fortLevel == CGTownInstance::EFortLevel::CASTLE)
|
||||
danger = std::max(danger * 2, danger + 10000);
|
||||
else if(fortLevel == CGTownInstance::EFortLevel::CITADEL)
|
||||
danger += 4000;
|
||||
danger = std::max(ui64(danger * 1.4), danger + 4000);
|
||||
}
|
||||
|
||||
return danger;
|
||||
@ -158,30 +148,14 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
|
||||
return 0;
|
||||
[[fallthrough]];
|
||||
}
|
||||
case Obj::MONSTER:
|
||||
case Obj::GARRISON:
|
||||
case Obj::GARRISON2:
|
||||
case Obj::CREATURE_GENERATOR1:
|
||||
case Obj::CREATURE_GENERATOR4:
|
||||
case Obj::MINE:
|
||||
case Obj::ABANDONED_MINE:
|
||||
case Obj::PANDORAS_BOX:
|
||||
case Obj::CRYPT: //crypt
|
||||
case Obj::CREATURE_BANK: //crebank
|
||||
case Obj::DRAGON_UTOPIA:
|
||||
case Obj::SHIPWRECK: //shipwreck
|
||||
case Obj::DERELICT_SHIP: //derelict ship
|
||||
default:
|
||||
{
|
||||
const CArmedInstance * a = dynamic_cast<const CArmedInstance *>(obj);
|
||||
return a->getArmyStrength();
|
||||
if (a)
|
||||
return a->getArmyStrength();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
case Obj::PYRAMID:
|
||||
{
|
||||
return estimateBankDanger(dynamic_cast<const CBank *>(obj));
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,12 +10,6 @@
|
||||
#pragma once
|
||||
#include "FuzzyEngines.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CBank;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
@ -30,8 +24,6 @@ private:
|
||||
public:
|
||||
FuzzyHelper(const Nullkiller * ai): ai(ai) {}
|
||||
|
||||
ui64 estimateBankDanger(const CBank * bank); //TODO: move to another class?
|
||||
|
||||
ui64 evaluateDanger(const CGObjectInstance * obj);
|
||||
ui64 evaluateDanger(const int3 & tile, const CGHeroInstance * visitor, bool checkGuards = true);
|
||||
};
|
||||
|
@ -34,13 +34,12 @@ using namespace Goals;
|
||||
std::unique_ptr<ObjectGraph> Nullkiller::baseGraph;
|
||||
|
||||
Nullkiller::Nullkiller()
|
||||
:activeHero(nullptr), scanDepth(ScanDepth::MAIN_FULL), useHeroChain(true)
|
||||
: activeHero(nullptr)
|
||||
, scanDepth(ScanDepth::MAIN_FULL)
|
||||
, useHeroChain(true)
|
||||
, memory(std::make_unique<AIMemory>())
|
||||
{
|
||||
memory = std::make_unique<AIMemory>();
|
||||
settings = std::make_unique<Settings>();
|
||||
|
||||
useObjectGraph = settings->isObjectGraphAllowed();
|
||||
openMap = settings->isOpenMap() || useObjectGraph;
|
||||
}
|
||||
|
||||
bool canUseOpenMap(std::shared_ptr<CCallback> cb, PlayerColor playerID)
|
||||
@ -62,17 +61,23 @@ bool canUseOpenMap(std::shared_ptr<CCallback> cb, PlayerColor playerID)
|
||||
return false;
|
||||
}
|
||||
|
||||
return cb->getStartInfo()->difficulty >= 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Nullkiller::init(std::shared_ptr<CCallback> cb, AIGateway * gateway)
|
||||
{
|
||||
this->cb = cb;
|
||||
this->gateway = gateway;
|
||||
|
||||
playerID = gateway->playerID;
|
||||
this->playerID = gateway->playerID;
|
||||
|
||||
if(openMap && !canUseOpenMap(cb, playerID))
|
||||
settings = std::make_unique<Settings>(cb->getStartInfo()->difficulty);
|
||||
|
||||
if(canUseOpenMap(cb, playerID))
|
||||
{
|
||||
useObjectGraph = settings->isObjectGraphAllowed();
|
||||
openMap = settings->isOpenMap() || useObjectGraph;
|
||||
}
|
||||
else
|
||||
{
|
||||
useObjectGraph = false;
|
||||
openMap = false;
|
||||
@ -122,11 +127,14 @@ void TaskPlan::merge(TSubgoal task)
|
||||
{
|
||||
TGoalVec blockers;
|
||||
|
||||
if (task->asTask()->priority <= 0)
|
||||
return;
|
||||
|
||||
for(auto & item : tasks)
|
||||
{
|
||||
for(auto objid : item.affectedObjects)
|
||||
{
|
||||
if(task == item.task || task->asTask()->isObjectAffected(objid))
|
||||
if(task == item.task || task->asTask()->isObjectAffected(objid) || (task->asTask()->getHero() != nullptr && task->asTask()->getHero() == item.task->asTask()->getHero()))
|
||||
{
|
||||
if(item.task->asTask()->priority >= task->asTask()->priority)
|
||||
return;
|
||||
@ -166,20 +174,19 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TGoalVec & tasks) const
|
||||
return taskptr(*bestTask);
|
||||
}
|
||||
|
||||
Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks) const
|
||||
Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks, int priorityTier) const
|
||||
{
|
||||
TaskPlan taskPlan;
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, tasks.size()), [this, &tasks](const tbb::blocked_range<size_t> & r)
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, tasks.size()), [this, &tasks, priorityTier](const tbb::blocked_range<size_t> & r)
|
||||
{
|
||||
auto evaluator = this->priorityEvaluators->acquire();
|
||||
|
||||
for(size_t i = r.begin(); i != r.end(); i++)
|
||||
{
|
||||
auto task = tasks[i];
|
||||
|
||||
if(task->asTask()->priority <= 0)
|
||||
task->asTask()->priority = evaluator->evaluate(task);
|
||||
if (task->asTask()->priority <= 0 || priorityTier != PriorityEvaluator::PriorityTier::BUILDINGS)
|
||||
task->asTask()->priority = evaluator->evaluate(task, priorityTier);
|
||||
}
|
||||
});
|
||||
|
||||
@ -216,7 +223,7 @@ void Nullkiller::decompose(Goals::TGoalVec & result, Goals::TSubgoal behavior, i
|
||||
|
||||
void Nullkiller::resetAiState()
|
||||
{
|
||||
std::unique_lock<std::mutex> lockGuard(aiStateMutex);
|
||||
std::unique_lock lockGuard(aiStateMutex);
|
||||
|
||||
lockedResources = TResources();
|
||||
scanDepth = ScanDepth::MAIN_FULL;
|
||||
@ -236,7 +243,7 @@ void Nullkiller::updateAiState(int pass, bool fast)
|
||||
{
|
||||
boost::this_thread::interruption_point();
|
||||
|
||||
std::unique_lock<std::mutex> lockGuard(aiStateMutex);
|
||||
std::unique_lock lockGuard(aiStateMutex);
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
@ -326,7 +333,7 @@ bool Nullkiller::arePathHeroesLocked(const AIPath & path) const
|
||||
if(lockReason != HeroLockedReason::NOT_LOCKED)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->getObjectName(), path.toString());
|
||||
logAi->trace("Hero %s is locked by %d. Discarding %s", path.targetHero->getObjectName(), (int)lockReason, path.toString());
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
@ -347,12 +354,24 @@ void Nullkiller::makeTurn()
|
||||
boost::lock_guard<boost::mutex> sharedStorageLock(AISharedStorage::locker);
|
||||
|
||||
const int MAX_DEPTH = 10;
|
||||
const float FAST_TASK_MINIMAL_PRIORITY = 0.7f;
|
||||
|
||||
resetAiState();
|
||||
|
||||
Goals::TGoalVec bestTasks;
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
float totalHeroStrength = 0;
|
||||
int totalTownLevel = 0;
|
||||
for (auto heroInfo : cb->getHeroesInfo())
|
||||
{
|
||||
totalHeroStrength += heroInfo->getTotalStrength();
|
||||
}
|
||||
for (auto townInfo : cb->getTownsInfo())
|
||||
{
|
||||
totalTownLevel += townInfo->getTownLevel();
|
||||
}
|
||||
logAi->info("Beginning: Strength: %f Townlevel: %d Resources: %s", totalHeroStrength, totalTownLevel, cb->getResourceAmount().toString());
|
||||
#endif
|
||||
for(int i = 1; i <= settings->getMaxPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++)
|
||||
{
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
@ -360,21 +379,30 @@ void Nullkiller::makeTurn()
|
||||
|
||||
Goals::TTask bestTask = taskptr(Goals::Invalid());
|
||||
|
||||
for(;i <= settings->getMaxPass(); i++)
|
||||
while(true)
|
||||
{
|
||||
bestTasks.clear();
|
||||
|
||||
decompose(bestTasks, sptr(RecruitHeroBehavior()), 1);
|
||||
decompose(bestTasks, sptr(BuyArmyBehavior()), 1);
|
||||
decompose(bestTasks, sptr(BuildingBehavior()), 1);
|
||||
|
||||
bestTask = choseBestTask(bestTasks);
|
||||
|
||||
if(bestTask->priority >= FAST_TASK_MINIMAL_PRIORITY)
|
||||
if(bestTask->priority > 0)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->info("Pass %d: Performing prio 0 task %s with prio: %d", i, bestTask->toString(), bestTask->priority);
|
||||
#endif
|
||||
if(!executeTask(bestTask))
|
||||
return;
|
||||
|
||||
updateAiState(i, true);
|
||||
bool fastUpdate = true;
|
||||
|
||||
if (bestTask->getHero() != nullptr)
|
||||
fastUpdate = false;
|
||||
|
||||
updateAiState(i, fastUpdate);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -382,7 +410,6 @@ void Nullkiller::makeTurn()
|
||||
}
|
||||
}
|
||||
|
||||
decompose(bestTasks, sptr(RecruitHeroBehavior()), 1);
|
||||
decompose(bestTasks, sptr(CaptureObjectsBehavior()), 1);
|
||||
decompose(bestTasks, sptr(ClusterBehavior()), MAX_DEPTH);
|
||||
decompose(bestTasks, sptr(DefenceBehavior()), MAX_DEPTH);
|
||||
@ -392,14 +419,26 @@ void Nullkiller::makeTurn()
|
||||
if(!isOpenMap())
|
||||
decompose(bestTasks, sptr(ExplorationBehavior()), MAX_DEPTH);
|
||||
|
||||
if(cb->getDate(Date::DAY) == 1 || heroManager->getHeroRoles().empty())
|
||||
TTaskVec selectedTasks;
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
int prioOfTask = 0;
|
||||
#endif
|
||||
for (int prio = PriorityEvaluator::PriorityTier::INSTAKILL; prio <= PriorityEvaluator::PriorityTier::DEFEND; ++prio)
|
||||
{
|
||||
decompose(bestTasks, sptr(StartupBehavior()), 1);
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
prioOfTask = prio;
|
||||
#endif
|
||||
selectedTasks = buildPlan(bestTasks, prio);
|
||||
if (!selectedTasks.empty() || settings->isUseFuzzy())
|
||||
break;
|
||||
}
|
||||
|
||||
auto selectedTasks = buildPlan(bestTasks);
|
||||
std::sort(selectedTasks.begin(), selectedTasks.end(), [](const TTask& a, const TTask& b)
|
||||
{
|
||||
return a->priority > b->priority;
|
||||
});
|
||||
|
||||
logAi->debug("Decission madel in %ld", timeElapsed(start));
|
||||
logAi->debug("Decision madel in %ld", timeElapsed(start));
|
||||
|
||||
if(selectedTasks.empty())
|
||||
{
|
||||
@ -438,7 +477,7 @@ void Nullkiller::makeTurn()
|
||||
bestTask->priority);
|
||||
}
|
||||
|
||||
if(bestTask->priority < MIN_PRIORITY)
|
||||
if((settings->isUseFuzzy() && bestTask->priority < MIN_PRIORITY) || (!settings->isUseFuzzy() && bestTask->priority <= 0))
|
||||
{
|
||||
auto heroes = cb->getHeroesInfo();
|
||||
auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
|
||||
@ -463,7 +502,9 @@ void Nullkiller::makeTurn()
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->info("Pass %d: Performing prio %d task %s with prio: %d", i, prioOfTask, bestTask->toString(), bestTask->priority);
|
||||
#endif
|
||||
if(!executeTask(bestTask))
|
||||
{
|
||||
if(hasAnySuccess)
|
||||
@ -471,13 +512,27 @@ void Nullkiller::makeTurn()
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
hasAnySuccess = true;
|
||||
}
|
||||
|
||||
hasAnySuccess |= handleTrading();
|
||||
|
||||
if(!hasAnySuccess)
|
||||
{
|
||||
logAi->trace("Nothing was done this turn. Ending turn.");
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
totalHeroStrength = 0;
|
||||
totalTownLevel = 0;
|
||||
for (auto heroInfo : cb->getHeroesInfo())
|
||||
{
|
||||
totalHeroStrength += heroInfo->getTotalStrength();
|
||||
}
|
||||
for (auto townInfo : cb->getTownsInfo())
|
||||
{
|
||||
totalTownLevel += townInfo->getTownLevel();
|
||||
}
|
||||
logAi->info("End: Strength: %f Townlevel: %d Resources: %s", totalHeroStrength, totalTownLevel, cb->getResourceAmount().toString());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
@ -554,4 +609,102 @@ void Nullkiller::lockResources(const TResources & res)
|
||||
lockedResources += res;
|
||||
}
|
||||
|
||||
bool Nullkiller::handleTrading()
|
||||
{
|
||||
bool haveTraded = false;
|
||||
bool shouldTryToTrade = true;
|
||||
int marketId = -1;
|
||||
for (auto town : cb->getTownsInfo())
|
||||
{
|
||||
if (town->hasBuiltSomeTradeBuilding())
|
||||
{
|
||||
marketId = town->id;
|
||||
}
|
||||
}
|
||||
if (marketId == -1)
|
||||
return false;
|
||||
if (const CGObjectInstance* obj = cb->getObj(ObjectInstanceID(marketId), false))
|
||||
{
|
||||
if (const auto* m = dynamic_cast<const IMarket*>(obj))
|
||||
{
|
||||
while (shouldTryToTrade)
|
||||
{
|
||||
shouldTryToTrade = false;
|
||||
buildAnalyzer->update();
|
||||
TResources required = buildAnalyzer->getTotalResourcesRequired();
|
||||
TResources income = buildAnalyzer->getDailyIncome();
|
||||
TResources available = cb->getResourceAmount();
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->debug("Available %s", available.toString());
|
||||
logAi->debug("Required %s", required.toString());
|
||||
#endif
|
||||
int mostWanted = -1;
|
||||
int mostExpendable = -1;
|
||||
float minRatio = std::numeric_limits<float>::max();
|
||||
float maxRatio = std::numeric_limits<float>::min();
|
||||
|
||||
for (int i = 0; i < required.size(); ++i)
|
||||
{
|
||||
if (required[i] <= 0)
|
||||
continue;
|
||||
float ratio = static_cast<float>(available[i]) / required[i];
|
||||
|
||||
if (ratio < minRatio) {
|
||||
minRatio = ratio;
|
||||
mostWanted = i;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < required.size(); ++i)
|
||||
{
|
||||
float ratio = available[i];
|
||||
if (required[i] > 0)
|
||||
ratio = static_cast<float>(available[i]) / required[i];
|
||||
else
|
||||
ratio = available[i];
|
||||
|
||||
bool okToSell = false;
|
||||
|
||||
if (i == GameResID::GOLD)
|
||||
{
|
||||
if (income[i] > 0 && !buildAnalyzer->isGoldPressureHigh())
|
||||
okToSell = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (required[i] <= 0 && income[i] > 0)
|
||||
okToSell = true;
|
||||
}
|
||||
|
||||
if (ratio > maxRatio && okToSell) {
|
||||
maxRatio = ratio;
|
||||
mostExpendable = i;
|
||||
}
|
||||
}
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->debug("mostExpendable: %d mostWanted: %d", mostExpendable, mostWanted);
|
||||
#endif
|
||||
if (mostExpendable == mostWanted || mostWanted == -1 || mostExpendable == -1)
|
||||
return false;
|
||||
|
||||
int toGive;
|
||||
int toGet;
|
||||
m->getOffer(mostExpendable, mostWanted, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
|
||||
//logAi->info("Offer is: I get %d of %s for %d of %s at %s", toGet, mostWanted, toGive, mostExpendable, obj->getObjectName());
|
||||
//TODO trade only as much as needed
|
||||
if (toGive && toGive <= available[mostExpendable]) //don't try to sell 0 resources
|
||||
{
|
||||
cb->trade(m->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(mostExpendable), GameResID(mostWanted), toGive);
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->info("Traded %d of %s for %d of %s at %s", toGive, mostExpendable, toGet, mostWanted, obj->getObjectName());
|
||||
#endif
|
||||
haveTraded = true;
|
||||
shouldTryToTrade = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return haveTraded;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -120,13 +120,14 @@ public:
|
||||
ScanDepth getScanDepth() const { return scanDepth; }
|
||||
bool isOpenMap() const { return openMap; }
|
||||
bool isObjectGraphAllowed() const { return useObjectGraph; }
|
||||
bool handleTrading();
|
||||
|
||||
private:
|
||||
void resetAiState();
|
||||
void updateAiState(int pass, bool fast = false);
|
||||
void decompose(Goals::TGoalVec & result, Goals::TSubgoal behavior, int decompositionMaxDepth) const;
|
||||
Goals::TTask choseBestTask(Goals::TGoalVec & tasks) const;
|
||||
Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks) const;
|
||||
Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks, int priorityTier) const;
|
||||
bool executeTask(Goals::TTask task);
|
||||
bool areAffectedObjectsPresent(Goals::TTask task) const;
|
||||
HeroRole getTaskRole(Goals::TTask task) const;
|
||||
|
@ -15,6 +15,8 @@
|
||||
#include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
|
||||
#include "../../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../../lib/mapping/CMapDefines.h"
|
||||
#include "../../../lib/RoadHandler.h"
|
||||
#include "../../../lib/CCreatureHandler.h"
|
||||
#include "../../../lib/VCMI_Lib.h"
|
||||
#include "../../../lib/StartInfo.h"
|
||||
@ -33,11 +35,9 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
#define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
|
||||
#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
|
||||
const float MIN_CRITICAL_VALUE = 2.0f;
|
||||
constexpr float MIN_CRITICAL_VALUE = 2.0f;
|
||||
|
||||
EvaluationContext::EvaluationContext(const Nullkiller * ai)
|
||||
EvaluationContext::EvaluationContext(const Nullkiller* ai)
|
||||
: movementCost(0.0),
|
||||
manaCost(0),
|
||||
danger(0),
|
||||
@ -51,9 +51,22 @@ EvaluationContext::EvaluationContext(const Nullkiller * ai)
|
||||
heroRole(HeroRole::SCOUT),
|
||||
turn(0),
|
||||
strategicalValue(0),
|
||||
conquestValue(0),
|
||||
evaluator(ai),
|
||||
enemyHeroDangerRatio(0),
|
||||
armyGrowth(0)
|
||||
threat(0),
|
||||
armyGrowth(0),
|
||||
armyInvolvement(0),
|
||||
defenseValue(0),
|
||||
isDefend(false),
|
||||
threatTurns(INT_MAX),
|
||||
involvesSailing(false),
|
||||
isTradeBuilding(false),
|
||||
isExchange(false),
|
||||
isArmyUpgrade(false),
|
||||
isHero(false),
|
||||
isEnemy(false),
|
||||
explorePriority(0)
|
||||
{
|
||||
}
|
||||
|
||||
@ -118,35 +131,17 @@ int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, cons
|
||||
return booster * (town->hasFort() && town->tempOwner != PlayerColor::NEUTRAL ? booster * 500 : 250);
|
||||
}
|
||||
|
||||
TResources getCreatureBankResources(const CGObjectInstance * target, const CGHeroInstance * hero)
|
||||
int32_t getResourcesGoldReward(const TResources & res)
|
||||
{
|
||||
//Fixme: unused variable hero
|
||||
int32_t result = 0;
|
||||
|
||||
auto objectInfo = target->getObjectHandler()->getObjectInfo(target->appearance);
|
||||
CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
|
||||
auto resources = bankInfo->getPossibleResourcesReward();
|
||||
TResources result = TResources();
|
||||
int sum = 0;
|
||||
|
||||
for(auto & reward : resources)
|
||||
for(auto r : GameResID::ALL_RESOURCES())
|
||||
{
|
||||
result += reward.data * reward.chance;
|
||||
sum += reward.chance;
|
||||
if(res[r] > 0)
|
||||
result += r == EGameResID::GOLD ? res[r] : res[r] * 100;
|
||||
}
|
||||
|
||||
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;
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
|
||||
@ -173,10 +168,10 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero
|
||||
for (auto c : creatures)
|
||||
{
|
||||
//Only if hero has slot for this creature in the army
|
||||
auto ccre = dynamic_cast<const CCreature*>(c.data.type);
|
||||
auto ccre = dynamic_cast<const CCreature*>(c.data.getType());
|
||||
if (hero->getSlotFor(ccre).validSlot() || duplicatingSlots > 0)
|
||||
{
|
||||
result += (c.data.type->getAIValue() * c.data.count) * c.chance;
|
||||
result += (c.data.getType()->getAIValue() * c.data.count) * c.chance;
|
||||
}
|
||||
/*else
|
||||
{
|
||||
@ -243,16 +238,16 @@ int getDwellingArmyCost(const CGObjectInstance * target)
|
||||
auto creature = creLevel.second.back().toCreature();
|
||||
auto creaturesAreFree = creature->getLevel() == 1;
|
||||
if(!creaturesAreFree)
|
||||
cost += creature->getRecruitCost(EGameResID::GOLD) * creLevel.first;
|
||||
cost += creature->getFullRecruitCost().marketValue() * creLevel.first;
|
||||
}
|
||||
}
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
static uint64_t evaluateArtifactArmyValue(const CArtifactInstance * art)
|
||||
static uint64_t evaluateArtifactArmyValue(const CArtifact * art)
|
||||
{
|
||||
if(art->artType->getId() == ArtifactID::SPELL_SCROLL)
|
||||
if(art->getId() == ArtifactID::SPELL_SCROLL)
|
||||
return 1500;
|
||||
|
||||
auto statsValue =
|
||||
@ -267,8 +262,10 @@ static uint64_t evaluateArtifactArmyValue(const CArtifactInstance * art)
|
||||
|
||||
auto classValue = 0;
|
||||
|
||||
switch(art->artType->aClass)
|
||||
switch(art->aClass)
|
||||
{
|
||||
case CArtifact::EartClass::ART_TREASURE:
|
||||
//FALL_THROUGH
|
||||
case CArtifact::EartClass::ART_MINOR:
|
||||
classValue = 1000;
|
||||
break;
|
||||
@ -302,22 +299,15 @@ uint64_t RewardEvaluator::getArmyReward(
|
||||
{
|
||||
case Obj::HILL_FORT:
|
||||
return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue;
|
||||
case Obj::CREATURE_BANK:
|
||||
return getCreatureBankArmyReward(target, hero);
|
||||
case Obj::CREATURE_GENERATOR1:
|
||||
case Obj::CREATURE_GENERATOR2:
|
||||
case Obj::CREATURE_GENERATOR3:
|
||||
case Obj::CREATURE_GENERATOR4:
|
||||
return getDwellingArmyValue(ai->cb.get(), target, checkGold);
|
||||
case Obj::CRYPT:
|
||||
case Obj::SHIPWRECK:
|
||||
case Obj::SHIPWRECK_SURVIVOR:
|
||||
case Obj::WARRIORS_TOMB:
|
||||
return 1000;
|
||||
case Obj::SPELL_SCROLL:
|
||||
//FALL_THROUGH
|
||||
case Obj::ARTIFACT:
|
||||
return evaluateArtifactArmyValue(dynamic_cast<const CGArtifact *>(target)->storedArtifact);
|
||||
case Obj::DRAGON_UTOPIA:
|
||||
return 10000;
|
||||
return evaluateArtifactArmyValue(dynamic_cast<const CGArtifact *>(target)->storedArtifact->getType());
|
||||
case Obj::HERO:
|
||||
return relations == PlayerRelations::ENEMIES
|
||||
? enemyArmyEliminationRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->getArmyStrength()
|
||||
@ -328,8 +318,46 @@ uint64_t RewardEvaluator::getArmyReward(
|
||||
case Obj::MAGIC_SPRING:
|
||||
return getManaRecoveryArmyReward(hero);
|
||||
default:
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
auto rewardable = dynamic_cast<const Rewardable::Interface *>(target);
|
||||
|
||||
if(rewardable)
|
||||
{
|
||||
auto totalValue = 0;
|
||||
|
||||
for(int index : rewardable->getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT))
|
||||
{
|
||||
auto & info = rewardable->configuration.info[index];
|
||||
|
||||
auto rewardValue = 0;
|
||||
|
||||
if(!info.reward.artifacts.empty())
|
||||
{
|
||||
for(auto artID : info.reward.artifacts)
|
||||
{
|
||||
const auto * art = dynamic_cast<const CArtifact *>(VLC->artifacts()->getById(artID));
|
||||
|
||||
rewardValue += evaluateArtifactArmyValue(art);
|
||||
}
|
||||
}
|
||||
|
||||
if(!info.reward.creatures.empty())
|
||||
{
|
||||
for(const auto & stackInfo : info.reward.creatures)
|
||||
{
|
||||
rewardValue += stackInfo.getType()->getAIValue() * stackInfo.getCount();
|
||||
}
|
||||
}
|
||||
|
||||
totalValue += rewardValue > 0 ? rewardValue / (info.reward.artifacts.size() + info.reward.creatures.size()) : 0;
|
||||
}
|
||||
|
||||
return totalValue;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t RewardEvaluator::getArmyGrowth(
|
||||
@ -468,12 +496,29 @@ uint64_t RewardEvaluator::townArmyGrowth(const CGTownInstance * town) const
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t RewardEvaluator::getManaRecoveryArmyReward(const CGHeroInstance * hero) const
|
||||
float RewardEvaluator::getManaRecoveryArmyReward(const CGHeroInstance * hero) const
|
||||
{
|
||||
return ai->heroManager->getMagicStrength(hero) * 10000 * (1.0f - std::sqrt(static_cast<float>(hero->mana) / hero->manaLimit()));
|
||||
}
|
||||
|
||||
float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) const
|
||||
float RewardEvaluator::getResourceRequirementStrength(const TResources & res) const
|
||||
{
|
||||
float sum = 0.0f;
|
||||
|
||||
for(TResources::nziterator it(res); it.valid(); it++)
|
||||
{
|
||||
//Evaluate resources used for construction. Gold is evaluated separately.
|
||||
if(it->resType != EGameResID::GOLD)
|
||||
{
|
||||
sum += 0.1f * it->resVal * getResourceRequirementStrength(it->resType)
|
||||
+ 0.05f * it->resVal * getTotalResourceRequirementStrength(it->resType);
|
||||
}
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target, const CGHeroInstance * hero) const
|
||||
{
|
||||
if(!target)
|
||||
return 0;
|
||||
@ -491,24 +536,10 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
|
||||
case Obj::RESOURCE:
|
||||
{
|
||||
auto resource = dynamic_cast<const CGResource *>(target);
|
||||
return resource->resourceID() == EGameResID::GOLD
|
||||
? 0
|
||||
: 0.2f * getTotalResourceRequirementStrength(resource->resourceID()) + 0.4f * getResourceRequirementStrength(resource->resourceID());
|
||||
}
|
||||
|
||||
case Obj::CREATURE_BANK:
|
||||
{
|
||||
auto resourceReward = getCreatureBankResources(target, nullptr);
|
||||
float sum = 0.0f;
|
||||
for (TResources::nziterator it (resourceReward); it.valid(); it++)
|
||||
{
|
||||
//Evaluate resources used for construction. Gold is evaluated separately.
|
||||
if (it->resType != EGameResID::GOLD)
|
||||
{
|
||||
sum += 0.1f * it->resVal * getResourceRequirementStrength(it->resType);
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
TResources res;
|
||||
res[resource->resourceID()] = resource->amount;
|
||||
|
||||
return getResourceRequirementStrength(res);
|
||||
}
|
||||
|
||||
case Obj::TOWN:
|
||||
@ -546,6 +577,70 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
|
||||
case Obj::KEYMASTER:
|
||||
return 0.6f;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
auto rewardable = dynamic_cast<const Rewardable::Interface *>(target);
|
||||
|
||||
if(rewardable && hero)
|
||||
{
|
||||
auto resourceReward = 0.0f;
|
||||
|
||||
for(int index : rewardable->getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT))
|
||||
{
|
||||
resourceReward += getResourceRequirementStrength(rewardable->configuration.info[index].reward.resources);
|
||||
}
|
||||
|
||||
return resourceReward;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
float RewardEvaluator::getConquestValue(const CGObjectInstance* target) const
|
||||
{
|
||||
if (!target)
|
||||
return 0;
|
||||
if (target->getOwner() == ai->playerID)
|
||||
return 0;
|
||||
switch (target->ID)
|
||||
{
|
||||
case Obj::TOWN:
|
||||
{
|
||||
if (ai->buildAnalyzer->getDevelopmentInfo().empty())
|
||||
return 10.0f;
|
||||
|
||||
auto town = dynamic_cast<const CGTownInstance*>(target);
|
||||
|
||||
if (town->getOwner() == ai->playerID)
|
||||
{
|
||||
auto armyIncome = townArmyGrowth(town);
|
||||
auto dailyIncome = town->dailyIncome()[EGameResID::GOLD];
|
||||
|
||||
return std::min(1.0f, std::sqrt(armyIncome / 40000.0f)) + std::min(0.3f, dailyIncome / 10000.0f);
|
||||
}
|
||||
|
||||
auto fortLevel = town->fortLevel();
|
||||
auto booster = 1.0f;
|
||||
|
||||
if (town->hasCapitol())
|
||||
return booster * 1.5;
|
||||
|
||||
if (fortLevel < CGTownInstance::CITADEL)
|
||||
return booster * (town->hasFort() ? 1.0 : 0.8);
|
||||
else
|
||||
return booster * (fortLevel == CGTownInstance::CASTLE ? 1.4 : 1.2);
|
||||
}
|
||||
|
||||
case Obj::HERO:
|
||||
return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
|
||||
? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance*>(target))
|
||||
: 0;
|
||||
|
||||
case Obj::KEYMASTER:
|
||||
return 0.6f;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@ -593,11 +688,11 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
|
||||
case Obj::ARENA:
|
||||
return 2;
|
||||
case Obj::SHRINE_OF_MAGIC_INCANTATION:
|
||||
return 0.2f;
|
||||
return 0.25f;
|
||||
case Obj::SHRINE_OF_MAGIC_GESTURE:
|
||||
return 0.3f;
|
||||
return 1.0f;
|
||||
case Obj::SHRINE_OF_MAGIC_THOUGHT:
|
||||
return 0.5f;
|
||||
return 2.0f;
|
||||
case Obj::LIBRARY_OF_ENLIGHTENMENT:
|
||||
return 8;
|
||||
case Obj::WITCH_HUT:
|
||||
@ -605,15 +700,55 @@ 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
|
||||
: 0;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
auto rewardable = dynamic_cast<const Rewardable::Interface *>(target);
|
||||
|
||||
if(rewardable)
|
||||
{
|
||||
auto totalValue = 0.0f;
|
||||
|
||||
for(int index : rewardable->getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT))
|
||||
{
|
||||
auto & info = rewardable->configuration.info[index];
|
||||
|
||||
auto rewardValue = 0.0f;
|
||||
|
||||
if(!info.reward.spells.empty())
|
||||
{
|
||||
for(auto spellID : info.reward.spells)
|
||||
{
|
||||
const spells::Spell * spell = VLC->spells()->getById(spellID);
|
||||
|
||||
if(hero->canLearnSpell(spell) && !hero->spellbookContainsSpell(spellID))
|
||||
{
|
||||
rewardValue += std::sqrt(spell->getLevel()) / 4.0f;
|
||||
}
|
||||
}
|
||||
|
||||
totalValue += rewardValue / info.reward.spells.size();
|
||||
}
|
||||
|
||||
if(!info.reward.primary.empty())
|
||||
{
|
||||
for(auto value : info.reward.primary)
|
||||
{
|
||||
totalValue += value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return totalValue;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const HitMapInfo & RewardEvaluator::getEnemyHeroDanger(const int3 & tile, uint8_t turn) const
|
||||
@ -635,7 +770,7 @@ int32_t getArmyCost(const CArmedInstance * army)
|
||||
|
||||
for(auto stack : army->Slots())
|
||||
{
|
||||
value += stack.second->getCreatureID().toCreature()->getRecruitCost(EGameResID::GOLD) * stack.second->count;
|
||||
value += stack.second->getCreatureID().toCreature()->getFullRecruitCost().marketValue() * stack.second->count;
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -671,22 +806,6 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
|
||||
auto * mine = dynamic_cast<const CGMine*>(target);
|
||||
return dailyIncomeMultiplier * (mine->producedResource == GameResID::GOLD ? 1000 : 75);
|
||||
}
|
||||
case Obj::MYSTICAL_GARDEN:
|
||||
case Obj::WINDMILL:
|
||||
return 100;
|
||||
case Obj::CAMPFIRE:
|
||||
return 800;
|
||||
case Obj::WAGON:
|
||||
return 100;
|
||||
case Obj::CREATURE_BANK:
|
||||
return getResourcesGoldReward(getCreatureBankResources(target, hero));
|
||||
case Obj::CRYPT:
|
||||
case Obj::DERELICT_SHIP:
|
||||
return 3000;
|
||||
case Obj::DRAGON_UTOPIA:
|
||||
return 10000;
|
||||
case Obj::SEA_CHEST:
|
||||
return 1500;
|
||||
case Obj::PANDORAS_BOX:
|
||||
return 2500;
|
||||
case Obj::PRISON:
|
||||
@ -697,8 +816,26 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
|
||||
? heroEliminationBonus + enemyArmyEliminationGoldRewardRatio * getArmyCost(dynamic_cast<const CGHeroInstance *>(target))
|
||||
: 0;
|
||||
default:
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
auto rewardable = dynamic_cast<const Rewardable::Interface *>(target);
|
||||
|
||||
if(rewardable)
|
||||
{
|
||||
auto goldReward = 0;
|
||||
|
||||
for(int index : rewardable->getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT))
|
||||
{
|
||||
auto & info = rewardable->configuration.info[index];
|
||||
|
||||
goldReward += getResourcesGoldReward(info.reward.resources);
|
||||
}
|
||||
|
||||
return goldReward;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
class HeroExchangeEvaluator : public IEvaluationContextBuilder
|
||||
@ -714,7 +851,9 @@ public:
|
||||
uint64_t armyStrength = heroExchange.getReinforcementArmyStrength(evaluationContext.evaluator.ai);
|
||||
|
||||
evaluationContext.addNonCriticalStrategicalValue(2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength());
|
||||
evaluationContext.conquestValue += 2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength();
|
||||
evaluationContext.heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroExchange.hero);
|
||||
evaluationContext.isExchange = true;
|
||||
}
|
||||
};
|
||||
|
||||
@ -732,6 +871,7 @@ public:
|
||||
|
||||
evaluationContext.armyReward += upgradeValue;
|
||||
evaluationContext.addNonCriticalStrategicalValue(upgradeValue / (float)armyUpgrade.hero->getArmyStrength());
|
||||
evaluationContext.isArmyUpgrade = true;
|
||||
}
|
||||
};
|
||||
|
||||
@ -746,22 +886,46 @@ public:
|
||||
int tilesDiscovered = task->value;
|
||||
|
||||
evaluationContext.addNonCriticalStrategicalValue(0.03f * tilesDiscovered);
|
||||
for (auto obj : evaluationContext.evaluator.ai->cb->getVisitableObjs(task->tile))
|
||||
{
|
||||
switch (obj->ID.num)
|
||||
{
|
||||
case Obj::MONOLITH_ONE_WAY_ENTRANCE:
|
||||
case Obj::MONOLITH_TWO_WAY:
|
||||
case Obj::SUBTERRANEAN_GATE:
|
||||
evaluationContext.explorePriority = 1;
|
||||
break;
|
||||
case Obj::REDWOOD_OBSERVATORY:
|
||||
case Obj::PILLAR_OF_FIRE:
|
||||
evaluationContext.explorePriority = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(evaluationContext.evaluator.ai->cb->getTile(task->tile)->roadType != RoadId::NO_ROAD)
|
||||
evaluationContext.explorePriority = 1;
|
||||
if (evaluationContext.explorePriority == 0)
|
||||
evaluationContext.explorePriority = 3;
|
||||
}
|
||||
};
|
||||
|
||||
class StayAtTownManaRecoveryEvaluator : public IEvaluationContextBuilder
|
||||
{
|
||||
public:
|
||||
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)
|
||||
if (task->goalType != Goals::STAY_AT_TOWN)
|
||||
return;
|
||||
|
||||
Goals::StayAtTown & stayAtTown = dynamic_cast<Goals::StayAtTown &>(*task);
|
||||
Goals::StayAtTown& stayAtTown = dynamic_cast<Goals::StayAtTown&>(*task);
|
||||
|
||||
evaluationContext.armyReward += evaluationContext.evaluator.getManaRecoveryArmyReward(stayAtTown.getHero());
|
||||
evaluationContext.movementCostByRole[evaluationContext.heroRole] += stayAtTown.getMovementWasted();
|
||||
evaluationContext.movementCost += stayAtTown.getMovementWasted();
|
||||
if (evaluationContext.armyReward == 0)
|
||||
evaluationContext.isDefend = true;
|
||||
else
|
||||
{
|
||||
evaluationContext.movementCost += stayAtTown.getMovementWasted();
|
||||
evaluationContext.movementCostByRole[evaluationContext.heroRole] += stayAtTown.getMovementWasted();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -772,15 +936,8 @@ void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uin
|
||||
if(enemyDanger.danger)
|
||||
{
|
||||
auto dangerRatio = enemyDanger.danger / (double)ourStrength;
|
||||
auto enemyHero = evaluationContext.evaluator.ai->cb->getObj(enemyDanger.hero.hid, false);
|
||||
bool isAI = enemyHero && isAnotherAi(enemyHero, *evaluationContext.evaluator.ai->cb);
|
||||
|
||||
if(isAI)
|
||||
{
|
||||
dangerRatio *= 1.5; // lets make AI bit more afraid of other AI.
|
||||
}
|
||||
|
||||
vstd::amax(evaluationContext.enemyHeroDangerRatio, dangerRatio);
|
||||
vstd::amax(evaluationContext.threat, enemyDanger.threat);
|
||||
}
|
||||
}
|
||||
|
||||
@ -824,6 +981,10 @@ public:
|
||||
else
|
||||
evaluationContext.addNonCriticalStrategicalValue(1.7f * multiplier * strategicalValue);
|
||||
|
||||
evaluationContext.defenseValue = town->fortLevel();
|
||||
evaluationContext.isDefend = true;
|
||||
evaluationContext.threatTurns = treat.turn;
|
||||
|
||||
vstd::amax(evaluationContext.danger, defendTown.getTreat().danger);
|
||||
addTileDanger(evaluationContext, town->visitablePos(), defendTown.getTurn(), defendTown.getDefenceStrength());
|
||||
}
|
||||
@ -845,6 +1006,9 @@ public:
|
||||
Goals::ExecuteHeroChain & chain = dynamic_cast<Goals::ExecuteHeroChain &>(*task);
|
||||
const AIPath & path = chain.getPath();
|
||||
|
||||
if (vstd::isAlmostZero(path.movementCost()))
|
||||
return;
|
||||
|
||||
vstd::amax(evaluationContext.danger, path.getTotalDanger());
|
||||
evaluationContext.movementCost += path.movementCost();
|
||||
evaluationContext.closestWayRatio = chain.closestWayRatio;
|
||||
@ -854,14 +1018,24 @@ public:
|
||||
for(auto & node : path.nodes)
|
||||
{
|
||||
vstd::amax(costsPerHero[node.targetHero], node.cost);
|
||||
if (node.layer == EPathfindingLayer::SAIL)
|
||||
evaluationContext.involvesSailing = true;
|
||||
}
|
||||
|
||||
float highestCostForSingleHero = 0;
|
||||
for(auto pair : costsPerHero)
|
||||
{
|
||||
auto role = evaluationContext.evaluator.ai->heroManager->getHeroRole(pair.first);
|
||||
|
||||
evaluationContext.movementCostByRole[role] += pair.second;
|
||||
if (pair.second > highestCostForSingleHero)
|
||||
highestCostForSingleHero = pair.second;
|
||||
}
|
||||
if (highestCostForSingleHero > 1 && costsPerHero.size() > 1)
|
||||
{
|
||||
//Chains that involve more than 1 hero doing something for more than a turn are too expensive in my book. They often involved heroes doing nothing just standing there waiting to fulfill their part of the chain.
|
||||
return;
|
||||
}
|
||||
evaluationContext.movementCost *= costsPerHero.size(); //further deincentivise chaining as it often involves bringing back the army afterwards
|
||||
|
||||
auto hero = task->hero;
|
||||
bool checkGold = evaluationContext.danger == 0;
|
||||
@ -880,10 +1054,18 @@ public:
|
||||
evaluationContext.armyGrowth += evaluationContext.evaluator.getArmyGrowth(target, hero, army);
|
||||
evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, heroRole);
|
||||
evaluationContext.addNonCriticalStrategicalValue(evaluationContext.evaluator.getStrategicalValue(target));
|
||||
evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target);
|
||||
if (target->ID == Obj::HERO)
|
||||
evaluationContext.isHero = true;
|
||||
if (target->getOwner().isValidPlayer() && ai->cb->getPlayerRelations(ai->playerID, target->getOwner()) == PlayerRelations::ENEMIES)
|
||||
evaluationContext.isEnemy = true;
|
||||
evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army);
|
||||
if(evaluationContext.danger > 0)
|
||||
evaluationContext.skillReward += (float)evaluationContext.danger / (float)hero->getArmyStrength();
|
||||
}
|
||||
evaluationContext.armyInvolvement += army->getArmyCost();
|
||||
|
||||
vstd::amax(evaluationContext.armyLossPersentage, path.getTotalArmyLoss() / (double)path.getHeroStrength());
|
||||
vstd::amax(evaluationContext.armyLossPersentage, (float)path.getTotalArmyLoss() / (float)army->getArmyStrength());
|
||||
addTileDanger(evaluationContext, path.targetTile(), path.turn(), path.getHeroStrength());
|
||||
vstd::amax(evaluationContext.turn, path.turn());
|
||||
}
|
||||
@ -924,6 +1106,7 @@ public:
|
||||
evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold) / boost;
|
||||
evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, role) / boost;
|
||||
evaluationContext.addNonCriticalStrategicalValue(evaluationContext.evaluator.getStrategicalValue(target) / boost);
|
||||
evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target);
|
||||
evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army) / boost;
|
||||
evaluationContext.movementCostByRole[role] += objInfo.second.movementCost / boost;
|
||||
evaluationContext.movementCost += objInfo.second.movementCost / boost;
|
||||
@ -949,6 +1132,14 @@ public:
|
||||
Goals::ExchangeSwapTownHeroes & swapCommand = dynamic_cast<Goals::ExchangeSwapTownHeroes &>(*task);
|
||||
const CGHeroInstance * garrisonHero = swapCommand.getGarrisonHero();
|
||||
|
||||
logAi->trace("buildEvaluationContext ExchangeSwapTownHeroesContextBuilder %s affected objects: %d", swapCommand.toString(), swapCommand.getAffectedObjects().size());
|
||||
for (auto obj : swapCommand.getAffectedObjects())
|
||||
{
|
||||
logAi->trace("affected object: %s", evaluationContext.evaluator.ai->cb->getObj(obj)->getObjectName());
|
||||
}
|
||||
if (garrisonHero)
|
||||
logAi->debug("with %s and %d", garrisonHero->getNameTranslated(), int(swapCommand.getLockingReason()));
|
||||
|
||||
if(garrisonHero && swapCommand.getLockingReason() == HeroLockedReason::DEFENCE)
|
||||
{
|
||||
auto defenderRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(garrisonHero);
|
||||
@ -957,6 +1148,9 @@ public:
|
||||
evaluationContext.movementCost += mpLeft;
|
||||
evaluationContext.movementCostByRole[defenderRole] += mpLeft;
|
||||
evaluationContext.heroRole = defenderRole;
|
||||
evaluationContext.isDefend = true;
|
||||
evaluationContext.armyInvolvement = garrisonHero->getArmyStrength();
|
||||
logAi->debug("evaluationContext.isDefend: %d", evaluationContext.isDefend);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1000,8 +1194,14 @@ public:
|
||||
evaluationContext.goldReward += 7 * bi.dailyIncome[EGameResID::GOLD] / 2; // 7 day income but half we already have
|
||||
evaluationContext.heroRole = HeroRole::MAIN;
|
||||
evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount;
|
||||
evaluationContext.goldCost += bi.buildCostWithPrerequisits[EGameResID::GOLD];
|
||||
int32_t cost = bi.buildCost[EGameResID::GOLD];
|
||||
evaluationContext.goldCost += cost;
|
||||
evaluationContext.closestWayRatio = 1;
|
||||
evaluationContext.buildingCost += bi.buildCostWithPrerequisites;
|
||||
if (bi.id == BuildingID::MARKETPLACE || bi.dailyIncome[EGameResID::WOOD] > 0)
|
||||
evaluationContext.isTradeBuilding = true;
|
||||
|
||||
logAi->trace("Building costs for %s : %s MarketValue: %d",bi.toString(), evaluationContext.buildingCost.toString(), evaluationContext.buildingCost.marketValue());
|
||||
|
||||
if(bi.creatureID != CreatureID::NONE)
|
||||
{
|
||||
@ -1028,7 +1228,18 @@ public:
|
||||
else if(bi.id >= BuildingID::MAGES_GUILD_1 && bi.id <= BuildingID::MAGES_GUILD_5)
|
||||
{
|
||||
evaluationContext.skillReward += 2 * (bi.id - BuildingID::MAGES_GUILD_1);
|
||||
for (auto hero : evaluationContext.evaluator.ai->cb->getHeroesInfo())
|
||||
{
|
||||
evaluationContext.armyInvolvement += hero->getArmyCost();
|
||||
}
|
||||
}
|
||||
int sameTownBonus = 0;
|
||||
for (auto town : evaluationContext.evaluator.ai->cb->getTownsInfo())
|
||||
{
|
||||
if (buildThis.town->getFaction() == town->getFaction())
|
||||
sameTownBonus += town->getTownLevel();
|
||||
}
|
||||
evaluationContext.armyReward *= sameTownBonus;
|
||||
|
||||
if(evaluationContext.goldReward)
|
||||
{
|
||||
@ -1048,7 +1259,7 @@ public:
|
||||
|
||||
uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const
|
||||
{
|
||||
if(ai->buildAnalyzer->hasAnyBuilding(town->getFaction(), bi.id))
|
||||
if(ai->buildAnalyzer->hasAnyBuilding(town->getFactionID(), bi.id))
|
||||
return 0;
|
||||
|
||||
auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);
|
||||
@ -1090,6 +1301,7 @@ EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal
|
||||
for(auto subgoal : parts)
|
||||
{
|
||||
context.goldCost += subgoal->goldCost;
|
||||
context.buildingCost += subgoal->buildingCost;
|
||||
|
||||
for(auto builder : evaluationContextBuilders)
|
||||
{
|
||||
@ -1100,7 +1312,7 @@ EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal
|
||||
return context;
|
||||
}
|
||||
|
||||
float PriorityEvaluator::evaluate(Goals::TSubgoal task)
|
||||
float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
||||
{
|
||||
auto evaluationContext = buildEvaluationContext(task);
|
||||
|
||||
@ -1113,47 +1325,284 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
|
||||
|
||||
double result = 0;
|
||||
|
||||
try
|
||||
if (ai->settings->isUseFuzzy())
|
||||
{
|
||||
armyLossPersentageVariable->setValue(evaluationContext.armyLossPersentage);
|
||||
heroRoleVariable->setValue(evaluationContext.heroRole);
|
||||
mainTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::MAIN]);
|
||||
scoutTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::SCOUT]);
|
||||
goldRewardVariable->setValue(goldRewardPerTurn);
|
||||
armyRewardVariable->setValue(evaluationContext.armyReward);
|
||||
armyGrowthVariable->setValue(evaluationContext.armyGrowth);
|
||||
skillRewardVariable->setValue(evaluationContext.skillReward);
|
||||
dangerVariable->setValue(evaluationContext.danger);
|
||||
rewardTypeVariable->setValue(rewardType);
|
||||
closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio);
|
||||
strategicalValueVariable->setValue(evaluationContext.strategicalValue);
|
||||
goldPressureVariable->setValue(ai->buildAnalyzer->getGoldPressure());
|
||||
goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->getFreeResources()[EGameResID::GOLD] + (float)ai->buildAnalyzer->getDailyIncome()[EGameResID::GOLD] + 1.0f));
|
||||
turnVariable->setValue(evaluationContext.turn);
|
||||
fearVariable->setValue(evaluationContext.enemyHeroDangerRatio);
|
||||
float fuzzyResult = 0;
|
||||
try
|
||||
{
|
||||
armyLossPersentageVariable->setValue(evaluationContext.armyLossPersentage);
|
||||
heroRoleVariable->setValue(evaluationContext.heroRole);
|
||||
mainTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::MAIN]);
|
||||
scoutTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::SCOUT]);
|
||||
goldRewardVariable->setValue(goldRewardPerTurn);
|
||||
armyRewardVariable->setValue(evaluationContext.armyReward);
|
||||
armyGrowthVariable->setValue(evaluationContext.armyGrowth);
|
||||
skillRewardVariable->setValue(evaluationContext.skillReward);
|
||||
dangerVariable->setValue(evaluationContext.danger);
|
||||
rewardTypeVariable->setValue(rewardType);
|
||||
closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio);
|
||||
strategicalValueVariable->setValue(evaluationContext.strategicalValue);
|
||||
goldPressureVariable->setValue(ai->buildAnalyzer->getGoldPressure());
|
||||
goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->getFreeResources()[EGameResID::GOLD] + (float)ai->buildAnalyzer->getDailyIncome()[EGameResID::GOLD] + 1.0f));
|
||||
turnVariable->setValue(evaluationContext.turn);
|
||||
fearVariable->setValue(evaluationContext.enemyHeroDangerRatio);
|
||||
|
||||
engine->process();
|
||||
engine->process();
|
||||
|
||||
result = value->getValue();
|
||||
fuzzyResult = value->getValue();
|
||||
}
|
||||
catch (fl::Exception& fe)
|
||||
{
|
||||
logAi->error("evaluate VisitTile: %s", fe.getWhat());
|
||||
}
|
||||
result = fuzzyResult;
|
||||
}
|
||||
catch(fl::Exception & fe)
|
||||
else
|
||||
{
|
||||
logAi->error("evaluate VisitTile: %s", fe.getWhat());
|
||||
float score = 0;
|
||||
const bool amIInDanger = ai->cb->getTownsInfo().empty() || (evaluationContext.isDefend && evaluationContext.threatTurns == 0);
|
||||
const float maxWillingToLose = amIInDanger ? 1 : ai->settings->getMaxArmyLossTarget();
|
||||
|
||||
bool arriveNextWeek = false;
|
||||
if (ai->cb->getDate(Date::DAY_OF_WEEK) + evaluationContext.turn > 7 && priorityTier < PriorityTier::FAR_KILL)
|
||||
arriveNextWeek = true;
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, army-involvement: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, explorePriority: %d isDefend: %d isEnemy: %d arriveNextWeek: %d",
|
||||
priorityTier,
|
||||
task->toString(),
|
||||
evaluationContext.armyLossPersentage,
|
||||
(int)evaluationContext.turn,
|
||||
evaluationContext.movementCostByRole[HeroRole::MAIN],
|
||||
evaluationContext.movementCostByRole[HeroRole::SCOUT],
|
||||
evaluationContext.armyInvolvement,
|
||||
goldRewardPerTurn,
|
||||
evaluationContext.goldCost,
|
||||
evaluationContext.armyReward,
|
||||
evaluationContext.armyGrowth,
|
||||
evaluationContext.skillReward,
|
||||
evaluationContext.danger,
|
||||
evaluationContext.threatTurns,
|
||||
evaluationContext.threat,
|
||||
evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout",
|
||||
evaluationContext.strategicalValue,
|
||||
evaluationContext.conquestValue,
|
||||
evaluationContext.closestWayRatio,
|
||||
evaluationContext.enemyHeroDangerRatio,
|
||||
evaluationContext.explorePriority,
|
||||
evaluationContext.isDefend,
|
||||
evaluationContext.isEnemy,
|
||||
arriveNextWeek);
|
||||
#endif
|
||||
|
||||
switch (priorityTier)
|
||||
{
|
||||
case PriorityTier::INSTAKILL: //Take towns / kill heroes in immediate reach
|
||||
{
|
||||
if (evaluationContext.turn > 0)
|
||||
return 0;
|
||||
if (evaluationContext.movementCost >= 1)
|
||||
return 0;
|
||||
if(evaluationContext.conquestValue > 0)
|
||||
score = evaluationContext.armyInvolvement;
|
||||
if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
|
||||
return 0;
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
if (evaluationContext.movementCost > 0)
|
||||
score /= evaluationContext.movementCost;
|
||||
break;
|
||||
}
|
||||
case PriorityTier::INSTADEFEND: //Defend immediately threatened towns
|
||||
{
|
||||
if (evaluationContext.isDefend && evaluationContext.threatTurns == 0 && evaluationContext.turn == 0)
|
||||
score = evaluationContext.armyInvolvement;
|
||||
if (evaluationContext.isEnemy && maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
case PriorityTier::KILL: //Take towns / kill heroes that are further away
|
||||
//FALL_THROUGH
|
||||
case PriorityTier::FAR_KILL:
|
||||
{
|
||||
if (evaluationContext.turn > 0 && evaluationContext.isHero)
|
||||
return 0;
|
||||
if (arriveNextWeek && evaluationContext.isEnemy)
|
||||
return 0;
|
||||
if (evaluationContext.conquestValue > 0)
|
||||
score = evaluationContext.armyInvolvement;
|
||||
if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
|
||||
return 0;
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
score *= evaluationContext.closestWayRatio;
|
||||
if (evaluationContext.movementCost > 0)
|
||||
score /= evaluationContext.movementCost;
|
||||
break;
|
||||
}
|
||||
case PriorityTier::UPGRADE:
|
||||
{
|
||||
if (!evaluationContext.isArmyUpgrade)
|
||||
return 0;
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1)
|
||||
return 0;
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
if (vstd::isAlmostZero(evaluationContext.armyLossPersentage) && evaluationContext.closestWayRatio < 1.0)
|
||||
return 0;
|
||||
score = 1000;
|
||||
if (evaluationContext.movementCost > 0)
|
||||
score /= evaluationContext.movementCost;
|
||||
break;
|
||||
}
|
||||
case PriorityTier::HIGH_PRIO_EXPLORE:
|
||||
{
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1)
|
||||
return 0;
|
||||
if (evaluationContext.explorePriority != 1)
|
||||
return 0;
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
if (vstd::isAlmostZero(evaluationContext.armyLossPersentage) && evaluationContext.closestWayRatio < 1.0)
|
||||
return 0;
|
||||
score = 1000;
|
||||
if (evaluationContext.movementCost > 0)
|
||||
score /= evaluationContext.movementCost;
|
||||
break;
|
||||
}
|
||||
case PriorityTier::HUNTER_GATHER: //Collect guarded stuff
|
||||
//FALL_THROUGH
|
||||
case PriorityTier::FAR_HUNTER_GATHER:
|
||||
{
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend)
|
||||
return 0;
|
||||
if (evaluationContext.buildingCost.marketValue() > 0)
|
||||
return 0;
|
||||
if (evaluationContext.isDefend && (evaluationContext.enemyHeroDangerRatio < 1 || evaluationContext.threatTurns > 0 || evaluationContext.turn > 0))
|
||||
return 0;
|
||||
if (evaluationContext.explorePriority == 3)
|
||||
return 0;
|
||||
if (evaluationContext.isArmyUpgrade)
|
||||
return 0;
|
||||
if ((evaluationContext.enemyHeroDangerRatio > 0 && arriveNextWeek) || evaluationContext.enemyHeroDangerRatio > 1)
|
||||
return 0;
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
if (vstd::isAlmostZero(evaluationContext.armyLossPersentage) && evaluationContext.closestWayRatio < 1.0)
|
||||
return 0;
|
||||
score += evaluationContext.strategicalValue * 1000;
|
||||
score += evaluationContext.goldReward;
|
||||
score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05;
|
||||
score += evaluationContext.armyReward;
|
||||
score += evaluationContext.armyGrowth;
|
||||
score -= evaluationContext.goldCost;
|
||||
score -= evaluationContext.armyInvolvement * evaluationContext.armyLossPersentage;
|
||||
if (score > 0)
|
||||
{
|
||||
score = 1000;
|
||||
if (evaluationContext.movementCost > 0)
|
||||
score /= evaluationContext.movementCost;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PriorityTier::LOW_PRIO_EXPLORE:
|
||||
{
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1)
|
||||
return 0;
|
||||
if (evaluationContext.explorePriority != 3)
|
||||
return 0;
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
if (evaluationContext.closestWayRatio < 1.0)
|
||||
return 0;
|
||||
score = 1000;
|
||||
if (evaluationContext.movementCost > 0)
|
||||
score /= evaluationContext.movementCost;
|
||||
break;
|
||||
}
|
||||
case PriorityTier::DEFEND: //Defend whatever if nothing else is to do
|
||||
{
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.isExchange)
|
||||
return 0;
|
||||
if (evaluationContext.isDefend || evaluationContext.isArmyUpgrade)
|
||||
score = evaluationContext.armyInvolvement;
|
||||
score /= (evaluationContext.turn + 1);
|
||||
break;
|
||||
}
|
||||
case PriorityTier::BUILDINGS: //For buildings and buying army
|
||||
{
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
//If we already have locked resources, we don't look at other buildings
|
||||
if (ai->getLockedResources().marketValue() > 0)
|
||||
return 0;
|
||||
score += evaluationContext.conquestValue * 1000;
|
||||
score += evaluationContext.strategicalValue * 1000;
|
||||
score += evaluationContext.goldReward;
|
||||
score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05;
|
||||
score += evaluationContext.armyReward;
|
||||
score += evaluationContext.armyGrowth;
|
||||
if (evaluationContext.buildingCost.marketValue() > 0)
|
||||
{
|
||||
if (!evaluationContext.isTradeBuilding && ai->getFreeResources()[EGameResID::WOOD] - evaluationContext.buildingCost[EGameResID::WOOD] < 5 && ai->buildAnalyzer->getDailyIncome()[EGameResID::WOOD] < 1)
|
||||
{
|
||||
logAi->trace("Should make sure to build market-place instead of %s", task->toString());
|
||||
for (auto town : ai->cb->getTownsInfo())
|
||||
{
|
||||
if (!town->hasBuiltSomeTradeBuilding())
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
score += 1000;
|
||||
auto resourcesAvailable = evaluationContext.evaluator.ai->getFreeResources();
|
||||
auto income = ai->buildAnalyzer->getDailyIncome();
|
||||
if(ai->buildAnalyzer->isGoldPressureHigh())
|
||||
score /= evaluationContext.buildingCost.marketValue();
|
||||
if (!resourcesAvailable.canAfford(evaluationContext.buildingCost))
|
||||
{
|
||||
TResources needed = evaluationContext.buildingCost - resourcesAvailable;
|
||||
needed.positive();
|
||||
int turnsTo = needed.maxPurchasableCount(income);
|
||||
if (turnsTo == INT_MAX)
|
||||
return 0;
|
||||
else
|
||||
score /= turnsTo;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend && vstd::isAlmostZero(evaluationContext.conquestValue))
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
result = score;
|
||||
//TODO: Figure out the root cause for why evaluationContext.closestWayRatio has become -nan(ind).
|
||||
if (std::isnan(result))
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, danger: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, result %f",
|
||||
logAi->trace("priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, army-involvement: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, result %f",
|
||||
priorityTier,
|
||||
task->toString(),
|
||||
evaluationContext.armyLossPersentage,
|
||||
(int)evaluationContext.turn,
|
||||
evaluationContext.movementCostByRole[HeroRole::MAIN],
|
||||
evaluationContext.movementCostByRole[HeroRole::SCOUT],
|
||||
evaluationContext.armyInvolvement,
|
||||
goldRewardPerTurn,
|
||||
evaluationContext.goldCost,
|
||||
evaluationContext.armyReward,
|
||||
evaluationContext.armyGrowth,
|
||||
evaluationContext.skillReward,
|
||||
evaluationContext.danger,
|
||||
evaluationContext.threatTurns,
|
||||
evaluationContext.threat,
|
||||
evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout",
|
||||
evaluationContext.strategicalValue,
|
||||
evaluationContext.conquestValue,
|
||||
evaluationContext.closestWayRatio,
|
||||
evaluationContext.enemyHeroDangerRatio,
|
||||
result);
|
||||
|
@ -39,7 +39,9 @@ public:
|
||||
int getGoldCost(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army) const;
|
||||
float getEnemyHeroStrategicalValue(const CGHeroInstance * enemy) const;
|
||||
float getResourceRequirementStrength(int resType) const;
|
||||
float getStrategicalValue(const CGObjectInstance * target) const;
|
||||
float getResourceRequirementStrength(const TResources & res) const;
|
||||
float getStrategicalValue(const CGObjectInstance * target, const CGHeroInstance * hero = nullptr) const;
|
||||
float getConquestValue(const CGObjectInstance* target) const;
|
||||
float getTotalResourceRequirementStrength(int resType) const;
|
||||
float evaluateWitchHutSkillScore(const CGObjectInstance * hut, const CGHeroInstance * hero, HeroRole role) const;
|
||||
float getSkillReward(const CGObjectInstance * target, const CGHeroInstance * hero, HeroRole role) const;
|
||||
@ -47,7 +49,7 @@ public:
|
||||
uint64_t getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const;
|
||||
const HitMapInfo & getEnemyHeroDanger(const int3 & tile, uint8_t turn) const;
|
||||
uint64_t townArmyGrowth(const CGTownInstance * town) const;
|
||||
uint64_t getManaRecoveryArmyReward(const CGHeroInstance * hero) const;
|
||||
float getManaRecoveryArmyReward(const CGHeroInstance * hero) const;
|
||||
};
|
||||
|
||||
struct DLL_EXPORT EvaluationContext
|
||||
@ -64,10 +66,24 @@ struct DLL_EXPORT EvaluationContext
|
||||
int32_t goldCost;
|
||||
float skillReward;
|
||||
float strategicalValue;
|
||||
float conquestValue;
|
||||
HeroRole heroRole;
|
||||
uint8_t turn;
|
||||
RewardEvaluator evaluator;
|
||||
float enemyHeroDangerRatio;
|
||||
float threat;
|
||||
float armyInvolvement;
|
||||
int defenseValue;
|
||||
bool isDefend;
|
||||
int threatTurns;
|
||||
TResources buildingCost;
|
||||
bool involvesSailing;
|
||||
bool isTradeBuilding;
|
||||
bool isExchange;
|
||||
bool isArmyUpgrade;
|
||||
bool isHero;
|
||||
bool isEnemy;
|
||||
int explorePriority;
|
||||
|
||||
EvaluationContext(const Nullkiller * ai);
|
||||
|
||||
@ -90,7 +106,22 @@ public:
|
||||
~PriorityEvaluator();
|
||||
void initVisitTile();
|
||||
|
||||
float evaluate(Goals::TSubgoal task);
|
||||
float evaluate(Goals::TSubgoal task, int priorityTier = BUILDINGS);
|
||||
|
||||
enum PriorityTier : int32_t
|
||||
{
|
||||
BUILDINGS = 0,
|
||||
INSTAKILL,
|
||||
INSTADEFEND,
|
||||
KILL,
|
||||
UPGRADE,
|
||||
HIGH_PRIO_EXPLORE,
|
||||
HUNTER_GATHER,
|
||||
LOW_PRIO_EXPLORE,
|
||||
FAR_KILL,
|
||||
FAR_HUNTER_GATHER,
|
||||
DEFEND
|
||||
};
|
||||
|
||||
private:
|
||||
const Nullkiller * ai;
|
||||
|
@ -11,6 +11,8 @@
|
||||
#include <limits>
|
||||
|
||||
#include "Settings.h"
|
||||
|
||||
#include "../../../lib/constants/StringConstants.h"
|
||||
#include "../../../lib/mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
|
||||
@ -22,56 +24,42 @@
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
Settings::Settings()
|
||||
Settings::Settings(int difficultyLevel)
|
||||
: maxRoamingHeroes(8),
|
||||
mainHeroTurnDistanceLimit(10),
|
||||
scoutHeroTurnDistanceLimit(5),
|
||||
maxGoldPressure(0.3f),
|
||||
maxGoldPressure(0.3f),
|
||||
retreatThresholdRelative(0.3),
|
||||
retreatThresholdAbsolute(10000),
|
||||
safeAttackRatio(1.1),
|
||||
maxpass(10),
|
||||
pathfinderBucketsCount(1),
|
||||
pathfinderBucketSize(32),
|
||||
allowObjectGraph(true),
|
||||
useTroopsFromGarrisons(false),
|
||||
openMap(true)
|
||||
updateHitmapOnTileReveal(false),
|
||||
openMap(true),
|
||||
useFuzzy(false)
|
||||
{
|
||||
JsonNode node = JsonUtils::assembleFromFiles("config/ai/nkai/nkai-settings");
|
||||
const std::string & difficultyName = GameConstants::DIFFICULTY_NAMES[difficultyLevel];
|
||||
const JsonNode & rootNode = JsonUtils::assembleFromFiles("config/ai/nkai/nkai-settings");
|
||||
const JsonNode & node = rootNode[difficultyName];
|
||||
|
||||
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()["maxGoldPressure"].isNumber())
|
||||
{
|
||||
maxGoldPressure = node.Struct()["maxGoldPressure"].Float();
|
||||
}
|
||||
|
||||
if(!node.Struct()["allowObjectGraph"].isNull())
|
||||
{
|
||||
allowObjectGraph = node.Struct()["allowObjectGraph"].Bool();
|
||||
}
|
||||
|
||||
if(!node.Struct()["openMap"].isNull())
|
||||
{
|
||||
openMap = node.Struct()["openMap"].Bool();
|
||||
}
|
||||
|
||||
if(!node.Struct()["useTroopsFromGarrisons"].isNull())
|
||||
{
|
||||
useTroopsFromGarrisons = node.Struct()["useTroopsFromGarrisons"].Bool();
|
||||
}
|
||||
maxRoamingHeroes = node["maxRoamingHeroes"].Integer();
|
||||
mainHeroTurnDistanceLimit = node["mainHeroTurnDistanceLimit"].Integer();
|
||||
scoutHeroTurnDistanceLimit = node["scoutHeroTurnDistanceLimit"].Integer();
|
||||
maxpass = node["maxpass"].Integer();
|
||||
pathfinderBucketsCount = node["pathfinderBucketsCount"].Integer();
|
||||
pathfinderBucketSize = node["pathfinderBucketSize"].Integer();
|
||||
maxGoldPressure = node["maxGoldPressure"].Float();
|
||||
retreatThresholdRelative = node["retreatThresholdRelative"].Float();
|
||||
retreatThresholdAbsolute = node["retreatThresholdAbsolute"].Float();
|
||||
maxArmyLossTarget = node["maxArmyLossTarget"].Float();
|
||||
safeAttackRatio = node["safeAttackRatio"].Float();
|
||||
allowObjectGraph = node["allowObjectGraph"].Bool();
|
||||
updateHitmapOnTileReveal = node["updateHitmapOnTileReveal"].Bool();
|
||||
openMap = node["openMap"].Bool();
|
||||
useFuzzy = node["useFuzzy"].Bool();
|
||||
useTroopsFromGarrisons = node["useTroopsFromGarrisons"].Bool();
|
||||
}
|
||||
}
|
||||
|
@ -25,21 +25,37 @@ namespace NKAI
|
||||
int mainHeroTurnDistanceLimit;
|
||||
int scoutHeroTurnDistanceLimit;
|
||||
int maxpass;
|
||||
int pathfinderBucketsCount;
|
||||
int pathfinderBucketSize;
|
||||
float maxGoldPressure;
|
||||
float retreatThresholdRelative;
|
||||
float retreatThresholdAbsolute;
|
||||
float safeAttackRatio;
|
||||
float maxArmyLossTarget;
|
||||
bool allowObjectGraph;
|
||||
bool useTroopsFromGarrisons;
|
||||
bool updateHitmapOnTileReveal;
|
||||
bool openMap;
|
||||
bool useFuzzy;
|
||||
|
||||
public:
|
||||
Settings();
|
||||
explicit Settings(int difficultyLevel);
|
||||
|
||||
int getMaxPass() const { return maxpass; }
|
||||
float getMaxGoldPressure() const { return maxGoldPressure; }
|
||||
float getRetreatThresholdRelative() const { return retreatThresholdRelative; }
|
||||
float getRetreatThresholdAbsolute() const { return retreatThresholdAbsolute; }
|
||||
float getSafeAttackRatio() const { return safeAttackRatio; }
|
||||
float getMaxArmyLossTarget() const { return maxArmyLossTarget; }
|
||||
int getMaxRoamingHeroes() const { return maxRoamingHeroes; }
|
||||
int getMainHeroTurnDistanceLimit() const { return mainHeroTurnDistanceLimit; }
|
||||
int getScoutHeroTurnDistanceLimit() const { return scoutHeroTurnDistanceLimit; }
|
||||
int getPathfinderBucketsCount() const { return pathfinderBucketsCount; }
|
||||
int getPathfinderBucketSize() const { return pathfinderBucketSize; }
|
||||
bool isObjectGraphAllowed() const { return allowObjectGraph; }
|
||||
bool isGarrisonTroopsUsageAllowed() const { return useTroopsFromGarrisons; }
|
||||
bool isUpdateHitmapOnTileReveal() const { return updateHitmapOnTileReveal; }
|
||||
bool isOpenMap() const { return openMap; }
|
||||
bool isUseFuzzy() const { return useFuzzy; }
|
||||
};
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ namespace Goals
|
||||
bool isAbstract; SETTER(bool, isAbstract)
|
||||
int value; SETTER(int, value)
|
||||
ui64 goldCost; SETTER(ui64, goldCost)
|
||||
TResources buildingCost; SETTER(TResources, buildingCost)
|
||||
int resID; SETTER(int, resID)
|
||||
int objid; SETTER(int, objid)
|
||||
int aid; SETTER(int, aid)
|
||||
|
@ -53,6 +53,9 @@ void AdventureSpellCast::accept(AIGateway * ai)
|
||||
throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated());
|
||||
}
|
||||
|
||||
if (hero->inTownGarrison)
|
||||
ai->myCb->swapGarrisonHero(hero->visitedTown);
|
||||
|
||||
auto wait = cb->waitTillRealize;
|
||||
|
||||
cb->waitTillRealize = true;
|
||||
|
@ -12,7 +12,7 @@
|
||||
#include "../AIGateway.h"
|
||||
#include "../AIUtility.h"
|
||||
#include "../../../lib/constants/StringConstants.h"
|
||||
|
||||
#include "../../../lib/entities/building/CBuilding.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
@ -23,7 +23,7 @@ BuildThis::BuildThis(BuildingID Bid, const CGTownInstance * tid)
|
||||
: ElementarGoal(Goals::BUILD_STRUCTURE)
|
||||
{
|
||||
buildingInfo = BuildingInfo(
|
||||
tid->town->buildings.at(Bid),
|
||||
tid->getTown()->buildings.at(Bid),
|
||||
nullptr,
|
||||
CreatureID::NONE,
|
||||
tid,
|
||||
@ -52,7 +52,7 @@ void BuildThis::accept(AIGateway * ai)
|
||||
if(cb->canBuildStructure(town, b) == EBuildingState::ALLOWED)
|
||||
{
|
||||
logAi->debug("Player %d will build %s in town of %s at %s",
|
||||
ai->playerID, town->town->buildings.at(b)->getNameTranslated(), town->getNameTranslated(), town->pos.toString());
|
||||
ai->playerID, town->getTown()->buildings.at(b)->getNameTranslated(), town->getNameTranslated(), town->anchorPos().toString());
|
||||
cb->buildBuilding(town, b);
|
||||
|
||||
return;
|
||||
|
@ -34,13 +34,13 @@ void BuyArmy::accept(AIGateway * ai)
|
||||
ui64 valueBought = 0;
|
||||
//buy the stacks with largest AI value
|
||||
|
||||
auto upgradeSuccessfull = ai->makePossibleUpgrades(town);
|
||||
auto upgradeSuccessful = ai->makePossibleUpgrades(town);
|
||||
|
||||
auto armyToBuy = ai->nullkiller->armyManager->getArmyAvailableToBuy(town->getUpperArmy(), town);
|
||||
|
||||
if(armyToBuy.empty())
|
||||
{
|
||||
if(upgradeSuccessfull)
|
||||
if(upgradeSuccessful)
|
||||
return;
|
||||
|
||||
throw cannotFulfillGoalException("No creatures to buy.");
|
||||
@ -58,7 +58,36 @@ void BuyArmy::accept(AIGateway * ai)
|
||||
|
||||
if(ci.count)
|
||||
{
|
||||
cb->recruitCreatures(town, town->getUpperArmy(), ci.creID, ci.count, ci.level);
|
||||
if (town->getUpperArmy()->stacksCount() == GameConstants::ARMY_SIZE)
|
||||
{
|
||||
SlotID lowestValueSlot;
|
||||
int lowestValue = std::numeric_limits<int>::max();
|
||||
for (auto slot : town->getUpperArmy()->Slots())
|
||||
{
|
||||
if (slot.second->getCreatureID() != CreatureID::NONE)
|
||||
{
|
||||
int currentStackMarketValue =
|
||||
slot.second->getCreatureID().toCreature()->getFullRecruitCost().marketValue() * slot.second->getCount();
|
||||
|
||||
if (slot.second->getCreatureID().toCreature()->getFactionID() == town->getFactionID())
|
||||
continue;
|
||||
|
||||
if (currentStackMarketValue < lowestValue)
|
||||
{
|
||||
lowestValue = currentStackMarketValue;
|
||||
lowestValueSlot = slot.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lowestValueSlot.validSlot())
|
||||
{
|
||||
cb->dismissCreature(town->getUpperArmy(), lowestValueSlot);
|
||||
}
|
||||
}
|
||||
if (town->getUpperArmy()->stacksCount() < GameConstants::ARMY_SIZE || town->getUpperArmy()->getSlotFor(ci.creID).validSlot()) //It is possible we don't scrap despite we wanted to due to not scrapping stacks that fit our faction
|
||||
{
|
||||
cb->recruitCreatures(town, town->getUpperArmy(), ci.creID, ci.count, ci.level);
|
||||
}
|
||||
valueBought += ci.count * ci.creID.toCreature()->getAIValue();
|
||||
}
|
||||
}
|
||||
|
@ -37,12 +37,6 @@ namespace Goals
|
||||
{
|
||||
return new T(static_cast<T const &>(*this)); //casting enforces template instantiation
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
bool operator==(const AbstractGoal & g) const override
|
||||
{
|
||||
|
@ -31,7 +31,7 @@ namespace Goals
|
||||
{
|
||||
objid = obj->id.getNum();
|
||||
tile = obj->visitablePos();
|
||||
name = obj->typeName;
|
||||
name = obj->getTypeName();
|
||||
}
|
||||
|
||||
bool operator==(const CaptureObject & other) const override;
|
||||
|
@ -12,7 +12,7 @@
|
||||
#include "../Behaviors/CaptureObjectsBehavior.h"
|
||||
#include "../AIGateway.h"
|
||||
#include "../../../lib/VCMI_Lib.h"
|
||||
#include "../../../lib/CGeneralTextHandler.h"
|
||||
#include "../../../lib/texts/CGeneralTextHandler.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
@ -90,9 +90,12 @@ void ExchangeSwapTownHeroes::accept(AIGateway * ai)
|
||||
|
||||
if(!town->garrisonHero)
|
||||
{
|
||||
while(upperArmy->stacksCount() != 0)
|
||||
if (!garrisonHero->canBeMergedWith(*town))
|
||||
{
|
||||
cb->dismissCreature(upperArmy, upperArmy->Slots().begin()->first);
|
||||
while (upperArmy->stacksCount() != 0)
|
||||
{
|
||||
cb->dismissCreature(upperArmy, upperArmy->Slots().begin()->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,11 +22,17 @@ ExecuteHeroChain::ExecuteHeroChain(const AIPath & path, const CGObjectInstance *
|
||||
{
|
||||
hero = path.targetHero;
|
||||
tile = path.targetTile();
|
||||
closestWayRatio = 1;
|
||||
|
||||
if(obj)
|
||||
{
|
||||
objid = obj->id.getNum();
|
||||
targetName = obj->typeName + tile.toString();
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
targetName = obj->getObjectName() + tile.toString();
|
||||
#else
|
||||
targetName = obj->getTypeName() + tile.toString();
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -80,6 +86,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
|
||||
ai->nullkiller->setActive(chainPath.targetHero, tile);
|
||||
ai->nullkiller->setTargetObject(objid);
|
||||
ai->nullkiller->objectClusterizer->reset();
|
||||
|
||||
auto targetObject = ai->myCb->getObj(static_cast<ObjectInstanceID>(objid), false);
|
||||
|
||||
@ -191,6 +198,26 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
}
|
||||
}
|
||||
|
||||
auto findWhirlpool = [&ai](const int3 & pos) -> ObjectInstanceID
|
||||
{
|
||||
auto objs = ai->myCb->getVisitableObjs(pos);
|
||||
auto whirlpool = std::find_if(objs.begin(), objs.end(), [](const CGObjectInstance * o)->bool
|
||||
{
|
||||
return o->ID == Obj::WHIRLPOOL;
|
||||
});
|
||||
|
||||
return whirlpool != objs.end() ? dynamic_cast<const CGWhirlpool *>(*whirlpool)->id : ObjectInstanceID(-1);
|
||||
};
|
||||
|
||||
auto sourceWhirlpool = findWhirlpool(hero->visitablePos());
|
||||
auto targetWhirlpool = findWhirlpool(node->coord);
|
||||
|
||||
if(i != chainPath.nodes.size() - 1 && sourceWhirlpool.hasValue() && sourceWhirlpool == targetWhirlpool)
|
||||
{
|
||||
logAi->trace("AI exited whirlpool at %s but expected at %s", hero->visitablePos().toString(), node->coord.toString());
|
||||
continue;
|
||||
}
|
||||
|
||||
if(hero->movementPointsRemaining())
|
||||
{
|
||||
try
|
||||
@ -234,7 +261,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
if(node->turns == 0)
|
||||
{
|
||||
logAi->error(
|
||||
"Unable to complete chain. Expected hero %s to arive to %s but he is at %s",
|
||||
"Unable to complete chain. Expected hero %s to arrive to %s but he is at %s",
|
||||
hero->getNameTranslated(),
|
||||
node->coord.toString(),
|
||||
hero->visitablePos().toString());
|
||||
@ -260,7 +287,11 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
|
||||
std::string ExecuteHeroChain::toString() const
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
return "ExecuteHeroChain " + targetName + " by " + chainPath.toString();
|
||||
#else
|
||||
return "ExecuteHeroChain " + targetName + " by " + chainPath.targetHero->getNameTranslated();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ExecuteHeroChain::moveHeroToTile(AIGateway * ai, const CGHeroInstance * hero, const int3 & tile)
|
||||
|
@ -59,7 +59,7 @@ void ExploreNeighbourTile::accept(AIGateway * ai)
|
||||
return;
|
||||
}
|
||||
|
||||
auto danger = ai->nullkiller->pathfinder->getStorage()->evaluateDanger(target, hero, true);
|
||||
auto danger = ai->nullkiller->dangerEvaluator->evaluateDanger(target, hero, true);
|
||||
|
||||
if(danger > 0 || !ai->moveHeroToTile(target, hero))
|
||||
{
|
||||
|
@ -68,10 +68,13 @@ void RecruitHero::accept(AIGateway * ai)
|
||||
throw cannotFulfillGoalException("Town " + t->nodeName() + " is occupied. Cannot recruit hero!");
|
||||
|
||||
cb->recruitHero(t, heroToHire);
|
||||
ai->nullkiller->heroManager->update();
|
||||
|
||||
if(t->visitingHero)
|
||||
ai->moveHeroToTile(t->visitablePos(), t->visitingHero.get());
|
||||
{
|
||||
std::unique_lock lockGuard(ai->nullkiller->aiStateMutex);
|
||||
|
||||
ai->nullkiller->heroManager->update();
|
||||
ai->nullkiller->objectClusterizer->reset();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ namespace Goals
|
||||
}
|
||||
|
||||
std::string toString() const override;
|
||||
const CGHeroInstance* getHero() const override { return heroToBuy; }
|
||||
void accept(AIGateway * ai) override;
|
||||
};
|
||||
}
|
||||
|
@ -36,16 +36,12 @@ std::string StayAtTown::toString() const
|
||||
{
|
||||
return "Stay at town " + town->getNameTranslated()
|
||||
+ " hero " + hero->getNameTranslated()
|
||||
+ ", mana: " + std::to_string(hero->mana);
|
||||
+ ", mana: " + std::to_string(hero->mana)
|
||||
+ " / " + std::to_string(hero->manaLimit());
|
||||
}
|
||||
|
||||
void StayAtTown::accept(AIGateway * ai)
|
||||
{
|
||||
if(hero->visitedTown != town)
|
||||
{
|
||||
logAi->error("Hero %s expected visiting town %s", hero->getNameTranslated(), town->getNameTranslated());
|
||||
}
|
||||
|
||||
ai->nullkiller->lockHero(hero, HeroLockedReason::DEFENCE);
|
||||
}
|
||||
|
||||
|
@ -14,27 +14,37 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
void ArmyFormation::rearrangeArmyForSiege(const CGTownInstance * town, const CGHeroInstance * attacker)
|
||||
void ArmyFormation::rearrangeArmyForWhirlpool(const CGHeroInstance * hero)
|
||||
{
|
||||
auto freeSlots = attacker->getFreeSlotsQueue();
|
||||
addSingleCreatureStacks(hero);
|
||||
}
|
||||
|
||||
void ArmyFormation::addSingleCreatureStacks(const CGHeroInstance * hero)
|
||||
{
|
||||
auto freeSlots = hero->getFreeSlotsQueue();
|
||||
|
||||
while(!freeSlots.empty())
|
||||
{
|
||||
auto weakestCreature = vstd::minElementByFun(attacker->Slots(), [](const std::pair<SlotID, CStackInstance *> & slot) -> int
|
||||
auto weakestCreature = vstd::minElementByFun(hero->Slots(), [](const std::pair<SlotID, CStackInstance *> & slot) -> int
|
||||
{
|
||||
return slot.second->getCount() == 1
|
||||
? std::numeric_limits<int>::max()
|
||||
: slot.second->getCreatureID().toCreature()->getAIValue();
|
||||
});
|
||||
|
||||
if(weakestCreature == attacker->Slots().end() || weakestCreature->second->getCount() == 1)
|
||||
if(weakestCreature == hero->Slots().end() || weakestCreature->second->getCount() == 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
cb->splitStack(attacker, attacker, weakestCreature->first, freeSlots.front(), 1);
|
||||
cb->splitStack(hero, hero, weakestCreature->first, freeSlots.front(), 1);
|
||||
freeSlots.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void ArmyFormation::rearrangeArmyForSiege(const CGTownInstance * town, const CGHeroInstance * attacker)
|
||||
{
|
||||
addSingleCreatureStacks(attacker);
|
||||
|
||||
if(town->fortLevel() > CGTownInstance::FORT)
|
||||
{
|
||||
|
@ -13,8 +13,6 @@
|
||||
|
||||
#include "../../../lib/GameConstants.h"
|
||||
#include "../../../lib/VCMI_Lib.h"
|
||||
#include "../../../lib/CTownHandler.h"
|
||||
#include "../../../lib/CBuildingHandler.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
@ -33,6 +31,10 @@ public:
|
||||
ArmyFormation(std::shared_ptr<CCallback> CB, const Nullkiller * ai): cb(CB) {}
|
||||
|
||||
void rearrangeArmyForSiege(const CGTownInstance * town, const CGHeroInstance * attacker);
|
||||
|
||||
void rearrangeArmyForWhirlpool(const CGHeroInstance * hero);
|
||||
|
||||
void addSingleCreatureStacks(const CGHeroInstance * hero);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ bool ExplorationHelper::scanSector(int scanRadius)
|
||||
{
|
||||
int3 tile = int3(0, 0, ourPos.z);
|
||||
|
||||
const auto & slice = (*(ts->fogOfWarMap))[ourPos.z];
|
||||
const auto & slice = ts->fogOfWarMap[ourPos.z];
|
||||
|
||||
for(tile.x = ourPos.x - scanRadius; tile.x <= ourPos.x + scanRadius; tile.x++)
|
||||
{
|
||||
@ -75,13 +75,13 @@ bool ExplorationHelper::scanMap()
|
||||
|
||||
foreach_tile_pos([&](const int3 & pos)
|
||||
{
|
||||
if((*(ts->fogOfWarMap))[pos.z][pos.x][pos.y])
|
||||
if(ts->fogOfWarMap[pos.z][pos.x][pos.y])
|
||||
{
|
||||
bool hasInvisibleNeighbor = false;
|
||||
|
||||
foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour)
|
||||
{
|
||||
if(!(*(ts->fogOfWarMap))[neighbour.z][neighbour.x][neighbour.y])
|
||||
if(!ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y])
|
||||
{
|
||||
hasInvisibleNeighbor = true;
|
||||
}
|
||||
@ -107,7 +107,7 @@ bool ExplorationHelper::scanMap()
|
||||
allowDeadEndCancellation = false;
|
||||
logAi->debug("Exploration scan all possible tiles for hero %s", hero->getNameTranslated());
|
||||
|
||||
boost::multi_array<ui8, 3> potentialTiles = *ts->fogOfWarMap;
|
||||
boost::multi_array<ui8, 3> potentialTiles = ts->fogOfWarMap;
|
||||
std::vector<int3> tilesToExploreFrom = edgeTiles;
|
||||
|
||||
// WARNING: POTENTIAL BUG
|
||||
@ -175,7 +175,7 @@ void ExplorationHelper::scanTile(const int3 & tile)
|
||||
continue;
|
||||
}
|
||||
|
||||
if(isSafeToVisit(hero, path.heroArmy, path.getTotalDanger()))
|
||||
if(isSafeToVisit(hero, path.heroArmy, path.getTotalDanger(), ai->settings->getSafeAttackRatio()))
|
||||
{
|
||||
bestGoal = goal;
|
||||
bestValue = ourValue;
|
||||
@ -191,7 +191,7 @@ int ExplorationHelper::howManyTilesWillBeDiscovered(const int3 & pos) const
|
||||
int ret = 0;
|
||||
int3 npos = int3(0, 0, pos.z);
|
||||
|
||||
const auto & slice = (*(ts->fogOfWarMap))[pos.z];
|
||||
const auto & slice = ts->fogOfWarMap[pos.z];
|
||||
|
||||
for(npos.x = pos.x - sightRadius; npos.x <= pos.x + sightRadius; npos.x++)
|
||||
{
|
||||
|
@ -13,8 +13,6 @@
|
||||
|
||||
#include "../../../lib/GameConstants.h"
|
||||
#include "../../../lib/VCMI_Lib.h"
|
||||
#include "../../../lib/CTownHandler.h"
|
||||
#include "../../../lib/CBuildingHandler.h"
|
||||
#include "../Goals/AbstractGoal.h"
|
||||
|
||||
namespace NKAI
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "StdInc.h"
|
||||
#include "AINodeStorage.h"
|
||||
#include "Actions/TownPortalAction.h"
|
||||
#include "Actions/WhirlpoolAction.h"
|
||||
#include "../Goals/Goals.h"
|
||||
#include "../AIGateway.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
@ -25,10 +26,10 @@ namespace NKAI
|
||||
{
|
||||
|
||||
std::shared_ptr<boost::multi_array<AIPathNode, 4>> AISharedStorage::shared;
|
||||
uint64_t AISharedStorage::version = 0;
|
||||
uint32_t AISharedStorage::version = 0;
|
||||
boost::mutex AISharedStorage::locker;
|
||||
std::set<int3> commitedTiles;
|
||||
std::set<int3> commitedTilesInitial;
|
||||
std::set<int3> committedTiles;
|
||||
std::set<int3> committedTilesInitial;
|
||||
|
||||
|
||||
const uint64_t FirstActorMask = 1;
|
||||
@ -36,19 +37,19 @@ 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;
|
||||
const bool DO_NOT_SAVE_TO_COMMITTED_TILES = false;
|
||||
|
||||
AISharedStorage::AISharedStorage(int3 sizes)
|
||||
AISharedStorage::AISharedStorage(int3 sizes, int numChains)
|
||||
{
|
||||
if(!shared){
|
||||
shared.reset(new boost::multi_array<AIPathNode, 4>(
|
||||
boost::extents[sizes.z][sizes.x][sizes.y][AIPathfinding::NUM_CHAINS]));
|
||||
boost::extents[sizes.z][sizes.x][sizes.y][numChains]));
|
||||
|
||||
nodes = shared;
|
||||
|
||||
foreach_tile_pos([&](const int3 & pos)
|
||||
{
|
||||
for(auto i = 0; i < AIPathfinding::NUM_CHAINS; i++)
|
||||
for(auto i = 0; i < numChains; i++)
|
||||
{
|
||||
auto & node = get(pos)[i];
|
||||
|
||||
@ -91,13 +92,21 @@ void AIPathNode::addSpecialAction(std::shared_ptr<const SpecialAction> action)
|
||||
}
|
||||
}
|
||||
|
||||
AINodeStorage::AINodeStorage(const Nullkiller * ai, const int3 & Sizes)
|
||||
: sizes(Sizes), ai(ai), cb(ai->cb.get()), nodes(Sizes)
|
||||
int AINodeStorage::getBucketCount() const
|
||||
{
|
||||
accesibility = std::make_unique<boost::multi_array<EPathAccessibility, 4>>(
|
||||
boost::extents[sizes.z][sizes.x][sizes.y][EPathfindingLayer::NUM_LAYERS]);
|
||||
return ai->settings->getPathfinderBucketsCount();
|
||||
}
|
||||
|
||||
dangerEvaluator.reset(new FuzzyHelper(ai));
|
||||
int AINodeStorage::getBucketSize() const
|
||||
{
|
||||
return ai->settings->getPathfinderBucketSize();
|
||||
}
|
||||
|
||||
AINodeStorage::AINodeStorage(const Nullkiller * ai, const int3 & Sizes)
|
||||
: sizes(Sizes), ai(ai), cb(ai->cb.get()), nodes(Sizes, ai->settings->getPathfinderBucketSize() * ai->settings->getPathfinderBucketsCount())
|
||||
{
|
||||
accessibility = std::make_unique<boost::multi_array<EPathAccessibility, 4>>(
|
||||
boost::extents[sizes.z][sizes.x][sizes.y][EPathfindingLayer::NUM_LAYERS]);
|
||||
}
|
||||
|
||||
AINodeStorage::~AINodeStorage() = default;
|
||||
@ -131,10 +140,10 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
|
||||
for(pos.y = 0; pos.y < sizes.y; ++pos.y)
|
||||
{
|
||||
const TerrainTile & tile = gs->map->getTile(pos);
|
||||
if (!tile.terType->isPassable())
|
||||
if (!tile.getTerrain()->isPassable())
|
||||
continue;
|
||||
|
||||
if (tile.terType->isWater())
|
||||
if (tile.isWater())
|
||||
{
|
||||
resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
|
||||
if (useFlying)
|
||||
@ -157,7 +166,7 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
|
||||
void AINodeStorage::clear()
|
||||
{
|
||||
actors.clear();
|
||||
commitedTiles.clear();
|
||||
committedTiles.clear();
|
||||
heroChainPass = EHeroChainPass::INITIAL;
|
||||
heroChainTurn = 0;
|
||||
heroChainMaxTurns = 1;
|
||||
@ -170,8 +179,8 @@ std::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
|
||||
const EPathfindingLayer layer,
|
||||
const ChainActor * actor)
|
||||
{
|
||||
int bucketIndex = ((uintptr_t)actor + static_cast<uint32_t>(layer)) % AIPathfinding::BUCKET_COUNT;
|
||||
int bucketOffset = bucketIndex * AIPathfinding::BUCKET_SIZE;
|
||||
int bucketIndex = ((uintptr_t)actor + static_cast<uint32_t>(layer)) % ai->settings->getPathfinderBucketsCount();
|
||||
int bucketOffset = bucketIndex * ai->settings->getPathfinderBucketSize();
|
||||
auto chains = nodes.get(pos);
|
||||
|
||||
if(blocked(pos, layer))
|
||||
@ -179,7 +188,7 @@ std::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for(auto i = AIPathfinding::BUCKET_SIZE - 1; i >= 0; i--)
|
||||
for(auto i = ai->settings->getPathfinderBucketSize() - 1; i >= 0; i--)
|
||||
{
|
||||
AIPathNode & node = chains[i + bucketOffset];
|
||||
|
||||
@ -224,7 +233,6 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
|
||||
|
||||
AIPathNode * initialNode = allocated.value();
|
||||
|
||||
initialNode->inPQ = false;
|
||||
initialNode->pq = nullptr;
|
||||
initialNode->turns = actor->initialTurn;
|
||||
initialNode->moveRemains = actor->initialMovement;
|
||||
@ -256,10 +264,45 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
|
||||
{
|
||||
commit(dstNode, srcNode, destination.action, destination.turn, destination.movementLeft, destination.cost);
|
||||
|
||||
if(srcNode->specialAction || srcNode->chainOther)
|
||||
// regular pathfinder can not go directly through whirlpool
|
||||
bool isWhirlpoolTeleport = destination.nodeObject
|
||||
&& destination.nodeObject->ID == Obj::WHIRLPOOL;
|
||||
|
||||
if(srcNode->specialAction
|
||||
|| srcNode->chainOther
|
||||
|| isWhirlpoolTeleport)
|
||||
{
|
||||
// there is some action on source tile which should be performed before we can bypass it
|
||||
destination.node->theNodeBefore = source.node;
|
||||
dstNode->theNodeBefore = source.node;
|
||||
|
||||
if(isWhirlpoolTeleport)
|
||||
{
|
||||
if(dstNode->actor->creatureSet->Slots().size() == 1
|
||||
&& dstNode->actor->creatureSet->Slots().begin()->second->getCount() == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto weakest = vstd::minElementByFun(dstNode->actor->creatureSet->Slots(), [](std::pair<SlotID, const CStackInstance *> pair) -> int
|
||||
{
|
||||
return pair.second->getCount() * pair.second->getCreatureID().toCreature()->getAIValue();
|
||||
});
|
||||
|
||||
if(weakest == dstNode->actor->creatureSet->Slots().end())
|
||||
{
|
||||
logAi->debug("Empty army entering whirlpool detected at tile %s", dstNode->coord.toString());
|
||||
destination.blocked = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(dstNode->actor->creatureSet->getFreeSlots().size())
|
||||
dstNode->armyLoss += weakest->second->getCreatureID().toCreature()->getAIValue();
|
||||
else
|
||||
dstNode->armyLoss += (weakest->second->getCount() + 1) / 2 * weakest->second->getCreatureID().toCreature()->getAIValue();
|
||||
|
||||
dstNode->specialAction = AIPathfinding::WhirlpoolAction::instance;
|
||||
}
|
||||
}
|
||||
|
||||
if(dstNode->specialAction && dstNode->actor)
|
||||
@ -276,7 +319,7 @@ void AINodeStorage::commit(
|
||||
int turn,
|
||||
int movementLeft,
|
||||
float cost,
|
||||
bool saveToCommited) const
|
||||
bool saveToCommitted) const
|
||||
{
|
||||
destination->action = action;
|
||||
destination->setCost(cost);
|
||||
@ -290,7 +333,7 @@ void AINodeStorage::commit(
|
||||
|
||||
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Commited %s -> %s, layer: %d, cost: %f, turn: %s, mp: %d, hero: %s, mask: %x, army: %lld",
|
||||
"Committed %s -> %s, layer: %d, cost: %f, turn: %s, mp: %d, hero: %s, mask: %x, army: %lld",
|
||||
source->coord.toString(),
|
||||
destination->coord.toString(),
|
||||
destination->layer,
|
||||
@ -302,9 +345,9 @@ void AINodeStorage::commit(
|
||||
destination->actor->armyValue);
|
||||
#endif
|
||||
|
||||
if(saveToCommited && destination->turns <= heroChainTurn)
|
||||
if(saveToCommitted && destination->turns <= heroChainTurn)
|
||||
{
|
||||
commitedTiles.insert(destination->coord);
|
||||
committedTiles.insert(destination->coord);
|
||||
}
|
||||
|
||||
if(destination->turns == source->turns)
|
||||
@ -374,7 +417,7 @@ bool AINodeStorage::increaseHeroChainTurnLimit()
|
||||
return false;
|
||||
|
||||
heroChainTurn++;
|
||||
commitedTiles.clear();
|
||||
committedTiles.clear();
|
||||
|
||||
for(auto layer : phisycalLayers)
|
||||
{
|
||||
@ -384,7 +427,7 @@ bool AINodeStorage::increaseHeroChainTurnLimit()
|
||||
{
|
||||
if(node.turns <= heroChainTurn && node.action != EPathNodeAction::UNKNOWN)
|
||||
{
|
||||
commitedTiles.insert(pos);
|
||||
committedTiles.insert(pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -453,8 +496,8 @@ public:
|
||||
AINodeStorage & storage, const std::vector<int3> & tiles, uint64_t chainMask, int heroChainTurn)
|
||||
:existingChains(), newChains(), delayedWork(), storage(storage), chainMask(chainMask), heroChainTurn(heroChainTurn), heroChain(), tiles(tiles)
|
||||
{
|
||||
existingChains.reserve(AIPathfinding::NUM_CHAINS);
|
||||
newChains.reserve(AIPathfinding::NUM_CHAINS);
|
||||
existingChains.reserve(storage.getBucketCount() * storage.getBucketSize());
|
||||
newChains.reserve(storage.getBucketCount() * storage.getBucketSize());
|
||||
}
|
||||
|
||||
void execute(const tbb::blocked_range<size_t>& r)
|
||||
@ -545,7 +588,7 @@ bool AINodeStorage::calculateHeroChain()
|
||||
heroChainPass = EHeroChainPass::CHAIN;
|
||||
heroChain.clear();
|
||||
|
||||
std::vector<int3> data(commitedTiles.begin(), commitedTiles.end());
|
||||
std::vector<int3> data(committedTiles.begin(), committedTiles.end());
|
||||
|
||||
if(data.size() > 100)
|
||||
{
|
||||
@ -576,7 +619,7 @@ bool AINodeStorage::calculateHeroChain()
|
||||
task.flushResult(heroChain);
|
||||
}
|
||||
|
||||
commitedTiles.clear();
|
||||
committedTiles.clear();
|
||||
|
||||
return !heroChain.empty();
|
||||
}
|
||||
@ -592,7 +635,7 @@ bool AINodeStorage::selectFirstActor()
|
||||
});
|
||||
|
||||
chainMask = strongest->chainMask;
|
||||
commitedTilesInitial = commitedTiles;
|
||||
committedTilesInitial = committedTiles;
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -627,7 +670,7 @@ bool AINodeStorage::selectNextActor()
|
||||
return false;
|
||||
|
||||
chainMask = nextActor->get()->chainMask;
|
||||
commitedTiles = commitedTilesInitial;
|
||||
committedTiles = committedTilesInitial;
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -654,7 +697,7 @@ void HeroChainCalculationTask::cleanupInefectiveChains(std::vector<ExchangeCandi
|
||||
if(isNotEffective)
|
||||
{
|
||||
logAi->trace(
|
||||
"Skip exchange %s[%x] -> %s[%x] at %s is ineficient",
|
||||
"Skip exchange %s[%x] -> %s[%x] at %s is inefficient",
|
||||
chainInfo.otherParent->actor->toString(),
|
||||
chainInfo.otherParent->actor->chainMask,
|
||||
chainInfo.carrierParent->actor->toString(),
|
||||
@ -686,6 +729,7 @@ void HeroChainCalculationTask::calculateHeroChain(
|
||||
if(node->action == EPathNodeAction::BATTLE
|
||||
|| node->action == EPathNodeAction::TELEPORT_BATTLE
|
||||
|| node->action == EPathNodeAction::TELEPORT_NORMAL
|
||||
|| node->action == EPathNodeAction::DISEMBARK
|
||||
|| node->action == EPathNodeAction::TELEPORT_BLOCKING_VISIT)
|
||||
{
|
||||
continue;
|
||||
@ -754,7 +798,7 @@ void HeroChainCalculationTask::calculateHeroChain(
|
||||
if(hasLessMp && hasLessExperience)
|
||||
{
|
||||
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
||||
logAi->trace("Exchange at %s is ineficient. Blocked.", carrier->coord.toString());
|
||||
logAi->trace("Exchange at %s is inefficient. Blocked.", carrier->coord.toString());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
@ -823,7 +867,7 @@ void HeroChainCalculationTask::addHeroChain(const std::vector<ExchangeCandidate>
|
||||
chainInfo.turns,
|
||||
chainInfo.moveRemains,
|
||||
chainInfo.getCost(),
|
||||
DO_NOT_SAVE_TO_COMMITED_TILES);
|
||||
DO_NOT_SAVE_TO_COMMITTED_TILES);
|
||||
|
||||
if(carrier->specialAction || carrier->chainOther)
|
||||
{
|
||||
@ -928,7 +972,7 @@ void AINodeStorage::setHeroes(std::map<const CGHeroInstance *, HeroRole> heroes)
|
||||
// do not allow our own heroes in garrison to act on map
|
||||
if(hero.first->getOwner() == ai->playerID
|
||||
&& hero.first->inTownGarrison
|
||||
&& (ai->isHeroLocked(hero.first) || ai->heroManager->heroCapReached()))
|
||||
&& (ai->isHeroLocked(hero.first) || ai->heroManager->heroCapReached(false)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -1015,8 +1059,8 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
|
||||
|
||||
for(auto & neighbour : accessibleExits)
|
||||
{
|
||||
auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->actor);
|
||||
|
||||
std::optional<AIPathNode *> node = getOrCreateNode(neighbour, source.node->layer, srcNode->actor);
|
||||
|
||||
if(!node)
|
||||
continue;
|
||||
|
||||
@ -1027,7 +1071,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
|
||||
return neighbours;
|
||||
}
|
||||
|
||||
struct TowmPortalFinder
|
||||
struct TownPortalFinder
|
||||
{
|
||||
const std::vector<CGPathNode *> & initialNodes;
|
||||
MasteryLevel::Type townPortalSkillLevel;
|
||||
@ -1040,7 +1084,7 @@ struct TowmPortalFinder
|
||||
SpellID spellID;
|
||||
const CSpell * townPortal;
|
||||
|
||||
TowmPortalFinder(
|
||||
TownPortalFinder(
|
||||
const ChainActor * actor,
|
||||
const std::vector<CGPathNode *> & initialNodes,
|
||||
std::vector<const CGTownInstance *> targetTowns,
|
||||
@ -1117,7 +1161,7 @@ struct TowmPortalFinder
|
||||
bestNode->turns,
|
||||
bestNode->moveRemains - movementNeeded,
|
||||
movementCost,
|
||||
DO_NOT_SAVE_TO_COMMITED_TILES);
|
||||
DO_NOT_SAVE_TO_COMMITTED_TILES);
|
||||
|
||||
node->theNodeBefore = bestNode;
|
||||
node->addSpecialAction(std::make_shared<AIPathfinding::TownPortalAction>(targetTown));
|
||||
@ -1146,7 +1190,7 @@ void AINodeStorage::calculateTownPortal(
|
||||
return; // no towns no need to run loop further
|
||||
}
|
||||
|
||||
TowmPortalFinder townPortalFinder(actor, initialNodes, towns, this);
|
||||
TownPortalFinder townPortalFinder(actor, initialNodes, towns, this);
|
||||
|
||||
if(townPortalFinder.actorCanCastTownPortal())
|
||||
{
|
||||
@ -1163,6 +1207,11 @@ void AINodeStorage::calculateTownPortal(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (targetTown->visitingHero
|
||||
&& (targetTown->visitingHero.get()->getFactionID() != actor->hero->getFactionID()
|
||||
|| targetTown->getUpperArmy()->stacksCount()))
|
||||
continue;
|
||||
|
||||
auto nodeOptional = townPortalFinder.createTownPortalNode(targetTown);
|
||||
|
||||
if(nodeOptional)
|
||||
@ -1279,7 +1328,7 @@ bool AINodeStorage::isOtherChainBetter(
|
||||
{
|
||||
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Block ineficient battle move %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||
"Block inefficient battle move %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||
source->coord.toString(),
|
||||
candidateNode.coord.toString(),
|
||||
candidateNode.actor->hero->getNameTranslated(),
|
||||
@ -1303,7 +1352,7 @@ bool AINodeStorage::isOtherChainBetter(
|
||||
{
|
||||
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Block ineficient move because of stronger army %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||
"Block inefficient move because of stronger army %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||
source->coord.toString(),
|
||||
candidateNode.coord.toString(),
|
||||
candidateNode.actor->hero->getNameTranslated(),
|
||||
@ -1329,7 +1378,7 @@ bool AINodeStorage::isOtherChainBetter(
|
||||
|
||||
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Block ineficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||
"Block inefficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||
source->coord.toString(),
|
||||
candidateNode.coord.toString(),
|
||||
candidateNode.actor->hero->getNameTranslated(),
|
||||
@ -1384,7 +1433,33 @@ void AINodeStorage::calculateChainInfo(std::vector<AIPath> & paths, const int3 &
|
||||
path.targetHero = node.actor->hero;
|
||||
path.heroArmy = node.actor->creatureSet;
|
||||
path.armyLoss = node.armyLoss;
|
||||
path.targetObjectDanger = evaluateDanger(pos, path.targetHero, !node.actor->allowBattle);
|
||||
path.targetObjectDanger = ai->dangerEvaluator->evaluateDanger(pos, path.targetHero, !node.actor->allowBattle);
|
||||
for (auto pathNode : path.nodes)
|
||||
{
|
||||
path.targetObjectDanger = std::max(ai->dangerEvaluator->evaluateDanger(pathNode.coord, path.targetHero, !node.actor->allowBattle), path.targetObjectDanger);
|
||||
}
|
||||
|
||||
if(path.targetObjectDanger > 0)
|
||||
{
|
||||
if(node.theNodeBefore)
|
||||
{
|
||||
auto prevNode = getAINode(node.theNodeBefore);
|
||||
|
||||
if(node.coord == prevNode->coord && node.actor->hero == prevNode->actor->hero)
|
||||
{
|
||||
paths.pop_back();
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
path.armyLoss = prevNode->armyLoss;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
path.armyLoss = 0;
|
||||
}
|
||||
}
|
||||
|
||||
path.targetObjectArmyLoss = evaluateArmyLoss(
|
||||
path.targetHero,
|
||||
@ -1509,7 +1584,7 @@ uint8_t AIPath::turn() const
|
||||
|
||||
uint64_t AIPath::getHeroStrength() const
|
||||
{
|
||||
return targetHero->getFightingStrength() * getHeroArmyStrengthWithCommander(targetHero, heroArmy);
|
||||
return targetHero->getHeroStrength() * getHeroArmyStrengthWithCommander(targetHero, heroArmy);
|
||||
}
|
||||
|
||||
uint64_t AIPath::getTotalDanger() const
|
||||
|
@ -29,9 +29,6 @@ namespace NKAI
|
||||
{
|
||||
namespace AIPathfinding
|
||||
{
|
||||
const int BUCKET_COUNT = 3;
|
||||
const int BUCKET_SIZE = 7;
|
||||
const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
|
||||
const int CHAIN_MAX_DEPTH = 4;
|
||||
}
|
||||
|
||||
@ -44,14 +41,17 @@ enum DayFlags : ui8
|
||||
|
||||
struct AIPathNode : public CGPathNode
|
||||
{
|
||||
std::shared_ptr<const SpecialAction> specialAction;
|
||||
|
||||
const AIPathNode * chainOther;
|
||||
const ChainActor * actor;
|
||||
|
||||
uint64_t danger;
|
||||
uint64_t armyLoss;
|
||||
uint32_t version;
|
||||
|
||||
int16_t manaCost;
|
||||
DayFlags dayFlags;
|
||||
const AIPathNode * chainOther;
|
||||
std::shared_ptr<const SpecialAction> specialAction;
|
||||
const ChainActor * actor;
|
||||
uint64_t version;
|
||||
|
||||
void addSpecialAction(std::shared_ptr<const SpecialAction> action);
|
||||
|
||||
@ -152,9 +152,9 @@ class AISharedStorage
|
||||
std::shared_ptr<boost::multi_array<AIPathNode, 4>> nodes;
|
||||
public:
|
||||
static boost::mutex locker;
|
||||
static uint64_t version;
|
||||
static uint32_t version;
|
||||
|
||||
AISharedStorage(int3 mapSize);
|
||||
AISharedStorage(int3 sizes, int numChains);
|
||||
~AISharedStorage();
|
||||
|
||||
STRONG_INLINE
|
||||
@ -169,11 +169,10 @@ class AINodeStorage : public INodeStorage
|
||||
private:
|
||||
int3 sizes;
|
||||
|
||||
std::unique_ptr<boost::multi_array<EPathAccessibility, 4>> accesibility;
|
||||
std::unique_ptr<boost::multi_array<EPathAccessibility, 4>> accessibility;
|
||||
|
||||
const CPlayerSpecificInfoCallback * cb;
|
||||
const Nullkiller * ai;
|
||||
std::unique_ptr<FuzzyHelper> dangerEvaluator;
|
||||
AISharedStorage nodes;
|
||||
std::vector<std::shared_ptr<ChainActor>> actors;
|
||||
std::vector<CGPathNode *> heroChain;
|
||||
@ -195,6 +194,9 @@ public:
|
||||
bool selectFirstActor();
|
||||
bool selectNextActor();
|
||||
|
||||
int getBucketCount() const;
|
||||
int getBucketSize() const;
|
||||
|
||||
std::vector<CGPathNode *> getInitialNodes() override;
|
||||
|
||||
virtual void calculateNeighbours(
|
||||
@ -218,7 +220,7 @@ public:
|
||||
int turn,
|
||||
int movementLeft,
|
||||
float cost,
|
||||
bool saveToCommited = true) const;
|
||||
bool saveToCommitted = true) const;
|
||||
|
||||
inline const AIPathNode * getAINode(const CGPathNode * node) const
|
||||
{
|
||||
@ -261,7 +263,7 @@ public:
|
||||
const AIPathNode & candidateNode,
|
||||
const AIPathNode & other) const;
|
||||
|
||||
bool isMovementIneficient(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
|
||||
bool isMovementInefficient(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
|
||||
{
|
||||
return hasBetterChain(source, destination);
|
||||
}
|
||||
@ -282,26 +284,21 @@ public:
|
||||
bool calculateHeroChain();
|
||||
bool calculateHeroChainFinal();
|
||||
|
||||
inline uint64_t evaluateDanger(const int3 & tile, const CGHeroInstance * hero, bool checkGuards) const
|
||||
{
|
||||
return dangerEvaluator->evaluateDanger(tile, hero, checkGuards);
|
||||
}
|
||||
|
||||
uint64_t evaluateArmyLoss(const CGHeroInstance * hero, uint64_t armyValue, uint64_t danger) const;
|
||||
|
||||
inline EPathAccessibility getAccessibility(const int3 & tile, EPathfindingLayer layer) const
|
||||
{
|
||||
return (*this->accesibility)[tile.z][tile.x][tile.y][layer];
|
||||
return (*this->accessibility)[tile.z][tile.x][tile.y][layer];
|
||||
}
|
||||
|
||||
inline void resetTile(const int3 & tile, EPathfindingLayer layer, EPathAccessibility tileAccessibility)
|
||||
{
|
||||
(*this->accesibility)[tile.z][tile.x][tile.y][layer] = tileAccessibility;
|
||||
(*this->accessibility)[tile.z][tile.x][tile.y][layer] = tileAccessibility;
|
||||
}
|
||||
|
||||
inline int getBucket(const ChainActor * actor) const
|
||||
{
|
||||
return ((uintptr_t)actor * 395) % AIPathfinding::BUCKET_COUNT;
|
||||
return ((uintptr_t)actor * 395) % getBucketCount();
|
||||
}
|
||||
|
||||
void calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours);
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include "Rules/AIMovementAfterDestinationRule.h"
|
||||
#include "Rules/AIMovementToDestinationRule.h"
|
||||
#include "Rules/AIPreviousNodeRule.h"
|
||||
#include "../Engine//Nullkiller.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
|
||||
#include "../../../lib/pathfinder/CPathfinder.h"
|
||||
|
||||
@ -44,10 +44,12 @@ namespace AIPathfinding
|
||||
Nullkiller * ai,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage,
|
||||
bool allowBypassObjects)
|
||||
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage, allowBypassObjects)), aiNodeStorage(nodeStorage)
|
||||
:PathfinderConfig(nodeStorage, cb, makeRuleset(cb, ai, nodeStorage, allowBypassObjects)), aiNodeStorage(nodeStorage)
|
||||
{
|
||||
options.canUseCast = true;
|
||||
options.allowLayerTransitioningAfterBattle = true;
|
||||
options.useTeleportWhirlpool = true;
|
||||
options.forceUseTeleportWhirlpool = true;
|
||||
}
|
||||
|
||||
AIPathfinderConfig::~AIPathfinderConfig() = default;
|
||||
|
55
AI/Nullkiller/Pathfinding/Actions/WhirlpoolAction.cpp
Normal file
55
AI/Nullkiller/Pathfinding/Actions/WhirlpoolAction.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* WhirlpoolAction.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 "../../Goals/AdventureSpellCast.h"
|
||||
#include "../../../../lib/mapObjects/MapObjects.h"
|
||||
#include "WhirlpoolAction.h"
|
||||
#include "../../AIGateway.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
using namespace AIPathfinding;
|
||||
|
||||
std::shared_ptr<WhirlpoolAction> WhirlpoolAction::instance = std::make_shared<WhirlpoolAction>();
|
||||
|
||||
void WhirlpoolAction::execute(AIGateway * ai, const CGHeroInstance * hero) const
|
||||
{
|
||||
ai->nullkiller->armyFormation->rearrangeArmyForWhirlpool(hero);
|
||||
}
|
||||
|
||||
std::string WhirlpoolAction::toString() const
|
||||
{
|
||||
return "Prepare for whirlpool";
|
||||
}
|
||||
/*
|
||||
bool TownPortalAction::canAct(const CGHeroInstance * hero, const AIPathNode * source) const
|
||||
{
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
logAi->trace(
|
||||
"Hero %s has %d mana and needed %d and already spent %d",
|
||||
hero->name,
|
||||
hero->mana,
|
||||
getManaCost(hero),
|
||||
source->manaCost);
|
||||
#endif
|
||||
|
||||
return hero->mana >= source->manaCost + getManaCost(hero);
|
||||
}
|
||||
|
||||
uint32_t TownPortalAction::getManaCost(const CGHeroInstance * hero) const
|
||||
{
|
||||
SpellID summonBoat = SpellID::TOWN_PORTAL;
|
||||
|
||||
return hero->getSpellCost(summonBoat.toSpell());
|
||||
}*/
|
||||
|
||||
}
|
35
AI/Nullkiller/Pathfinding/Actions/WhirlpoolAction.h
Normal file
35
AI/Nullkiller/Pathfinding/Actions/WhirlpoolAction.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* WhirlpoolAction.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SpecialAction.h"
|
||||
#include "../../../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../Goals/AdventureSpellCast.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
namespace AIPathfinding
|
||||
{
|
||||
class WhirlpoolAction : public SpecialAction
|
||||
{
|
||||
public:
|
||||
WhirlpoolAction()
|
||||
{
|
||||
}
|
||||
|
||||
static std::shared_ptr<WhirlpoolAction> instance;
|
||||
|
||||
void execute(AIGateway * ai, const CGHeroInstance * hero) const override;
|
||||
|
||||
std::string toString() const override;
|
||||
};
|
||||
}
|
||||
}
|
@ -46,7 +46,7 @@ ChainActor::ChainActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t
|
||||
initialMovement = hero->movementPointsRemaining();
|
||||
initialTurn = 0;
|
||||
armyValue = getHeroArmyStrengthWithCommander(hero, hero);
|
||||
heroFightingStrength = hero->getFightingStrength();
|
||||
heroFightingStrength = hero->getHeroStrength();
|
||||
tiCache.reset(new TurnInfo(hero));
|
||||
}
|
||||
|
||||
@ -182,7 +182,7 @@ ExchangeResult HeroActor::tryExchangeNoLock(const ChainActor * specialActor, con
|
||||
return &actor == specialActor;
|
||||
});
|
||||
|
||||
result.actor = &(dynamic_cast<HeroActor *>(result.actor)->specialActors[index]);
|
||||
result.actor = &(dynamic_cast<HeroActor *>(result.actor)->specialActors.at(index));
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -217,7 +217,7 @@ ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other)
|
||||
ExchangeResult result;
|
||||
|
||||
{
|
||||
boost::shared_lock<boost::shared_mutex> lock(sync, boost::try_to_lock);
|
||||
boost::shared_lock lock(sync, boost::try_to_lock);
|
||||
|
||||
if(!lock.owns_lock())
|
||||
{
|
||||
@ -237,7 +237,7 @@ ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other)
|
||||
}
|
||||
|
||||
{
|
||||
boost::unique_lock<boost::shared_mutex> uniqueLock(sync, boost::try_to_lock);
|
||||
boost::unique_lock uniqueLock(sync, boost::try_to_lock);
|
||||
|
||||
if(!uniqueLock.owns_lock())
|
||||
{
|
||||
@ -374,10 +374,12 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
|
||||
for(auto & creatureToBuy : buyArmy)
|
||||
{
|
||||
auto targetSlot = target->getSlotFor(creatureToBuy.creID.toCreature());
|
||||
|
||||
target->addToSlot(targetSlot, creatureToBuy.creID, creatureToBuy.count);
|
||||
target->armyCost += creatureToBuy.creID.toCreature()->getFullRecruitCost() * creatureToBuy.count;
|
||||
target->requireBuyArmy = true;
|
||||
if (targetSlot.validSlot())
|
||||
{
|
||||
target->addToSlot(targetSlot, creatureToBuy.creID, creatureToBuy.count);
|
||||
target->armyCost += creatureToBuy.creID.toCreature()->getFullRecruitCost() * creatureToBuy.count;
|
||||
target->requireBuyArmy = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -440,7 +442,7 @@ int DwellingActor::getInitialTurn(bool waitForGrowth, int dayOfWeek)
|
||||
|
||||
std::string DwellingActor::toString() const
|
||||
{
|
||||
return dwelling->typeName + dwelling->visitablePos().toString();
|
||||
return dwelling->getTypeName() + dwelling->visitablePos().toString();
|
||||
}
|
||||
|
||||
CCreatureSet * DwellingActor::getDwellingCreatures(const CGDwelling * dwelling, bool waitForGrowth)
|
||||
|
@ -113,7 +113,7 @@ public:
|
||||
static const int SPECIAL_ACTORS_COUNT = 7;
|
||||
|
||||
private:
|
||||
ChainActor specialActors[SPECIAL_ACTORS_COUNT];
|
||||
std::array<ChainActor, SPECIAL_ACTORS_COUNT> specialActors;
|
||||
std::unique_ptr<HeroExchangeMap> exchangeMap;
|
||||
|
||||
void setupSpecialActors();
|
||||
|
@ -160,7 +160,7 @@ void GraphPaths::dumpToLog() const
|
||||
node.previous.coord.toString(),
|
||||
tile.first.toString(),
|
||||
node.cost,
|
||||
node.danger);
|
||||
node.linkDanger);
|
||||
}
|
||||
|
||||
logBuilder.addLine(node.previous.coord, tile.first);
|
||||
@ -169,14 +169,17 @@ void GraphPaths::dumpToLog() const
|
||||
});
|
||||
}
|
||||
|
||||
bool GraphPathNode::tryUpdate(const GraphPathNodePointer & pos, const GraphPathNode & prev, const ObjectLink & link)
|
||||
bool GraphPathNode::tryUpdate(
|
||||
const GraphPathNodePointer & pos,
|
||||
const GraphPathNode & prev,
|
||||
const ObjectLink & link)
|
||||
{
|
||||
auto newCost = prev.cost + link.cost;
|
||||
|
||||
if(newCost < cost)
|
||||
{
|
||||
previous = pos;
|
||||
danger = prev.danger + link.danger;
|
||||
linkDanger = link.danger;
|
||||
cost = newCost;
|
||||
|
||||
return true;
|
||||
@ -199,7 +202,7 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
|
||||
|
||||
std::vector<GraphPathNodePointer> tilesToPass;
|
||||
|
||||
uint64_t danger = node.danger;
|
||||
uint64_t danger = node.linkDanger;
|
||||
float cost = node.cost;
|
||||
bool allowBattle = false;
|
||||
|
||||
@ -212,13 +215,13 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
|
||||
if(currentTile == pathNodes.end())
|
||||
break;
|
||||
|
||||
auto currentNode = currentTile->second[current.nodeType];
|
||||
auto & currentNode = currentTile->second[current.nodeType];
|
||||
|
||||
if(!currentNode.previous.valid())
|
||||
break;
|
||||
|
||||
allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE;
|
||||
vstd::amax(danger, currentNode.danger);
|
||||
vstd::amax(danger, currentNode.linkDanger);
|
||||
vstd::amax(cost, currentNode.cost);
|
||||
|
||||
tilesToPass.push_back(current);
|
||||
@ -239,9 +242,13 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
|
||||
if(path.targetHero != hero)
|
||||
continue;
|
||||
|
||||
for(auto graphTile = tilesToPass.rbegin(); graphTile != tilesToPass.rend(); graphTile++)
|
||||
uint64_t loss = 0;
|
||||
uint64_t strength = getHeroArmyStrengthWithCommander(path.targetHero, path.heroArmy);
|
||||
|
||||
for(auto graphTile = ++tilesToPass.rbegin(); graphTile != tilesToPass.rend(); graphTile++)
|
||||
{
|
||||
AIPathNodeInfo n;
|
||||
auto & node = getNode(*graphTile);
|
||||
|
||||
n.coord = graphTile->coord;
|
||||
n.cost = cost;
|
||||
@ -249,7 +256,21 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
|
||||
n.danger = danger;
|
||||
n.targetHero = hero;
|
||||
n.parentIndex = -1;
|
||||
n.specialAction = getNode(*graphTile).specialAction;
|
||||
n.specialAction = node.specialAction;
|
||||
|
||||
if(node.linkDanger > 0)
|
||||
{
|
||||
auto additionalLoss = ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, strength, node.linkDanger);
|
||||
loss += additionalLoss;
|
||||
|
||||
if(strength > additionalLoss)
|
||||
strength -= additionalLoss;
|
||||
else
|
||||
{
|
||||
strength = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(n.specialAction)
|
||||
{
|
||||
@ -264,8 +285,13 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
|
||||
path.nodes.insert(path.nodes.begin(), n);
|
||||
}
|
||||
|
||||
path.armyLoss += ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), danger);
|
||||
path.targetObjectDanger = ai->pathfinder->getStorage()->evaluateDanger(tile, path.targetHero, !allowBattle);
|
||||
if(strength == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
path.armyLoss += loss;
|
||||
path.targetObjectDanger = ai->dangerEvaluator->evaluateDanger(tile, path.targetHero, !allowBattle);
|
||||
path.targetObjectArmyLoss = ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), path.targetObjectDanger);
|
||||
|
||||
paths.push_back(path);
|
||||
@ -287,7 +313,7 @@ void GraphPaths::quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3
|
||||
|
||||
std::vector<GraphPathNodePointer> tilesToPass;
|
||||
|
||||
uint64_t danger = targetNode.danger;
|
||||
uint64_t danger = targetNode.linkDanger;
|
||||
float cost = targetNode.cost;
|
||||
bool allowBattle = false;
|
||||
|
||||
@ -303,7 +329,7 @@ void GraphPaths::quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3
|
||||
auto currentNode = currentTile->second[current.nodeType];
|
||||
|
||||
allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE;
|
||||
vstd::amax(danger, currentNode.danger);
|
||||
vstd::amax(danger, currentNode.linkDanger);
|
||||
vstd::amax(cost, currentNode.cost);
|
||||
|
||||
tilesToPass.push_back(current);
|
||||
@ -330,7 +356,7 @@ void GraphPaths::quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3
|
||||
path.heroArmy = entryPath.heroArmy;
|
||||
path.exchangeCount = entryPath.exchangeCount;
|
||||
path.armyLoss = entryPath.armyLoss + ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), danger);
|
||||
path.targetObjectDanger = ai->pathfinder->getStorage()->evaluateDanger(tile, path.targetHero, !allowBattle);
|
||||
path.targetObjectDanger = ai->dangerEvaluator->evaluateDanger(tile, path.targetHero, !allowBattle);
|
||||
path.targetObjectArmyLoss = ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), path.targetObjectDanger);
|
||||
|
||||
AIPathNodeInfo n;
|
||||
@ -341,7 +367,7 @@ void GraphPaths::quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3
|
||||
// final node
|
||||
n.coord = tile;
|
||||
n.cost = targetNode.cost;
|
||||
n.danger = targetNode.danger;
|
||||
n.danger = danger;
|
||||
n.parentIndex = path.nodes.size();
|
||||
path.nodes.push_back(n);
|
||||
|
||||
@ -368,7 +394,7 @@ void GraphPaths::quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3
|
||||
n.coord = graphTile->coord;
|
||||
n.cost = node.cost;
|
||||
n.turns = static_cast<ui8>(node.cost);
|
||||
n.danger = node.danger;
|
||||
n.danger = danger;
|
||||
n.specialAction = node.specialAction;
|
||||
n.parentIndex = path.nodes.size();
|
||||
|
||||
|
@ -67,7 +67,7 @@ struct GraphPathNode
|
||||
GrapthPathNodeType nodeType = GrapthPathNodeType::NORMAL;
|
||||
GraphPathNodePointer previous;
|
||||
float cost = BAD_COST;
|
||||
uint64_t danger = 0;
|
||||
uint64_t linkDanger = 0;
|
||||
const CGObjectInstance * obj = nullptr;
|
||||
std::shared_ptr<SpecialAction> specialAction;
|
||||
|
||||
|
@ -164,7 +164,7 @@ void ObjectGraphCalculator::calculateConnections(const int3 & pos, std::vector<A
|
||||
auto from = path.targetHero->visitablePos();
|
||||
auto fromObj = actorObjectMap[path.targetHero];
|
||||
|
||||
auto danger = ai->pathfinder->getStorage()->evaluateDanger(pos, path.targetHero, true);
|
||||
auto danger = ai->dangerEvaluator->evaluateDanger(pos, path.targetHero, true);
|
||||
auto updated = target->tryAddConnection(
|
||||
from,
|
||||
pos,
|
||||
@ -220,7 +220,7 @@ void ObjectGraphCalculator::calculateConnections(const int3 & pos, std::vector<A
|
||||
continue;
|
||||
}
|
||||
|
||||
auto danger = ai->pathfinder->getStorage()->evaluateDanger(pos2, path1.targetHero, true);
|
||||
auto danger = ai->dangerEvaluator->evaluateDanger(pos2, path1.targetHero, true);
|
||||
|
||||
auto updated = target->tryAddConnection(
|
||||
pos1,
|
||||
@ -321,7 +321,7 @@ void ObjectGraphCalculator::addObjectActor(const CGObjectInstance * obj)
|
||||
|
||||
void ObjectGraphCalculator::addJunctionActor(const int3 & visitablePos, bool isVirtualBoat)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(syncLock);
|
||||
std::lock_guard lock(syncLock);
|
||||
|
||||
auto internalCb = temporaryActorHeroes.front()->cb;
|
||||
auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(internalCb)).get();
|
||||
|
@ -164,7 +164,7 @@ namespace AIPathfinding
|
||||
if(hero->canCastThisSpell(summonBoatSpell)
|
||||
&& hero->getSpellSchoolLevel(summonBoatSpell) >= MasteryLevel::ADVANCED)
|
||||
{
|
||||
// TODO: For lower school level we might need to check the existance of some boat
|
||||
// TODO: For lower school level we might need to check the existence of some boat
|
||||
summonableVirtualBoats[hero] = std::make_shared<SummonBoatAction>();
|
||||
}
|
||||
}
|
||||
|
@ -11,9 +11,11 @@
|
||||
#include "AIMovementAfterDestinationRule.h"
|
||||
#include "../Actions/BattleAction.h"
|
||||
#include "../Actions/QuestAction.h"
|
||||
#include "../Actions/WhirlpoolAction.h"
|
||||
#include "../../Goals/Invalid.h"
|
||||
#include "AIPreviousNodeRule.h"
|
||||
#include "../../../../lib/pathfinder/PathfinderOptions.h"
|
||||
#include "../../../../lib/pathfinder/CPathfinder.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
@ -34,7 +36,7 @@ namespace AIPathfinding
|
||||
const PathfinderConfig * pathfinderConfig,
|
||||
CPathfinderHelper * pathfinderHelper) const
|
||||
{
|
||||
if(nodeStorage->isMovementIneficient(source, destination))
|
||||
if(nodeStorage->isMovementInefficient(source, destination))
|
||||
{
|
||||
destination.node->locked = true;
|
||||
destination.blocked = true;
|
||||
@ -225,7 +227,7 @@ namespace AIPathfinding
|
||||
return false;
|
||||
}
|
||||
|
||||
auto danger = nodeStorage->evaluateDanger(destination.coord, nodeStorage->getHero(destination.node), true);
|
||||
auto danger = ai->dangerEvaluator->evaluateDanger(destination.coord, nodeStorage->getHero(destination.node), true);
|
||||
|
||||
if(danger)
|
||||
{
|
||||
@ -311,7 +313,7 @@ namespace AIPathfinding
|
||||
}
|
||||
|
||||
auto hero = nodeStorage->getHero(source.node);
|
||||
uint64_t danger = nodeStorage->evaluateDanger(destination.coord, hero, true);
|
||||
uint64_t danger = ai->dangerEvaluator->evaluateDanger(destination.coord, hero, true);
|
||||
uint64_t actualArmyValue = srcNode->actor->armyValue - srcNode->armyLoss;
|
||||
uint64_t loss = nodeStorage->evaluateArmyLoss(hero, actualArmyValue, danger);
|
||||
|
||||
|
@ -1,84 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<CodeBlocks_project_file>
|
||||
<FileVersion major="1" minor="6" />
|
||||
<Project>
|
||||
<Option title="StupidAI" />
|
||||
<Option pch_mode="2" />
|
||||
<Option compiler="gcc" />
|
||||
<Build>
|
||||
<Target title="Debug-win32">
|
||||
<Option platforms="Windows;" />
|
||||
<Option output="../StupidAI" imp_lib="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).a" def_file="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).def" prefix_auto="1" extension_auto="1" />
|
||||
<Option object_output="obj/Debug/x86" />
|
||||
<Option type="3" />
|
||||
<Option compiler="gcc" />
|
||||
<Compiler>
|
||||
<Add option="-ggdb" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add option="-lboost_system$(#boost.libsuffix32)" />
|
||||
<Add option="-lVCMI_lib" />
|
||||
<Add directory="$(#boost.lib32)" />
|
||||
</Linker>
|
||||
</Target>
|
||||
<Target title="Release-win32">
|
||||
<Option platforms="Windows;" />
|
||||
<Option output="../StupidAI" imp_lib="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).a" def_file="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).def" prefix_auto="1" extension_auto="1" />
|
||||
<Option object_output="obj/Release/x86" />
|
||||
<Option type="3" />
|
||||
<Option compiler="gcc" />
|
||||
<Compiler>
|
||||
<Add option="-fomit-frame-pointer" />
|
||||
<Add option="-O3" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add option="-s" />
|
||||
<Add option="-lboost_system$(#boost.libsuffix32)" />
|
||||
<Add option="-lVCMI_lib" />
|
||||
<Add directory="$(#boost.lib32)" />
|
||||
</Linker>
|
||||
</Target>
|
||||
<Target title="Debug-win64">
|
||||
<Option platforms="Windows;" />
|
||||
<Option output="../StupidAI" imp_lib="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).a" def_file="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).def" prefix_auto="1" extension_auto="1" />
|
||||
<Option object_output="obj/Debug/x64" />
|
||||
<Option type="3" />
|
||||
<Option compiler="gnu_gcc_compiler_x64" />
|
||||
<Linker>
|
||||
<Add option="-lboost_system$(#boost.libsuffix64)" />
|
||||
<Add option="-lVCMI_lib" />
|
||||
<Add directory="$(#boost.lib64)" />
|
||||
</Linker>
|
||||
</Target>
|
||||
</Build>
|
||||
<Compiler>
|
||||
<Add option="-pedantic" />
|
||||
<Add option="-Wextra" />
|
||||
<Add option="-Wall" />
|
||||
<Add option="-std=gnu++11" />
|
||||
<Add option="-fexceptions" />
|
||||
<Add option="-Wpointer-arith" />
|
||||
<Add option="-Wno-switch" />
|
||||
<Add option="-Wno-sign-compare" />
|
||||
<Add option="-Wno-unused-parameter" />
|
||||
<Add option="-Wno-overloaded-virtual" />
|
||||
<Add option="-DBOOST_ALL_DYN_LINK" />
|
||||
<Add option="-DBOOST_SYSTEM_NO_DEPRECATED" />
|
||||
<Add option="-D_WIN32_WINNT=0x0600" />
|
||||
<Add option="-D_WIN32" />
|
||||
<Add directory="$(#boost.include)" />
|
||||
<Add directory="../../include" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add directory="../.." />
|
||||
</Linker>
|
||||
<Unit filename="StdInc.h">
|
||||
<Option compile="1" />
|
||||
<Option weight="0" />
|
||||
</Unit>
|
||||
<Unit filename="StupidAI.cpp" />
|
||||
<Unit filename="StupidAI.h" />
|
||||
<Unit filename="main.cpp" />
|
||||
<Extensions />
|
||||
</Project>
|
||||
</CodeBlocks_project_file>
|
@ -18,7 +18,7 @@
|
||||
#include "../../lib/CRandomGenerator.h"
|
||||
|
||||
CStupidAI::CStupidAI()
|
||||
: side(-1)
|
||||
: side(BattleSide::NONE)
|
||||
, wasWaitingForRealize(false)
|
||||
, wasUnlockingGs(false)
|
||||
{
|
||||
@ -262,7 +262,7 @@ void CStupidAI::battleStacksEffectsSet(const BattleID & battleID, const SetStack
|
||||
print("battleStacksEffectsSet called");
|
||||
}
|
||||
|
||||
void CStupidAI::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side, bool replayAllowed)
|
||||
void CStupidAI::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide Side, bool replayAllowed)
|
||||
{
|
||||
print("battleStart called");
|
||||
side = Side;
|
||||
@ -296,7 +296,11 @@ BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stac
|
||||
for(auto hex : hexes)
|
||||
{
|
||||
if(vstd::contains(avHexes, hex))
|
||||
{
|
||||
if(stack->position == hex)
|
||||
return BattleAction::makeDefend(stack);
|
||||
return BattleAction::makeMove(stack, hex);
|
||||
}
|
||||
|
||||
if(stack->coversPos(hex))
|
||||
{
|
||||
@ -336,7 +340,11 @@ BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stac
|
||||
}
|
||||
|
||||
if(vstd::contains(avHexes, currentDest))
|
||||
{
|
||||
if(stack->position == currentDest)
|
||||
return BattleAction::makeDefend(stack);
|
||||
return BattleAction::makeMove(stack, currentDest);
|
||||
}
|
||||
|
||||
currentDest = reachability.predecessors[currentDest];
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ class EnemyInfo;
|
||||
|
||||
class CStupidAI : public CBattleGameInterface
|
||||
{
|
||||
int side;
|
||||
BattleSide side;
|
||||
std::shared_ptr<CBattleCallback> cb;
|
||||
std::shared_ptr<Environment> env;
|
||||
|
||||
@ -47,7 +47,7 @@ public:
|
||||
void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override;
|
||||
void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override;//called when a specific effect is set to stacks
|
||||
//void battleTriggerEffect(const BattleTriggerEffect & bte) override;
|
||||
void battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right
|
||||
void battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right
|
||||
void battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca) override; //called when catapult makes an attack
|
||||
|
||||
private:
|
||||
|
@ -1,152 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="RD|Win32">
|
||||
<Configuration>RD</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="RD|x64">
|
||||
<Configuration>RD</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{15DABC90-234A-4B6B-9EEB-777C4768B82B}</ProjectGuid>
|
||||
<RootNamespace>StupidAI</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<PlatformToolset>v140_xp</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<PlatformToolset>v140_xp</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<PlatformToolset>v140_xp</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\..\VCMI_global_debug.props" />
|
||||
<Import Project="..\..\VCMI_global.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\..\VCMI_global_debug.props" />
|
||||
<Import Project="..\..\VCMI_global.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\..\VCMI_global_release.props" />
|
||||
<Import Project="..\..\VCMI_global.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\..\VCMI_global_release.props" />
|
||||
<Import Project="..\..\VCMI_global.props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<OutDir>$(VCMI_Out)\AI\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<OutDir>$(VCMI_Out)\AI\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">
|
||||
<OutDir>$(VCMI_Out)/AI</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'">
|
||||
<OutDir>$(VCMI_Out)\AI\</OutDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
|
||||
<AdditionalOptions>/Zm150 %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalOptions>-Zm150 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalLibraryDirectories>..\..\..\libs;..\..;..</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
|
||||
<AdditionalOptions>/Zm150 %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(VCMI_Out)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
|
||||
<AdditionalOptions>/Zm150 %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="StdInc.cpp">
|
||||
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">StdInc.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='RD|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="StupidAI.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="StdInc.h" />
|
||||
<ClInclude Include="StupidAI.h" />
|
||||
<ClInclude Include="..\..\Global.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
@ -15,8 +15,6 @@
|
||||
|
||||
#include "../../lib/UnlockGuard.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/mapObjects/CBank.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/mapObjects/CQuest.h"
|
||||
#include "../../lib/mapping/CMapDefines.h"
|
||||
@ -188,7 +186,7 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
|
||||
{
|
||||
// TODO: Such information should be provided by pathfinder
|
||||
// Tile must be free or with unoccupied boat
|
||||
if(!t->blocked)
|
||||
if(!t->blocked())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -249,8 +247,8 @@ bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2)
|
||||
|
||||
bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2)
|
||||
{
|
||||
auto art1 = a1->artType;
|
||||
auto art2 = a2->artType;
|
||||
auto art1 = a1->getType();
|
||||
auto art2 = a2->getType();
|
||||
|
||||
if(art1->getPrice() == art2->getPrice())
|
||||
return art1->valOfBonuses(BonusType::PRIMARY_SKILL) > art2->valOfBonuses(BonusType::PRIMARY_SKILL);
|
||||
|
@ -10,9 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../lib/VCMI_Lib.h"
|
||||
#include "../../lib/CBuildingHandler.h"
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
#include "../../lib/CTownHandler.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
#include "../../lib/CStopWatch.h"
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
@ -27,11 +25,9 @@ using crstring = const std::string &;
|
||||
using dwellingContent = std::pair<ui32, std::vector<CreatureID>>;
|
||||
|
||||
const int ACTUAL_RESOURCE_COUNT = 7;
|
||||
const int ALLOWED_ROAMING_HEROES = 8;
|
||||
|
||||
//implementation-dependent
|
||||
extern const double SAFE_ATTACK_CONSTANT;
|
||||
extern const int GOLD_RESERVE;
|
||||
|
||||
extern thread_local CCallback * cb;
|
||||
extern thread_local VCAI * ai;
|
||||
@ -68,14 +64,6 @@ public:
|
||||
|
||||
const CGHeroInstance * get(bool doWeExpectNull = false) const;
|
||||
bool validAndSet() const;
|
||||
|
||||
|
||||
template<typename Handler> void serialize(Handler & h)
|
||||
{
|
||||
h & this->h;
|
||||
h & hid;
|
||||
h & name;
|
||||
}
|
||||
};
|
||||
|
||||
enum BattleState
|
||||
@ -100,12 +88,6 @@ struct ObjectIdRef
|
||||
ObjectIdRef(const CGObjectInstance * obj);
|
||||
|
||||
bool operator<(const ObjectIdRef & rhs) const;
|
||||
|
||||
|
||||
template<typename Handler> void serialize(Handler & h)
|
||||
{
|
||||
h & id;
|
||||
}
|
||||
};
|
||||
|
||||
struct TimeCheck
|
||||
|
@ -36,7 +36,7 @@ std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, c
|
||||
{
|
||||
for(auto & i : armyPtr->Slots())
|
||||
{
|
||||
auto cre = dynamic_cast<const CCreature*>(i.second->type);
|
||||
auto cre = dynamic_cast<const CCreature*>(i.second->getType());
|
||||
auto & slotInfp = creToPower[cre];
|
||||
|
||||
slotInfp.creature = cre;
|
||||
|
@ -14,8 +14,6 @@
|
||||
|
||||
#include "../../lib/GameConstants.h"
|
||||
#include "../../lib/VCMI_Lib.h"
|
||||
#include "../../lib/CTownHandler.h"
|
||||
#include "../../lib/CBuildingHandler.h"
|
||||
#include "VCAI.h"
|
||||
|
||||
struct SlotInfo
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../lib/entities/building/CBuilding.h"
|
||||
|
||||
bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays)
|
||||
{
|
||||
@ -22,13 +23,13 @@ bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!vstd::contains(t->town->buildings, building))
|
||||
if (!vstd::contains(t->getTown()->buildings, building))
|
||||
return false; // no such building in town
|
||||
|
||||
if (t->hasBuilt(building)) //Already built? Shouldn't happen in general
|
||||
return true;
|
||||
|
||||
const CBuilding * buildPtr = t->town->buildings.at(building);
|
||||
const CBuilding * buildPtr = t->getTown()->buildings.at(building);
|
||||
|
||||
auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID)
|
||||
{
|
||||
@ -50,7 +51,7 @@ bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID
|
||||
|
||||
for (const auto & buildID : toBuild)
|
||||
{
|
||||
const CBuilding * b = t->town->buildings.at(buildID);
|
||||
const CBuilding * b = t->getTown()->buildings.at(buildID);
|
||||
|
||||
EBuildingState canBuild = cb->canBuildStructure(t, buildID);
|
||||
if (canBuild == EBuildingState::ALLOWED)
|
||||
@ -142,9 +143,9 @@ static const std::vector<BuildingID> basicGoldSource = { BuildingID::TOWN_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 };
|
||||
BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7, BuildingID::DWELL_LVL_8 };
|
||||
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 };
|
||||
BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP, BuildingID::DWELL_LVL_8_UP };
|
||||
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 };
|
||||
@ -195,7 +196,7 @@ bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
|
||||
return true;
|
||||
|
||||
//workaround for mantis #2696 - build capitol with separate algorithm if it is available
|
||||
if(vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) && getMaxPossibleGoldBuilding(t) == BuildingID::CAPITOL)
|
||||
if(t->hasBuilt(BuildingID::CITY_HALL) && getMaxPossibleGoldBuilding(t) == BuildingID::CAPITOL)
|
||||
{
|
||||
if(tryBuildNextStructure(t, capitolAndRequirements))
|
||||
return true;
|
||||
@ -219,7 +220,7 @@ bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
|
||||
|
||||
//at the end, try to get and build any extra buildings with nonstandard slots (for example HotA 3rd level dwelling)
|
||||
std::vector<BuildingID> extraBuildings;
|
||||
for (auto buildingInfo : t->town->buildings)
|
||||
for (auto buildingInfo : t->getTown()->buildings)
|
||||
{
|
||||
if (buildingInfo.first > BuildingID::DWELL_UP2_FIRST)
|
||||
extraBuildings.push_back(buildingInfo.first);
|
||||
|
@ -14,8 +14,6 @@
|
||||
|
||||
#include "../../lib/GameConstants.h"
|
||||
#include "../../lib/VCMI_Lib.h"
|
||||
#include "../../lib/CTownHandler.h"
|
||||
#include "../../lib/CBuildingHandler.h"
|
||||
#include "VCAI.h"
|
||||
|
||||
struct DLL_EXPORT PotentialBuilding
|
||||
|
@ -219,12 +219,6 @@ float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, c
|
||||
enemyFlyers->setValue(enemyStructure.flyers);
|
||||
enemySpeed->setValue(enemyStructure.maxSpeed);
|
||||
|
||||
bool bank = dynamic_cast<const CBank *>(enemy);
|
||||
if(bank)
|
||||
bankPresent->setValue(1);
|
||||
else
|
||||
bankPresent->setValue(0);
|
||||
|
||||
const CGTownInstance * fort = dynamic_cast<const CGTownInstance *>(enemy);
|
||||
if(fort)
|
||||
castleWalls->setValue(fort->fortLevel());
|
||||
|
@ -16,7 +16,6 @@
|
||||
#include "../../lib/mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../../lib/mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
|
||||
#include "../../lib/mapObjects/CBank.h"
|
||||
#include "../../lib/mapObjects/CGCreature.h"
|
||||
#include "../../lib/mapObjects/CGDwelling.h"
|
||||
#include "../../lib/gameState/InfoAboutArmy.h"
|
||||
@ -62,25 +61,6 @@ Goals::TSubgoal FuzzyHelper::chooseSolution(Goals::TGoalVec vec)
|
||||
return result;
|
||||
}
|
||||
|
||||
ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
|
||||
{
|
||||
//this one is not fuzzy anymore, just calculate weighted average
|
||||
|
||||
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(bank->cb))
|
||||
{
|
||||
totalStrength += config.second.totalStrength * config.first;
|
||||
totalChance += config.first;
|
||||
}
|
||||
return totalStrength / std::max<ui8>(totalChance, 1); //avoid division by zero
|
||||
|
||||
}
|
||||
|
||||
float FuzzyHelper::evaluate(Goals::VisitTile & g)
|
||||
{
|
||||
if(g.parent)
|
||||
@ -301,32 +281,13 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj, const VCAI * ai)
|
||||
cb->getTownInfo(obj, iat);
|
||||
return iat.army.getStrength();
|
||||
}
|
||||
case Obj::MONSTER:
|
||||
{
|
||||
//TODO!!!!!!!!
|
||||
const CGCreature * cre = dynamic_cast<const CGCreature *>(obj);
|
||||
return cre->getArmyStrength();
|
||||
}
|
||||
case Obj::CREATURE_GENERATOR1:
|
||||
case Obj::CREATURE_GENERATOR4:
|
||||
{
|
||||
const CGDwelling * d = dynamic_cast<const CGDwelling *>(obj);
|
||||
return d->getArmyStrength();
|
||||
}
|
||||
case Obj::MINE:
|
||||
case Obj::ABANDONED_MINE:
|
||||
default:
|
||||
{
|
||||
const CArmedInstance * a = dynamic_cast<const CArmedInstance *>(obj);
|
||||
return a->getArmyStrength();
|
||||
if (a)
|
||||
return a->getArmyStrength();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
case Obj::CRYPT: //crypt
|
||||
case Obj::CREATURE_BANK: //crebank
|
||||
case Obj::DRAGON_UTOPIA:
|
||||
case Obj::SHIPWRECK: //shipwreck
|
||||
case Obj::DERELICT_SHIP: //derelict ship
|
||||
case Obj::PYRAMID:
|
||||
return estimateBankDanger(dynamic_cast<const CBank *>(obj));
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -10,12 +10,6 @@
|
||||
#pragma once
|
||||
#include "FuzzyEngines.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CBank;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class DLL_EXPORT FuzzyHelper
|
||||
{
|
||||
public:
|
||||
@ -42,8 +36,6 @@ public:
|
||||
float evaluate(Goals::AbstractGoal & g);
|
||||
void setPriority(Goals::TSubgoal & g);
|
||||
|
||||
ui64 estimateBankDanger(const CBank * bank); //TODO: move to another class?
|
||||
|
||||
Goals::TSubgoal chooseSolution(Goals::TGoalVec vec);
|
||||
//std::shared_ptr<AbstractGoal> chooseSolution (std::vector<std::shared_ptr<AbstractGoal>> & vec);
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user