mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-28 08:48:48 +02:00
Merge remote-tracking branch 'origin/develop' into reward_select_all
# Conflicts: # lib/mapObjects/CRewardableObject.cpp
This commit is contained in:
commit
fe8bcc5758
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot#example-dependabotyml-file-for-github-actions
|
||||
# Set update schedule for GitHub Actions
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
# Check for updates to GitHub Actions every day
|
||||
interval: "daily"
|
170
.github/workflows/github.yml
vendored
170
.github/workflows/github.yml
vendored
@ -6,9 +6,8 @@ on:
|
||||
- features/*
|
||||
- beta
|
||||
- master
|
||||
- develop
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
@ -16,55 +15,7 @@ env:
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
check_last_build:
|
||||
if: github.event.schedule != ''
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
skip_build: ${{ steps.check_if_built.outputs.skip_build }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Get repo name
|
||||
id: get_repo_name
|
||||
run: echo "::set-output name=value::${GITHUB_REPOSITORY#*/}"
|
||||
- name: Get last successful build for ${{ github.sha }}
|
||||
uses: octokit/request-action@v2.1.0
|
||||
id: get_last_scheduled_run
|
||||
with:
|
||||
route: GET /repos/{owner}/{repo}/actions/runs
|
||||
owner: ${{ github.repository_owner }}
|
||||
repo: ${{ steps.get_repo_name.outputs.value }}
|
||||
status: success
|
||||
per_page: 1
|
||||
head_sha: ${{ github.sha }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Check if successful build of the current commit exists
|
||||
id: check_if_built
|
||||
run: |
|
||||
if [ ${{ fromJson(steps.get_last_scheduled_run.outputs.data).total_count }} -gt 0 ]; then
|
||||
echo '::set-output name=skip_build::1'
|
||||
else
|
||||
echo '::set-output name=skip_build::0'
|
||||
fi
|
||||
- name: Cancel current run
|
||||
if: steps.check_if_built.outputs.skip_build == 1
|
||||
uses: octokit/request-action@v2.1.0
|
||||
with:
|
||||
route: POST /repos/{owner}/{repo}/actions/runs/{run_id}/cancel
|
||||
owner: ${{ github.repository_owner }}
|
||||
repo: ${{ steps.get_repo_name.outputs.value }}
|
||||
run_id: ${{ github.run_id }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Wait for the run to be cancelled
|
||||
if: steps.check_if_built.outputs.skip_build == 1
|
||||
run: sleep 60
|
||||
|
||||
build:
|
||||
needs: check_last_build
|
||||
if: always() && needs.check_last_build.skip_build != 1
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@ -73,8 +24,8 @@ jobs:
|
||||
test: 0
|
||||
preset: linux-clang-test
|
||||
- platform: linux
|
||||
os: ubuntu-20.04
|
||||
test: 0
|
||||
os: ubuntu-22.04
|
||||
test: 1
|
||||
preset: linux-gcc-test
|
||||
- platform: linux
|
||||
os: ubuntu-20.04
|
||||
@ -84,6 +35,7 @@ jobs:
|
||||
os: macos-12
|
||||
test: 0
|
||||
pack: 1
|
||||
pack_type: Release
|
||||
extension: dmg
|
||||
preset: macos-conan-ninja-release
|
||||
conan_profile: macos-intel
|
||||
@ -93,6 +45,7 @@ jobs:
|
||||
os: macos-12
|
||||
test: 0
|
||||
pack: 1
|
||||
pack_type: Release
|
||||
extension: dmg
|
||||
preset: macos-arm-conan-ninja-release
|
||||
conan_profile: macos-arm
|
||||
@ -102,6 +55,7 @@ jobs:
|
||||
os: macos-12
|
||||
test: 0
|
||||
pack: 1
|
||||
pack_type: Release
|
||||
extension: ipa
|
||||
preset: ios-release-conan-ccache
|
||||
conan_profile: ios-arm64
|
||||
@ -110,17 +64,29 @@ jobs:
|
||||
os: windows-latest
|
||||
test: 0
|
||||
pack: 1
|
||||
pack_type: RelWithDebInfo
|
||||
extension: exe
|
||||
preset: windows-msvc-release-ccache
|
||||
- platform: mingw-ubuntu
|
||||
- platform: mingw
|
||||
os: ubuntu-22.04
|
||||
test: 0
|
||||
pack: 1
|
||||
pack_type: Release
|
||||
extension: exe
|
||||
cpack_args: -D CPACK_NSIS_EXECUTABLE=`which makensis`
|
||||
cmake_args: -G Ninja
|
||||
preset: windows-mingw-conan-linux
|
||||
conan_profile: mingw64-linux.jinja
|
||||
- platform: mingw-32
|
||||
os: ubuntu-22.04
|
||||
test: 0
|
||||
pack: 1
|
||||
pack_type: Release
|
||||
extension: exe
|
||||
cpack_args: -D CPACK_NSIS_EXECUTABLE=`which makensis`
|
||||
cmake_args: -G Ninja
|
||||
preset: windows-mingw-conan-linux
|
||||
conan_profile: mingw32-linux.jinja
|
||||
- platform: android-32
|
||||
os: ubuntu-22.04
|
||||
extension: apk
|
||||
@ -141,7 +107,7 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@ -150,7 +116,7 @@ jobs:
|
||||
run: |
|
||||
find . -path ./.git -prune -o -path ./AI/FuzzyLite -prune -o -path ./test/googletest \
|
||||
-o -path ./osx -prune -o -type f \
|
||||
-not -name '*.png' -and -not -name '*.vcxproj*' -and -not -name '*.props' -and -not -name '*.wav' -and -not -name '*.ico' -and -not -name '*.bat' -print0 | \
|
||||
-not -name '*.png' -and -not -name '*.vcxproj*' -and -not -name '*.props' -and -not -name '*.wav' -and -not -name '*.webm' -and -not -name '*.ico' -and -not -name '*.bat' -print0 | \
|
||||
{ ! xargs -0 grep -l -z -P '\r\n'; }
|
||||
|
||||
- name: Validate JSON
|
||||
@ -158,7 +124,7 @@ jobs:
|
||||
# also, running it on multiple presets is redundant and slightly increases already long CI built times
|
||||
if: ${{ startsWith(matrix.preset, 'linux-clang-test') }}
|
||||
run: |
|
||||
pip3 install json5 jstyleson
|
||||
pip3 install jstyleson
|
||||
python3 CI/linux-qt6/validate_json.py
|
||||
|
||||
- name: Dependencies
|
||||
@ -168,7 +134,7 @@ jobs:
|
||||
|
||||
# ensure the ccache for each PR is separate so they don't interfere with each other
|
||||
# fall back to ccache of the vcmi/vcmi repo if no PR-specific ccache is found
|
||||
- name: Ccache for PRs
|
||||
- name: ccache for PRs
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
if: ${{ github.event.number != '' }}
|
||||
with:
|
||||
@ -180,9 +146,9 @@ jobs:
|
||||
max-size: "5G"
|
||||
verbose: 2
|
||||
|
||||
- name: Ccache for everything but PRs
|
||||
- name: ccache for everything but PRs
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
if: ${{ github.event.number == '' }}
|
||||
if: ${{ (github.repository == 'vcmi/vcmi' && github.event.number == '' && github.ref == 'refs/heads/develop') || github.repository != 'vcmi/vcmi' }}
|
||||
with:
|
||||
key: ${{ matrix.preset }}-no-PR
|
||||
restore-keys: |
|
||||
@ -191,7 +157,17 @@ jobs:
|
||||
max-size: "5G"
|
||||
verbose: 2
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
- name: Prepare Heroes 3 data
|
||||
env:
|
||||
HEROES_3_DATA_PASSWORD: ${{ secrets.HEROES_3_DATA_PASSWORD }}
|
||||
if: ${{ env.HEROES_3_DATA_PASSWORD != '' && matrix.test == 1 }}
|
||||
run: |
|
||||
wget --progress=dot:giga https://github.com/vcmi-mods/vcmi-test-data/releases/download/v1.0/h3_assets.zip
|
||||
7za x h3_assets.zip -p$HEROES_3_DATA_PASSWORD
|
||||
mkdir -p ~/.local/share/vcmi/
|
||||
mv h3_assets/* ~/.local/share/vcmi/
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
if: "${{ matrix.conan_profile != '' }}"
|
||||
with:
|
||||
python-version: '3.10'
|
||||
@ -211,10 +187,6 @@ jobs:
|
||||
env:
|
||||
GENERATE_ONLY_BUILT_CONFIG: 1
|
||||
|
||||
- name: Git branch name
|
||||
id: git-branch-name
|
||||
uses: EthanSK/git-branch-name-action@v1
|
||||
|
||||
- name: Build Number
|
||||
run: |
|
||||
source '${{github.workspace}}/CI/get_package_name.sh'
|
||||
@ -227,31 +199,42 @@ jobs:
|
||||
env:
|
||||
PULL_REQUEST: ${{ github.event.pull_request.number }}
|
||||
|
||||
- name: CMake Preset with ccache
|
||||
- name: Configure
|
||||
run: |
|
||||
cmake -DCMAKE_CXX_COMPILER_LAUNCHER=ccache --preset ${{ matrix.preset }}
|
||||
if [[ ${{matrix.preset}} == linux-gcc-test ]]; then GCC13=1; fi
|
||||
cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }} ${GCC13:+-DCMAKE_C_COMPILER=gcc-13 -DCMAKE_CXX_COMPILER=g++-13}
|
||||
|
||||
- name: Build Preset
|
||||
- name: Build
|
||||
run: |
|
||||
cmake --build --preset ${{matrix.preset}}
|
||||
|
||||
- name: Test
|
||||
if: ${{ matrix.test == 1 }}
|
||||
env:
|
||||
HEROES_3_DATA_PASSWORD: ${{ secrets.HEROES_3_DATA_PASSWORD }}
|
||||
if: ${{ env.HEROES_3_DATA_PASSWORD != '' && matrix.test == 1 }}
|
||||
run: |
|
||||
ctest --preset ${{matrix.preset}}
|
||||
|
||||
- name: Kill XProtect to work around CPack issue on macOS
|
||||
if: ${{ startsWith(matrix.platform, 'mac') }}
|
||||
run: |
|
||||
# Cf. https://github.com/actions/runner-images/issues/7522#issuecomment-1556766641
|
||||
echo Killing...; sudo pkill -9 XProtect >/dev/null || true;
|
||||
echo "Waiting..."; counter=0; while pgrep XProtect && ((counter < 20)); do sleep 3; ((counter++)); done
|
||||
pgrep XProtect || true
|
||||
|
||||
- name: Pack
|
||||
id: cpack
|
||||
if: ${{ matrix.pack == 1 }}
|
||||
run: |
|
||||
cd '${{github.workspace}}/out/build/${{matrix.preset}}'
|
||||
CPACK_PATH=`which -a cpack | grep -m1 -v -i chocolatey`
|
||||
"$CPACK_PATH" -C ${{env.BUILD_TYPE}} ${{ matrix.cpack_args }}
|
||||
counter=0; until "$CPACK_PATH" -C ${{matrix.pack_type}} ${{ matrix.cpack_args }} || ((counter > 20)); do sleep 3; ((counter++)); done
|
||||
test -f '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' \
|
||||
&& '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' '${{github.workspace}}' "$(ls '${{ env.VCMI_PACKAGE_FILE_NAME }}'.*)"
|
||||
rm -rf _CPack_Packages
|
||||
|
||||
- name: Create android package
|
||||
- name: Create Android package
|
||||
if: ${{ startsWith(matrix.platform, 'android') }}
|
||||
run: |
|
||||
cd android
|
||||
@ -266,7 +249,7 @@ jobs:
|
||||
|
||||
- name: Artifacts
|
||||
if: ${{ matrix.pack == 1 }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
|
||||
path: |
|
||||
@ -274,22 +257,30 @@ jobs:
|
||||
|
||||
- name: Android artifacts
|
||||
if: ${{ startsWith(matrix.platform, 'android') }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
|
||||
path: |
|
||||
${{ env.ANDROID_APK_PATH }}
|
||||
|
||||
- name: Symbols
|
||||
if: ${{ matrix.platform == 'msvc' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} - symbols
|
||||
path: |
|
||||
${{github.workspace}}/**/*.pdb
|
||||
|
||||
- name: Android JNI ${{matrix.platform}}
|
||||
if: ${{ startsWith(matrix.platform, 'android') && github.ref == 'refs/heads/master' }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Android JNI ${{matrix.platform}}
|
||||
path: |
|
||||
${{ github.workspace }}/android/vcmi-app/src/main/jniLibs
|
||||
|
||||
- name: Upload build
|
||||
if: ${{ (matrix.pack == 1 || startsWith(matrix.platform, 'android')) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc' }}
|
||||
if: ${{ (matrix.pack == 1 || startsWith(matrix.platform, 'android')) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc' && matrix.platform != 'mingw-32' }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
if cd '${{github.workspace}}/android/vcmi-app/build/outputs/apk/daily' ; then
|
||||
@ -301,14 +292,6 @@ jobs:
|
||||
env:
|
||||
DEPLOY_RSA: ${{ secrets.DEPLOY_RSA }}
|
||||
PACKAGE_EXTENSION: ${{ matrix.extension }}
|
||||
|
||||
- uses: act10ns/slack@v1
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
channel: '#notifications'
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
if: always()
|
||||
|
||||
# copy-pasted mostly
|
||||
bundle_release:
|
||||
@ -331,7 +314,7 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@ -340,10 +323,11 @@ jobs:
|
||||
env:
|
||||
VCMI_BUILD_PLATFORM: x64
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
if: "${{ matrix.conan_profile != '' }}"
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Conan setup
|
||||
if: "${{ matrix.conan_profile != '' }}"
|
||||
run: |
|
||||
@ -359,10 +343,6 @@ jobs:
|
||||
env:
|
||||
GENERATE_ONLY_BUILT_CONFIG: 1
|
||||
|
||||
- name: Git branch name
|
||||
id: git-branch-name
|
||||
uses: EthanSK/git-branch-name-action@v1
|
||||
|
||||
- name: Build Number
|
||||
run: |
|
||||
source '${{github.workspace}}/CI/get_package_name.sh'
|
||||
@ -384,12 +364,12 @@ jobs:
|
||||
cmake --build --preset ${{matrix.preset}}
|
||||
|
||||
- name: Download libs x64
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: Android JNI android-64
|
||||
path: ${{ github.workspace }}/android/vcmi-app/src/main/jniLibs/
|
||||
|
||||
- name: Create android package
|
||||
- name: Create Android package
|
||||
run: |
|
||||
cd android
|
||||
./gradlew bundleRelease --info
|
||||
@ -399,16 +379,8 @@ jobs:
|
||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
|
||||
- name: Android artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.VCMI_PACKAGE_FILE_NAME }}
|
||||
path: |
|
||||
${{ env.ANDROID_APK_PATH }}
|
||||
|
||||
- uses: act10ns/slack@v1
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
channel: '#notifications'
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
if: always()
|
||||
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,12 +1,15 @@
|
||||
/client/vcmiclient
|
||||
/server/vcmiserver
|
||||
/launcher/.lupdate
|
||||
/launcher/vcmilauncher
|
||||
/mapeditor/.lupdate
|
||||
/launcher/vcmilauncher_automoc.cpp
|
||||
/conan-*
|
||||
|
||||
build/
|
||||
.cache/*
|
||||
out/
|
||||
/.qt
|
||||
*.dll
|
||||
*.exe
|
||||
*.depend
|
||||
@ -41,6 +44,8 @@ VCMI_VS11.sdf
|
||||
VCMI_VS11.opensdf
|
||||
.DS_Store
|
||||
CMakeUserPresets.json
|
||||
compile_commands.json
|
||||
fuzzylite.pc
|
||||
|
||||
# Visual Studio
|
||||
*.suo
|
||||
@ -61,5 +66,8 @@ CMakeUserPresets.json
|
||||
/deps
|
||||
.vs/
|
||||
|
||||
# Visual Studio Code
|
||||
/.vscode/
|
||||
|
||||
# CLion
|
||||
.idea/
|
||||
|
@ -33,7 +33,8 @@ void DamageCache::buildDamageCache(std::shared_ptr<HypotheticBattle> hb, int sid
|
||||
return u->isValidTarget();
|
||||
});
|
||||
|
||||
std::vector<const battle::Unit *> ourUnits, enemyUnits;
|
||||
std::vector<const battle::Unit *> ourUnits;
|
||||
std::vector<const battle::Unit *> enemyUnits;
|
||||
|
||||
for(auto stack : stacks)
|
||||
{
|
||||
@ -61,16 +62,12 @@ void DamageCache::buildDamageCache(std::shared_ptr<HypotheticBattle> hb, int sid
|
||||
|
||||
int64_t DamageCache::getDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb)
|
||||
{
|
||||
auto damage = damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount();
|
||||
bool wasComputedBefore = damageCache[attacker->unitId()].count(defender->unitId());
|
||||
|
||||
if(damage == 0)
|
||||
{
|
||||
if (!wasComputedBefore)
|
||||
cacheDamage(attacker, defender, hb);
|
||||
|
||||
damage = damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount();
|
||||
}
|
||||
|
||||
return static_cast<int64_t>(damage);
|
||||
return damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount();
|
||||
}
|
||||
|
||||
int64_t DamageCache::getOriginalDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb)
|
||||
@ -295,8 +292,10 @@ AttackPossibility AttackPossibility::evaluate(
|
||||
|
||||
for(int i = 0; i < totalAttacks; i++)
|
||||
{
|
||||
int64_t damageDealt, damageReceived;
|
||||
float defenderDamageReduce, attackerDamageReduce;
|
||||
int64_t damageDealt;
|
||||
int64_t damageReceived;
|
||||
float defenderDamageReduce;
|
||||
float attackerDamageReduce;
|
||||
|
||||
DamageEstimation retaliation;
|
||||
auto attackDmg = state->battleEstimateDamage(ap.attack, &retaliation);
|
||||
|
@ -10,7 +10,6 @@
|
||||
#pragma once
|
||||
#include "../../lib/battle/CUnitState.h"
|
||||
#include "../../CCallback.h"
|
||||
#include "common.h"
|
||||
#include "StackWithBonuses.h"
|
||||
|
||||
#define BATTLE_TRACE_LEVEL 0
|
||||
|
@ -49,7 +49,6 @@ CBattleAI::~CBattleAI()
|
||||
|
||||
void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB)
|
||||
{
|
||||
setCbc(CB);
|
||||
env = ENV;
|
||||
cb = CB;
|
||||
playerID = *CB->getPlayerID();
|
||||
@ -90,7 +89,8 @@ void CBattleAI::yourTacticPhase(const BattleID & battleID, int distance)
|
||||
static float getStrengthRatio(std::shared_ptr<CBattleInfoCallback> cb, int side)
|
||||
{
|
||||
auto stacks = cb->battleGetAllStacks();
|
||||
auto our = 0, enemy = 0;
|
||||
auto our = 0;
|
||||
auto enemy = 0;
|
||||
|
||||
for(auto stack : stacks)
|
||||
{
|
||||
@ -120,7 +120,6 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
|
||||
};
|
||||
|
||||
BattleAction result = BattleAction::makeDefend(stack);
|
||||
setCbc(cb); //TODO: make solid sure that AIs always use their callbacks (need to take care of event handlers too)
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
@ -145,7 +144,7 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
|
||||
|
||||
result = evaluator.selectStackAction(stack);
|
||||
|
||||
if(!skipCastUntilNextBattle && evaluator.canCastSpell())
|
||||
if(autobattlePreferences.enableSpellsUsage && !skipCastUntilNextBattle && evaluator.canCastSpell())
|
||||
{
|
||||
auto spelCasted = evaluator.attemptCastingSpell(stack);
|
||||
|
||||
|
@ -22,6 +22,8 @@
|
||||
#include "../../lib/battle/BattleStateInfoForRetreat.h"
|
||||
#include "../../lib/battle/CObstacleInstance.h"
|
||||
#include "../../lib/battle/BattleAction.h"
|
||||
#include "../../lib/CRandomGenerator.h"
|
||||
|
||||
|
||||
// TODO: remove
|
||||
// Eventually only IBattleInfoCallback and battle::Unit should be used,
|
||||
@ -70,8 +72,9 @@ std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
|
||||
std::optional<PossibleSpellcast> BattleEvaluator::findBestCreatureSpell(const CStack *stack)
|
||||
{
|
||||
//TODO: faerie dragon type spell should be selected by server
|
||||
SpellID creatureSpellToCast = cb->getBattle(battleID)->battleGetRandomStackSpell(CRandomGenerator::getDefault(), stack, CBattleInfoCallback::RANDOM_AIMED);
|
||||
if(stack->hasBonusOfType(BonusType::SPELLCASTER) && stack->canCast() && creatureSpellToCast != SpellID::NONE)
|
||||
SpellID creatureSpellToCast = cb->getBattle(battleID)->getRandomCastedSpell(CRandomGenerator::getDefault(), stack);
|
||||
|
||||
if(stack->canCast() && creatureSpellToCast != SpellID::NONE)
|
||||
{
|
||||
const CSpell * spell = creatureSpellToCast.toSpell();
|
||||
|
||||
@ -146,7 +149,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
(int)bestAttack.from,
|
||||
(int)bestAttack.attack.attacker->getPosition().hex,
|
||||
bestAttack.attack.chargeDistance,
|
||||
bestAttack.attack.attacker->speed(0, true),
|
||||
bestAttack.attack.attacker->getMovementRange(0),
|
||||
bestAttack.defenderDamageReduce,
|
||||
bestAttack.attackerDamageReduce,
|
||||
score
|
||||
@ -224,7 +227,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
}
|
||||
}
|
||||
|
||||
return BattleAction::makeDefend(stack);
|
||||
return stack->waited() ? BattleAction::makeDefend(stack) : BattleAction::makeWait(stack);
|
||||
}
|
||||
|
||||
uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock> start)
|
||||
@ -349,10 +352,11 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
LOGL("Casting spells sounds like fun. Let's see...");
|
||||
//Get all spells we can cast
|
||||
std::vector<const CSpell*> possibleSpells;
|
||||
vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [hero, this](const CSpell *s) -> bool
|
||||
{
|
||||
return s->canBeCast(cb->getBattle(battleID).get(), spells::Mode::HERO, hero);
|
||||
});
|
||||
|
||||
for (auto const & s : VLC->spellh->objects)
|
||||
if (s->canBeCast(cb->getBattle(battleID).get(), spells::Mode::HERO, hero))
|
||||
possibleSpells.push_back(s.get());
|
||||
|
||||
LOGFL("I can cast %d spells.", possibleSpells.size());
|
||||
|
||||
vstd::erase_if(possibleSpells, [](const CSpell *s)
|
||||
@ -427,33 +431,36 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
|
||||
state->nextTurn(unit->unitId());
|
||||
|
||||
PotentialTargets pt(unit, damageCache, state);
|
||||
PotentialTargets potentialTargets(unit, damageCache, state);
|
||||
|
||||
if(!pt.possibleAttacks.empty())
|
||||
if(!potentialTargets.possibleAttacks.empty())
|
||||
{
|
||||
AttackPossibility ap = pt.bestAction();
|
||||
AttackPossibility attackPossibility = potentialTargets.bestAction();
|
||||
|
||||
auto swb = state->getForUpdate(unit->unitId());
|
||||
*swb = *ap.attackerState;
|
||||
auto stackWithBonuses = state->getForUpdate(unit->unitId());
|
||||
*stackWithBonuses = *attackPossibility.attackerState;
|
||||
|
||||
if(ap.defenderDamageReduce > 0)
|
||||
swb->removeUnitBonus(Bonus::UntilAttack);
|
||||
if(ap.attackerDamageReduce > 0)
|
||||
swb->removeUnitBonus(Bonus::UntilBeingAttacked);
|
||||
|
||||
for(auto affected : ap.affectedUnits)
|
||||
if(attackPossibility.defenderDamageReduce > 0)
|
||||
{
|
||||
swb = state->getForUpdate(affected->unitId());
|
||||
*swb = *affected;
|
||||
stackWithBonuses->removeUnitBonus(Bonus::UntilAttack);
|
||||
stackWithBonuses->removeUnitBonus(Bonus::UntilOwnAttack);
|
||||
}
|
||||
if(attackPossibility.attackerDamageReduce > 0)
|
||||
stackWithBonuses->removeUnitBonus(Bonus::UntilBeingAttacked);
|
||||
|
||||
if(ap.defenderDamageReduce > 0)
|
||||
swb->removeUnitBonus(Bonus::UntilBeingAttacked);
|
||||
if(ap.attackerDamageReduce > 0 && ap.attack.defender->unitId() == affected->unitId())
|
||||
swb->removeUnitBonus(Bonus::UntilAttack);
|
||||
for(auto affected : attackPossibility.affectedUnits)
|
||||
{
|
||||
stackWithBonuses = state->getForUpdate(affected->unitId());
|
||||
*stackWithBonuses = *affected;
|
||||
|
||||
if(attackPossibility.defenderDamageReduce > 0)
|
||||
stackWithBonuses->removeUnitBonus(Bonus::UntilBeingAttacked);
|
||||
if(attackPossibility.attackerDamageReduce > 0 && attackPossibility.attack.defender->unitId() == affected->unitId())
|
||||
stackWithBonuses->removeUnitBonus(Bonus::UntilAttack);
|
||||
}
|
||||
}
|
||||
|
||||
auto bav = pt.bestActionValue();
|
||||
auto bav = potentialTargets.bestActionValue();
|
||||
|
||||
//best action is from effective owner`s point if view, we need to convert to our point if view
|
||||
if(state->battleGetOwner(unit) != playerID)
|
||||
@ -549,7 +556,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
auto needFullEval = vstd::contains_if(allUnits, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
auto original = cb->getBattle(battleID)->battleGetUnitByID(u->unitId());
|
||||
return !original || u->speed() != original->speed();
|
||||
return !original || u->getMovementRange() != original->getMovementRange();
|
||||
});
|
||||
|
||||
DamageCache safeCopy = damageCache;
|
||||
@ -605,7 +612,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
|
||||
if(ourUnit * goodEffect == 1)
|
||||
{
|
||||
if(ourUnit && goodEffect && (unit->isClone() || unit->isGhost() || !unit->unitSlot().validSlot()))
|
||||
if(ourUnit && goodEffect && (unit->isClone() || unit->isGhost()))
|
||||
continue;
|
||||
|
||||
ps.value += dpsReduce * scoreEvaluator.getPositiveEffectMultiplier();
|
||||
|
@ -258,7 +258,9 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
|
||||
updateReachabilityMap(hb);
|
||||
|
||||
if(result.bestAttack.attack.shooting && hb->battleHasShootingPenalty(activeStack, result.bestAttack.dest))
|
||||
if(result.bestAttack.attack.shooting
|
||||
&& !activeStack->waited()
|
||||
&& hb->battleHasShootingPenalty(activeStack, result.bestAttack.dest))
|
||||
{
|
||||
if(!canBeHitThisTurn(result.bestAttack))
|
||||
return result; // lets wait
|
||||
@ -268,7 +270,7 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
{
|
||||
float score = evaluateExchange(ap, 0, targets, damageCache, hb);
|
||||
|
||||
if(score > result.score || (score == result.score && result.wait))
|
||||
if(score > result.score || (vstd::isAlmostEqual(score, result.score) && result.wait))
|
||||
{
|
||||
result.score = score;
|
||||
result.bestAttack = ap;
|
||||
@ -295,7 +297,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
if(targets.unreachableEnemies.empty())
|
||||
return result;
|
||||
|
||||
auto speed = activeStack->speed();
|
||||
auto speed = activeStack->getMovementRange();
|
||||
|
||||
if(speed == 0)
|
||||
return result;
|
||||
@ -322,7 +324,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
|
||||
auto turnsToRich = (distance - 1) / speed + 1;
|
||||
auto hexes = closestStack->getSurroundingHexes();
|
||||
auto enemySpeed = closestStack->speed();
|
||||
auto enemySpeed = closestStack->getMovementRange();
|
||||
auto speedRatio = speed / static_cast<float>(enemySpeed);
|
||||
auto multiplier = speedRatio > 1 ? 1 : speedRatio;
|
||||
|
||||
@ -481,11 +483,6 @@ float BattleExchangeEvaluator::evaluateExchange(
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> hb)
|
||||
{
|
||||
if(ap.from.hex == 127)
|
||||
{
|
||||
logAi->trace("x");
|
||||
}
|
||||
|
||||
BattleScore score = calculateExchange(ap, turn, targets, damageCache, hb);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
@ -687,11 +684,6 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
for(auto hex : hexes)
|
||||
reachabilityMap[hex] = getOneTurnReachableUnits(turn, hex);
|
||||
|
||||
if(!ap.attack.shooting)
|
||||
{
|
||||
v.adjustPositions(melleeAttackers, ap, reachabilityMap);
|
||||
}
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Exchange score: enemy: %2f, our -%2f", v.getScore().enemyDamageReduce, v.getScore().ourDamageReduce);
|
||||
#endif
|
||||
@ -699,69 +691,6 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
return v.getScore();
|
||||
}
|
||||
|
||||
void BattleExchangeVariant::adjustPositions(
|
||||
std::vector<const battle::Unit*> attackers,
|
||||
const AttackPossibility & ap,
|
||||
std::map<BattleHex, battle::Units> & reachabilityMap)
|
||||
{
|
||||
auto hexes = ap.attack.defender->getSurroundingHexes();
|
||||
|
||||
boost::sort(attackers, [&](const battle::Unit * u1, const battle::Unit * u2) -> bool
|
||||
{
|
||||
if(attackerValue[u1->unitId()].isRetalitated && !attackerValue[u2->unitId()].isRetalitated)
|
||||
return true;
|
||||
|
||||
if(attackerValue[u2->unitId()].isRetalitated && !attackerValue[u1->unitId()].isRetalitated)
|
||||
return false;
|
||||
|
||||
return attackerValue[u1->unitId()].value > attackerValue[u2->unitId()].value;
|
||||
});
|
||||
|
||||
vstd::erase_if_present(hexes, ap.from);
|
||||
vstd::erase_if_present(hexes, ap.attack.attacker->occupiedHex(ap.attack.attackerPos));
|
||||
|
||||
float notRealizedDamage = 0;
|
||||
|
||||
for(auto unit : attackers)
|
||||
{
|
||||
if(unit->unitId() == ap.attack.attacker->unitId())
|
||||
continue;
|
||||
|
||||
if(!vstd::contains_if(hexes, [&](BattleHex h) -> bool
|
||||
{
|
||||
return vstd::contains(reachabilityMap[h], unit);
|
||||
}))
|
||||
{
|
||||
notRealizedDamage += attackerValue[unit->unitId()].value;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto desiredPosition = vstd::minElementByFun(hexes, [&](BattleHex h) -> float
|
||||
{
|
||||
auto score = vstd::contains(reachabilityMap[h], unit)
|
||||
? reachabilityMap[h].size()
|
||||
: 0;
|
||||
|
||||
if(unit->doubleWide())
|
||||
{
|
||||
auto backHex = unit->occupiedHex(h);
|
||||
|
||||
if(vstd::contains(hexes, backHex))
|
||||
score += reachabilityMap[backHex].size();
|
||||
}
|
||||
|
||||
return score;
|
||||
});
|
||||
|
||||
hexes.erase(desiredPosition);
|
||||
}
|
||||
|
||||
if(notRealizedDamage > ap.attackValue() && notRealizedDamage > attackerValue[ap.attack.attacker->unitId()].value)
|
||||
{
|
||||
dpsScore = BattleScore(EvaluationResult::INEFFECTIVE_SCORE, 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap)
|
||||
{
|
||||
for(auto pos : ap.attack.attacker->getSurroundingHexes())
|
||||
@ -824,7 +753,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
|
||||
continue;
|
||||
}
|
||||
|
||||
auto unitSpeed = unit->speed(turn);
|
||||
auto unitSpeed = unit->getMovementRange(turn);
|
||||
auto radius = unitSpeed * (turn + 1);
|
||||
|
||||
ReachabilityInfo unitReachability = vstd::getOrCompute(
|
||||
@ -887,14 +816,15 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
|
||||
continue;
|
||||
|
||||
auto blockedUnitDamage = unit->getMinDamage(hb.battleCanShoot(unit)) * unit->getCount();
|
||||
auto ratio = blockedUnitDamage / (blockedUnitDamage + activeUnitDamage);
|
||||
float ratio = blockedUnitDamage / (float)(blockedUnitDamage + activeUnitDamage + 0.01);
|
||||
|
||||
auto unitReachability = turnBattle.getReachability(unit);
|
||||
auto unitSpeed = unit->getMovementRange(turn); // Cached value, to avoid performance hit
|
||||
|
||||
for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
|
||||
{
|
||||
bool enemyUnit = false;
|
||||
bool reachable = unitReachability.distances[hex] <= unit->speed(turn);
|
||||
bool reachable = unitReachability.distances[hex] <= unitSpeed;
|
||||
|
||||
if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
|
||||
{
|
||||
@ -906,14 +836,14 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
|
||||
|
||||
for(BattleHex neighbor : hex.neighbouringTiles())
|
||||
{
|
||||
reachable = unitReachability.distances[neighbor] <= unit->speed(turn);
|
||||
reachable = unitReachability.distances[neighbor] <= unitSpeed;
|
||||
|
||||
if(reachable) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!reachable && vstd::contains(reachabilityMap[hex], unit))
|
||||
if(!reachable && std::count(reachabilityMap[hex].begin(), reachabilityMap[hex].end(), unit) > 1)
|
||||
{
|
||||
blockingScore += ratio * (enemyUnit ? BLOCKING_OWN_ATTACK_PENALTY : BLOCKING_OWN_MOVE_PENALTY);
|
||||
}
|
||||
|
@ -106,11 +106,6 @@ public:
|
||||
|
||||
const BattleScore & getScore() const { return dpsScore; }
|
||||
|
||||
void adjustPositions(
|
||||
std::vector<const battle::Unit *> attackers,
|
||||
const AttackPossibility & ap,
|
||||
std::map<BattleHex, battle::Units> & reachabilityMap);
|
||||
|
||||
private:
|
||||
BattleScore dpsScore;
|
||||
std::map<uint32_t, AttackerValue> attackerValue;
|
||||
|
@ -2,7 +2,6 @@ set(battleAI_SRCS
|
||||
AttackPossibility.cpp
|
||||
BattleAI.cpp
|
||||
BattleEvaluator.cpp
|
||||
common.cpp
|
||||
EnemyInfo.cpp
|
||||
PossibleSpellcast.cpp
|
||||
PotentialTargets.cpp
|
||||
@ -17,7 +16,6 @@ set(battleAI_HEADERS
|
||||
AttackPossibility.h
|
||||
BattleAI.h
|
||||
BattleEvaluator.h
|
||||
common.h
|
||||
EnemyInfo.h
|
||||
PotentialTargets.h
|
||||
PossibleSpellcast.h
|
||||
@ -26,12 +24,12 @@ set(battleAI_HEADERS
|
||||
BattleExchangeVariant.h
|
||||
)
|
||||
|
||||
if(NOT ENABLE_STATIC_AI_LIBS)
|
||||
if(NOT ENABLE_STATIC_LIBS)
|
||||
list(APPEND battleAI_SRCS main.cpp StdInc.cpp)
|
||||
endif()
|
||||
assign_source_group(${battleAI_SRCS} ${battleAI_HEADERS})
|
||||
|
||||
if(ENABLE_STATIC_AI_LIBS)
|
||||
if(ENABLE_STATIC_LIBS)
|
||||
add_library(BattleAI STATIC ${battleAI_SRCS} ${battleAI_HEADERS})
|
||||
else()
|
||||
add_library(BattleAI SHARED ${battleAI_SRCS} ${battleAI_HEADERS})
|
||||
@ -39,7 +37,7 @@ else()
|
||||
endif()
|
||||
|
||||
target_include_directories(BattleAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(BattleAI PRIVATE ${VCMI_LIB_TARGET} TBB::tbb)
|
||||
target_link_libraries(BattleAI PRIVATE vcmi TBB::tbb)
|
||||
|
||||
vcmi_set_output_dir(BattleAI "AI")
|
||||
enable_pch(BattleAI)
|
||||
|
@ -134,7 +134,7 @@ SlotID StackWithBonuses::unitSlot() const
|
||||
TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const CSelector & limit,
|
||||
const CBonusSystemNode * root, const std::string & cachingStr) const
|
||||
{
|
||||
TBonusListPtr ret = std::make_shared<BonusList>();
|
||||
auto ret = std::make_shared<BonusList>();
|
||||
TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, root, cachingStr);
|
||||
|
||||
vstd::copy_if(*originalList, std::back_inserter(*ret), [this](const std::shared_ptr<Bonus> & b)
|
||||
@ -298,7 +298,7 @@ std::shared_ptr<StackWithBonuses> HypotheticBattle::getForUpdate(uint32_t id)
|
||||
}
|
||||
}
|
||||
|
||||
battle::Units HypotheticBattle::getUnitsIf(battle::UnitFilter predicate) const
|
||||
battle::Units HypotheticBattle::getUnitsIf(const battle::UnitFilter & predicate) const
|
||||
{
|
||||
battle::Units proxyed = BattleProxy::getUnitsIf(predicate);
|
||||
|
||||
@ -356,7 +356,7 @@ void HypotheticBattle::addUnit(uint32_t id, const JsonNode & data)
|
||||
{
|
||||
battle::UnitInfo info;
|
||||
info.load(id, data);
|
||||
std::shared_ptr<StackWithBonuses> newUnit = std::make_shared<StackWithBonuses>(this, info);
|
||||
auto newUnit = std::make_shared<StackWithBonuses>(this, info);
|
||||
stackStates[newUnit->unitId()] = newUnit;
|
||||
}
|
||||
|
||||
|
@ -114,7 +114,7 @@ public:
|
||||
|
||||
int32_t getActiveStackID() const override;
|
||||
|
||||
battle::Units getUnitsIf(battle::UnitFilter predicate) const override;
|
||||
battle::Units getUnitsIf(const battle::UnitFilter & predicate) const override;
|
||||
|
||||
void nextRound() override;
|
||||
void nextTurn(uint32_t unitId) override;
|
||||
|
@ -1,26 +0,0 @@
|
||||
/*
|
||||
* common.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class CBattleCallback;
|
||||
|
||||
template<typename Key, typename Val, typename Val2>
|
||||
const Val getValOr(const std::map<Key, Val> &Map, const Key &key, const Val2 defaultValue)
|
||||
{
|
||||
//returning references here won't work: defaultValue must be converted into Val, creating temporary
|
||||
auto i = Map.find(key);
|
||||
if(i != Map.end())
|
||||
return i->second;
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
void setCbc(std::shared_ptr<CBattleCallback> cb);
|
||||
std::shared_ptr<CBattleCallback> getCbc();
|
@ -15,7 +15,7 @@
|
||||
#define strcpy_s(a, b, c) strncpy(a, c, b)
|
||||
#endif
|
||||
|
||||
static const char *g_cszAiName = "Battle AI";
|
||||
static const char * const g_cszAiName = "Battle AI";
|
||||
|
||||
extern "C" DLL_EXPORT int GetGlobalAiVersion()
|
||||
{
|
||||
|
@ -14,11 +14,11 @@
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/battle/BattleAction.h"
|
||||
|
||||
void CEmptyAI::saveGame(BinarySerializer & h, const int version)
|
||||
void CEmptyAI::saveGame(BinarySerializer & h)
|
||||
{
|
||||
}
|
||||
|
||||
void CEmptyAI::loadGame(BinaryDeserializer & h, const int version)
|
||||
void CEmptyAI::loadGame(BinaryDeserializer & h)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -19,8 +19,8 @@ class CEmptyAI : public CGlobalAI
|
||||
std::shared_ptr<CCallback> cb;
|
||||
|
||||
public:
|
||||
virtual void saveGame(BinarySerializer & h, const int version) override;
|
||||
virtual void loadGame(BinaryDeserializer & h, const int version) override;
|
||||
void saveGame(BinarySerializer & h) override;
|
||||
void loadGame(BinaryDeserializer & h) override;
|
||||
|
||||
void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
|
||||
void yourTurn(QueryID queryID) override;
|
||||
|
@ -8,12 +8,12 @@ set(emptyAI_HEADERS
|
||||
CEmptyAI.h
|
||||
)
|
||||
|
||||
if(NOT ENABLE_STATIC_AI_LIBS)
|
||||
if(NOT ENABLE_STATIC_LIBS)
|
||||
list(APPEND emptyAI_SRCS main.cpp StdInc.cpp)
|
||||
endif()
|
||||
assign_source_group(${emptyAI_SRCS} ${emptyAI_HEADERS})
|
||||
|
||||
if(ENABLE_STATIC_AI_LIBS)
|
||||
if(ENABLE_STATIC_LIBS)
|
||||
add_library(EmptyAI STATIC ${emptyAI_SRCS} ${emptyAI_HEADERS})
|
||||
else()
|
||||
add_library(EmptyAI SHARED ${emptyAI_SRCS} ${emptyAI_HEADERS})
|
||||
@ -21,7 +21,7 @@ else()
|
||||
endif()
|
||||
|
||||
target_include_directories(EmptyAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(EmptyAI PRIVATE ${VCMI_LIB_TARGET})
|
||||
target_link_libraries(EmptyAI PRIVATE vcmi)
|
||||
|
||||
vcmi_set_output_dir(EmptyAI "AI")
|
||||
enable_pch(EmptyAI)
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
#include "CEmptyAI.h"
|
||||
|
||||
std::set<CGlobalAI*> ais;
|
||||
extern "C" DLL_EXPORT int GetGlobalAiVersion()
|
||||
{
|
||||
return AI_INTERFACE_VER;
|
||||
|
@ -9,6 +9,7 @@
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
|
||||
#include "../../lib/ArtifactUtils.h"
|
||||
#include "../../lib/UnlockGuard.h"
|
||||
#include "../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../lib/mapObjects/ObjectTemplate.h"
|
||||
@ -63,7 +64,7 @@ struct SetGlobalState
|
||||
};
|
||||
|
||||
|
||||
#define SET_GLOBAL_STATE(ai) SetGlobalState _hlpSetState(ai);
|
||||
#define SET_GLOBAL_STATE(ai) SetGlobalState _hlpSetState(ai)
|
||||
|
||||
#define NET_EVENT_HANDLER SET_GLOBAL_STATE(this)
|
||||
#define MAKING_TURN SET_GLOBAL_STATE(this)
|
||||
@ -100,7 +101,7 @@ void AIGateway::heroMoved(const TryMoveHero & details, bool verbose)
|
||||
if(!hero)
|
||||
validateObject(details.id); //enemy hero may have left visible area
|
||||
|
||||
const int3 from = hero ? hero->convertToVisitablePos(details.start) : (details.start - int3(0,1,0));;
|
||||
const int3 from = hero ? hero->convertToVisitablePos(details.start) : (details.start - int3(0,1,0));
|
||||
const int3 to = hero ? hero->convertToVisitablePos(details.end) : (details.end - int3(0,1,0));
|
||||
|
||||
const CGObjectInstance * o1 = vstd::frontOrNull(cb->getVisitableObjs(from, verbose));
|
||||
@ -420,14 +421,14 @@ void AIGateway::requestRealized(PackageApplied * pa)
|
||||
NET_EVENT_HANDLER;
|
||||
if(status.haveTurn())
|
||||
{
|
||||
if(pa->packType == typeList.getTypeID<EndTurn>())
|
||||
if(pa->packType == CTypeList::getInstance().getTypeID<EndTurn>(nullptr))
|
||||
{
|
||||
if(pa->result)
|
||||
status.madeTurn();
|
||||
}
|
||||
}
|
||||
|
||||
if(pa->packType == typeList.getTypeID<QueryReply>())
|
||||
if(pa->packType == CTypeList::getInstance().getTypeID<QueryReply>(nullptr))
|
||||
{
|
||||
status.receivedAnswerConfirmation(pa->requestID, pa->result);
|
||||
}
|
||||
@ -480,7 +481,7 @@ void AIGateway::objectPropertyChanged(const SetObjectProperty * sop)
|
||||
NET_EVENT_HANDLER;
|
||||
if(sop->what == ObjProperty::OWNER)
|
||||
{
|
||||
auto relations = myCb->getPlayerRelations(playerID, (PlayerColor)sop->val);
|
||||
auto relations = myCb->getPlayerRelations(playerID, sop->identifier.as<PlayerColor>());
|
||||
auto obj = myCb->getObj(sop->id, false);
|
||||
|
||||
if(!nullkiller) // crash protection
|
||||
@ -585,11 +586,18 @@ void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, s
|
||||
|
||||
requestActionASAP([=]()
|
||||
{
|
||||
int sel = 0;
|
||||
|
||||
if(hPtr.validAndSet())
|
||||
{
|
||||
std::unique_lock<std::mutex> lockGuard(nullkiller->aiStateMutex);
|
||||
|
||||
nullkiller->heroManager->update();
|
||||
answerQuery(queryID, nullkiller->heroManager->selectBestSkill(hPtr, skills));
|
||||
|
||||
sel = nullkiller->heroManager->selectBestSkill(hPtr, skills);
|
||||
}
|
||||
|
||||
answerQuery(queryID, sel);
|
||||
});
|
||||
}
|
||||
|
||||
@ -624,7 +632,8 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
|
||||
auto topObj = objects.front()->id == hero->id ? objects.back() : objects.front();
|
||||
auto objType = topObj->ID; // top object should be our hero
|
||||
auto goalObjectID = nullkiller->getTargetObject();
|
||||
auto ratio = (float)nullkiller->dangerEvaluator->evaluateDanger(target, hero.get()) / (float)hero->getTotalStrength();
|
||||
auto danger = nullkiller->dangerEvaluator->evaluateDanger(target, hero.get());
|
||||
auto ratio = static_cast<float>(danger) / hero->getTotalStrength();
|
||||
|
||||
answer = topObj->id == goalObjectID; // no if we do not aim to visit this object
|
||||
logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), hero.name, ratio);
|
||||
@ -640,7 +649,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
|
||||
}
|
||||
else if(objType == Obj::ARTIFACT || objType == Obj::RESOURCE)
|
||||
{
|
||||
bool dangerUnknown = ratio == 0;
|
||||
bool dangerUnknown = danger == 0;
|
||||
bool dangerTooHigh = ratio > (1 / SAFE_ATTACK_CONSTANT);
|
||||
|
||||
answer = !dangerUnknown && !dangerTooHigh;
|
||||
@ -660,14 +669,18 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
|
||||
if(selection) //select from multiple components -> take the last one (they're indexed [1-size])
|
||||
sel = components.size();
|
||||
|
||||
// TODO: Find better way to understand it is Chest of Treasures
|
||||
if(hero.validAndSet()
|
||||
&& components.size() == 2
|
||||
&& components.front().id == Component::EComponentType::RESOURCE
|
||||
&& (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN
|
||||
|| nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE))
|
||||
{
|
||||
sel = 1; // for now lets pick gold from a chest.
|
||||
std::unique_lock<std::mutex> mxLock(nullkiller->aiStateMutex);
|
||||
|
||||
// TODO: Find better way to understand it is Chest of Treasures
|
||||
if(hero.validAndSet()
|
||||
&& components.size() == 2
|
||||
&& components.front().type == ComponentType::RESOURCE
|
||||
&& (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN
|
||||
|| nullkiller->buildAnalyzer->isGoldPreasureHigh()))
|
||||
{
|
||||
sel = 1;
|
||||
}
|
||||
}
|
||||
|
||||
answerQuery(askID, sel);
|
||||
@ -746,27 +759,25 @@ void AIGateway::showMapObjectSelectDialog(QueryID askID, const Component & icon,
|
||||
requestActionASAP([=](){ answerQuery(askID, selectedObject.getNum()); });
|
||||
}
|
||||
|
||||
void AIGateway::saveGame(BinarySerializer & h, const int version)
|
||||
void AIGateway::saveGame(BinarySerializer & h)
|
||||
{
|
||||
LOG_TRACE_PARAMS(logAi, "version '%i'", version);
|
||||
NET_EVENT_HANDLER;
|
||||
nullkiller->memory->removeInvisibleObjects(myCb.get());
|
||||
|
||||
CAdventureAI::saveGame(h, version);
|
||||
serializeInternal(h, version);
|
||||
CAdventureAI::saveGame(h);
|
||||
serializeInternal(h);
|
||||
}
|
||||
|
||||
void AIGateway::loadGame(BinaryDeserializer & h, const int version)
|
||||
void AIGateway::loadGame(BinaryDeserializer & h)
|
||||
{
|
||||
LOG_TRACE_PARAMS(logAi, "version '%i'", version);
|
||||
//NET_EVENT_HANDLER;
|
||||
|
||||
#if 0
|
||||
//disabled due to issue 2890
|
||||
registerGoals(h);
|
||||
#endif // 0
|
||||
CAdventureAI::loadGame(h, version);
|
||||
serializeInternal(h, version);
|
||||
CAdventureAI::loadGame(h);
|
||||
serializeInternal(h);
|
||||
}
|
||||
|
||||
bool AIGateway::makePossibleUpgrades(const CArmedInstance * obj)
|
||||
@ -858,6 +869,8 @@ void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h
|
||||
{
|
||||
makePossibleUpgrades(h.get());
|
||||
|
||||
std::unique_lock<std::mutex> lockGuard(nullkiller->aiStateMutex);
|
||||
|
||||
if(!h->visitedTown->garrisonHero || !nullkiller->isHeroLocked(h->visitedTown->garrisonHero))
|
||||
moveCreaturesToHero(h->visitedTown);
|
||||
|
||||
@ -995,21 +1008,21 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
|
||||
for(auto p : h->artifactsWorn)
|
||||
{
|
||||
if(p.second.artifact)
|
||||
allArtifacts.push_back(ArtifactLocation(h, p.first));
|
||||
allArtifacts.push_back(ArtifactLocation(h->id, p.first));
|
||||
}
|
||||
}
|
||||
for(auto slot : h->artifactsInBackpack)
|
||||
allArtifacts.push_back(ArtifactLocation(h, h->getArtPos(slot.artifact)));
|
||||
allArtifacts.push_back(ArtifactLocation(h->id, h->getArtPos(slot.artifact)));
|
||||
|
||||
if(otherh)
|
||||
{
|
||||
for(auto p : otherh->artifactsWorn)
|
||||
{
|
||||
if(p.second.artifact)
|
||||
allArtifacts.push_back(ArtifactLocation(otherh, p.first));
|
||||
allArtifacts.push_back(ArtifactLocation(otherh->id, p.first));
|
||||
}
|
||||
for(auto slot : otherh->artifactsInBackpack)
|
||||
allArtifacts.push_back(ArtifactLocation(otherh, otherh->getArtPos(slot.artifact)));
|
||||
allArtifacts.push_back(ArtifactLocation(otherh->id, otherh->getArtPos(slot.artifact)));
|
||||
}
|
||||
//we give stuff to one hero or another, depending on giveStuffToFirstHero
|
||||
|
||||
@ -1021,13 +1034,13 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
|
||||
|
||||
for(auto location : allArtifacts)
|
||||
{
|
||||
if(location.relatedObj() == target && location.slot < ArtifactPosition::AFTER_LAST)
|
||||
if(location.artHolder == target->id && ArtifactUtils::isSlotEquipment(location.slot))
|
||||
continue; //don't reequip artifact we already wear
|
||||
|
||||
if(location.slot == ArtifactPosition::MACH4) // don't attempt to move catapult
|
||||
continue;
|
||||
|
||||
auto s = location.getSlot();
|
||||
auto s = cb->getHero(location.artHolder)->getSlot(location.slot);
|
||||
if(!s || s->locked) //we can't move locks
|
||||
continue;
|
||||
auto artifact = s->artifact;
|
||||
@ -1038,9 +1051,9 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
|
||||
bool emptySlotFound = false;
|
||||
for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
|
||||
{
|
||||
ArtifactLocation destLocation(target, slot);
|
||||
if(target->isPositionFree(slot) && artifact->canBePutAt(destLocation, true)) //combined artifacts are not always allowed to move
|
||||
if(target->isPositionFree(slot) && artifact->canBePutAt(target, slot, true)) //combined artifacts are not always allowed to move
|
||||
{
|
||||
ArtifactLocation destLocation(target->id, slot);
|
||||
cb->swapArtifacts(location, destLocation); //just put into empty slot
|
||||
emptySlotFound = true;
|
||||
changeMade = true;
|
||||
@ -1054,11 +1067,11 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
|
||||
auto otherSlot = target->getSlot(slot);
|
||||
if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one
|
||||
{
|
||||
ArtifactLocation destLocation(target, slot);
|
||||
//if that artifact is better than what we have, pick it
|
||||
if(compareArtifacts(artifact, otherSlot->artifact) && artifact->canBePutAt(destLocation, true)) //combined artifacts are not always allowed to move
|
||||
if(compareArtifacts(artifact, otherSlot->artifact) && artifact->canBePutAt(target, slot, true)) //combined artifacts are not always allowed to move
|
||||
{
|
||||
cb->swapArtifacts(location, ArtifactLocation(target, target->getArtPos(otherSlot->artifact)));
|
||||
ArtifactLocation destLocation(target->id, slot);
|
||||
cb->swapArtifacts(location, ArtifactLocation(target->id, target->getArtPos(otherSlot->artifact)));
|
||||
changeMade = true;
|
||||
break;
|
||||
}
|
||||
@ -1119,15 +1132,6 @@ void AIGateway::battleEnd(const BattleID & battleID, const BattleResult * br, Qu
|
||||
logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.toString(), (won ? "won" : "lost"), battlename);
|
||||
battlename.clear();
|
||||
|
||||
if (queryID != QueryID::NONE)
|
||||
{
|
||||
status.addQuery(queryID, "Combat result dialog");
|
||||
const int confirmAction = 0;
|
||||
requestActionASAP([=]()
|
||||
{
|
||||
answerQuery(queryID, confirmAction);
|
||||
});
|
||||
}
|
||||
CAdventureAI::battleEnd(battleID, br, queryID);
|
||||
}
|
||||
|
||||
@ -1403,7 +1407,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
|
||||
int accquiredResources = 0;
|
||||
if(const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(g.objid), false))
|
||||
{
|
||||
if(const IMarket * m = IMarket::castFrom(obj, false))
|
||||
if(const auto * m = dynamic_cast<const IMarket*>(obj))
|
||||
{
|
||||
auto freeRes = cb->getResourceAmount(); //trade only resources which are not reserved
|
||||
for(auto it = ResourceSet::nziterator(freeRes); it.valid(); it++)
|
||||
@ -1412,13 +1416,14 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
|
||||
if(res.getNum() == g.resID) //sell any other resource
|
||||
continue;
|
||||
|
||||
int toGive, toGet;
|
||||
int toGive;
|
||||
int toGet;
|
||||
m->getOffer(res, g.resID, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
|
||||
toGive = static_cast<int>(toGive * (it->resVal / toGive)); //round down
|
||||
//TODO trade only as much as needed
|
||||
if (toGive) //don't try to sell 0 resources
|
||||
{
|
||||
cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, g.resID, toGive);
|
||||
cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
|
||||
accquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
|
||||
logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, accquiredResources, g.resID, obj->getObjectName());
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ public:
|
||||
void heroVisit(const CGObjectInstance * obj, bool started);
|
||||
|
||||
|
||||
template<typename Handler> void serialize(Handler & h, const int version)
|
||||
template<typename Handler> void serialize(Handler & h)
|
||||
{
|
||||
h & battle;
|
||||
h & remainingQueries;
|
||||
@ -119,8 +119,8 @@ public:
|
||||
void showGarrisonDialog(const CArmedInstance * up, const CGHeroInstance * down, bool removableUnits, QueryID queryID) override; //all stacks operations between these objects become allowed, interface has to call onEnd when done
|
||||
void showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
|
||||
void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
|
||||
void saveGame(BinarySerializer & h, const int version) override; //saving
|
||||
void loadGame(BinaryDeserializer & h, const int version) override; //loading
|
||||
void saveGame(BinarySerializer & h) override; //saving
|
||||
void loadGame(BinaryDeserializer & h) override; //loading
|
||||
void finish() override;
|
||||
|
||||
void availableCreaturesChanged(const CGDwelling * town) override;
|
||||
@ -203,7 +203,7 @@ public:
|
||||
//special function that can be called ONLY from game events handling thread and will send request ASAP
|
||||
void requestActionASAP(std::function<void()> whatToDo);
|
||||
|
||||
template<typename Handler> void serializeInternal(Handler & h, const int version)
|
||||
template<typename Handler> void serializeInternal(Handler & h)
|
||||
{
|
||||
h & nullkiller->memory->knownTeleportChannels;
|
||||
h & nullkiller->memory->knownSubterraneanGates;
|
||||
|
@ -276,12 +276,10 @@ creInfo infoFromDC(const dwellingContent & dc)
|
||||
ci.creID = dc.second.size() ? dc.second.back() : CreatureID(-1); //should never be accessed
|
||||
if (ci.creID != CreatureID::NONE)
|
||||
{
|
||||
ci.cre = VLC->creatures()->getById(ci.creID);
|
||||
ci.level = ci.cre->getLevel(); //this is creature tier, while tryRealize expects dwelling level. Ignore.
|
||||
ci.level = ci.creID.toCreature()->getLevel(); //this is creature tier, while tryRealize expects dwelling level. Ignore.
|
||||
}
|
||||
else
|
||||
{
|
||||
ci.cre = nullptr;
|
||||
ci.level = 0;
|
||||
}
|
||||
return ci;
|
||||
@ -439,7 +437,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
|
||||
case Obj::MAGIC_WELL:
|
||||
return h->mana < h->manaLimit();
|
||||
case Obj::PRISON:
|
||||
return ai->cb->getHeroesInfo().size() < VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP);
|
||||
return !ai->heroManager->heroCapReached();
|
||||
case Obj::TAVERN:
|
||||
case Obj::EYE_OF_MAGI:
|
||||
case Obj::BOAT:
|
||||
|
@ -60,7 +60,9 @@ struct creInfo;
|
||||
class AIGateway;
|
||||
class Nullkiller;
|
||||
|
||||
const int GOLD_MINE_PRODUCTION = 1000, WOOD_ORE_MINE_PRODUCTION = 2, RESOURCE_MINE_PRODUCTION = 1;
|
||||
const int GOLD_MINE_PRODUCTION = 1000;
|
||||
const int WOOD_ORE_MINE_PRODUCTION = 2;
|
||||
const int RESOURCE_MINE_PRODUCTION = 1;
|
||||
const int ACTUAL_RESOURCE_COUNT = 7;
|
||||
const int ALLOWED_ROAMING_HEROES = 8;
|
||||
|
||||
@ -113,7 +115,7 @@ public:
|
||||
bool validAndSet() const;
|
||||
|
||||
|
||||
template<typename Handler> void serialize(Handler & h, const int version)
|
||||
template<typename Handler> void serialize(Handler & h)
|
||||
{
|
||||
h & this->h;
|
||||
h & hid;
|
||||
@ -145,7 +147,7 @@ struct ObjectIdRef
|
||||
bool operator<(const ObjectIdRef & rhs) const;
|
||||
|
||||
|
||||
template<typename Handler> void serialize(Handler & h, const int version)
|
||||
template<typename Handler> void serialize(Handler & h)
|
||||
{
|
||||
h & id;
|
||||
}
|
||||
@ -161,7 +163,6 @@ struct creInfo
|
||||
{
|
||||
int count;
|
||||
CreatureID creID;
|
||||
const Creature * cre;
|
||||
int level;
|
||||
};
|
||||
creInfo infoFromDC(const dwellingContent & dc);
|
||||
|
@ -63,9 +63,9 @@ std::vector<SlotInfo> ArmyManager::toSlotInfo(std::vector<creInfo> army) const
|
||||
{
|
||||
SlotInfo slot;
|
||||
|
||||
slot.creature = VLC->creh->objects[i.cre->getId()];
|
||||
slot.creature = i.creID.toCreature();
|
||||
slot.count = i.count;
|
||||
slot.power = evaluateStackPower(i.cre, i.count);
|
||||
slot.power = evaluateStackPower(i.creID.toCreature(), i.count);
|
||||
|
||||
result.push_back(slot);
|
||||
}
|
||||
@ -117,7 +117,7 @@ std::vector<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<Slot
|
||||
if(left.creature->getLevel() != right.creature->getLevel())
|
||||
return left.creature->getLevel() < right.creature->getLevel();
|
||||
|
||||
return left.creature->speed() > right.creature->speed();
|
||||
return left.creature->getMovementRange() > right.creature->getMovementRange();
|
||||
});
|
||||
|
||||
return weakest;
|
||||
@ -128,7 +128,7 @@ class TemporaryArmy : public CArmedInstance
|
||||
public:
|
||||
void armyChanged() override {}
|
||||
TemporaryArmy()
|
||||
:CArmedInstance(true)
|
||||
:CArmedInstance(nullptr, true)
|
||||
{
|
||||
}
|
||||
};
|
||||
@ -259,7 +259,7 @@ std::shared_ptr<CCreatureSet> ArmyManager::getArmyAvailableToBuyAsCCreatureSet(
|
||||
if(!ci.count || ci.creID == CreatureID::NONE)
|
||||
continue;
|
||||
|
||||
vstd::amin(ci.count, availableRes / ci.cre->getFullRecruitCost()); //max count we can afford
|
||||
vstd::amin(ci.count, availableRes / ci.creID.toCreature()->getFullRecruitCost()); //max count we can afford
|
||||
|
||||
if(!ci.count)
|
||||
continue;
|
||||
@ -270,7 +270,7 @@ std::shared_ptr<CCreatureSet> ArmyManager::getArmyAvailableToBuyAsCCreatureSet(
|
||||
break;
|
||||
|
||||
army->setCreature(dst, ci.creID, ci.count);
|
||||
availableRes -= ci.cre->getFullRecruitCost() * ci.count;
|
||||
availableRes -= ci.creID.toCreature()->getFullRecruitCost() * ci.count;
|
||||
}
|
||||
|
||||
return army;
|
||||
@ -287,7 +287,7 @@ ui64 ArmyManager::howManyReinforcementsCanBuy(
|
||||
|
||||
for(const creInfo & ci : army)
|
||||
{
|
||||
aivalue += ci.count * ci.cre->getAIValue();
|
||||
aivalue += ci.count * ci.creID.toCreature()->getAIValue();
|
||||
}
|
||||
|
||||
return aivalue;
|
||||
@ -320,7 +320,7 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
|
||||
|
||||
if(i < GameConstants::CREATURES_PER_TOWN && countGrowth)
|
||||
{
|
||||
ci.count += town ? town->creatureGrowth(i) : ci.cre->getGrowth();
|
||||
ci.count += town ? town->creatureGrowth(i) : ci.creID.toCreature()->getGrowth();
|
||||
}
|
||||
|
||||
if(!ci.count) continue;
|
||||
@ -334,13 +334,13 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
|
||||
freeHeroSlots--; //new slot will be occupied
|
||||
}
|
||||
|
||||
vstd::amin(ci.count, availableRes / ci.cre->getFullRecruitCost()); //max count we can afford
|
||||
vstd::amin(ci.count, availableRes / ci.creID.toCreature()->getFullRecruitCost()); //max count we can afford
|
||||
|
||||
if(!ci.count) continue;
|
||||
|
||||
ci.level = i; //this is important for Dungeon Summoning Portal
|
||||
creaturesInDwellings.push_back(ci);
|
||||
availableRes -= ci.cre->getFullRecruitCost() * ci.count;
|
||||
availableRes -= ci.creID.toCreature()->getFullRecruitCost() * ci.count;
|
||||
}
|
||||
|
||||
return creaturesInDwellings;
|
||||
|
@ -120,6 +120,11 @@ TResources BuildAnalyzer::getTotalResourcesRequired() const
|
||||
return result;
|
||||
}
|
||||
|
||||
bool BuildAnalyzer::isGoldPreasureHigh() const
|
||||
{
|
||||
return goldPreasure > ai->settings->getMaxGoldPreasure();
|
||||
}
|
||||
|
||||
void BuildAnalyzer::update()
|
||||
{
|
||||
logAi->trace("Start analysing build");
|
||||
@ -318,7 +323,7 @@ bool BuildAnalyzer::hasAnyBuilding(int32_t alignment, BuildingID bid) const
|
||||
{
|
||||
for(auto tdi : developmentInfos)
|
||||
{
|
||||
if(tdi.town->subID == alignment && tdi.town->hasBuilt(bid))
|
||||
if(tdi.town->getFaction() == alignment && tdi.town->hasBuilt(bid))
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -96,6 +96,7 @@ public:
|
||||
const std::vector<TownDevelopmentInfo> & getDevelopmentInfo() const { return developmentInfos; }
|
||||
TResources getDailyIncome() const { return dailyIncome; }
|
||||
float getGoldPreasure() const { return goldPreasure; }
|
||||
bool isGoldPreasureHigh() const;
|
||||
bool hasAnyBuilding(int32_t alignment, BuildingID bid) const;
|
||||
|
||||
private:
|
||||
|
@ -11,11 +11,12 @@
|
||||
#include "DangerHitMapAnalyzer.h"
|
||||
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../../../lib/CRandomGenerator.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
HitMapInfo HitMapInfo::NoThreat;
|
||||
const HitMapInfo HitMapInfo::NoThreat;
|
||||
|
||||
double HitMapInfo::value() const
|
||||
{
|
||||
@ -165,7 +166,7 @@ void DangerHitMapAnalyzer::calculateTileOwners()
|
||||
|
||||
auto addTownHero = [&](const CGTownInstance * town)
|
||||
{
|
||||
auto townHero = new CGHeroInstance();
|
||||
auto townHero = new CGHeroInstance(town->cb);
|
||||
CRandomGenerator rng;
|
||||
auto visitablePos = town->visitablePos();
|
||||
|
||||
@ -225,7 +226,7 @@ void DangerHitMapAnalyzer::calculateTileOwners()
|
||||
}
|
||||
}
|
||||
|
||||
if(ourDistance == enemyDistance)
|
||||
if(vstd::isAlmostEqual(ourDistance, enemyDistance))
|
||||
{
|
||||
hitMap[pos.x][pos.y][pos.z].closestTown = nullptr;
|
||||
}
|
||||
@ -265,8 +266,9 @@ uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath &
|
||||
{
|
||||
int3 tile = path.targetTile();
|
||||
int turn = path.turn();
|
||||
const HitMapNode & info = hitMap[tile.x][tile.y][tile.z];
|
||||
|
||||
const auto& info = getTileThreat(tile);
|
||||
|
||||
return (info.fastestDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.fastestDanger.danger))
|
||||
|| (info.maximumDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.maximumDanger.danger));
|
||||
}
|
||||
@ -280,13 +282,9 @@ const HitMapNode & DangerHitMapAnalyzer::getObjectThreat(const CGObjectInstance
|
||||
|
||||
const HitMapNode & DangerHitMapAnalyzer::getTileThreat(const int3 & tile) const
|
||||
{
|
||||
const HitMapNode & info = hitMap[tile.x][tile.y][tile.z];
|
||||
|
||||
return info;
|
||||
return hitMap[tile.x][tile.y][tile.z];
|
||||
}
|
||||
|
||||
const std::set<const CGObjectInstance *> empty = {};
|
||||
|
||||
std::set<const CGObjectInstance *> DangerHitMapAnalyzer::getOneTurnAccessibleObjects(const CGHeroInstance * enemy) const
|
||||
{
|
||||
std::set<const CGObjectInstance *> result;
|
||||
|
@ -18,7 +18,7 @@ struct AIPath;
|
||||
|
||||
struct HitMapInfo
|
||||
{
|
||||
static HitMapInfo NoThreat;
|
||||
static const HitMapInfo NoThreat;
|
||||
|
||||
uint64_t danger;
|
||||
uint8_t turn;
|
||||
|
@ -17,7 +17,7 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluator(
|
||||
const SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluator(
|
||||
{
|
||||
std::make_shared<SecondarySkillScoreMap>(
|
||||
std::map<SecondarySkill, float>
|
||||
@ -46,7 +46,7 @@ SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluato
|
||||
std::make_shared<AtLeastOneMagicRule>()
|
||||
});
|
||||
|
||||
SecondarySkillEvaluator HeroManager::scountSkillsScores = SecondarySkillEvaluator(
|
||||
const SecondarySkillEvaluator HeroManager::scountSkillsScores = SecondarySkillEvaluator(
|
||||
{
|
||||
std::make_shared<SecondarySkillScoreMap>(
|
||||
std::map<SecondarySkill, float>
|
||||
@ -187,7 +187,9 @@ bool HeroManager::heroCapReached() const
|
||||
int heroCount = cb->getHeroCount(ai->playerID, includeGarnisoned);
|
||||
|
||||
return heroCount >= ALLOWED_ROAMING_HEROES
|
||||
|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP);
|
||||
|| heroCount >= ai->settings->getMaxRoamingHeroes()
|
||||
|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)
|
||||
|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP);
|
||||
}
|
||||
|
||||
float HeroManager::getMagicStrength(const CGHeroInstance * hero) const
|
||||
@ -331,7 +333,7 @@ void WisdomRule::evaluateScore(const CGHeroInstance * hero, SecondarySkill skill
|
||||
score += 1.5;
|
||||
}
|
||||
|
||||
std::vector<SecondarySkill> AtLeastOneMagicRule::magicSchools = {
|
||||
const std::vector<SecondarySkill> AtLeastOneMagicRule::magicSchools = {
|
||||
SecondarySkill::AIR_MAGIC,
|
||||
SecondarySkill::EARTH_MAGIC,
|
||||
SecondarySkill::FIRE_MAGIC,
|
||||
|
@ -58,8 +58,8 @@ public:
|
||||
class DLL_EXPORT HeroManager : public IHeroManager
|
||||
{
|
||||
private:
|
||||
static SecondarySkillEvaluator wariorSkillsScores;
|
||||
static SecondarySkillEvaluator scountSkillsScores;
|
||||
static const SecondarySkillEvaluator wariorSkillsScores;
|
||||
static const SecondarySkillEvaluator scountSkillsScores;
|
||||
|
||||
CCallback * cb; //this is enough, but we downcast from CCallback
|
||||
const Nullkiller * ai;
|
||||
@ -114,7 +114,7 @@ public:
|
||||
class AtLeastOneMagicRule : public ISecondarySkillRule
|
||||
{
|
||||
private:
|
||||
static std::vector<SecondarySkill> magicSchools;
|
||||
static const std::vector<SecondarySkill> magicSchools;
|
||||
|
||||
public:
|
||||
void evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const override;
|
||||
|
@ -47,13 +47,13 @@ Goals::TGoalVec BuildingBehavior::decompose() const
|
||||
totalDevelopmentCost.toString());
|
||||
|
||||
auto & developmentInfos = ai->nullkiller->buildAnalyzer->getDevelopmentInfo();
|
||||
auto goldPreasure = ai->nullkiller->buildAnalyzer->getGoldPreasure();
|
||||
auto isGoldPreasureLow = !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh();
|
||||
|
||||
for(auto & developmentInfo : developmentInfos)
|
||||
{
|
||||
for(auto & buildingInfo : developmentInfo.toBuild)
|
||||
{
|
||||
if(goldPreasure < MAX_GOLD_PEASURE || buildingInfo.dailyIncome[EGameResID::GOLD] > 0)
|
||||
if(isGoldPreasureLow || buildingInfo.dailyIncome[EGameResID::GOLD] > 0)
|
||||
{
|
||||
if(buildingInfo.notEnoughRes)
|
||||
{
|
||||
|
@ -25,9 +25,9 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual Goals::TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
virtual bool operator==(const BuildingBehavior & other) const override
|
||||
Goals::TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
bool operator==(const BuildingBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -46,8 +46,7 @@ Goals::TGoalVec BuyArmyBehavior::decompose() const
|
||||
|
||||
for(const CGHeroInstance * targetHero : heroes)
|
||||
{
|
||||
if(ai->nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE
|
||||
&& !town->hasBuilt(BuildingID::CITY_HALL))
|
||||
if(ai->nullkiller->buildAnalyzer->isGoldPreasureHigh() && !town->hasBuilt(BuildingID::CITY_HALL))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
@ -24,9 +24,9 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual Goals::TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
virtual bool operator==(const BuyArmyBehavior & other) const override
|
||||
Goals::TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
bool operator==(const BuyArmyBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -48,8 +48,8 @@ namespace Goals
|
||||
specificObjects = true;
|
||||
}
|
||||
|
||||
virtual Goals::TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
Goals::TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
CaptureObjectsBehavior & ofType(int type)
|
||||
{
|
||||
@ -65,7 +65,7 @@ namespace Goals
|
||||
return *this;
|
||||
}
|
||||
|
||||
virtual bool operator==(const CaptureObjectsBehavior & other) const override;
|
||||
bool operator==(const CaptureObjectsBehavior & other) const override;
|
||||
|
||||
static Goals::TGoalVec getVisitGoals(const std::vector<AIPath> & paths, const CGObjectInstance * objToVisit = nullptr);
|
||||
|
||||
|
@ -28,10 +28,10 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
virtual bool operator==(const ClusterBehavior & other) const override
|
||||
bool operator==(const ClusterBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -29,10 +29,10 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual Goals::TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
Goals::TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
virtual bool operator==(const DefenceBehavior & other) const override
|
||||
bool operator==(const DefenceBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -246,7 +246,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
||||
{
|
||||
auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero);
|
||||
|
||||
if(heroRole == HeroRole::MAIN && path.turn() < SCOUT_TURN_DISTANCE_LIMIT)
|
||||
if(heroRole == HeroRole::MAIN && path.turn() < ai->nullkiller->settings->getScoutHeroTurnDistanceLimit())
|
||||
hasMainAround = true;
|
||||
}
|
||||
|
||||
@ -335,7 +335,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
||||
if(!upgrade.upgradeValue
|
||||
&& armyToGetOrBuy.upgradeValue > 20000
|
||||
&& ai->nullkiller->heroManager->canRecruitHero(town)
|
||||
&& path.turn() < SCOUT_TURN_DISTANCE_LIMIT)
|
||||
&& path.turn() < ai->nullkiller->settings->getScoutHeroTurnDistanceLimit())
|
||||
{
|
||||
for(auto hero : cb->getAvailableHeroes(town))
|
||||
{
|
||||
@ -344,7 +344,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
||||
|
||||
if(scoutReinforcement >= armyToGetOrBuy.upgradeValue
|
||||
&& ai->nullkiller->getFreeGold() >20000
|
||||
&& ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE)
|
||||
&& !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh())
|
||||
{
|
||||
Composition recruitHero;
|
||||
|
||||
|
@ -25,10 +25,10 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
virtual bool operator==(const GatherArmyBehavior & other) const override
|
||||
bool operator==(const GatherArmyBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -85,8 +85,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const
|
||||
continue;
|
||||
|
||||
if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1
|
||||
|| (ai->nullkiller->getFreeResources()[EGameResID::GOLD] > 10000
|
||||
&& ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE))
|
||||
|| (ai->nullkiller->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh()))
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero(town).setpriority(3)));
|
||||
}
|
||||
|
@ -25,10 +25,10 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
virtual bool operator==(const RecruitHeroBehavior & other) const override
|
||||
bool operator==(const RecruitHeroBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ bool needToRecruitHero(const CGTownInstance * startupTown)
|
||||
|
||||
for(auto obj : ai->nullkiller->objectClusterizer->getNearbyObjects())
|
||||
{
|
||||
if((obj->ID == Obj::RESOURCE && obj->subID == GameResID(EGameResID::GOLD))
|
||||
if((obj->ID == Obj::RESOURCE && dynamic_cast<const CGResource *>(obj)->resourceID() == EGameResID::GOLD)
|
||||
|| obj->ID == Obj::TREASURE_CHEST
|
||||
|| obj->ID == Obj::CAMPFIRE
|
||||
|| obj->ID == Obj::WATER_WHEEL)
|
||||
|
@ -25,10 +25,10 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
virtual bool operator==(const StartupBehavior & other) const override
|
||||
bool operator==(const StartupBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -25,10 +25,10 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
virtual bool operator==(const StayAtTownBehavior & other) const override
|
||||
bool operator==(const StayAtTownBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ set(Nullkiller_SRCS
|
||||
AIUtility.cpp
|
||||
Analyzers/ArmyManager.cpp
|
||||
Analyzers/HeroManager.cpp
|
||||
Engine/Settings.cpp
|
||||
Engine/FuzzyEngines.cpp
|
||||
Engine/FuzzyHelper.cpp
|
||||
Engine/AIMemory.cpp
|
||||
@ -80,6 +81,7 @@ set(Nullkiller_HEADERS
|
||||
AIUtility.h
|
||||
Analyzers/ArmyManager.h
|
||||
Analyzers/HeroManager.h
|
||||
Engine/Settings.h
|
||||
Engine/FuzzyEngines.h
|
||||
Engine/FuzzyHelper.h
|
||||
Engine/AIMemory.h
|
||||
@ -125,12 +127,12 @@ set(Nullkiller_HEADERS
|
||||
AIGateway.h
|
||||
)
|
||||
|
||||
if(NOT ENABLE_STATIC_AI_LIBS)
|
||||
if(NOT ENABLE_STATIC_LIBS)
|
||||
list(APPEND Nullkiller_SRCS main.cpp StdInc.cpp)
|
||||
endif()
|
||||
assign_source_group(${Nullkiller_SRCS} ${Nullkiller_HEADERS})
|
||||
|
||||
if(ENABLE_STATIC_AI_LIBS)
|
||||
if(ENABLE_STATIC_LIBS)
|
||||
add_library(Nullkiller STATIC ${Nullkiller_SRCS} ${Nullkiller_HEADERS})
|
||||
else()
|
||||
add_library(Nullkiller SHARED ${Nullkiller_SRCS} ${Nullkiller_HEADERS})
|
||||
@ -138,7 +140,7 @@ else()
|
||||
endif()
|
||||
|
||||
target_include_directories(Nullkiller PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(Nullkiller PUBLIC ${VCMI_LIB_TARGET} fuzzylite::fuzzylite TBB::tbb)
|
||||
target_link_libraries(Nullkiller PUBLIC vcmi fuzzylite::fuzzylite TBB::tbb)
|
||||
|
||||
vcmi_set_output_dir(Nullkiller "AI")
|
||||
enable_pch(Nullkiller)
|
||||
|
@ -39,7 +39,9 @@ void engineBase::addRule(const std::string & txt)
|
||||
|
||||
struct armyStructure
|
||||
{
|
||||
float walkers, shooters, flyers;
|
||||
float walkers;
|
||||
float shooters;
|
||||
float flyers;
|
||||
ui32 maxSpeed;
|
||||
};
|
||||
|
||||
|
@ -41,8 +41,14 @@ public:
|
||||
TacticalAdvantageEngine();
|
||||
float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us
|
||||
private:
|
||||
fl::InputVariable * ourWalkers, *ourShooters, *ourFlyers, *enemyWalkers, *enemyShooters, *enemyFlyers;
|
||||
fl::InputVariable * ourSpeed, *enemySpeed;
|
||||
fl::InputVariable * ourWalkers;
|
||||
fl::InputVariable * ourShooters;
|
||||
fl::InputVariable * ourFlyers;
|
||||
fl::InputVariable * enemyWalkers;
|
||||
fl::InputVariable * enemyShooters;
|
||||
fl::InputVariable * enemyFlyers;
|
||||
fl::InputVariable * ourSpeed;
|
||||
fl::InputVariable * enemySpeed;
|
||||
fl::InputVariable * bankPresent;
|
||||
fl::InputVariable * castleWalls;
|
||||
fl::OutputVariable * threat;
|
||||
|
@ -24,13 +24,13 @@ ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
|
||||
{
|
||||
//this one is not fuzzy anymore, just calculate weighted average
|
||||
|
||||
auto objectInfo = VLC->objtypeh->getHandlerFor(bank->ID, bank->subID)->getObjectInfo(bank->appearance);
|
||||
auto objectInfo = bank->getObjectHandler()->getObjectInfo(bank->appearance);
|
||||
|
||||
CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
|
||||
|
||||
ui64 totalStrength = 0;
|
||||
ui8 totalChance = 0;
|
||||
for(auto config : bankInfo->getPossibleGuards())
|
||||
for(auto config : bankInfo->getPossibleGuards(bank->cb))
|
||||
{
|
||||
totalStrength += config.second.totalStrength * config.first;
|
||||
totalChance += config.first;
|
||||
@ -161,10 +161,7 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
|
||||
}
|
||||
case Obj::PYRAMID:
|
||||
{
|
||||
if(obj->subID == 0)
|
||||
return estimateBankDanger(dynamic_cast<const CBank *>(obj));
|
||||
else
|
||||
return 0;
|
||||
return estimateBankDanger(dynamic_cast<const CBank *>(obj));
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
|
@ -27,15 +27,11 @@ namespace NKAI
|
||||
|
||||
using namespace Goals;
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
#define MAXPASS 1000000
|
||||
#else
|
||||
#define MAXPASS 30
|
||||
#endif
|
||||
|
||||
Nullkiller::Nullkiller()
|
||||
:activeHero(nullptr), scanDepth(ScanDepth::MAIN_FULL), useHeroChain(true)
|
||||
{
|
||||
memory.reset(new AIMemory());
|
||||
memory = std::make_unique<AIMemory>();
|
||||
settings = std::make_unique<Settings>();
|
||||
}
|
||||
|
||||
void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID)
|
||||
@ -115,6 +111,8 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositi
|
||||
|
||||
void Nullkiller::resetAiState()
|
||||
{
|
||||
std::unique_lock<std::mutex> lockGuard(aiStateMutex);
|
||||
|
||||
lockedResources = TResources();
|
||||
scanDepth = ScanDepth::MAIN_FULL;
|
||||
playerID = ai->playerID;
|
||||
@ -127,6 +125,8 @@ void Nullkiller::updateAiState(int pass, bool fast)
|
||||
{
|
||||
boost::this_thread::interruption_point();
|
||||
|
||||
std::unique_lock<std::mutex> lockGuard(aiStateMutex);
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
activeHero = nullptr;
|
||||
@ -162,12 +162,12 @@ void Nullkiller::updateAiState(int pass, bool fast)
|
||||
|
||||
if(scanDepth == ScanDepth::SMALL)
|
||||
{
|
||||
cfg.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT;
|
||||
cfg.mainTurnDistanceLimit = ai->nullkiller->settings->getMainHeroTurnDistanceLimit();
|
||||
}
|
||||
|
||||
if(scanDepth != ScanDepth::ALL_FULL)
|
||||
{
|
||||
cfg.scoutTurnDistanceLimit = SCOUT_TURN_DISTANCE_LIMIT;
|
||||
cfg.scoutTurnDistanceLimit = ai->nullkiller->settings->getScoutHeroTurnDistanceLimit();
|
||||
}
|
||||
|
||||
boost::this_thread::interruption_point();
|
||||
@ -231,13 +231,13 @@ void Nullkiller::makeTurn()
|
||||
|
||||
resetAiState();
|
||||
|
||||
for(int i = 1; i <= MAXPASS; i++)
|
||||
for(int i = 1; i <= settings->getMaxPass(); i++)
|
||||
{
|
||||
updateAiState(i);
|
||||
|
||||
Goals::TTask bestTask = taskptr(Goals::Invalid());
|
||||
|
||||
for(;i <= MAXPASS; i++)
|
||||
for(;i <= settings->getMaxPass(); i++)
|
||||
{
|
||||
Goals::TTaskVec fastTasks = {
|
||||
choseBestTask(sptr(BuyArmyBehavior()), 1),
|
||||
@ -324,9 +324,9 @@ void Nullkiller::makeTurn()
|
||||
|
||||
executeTask(bestTask);
|
||||
|
||||
if(i == MAXPASS)
|
||||
if(i == settings->getMaxPass())
|
||||
{
|
||||
logAi->error("Goal %s exceeded maxpass. Terminating AI turn.", taskDescription);
|
||||
logAi->warn("Goal %s exceeded maxpass. Terminating AI turn.", taskDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include "PriorityEvaluator.h"
|
||||
#include "FuzzyHelper.h"
|
||||
#include "Settings.h"
|
||||
#include "AIMemory.h"
|
||||
#include "DeepDecomposer.h"
|
||||
#include "../Analyzers/DangerHitMapAnalyzer.h"
|
||||
@ -23,7 +24,6 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
const float MAX_GOLD_PEASURE = 0.3f;
|
||||
const float MIN_PRIORITY = 0.01f;
|
||||
const float SMALL_SCAN_MIN_PRIORITY = 0.4f;
|
||||
|
||||
@ -71,8 +71,10 @@ public:
|
||||
std::unique_ptr<FuzzyHelper> dangerEvaluator;
|
||||
std::unique_ptr<DeepDecomposer> decomposer;
|
||||
std::unique_ptr<ArmyFormation> armyFormation;
|
||||
std::unique_ptr<Settings> settings;
|
||||
PlayerColor playerID;
|
||||
std::shared_ptr<CCallback> cb;
|
||||
std::mutex aiStateMutex;
|
||||
|
||||
Nullkiller();
|
||||
void init(std::shared_ptr<CCallback> cb, PlayerColor playerID);
|
||||
|
@ -69,7 +69,7 @@ PriorityEvaluator::~PriorityEvaluator()
|
||||
|
||||
void PriorityEvaluator::initVisitTile()
|
||||
{
|
||||
auto file = CResourceHandler::get()->load(ResourcePath("config/ai/object-priorities.txt"))->readAll();
|
||||
auto file = CResourceHandler::get()->load(ResourcePath("config/ai/nkai/object-priorities.txt"))->readAll();
|
||||
std::string str = std::string((char *)file.first.get(), file.second);
|
||||
engine = fl::FllImporter().fromString(str);
|
||||
armyLossPersentageVariable = engine->getInputVariable("armyLoss");
|
||||
@ -122,7 +122,7 @@ TResources getCreatureBankResources(const CGObjectInstance * target, const CGHer
|
||||
{
|
||||
//Fixme: unused variable hero
|
||||
|
||||
auto objectInfo = VLC->objtypeh->getHandlerFor(target->ID, target->subID)->getObjectInfo(target->appearance);
|
||||
auto objectInfo = target->getObjectHandler()->getObjectInfo(target->appearance);
|
||||
CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
|
||||
auto resources = bankInfo->getPossibleResourcesReward();
|
||||
TResources result = TResources();
|
||||
@ -137,11 +137,23 @@ TResources getCreatureBankResources(const CGObjectInstance * target, const CGHer
|
||||
return sum > 1 ? result / sum : result;
|
||||
}
|
||||
|
||||
uint64_t getResourcesGoldReward(const TResources & res)
|
||||
{
|
||||
int nonGoldResources = res[EGameResID::GEMS]
|
||||
+ res[EGameResID::SULFUR]
|
||||
+ res[EGameResID::WOOD]
|
||||
+ res[EGameResID::ORE]
|
||||
+ res[EGameResID::CRYSTAL]
|
||||
+ res[EGameResID::MERCURY];
|
||||
|
||||
return res[EGameResID::GOLD] + 100 * nonGoldResources;
|
||||
}
|
||||
|
||||
uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
|
||||
{
|
||||
auto objectInfo = VLC->objtypeh->getHandlerFor(target->ID, target->subID)->getObjectInfo(target->appearance);
|
||||
auto objectInfo = target->getObjectHandler()->getObjectInfo(target->appearance);
|
||||
CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
|
||||
auto creatures = bankInfo->getPossibleCreaturesReward();
|
||||
auto creatures = bankInfo->getPossibleCreaturesReward(target->cb);
|
||||
uint64_t result = 0;
|
||||
|
||||
const auto& slots = hero->Slots();
|
||||
@ -236,7 +248,7 @@ int getDwellingArmyCost(const CGObjectInstance * target)
|
||||
return cost;
|
||||
}
|
||||
|
||||
uint64_t evaluateArtifactArmyValue(CArtifactInstance * art)
|
||||
static uint64_t evaluateArtifactArmyValue(const CArtifactInstance * art)
|
||||
{
|
||||
if(art->artType->getId() == ArtifactID::SPELL_SCROLL)
|
||||
return 1500;
|
||||
@ -467,14 +479,20 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
|
||||
switch(target->ID)
|
||||
{
|
||||
case Obj::MINE:
|
||||
return target->subID == GameResID(EGameResID::GOLD)
|
||||
{
|
||||
auto mine = dynamic_cast<const CGMine *>(target);
|
||||
return mine->producedResource == EGameResID::GOLD
|
||||
? 0.5f
|
||||
: 0.4f * getTotalResourceRequirementStrength(target->subID) + 0.1f * getResourceRequirementStrength(target->subID);
|
||||
: 0.4f * getTotalResourceRequirementStrength(mine->producedResource) + 0.1f * getResourceRequirementStrength(mine->producedResource);
|
||||
}
|
||||
|
||||
case Obj::RESOURCE:
|
||||
return target->subID == GameResID(EGameResID::GOLD)
|
||||
{
|
||||
auto resource = dynamic_cast<const CGResource *>(target);
|
||||
return resource->resourceID() == EGameResID::GOLD
|
||||
? 0
|
||||
: 0.2f * getTotalResourceRequirementStrength(target->subID) + 0.4f * getResourceRequirementStrength(target->subID);
|
||||
: 0.2f * getTotalResourceRequirementStrength(resource->resourceID()) + 0.4f * getResourceRequirementStrength(resource->resourceID());
|
||||
}
|
||||
|
||||
case Obj::CREATURE_BANK:
|
||||
{
|
||||
@ -485,7 +503,7 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
|
||||
//Evaluate resources used for construction. Gold is evaluated separately.
|
||||
if (it->resType != EGameResID::GOLD)
|
||||
{
|
||||
sum += 0.1f * getResourceRequirementStrength(it->resType);
|
||||
sum += 0.1f * it->resVal * getResourceRequirementStrength(it->resType);
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
@ -523,6 +541,9 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
|
||||
? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance *>(target))
|
||||
: 0;
|
||||
|
||||
case Obj::KEYMASTER:
|
||||
return 0.6f;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@ -582,6 +603,8 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
|
||||
case Obj::PANDORAS_BOX:
|
||||
//Can contains experience, spells, or skills (only on custom maps)
|
||||
return 2.5f;
|
||||
case Obj::PYRAMID:
|
||||
return 3.0f;
|
||||
case Obj::HERO:
|
||||
return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
|
||||
? enemyHeroEliminationSkillRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->level
|
||||
@ -626,12 +649,14 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
|
||||
const int dailyIncomeMultiplier = 5;
|
||||
const float enemyArmyEliminationGoldRewardRatio = 0.2f;
|
||||
const int32_t heroEliminationBonus = GameConstants::HERO_GOLD_COST / 2;
|
||||
auto isGold = target->subID == GameResID(EGameResID::GOLD); // TODO: other resorces could be sold but need to evaluate market power
|
||||
|
||||
switch(target->ID)
|
||||
{
|
||||
case Obj::RESOURCE:
|
||||
return isGold ? 600 : 100;
|
||||
{
|
||||
auto * res = dynamic_cast<const CGResource*>(target);
|
||||
return res->resourceID() == GameResID::GOLD ? 600 : 100;
|
||||
}
|
||||
case Obj::TREASURE_CHEST:
|
||||
return 1500;
|
||||
case Obj::WATER_WHEEL:
|
||||
@ -640,7 +665,10 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
|
||||
return dailyIncomeMultiplier * estimateTownIncome(ai->cb.get(), target, hero);
|
||||
case Obj::MINE:
|
||||
case Obj::ABANDONED_MINE:
|
||||
return dailyIncomeMultiplier * (isGold ? 1000 : 75);
|
||||
{
|
||||
auto * mine = dynamic_cast<const CGMine*>(target);
|
||||
return dailyIncomeMultiplier * (mine->producedResource == GameResID::GOLD ? 1000 : 75);
|
||||
}
|
||||
case Obj::MYSTICAL_GARDEN:
|
||||
case Obj::WINDMILL:
|
||||
return 100;
|
||||
@ -649,7 +677,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
|
||||
case Obj::WAGON:
|
||||
return 100;
|
||||
case Obj::CREATURE_BANK:
|
||||
return getCreatureBankResources(target, hero)[EGameResID::GOLD];
|
||||
return getResourcesGoldReward(getCreatureBankResources(target, hero));
|
||||
case Obj::CRYPT:
|
||||
case Obj::DERELICT_SHIP:
|
||||
return 3000;
|
||||
@ -674,7 +702,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
|
||||
class HeroExchangeEvaluator : public IEvaluationContextBuilder
|
||||
{
|
||||
public:
|
||||
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
{
|
||||
if(task->goalType != Goals::HERO_EXCHANGE)
|
||||
return;
|
||||
@ -691,7 +719,7 @@ public:
|
||||
class ArmyUpgradeEvaluator : public IEvaluationContextBuilder
|
||||
{
|
||||
public:
|
||||
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
{
|
||||
if(task->goalType != Goals::ARMY_UPGRADE)
|
||||
return;
|
||||
@ -708,7 +736,7 @@ public:
|
||||
class StayAtTownManaRecoveryEvaluator : public IEvaluationContextBuilder
|
||||
{
|
||||
public:
|
||||
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
{
|
||||
if(task->goalType != Goals::STAY_AT_TOWN)
|
||||
return;
|
||||
@ -743,7 +771,7 @@ void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uin
|
||||
class DefendTownEvaluator : public IEvaluationContextBuilder
|
||||
{
|
||||
public:
|
||||
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
{
|
||||
if(task->goalType != Goals::DEFEND_TOWN)
|
||||
return;
|
||||
@ -793,7 +821,7 @@ private:
|
||||
public:
|
||||
ExecuteHeroChainEvaluationContextBuilder(const Nullkiller * ai) : ai(ai) {}
|
||||
|
||||
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
{
|
||||
if(task->goalType != Goals::EXECUTE_HERO_CHAIN)
|
||||
return;
|
||||
@ -851,7 +879,7 @@ class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder
|
||||
public:
|
||||
ClusterEvaluationContextBuilder(const Nullkiller * ai) {}
|
||||
|
||||
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
{
|
||||
if(task->goalType != Goals::UNLOCK_CLUSTER)
|
||||
return;
|
||||
@ -898,7 +926,7 @@ public:
|
||||
class ExchangeSwapTownHeroesContextBuilder : public IEvaluationContextBuilder
|
||||
{
|
||||
public:
|
||||
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
{
|
||||
if(task->goalType != Goals::EXCHANGE_SWAP_TOWN_HEROES)
|
||||
return;
|
||||
@ -926,7 +954,7 @@ private:
|
||||
public:
|
||||
DismissHeroContextBuilder(const Nullkiller * ai) : ai(ai) {}
|
||||
|
||||
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
{
|
||||
if(task->goalType != Goals::DISMISS_HERO)
|
||||
return;
|
||||
@ -946,7 +974,7 @@ public:
|
||||
class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder
|
||||
{
|
||||
public:
|
||||
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
{
|
||||
if(task->goalType != Goals::BUILD_STRUCTURE)
|
||||
return;
|
||||
@ -1005,7 +1033,7 @@ public:
|
||||
|
||||
uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const
|
||||
{
|
||||
if(ai->buildAnalyzer->hasAnyBuilding(town->subID, bi.id))
|
||||
if(ai->buildAnalyzer->hasAnyBuilding(town->getFaction(), bi.id))
|
||||
return 0;
|
||||
|
||||
auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);
|
||||
|
78
AI/Nullkiller/Engine/Settings.cpp
Normal file
78
AI/Nullkiller/Engine/Settings.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Settings.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include <limits>
|
||||
|
||||
#include "Settings.h"
|
||||
#include "../../../lib/mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
|
||||
#include "../../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../../lib/modding/CModHandler.h"
|
||||
#include "../../../lib/VCMI_Lib.h"
|
||||
#include "../../../lib/filesystem/Filesystem.h"
|
||||
#include "../../../lib/json/JsonNode.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
Settings::Settings()
|
||||
: maxRoamingHeroes(8),
|
||||
mainHeroTurnDistanceLimit(10),
|
||||
scoutHeroTurnDistanceLimit(5),
|
||||
maxGoldPreasure(0.3f),
|
||||
maxpass(30)
|
||||
{
|
||||
ResourcePath resource("config/ai/nkai/nkai-settings", EResType::JSON);
|
||||
|
||||
loadFromMod("core", resource);
|
||||
|
||||
for(const auto & modName : VLC->modh->getActiveMods())
|
||||
{
|
||||
if(CResourceHandler::get(modName)->existsResource(resource))
|
||||
loadFromMod(modName, resource);
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::loadFromMod(const std::string & modName, const ResourcePath & resource)
|
||||
{
|
||||
if(!CResourceHandler::get(modName)->existsResource(resource))
|
||||
{
|
||||
logGlobal->error("Failed to load font %s from mod %s", resource.getName(), modName);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonNode node(JsonPath::fromResource(resource), modName);
|
||||
|
||||
if(node.Struct()["maxRoamingHeroes"].isNumber())
|
||||
{
|
||||
maxRoamingHeroes = node.Struct()["maxRoamingHeroes"].Integer();
|
||||
}
|
||||
|
||||
if(node.Struct()["mainHeroTurnDistanceLimit"].isNumber())
|
||||
{
|
||||
mainHeroTurnDistanceLimit = node.Struct()["mainHeroTurnDistanceLimit"].Integer();
|
||||
}
|
||||
|
||||
if(node.Struct()["scoutHeroTurnDistanceLimit"].isNumber())
|
||||
{
|
||||
scoutHeroTurnDistanceLimit = node.Struct()["scoutHeroTurnDistanceLimit"].Integer();
|
||||
}
|
||||
|
||||
if(node.Struct()["maxpass"].isNumber())
|
||||
{
|
||||
maxpass = node.Struct()["maxpass"].Integer();
|
||||
}
|
||||
|
||||
if(node.Struct()["maxGoldPreasure"].isNumber())
|
||||
{
|
||||
maxGoldPreasure = node.Struct()["maxGoldPreasure"].Float();
|
||||
}
|
||||
}
|
||||
}
|
42
AI/Nullkiller/Engine/Settings.h
Normal file
42
AI/Nullkiller/Engine/Settings.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Settings.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class JsonNode;
|
||||
class ResourcePath;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
class Settings
|
||||
{
|
||||
private:
|
||||
int maxRoamingHeroes;
|
||||
int mainHeroTurnDistanceLimit;
|
||||
int scoutHeroTurnDistanceLimit;
|
||||
int maxpass;
|
||||
float maxGoldPreasure;
|
||||
|
||||
public:
|
||||
Settings();
|
||||
|
||||
int getMaxPass() const { return maxpass; }
|
||||
float getMaxGoldPreasure() const { return maxGoldPreasure; }
|
||||
int getMaxRoamingHeroes() const { return maxRoamingHeroes; }
|
||||
int getMainHeroTurnDistanceLimit() const { return mainHeroTurnDistanceLimit; }
|
||||
int getScoutHeroTurnDistanceLimit() const { return scoutHeroTurnDistanceLimit; }
|
||||
|
||||
private:
|
||||
void loadFromMod(const std::string & modName, const ResourcePath & resource);
|
||||
};
|
||||
}
|
@ -180,11 +180,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~cannotFulfillGoalException() throw ()
|
||||
{
|
||||
};
|
||||
|
||||
const char * what() const throw () override
|
||||
const char * what() const noexcept override
|
||||
{
|
||||
return msg.c_str();
|
||||
}
|
||||
@ -203,11 +199,7 @@ public:
|
||||
msg = goal->toString();
|
||||
}
|
||||
|
||||
virtual ~goalFulfilledException() throw ()
|
||||
{
|
||||
};
|
||||
|
||||
const char * what() const throw () override
|
||||
const char * what() const noexcept override
|
||||
{
|
||||
return msg.c_str();
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ namespace Goals
|
||||
|
||||
void accept(AIGateway * ai) override;
|
||||
std::string toString() const override;
|
||||
virtual bool operator==(const AdventureSpellCast & other) const override;
|
||||
bool operator==(const AdventureSpellCast & other) const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ namespace Goals
|
||||
TSubgoal whatToDoToAchieve() override;
|
||||
bool fulfillsMe(TSubgoal goal) override;
|
||||
|
||||
virtual bool operator==(const Build & other) const override
|
||||
bool operator==(const Build & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ namespace Goals
|
||||
|
||||
void accept(AIGateway * ai) override;
|
||||
std::string toString() const override;
|
||||
virtual bool operator==(const BuildBoat & other) const override;
|
||||
bool operator==(const BuildBoat & other) const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -39,8 +39,8 @@ namespace Goals
|
||||
}
|
||||
BuildThis(BuildingID Bid, const CGTownInstance * tid);
|
||||
|
||||
virtual bool operator==(const BuildThis & other) const override;
|
||||
virtual std::string toString() const override;
|
||||
bool operator==(const BuildThis & other) const override;
|
||||
std::string toString() const override;
|
||||
void accept(AIGateway * ai) override;
|
||||
};
|
||||
}
|
||||
|
@ -54,12 +54,12 @@ void BuyArmy::accept(AIGateway * ai)
|
||||
if(objid != CreatureID::NONE && ci.creID.getNum() != objid)
|
||||
continue;
|
||||
|
||||
vstd::amin(ci.count, res / ci.cre->getFullRecruitCost());
|
||||
vstd::amin(ci.count, res / ci.creID.toCreature()->getFullRecruitCost());
|
||||
|
||||
if(ci.count)
|
||||
{
|
||||
cb->recruitCreatures(town, town->getUpperArmy(), ci.creID, ci.count, ci.level);
|
||||
valueBought += ci.count * ci.cre->getAIValue();
|
||||
valueBought += ci.count * ci.creID.toCreature()->getAIValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,11 +36,11 @@ namespace Goals
|
||||
priority = 3;//TODO: evaluate?
|
||||
}
|
||||
|
||||
virtual bool operator==(const BuyArmy & other) const override;
|
||||
bool operator==(const BuyArmy & other) const override;
|
||||
|
||||
virtual std::string toString() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
virtual void accept(AIGateway * ai) override;
|
||||
void accept(AIGateway * ai) override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -37,14 +37,14 @@ namespace Goals
|
||||
{
|
||||
return new T(static_cast<T const &>(*this)); //casting enforces template instantiation
|
||||
}
|
||||
template<typename Handler> void serialize(Handler & h, const int version)
|
||||
template<typename Handler> void serialize(Handler & h)
|
||||
{
|
||||
h & static_cast<AbstractGoal &>(*this);
|
||||
//h & goalType & isElementar & isAbstract & priority;
|
||||
//h & value & resID & objid & aid & tile & hero & town & bid;
|
||||
}
|
||||
|
||||
virtual bool operator==(const AbstractGoal & g) const override
|
||||
bool operator==(const AbstractGoal & g) const override
|
||||
{
|
||||
if(goalType != g.goalType)
|
||||
return false;
|
||||
@ -54,7 +54,7 @@ namespace Goals
|
||||
|
||||
virtual bool operator==(const T & other) const = 0;
|
||||
|
||||
virtual TGoalVec decompose() const override
|
||||
TGoalVec decompose() const override
|
||||
{
|
||||
TSubgoal single = decomposeSingle();
|
||||
|
||||
@ -90,11 +90,11 @@ namespace Goals
|
||||
return *((T *)this);
|
||||
}
|
||||
|
||||
virtual bool isElementar() const override { return true; }
|
||||
bool isElementar() const override { return true; }
|
||||
|
||||
virtual HeroPtr getHero() const override { return AbstractGoal::hero; }
|
||||
HeroPtr getHero() const override { return AbstractGoal::hero; }
|
||||
|
||||
virtual int getHeroExchangeCount() const override { return 0; }
|
||||
int getHeroExchangeCount() const override { return 0; }
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -34,11 +34,11 @@ namespace Goals
|
||||
name = obj->getObjectName();
|
||||
}
|
||||
|
||||
virtual bool operator==(const CaptureObject & other) const override;
|
||||
virtual Goals::TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
virtual bool hasHash() const override { return true; }
|
||||
virtual uint64_t getHash() const override;
|
||||
bool operator==(const CaptureObject & other) const override;
|
||||
Goals::TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
bool hasHash() const override { return true; }
|
||||
uint64_t getHash() const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -98,7 +98,7 @@ std::string CompleteQuest::questToString() const
|
||||
return "inactive quest";
|
||||
|
||||
MetaString ms;
|
||||
q.quest->getRolloverText(ms, false);
|
||||
q.quest->getRolloverText(q.obj->cb, ms, false);
|
||||
|
||||
return ms.toString();
|
||||
}
|
||||
@ -210,7 +210,7 @@ TGoalVec CompleteQuest::missionResources() const
|
||||
|
||||
TGoalVec CompleteQuest::missionDestroyObj() const
|
||||
{
|
||||
auto obj = cb->getObjByQuestIdentifier(q.quest->killTarget);
|
||||
auto obj = cb->getObj(q.quest->killTarget);
|
||||
|
||||
if(!obj)
|
||||
return CaptureObjectsBehavior(q.obj).decompose();
|
||||
|
@ -29,12 +29,12 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual Goals::TGoalVec decompose() const override;
|
||||
virtual std::string toString() const override;
|
||||
virtual bool hasHash() const override { return true; }
|
||||
virtual uint64_t getHash() const override;
|
||||
Goals::TGoalVec decompose() const override;
|
||||
std::string toString() const override;
|
||||
bool hasHash() const override { return true; }
|
||||
uint64_t getHash() const override;
|
||||
|
||||
virtual bool operator==(const CompleteQuest & other) const override;
|
||||
bool operator==(const CompleteQuest & other) const override;
|
||||
|
||||
private:
|
||||
TGoalVec tryCompleteQuest() const;
|
||||
|
@ -26,15 +26,15 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool operator==(const Composition & other) const override;
|
||||
virtual std::string toString() const override;
|
||||
bool operator==(const Composition & other) const override;
|
||||
std::string toString() const override;
|
||||
void accept(AIGateway * ai) override;
|
||||
Composition & addNext(const AbstractGoal & goal);
|
||||
Composition & addNext(TSubgoal goal);
|
||||
Composition & addNextSequence(const TGoalVec & taskSequence);
|
||||
virtual TGoalVec decompose() const override;
|
||||
virtual bool isElementar() const override;
|
||||
virtual int getHeroExchangeCount() const override;
|
||||
TGoalVec decompose() const override;
|
||||
bool isElementar() const override;
|
||||
int getHeroExchangeCount() const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ namespace Goals
|
||||
{
|
||||
tile = Tile;
|
||||
}
|
||||
virtual bool operator==(const DigAtTile & other) const override;
|
||||
bool operator==(const DigAtTile & other) const override;
|
||||
|
||||
private:
|
||||
//TSubgoal decomposeSingle() const override;
|
||||
|
@ -26,7 +26,7 @@ namespace Goals
|
||||
|
||||
void accept(AIGateway * ai) override;
|
||||
std::string toString() const override;
|
||||
virtual bool operator==(const DismissHero & other) const override;
|
||||
bool operator==(const DismissHero & other) const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ namespace Goals
|
||||
|
||||
void accept(AIGateway * ai) override;
|
||||
std::string toString() const override;
|
||||
virtual bool operator==(const ExchangeSwapTownHeroes & other) const override;
|
||||
bool operator==(const ExchangeSwapTownHeroes & other) const override;
|
||||
|
||||
const CGHeroInstance * getGarrisonHero() const { return garrisonHero; }
|
||||
HeroLockedReason getLockingReason() const { return lockingReason; }
|
||||
|
@ -30,10 +30,10 @@ namespace Goals
|
||||
|
||||
void accept(AIGateway * ai) override;
|
||||
std::string toString() const override;
|
||||
virtual bool operator==(const ExecuteHeroChain & other) const override;
|
||||
bool operator==(const ExecuteHeroChain & other) const override;
|
||||
const AIPath & getPath() const { return chainPath; }
|
||||
|
||||
virtual int getHeroExchangeCount() const override { return chainPath.exchangeCount; }
|
||||
int getHeroExchangeCount() const override { return chainPath.exchangeCount; }
|
||||
|
||||
private:
|
||||
bool moveHeroToTile(const CGHeroInstance * hero, const int3 & tile);
|
||||
|
@ -36,7 +36,7 @@ namespace Goals
|
||||
TGoalVec getAllPossibleSubgoals() override;
|
||||
TSubgoal whatToDoToAchieve() override;
|
||||
std::string completeMessage() const override;
|
||||
virtual bool operator==(const GatherArmy & other) const override;
|
||||
bool operator==(const GatherArmy & other) const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -32,17 +32,17 @@ namespace Goals
|
||||
return TGoalVec();
|
||||
}
|
||||
|
||||
virtual bool operator==(const Invalid & other) const override
|
||||
bool operator==(const Invalid & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual std::string toString() const override
|
||||
std::string toString() const override
|
||||
{
|
||||
return "Invalid";
|
||||
}
|
||||
|
||||
virtual void accept(AIGateway * ai) override
|
||||
void accept(AIGateway * ai) override
|
||||
{
|
||||
throw cannotFulfillGoalException("Can not fulfill Invalid goal!");
|
||||
}
|
||||
|
@ -38,12 +38,12 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool operator==(const RecruitHero & other) const override
|
||||
bool operator==(const RecruitHero & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual std::string toString() const override;
|
||||
std::string toString() const override;
|
||||
void accept(AIGateway * ai) override;
|
||||
};
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ namespace Goals
|
||||
|
||||
void accept(AIGateway * ai) override;
|
||||
std::string toString() const override;
|
||||
virtual bool operator==(const SaveResources & other) const override;
|
||||
bool operator==(const SaveResources & other) const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -26,8 +26,8 @@ namespace Goals
|
||||
public:
|
||||
StayAtTown(const CGTownInstance * town, AIPath & path);
|
||||
|
||||
virtual bool operator==(const StayAtTown & other) const override;
|
||||
virtual std::string toString() const override;
|
||||
bool operator==(const StayAtTown & other) const override;
|
||||
std::string toString() const override;
|
||||
void accept(AIGateway * ai) override;
|
||||
float getMovementWasted() const { return movementWasted; }
|
||||
};
|
||||
|
@ -34,7 +34,7 @@ namespace Goals
|
||||
value = val;
|
||||
objid = Objid;
|
||||
}
|
||||
virtual bool operator==(const Trade & other) const override;
|
||||
bool operator==(const Trade & other) const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -29,8 +29,8 @@ namespace Goals
|
||||
ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade);
|
||||
ArmyUpgrade(const CGHeroInstance * targetMain, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade);
|
||||
|
||||
virtual bool operator==(const ArmyUpgrade & other) const override;
|
||||
virtual std::string toString() const override;
|
||||
bool operator==(const ArmyUpgrade & other) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
uint64_t getUpgradeValue() const { return upgradeValue; }
|
||||
uint64_t getInitialArmyValue() const { return initialValue; }
|
||||
|
@ -30,8 +30,8 @@ namespace Goals
|
||||
DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath, bool isCounterAttack = false);
|
||||
DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const CGHeroInstance * defender);
|
||||
|
||||
virtual bool operator==(const DefendTown & other) const override;
|
||||
virtual std::string toString() const override;
|
||||
bool operator==(const DefendTown & other) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
const HitMapInfo & getTreat() const { return treat; }
|
||||
|
||||
|
@ -28,8 +28,8 @@ namespace Goals
|
||||
sethero(targetHero);
|
||||
}
|
||||
|
||||
virtual bool operator==(const HeroExchange & other) const override;
|
||||
virtual std::string toString() const override;
|
||||
bool operator==(const HeroExchange & other) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
uint64_t getReinforcementArmyStrength() const;
|
||||
};
|
||||
|
@ -36,8 +36,8 @@ namespace Goals
|
||||
sethero(pathToCenter.targetHero);
|
||||
}
|
||||
|
||||
virtual bool operator==(const UnlockCluster & other) const override;
|
||||
virtual std::string toString() const override;
|
||||
bool operator==(const UnlockCluster & other) const override;
|
||||
std::string toString() const override;
|
||||
std::shared_ptr<ObjectCluster> getCluster() const { return cluster; }
|
||||
const AIPath & getPathToCenter() { return pathToCenter; }
|
||||
};
|
||||
|
@ -35,6 +35,8 @@ const uint64_t MIN_ARMY_STRENGTH_FOR_CHAIN = 5000;
|
||||
const uint64_t MIN_ARMY_STRENGTH_FOR_NEXT_ACTOR = 1000;
|
||||
const uint64_t CHAIN_MAX_DEPTH = 4;
|
||||
|
||||
const bool DO_NOT_SAVE_TO_COMMITED_TILES = false;
|
||||
|
||||
AISharedStorage::AISharedStorage(int3 sizes)
|
||||
{
|
||||
if(!shared){
|
||||
@ -90,7 +92,7 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
|
||||
|
||||
//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
|
||||
const PlayerColor fowPlayer = ai->playerID;
|
||||
const auto fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap;
|
||||
const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap;
|
||||
const int3 sizes = gs->getMapSize();
|
||||
|
||||
//Each thread gets different x, but an array of y located next to each other in memory
|
||||
@ -234,6 +236,7 @@ void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, EPath
|
||||
heroNode.specialAction.reset();
|
||||
heroNode.armyLoss = 0;
|
||||
heroNode.chainOther = nullptr;
|
||||
heroNode.dayFlags = DayFlags::NONE;
|
||||
heroNode.update(coord, layer, accessibility);
|
||||
}
|
||||
}
|
||||
@ -265,7 +268,8 @@ void AINodeStorage::commit(
|
||||
EPathNodeAction action,
|
||||
int turn,
|
||||
int movementLeft,
|
||||
float cost) const
|
||||
float cost,
|
||||
bool saveToCommited) const
|
||||
{
|
||||
destination->action = action;
|
||||
destination->setCost(cost);
|
||||
@ -291,10 +295,15 @@ void AINodeStorage::commit(
|
||||
destination->actor->armyValue);
|
||||
#endif
|
||||
|
||||
if(destination->turns <= heroChainTurn)
|
||||
if(saveToCommited && destination->turns <= heroChainTurn)
|
||||
{
|
||||
commitedTiles.insert(destination->coord);
|
||||
}
|
||||
|
||||
if(destination->turns == source->turns)
|
||||
{
|
||||
destination->dayFlags = source->dayFlags;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
|
||||
@ -323,7 +332,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
|
||||
return neighbours;
|
||||
}
|
||||
|
||||
EPathfindingLayer phisycalLayers[2] = {EPathfindingLayer::LAND, EPathfindingLayer::SAIL};
|
||||
constexpr std::array phisycalLayers = {EPathfindingLayer::LAND, EPathfindingLayer::SAIL};
|
||||
|
||||
bool AINodeStorage::increaseHeroChainTurnLimit()
|
||||
{
|
||||
@ -778,7 +787,14 @@ void HeroChainCalculationTask::addHeroChain(const std::vector<ExchangeCandidate>
|
||||
continue;
|
||||
}
|
||||
|
||||
storage.commit(exchangeNode, carrier, carrier->action, chainInfo.turns, chainInfo.moveRemains, chainInfo.getCost());
|
||||
storage.commit(
|
||||
exchangeNode,
|
||||
carrier,
|
||||
carrier->action,
|
||||
chainInfo.turns,
|
||||
chainInfo.moveRemains,
|
||||
chainInfo.getCost(),
|
||||
DO_NOT_SAVE_TO_COMMITED_TILES);
|
||||
|
||||
if(carrier->specialAction || carrier->chainOther)
|
||||
{
|
||||
@ -827,6 +843,7 @@ ExchangeCandidate HeroChainCalculationTask::calculateExchange(
|
||||
candidate.turns = carrierParentNode->turns;
|
||||
candidate.setCost(carrierParentNode->getCost() + otherParentNode->getCost() / 1000.0);
|
||||
candidate.moveRemains = carrierParentNode->moveRemains;
|
||||
candidate.danger = carrierParentNode->danger;
|
||||
|
||||
if(carrierParentNode->turns < otherParentNode->turns)
|
||||
{
|
||||
@ -1070,7 +1087,8 @@ struct TowmPortalFinder
|
||||
EPathNodeAction::TELEPORT_NORMAL,
|
||||
bestNode->turns,
|
||||
bestNode->moveRemains - movementNeeded,
|
||||
movementCost);
|
||||
movementCost,
|
||||
DO_NOT_SAVE_TO_COMMITED_TILES);
|
||||
|
||||
node->theNodeBefore = bestNode;
|
||||
node->addSpecialAction(std::make_shared<AIPathfinding::TownPortalAction>(targetTown));
|
||||
@ -1247,8 +1265,8 @@ bool AINodeStorage::hasBetterChain(
|
||||
&& nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength
|
||||
&& node.getCost() <= candidateNode->getCost())
|
||||
{
|
||||
if(nodeActor->heroFightingStrength == candidateActor->heroFightingStrength
|
||||
&& node.getCost() == candidateNode->getCost()
|
||||
if(vstd::isAlmostEqual(nodeActor->heroFightingStrength, candidateActor->heroFightingStrength)
|
||||
&& vstd::isAlmostEqual(node.getCost(), candidateNode->getCost())
|
||||
&& &node < candidateNode)
|
||||
{
|
||||
continue;
|
||||
|
@ -24,9 +24,6 @@
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
const int SCOUT_TURN_DISTANCE_LIMIT = 5;
|
||||
const int MAIN_TURN_DISTANCE_LIMIT = 10;
|
||||
|
||||
namespace AIPathfinding
|
||||
{
|
||||
#ifdef ENVIRONMENT64
|
||||
@ -41,11 +38,19 @@ namespace AIPathfinding
|
||||
const int CHAIN_MAX_DEPTH = 4;
|
||||
}
|
||||
|
||||
enum DayFlags : ui8
|
||||
{
|
||||
NONE = 0,
|
||||
FLY_CAST = 1,
|
||||
WATER_WALK_CAST = 2
|
||||
};
|
||||
|
||||
struct AIPathNode : public CGPathNode
|
||||
{
|
||||
uint64_t danger;
|
||||
uint64_t armyLoss;
|
||||
int32_t manaCost;
|
||||
int16_t manaCost;
|
||||
DayFlags dayFlags;
|
||||
const AIPathNode * chainOther;
|
||||
std::shared_ptr<const SpecialAction> specialAction;
|
||||
const ChainActor * actor;
|
||||
@ -180,7 +185,7 @@ public:
|
||||
bool selectFirstActor();
|
||||
bool selectNextActor();
|
||||
|
||||
virtual std::vector<CGPathNode *> getInitialNodes() override;
|
||||
std::vector<CGPathNode *> getInitialNodes() override;
|
||||
|
||||
virtual std::vector<CGPathNode *> calculateNeighbours(
|
||||
const PathNodeInfo & source,
|
||||
@ -192,7 +197,7 @@ public:
|
||||
const PathfinderConfig * pathfinderConfig,
|
||||
const CPathfinderHelper * pathfinderHelper) override;
|
||||
|
||||
virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override;
|
||||
void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override;
|
||||
|
||||
void commit(
|
||||
AIPathNode * destination,
|
||||
@ -200,7 +205,8 @@ public:
|
||||
EPathNodeAction action,
|
||||
int turn,
|
||||
int movementLeft,
|
||||
float cost) const;
|
||||
float cost,
|
||||
bool saveToCommited = true) const;
|
||||
|
||||
inline const AIPathNode * getAINode(const CGPathNode * node) const
|
||||
{
|
||||
|
@ -34,7 +34,7 @@ namespace AIPathfinding
|
||||
|
||||
~AIPathfinderConfig();
|
||||
|
||||
virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
|
||||
CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -22,18 +22,18 @@ namespace NKAI
|
||||
|
||||
namespace AIPathfinding
|
||||
{
|
||||
AdventureCastAction::AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero)
|
||||
:spellToCast(spellToCast), hero(hero)
|
||||
AdventureCastAction::AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero, DayFlags flagsToAdd)
|
||||
:spellToCast(spellToCast), hero(hero), flagsToAdd(flagsToAdd)
|
||||
{
|
||||
manaCost = hero->getSpellCost(spellToCast.toSpell());
|
||||
}
|
||||
|
||||
WaterWalkingAction::WaterWalkingAction(const CGHeroInstance * hero)
|
||||
:AdventureCastAction(SpellID::WATER_WALK, hero)
|
||||
:AdventureCastAction(SpellID::WATER_WALK, hero, DayFlags::WATER_WALK_CAST)
|
||||
{ }
|
||||
|
||||
AirWalkingAction::AirWalkingAction(const CGHeroInstance * hero)
|
||||
: AdventureCastAction(SpellID::FLY, hero)
|
||||
: AdventureCastAction(SpellID::FLY, hero, DayFlags::FLY_CAST)
|
||||
{
|
||||
}
|
||||
|
||||
@ -41,11 +41,12 @@ namespace AIPathfinding
|
||||
const CGHeroInstance * hero,
|
||||
CDestinationNodeInfo & destination,
|
||||
const PathNodeInfo & source,
|
||||
AIPathNode * dstMode,
|
||||
AIPathNode * dstNode,
|
||||
const AIPathNode * srcNode) const
|
||||
{
|
||||
dstMode->manaCost = srcNode->manaCost + manaCost;
|
||||
dstMode->theNodeBefore = source.node;
|
||||
dstNode->manaCost = srcNode->manaCost + manaCost;
|
||||
dstNode->theNodeBefore = source.node;
|
||||
dstNode->dayFlags = static_cast<DayFlags>(dstNode->dayFlags | flagsToAdd);
|
||||
}
|
||||
|
||||
void AdventureCastAction::execute(const CGHeroInstance * hero) const
|
||||
|
@ -24,11 +24,12 @@ namespace AIPathfinding
|
||||
SpellID spellToCast;
|
||||
const CGHeroInstance * hero;
|
||||
int manaCost;
|
||||
DayFlags flagsToAdd;
|
||||
|
||||
public:
|
||||
AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero);
|
||||
AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero, DayFlags flagsToAdd = DayFlags::NONE);
|
||||
|
||||
virtual void execute(const CGHeroInstance * hero) const override;
|
||||
void execute(const CGHeroInstance * hero) const override;
|
||||
|
||||
virtual void applyOnDestination(
|
||||
const CGHeroInstance * hero,
|
||||
@ -37,9 +38,9 @@ namespace AIPathfinding
|
||||
AIPathNode * dstMode,
|
||||
const AIPathNode * srcNode) const override;
|
||||
|
||||
virtual bool canAct(const AIPathNode * source) const override;
|
||||
bool canAct(const AIPathNode * source) const override;
|
||||
|
||||
virtual std::string toString() const override;
|
||||
std::string toString() const override;
|
||||
};
|
||||
|
||||
class WaterWalkingAction : public AdventureCastAction
|
||||
|
@ -28,9 +28,9 @@ namespace AIPathfinding
|
||||
{
|
||||
}
|
||||
|
||||
virtual void execute(const CGHeroInstance * hero) const override;
|
||||
void execute(const CGHeroInstance * hero) const override;
|
||||
|
||||
virtual std::string toString() const override;
|
||||
std::string toString() const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ namespace AIPathfinding
|
||||
class SummonBoatAction : public VirtualBoatAction
|
||||
{
|
||||
public:
|
||||
virtual void execute(const CGHeroInstance * hero) const override;
|
||||
void execute(const CGHeroInstance * hero) const override;
|
||||
|
||||
virtual void applyOnDestination(
|
||||
const CGHeroInstance * hero,
|
||||
@ -34,11 +34,11 @@ namespace AIPathfinding
|
||||
AIPathNode * dstMode,
|
||||
const AIPathNode * srcNode) const override;
|
||||
|
||||
virtual bool canAct(const AIPathNode * source) const override;
|
||||
bool canAct(const AIPathNode * source) const override;
|
||||
|
||||
virtual const ChainActor * getActor(const ChainActor * sourceActor) const override;
|
||||
const ChainActor * getActor(const ChainActor * sourceActor) const override;
|
||||
|
||||
virtual std::string toString() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
private:
|
||||
int32_t getManaCost(const CGHeroInstance * hero) const;
|
||||
@ -56,17 +56,17 @@ namespace AIPathfinding
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool canAct(const AIPathNode * source) const override;
|
||||
bool canAct(const AIPathNode * source) const override;
|
||||
|
||||
virtual void execute(const CGHeroInstance * hero) const override;
|
||||
void execute(const CGHeroInstance * hero) const override;
|
||||
|
||||
virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
|
||||
Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
|
||||
|
||||
virtual const ChainActor * getActor(const ChainActor * sourceActor) const override;
|
||||
const ChainActor * getActor(const ChainActor * sourceActor) const override;
|
||||
|
||||
virtual std::string toString() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
virtual const CGObjectInstance * targetObject() const override;
|
||||
const CGObjectInstance * targetObject() const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -28,13 +28,13 @@ namespace AIPathfinding
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool canAct(const AIPathNode * node) const override;
|
||||
bool canAct(const AIPathNode * node) const override;
|
||||
|
||||
virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
|
||||
Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
|
||||
|
||||
virtual void execute(const CGHeroInstance * hero) const override;
|
||||
void execute(const CGHeroInstance * hero) const override;
|
||||
|
||||
virtual std::string toString() const override;
|
||||
std::string toString() const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -29,9 +29,9 @@ namespace AIPathfinding
|
||||
{
|
||||
}
|
||||
|
||||
virtual void execute(const CGHeroInstance * hero) const override;
|
||||
void execute(const CGHeroInstance * hero) const override;
|
||||
|
||||
virtual std::string toString() const override;
|
||||
std::string toString() const override;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
|
||||
using namespace NKAI;
|
||||
|
||||
CCreatureSet emptyArmy;
|
||||
const CCreatureSet emptyArmy;
|
||||
|
||||
bool HeroExchangeArmy::needsLastStack() const
|
||||
{
|
||||
@ -327,7 +327,7 @@ ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other)
|
||||
return result;
|
||||
}
|
||||
|
||||
HeroActor * exchanged = new HeroActor(actor, other, newArmy, ai);
|
||||
auto * exchanged = new HeroActor(actor, other, newArmy, ai);
|
||||
|
||||
exchanged->armyCost += newArmy->armyCost;
|
||||
result.actor = exchanged;
|
||||
@ -342,7 +342,7 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
|
||||
const CGObjectInstance * upgrader,
|
||||
TResources resources) const
|
||||
{
|
||||
HeroExchangeArmy * target = new HeroExchangeArmy();
|
||||
auto * target = new HeroExchangeArmy();
|
||||
auto upgradeInfo = ai->armyManager->calculateCreaturesUpgrade(army, upgrader, resources);
|
||||
|
||||
if(upgradeInfo.upgradeValue)
|
||||
@ -373,10 +373,10 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
|
||||
|
||||
for(auto & creatureToBuy : buyArmy)
|
||||
{
|
||||
auto targetSlot = target->getSlotFor(dynamic_cast<const CCreature*>(creatureToBuy.cre));
|
||||
auto targetSlot = target->getSlotFor(creatureToBuy.creID.toCreature());
|
||||
|
||||
target->addToSlot(targetSlot, creatureToBuy.creID, creatureToBuy.count);
|
||||
target->armyCost += creatureToBuy.cre->getFullRecruitCost() * creatureToBuy.count;
|
||||
target->armyCost += creatureToBuy.creID.toCreature()->getFullRecruitCost() * creatureToBuy.count;
|
||||
target->requireBuyArmy = true;
|
||||
}
|
||||
}
|
||||
@ -393,7 +393,7 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
|
||||
|
||||
HeroExchangeArmy * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const
|
||||
{
|
||||
HeroExchangeArmy * target = new HeroExchangeArmy();
|
||||
auto * target = new HeroExchangeArmy();
|
||||
auto bestArmy = ai->armyManager->getBestArmy(actor->hero, army1, army2);
|
||||
|
||||
for(auto & slotInfo : bestArmy)
|
||||
@ -445,7 +445,7 @@ std::string DwellingActor::toString() const
|
||||
|
||||
CCreatureSet * DwellingActor::getDwellingCreatures(const CGDwelling * dwelling, bool waitForGrowth)
|
||||
{
|
||||
CCreatureSet * dwellingCreatures = new CCreatureSet();
|
||||
auto * dwellingCreatures = new CCreatureSet();
|
||||
|
||||
for(auto & creatureInfo : dwelling->creatures)
|
||||
{
|
||||
|
@ -28,10 +28,10 @@ class HeroExchangeArmy : public CArmedInstance
|
||||
public:
|
||||
TResources armyCost;
|
||||
bool requireBuyArmy;
|
||||
virtual bool needsLastStack() const override;
|
||||
bool needsLastStack() const override;
|
||||
std::shared_ptr<SpecialAction> getActorAction() const;
|
||||
|
||||
HeroExchangeArmy(): CArmedInstance(true), requireBuyArmy(false) {}
|
||||
HeroExchangeArmy(): CArmedInstance(nullptr, true), requireBuyArmy(false) {}
|
||||
};
|
||||
|
||||
struct ExchangeResult
|
||||
@ -51,24 +51,24 @@ protected:
|
||||
|
||||
public:
|
||||
uint64_t chainMask;
|
||||
bool isMovable;
|
||||
bool allowUseResources;
|
||||
bool allowBattle;
|
||||
bool allowSpellCast;
|
||||
bool isMovable = false;
|
||||
bool allowUseResources = false;
|
||||
bool allowBattle = false;
|
||||
bool allowSpellCast = false;
|
||||
std::shared_ptr<SpecialAction> actorAction;
|
||||
const CGHeroInstance * hero;
|
||||
HeroRole heroRole;
|
||||
const CCreatureSet * creatureSet;
|
||||
const ChainActor * battleActor;
|
||||
const ChainActor * castActor;
|
||||
const ChainActor * resourceActor;
|
||||
const ChainActor * carrierParent;
|
||||
const ChainActor * otherParent;
|
||||
const ChainActor * baseActor;
|
||||
const CCreatureSet * creatureSet = nullptr;
|
||||
const ChainActor * battleActor = nullptr;
|
||||
const ChainActor * castActor = nullptr;
|
||||
const ChainActor * resourceActor = nullptr;
|
||||
const ChainActor * carrierParent = nullptr;
|
||||
const ChainActor * otherParent = nullptr;
|
||||
const ChainActor * baseActor = nullptr;
|
||||
int3 initialPosition;
|
||||
EPathfindingLayer layer;
|
||||
uint32_t initialMovement;
|
||||
uint32_t initialTurn;
|
||||
uint32_t initialMovement = 0;
|
||||
uint32_t initialTurn = 0;
|
||||
uint64_t armyValue;
|
||||
float heroFightingStrength;
|
||||
uint8_t actorExchangeCount;
|
||||
@ -126,7 +126,7 @@ public:
|
||||
HeroActor(const ChainActor * carrier, const ChainActor * other, const HeroExchangeArmy * army, const Nullkiller * ai);
|
||||
|
||||
protected:
|
||||
virtual ExchangeResult tryExchangeNoLock(const ChainActor * specialActor, const ChainActor * other) const override;
|
||||
ExchangeResult tryExchangeNoLock(const ChainActor * specialActor, const ChainActor * other) const override;
|
||||
};
|
||||
|
||||
class ObjectActor : public ChainActor
|
||||
@ -136,7 +136,7 @@ private:
|
||||
|
||||
public:
|
||||
ObjectActor(const CGObjectInstance * obj, const CCreatureSet * army, uint64_t chainMask, int initialTurn);
|
||||
virtual std::string toString() const override;
|
||||
std::string toString() const override;
|
||||
const CGObjectInstance * getActorObject() const override;
|
||||
};
|
||||
|
||||
@ -154,7 +154,7 @@ private:
|
||||
public:
|
||||
DwellingActor(const CGDwelling * dwelling, uint64_t chainMask, bool waitForGrowth, int dayOfWeek);
|
||||
~DwellingActor();
|
||||
virtual std::string toString() const override;
|
||||
std::string toString() const override;
|
||||
|
||||
protected:
|
||||
int getInitialTurn(bool waitForGrowth, int dayOfWeek);
|
||||
@ -168,7 +168,7 @@ private:
|
||||
|
||||
public:
|
||||
TownGarrisonActor(const CGTownInstance * town, uint64_t chainMask);
|
||||
virtual std::string toString() const override;
|
||||
std::string toString() const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -61,6 +61,12 @@ namespace AIPathfinding
|
||||
|
||||
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::WATER)
|
||||
{
|
||||
if(nodeStorage->getAINode(source.node)->dayFlags & DayFlags::WATER_WALK_CAST)
|
||||
{
|
||||
destination.blocked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
auto action = waterWalkingActions.find(nodeStorage->getHero(source.node));
|
||||
|
||||
if(action != waterWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL))
|
||||
@ -73,6 +79,12 @@ namespace AIPathfinding
|
||||
|
||||
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::AIR)
|
||||
{
|
||||
if(nodeStorage->getAINode(source.node)->dayFlags & DayFlags::FLY_CAST)
|
||||
{
|
||||
destination.blocked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
auto action = airWalkingActions.find(nodeStorage->getHero(source.node));
|
||||
|
||||
if(action != airWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL))
|
||||
@ -91,12 +103,12 @@ namespace AIPathfinding
|
||||
|
||||
for(const CGHeroInstance * hero : nodeStorage->getAllHeroes())
|
||||
{
|
||||
if(hero->canCastThisSpell(waterWalk.toSpell()))
|
||||
if(hero->canCastThisSpell(waterWalk.toSpell()) && hero->mana >= hero->getSpellCost(waterWalk.toSpell()))
|
||||
{
|
||||
waterWalkingActions[hero] = std::make_shared<WaterWalkingAction>(hero);
|
||||
}
|
||||
|
||||
if(hero->canCastThisSpell(airWalk.toSpell()))
|
||||
if(hero->canCastThisSpell(airWalk.toSpell()) && hero->mana >= hero->getSpellCost(airWalk.toSpell()))
|
||||
{
|
||||
airWalkingActions[hero] = std::make_shared<AirWalkingAction>(hero);
|
||||
}
|
||||
@ -119,7 +131,7 @@ namespace AIPathfinding
|
||||
{
|
||||
if(obj->ID != Obj::TOWN) //towns were handled in the previous loop
|
||||
{
|
||||
if(const IShipyard * shipyard = IShipyard::castFrom(obj))
|
||||
if(const auto * shipyard = dynamic_cast<const IShipyard *>(obj))
|
||||
shipyards.push_back(shipyard);
|
||||
}
|
||||
}
|
||||
@ -179,11 +191,6 @@ namespace AIPathfinding
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if(!specialAction->canAct(nodeStorage->getAINode(source.node)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
|
||||
{
|
||||
auto castNodeOptional = nodeStorage->getOrCreateNode(
|
||||
|
@ -14,7 +14,7 @@
|
||||
#define strcpy_s(a, b, c) strncpy(a, c, b)
|
||||
#endif
|
||||
|
||||
static const char * g_cszAiName = "Nullkiller";
|
||||
static const char * const g_cszAiName = "Nullkiller";
|
||||
|
||||
extern "C" DLL_EXPORT int GetGlobalAiVersion()
|
||||
{
|
||||
|
@ -8,19 +8,19 @@ set(stupidAI_HEADERS
|
||||
StupidAI.h
|
||||
)
|
||||
|
||||
if(NOT ENABLE_STATIC_AI_LIBS)
|
||||
if(NOT ENABLE_STATIC_LIBS)
|
||||
list(APPEND stupidAI_SRCS main.cpp StdInc.cpp)
|
||||
endif()
|
||||
assign_source_group(${stupidAI_SRCS} ${stupidAI_HEADERS})
|
||||
|
||||
if(ENABLE_STATIC_AI_LIBS)
|
||||
if(ENABLE_STATIC_LIBS)
|
||||
add_library(StupidAI STATIC ${stupidAI_SRCS} ${stupidAI_HEADERS})
|
||||
else()
|
||||
add_library(StupidAI SHARED ${stupidAI_SRCS} ${stupidAI_HEADERS})
|
||||
install(TARGETS StupidAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR})
|
||||
endif()
|
||||
|
||||
target_link_libraries(StupidAI PRIVATE ${VCMI_LIB_TARGET})
|
||||
target_link_libraries(StupidAI PRIVATE vcmi)
|
||||
target_include_directories(StupidAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
vcmi_set_output_dir(StupidAI "AI")
|
||||
|
@ -15,8 +15,7 @@
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
#include "../../lib/battle/BattleAction.h"
|
||||
#include "../../lib/battle/BattleInfo.h"
|
||||
|
||||
static std::shared_ptr<CBattleCallback> cbc;
|
||||
#include "../../lib/CRandomGenerator.h"
|
||||
|
||||
CStupidAI::CStupidAI()
|
||||
: side(-1)
|
||||
@ -41,7 +40,7 @@ void CStupidAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
|
||||
{
|
||||
print("init called, saving ptr to IBattleCallback");
|
||||
env = ENV;
|
||||
cbc = cb = CB;
|
||||
cb = CB;
|
||||
|
||||
wasWaitingForRealize = CB->waitTillRealize;
|
||||
wasUnlockingGs = CB->unlockGsWhenWaiting;
|
||||
@ -68,15 +67,16 @@ class EnemyInfo
|
||||
{
|
||||
public:
|
||||
const CStack * s;
|
||||
int adi, adr;
|
||||
int adi;
|
||||
int adr;
|
||||
std::vector<BattleHex> attackFrom; //for melee fight
|
||||
EnemyInfo(const CStack * _s) : s(_s), adi(0), adr(0)
|
||||
{}
|
||||
void calcDmg(const BattleID & battleID, const CStack * ourStack)
|
||||
void calcDmg(std::shared_ptr<CBattleCallback> cb, const BattleID & battleID, const CStack * ourStack)
|
||||
{
|
||||
// FIXME: provide distance info for Jousting bonus
|
||||
DamageEstimation retal;
|
||||
DamageEstimation dmg = cbc->getBattle(battleID)->battleEstimateDamage(ourStack, s, 0, &retal);
|
||||
DamageEstimation dmg = cb->getBattle(battleID)->battleEstimateDamage(ourStack, s, 0, &retal);
|
||||
adi = static_cast<int>((dmg.damage.min + dmg.damage.max) / 2);
|
||||
adr = static_cast<int>((retal.damage.min + retal.damage.max) / 2);
|
||||
}
|
||||
@ -92,14 +92,14 @@ bool isMoreProfitable(const EnemyInfo &ei1, const EnemyInfo& ei2)
|
||||
return (ei1.adi-ei1.adr) < (ei2.adi - ei2.adr);
|
||||
}
|
||||
|
||||
static bool willSecondHexBlockMoreEnemyShooters(const BattleID & battleID, const BattleHex &h1, const BattleHex &h2)
|
||||
static bool willSecondHexBlockMoreEnemyShooters(std::shared_ptr<CBattleCallback> cb, const BattleID & battleID, const BattleHex &h1, const BattleHex &h2)
|
||||
{
|
||||
int shooters[2] = {0}; //count of shooters on hexes
|
||||
|
||||
for(int i = 0; i < 2; i++)
|
||||
{
|
||||
for (auto & neighbour : (i ? h2 : h1).neighbouringTiles())
|
||||
if(const auto * s = cbc->getBattle(battleID)->battleGetUnitByPos(neighbour))
|
||||
if(const auto * s = cb->getBattle(battleID)->battleGetUnitByPos(neighbour))
|
||||
if(s->isShooter())
|
||||
shooters[i]++;
|
||||
}
|
||||
@ -117,7 +117,9 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
|
||||
//boost::this_thread::sleep_for(boost::chrono::seconds(2));
|
||||
print("activeStack called for " + stack->nodeName());
|
||||
ReachabilityInfo dists = cb->getBattle(battleID)->getReachability(stack);
|
||||
std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
|
||||
std::vector<EnemyInfo> enemiesShootable;
|
||||
std::vector<EnemyInfo> enemiesReachable;
|
||||
std::vector<EnemyInfo> enemiesUnreachable;
|
||||
|
||||
if(stack->creatureId() == CreatureID::CATAPULT)
|
||||
{
|
||||
@ -152,7 +154,7 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
|
||||
{
|
||||
if(CStack::isMeleeAttackPossible(stack, s, hex))
|
||||
{
|
||||
std::vector<EnemyInfo>::iterator i = std::find(enemiesReachable.begin(), enemiesReachable.end(), s);
|
||||
auto i = std::find(enemiesReachable.begin(), enemiesReachable.end(), s);
|
||||
if(i == enemiesReachable.end())
|
||||
{
|
||||
enemiesReachable.push_back(s);
|
||||
@ -169,10 +171,10 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
|
||||
}
|
||||
|
||||
for ( auto & enemy : enemiesReachable )
|
||||
enemy.calcDmg(battleID, stack);
|
||||
enemy.calcDmg(cb, battleID, stack);
|
||||
|
||||
for ( auto & enemy : enemiesShootable )
|
||||
enemy.calcDmg(battleID, stack);
|
||||
enemy.calcDmg(cb, battleID, stack);
|
||||
|
||||
if(enemiesShootable.size())
|
||||
{
|
||||
@ -183,7 +185,7 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
|
||||
else if(enemiesReachable.size())
|
||||
{
|
||||
const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable);
|
||||
BattleHex targetHex = *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), [&](auto a, auto b) { return willSecondHexBlockMoreEnemyShooters(battleID, a, b);});
|
||||
BattleHex targetHex = *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), [&](auto a, auto b) { return willSecondHexBlockMoreEnemyShooters(cb, battleID, a, b);});
|
||||
|
||||
cb->battleMakeUnitAction(battleID, BattleAction::makeMeleeAttack(stack, ei.s->getPosition(), targetHex));
|
||||
return;
|
||||
|
@ -16,7 +16,7 @@
|
||||
#define strcpy_s(a, b, c) strncpy(a, c, b)
|
||||
#endif
|
||||
|
||||
static const char *g_cszAiName = "Stupid AI 0.1";
|
||||
static const char * const g_cszAiName = "Stupid AI 0.1";
|
||||
|
||||
extern "C" DLL_EXPORT int GetGlobalAiVersion()
|
||||
{
|
||||
|
@ -26,7 +26,6 @@ using crint3 = const int3 &;
|
||||
using crstring = const std::string &;
|
||||
using dwellingContent = std::pair<ui32, std::vector<CreatureID>>;
|
||||
|
||||
const int GOLD_MINE_PRODUCTION = 1000, WOOD_ORE_MINE_PRODUCTION = 2, RESOURCE_MINE_PRODUCTION = 1;
|
||||
const int ACTUAL_RESOURCE_COUNT = 7;
|
||||
const int ALLOWED_ROAMING_HEROES = 8;
|
||||
|
||||
@ -71,7 +70,7 @@ public:
|
||||
bool validAndSet() const;
|
||||
|
||||
|
||||
template<typename Handler> void serialize(Handler & h, const int version)
|
||||
template<typename Handler> void serialize(Handler & h)
|
||||
{
|
||||
h & this->h;
|
||||
h & hid;
|
||||
@ -103,7 +102,7 @@ struct ObjectIdRef
|
||||
bool operator<(const ObjectIdRef & rhs) const;
|
||||
|
||||
|
||||
template<typename Handler> void serialize(Handler & h, const int version)
|
||||
template<typename Handler> void serialize(Handler & h)
|
||||
{
|
||||
h & id;
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ std::vector<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<Slot
|
||||
if(left.creature->getLevel() != right.creature->getLevel())
|
||||
return left.creature->getLevel() < right.creature->getLevel();
|
||||
|
||||
return left.creature->speed() > right.creature->speed();
|
||||
return left.creature->getMovementRange() > right.creature->getMovementRange();
|
||||
});
|
||||
|
||||
return weakest;
|
||||
|
@ -139,17 +139,17 @@ void BuildingManager::setAI(VCAI * AI)
|
||||
//Set of buildings for different goals. Does not include any prerequisites.
|
||||
static const std::vector<BuildingID> essential = { BuildingID::TAVERN, BuildingID::TOWN_HALL };
|
||||
static const std::vector<BuildingID> basicGoldSource = { BuildingID::TOWN_HALL, BuildingID::CITY_HALL };
|
||||
static const std::vector<BuildingID> defence = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE };
|
||||
static const std::vector<BuildingID> capitolAndRequirements = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::CAPITOL };
|
||||
static const std::vector<BuildingID> unitsSource = { BuildingID::DWELL_LVL_1, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3,
|
||||
BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7 };
|
||||
static const std::vector<BuildingID> unitsUpgrade = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP,
|
||||
BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP };
|
||||
static const std::vector<BuildingID> unitGrowth = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1,
|
||||
BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR };
|
||||
static const std::vector<BuildingID> unitGrowth = { BuildingID::HORDE_1, BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR };
|
||||
static const std::vector<BuildingID> _spells = { BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
|
||||
BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5 };
|
||||
static const std::vector<BuildingID> extra = { BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3,
|
||||
BuildingID::SPECIAL_4, BuildingID::SHIPYARD }; // all remaining buildings
|
||||
static const std::vector<BuildingID> extra = { BuildingID::MARKETPLACE, BuildingID::BLACKSMITH, BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2,
|
||||
BuildingID::SPECIAL_3, BuildingID::SPECIAL_4, BuildingID::SHIPYARD }; // all remaining buildings
|
||||
|
||||
bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
|
||||
{
|
||||
@ -172,8 +172,26 @@ bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
|
||||
if(tryBuildAnyStructure(t, essential))
|
||||
return true;
|
||||
|
||||
//the more gold the better and less problems later //TODO: what about building mage guild / marketplace etc. with city hall disabled in editor?
|
||||
if(tryBuildNextStructure(t, basicGoldSource))
|
||||
if (cb->getDate(Date::DAY_OF_WEEK) < 5) // first 4 days of week - try to focus on dwellings
|
||||
{
|
||||
if (tryBuildNextStructure(t, unitsSource, 4))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cb->getDate(Date::DAY_OF_WEEK) > 4) // last 3 days of week - try to focus on growth by building Fort/Citadel/Castle
|
||||
{
|
||||
if (tryBuildNextStructure(t, defence, 3))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (t->hasBuilt(BuildingID::CASTLE))
|
||||
{
|
||||
if (tryBuildAnyStructure(t, unitGrowth))
|
||||
return true;
|
||||
}
|
||||
|
||||
//try to make City Hall
|
||||
if (tryBuildNextStructure(t, basicGoldSource))
|
||||
return true;
|
||||
|
||||
//workaround for mantis #2696 - build capitol with separate algorithm if it is available
|
||||
@ -183,25 +201,6 @@ bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!t->hasBuilt(BuildingID::FORT)) //in vast majority of situations fort is top priority building if we already have city hall, TODO: unite with unitGrowth building chain
|
||||
if(tryBuildThisStructure(t, BuildingID::FORT))
|
||||
return true;
|
||||
|
||||
|
||||
|
||||
if (cb->getDate(Date::DAY_OF_WEEK) > 6) // last 2 days of week - try to focus on growth
|
||||
{
|
||||
if (tryBuildNextStructure(t, unitGrowth, 2))
|
||||
return true;
|
||||
}
|
||||
|
||||
//try building dwellings
|
||||
if (t->hasBuilt(BuildingID::FORT))
|
||||
{
|
||||
if (tryBuildAnyStructure(t, unitsSource, 8 - cb->getDate(Date::DAY_OF_WEEK)))
|
||||
return true;
|
||||
}
|
||||
|
||||
//try to upgrade dwelling
|
||||
for (int i = 0; i < unitsUpgrade.size(); i++)
|
||||
{
|
||||
|
@ -94,12 +94,12 @@ set(VCAI_HEADERS
|
||||
VCAI.h
|
||||
)
|
||||
|
||||
if(NOT ENABLE_STATIC_AI_LIBS)
|
||||
if(NOT ENABLE_STATIC_LIBS)
|
||||
list(APPEND VCAI_SRCS main.cpp StdInc.cpp)
|
||||
endif()
|
||||
assign_source_group(${VCAI_SRCS} ${VCAI_HEADERS})
|
||||
|
||||
if(ENABLE_STATIC_AI_LIBS)
|
||||
if(ENABLE_STATIC_LIBS)
|
||||
add_library(VCAI STATIC ${VCAI_SRCS} ${VCAI_HEADERS})
|
||||
else()
|
||||
add_library(VCAI SHARED ${VCAI_SRCS} ${VCAI_HEADERS})
|
||||
@ -107,7 +107,7 @@ else()
|
||||
endif()
|
||||
|
||||
target_include_directories(VCAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(VCAI PUBLIC ${VCMI_LIB_TARGET} fuzzylite::fuzzylite)
|
||||
target_link_libraries(VCAI PUBLIC vcmi fuzzylite::fuzzylite)
|
||||
|
||||
vcmi_set_output_dir(VCAI "AI")
|
||||
enable_pch(VCAI)
|
||||
|
@ -37,7 +37,9 @@ void engineBase::addRule(const std::string & txt)
|
||||
|
||||
struct armyStructure
|
||||
{
|
||||
float walkers, shooters, flyers;
|
||||
float walkers;
|
||||
float shooters;
|
||||
float flyers;
|
||||
ui32 maxSpeed;
|
||||
};
|
||||
|
||||
@ -406,7 +408,7 @@ float VisitObjEngine::evaluate(Goals::VisitObj & goal)
|
||||
else
|
||||
{
|
||||
MapObjectsEvaluator::getInstance().addObjectData(obj->ID, obj->subID, 0);
|
||||
logGlobal->error("AI met object type it doesn't know - ID: " + std::to_string(obj->ID) + ", subID: " + std::to_string(obj->subID) + " - adding to database with value " + std::to_string(objValue));
|
||||
logGlobal->error("AI met object type it doesn't know - ID: %d, subID: %d - adding to database with value %d ", obj->ID, obj->subID, objValue);
|
||||
}
|
||||
|
||||
setSharedFuzzyVariables(goal);
|
||||
|
@ -38,8 +38,14 @@ public:
|
||||
TacticalAdvantageEngine();
|
||||
float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us
|
||||
private:
|
||||
fl::InputVariable * ourWalkers, *ourShooters, *ourFlyers, *enemyWalkers, *enemyShooters, *enemyFlyers;
|
||||
fl::InputVariable * ourSpeed, *enemySpeed;
|
||||
fl::InputVariable * ourWalkers;
|
||||
fl::InputVariable * ourShooters;
|
||||
fl::InputVariable * ourFlyers;
|
||||
fl::InputVariable * enemyWalkers;
|
||||
fl::InputVariable * enemyShooters;
|
||||
fl::InputVariable * enemyFlyers;
|
||||
fl::InputVariable * ourSpeed;
|
||||
fl::InputVariable * enemySpeed;
|
||||
fl::InputVariable * bankPresent;
|
||||
fl::InputVariable * castleWalls;
|
||||
fl::OutputVariable * threat;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user