mirror of
https://github.com/vcmi/vcmi.git
synced 2025-03-17 20:58:07 +02:00
Merge branch 'develop' into timed_events_objects_removal
This commit is contained in:
commit
e9be46af98
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -15,6 +15,7 @@ Please attach game logs: `VCMI_client.txt`, `VCMI_server.txt` etc.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
@ -24,7 +25,7 @@ Steps to reproduce the behavior:
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Actual behavior**
|
||||
A clear description what is currently happening
|
||||
A clear description what is currently happening
|
||||
|
||||
**Did it work earlier?**
|
||||
If this something which worked well some time ago, please let us know about version where it works or at date when it worked.
|
||||
@ -33,8 +34,9 @@ If this something which worked well some time ago, please let us know about vers
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Version**
|
||||
- OS: [e.g. Windows, macOS Intel, macOS ARM, Android, Linux, iOS]
|
||||
- Version: [VCMI version]
|
||||
|
||||
- OS: [e.g. Windows, macOS Intel, macOS ARM, Android, Linux, iOS]
|
||||
- Version: [VCMI version]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
135
.github/workflows/github.yml
vendored
135
.github/workflows/github.yml
vendored
@ -3,7 +3,6 @@ name: VCMI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- features/*
|
||||
- beta
|
||||
- master
|
||||
- develop
|
||||
@ -22,84 +21,112 @@ jobs:
|
||||
- platform: linux-qt6
|
||||
os: ubuntu-24.04
|
||||
test: 0
|
||||
before_install: linux_qt6.sh
|
||||
preset: linux-clang-test
|
||||
- platform: linux
|
||||
os: ubuntu-24.04
|
||||
test: 1
|
||||
before_install: linux_qt5.sh
|
||||
preset: linux-gcc-test
|
||||
- platform: linux
|
||||
os: ubuntu-20.04
|
||||
test: 0
|
||||
before_install: linux_qt5.sh
|
||||
preset: linux-gcc-debug
|
||||
- platform: mac-intel
|
||||
os: macos-13
|
||||
test: 0
|
||||
pack: 1
|
||||
upload: 1
|
||||
pack_type: Release
|
||||
extension: dmg
|
||||
before_install: macos.sh
|
||||
preset: macos-conan-ninja-release
|
||||
conan_profile: macos-intel
|
||||
conan_prebuilts: dependencies-mac-intel
|
||||
conan_options: --options with_apple_system_libs=True
|
||||
artifact_platform: intel
|
||||
- platform: mac-arm
|
||||
os: macos-13
|
||||
test: 0
|
||||
pack: 1
|
||||
upload: 1
|
||||
pack_type: Release
|
||||
extension: dmg
|
||||
before_install: macos.sh
|
||||
preset: macos-arm-conan-ninja-release
|
||||
conan_profile: macos-arm
|
||||
conan_prebuilts: dependencies-mac-arm
|
||||
conan_options: --options with_apple_system_libs=True
|
||||
artifact_platform: arm
|
||||
- platform: ios
|
||||
os: macos-13
|
||||
test: 0
|
||||
pack: 1
|
||||
upload: 1
|
||||
pack_type: Release
|
||||
extension: ipa
|
||||
before_install: macos.sh
|
||||
preset: ios-release-conan-ccache
|
||||
conan_profile: ios-arm64
|
||||
conan_prebuilts: dependencies-ios
|
||||
conan_options: --options with_apple_system_libs=True
|
||||
- platform: msvc
|
||||
- platform: msvc-x64
|
||||
os: windows-latest
|
||||
test: 0
|
||||
pack: 1
|
||||
upload: 1
|
||||
pack_type: RelWithDebInfo
|
||||
extension: exe
|
||||
before_install: msvc.sh
|
||||
preset: windows-msvc-release
|
||||
- platform: msvc-x86
|
||||
os: windows-latest
|
||||
test: 0
|
||||
pack: 1
|
||||
pack_type: RelWithDebInfo
|
||||
extension: exe
|
||||
preset: windows-msvc-release
|
||||
- platform: mingw
|
||||
os: ubuntu-22.04
|
||||
before_install: msvc.sh
|
||||
preset: windows-msvc-release-x86
|
||||
- platform: mingw_x86_64
|
||||
os: ubuntu-24.04
|
||||
test: 0
|
||||
pack: 1
|
||||
pack_type: Release
|
||||
extension: exe
|
||||
cpack_args: -D CPACK_NSIS_EXECUTABLE=`which makensis`
|
||||
cmake_args: -G Ninja
|
||||
before_install: mingw.sh
|
||||
preset: windows-mingw-conan-linux
|
||||
conan_profile: mingw64-linux.jinja
|
||||
- platform: mingw-32
|
||||
os: ubuntu-22.04
|
||||
conan_prebuilts: dependencies-mingw-x86-64
|
||||
- platform: mingw_x86
|
||||
os: ubuntu-24.04
|
||||
test: 0
|
||||
pack: 1
|
||||
pack_type: Release
|
||||
extension: exe
|
||||
cpack_args: -D CPACK_NSIS_EXECUTABLE=`which makensis`
|
||||
cmake_args: -G Ninja
|
||||
before_install: mingw.sh
|
||||
preset: windows-mingw-conan-linux
|
||||
conan_profile: mingw32-linux.jinja
|
||||
conan_prebuilts: dependencies-mingw-x86
|
||||
- platform: android-32
|
||||
os: macos-14
|
||||
os: ubuntu-24.04
|
||||
upload: 1
|
||||
extension: apk
|
||||
preset: android-conan-ninja-release
|
||||
conan_profile: android-32
|
||||
conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT
|
||||
before_install: android.sh
|
||||
conan_profile: android-32-ndk
|
||||
conan_prebuilts: dependencies-android-armeabi-v7a
|
||||
artifact_platform: armeabi-v7a
|
||||
- platform: android-64
|
||||
os: macos-14
|
||||
os: ubuntu-24.04
|
||||
upload: 1
|
||||
extension: apk
|
||||
preset: android-conan-ninja-release
|
||||
conan_profile: android-64
|
||||
conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT
|
||||
before_install: android.sh
|
||||
conan_profile: android-64-ndk
|
||||
conan_prebuilts: dependencies-android-arm64-v8a
|
||||
artifact_platform: arm64-v8a
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
@ -107,15 +134,25 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Dependencies
|
||||
run: source '${{github.workspace}}/CI/${{matrix.platform}}/before_install.sh'
|
||||
- name: Prepare CI
|
||||
if: "${{ matrix.before_install != '' }}"
|
||||
run: source '${{github.workspace}}/CI/before_install/${{matrix.before_install}}'
|
||||
env:
|
||||
VCMI_BUILD_PLATFORM: x64
|
||||
|
||||
- name: Install Conan Dependencies
|
||||
if: "${{ matrix.conan_prebuilts != '' }}"
|
||||
run: source '${{github.workspace}}/CI/install_conan_dependencies.sh' '${{matrix.conan_prebuilts}}'
|
||||
|
||||
- name: Install vcpkg Dependencies
|
||||
if: ${{ startsWith(matrix.platform, 'msvc') }}
|
||||
run: source '${{github.workspace}}/CI/install_vcpkg_dependencies.sh' '${{matrix.platform}}'
|
||||
|
||||
# ensure the ccache for each PR is separate so they don't interfere with each other
|
||||
# fall back to ccache of the vcmi/vcmi repo if no PR-specific ccache is found
|
||||
- name: ccache for PRs
|
||||
@ -157,15 +194,13 @@ jobs:
|
||||
mkdir -p ~/.local/share/vcmi/
|
||||
mv h3_assets/* ~/.local/share/vcmi/
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
- name: Install Conan
|
||||
if: "${{ matrix.conan_profile != '' }}"
|
||||
with:
|
||||
python-version: '3.10'
|
||||
run: pipx install 'conan<2.0'
|
||||
|
||||
- name: Conan setup
|
||||
- name: Install Conan profile
|
||||
if: "${{ matrix.conan_profile != '' }}"
|
||||
run: |
|
||||
pip3 install 'conan<2.0'
|
||||
conan profile new default --detect
|
||||
conan install . \
|
||||
--install-folder=conan-generated \
|
||||
@ -177,7 +212,13 @@ jobs:
|
||||
env:
|
||||
GENERATE_ONLY_BUILT_CONFIG: 1
|
||||
|
||||
- uses: actions/setup-java@v4
|
||||
# Workaround for gradle not discovering SDK that was installed via conan
|
||||
- name: Find Android NDK
|
||||
if: ${{ startsWith(matrix.platform, 'android') }}
|
||||
run: sudo ln -s -T /home/runner/.conan/data/android-ndk/r25c/_/_/package/4db1be536558d833e52e862fd84d64d75c2b3656/bin /usr/local/lib/android/sdk/ndk/25.2.9519653
|
||||
|
||||
- name: Install Java
|
||||
uses: actions/setup-java@v4
|
||||
if: ${{ startsWith(matrix.platform, 'android') }}
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
@ -208,11 +249,11 @@ jobs:
|
||||
elif [[ (${{matrix.preset}} == android-conan-ninja-release) && (${{github.ref}} != 'refs/heads/master') ]]
|
||||
then
|
||||
cmake -DENABLE_CCACHE:BOOL=ON -DANDROID_GRADLE_PROPERTIES="applicationIdSuffix=.daily;signingConfig=dailySigning;applicationLabel=VCMI daily" --preset ${{ matrix.preset }}
|
||||
elif [[ ${{matrix.platform}} != msvc ]]
|
||||
elif [[ ${{startsWith(matrix.platform, 'msvc') }} ]]
|
||||
then
|
||||
cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }}
|
||||
else
|
||||
cmake --preset ${{ matrix.preset }}
|
||||
else
|
||||
cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }}
|
||||
fi
|
||||
|
||||
- name: Build
|
||||
@ -242,10 +283,13 @@ jobs:
|
||||
if: ${{ matrix.pack == 1 }}
|
||||
run: |
|
||||
cd '${{github.workspace}}/out/build/${{matrix.preset}}'
|
||||
CPACK_PATH=`which -a cpack | grep -m1 -v -i chocolatey`
|
||||
counter=0; until "$CPACK_PATH" -C ${{matrix.pack_type}} ${{ matrix.cpack_args }} || ((counter > 20)); do sleep 3; ((counter++)); done
|
||||
test -f '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' \
|
||||
&& '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' '${{github.workspace}}' "$(ls '${{ env.VCMI_PACKAGE_FILE_NAME }}'.*)"
|
||||
|
||||
# Workaround for CPack bug on macOS 13
|
||||
counter=0
|
||||
until cpack -C ${{matrix.pack_type}} || ((counter > 20)); do
|
||||
sleep 3
|
||||
((counter++))
|
||||
done
|
||||
rm -rf _CPack_Packages
|
||||
|
||||
- name: Artifacts
|
||||
@ -253,6 +297,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
|
||||
compression-level: 0
|
||||
path: |
|
||||
${{github.workspace}}/out/build/${{matrix.preset}}/${{ env.VCMI_PACKAGE_FILE_NAME }}.${{ matrix.extension }}
|
||||
|
||||
@ -268,32 +313,35 @@ jobs:
|
||||
echo "ANDROID_APK_PATH=$ANDROID_APK_PATH" >> $GITHUB_ENV
|
||||
echo "ANDROID_AAB_PATH=$ANDROID_AAB_PATH" >> $GITHUB_ENV
|
||||
|
||||
- name: Android apk artifacts
|
||||
- name: Upload android apk artifacts
|
||||
if: ${{ startsWith(matrix.platform, 'android') }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
|
||||
compression-level: 0
|
||||
path: |
|
||||
${{ env.ANDROID_APK_PATH }}
|
||||
|
||||
- name: Android aab artifacts
|
||||
if: ${{ startsWith(matrix.platform, 'android') }}
|
||||
- name: Upload Android aab artifacts
|
||||
if: ${{ startsWith(matrix.platform, 'android') && github.ref == 'refs/heads/master' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} - aab
|
||||
compression-level: 0
|
||||
path: |
|
||||
${{ env.ANDROID_AAB_PATH }}
|
||||
|
||||
- name: Symbols
|
||||
if: ${{ matrix.platform == 'msvc' }}
|
||||
- name: Upload debug symbols
|
||||
if: ${{ startsWith(matrix.platform, 'msvc') }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} - symbols
|
||||
compression-level: 9
|
||||
path: |
|
||||
${{github.workspace}}/**/*.pdb
|
||||
|
||||
- name: Upload build
|
||||
if: ${{ (matrix.pack == 1 || startsWith(matrix.platform, 'android')) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc' && matrix.platform != 'mingw-32' }}
|
||||
if: ${{ (matrix.upload == 1) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master') }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
if [ -z '${{ env.ANDROID_APK_PATH }}' ] ; then
|
||||
@ -343,11 +391,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
if: "${{ matrix.conan_profile != '' }}"
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Ensure LF line endings
|
||||
run: |
|
||||
find . -path ./.git -prune -o -path ./AI/FuzzyLite -prune -o -path ./test/googletest \
|
||||
@ -358,4 +401,10 @@ jobs:
|
||||
- name: Validate JSON
|
||||
run: |
|
||||
sudo apt install python3-jstyleson
|
||||
python3 CI/linux-qt6/validate_json.py
|
||||
python3 CI/validate_json.py
|
||||
|
||||
- name: Validate Markdown
|
||||
uses: DavidAnson/markdownlint-cli2-action@v18
|
||||
with:
|
||||
config: 'CI/example.markdownlint-cli2.jsonc'
|
||||
globs: '**/*.md'
|
||||
|
@ -58,7 +58,7 @@ void DamageCache::buildObstacleDamageCache(std::shared_ptr<HypotheticBattle> hb,
|
||||
return u->alive() && !u->isTurret() && u->getPosition().isValid();
|
||||
});
|
||||
|
||||
std::shared_ptr<HypotheticBattle> inner = std::make_shared<HypotheticBattle>(hb->env, hb);
|
||||
auto inner = std::make_shared<HypotheticBattle>(hb->env, hb);
|
||||
|
||||
for(auto stack : stacks)
|
||||
{
|
||||
|
@ -394,7 +394,7 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
|
||||
{
|
||||
std::set<BattleHex> obstacleHexes;
|
||||
|
||||
auto insertAffected = [](const CObstacleInstance & spellObst, std::set<BattleHex> obstacleHexes) {
|
||||
auto insertAffected = [](const CObstacleInstance & spellObst, std::set<BattleHex> & obstacleHexes) {
|
||||
auto affectedHexes = spellObst.getAffectedTiles();
|
||||
obstacleHexes.insert(affectedHexes.cbegin(), affectedHexes.cend());
|
||||
};
|
||||
@ -675,7 +675,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
spells::BattleCast cast(state.get(), hero, spells::Mode::HERO, ps.spell);
|
||||
cast.castEval(state->getServerCallback(), ps.dest);
|
||||
|
||||
auto allUnits = state->battleGetUnitsIf([](const battle::Unit * u) -> bool { return true; });
|
||||
auto allUnits = state->battleGetUnitsIf([](const battle::Unit * u) -> bool { return u->isValidTarget(); });
|
||||
|
||||
auto needFullEval = vstd::contains_if(allUnits, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
@ -731,7 +731,6 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
|
||||
ps.value = scoreEvaluator.evaluateExchange(updatedAttack, cachedAttack.turn, *targets, innerCache, state);
|
||||
}
|
||||
|
||||
for(const auto & unit : allUnits)
|
||||
{
|
||||
if(!unit->isValidTarget(true))
|
||||
@ -771,11 +770,31 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
ps.value -= 4 * dpsReduce * scoreEvaluator.getNegativeEffectMultiplier();
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace(
|
||||
"Spell affects %s (%d), dps: %2f",
|
||||
unit->creatureId().toCreature()->getNameSingularTranslated(),
|
||||
unit->getCount(),
|
||||
dpsReduce);
|
||||
// Ensure ps.dest is not empty before accessing the first element
|
||||
if (!ps.dest.empty())
|
||||
{
|
||||
logAi->trace(
|
||||
"Spell %s to %d affects %s (%d), dps: %2f oldHealth: %d newHealth: %d",
|
||||
ps.spell->getNameTranslated(),
|
||||
ps.dest.at(0).hexValue.hex, // Safe to access .at(0) now
|
||||
unit->creatureId().toCreature()->getNameSingularTranslated(),
|
||||
unit->getCount(),
|
||||
dpsReduce,
|
||||
oldHealth,
|
||||
newHealth);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle the case where ps.dest is empty
|
||||
logAi->trace(
|
||||
"Spell %s has no destination, affects %s (%d), dps: %2f oldHealth: %d newHealth: %d",
|
||||
ps.spell->getNameTranslated(),
|
||||
unit->creatureId().toCreature()->getNameSingularTranslated(),
|
||||
unit->getCount(),
|
||||
dpsReduce,
|
||||
oldHealth,
|
||||
newHealth);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@ -906,7 +906,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
|
||||
{
|
||||
std::vector<const battle::Unit *> result;
|
||||
|
||||
for(int i = 0; i < turnOrder.size(); i++)
|
||||
for(int i = 0; i < turnOrder.size(); i++, turn++)
|
||||
{
|
||||
auto & turnQueue = turnOrder[i];
|
||||
HypotheticBattle turnBattle(env.get(), cb);
|
||||
|
@ -531,44 +531,44 @@ vstd::RNG * HypotheticBattle::HypotheticServerCallback::getRNG()
|
||||
return &rngStub;
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(CPackForClient * pack)
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(CPackForClient & pack)
|
||||
{
|
||||
logAi->error("Package of type %s is not allowed in battle evaluation", typeid(pack).name());
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(BattleLogMessage * pack)
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(BattleLogMessage & pack)
|
||||
{
|
||||
pack->applyBattle(owner);
|
||||
pack.applyBattle(owner);
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(BattleStackMoved * pack)
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(BattleStackMoved & pack)
|
||||
{
|
||||
pack->applyBattle(owner);
|
||||
pack.applyBattle(owner);
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(BattleUnitsChanged * pack)
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(BattleUnitsChanged & pack)
|
||||
{
|
||||
pack->applyBattle(owner);
|
||||
pack.applyBattle(owner);
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(SetStackEffect * pack)
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(SetStackEffect & pack)
|
||||
{
|
||||
pack->applyBattle(owner);
|
||||
pack.applyBattle(owner);
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(StacksInjured * pack)
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(StacksInjured & pack)
|
||||
{
|
||||
pack->applyBattle(owner);
|
||||
pack.applyBattle(owner);
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(BattleObstaclesChanged * pack)
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(BattleObstaclesChanged & pack)
|
||||
{
|
||||
pack->applyBattle(owner);
|
||||
pack.applyBattle(owner);
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(CatapultAttack * pack)
|
||||
void HypotheticBattle::HypotheticServerCallback::apply(CatapultAttack & pack)
|
||||
{
|
||||
pack->applyBattle(owner);
|
||||
pack.applyBattle(owner);
|
||||
}
|
||||
|
||||
HypotheticBattle::HypotheticEnvironment::HypotheticEnvironment(HypotheticBattle * owner_, const Environment * upperEnvironment)
|
||||
|
@ -189,15 +189,15 @@ private:
|
||||
|
||||
vstd::RNG * getRNG() override;
|
||||
|
||||
void apply(CPackForClient * pack) override;
|
||||
void apply(CPackForClient & pack) override;
|
||||
|
||||
void apply(BattleLogMessage * pack) override;
|
||||
void apply(BattleStackMoved * pack) override;
|
||||
void apply(BattleUnitsChanged * pack) override;
|
||||
void apply(SetStackEffect * pack) override;
|
||||
void apply(StacksInjured * pack) override;
|
||||
void apply(BattleObstaclesChanged * pack) override;
|
||||
void apply(CatapultAttack * pack) override;
|
||||
void apply(BattleLogMessage & pack) override;
|
||||
void apply(BattleStackMoved & pack) override;
|
||||
void apply(BattleUnitsChanged & pack) override;
|
||||
void apply(SetStackEffect & pack) override;
|
||||
void apply(StacksInjured & pack) override;
|
||||
void apply(BattleObstaclesChanged & pack) override;
|
||||
void apply(CatapultAttack & pack) override;
|
||||
private:
|
||||
HypotheticBattle * owner;
|
||||
RNGStub rngStub;
|
||||
|
@ -17,7 +17,6 @@
|
||||
#include "../../lib/mapObjects/ObjectTemplate.h"
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/IGameSettings.h"
|
||||
#include "../../lib/gameState/CGameState.h"
|
||||
#include "../../lib/serializer/CTypeList.h"
|
||||
@ -35,11 +34,6 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
// our to enemy strength ratio constants
|
||||
const float SAFE_ATTACK_CONSTANT = 1.1f;
|
||||
const float RETREAT_THRESHOLD = 0.3f;
|
||||
const double RETREAT_ABSOLUTE_THRESHOLD = 10000.;
|
||||
|
||||
//one thread may be turn of AI and another will be handling a side effect for AI2
|
||||
thread_local CCallback * cb = nullptr;
|
||||
thread_local AIGateway * ai = nullptr;
|
||||
@ -554,7 +548,7 @@ std::optional<BattleAction> AIGateway::makeSurrenderRetreatDecision(const Battle
|
||||
double fightRatio = ourStrength / (double)battleState.getEnemyStrength();
|
||||
|
||||
// if we have no towns - things are already bad, so retreat is not an option.
|
||||
if(cb->getTownsInfo().size() && ourStrength < RETREAT_ABSOLUTE_THRESHOLD && fightRatio < RETREAT_THRESHOLD && battleState.canFlee)
|
||||
if(cb->getTownsInfo().size() && ourStrength < nullkiller->settings->getRetreatThresholdAbsolute() && fightRatio < nullkiller->settings->getRetreatThresholdRelative() && battleState.canFlee)
|
||||
{
|
||||
return BattleAction::makeRetreat(battleState.ourSide);
|
||||
}
|
||||
@ -649,12 +643,12 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
|
||||
auto danger = nullkiller->dangerEvaluator->evaluateDanger(target, hero.get());
|
||||
auto ratio = static_cast<float>(danger) / hero->getTotalStrength();
|
||||
|
||||
answer = 1;
|
||||
answer = true;
|
||||
|
||||
if(topObj->id != goalObjectID && nullkiller->dangerEvaluator->evaluateDanger(topObj) > 0)
|
||||
{
|
||||
// no if we do not aim to visit this object
|
||||
answer = 0;
|
||||
answer = false;
|
||||
}
|
||||
|
||||
logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), hero.name(), ratio);
|
||||
@ -671,7 +665,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
|
||||
else if(objType == Obj::ARTIFACT || objType == Obj::RESOURCE)
|
||||
{
|
||||
bool dangerUnknown = danger == 0;
|
||||
bool dangerTooHigh = ratio > (1 / SAFE_ATTACK_CONSTANT);
|
||||
bool dangerTooHigh = ratio * nullkiller->settings->getSafeAttackRatio() > 1;
|
||||
|
||||
answer = !dangerUnknown && !dangerTooHigh;
|
||||
}
|
||||
@ -764,7 +758,7 @@ void AIGateway::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstan
|
||||
//you can't request action from action-response thread
|
||||
requestActionASAP([=]()
|
||||
{
|
||||
if(removableUnits && up->tempOwner == down->tempOwner && nullkiller->settings->isGarrisonTroopsUsageAllowed() && !cb->getStartInfo()->isSteadwickFallCampaignMission())
|
||||
if(removableUnits && up->tempOwner == down->tempOwner && nullkiller->settings->isGarrisonTroopsUsageAllowed() && !cb->getStartInfo()->isRestorationOfErathiaCampaign())
|
||||
{
|
||||
pickBestCreatures(down, up);
|
||||
}
|
||||
@ -864,7 +858,7 @@ void AIGateway::makeTurn()
|
||||
|
||||
void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
|
||||
{
|
||||
LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->pos.toString());
|
||||
LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->anchorPos().toString());
|
||||
switch(obj->ID)
|
||||
{
|
||||
case Obj::TOWN:
|
||||
@ -1056,7 +1050,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
|
||||
//FIXME: why are the above possible to be null?
|
||||
|
||||
bool emptySlotFound = false;
|
||||
for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
|
||||
for(auto slot : artifact->getType()->getPossibleSlots().at(target->bearerType()))
|
||||
{
|
||||
if(target->isPositionFree(slot) && artifact->canBePutAt(target, slot, true)) //combined artifacts are not always allowed to move
|
||||
{
|
||||
@ -1069,7 +1063,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
|
||||
}
|
||||
if(!emptySlotFound) //try to put that atifact in already occupied slot
|
||||
{
|
||||
for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
|
||||
for(auto slot : artifact->getType()->getPossibleSlots().at(target->bearerType()))
|
||||
{
|
||||
auto otherSlot = target->getSlot(slot);
|
||||
if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one
|
||||
@ -1080,8 +1074,8 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
|
||||
{
|
||||
logAi->trace(
|
||||
"Exchange artifacts %s <-> %s",
|
||||
artifact->artType->getNameTranslated(),
|
||||
otherSlot->artifact->artType->getNameTranslated());
|
||||
artifact->getType()->getNameTranslated(),
|
||||
otherSlot->artifact->getType()->getNameTranslated());
|
||||
|
||||
if(!otherSlot->artifact->canBePutAt(artHolder, location.slot, true))
|
||||
{
|
||||
@ -1130,10 +1124,10 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re
|
||||
{
|
||||
for(auto stack : recruiter->Slots())
|
||||
{
|
||||
if(!stack.second->type)
|
||||
if(!stack.second->getType())
|
||||
continue;
|
||||
|
||||
auto duplicatingSlot = recruiter->getSlotFor(stack.second->type);
|
||||
auto duplicatingSlot = recruiter->getSlotFor(stack.second->getCreature());
|
||||
|
||||
if(duplicatingSlot != stack.first)
|
||||
{
|
||||
@ -1454,8 +1448,8 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
|
||||
void AIGateway::buildStructure(const CGTownInstance * t, BuildingID building)
|
||||
{
|
||||
auto name = t->town->buildings.at(building)->getNameTranslated();
|
||||
logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->pos.toString());
|
||||
auto name = t->getTown()->buildings.at(building)->getNameTranslated();
|
||||
logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->anchorPos().toString());
|
||||
cb->buildBuilding(t, building); //just do this;
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,6 @@
|
||||
|
||||
#include "../../lib/UnlockGuard.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../lib/mapping/CMapDefines.h"
|
||||
#include "../../lib/gameState/QuestInfo.h"
|
||||
@ -147,21 +146,21 @@ bool HeroPtr::operator==(const HeroPtr & rhs) const
|
||||
return h == rhs.get(true);
|
||||
}
|
||||
|
||||
bool isSafeToVisit(const CGHeroInstance * h, const CCreatureSet * heroArmy, uint64_t dangerStrength)
|
||||
bool isSafeToVisit(const CGHeroInstance * h, const CCreatureSet * heroArmy, uint64_t dangerStrength, float safeAttackRatio)
|
||||
{
|
||||
const ui64 heroStrength = h->getFightingStrength() * heroArmy->getArmyStrength();
|
||||
const ui64 heroStrength = h->getHeroStrength() * heroArmy->getArmyStrength();
|
||||
|
||||
if(dangerStrength)
|
||||
{
|
||||
return heroStrength / SAFE_ATTACK_CONSTANT > dangerStrength;
|
||||
return heroStrength > dangerStrength * safeAttackRatio;
|
||||
}
|
||||
|
||||
return true; //there's no danger
|
||||
}
|
||||
|
||||
bool isSafeToVisit(const CGHeroInstance * h, uint64_t dangerStrength)
|
||||
bool isSafeToVisit(const CGHeroInstance * h, uint64_t dangerStrength, float safeAttackRatio)
|
||||
{
|
||||
return isSafeToVisit(h, h, dangerStrength);
|
||||
return isSafeToVisit(h, h, dangerStrength, safeAttackRatio);
|
||||
}
|
||||
|
||||
bool isObjectRemovable(const CGObjectInstance * obj)
|
||||
@ -194,7 +193,7 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
|
||||
{
|
||||
// TODO: Such information should be provided by pathfinder
|
||||
// Tile must be free or with unoccupied boat
|
||||
if(!t->blocked)
|
||||
if(!t->blocked())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -268,8 +267,8 @@ bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2)
|
||||
|
||||
bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2)
|
||||
{
|
||||
auto art1 = a1->artType;
|
||||
auto art2 = a2->artType;
|
||||
auto art1 = a1->getType();
|
||||
auto art2 = a2->getType();
|
||||
|
||||
if(art1->getPrice() == art2->getPrice())
|
||||
return art1->valOfBonuses(BonusType::PRIMARY_SKILL) > art2->valOfBonuses(BonusType::PRIMARY_SKILL);
|
||||
@ -313,7 +312,7 @@ int getDuplicatingSlots(const CArmedInstance * army)
|
||||
|
||||
for(auto stack : army->Slots())
|
||||
{
|
||||
if(stack.second->type && army->getSlotFor(stack.second->type) != stack.first)
|
||||
if(stack.second->getCreature() && army->getSlotFor(stack.second->getCreature()) != stack.first)
|
||||
duplicatingSlots++;
|
||||
}
|
||||
|
||||
@ -388,7 +387,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
|
||||
{
|
||||
for(auto slot : h->Slots())
|
||||
{
|
||||
if(slot.second->type->hasUpgrades())
|
||||
if(slot.second->getType()->hasUpgrades())
|
||||
return true; //TODO: check price?
|
||||
}
|
||||
return false;
|
||||
|
@ -61,11 +61,6 @@ const int GOLD_MINE_PRODUCTION = 1000;
|
||||
const int WOOD_ORE_MINE_PRODUCTION = 2;
|
||||
const int RESOURCE_MINE_PRODUCTION = 1;
|
||||
const int ACTUAL_RESOURCE_COUNT = 7;
|
||||
const int ALLOWED_ROAMING_HEROES = 8;
|
||||
|
||||
//implementation-dependent
|
||||
extern const float SAFE_ATTACK_CONSTANT;
|
||||
extern const int GOLD_RESERVE;
|
||||
|
||||
extern thread_local CCallback * cb;
|
||||
|
||||
@ -213,8 +208,8 @@ bool isBlockVisitObj(const int3 & pos);
|
||||
bool isWeeklyRevisitable(const Nullkiller * ai, const CGObjectInstance * obj);
|
||||
|
||||
bool isObjectRemovable(const CGObjectInstance * obj); //FIXME FIXME: move logic to object property!
|
||||
bool isSafeToVisit(const CGHeroInstance * h, uint64_t dangerStrength);
|
||||
bool isSafeToVisit(const CGHeroInstance * h, const CCreatureSet *, uint64_t dangerStrength);
|
||||
bool isSafeToVisit(const CGHeroInstance * h, uint64_t dangerStrength, float safeAttackRatio);
|
||||
bool isSafeToVisit(const CGHeroInstance * h, const CCreatureSet *, uint64_t dangerStrength, float safeAttackRatio);
|
||||
|
||||
bool compareHeroStrength(const CGHeroInstance * h1, const CGHeroInstance * h2);
|
||||
bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2);
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../../../CCallback.h"
|
||||
#include "../../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../../lib/IGameSettings.h"
|
||||
#include "../../../lib/GameConstants.h"
|
||||
|
||||
namespace NKAI
|
||||
@ -90,7 +91,7 @@ std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, c
|
||||
{
|
||||
for(auto & i : armyPtr->Slots())
|
||||
{
|
||||
auto cre = dynamic_cast<const CCreature*>(i.second->type);
|
||||
auto cre = dynamic_cast<const CCreature*>(i.second->getType());
|
||||
auto & slotInfp = creToPower[cre];
|
||||
|
||||
slotInfp.creature = cre;
|
||||
@ -144,7 +145,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
|
||||
|
||||
for(auto & slot : sortedSlots)
|
||||
{
|
||||
alignmentMap[slot.creature->getFaction()] += slot.power;
|
||||
alignmentMap[slot.creature->getFactionID()] += slot.power;
|
||||
}
|
||||
|
||||
std::set<FactionID> allowedFactions;
|
||||
@ -152,16 +153,6 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
|
||||
uint64_t armyValue = 0;
|
||||
|
||||
TemporaryArmy newArmyInstance;
|
||||
auto bonusModifiers = armyCarrier->getBonuses(Selector::type()(BonusType::MORALE));
|
||||
|
||||
for(auto bonus : *bonusModifiers)
|
||||
{
|
||||
// army bonuses will change and object bonuses are temporary
|
||||
if(bonus->source != BonusSource::ARMY && bonus->source != BonusSource::OBJECT_INSTANCE && bonus->source != BonusSource::OBJECT_TYPE)
|
||||
{
|
||||
newArmyInstance.addNewBonus(std::make_shared<Bonus>(*bonus));
|
||||
}
|
||||
}
|
||||
|
||||
while(allowedFactions.size() < alignmentMap.size())
|
||||
{
|
||||
@ -178,7 +169,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
|
||||
|
||||
for(auto & slot : sortedSlots)
|
||||
{
|
||||
if(vstd::contains(allowedFactions, slot.creature->getFaction()))
|
||||
if(vstd::contains(allowedFactions, slot.creature->getFactionID()))
|
||||
{
|
||||
auto slotID = newArmyInstance.getSlotFor(slot.creature->getId());
|
||||
|
||||
@ -197,16 +188,18 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
|
||||
auto morale = slot.second->moraleVal();
|
||||
auto multiplier = 1.0f;
|
||||
|
||||
const float BadMoraleChance = 0.083f;
|
||||
const float HighMoraleChance = 0.04f;
|
||||
const auto & badMoraleDice = cb->getSettings().getVector(EGameSettings::COMBAT_BAD_MORALE_DICE);
|
||||
const auto & highMoraleDice = cb->getSettings().getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE);
|
||||
|
||||
if(morale < 0)
|
||||
if(morale < 0 && !badMoraleDice.empty())
|
||||
{
|
||||
multiplier += morale * BadMoraleChance;
|
||||
size_t diceIndex = std::min<size_t>(badMoraleDice.size(), -morale) - 1;
|
||||
multiplier -= 1.0 / badMoraleDice.at(diceIndex);
|
||||
}
|
||||
else if(morale > 0)
|
||||
else if(morale > 0 && !highMoraleDice.empty())
|
||||
{
|
||||
multiplier += morale * HighMoraleChance;
|
||||
size_t diceIndex = std::min<size_t>(highMoraleDice.size(), morale) - 1;
|
||||
multiplier += 1.0 / highMoraleDice.at(diceIndex);
|
||||
}
|
||||
|
||||
newValue += multiplier * slot.second->getPower();
|
||||
|
@ -17,7 +17,7 @@ namespace NKAI
|
||||
|
||||
void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
|
||||
{
|
||||
auto townInfo = developmentInfo.town->town;
|
||||
auto townInfo = developmentInfo.town->getTown();
|
||||
auto creatures = townInfo->creatures;
|
||||
auto buildings = townInfo->getAllBuildings();
|
||||
|
||||
@ -31,7 +31,7 @@ void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
|
||||
}
|
||||
}
|
||||
|
||||
for(int level = 0; level < developmentInfo.town->town->creatures.size(); level++)
|
||||
for(int level = 0; level < developmentInfo.town->getTown()->creatures.size(); level++)
|
||||
{
|
||||
logAi->trace("Checking dwelling level %d", level);
|
||||
BuildingInfo nextToBuild = BuildingInfo();
|
||||
@ -39,7 +39,6 @@ void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
|
||||
for(int upgradeIndex : {1, 0})
|
||||
{
|
||||
BuildingID building = BuildingID(BuildingID::getDwellingFromLevel(level, upgradeIndex));
|
||||
|
||||
if(!vstd::contains(buildings, building))
|
||||
continue; // no such building in town
|
||||
|
||||
@ -73,16 +72,23 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo)
|
||||
|
||||
if(developmentInfo.existingDwellings.size() >= 2 && ai->cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday)
|
||||
{
|
||||
otherBuildings.push_back({BuildingID::CITADEL, BuildingID::CASTLE});
|
||||
otherBuildings.push_back({BuildingID::HORDE_1});
|
||||
otherBuildings.push_back({BuildingID::HORDE_2});
|
||||
}
|
||||
|
||||
otherBuildings.push_back({ BuildingID::CITADEL, BuildingID::CASTLE });
|
||||
otherBuildings.push_back({ BuildingID::RESOURCE_SILO });
|
||||
otherBuildings.push_back({ BuildingID::SPECIAL_1 });
|
||||
otherBuildings.push_back({ BuildingID::SPECIAL_2 });
|
||||
otherBuildings.push_back({ BuildingID::SPECIAL_3 });
|
||||
otherBuildings.push_back({ BuildingID::SPECIAL_4 });
|
||||
otherBuildings.push_back({ BuildingID::MARKETPLACE });
|
||||
|
||||
for(auto & buildingSet : otherBuildings)
|
||||
{
|
||||
for(auto & buildingID : buildingSet)
|
||||
{
|
||||
if(!developmentInfo.town->hasBuilt(buildingID) && developmentInfo.town->town->buildings.count(buildingID))
|
||||
if(!developmentInfo.town->hasBuilt(buildingID) && developmentInfo.town->getTown()->buildings.count(buildingID))
|
||||
{
|
||||
developmentInfo.addBuildingToBuild(getBuildingOrPrerequisite(developmentInfo.town, buildingID));
|
||||
|
||||
@ -141,6 +147,8 @@ void BuildAnalyzer::update()
|
||||
|
||||
auto towns = ai->cb->getTownsInfo();
|
||||
|
||||
float economyDevelopmentCost = 0;
|
||||
|
||||
for(const CGTownInstance* town : towns)
|
||||
{
|
||||
logAi->trace("Checking town %s", town->getNameTranslated());
|
||||
@ -153,6 +161,11 @@ void BuildAnalyzer::update()
|
||||
|
||||
requiredResources += developmentInfo.requiredResources;
|
||||
totalDevelopmentCost += developmentInfo.townDevelopmentCost;
|
||||
for(auto building : developmentInfo.toBuild)
|
||||
{
|
||||
if (building.dailyIncome[EGameResID::GOLD] > 0)
|
||||
economyDevelopmentCost += building.buildCostWithPrerequisites[EGameResID::GOLD];
|
||||
}
|
||||
armyCost += developmentInfo.armyCost;
|
||||
|
||||
for(auto bi : developmentInfo.toBuild)
|
||||
@ -171,15 +184,7 @@ void BuildAnalyzer::update()
|
||||
|
||||
updateDailyIncome();
|
||||
|
||||
if(ai->cb->getDate(Date::DAY) == 1)
|
||||
{
|
||||
goldPressure = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
goldPressure = ai->getLockedResources()[EGameResID::GOLD] / 5000.0f
|
||||
+ (float)armyCost[EGameResID::GOLD] / (1 + 2 * ai->getFreeGold() + (float)dailyIncome[EGameResID::GOLD] * 7.0f);
|
||||
}
|
||||
goldPressure = (ai->getLockedResources()[EGameResID::GOLD] + (float)armyCost[EGameResID::GOLD] + economyDevelopmentCost) / (1 + 2 * ai->getFreeGold() + (float)dailyIncome[EGameResID::GOLD] * 7.0f);
|
||||
|
||||
logAi->trace("Gold pressure: %f", goldPressure);
|
||||
}
|
||||
@ -198,7 +203,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
bool excludeDwellingDependencies) const
|
||||
{
|
||||
BuildingID building = toBuild;
|
||||
auto townInfo = town->town;
|
||||
auto townInfo = town->getTown();
|
||||
|
||||
const CBuilding * buildPtr = townInfo->buildings.at(building);
|
||||
const CCreature * creature = nullptr;
|
||||
@ -237,6 +242,12 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
logAi->trace("checking %s", info.name);
|
||||
logAi->trace("buildInfo %s", info.toString());
|
||||
|
||||
int highestFort = 0;
|
||||
for (auto twn : ai->cb->getTownsInfo())
|
||||
{
|
||||
highestFort = std::max(highestFort, (int)twn->fortLevel());
|
||||
}
|
||||
|
||||
if(!town->hasBuilt(building))
|
||||
{
|
||||
auto canBuild = ai->cb->canBuildStructure(town, building);
|
||||
@ -281,7 +292,15 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
prerequisite.baseCreatureID = info.baseCreatureID;
|
||||
prerequisite.prerequisitesCount++;
|
||||
prerequisite.armyCost = info.armyCost;
|
||||
prerequisite.dailyIncome = info.dailyIncome;
|
||||
bool haveSameOrBetterFort = false;
|
||||
if (prerequisite.id == BuildingID::FORT && highestFort >= CGTownInstance::EFortLevel::FORT)
|
||||
haveSameOrBetterFort = true;
|
||||
if (prerequisite.id == BuildingID::CITADEL && highestFort >= CGTownInstance::EFortLevel::CITADEL)
|
||||
haveSameOrBetterFort = true;
|
||||
if (prerequisite.id == BuildingID::CASTLE && highestFort >= CGTownInstance::EFortLevel::CASTLE)
|
||||
haveSameOrBetterFort = true;
|
||||
if(!haveSameOrBetterFort)
|
||||
prerequisite.dailyIncome = info.dailyIncome;
|
||||
|
||||
return prerequisite;
|
||||
}
|
||||
@ -327,7 +346,7 @@ bool BuildAnalyzer::hasAnyBuilding(int32_t alignment, BuildingID bid) const
|
||||
{
|
||||
for(auto tdi : developmentInfos)
|
||||
{
|
||||
if(tdi.town->getFaction() == alignment && tdi.town->hasBuilt(bid))
|
||||
if(tdi.town->getFactionID() == alignment && tdi.town->hasBuilt(bid))
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,6 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
|
||||
heroes[hero->tempOwner][hero] = HeroRole::MAIN;
|
||||
}
|
||||
|
||||
if(obj->ID == Obj::TOWN)
|
||||
{
|
||||
auto town = dynamic_cast<const CGTownInstance *>(obj);
|
||||
@ -140,6 +139,7 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
|
||||
newThreat.hero = path.targetHero;
|
||||
newThreat.turn = path.turn();
|
||||
newThreat.threat = path.getHeroStrength() * (1 - path.movementCost() / 2.0);
|
||||
newThreat.danger = path.getHeroStrength();
|
||||
|
||||
if(newThreat.value() > node.maximumDanger.value())
|
||||
@ -316,8 +316,8 @@ uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath &
|
||||
|
||||
const auto& info = getTileThreat(tile);
|
||||
|
||||
return (info.fastestDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.fastestDanger.danger))
|
||||
|| (info.maximumDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.maximumDanger.danger));
|
||||
return (info.fastestDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.fastestDanger.danger, ai->settings->getSafeAttackRatio()))
|
||||
|| (info.maximumDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.maximumDanger.danger, ai->settings->getSafeAttackRatio()));
|
||||
}
|
||||
|
||||
const HitMapNode & DangerHitMapAnalyzer::getObjectThreat(const CGObjectInstance * obj) const
|
||||
|
@ -22,6 +22,7 @@ struct HitMapInfo
|
||||
|
||||
uint64_t danger;
|
||||
uint8_t turn;
|
||||
float threat;
|
||||
HeroPtr hero;
|
||||
|
||||
HitMapInfo()
|
||||
@ -33,6 +34,7 @@ struct HitMapInfo
|
||||
{
|
||||
danger = 0;
|
||||
turn = 255;
|
||||
threat = 0;
|
||||
hero = HeroPtr();
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,6 @@
|
||||
#include "../StdInc.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../../lib/CHeroHandler.h"
|
||||
#include "../../../lib/IGameSettings.h"
|
||||
|
||||
namespace NKAI
|
||||
@ -71,7 +70,7 @@ float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance *
|
||||
|
||||
float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
|
||||
{
|
||||
auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, BonusSourceID(hero->type->getId()));
|
||||
auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, BonusSourceID(hero->getHeroTypeID()));
|
||||
auto secondarySkillBonus = Selector::targetSourceType()(BonusSource::SECONDARY_SKILL);
|
||||
auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus));
|
||||
auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(BonusSource::SECONDARY_SKILL));
|
||||
@ -96,7 +95,7 @@ float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
|
||||
|
||||
float HeroManager::evaluateFightingStrength(const CGHeroInstance * hero) const
|
||||
{
|
||||
return evaluateSpeciality(hero) + wariorSkillsScores.evaluateSecSkills(hero) + hero->level * 1.5f;
|
||||
return evaluateSpeciality(hero) + wariorSkillsScores.evaluateSecSkills(hero) + hero->getBasePrimarySkillValue(PrimarySkill::ATTACK) + hero->getBasePrimarySkillValue(PrimarySkill::DEFENSE) + hero->getBasePrimarySkillValue(PrimarySkill::SPELL_POWER) + hero->getBasePrimarySkillValue(PrimarySkill::KNOWLEDGE);
|
||||
}
|
||||
|
||||
void HeroManager::update()
|
||||
@ -109,7 +108,7 @@ void HeroManager::update()
|
||||
for(auto & hero : myHeroes)
|
||||
{
|
||||
scores[hero] = evaluateFightingStrength(hero);
|
||||
knownFightingStrength[hero->id] = hero->getFightingStrength();
|
||||
knownFightingStrength[hero->id] = hero->getHeroStrength();
|
||||
}
|
||||
|
||||
auto scoreSort = [&](const CGHeroInstance * h1, const CGHeroInstance * h2) -> bool
|
||||
@ -148,7 +147,10 @@ void HeroManager::update()
|
||||
|
||||
HeroRole HeroManager::getHeroRole(const HeroPtr & hero) const
|
||||
{
|
||||
return heroRoles.at(hero);
|
||||
if (heroRoles.find(hero) != heroRoles.end())
|
||||
return heroRoles.at(hero);
|
||||
else
|
||||
return HeroRole::SCOUT;
|
||||
}
|
||||
|
||||
const std::map<HeroPtr, HeroRole> & HeroManager::getHeroRoles() const
|
||||
@ -189,13 +191,11 @@ float HeroManager::evaluateHero(const CGHeroInstance * hero) const
|
||||
return evaluateFightingStrength(hero);
|
||||
}
|
||||
|
||||
bool HeroManager::heroCapReached() const
|
||||
bool HeroManager::heroCapReached(bool includeGarrisoned) const
|
||||
{
|
||||
const bool includeGarnisoned = true;
|
||||
int heroCount = cb->getHeroCount(ai->playerID, includeGarnisoned);
|
||||
int heroCount = cb->getHeroCount(ai->playerID, includeGarrisoned);
|
||||
|
||||
return heroCount >= ALLOWED_ROAMING_HEROES
|
||||
|| heroCount >= ai->settings->getMaxRoamingHeroes()
|
||||
return heroCount >= ai->settings->getMaxRoamingHeroes()
|
||||
|| heroCount >= cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)
|
||||
|| heroCount >= cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP);
|
||||
}
|
||||
@ -205,7 +205,7 @@ float HeroManager::getFightingStrengthCached(const CGHeroInstance * hero) const
|
||||
auto cached = knownFightingStrength.find(hero->id);
|
||||
|
||||
//FIXME: fallback to hero->getFightingStrength() is VERY slow on higher difficulties (no object graph? map reveal?)
|
||||
return cached != knownFightingStrength.end() ? cached->second : hero->getFightingStrength();
|
||||
return cached != knownFightingStrength.end() ? cached->second : hero->getHeroStrength();
|
||||
}
|
||||
|
||||
float HeroManager::getMagicStrength(const CGHeroInstance * hero) const
|
||||
@ -282,7 +282,7 @@ const CGHeroInstance * HeroManager::findHeroWithGrail() const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const CGHeroInstance * HeroManager::findWeakHeroToDismiss(uint64_t armyLimit) const
|
||||
const CGHeroInstance * HeroManager::findWeakHeroToDismiss(uint64_t armyLimit, const CGTownInstance* townToSpare) const
|
||||
{
|
||||
const CGHeroInstance * weakestHero = nullptr;
|
||||
auto myHeroes = ai->cb->getHeroesInfo();
|
||||
@ -293,12 +293,13 @@ const CGHeroInstance * HeroManager::findWeakHeroToDismiss(uint64_t armyLimit) co
|
||||
|| existingHero->getArmyStrength() >armyLimit
|
||||
|| getHeroRole(existingHero) == HeroRole::MAIN
|
||||
|| existingHero->movementPointsRemaining()
|
||||
|| (townToSpare != nullptr && existingHero->visitedTown == townToSpare)
|
||||
|| existingHero->artifactsWorn.size() > (existingHero->hasSpellbook() ? 2 : 1))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!weakestHero || weakestHero->getFightingStrength() > existingHero->getFightingStrength())
|
||||
if(!weakestHero || weakestHero->getHeroStrength() > existingHero->getHeroStrength())
|
||||
{
|
||||
weakestHero = existingHero;
|
||||
}
|
||||
|
@ -56,9 +56,9 @@ public:
|
||||
float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const;
|
||||
float evaluateHero(const CGHeroInstance * hero) const;
|
||||
bool canRecruitHero(const CGTownInstance * t = nullptr) const;
|
||||
bool heroCapReached() const;
|
||||
bool heroCapReached(bool includeGarrisoned = true) const;
|
||||
const CGHeroInstance * findHeroWithGrail() const;
|
||||
const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit) const;
|
||||
const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit, const CGTownInstance * townToSpare = nullptr) const;
|
||||
float getMagicStrength(const CGHeroInstance * hero) const;
|
||||
float getFightingStrengthCached(const CGHeroInstance * hero) const;
|
||||
|
||||
|
@ -97,9 +97,10 @@ std::optional<const CGObjectInstance *> ObjectClusterizer::getBlocker(const AIPa
|
||||
{
|
||||
auto guardPos = ai->cb->getGuardingCreaturePosition(node.coord);
|
||||
|
||||
blockers = ai->cb->getVisitableObjs(node.coord);
|
||||
if (ai->cb->isVisible(node.coord))
|
||||
blockers = ai->cb->getVisitableObjs(node.coord);
|
||||
|
||||
if(guardPos.valid())
|
||||
if(guardPos.valid() && ai->cb->isVisible(guardPos))
|
||||
{
|
||||
auto guard = ai->cb->getTopObj(ai->cb->getGuardingCreaturePosition(node.coord));
|
||||
|
||||
@ -474,9 +475,11 @@ void ObjectClusterizer::clusterizeObject(
|
||||
|
||||
heroesProcessed.insert(path.targetHero);
|
||||
|
||||
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
|
||||
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), PriorityEvaluator::PriorityTier::HUNTER_GATHER);
|
||||
|
||||
if(priority < MIN_PRIORITY)
|
||||
if(ai->settings->isUseFuzzy() && priority < MIN_PRIORITY)
|
||||
continue;
|
||||
else if (priority <= 0)
|
||||
continue;
|
||||
|
||||
ClusterMap::accessor cluster;
|
||||
@ -495,9 +498,11 @@ void ObjectClusterizer::clusterizeObject(
|
||||
|
||||
heroesProcessed.insert(path.targetHero);
|
||||
|
||||
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
|
||||
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), PriorityEvaluator::PriorityTier::HUNTER_GATHER);
|
||||
|
||||
if(priority < MIN_PRIORITY)
|
||||
if (ai->settings->isUseFuzzy() && priority < MIN_PRIORITY)
|
||||
continue;
|
||||
else if (priority <= 0)
|
||||
continue;
|
||||
|
||||
bool interestingObject = path.turn() <= 2 || priority > 0.5f;
|
||||
|
@ -49,26 +49,49 @@ Goals::TGoalVec BuildingBehavior::decompose(const Nullkiller * ai) const
|
||||
auto & developmentInfos = ai->buildAnalyzer->getDevelopmentInfo();
|
||||
auto isGoldPressureLow = !ai->buildAnalyzer->isGoldPressureHigh();
|
||||
|
||||
ai->dangerHitMap->updateHitMap();
|
||||
|
||||
for(auto & developmentInfo : developmentInfos)
|
||||
{
|
||||
for(auto & buildingInfo : developmentInfo.toBuild)
|
||||
bool emergencyDefense = false;
|
||||
uint8_t closestThreat = std::numeric_limits<uint8_t>::max();
|
||||
for (auto threat : ai->dangerHitMap->getTownThreats(developmentInfo.town))
|
||||
{
|
||||
if(isGoldPressureLow || buildingInfo.dailyIncome[EGameResID::GOLD] > 0)
|
||||
closestThreat = std::min(closestThreat, threat.turn);
|
||||
}
|
||||
for (auto& buildingInfo : developmentInfo.toBuild)
|
||||
{
|
||||
if (closestThreat <= 1 && developmentInfo.town->fortLevel() < CGTownInstance::EFortLevel::CASTLE && !buildingInfo.notEnoughRes)
|
||||
{
|
||||
if(buildingInfo.notEnoughRes)
|
||||
if (buildingInfo.id == BuildingID::CITADEL || buildingInfo.id == BuildingID::CASTLE)
|
||||
{
|
||||
if(ai->getLockedResources().canAfford(buildingInfo.buildCost))
|
||||
continue;
|
||||
|
||||
Composition composition;
|
||||
|
||||
composition.addNext(BuildThis(buildingInfo, developmentInfo));
|
||||
composition.addNext(SaveResources(buildingInfo.buildCost));
|
||||
|
||||
tasks.push_back(sptr(composition));
|
||||
}
|
||||
else
|
||||
tasks.push_back(sptr(BuildThis(buildingInfo, developmentInfo)));
|
||||
emergencyDefense = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!emergencyDefense)
|
||||
{
|
||||
for (auto& buildingInfo : developmentInfo.toBuild)
|
||||
{
|
||||
if (isGoldPressureLow || buildingInfo.dailyIncome[EGameResID::GOLD] > 0)
|
||||
{
|
||||
if (buildingInfo.notEnoughRes)
|
||||
{
|
||||
if (ai->getLockedResources().canAfford(buildingInfo.buildCost))
|
||||
continue;
|
||||
|
||||
Composition composition;
|
||||
|
||||
composition.addNext(BuildThis(buildingInfo, developmentInfo));
|
||||
composition.addNext(SaveResources(buildingInfo.buildCost));
|
||||
tasks.push_back(sptr(composition));
|
||||
}
|
||||
else
|
||||
{
|
||||
tasks.push_back(sptr(BuildThis(buildingInfo, developmentInfo)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,9 +28,6 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
|
||||
if(ai->cb->getDate(Date::DAY) == 1)
|
||||
return tasks;
|
||||
|
||||
auto heroes = cb->getHeroesInfo();
|
||||
|
||||
if(heroes.empty())
|
||||
@ -38,19 +35,23 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const
|
||||
return tasks;
|
||||
}
|
||||
|
||||
ai->dangerHitMap->updateHitMap();
|
||||
|
||||
for(auto town : cb->getTownsInfo())
|
||||
{
|
||||
uint8_t closestThreat = ai->dangerHitMap->getTileThreat(town->visitablePos()).fastestDanger.turn;
|
||||
|
||||
if (closestThreat >=2 && ai->buildAnalyzer->isGoldPressureHigh() && !town->hasBuilt(BuildingID::CITY_HALL) && cb->canBuildStructure(town, BuildingID::CITY_HALL) != EBuildingState::FORBIDDEN)
|
||||
{
|
||||
return tasks;
|
||||
}
|
||||
|
||||
auto townArmyAvailableToBuy = ai->armyManager->getArmyAvailableToBuyAsCCreatureSet(
|
||||
town,
|
||||
ai->getFreeResources());
|
||||
|
||||
for(const CGHeroInstance * targetHero : heroes)
|
||||
{
|
||||
if(ai->buildAnalyzer->isGoldPressureHigh() && !town->hasBuilt(BuildingID::CITY_HALL))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(ai->heroManager->getHeroRole(targetHero) == HeroRole::MAIN)
|
||||
{
|
||||
auto reinforcement = ai->armyManager->howManyReinforcementsCanGet(
|
||||
|
@ -68,14 +68,6 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(
|
||||
logAi->trace("Path found %s", path.toString());
|
||||
#endif
|
||||
|
||||
if(nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.getHeroStrength());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
if(objToVisit && !force && !shouldVisit(nullkiller, path.targetHero, objToVisit))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
@ -87,6 +79,9 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(
|
||||
auto hero = path.targetHero;
|
||||
auto danger = path.getTotalDanger();
|
||||
|
||||
if (hero->getOwner() != nullkiller->playerID)
|
||||
continue;
|
||||
|
||||
if(nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT
|
||||
&& (path.getTotalDanger() == 0 || path.turn() > 0)
|
||||
&& path.exchangeCount > 1)
|
||||
@ -119,7 +114,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(
|
||||
continue;
|
||||
}
|
||||
|
||||
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
|
||||
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger, nullkiller->settings->getSafeAttackRatio());
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
|
@ -41,6 +41,9 @@ Goals::TGoalVec DefenceBehavior::decompose(const Nullkiller * ai) const
|
||||
for(auto town : ai->cb->getTownsInfo())
|
||||
{
|
||||
evaluateDefence(tasks, town, ai);
|
||||
//Let's do only one defence-task per pass since otherwise it can try to hire the same hero twice
|
||||
if (!tasks.empty())
|
||||
break;
|
||||
}
|
||||
|
||||
return tasks;
|
||||
@ -130,7 +133,7 @@ bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoa
|
||||
|
||||
tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, nullptr).setpriority(5)));
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
else if(ai->heroManager->getHeroRole(town->garrisonHero.get()) == HeroRole::MAIN)
|
||||
{
|
||||
@ -141,7 +144,7 @@ bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoa
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::DismissHero(heroToDismiss).setpriority(5)));
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -158,11 +161,10 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
|
||||
threats.push_back(threatNode.fastestDanger); // no guarantee that fastest danger will be there
|
||||
|
||||
if(town->garrisonHero && handleGarrisonHeroFromPreviousTurn(town, tasks, ai))
|
||||
if (town->garrisonHero && handleGarrisonHeroFromPreviousTurn(town, tasks, ai))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!threatNode.fastestDanger.hero)
|
||||
{
|
||||
logAi->trace("No threat found for town %s", town->getNameTranslated());
|
||||
@ -250,6 +252,16 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!path.targetHero->canBeMergedWith(*town))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Can't merge armies of hero %s and town %s",
|
||||
path.targetHero->getObjectName(),
|
||||
town->getObjectName());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
if(path.targetHero == town->visitingHero.get() && path.exchangeCount == 1)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
@ -261,6 +273,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
// dismiss creatures we are not able to pick to be able to hide in garrison
|
||||
if(town->garrisonHero
|
||||
|| town->getUpperArmy()->stacksCount() == 0
|
||||
|| path.targetHero->canBeMergedWith(*town)
|
||||
|| (town->getUpperArmy()->getArmyStrength() < 500 && town->fortLevel() >= CGTownInstance::CITADEL))
|
||||
{
|
||||
tasks.push_back(
|
||||
@ -292,7 +305,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
continue;
|
||||
}
|
||||
|
||||
if(threat.turn == 0 || (path.turn() <= threat.turn && path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= threat.danger))
|
||||
if(threat.turn == 0 || (path.turn() <= threat.turn && path.getHeroStrength() * ai->settings->getSafeAttackRatio() >= threat.danger))
|
||||
{
|
||||
if(ai->arePathHeroesLocked(path))
|
||||
{
|
||||
@ -343,23 +356,14 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
}
|
||||
else if(town->visitingHero && path.targetHero != town->visitingHero && !path.containsHero(town->visitingHero))
|
||||
{
|
||||
if(town->garrisonHero)
|
||||
if(town->garrisonHero && town->garrisonHero != path.targetHero)
|
||||
{
|
||||
if(ai->heroManager->getHeroRole(town->visitingHero.get()) == HeroRole::SCOUT
|
||||
&& town->visitingHero->getArmyStrength() < path.heroArmy->getArmyStrength() / 20)
|
||||
{
|
||||
if(path.turn() == 0)
|
||||
sequence.push_back(sptr(DismissHero(town->visitingHero.get())));
|
||||
}
|
||||
else
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Cancel moving %s to defend town %s as the town has garrison hero",
|
||||
path.targetHero->getObjectName(),
|
||||
town->getObjectName());
|
||||
logAi->trace("Cancel moving %s to defend town %s as the town has garrison hero",
|
||||
path.targetHero->getObjectName(),
|
||||
town->getObjectName());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else if(path.turn() == 0)
|
||||
{
|
||||
@ -405,6 +409,9 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
|
||||
void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitMapInfo & threat, const CGTownInstance * town, const Nullkiller * ai) const
|
||||
{
|
||||
if (threat.turn > 0 || town->garrisonHero || town->visitingHero)
|
||||
return;
|
||||
|
||||
if(town->hasBuilt(BuildingID::TAVERN)
|
||||
&& ai->cb->getResourceAmount(EGameResID::GOLD) > GameConstants::HERO_GOLD_COST)
|
||||
{
|
||||
@ -451,7 +458,7 @@ void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitM
|
||||
}
|
||||
else if(ai->heroManager->heroCapReached())
|
||||
{
|
||||
heroToDismiss = ai->heroManager->findWeakHeroToDismiss(hero->getArmyStrength());
|
||||
heroToDismiss = ai->heroManager->findWeakHeroToDismiss(hero->getArmyStrength(), town);
|
||||
|
||||
if(!heroToDismiss)
|
||||
continue;
|
||||
|
@ -33,48 +33,32 @@ Goals::TGoalVec ExplorationBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
|
||||
for(auto obj : ai->memory->visitableObjs)
|
||||
for (auto obj : ai->memory->visitableObjs)
|
||||
{
|
||||
if(!vstd::contains(ai->memory->alreadyVisited, obj))
|
||||
switch (obj->ID.num)
|
||||
{
|
||||
switch(obj->ID.num)
|
||||
{
|
||||
case Obj::REDWOOD_OBSERVATORY:
|
||||
case Obj::PILLAR_OF_FIRE:
|
||||
tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 200)).addNext(CaptureObject(obj))));
|
||||
{
|
||||
auto rObj = dynamic_cast<const CRewardableObject*>(obj);
|
||||
if (!rObj->wasScouted(ai->playerID))
|
||||
tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 200)).addNext(CaptureObject(obj))));
|
||||
break;
|
||||
}
|
||||
case Obj::MONOLITH_ONE_WAY_ENTRANCE:
|
||||
case Obj::MONOLITH_TWO_WAY:
|
||||
case Obj::SUBTERRANEAN_GATE:
|
||||
case Obj::WHIRLPOOL:
|
||||
auto tObj = dynamic_cast<const CGTeleport *>(obj);
|
||||
if(TeleportChannel::IMPASSABLE != ai->memory->knownTeleportChannels[tObj->channel]->passability)
|
||||
{
|
||||
tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 50)).addNext(CaptureObject(obj))));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(obj->ID.num)
|
||||
{
|
||||
case Obj::MONOLITH_TWO_WAY:
|
||||
case Obj::SUBTERRANEAN_GATE:
|
||||
case Obj::WHIRLPOOL:
|
||||
auto tObj = dynamic_cast<const CGTeleport *>(obj);
|
||||
if(TeleportChannel::IMPASSABLE == ai->memory->knownTeleportChannels[tObj->channel]->passability)
|
||||
break;
|
||||
for(auto exit : ai->memory->knownTeleportChannels[tObj->channel]->exits)
|
||||
auto tObj = dynamic_cast<const CGTeleport*>(obj);
|
||||
for (auto exit : cb->getTeleportChannelExits(tObj->channel))
|
||||
{
|
||||
if(!cb->getObj(exit))
|
||||
{
|
||||
// Always attempt to visit two-way teleports if one of channel exits is not visible
|
||||
tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 50)).addNext(CaptureObject(obj))));
|
||||
break;
|
||||
if (exit != tObj->id)
|
||||
{
|
||||
if (!cb->isVisible(cb->getObjInstance(exit)))
|
||||
tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 50)).addNext(CaptureObject(obj))));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +81,9 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const Nullkiller * ai, con
|
||||
logAi->trace("Path found %s, %s, %lld", path.toString(), path.targetHero->getObjectName(), path.heroArmy->getArmyStrength());
|
||||
#endif
|
||||
|
||||
if (path.targetHero->getOwner() != ai->playerID)
|
||||
continue;
|
||||
|
||||
if(path.containsHero(hero))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
@ -89,14 +92,6 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const Nullkiller * ai, con
|
||||
continue;
|
||||
}
|
||||
|
||||
if(path.turn() > 0 && ai->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
if(ai->arePathHeroesLocked(path))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
@ -150,7 +145,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const Nullkiller * ai, con
|
||||
}
|
||||
|
||||
auto danger = path.getTotalDanger();
|
||||
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
|
||||
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger, ai->settings->getSafeAttackRatio());
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
@ -292,17 +287,6 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
|
||||
continue;
|
||||
}
|
||||
|
||||
auto heroRole = ai->heroManager->getHeroRole(path.targetHero);
|
||||
|
||||
if(heroRole == HeroRole::SCOUT
|
||||
&& ai->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
auto upgrade = ai->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
|
||||
|
||||
if(!upgrader->garrisonHero
|
||||
@ -320,14 +304,6 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
|
||||
|
||||
armyToGetOrBuy.upgradeValue -= path.heroArmy->getArmyStrength();
|
||||
|
||||
armyToGetOrBuy.addArmyToBuy(
|
||||
ai->armyManager->toSlotInfo(
|
||||
ai->armyManager->getArmyAvailableToBuy(
|
||||
path.heroArmy,
|
||||
upgrader,
|
||||
ai->getFreeResources(),
|
||||
path.turn())));
|
||||
|
||||
upgrade.upgradeValue += armyToGetOrBuy.upgradeValue;
|
||||
upgrade.upgradeCost += armyToGetOrBuy.upgradeCost;
|
||||
vstd::concatenate(upgrade.resultingArmy, armyToGetOrBuy.resultingArmy);
|
||||
@ -339,8 +315,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
|
||||
{
|
||||
for(auto hero : cb->getAvailableHeroes(upgrader))
|
||||
{
|
||||
auto scoutReinforcement = ai->armyManager->howManyReinforcementsCanBuy(hero, upgrader)
|
||||
+ ai->armyManager->howManyReinforcementsCanGet(hero, upgrader);
|
||||
auto scoutReinforcement = ai->armyManager->howManyReinforcementsCanGet(hero, upgrader);
|
||||
|
||||
if(scoutReinforcement >= armyToGetOrBuy.upgradeValue
|
||||
&& ai->getFreeGold() >20000
|
||||
@ -366,7 +341,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
|
||||
|
||||
auto danger = path.getTotalDanger();
|
||||
|
||||
auto isSafe = isSafeToVisit(path.targetHero, path.heroArmy, danger);
|
||||
auto isSafe = isSafeToVisit(path.targetHero, path.heroArmy, danger, ai->settings->getSafeAttackRatio());
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
|
@ -31,9 +31,11 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const
|
||||
|
||||
auto ourHeroes = ai->heroManager->getHeroRoles();
|
||||
auto minScoreToHireMain = std::numeric_limits<float>::max();
|
||||
int currentArmyValue = 0;
|
||||
|
||||
for(auto hero : ourHeroes)
|
||||
{
|
||||
currentArmyValue += hero.first->getArmyCost();
|
||||
if(hero.second != HeroRole::MAIN)
|
||||
continue;
|
||||
|
||||
@ -45,51 +47,88 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const
|
||||
minScoreToHireMain = newScore;
|
||||
}
|
||||
}
|
||||
// If we don't have any heros we might want to lower our expectations.
|
||||
if (ourHeroes.empty())
|
||||
minScoreToHireMain = 0;
|
||||
|
||||
const CGHeroInstance* bestHeroToHire = nullptr;
|
||||
const CGTownInstance* bestTownToHireFrom = nullptr;
|
||||
float bestScore = 0;
|
||||
bool haveCapitol = false;
|
||||
|
||||
ai->dangerHitMap->updateHitMap();
|
||||
int treasureSourcesCount = 0;
|
||||
|
||||
for(auto town : towns)
|
||||
{
|
||||
uint8_t closestThreat = UINT8_MAX;
|
||||
for (auto threat : ai->dangerHitMap->getTownThreats(town))
|
||||
{
|
||||
closestThreat = std::min(closestThreat, threat.turn);
|
||||
}
|
||||
//Don't hire a hero where there already is one present
|
||||
if (town->visitingHero && town->garrisonHero)
|
||||
continue;
|
||||
float visitability = 0;
|
||||
for (auto checkHero : ourHeroes)
|
||||
{
|
||||
if (ai->dangerHitMap->getClosestTown(checkHero.first.get()->visitablePos()) == town)
|
||||
visitability++;
|
||||
}
|
||||
if(ai->heroManager->canRecruitHero(town))
|
||||
{
|
||||
auto availableHeroes = ai->cb->getAvailableHeroes(town);
|
||||
|
||||
for(auto hero : availableHeroes)
|
||||
|
||||
for (auto obj : ai->objectClusterizer->getNearbyObjects())
|
||||
{
|
||||
auto score = ai->heroManager->evaluateHero(hero);
|
||||
|
||||
if(score > minScoreToHireMain)
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero(town, hero).setpriority(200)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int treasureSourcesCount = 0;
|
||||
|
||||
for(auto obj : ai->objectClusterizer->getNearbyObjects())
|
||||
{
|
||||
if((obj->ID == Obj::RESOURCE)
|
||||
if ((obj->ID == Obj::RESOURCE)
|
||||
|| obj->ID == Obj::TREASURE_CHEST
|
||||
|| obj->ID == Obj::CAMPFIRE
|
||||
|| isWeeklyRevisitable(ai, obj)
|
||||
|| obj->ID ==Obj::ARTIFACT)
|
||||
|| obj->ID == Obj::ARTIFACT)
|
||||
{
|
||||
auto tile = obj->visitablePos();
|
||||
auto closestTown = ai->dangerHitMap->getClosestTown(tile);
|
||||
|
||||
if(town == closestTown)
|
||||
if (town == closestTown)
|
||||
treasureSourcesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if(treasureSourcesCount < 5 && (town->garrisonHero || town->getUpperArmy()->getArmyStrength() < 10000))
|
||||
continue;
|
||||
|
||||
if(ai->cb->getHeroesInfo().size() < ai->cb->getTownsInfo().size() + 1
|
||||
|| (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureHigh()))
|
||||
for(auto hero : availableHeroes)
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero(town).setpriority(3)));
|
||||
auto score = ai->heroManager->evaluateHero(hero);
|
||||
if(score > minScoreToHireMain)
|
||||
{
|
||||
score *= score / minScoreToHireMain;
|
||||
}
|
||||
score *= (hero->getArmyCost() + currentArmyValue);
|
||||
if (hero->getFactionID() == town->getFactionID())
|
||||
score *= 1.5;
|
||||
if (vstd::isAlmostZero(visitability))
|
||||
score *= 30 * town->getTownLevel();
|
||||
else
|
||||
score *= town->getTownLevel() / visitability;
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestHeroToHire = hero;
|
||||
bestTownToHireFrom = town;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (town->hasCapitol())
|
||||
haveCapitol = true;
|
||||
}
|
||||
if (bestHeroToHire && bestTownToHireFrom)
|
||||
{
|
||||
if (ai->cb->getHeroesInfo().size() == 0
|
||||
|| treasureSourcesCount > ai->cb->getHeroesInfo().size() * 5
|
||||
|| (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureHigh() && haveCapitol)
|
||||
|| (ai->getFreeResources()[EGameResID::GOLD] > 30000 && !ai->buildAnalyzer->isGoldPressureHigh()))
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero(bestTownToHireFrom, bestHeroToHire).setpriority((float)3 / (ourHeroes.size() + 1))));
|
||||
}
|
||||
}
|
||||
|
||||
return tasks;
|
||||
|
@ -39,9 +39,6 @@ Goals::TGoalVec StayAtTownBehavior::decompose(const Nullkiller * ai) const
|
||||
|
||||
for(auto town : towns)
|
||||
{
|
||||
if(!town->hasBuilt(BuildingID::MAGES_GUILD_1))
|
||||
continue;
|
||||
|
||||
ai->pathfinder->calculatePathInfo(paths, town->visitablePos());
|
||||
|
||||
for(auto & path : paths)
|
||||
@ -49,14 +46,8 @@ Goals::TGoalVec StayAtTownBehavior::decompose(const Nullkiller * ai) const
|
||||
if(town->visitingHero && town->visitingHero.get() != path.targetHero)
|
||||
continue;
|
||||
|
||||
if(!path.targetHero->hasSpellbook() || path.targetHero->mana >= 0.75f * path.targetHero->manaLimit())
|
||||
continue;
|
||||
|
||||
if(path.turn() == 0 && !path.getFirstBlockedAction() && path.exchangeCount <= 1)
|
||||
if(!path.getFirstBlockedAction() && path.exchangeCount <= 1)
|
||||
{
|
||||
if(path.targetHero->mana == path.targetHero->manaLimit())
|
||||
continue;
|
||||
|
||||
Composition stayAtTown;
|
||||
|
||||
stayAtTown.addNextSequence({
|
||||
|
@ -17,8 +17,7 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
#define MIN_AI_STRENGTH (0.5f) //lower when combat AI gets smarter
|
||||
#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
|
||||
constexpr float MIN_AI_STRENGTH = 0.5f; //lower when combat AI gets smarter
|
||||
|
||||
engineBase::engineBase()
|
||||
{
|
||||
|
@ -52,6 +52,15 @@ ui64 FuzzyHelper::evaluateDanger(const int3 & tile, const CGHeroInstance * visit
|
||||
{
|
||||
objectDanger += evaluateDanger(hero->visitedTown.get());
|
||||
}
|
||||
objectDanger *= ai->heroManager->getFightingStrengthCached(hero);
|
||||
}
|
||||
if (objWithID<Obj::TOWN>(dangerousObject))
|
||||
{
|
||||
auto town = dynamic_cast<const CGTownInstance*>(dangerousObject);
|
||||
auto hero = town->garrisonHero;
|
||||
|
||||
if (hero)
|
||||
objectDanger *= ai->heroManager->getFightingStrengthCached(hero);
|
||||
}
|
||||
|
||||
if(objectDanger)
|
||||
@ -117,10 +126,10 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
|
||||
{
|
||||
auto fortLevel = town->fortLevel();
|
||||
|
||||
if(fortLevel == CGTownInstance::EFortLevel::CASTLE)
|
||||
danger += 10000;
|
||||
if (fortLevel == CGTownInstance::EFortLevel::CASTLE)
|
||||
danger = std::max(danger * 2, danger + 10000);
|
||||
else if(fortLevel == CGTownInstance::EFortLevel::CITADEL)
|
||||
danger += 4000;
|
||||
danger = std::max(ui64(danger * 1.4), danger + 4000);
|
||||
}
|
||||
|
||||
return danger;
|
||||
|
@ -34,13 +34,12 @@ using namespace Goals;
|
||||
std::unique_ptr<ObjectGraph> Nullkiller::baseGraph;
|
||||
|
||||
Nullkiller::Nullkiller()
|
||||
:activeHero(nullptr), scanDepth(ScanDepth::MAIN_FULL), useHeroChain(true)
|
||||
: activeHero(nullptr)
|
||||
, scanDepth(ScanDepth::MAIN_FULL)
|
||||
, useHeroChain(true)
|
||||
, memory(std::make_unique<AIMemory>())
|
||||
{
|
||||
memory = std::make_unique<AIMemory>();
|
||||
settings = std::make_unique<Settings>();
|
||||
|
||||
useObjectGraph = settings->isObjectGraphAllowed();
|
||||
openMap = settings->isOpenMap() || useObjectGraph;
|
||||
}
|
||||
|
||||
bool canUseOpenMap(std::shared_ptr<CCallback> cb, PlayerColor playerID)
|
||||
@ -62,17 +61,23 @@ bool canUseOpenMap(std::shared_ptr<CCallback> cb, PlayerColor playerID)
|
||||
return false;
|
||||
}
|
||||
|
||||
return cb->getStartInfo()->difficulty >= 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Nullkiller::init(std::shared_ptr<CCallback> cb, AIGateway * gateway)
|
||||
{
|
||||
this->cb = cb;
|
||||
this->gateway = gateway;
|
||||
|
||||
playerID = gateway->playerID;
|
||||
this->playerID = gateway->playerID;
|
||||
|
||||
if(openMap && !canUseOpenMap(cb, playerID))
|
||||
settings = std::make_unique<Settings>(cb->getStartInfo()->difficulty);
|
||||
|
||||
if(canUseOpenMap(cb, playerID))
|
||||
{
|
||||
useObjectGraph = settings->isObjectGraphAllowed();
|
||||
openMap = settings->isOpenMap() || useObjectGraph;
|
||||
}
|
||||
else
|
||||
{
|
||||
useObjectGraph = false;
|
||||
openMap = false;
|
||||
@ -122,11 +127,14 @@ void TaskPlan::merge(TSubgoal task)
|
||||
{
|
||||
TGoalVec blockers;
|
||||
|
||||
if (task->asTask()->priority <= 0)
|
||||
return;
|
||||
|
||||
for(auto & item : tasks)
|
||||
{
|
||||
for(auto objid : item.affectedObjects)
|
||||
{
|
||||
if(task == item.task || task->asTask()->isObjectAffected(objid))
|
||||
if(task == item.task || task->asTask()->isObjectAffected(objid) || (task->asTask()->getHero() != nullptr && task->asTask()->getHero() == item.task->asTask()->getHero()))
|
||||
{
|
||||
if(item.task->asTask()->priority >= task->asTask()->priority)
|
||||
return;
|
||||
@ -166,20 +174,19 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TGoalVec & tasks) const
|
||||
return taskptr(*bestTask);
|
||||
}
|
||||
|
||||
Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks) const
|
||||
Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks, int priorityTier) const
|
||||
{
|
||||
TaskPlan taskPlan;
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, tasks.size()), [this, &tasks](const tbb::blocked_range<size_t> & r)
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, tasks.size()), [this, &tasks, priorityTier](const tbb::blocked_range<size_t> & r)
|
||||
{
|
||||
auto evaluator = this->priorityEvaluators->acquire();
|
||||
|
||||
for(size_t i = r.begin(); i != r.end(); i++)
|
||||
{
|
||||
auto task = tasks[i];
|
||||
|
||||
if(task->asTask()->priority <= 0)
|
||||
task->asTask()->priority = evaluator->evaluate(task);
|
||||
if (task->asTask()->priority <= 0 || priorityTier != PriorityEvaluator::PriorityTier::BUILDINGS)
|
||||
task->asTask()->priority = evaluator->evaluate(task, priorityTier);
|
||||
}
|
||||
});
|
||||
|
||||
@ -326,7 +333,7 @@ bool Nullkiller::arePathHeroesLocked(const AIPath & path) const
|
||||
if(lockReason != HeroLockedReason::NOT_LOCKED)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->getObjectName(), path.toString());
|
||||
logAi->trace("Hero %s is locked by %d. Discarding %s", path.targetHero->getObjectName(), (int)lockReason, path.toString());
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
@ -347,12 +354,24 @@ void Nullkiller::makeTurn()
|
||||
boost::lock_guard<boost::mutex> sharedStorageLock(AISharedStorage::locker);
|
||||
|
||||
const int MAX_DEPTH = 10;
|
||||
const float FAST_TASK_MINIMAL_PRIORITY = 0.7f;
|
||||
|
||||
resetAiState();
|
||||
|
||||
Goals::TGoalVec bestTasks;
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
float totalHeroStrength = 0;
|
||||
int totalTownLevel = 0;
|
||||
for (auto heroInfo : cb->getHeroesInfo())
|
||||
{
|
||||
totalHeroStrength += heroInfo->getTotalStrength();
|
||||
}
|
||||
for (auto townInfo : cb->getTownsInfo())
|
||||
{
|
||||
totalTownLevel += townInfo->getTownLevel();
|
||||
}
|
||||
logAi->info("Beginning: Strength: %f Townlevel: %d Resources: %s", totalHeroStrength, totalTownLevel, cb->getResourceAmount().toString());
|
||||
#endif
|
||||
for(int i = 1; i <= settings->getMaxPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++)
|
||||
{
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
@ -360,17 +379,21 @@ void Nullkiller::makeTurn()
|
||||
|
||||
Goals::TTask bestTask = taskptr(Goals::Invalid());
|
||||
|
||||
for(;i <= settings->getMaxPass(); i++)
|
||||
while(true)
|
||||
{
|
||||
bestTasks.clear();
|
||||
|
||||
decompose(bestTasks, sptr(RecruitHeroBehavior()), 1);
|
||||
decompose(bestTasks, sptr(BuyArmyBehavior()), 1);
|
||||
decompose(bestTasks, sptr(BuildingBehavior()), 1);
|
||||
|
||||
bestTask = choseBestTask(bestTasks);
|
||||
|
||||
if(bestTask->priority >= FAST_TASK_MINIMAL_PRIORITY)
|
||||
if(bestTask->priority > 0)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->info("Pass %d: Performing prio 0 task %s with prio: %d", i, bestTask->toString(), bestTask->priority);
|
||||
#endif
|
||||
if(!executeTask(bestTask))
|
||||
return;
|
||||
|
||||
@ -382,7 +405,6 @@ void Nullkiller::makeTurn()
|
||||
}
|
||||
}
|
||||
|
||||
decompose(bestTasks, sptr(RecruitHeroBehavior()), 1);
|
||||
decompose(bestTasks, sptr(CaptureObjectsBehavior()), 1);
|
||||
decompose(bestTasks, sptr(ClusterBehavior()), MAX_DEPTH);
|
||||
decompose(bestTasks, sptr(DefenceBehavior()), MAX_DEPTH);
|
||||
@ -392,12 +414,24 @@ void Nullkiller::makeTurn()
|
||||
if(!isOpenMap())
|
||||
decompose(bestTasks, sptr(ExplorationBehavior()), MAX_DEPTH);
|
||||
|
||||
if(cb->getDate(Date::DAY) == 1 || heroManager->getHeroRoles().empty())
|
||||
TTaskVec selectedTasks;
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
int prioOfTask = 0;
|
||||
#endif
|
||||
for (int prio = PriorityEvaluator::PriorityTier::INSTAKILL; prio <= PriorityEvaluator::PriorityTier::DEFEND; ++prio)
|
||||
{
|
||||
decompose(bestTasks, sptr(StartupBehavior()), 1);
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
prioOfTask = prio;
|
||||
#endif
|
||||
selectedTasks = buildPlan(bestTasks, prio);
|
||||
if (!selectedTasks.empty() || settings->isUseFuzzy())
|
||||
break;
|
||||
}
|
||||
|
||||
auto selectedTasks = buildPlan(bestTasks);
|
||||
std::sort(selectedTasks.begin(), selectedTasks.end(), [](const TTask& a, const TTask& b)
|
||||
{
|
||||
return a->priority > b->priority;
|
||||
});
|
||||
|
||||
logAi->debug("Decision madel in %ld", timeElapsed(start));
|
||||
|
||||
@ -438,7 +472,7 @@ void Nullkiller::makeTurn()
|
||||
bestTask->priority);
|
||||
}
|
||||
|
||||
if(bestTask->priority < MIN_PRIORITY)
|
||||
if((settings->isUseFuzzy() && bestTask->priority < MIN_PRIORITY) || (!settings->isUseFuzzy() && bestTask->priority <= 0))
|
||||
{
|
||||
auto heroes = cb->getHeroesInfo();
|
||||
auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
|
||||
@ -463,7 +497,9 @@ void Nullkiller::makeTurn()
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->info("Pass %d: Performing prio %d task %s with prio: %d", i, prioOfTask, bestTask->toString(), bestTask->priority);
|
||||
#endif
|
||||
if(!executeTask(bestTask))
|
||||
{
|
||||
if(hasAnySuccess)
|
||||
@ -471,13 +507,27 @@ void Nullkiller::makeTurn()
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
hasAnySuccess = true;
|
||||
}
|
||||
|
||||
hasAnySuccess |= handleTrading();
|
||||
|
||||
if(!hasAnySuccess)
|
||||
{
|
||||
logAi->trace("Nothing was done this turn. Ending turn.");
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
totalHeroStrength = 0;
|
||||
totalTownLevel = 0;
|
||||
for (auto heroInfo : cb->getHeroesInfo())
|
||||
{
|
||||
totalHeroStrength += heroInfo->getTotalStrength();
|
||||
}
|
||||
for (auto townInfo : cb->getTownsInfo())
|
||||
{
|
||||
totalTownLevel += townInfo->getTownLevel();
|
||||
}
|
||||
logAi->info("End: Strength: %f Townlevel: %d Resources: %s", totalHeroStrength, totalTownLevel, cb->getResourceAmount().toString());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
@ -554,4 +604,102 @@ void Nullkiller::lockResources(const TResources & res)
|
||||
lockedResources += res;
|
||||
}
|
||||
|
||||
bool Nullkiller::handleTrading()
|
||||
{
|
||||
bool haveTraded = false;
|
||||
bool shouldTryToTrade = true;
|
||||
int marketId = -1;
|
||||
for (auto town : cb->getTownsInfo())
|
||||
{
|
||||
if (town->hasBuiltSomeTradeBuilding())
|
||||
{
|
||||
marketId = town->id;
|
||||
}
|
||||
}
|
||||
if (marketId == -1)
|
||||
return false;
|
||||
if (const CGObjectInstance* obj = cb->getObj(ObjectInstanceID(marketId), false))
|
||||
{
|
||||
if (const auto* m = dynamic_cast<const IMarket*>(obj))
|
||||
{
|
||||
while (shouldTryToTrade)
|
||||
{
|
||||
shouldTryToTrade = false;
|
||||
buildAnalyzer->update();
|
||||
TResources required = buildAnalyzer->getTotalResourcesRequired();
|
||||
TResources income = buildAnalyzer->getDailyIncome();
|
||||
TResources available = cb->getResourceAmount();
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->debug("Available %s", available.toString());
|
||||
logAi->debug("Required %s", required.toString());
|
||||
#endif
|
||||
int mostWanted = -1;
|
||||
int mostExpendable = -1;
|
||||
float minRatio = std::numeric_limits<float>::max();
|
||||
float maxRatio = std::numeric_limits<float>::min();
|
||||
|
||||
for (int i = 0; i < required.size(); ++i)
|
||||
{
|
||||
if (required[i] <= 0)
|
||||
continue;
|
||||
float ratio = static_cast<float>(available[i]) / required[i];
|
||||
|
||||
if (ratio < minRatio) {
|
||||
minRatio = ratio;
|
||||
mostWanted = i;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < required.size(); ++i)
|
||||
{
|
||||
float ratio = available[i];
|
||||
if (required[i] > 0)
|
||||
ratio = static_cast<float>(available[i]) / required[i];
|
||||
else
|
||||
ratio = available[i];
|
||||
|
||||
bool okToSell = false;
|
||||
|
||||
if (i == GameResID::GOLD)
|
||||
{
|
||||
if (income[i] > 0 && !buildAnalyzer->isGoldPressureHigh())
|
||||
okToSell = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (required[i] <= 0 && income[i] > 0)
|
||||
okToSell = true;
|
||||
}
|
||||
|
||||
if (ratio > maxRatio && okToSell) {
|
||||
maxRatio = ratio;
|
||||
mostExpendable = i;
|
||||
}
|
||||
}
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->debug("mostExpendable: %d mostWanted: %d", mostExpendable, mostWanted);
|
||||
#endif
|
||||
if (mostExpendable == mostWanted || mostWanted == -1 || mostExpendable == -1)
|
||||
return false;
|
||||
|
||||
int toGive;
|
||||
int toGet;
|
||||
m->getOffer(mostExpendable, mostWanted, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
|
||||
//logAi->info("Offer is: I get %d of %s for %d of %s at %s", toGet, mostWanted, toGive, mostExpendable, obj->getObjectName());
|
||||
//TODO trade only as much as needed
|
||||
if (toGive && toGive <= available[mostExpendable]) //don't try to sell 0 resources
|
||||
{
|
||||
cb->trade(m->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(mostExpendable), GameResID(mostWanted), toGive);
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->info("Traded %d of %s for %d of %s at %s", toGive, mostExpendable, toGet, mostWanted, obj->getObjectName());
|
||||
#endif
|
||||
haveTraded = true;
|
||||
shouldTryToTrade = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return haveTraded;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -120,13 +120,14 @@ public:
|
||||
ScanDepth getScanDepth() const { return scanDepth; }
|
||||
bool isOpenMap() const { return openMap; }
|
||||
bool isObjectGraphAllowed() const { return useObjectGraph; }
|
||||
bool handleTrading();
|
||||
|
||||
private:
|
||||
void resetAiState();
|
||||
void updateAiState(int pass, bool fast = false);
|
||||
void decompose(Goals::TGoalVec & result, Goals::TSubgoal behavior, int decompositionMaxDepth) const;
|
||||
Goals::TTask choseBestTask(Goals::TGoalVec & tasks) const;
|
||||
Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks) const;
|
||||
Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks, int priorityTier) const;
|
||||
bool executeTask(Goals::TTask task);
|
||||
bool areAffectedObjectsPresent(Goals::TTask task) const;
|
||||
HeroRole getTaskRole(Goals::TTask task) const;
|
||||
|
@ -15,6 +15,8 @@
|
||||
#include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
|
||||
#include "../../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../../lib/mapping/CMapDefines.h"
|
||||
#include "../../../lib/RoadHandler.h"
|
||||
#include "../../../lib/CCreatureHandler.h"
|
||||
#include "../../../lib/VCMI_Lib.h"
|
||||
#include "../../../lib/StartInfo.h"
|
||||
@ -33,11 +35,9 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
#define MIN_AI_STRENGTH (0.5f) //lower when combat AI gets smarter
|
||||
#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
|
||||
const float MIN_CRITICAL_VALUE = 2.0f;
|
||||
constexpr float MIN_CRITICAL_VALUE = 2.0f;
|
||||
|
||||
EvaluationContext::EvaluationContext(const Nullkiller * ai)
|
||||
EvaluationContext::EvaluationContext(const Nullkiller* ai)
|
||||
: movementCost(0.0),
|
||||
manaCost(0),
|
||||
danger(0),
|
||||
@ -51,9 +51,22 @@ EvaluationContext::EvaluationContext(const Nullkiller * ai)
|
||||
heroRole(HeroRole::SCOUT),
|
||||
turn(0),
|
||||
strategicalValue(0),
|
||||
conquestValue(0),
|
||||
evaluator(ai),
|
||||
enemyHeroDangerRatio(0),
|
||||
armyGrowth(0)
|
||||
threat(0),
|
||||
armyGrowth(0),
|
||||
armyInvolvement(0),
|
||||
defenseValue(0),
|
||||
isDefend(false),
|
||||
threatTurns(INT_MAX),
|
||||
involvesSailing(false),
|
||||
isTradeBuilding(false),
|
||||
isExchange(false),
|
||||
isArmyUpgrade(false),
|
||||
isHero(false),
|
||||
isEnemy(false),
|
||||
explorePriority(0)
|
||||
{
|
||||
}
|
||||
|
||||
@ -155,10 +168,10 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero
|
||||
for (auto c : creatures)
|
||||
{
|
||||
//Only if hero has slot for this creature in the army
|
||||
auto ccre = dynamic_cast<const CCreature*>(c.data.type);
|
||||
auto ccre = dynamic_cast<const CCreature*>(c.data.getType());
|
||||
if (hero->getSlotFor(ccre).validSlot() || duplicatingSlots > 0)
|
||||
{
|
||||
result += (c.data.type->getAIValue() * c.data.count) * c.chance;
|
||||
result += (c.data.getType()->getAIValue() * c.data.count) * c.chance;
|
||||
}
|
||||
/*else
|
||||
{
|
||||
@ -225,7 +238,7 @@ int getDwellingArmyCost(const CGObjectInstance * target)
|
||||
auto creature = creLevel.second.back().toCreature();
|
||||
auto creaturesAreFree = creature->getLevel() == 1;
|
||||
if(!creaturesAreFree)
|
||||
cost += creature->getRecruitCost(EGameResID::GOLD) * creLevel.first;
|
||||
cost += creature->getFullRecruitCost().marketValue() * creLevel.first;
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,6 +264,8 @@ static uint64_t evaluateArtifactArmyValue(const CArtifact * art)
|
||||
|
||||
switch(art->aClass)
|
||||
{
|
||||
case CArtifact::EartClass::ART_TREASURE:
|
||||
//FALL_THROUGH
|
||||
case CArtifact::EartClass::ART_MINOR:
|
||||
classValue = 1000;
|
||||
break;
|
||||
@ -289,8 +304,10 @@ uint64_t RewardEvaluator::getArmyReward(
|
||||
case Obj::CREATURE_GENERATOR3:
|
||||
case Obj::CREATURE_GENERATOR4:
|
||||
return getDwellingArmyValue(ai->cb.get(), target, checkGold);
|
||||
case Obj::SPELL_SCROLL:
|
||||
//FALL_THROUGH
|
||||
case Obj::ARTIFACT:
|
||||
return evaluateArtifactArmyValue(dynamic_cast<const CGArtifact *>(target)->storedArtifact->artType);
|
||||
return evaluateArtifactArmyValue(dynamic_cast<const CGArtifact *>(target)->storedArtifact->getType());
|
||||
case Obj::HERO:
|
||||
return relations == PlayerRelations::ENEMIES
|
||||
? enemyArmyEliminationRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->getArmyStrength()
|
||||
@ -479,7 +496,7 @@ uint64_t RewardEvaluator::townArmyGrowth(const CGTownInstance * town) const
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t RewardEvaluator::getManaRecoveryArmyReward(const CGHeroInstance * hero) const
|
||||
float RewardEvaluator::getManaRecoveryArmyReward(const CGHeroInstance * hero) const
|
||||
{
|
||||
return ai->heroManager->getMagicStrength(hero) * 10000 * (1.0f - std::sqrt(static_cast<float>(hero->mana) / hero->manaLimit()));
|
||||
}
|
||||
@ -581,6 +598,54 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target, cons
|
||||
return 0;
|
||||
}
|
||||
|
||||
float RewardEvaluator::getConquestValue(const CGObjectInstance* target) const
|
||||
{
|
||||
if (!target)
|
||||
return 0;
|
||||
if (target->getOwner() == ai->playerID)
|
||||
return 0;
|
||||
switch (target->ID)
|
||||
{
|
||||
case Obj::TOWN:
|
||||
{
|
||||
if (ai->buildAnalyzer->getDevelopmentInfo().empty())
|
||||
return 10.0f;
|
||||
|
||||
auto town = dynamic_cast<const CGTownInstance*>(target);
|
||||
|
||||
if (town->getOwner() == ai->playerID)
|
||||
{
|
||||
auto armyIncome = townArmyGrowth(town);
|
||||
auto dailyIncome = town->dailyIncome()[EGameResID::GOLD];
|
||||
|
||||
return std::min(1.0f, std::sqrt(armyIncome / 40000.0f)) + std::min(0.3f, dailyIncome / 10000.0f);
|
||||
}
|
||||
|
||||
auto fortLevel = town->fortLevel();
|
||||
auto booster = 1.0f;
|
||||
|
||||
if (town->hasCapitol())
|
||||
return booster * 1.5;
|
||||
|
||||
if (fortLevel < CGTownInstance::CITADEL)
|
||||
return booster * (town->hasFort() ? 1.0 : 0.8);
|
||||
else
|
||||
return booster * (fortLevel == CGTownInstance::CASTLE ? 1.4 : 1.2);
|
||||
}
|
||||
|
||||
case Obj::HERO:
|
||||
return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
|
||||
? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance*>(target))
|
||||
: 0;
|
||||
|
||||
case Obj::KEYMASTER:
|
||||
return 0.6f;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
float RewardEvaluator::evaluateWitchHutSkillScore(const CGObjectInstance * hut, const CGHeroInstance * hero, HeroRole role) const
|
||||
{
|
||||
auto rewardable = dynamic_cast<const CRewardableObject *>(hut);
|
||||
@ -705,7 +770,7 @@ int32_t getArmyCost(const CArmedInstance * army)
|
||||
|
||||
for(auto stack : army->Slots())
|
||||
{
|
||||
value += stack.second->getCreatureID().toCreature()->getRecruitCost(EGameResID::GOLD) * stack.second->count;
|
||||
value += stack.second->getCreatureID().toCreature()->getFullRecruitCost().marketValue() * stack.second->count;
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -786,7 +851,9 @@ public:
|
||||
uint64_t armyStrength = heroExchange.getReinforcementArmyStrength(evaluationContext.evaluator.ai);
|
||||
|
||||
evaluationContext.addNonCriticalStrategicalValue(2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength());
|
||||
evaluationContext.conquestValue += 2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength();
|
||||
evaluationContext.heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroExchange.hero);
|
||||
evaluationContext.isExchange = true;
|
||||
}
|
||||
};
|
||||
|
||||
@ -804,6 +871,7 @@ public:
|
||||
|
||||
evaluationContext.armyReward += upgradeValue;
|
||||
evaluationContext.addNonCriticalStrategicalValue(upgradeValue / (float)armyUpgrade.hero->getArmyStrength());
|
||||
evaluationContext.isArmyUpgrade = true;
|
||||
}
|
||||
};
|
||||
|
||||
@ -818,22 +886,46 @@ public:
|
||||
int tilesDiscovered = task->value;
|
||||
|
||||
evaluationContext.addNonCriticalStrategicalValue(0.03f * tilesDiscovered);
|
||||
for (auto obj : evaluationContext.evaluator.ai->cb->getVisitableObjs(task->tile))
|
||||
{
|
||||
switch (obj->ID.num)
|
||||
{
|
||||
case Obj::MONOLITH_ONE_WAY_ENTRANCE:
|
||||
case Obj::MONOLITH_TWO_WAY:
|
||||
case Obj::SUBTERRANEAN_GATE:
|
||||
evaluationContext.explorePriority = 1;
|
||||
break;
|
||||
case Obj::REDWOOD_OBSERVATORY:
|
||||
case Obj::PILLAR_OF_FIRE:
|
||||
evaluationContext.explorePriority = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(evaluationContext.evaluator.ai->cb->getTile(task->tile)->roadType != RoadId::NO_ROAD)
|
||||
evaluationContext.explorePriority = 1;
|
||||
if (evaluationContext.explorePriority == 0)
|
||||
evaluationContext.explorePriority = 3;
|
||||
}
|
||||
};
|
||||
|
||||
class StayAtTownManaRecoveryEvaluator : public IEvaluationContextBuilder
|
||||
{
|
||||
public:
|
||||
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
void buildEvaluationContext(EvaluationContext& evaluationContext, Goals::TSubgoal task) const override
|
||||
{
|
||||
if(task->goalType != Goals::STAY_AT_TOWN)
|
||||
if (task->goalType != Goals::STAY_AT_TOWN)
|
||||
return;
|
||||
|
||||
Goals::StayAtTown & stayAtTown = dynamic_cast<Goals::StayAtTown &>(*task);
|
||||
Goals::StayAtTown& stayAtTown = dynamic_cast<Goals::StayAtTown&>(*task);
|
||||
|
||||
evaluationContext.armyReward += evaluationContext.evaluator.getManaRecoveryArmyReward(stayAtTown.getHero());
|
||||
evaluationContext.movementCostByRole[evaluationContext.heroRole] += stayAtTown.getMovementWasted();
|
||||
evaluationContext.movementCost += stayAtTown.getMovementWasted();
|
||||
if (evaluationContext.armyReward == 0)
|
||||
evaluationContext.isDefend = true;
|
||||
else
|
||||
{
|
||||
evaluationContext.movementCost += stayAtTown.getMovementWasted();
|
||||
evaluationContext.movementCostByRole[evaluationContext.heroRole] += stayAtTown.getMovementWasted();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -844,15 +936,8 @@ void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uin
|
||||
if(enemyDanger.danger)
|
||||
{
|
||||
auto dangerRatio = enemyDanger.danger / (double)ourStrength;
|
||||
auto enemyHero = evaluationContext.evaluator.ai->cb->getObj(enemyDanger.hero.hid, false);
|
||||
bool isAI = enemyHero && isAnotherAi(enemyHero, *evaluationContext.evaluator.ai->cb);
|
||||
|
||||
if(isAI)
|
||||
{
|
||||
dangerRatio *= 1.5; // lets make AI bit more afraid of other AI.
|
||||
}
|
||||
|
||||
vstd::amax(evaluationContext.enemyHeroDangerRatio, dangerRatio);
|
||||
vstd::amax(evaluationContext.threat, enemyDanger.threat);
|
||||
}
|
||||
}
|
||||
|
||||
@ -896,6 +981,10 @@ public:
|
||||
else
|
||||
evaluationContext.addNonCriticalStrategicalValue(1.7f * multiplier * strategicalValue);
|
||||
|
||||
evaluationContext.defenseValue = town->fortLevel();
|
||||
evaluationContext.isDefend = true;
|
||||
evaluationContext.threatTurns = treat.turn;
|
||||
|
||||
vstd::amax(evaluationContext.danger, defendTown.getTreat().danger);
|
||||
addTileDanger(evaluationContext, town->visitablePos(), defendTown.getTurn(), defendTown.getDefenceStrength());
|
||||
}
|
||||
@ -926,6 +1015,8 @@ public:
|
||||
for(auto & node : path.nodes)
|
||||
{
|
||||
vstd::amax(costsPerHero[node.targetHero], node.cost);
|
||||
if (node.layer == EPathfindingLayer::SAIL)
|
||||
evaluationContext.involvesSailing = true;
|
||||
}
|
||||
|
||||
for(auto pair : costsPerHero)
|
||||
@ -952,10 +1043,18 @@ public:
|
||||
evaluationContext.armyGrowth += evaluationContext.evaluator.getArmyGrowth(target, hero, army);
|
||||
evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, heroRole);
|
||||
evaluationContext.addNonCriticalStrategicalValue(evaluationContext.evaluator.getStrategicalValue(target));
|
||||
evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target);
|
||||
if (target->ID == Obj::HERO)
|
||||
evaluationContext.isHero = true;
|
||||
if (target->getOwner() != PlayerColor::NEUTRAL && ai->cb->getPlayerRelations(ai->playerID, target->getOwner()) == PlayerRelations::ENEMIES)
|
||||
evaluationContext.isEnemy = true;
|
||||
evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army);
|
||||
evaluationContext.armyInvolvement += army->getArmyCost();
|
||||
if(evaluationContext.danger > 0)
|
||||
evaluationContext.skillReward += (float)evaluationContext.danger / (float)hero->getArmyStrength();
|
||||
}
|
||||
|
||||
vstd::amax(evaluationContext.armyLossPersentage, path.getTotalArmyLoss() / (double)path.getHeroStrength());
|
||||
vstd::amax(evaluationContext.armyLossPersentage, (float)path.getTotalArmyLoss() / (float)army->getArmyStrength());
|
||||
addTileDanger(evaluationContext, path.targetTile(), path.turn(), path.getHeroStrength());
|
||||
vstd::amax(evaluationContext.turn, path.turn());
|
||||
}
|
||||
@ -996,6 +1095,7 @@ public:
|
||||
evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold) / boost;
|
||||
evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, role) / boost;
|
||||
evaluationContext.addNonCriticalStrategicalValue(evaluationContext.evaluator.getStrategicalValue(target) / boost);
|
||||
evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target);
|
||||
evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army) / boost;
|
||||
evaluationContext.movementCostByRole[role] += objInfo.second.movementCost / boost;
|
||||
evaluationContext.movementCost += objInfo.second.movementCost / boost;
|
||||
@ -1021,6 +1121,14 @@ public:
|
||||
Goals::ExchangeSwapTownHeroes & swapCommand = dynamic_cast<Goals::ExchangeSwapTownHeroes &>(*task);
|
||||
const CGHeroInstance * garrisonHero = swapCommand.getGarrisonHero();
|
||||
|
||||
logAi->trace("buildEvaluationContext ExchangeSwapTownHeroesContextBuilder %s affected objects: %d", swapCommand.toString(), swapCommand.getAffectedObjects().size());
|
||||
for (auto obj : swapCommand.getAffectedObjects())
|
||||
{
|
||||
logAi->trace("affected object: %s", evaluationContext.evaluator.ai->cb->getObj(obj)->getObjectName());
|
||||
}
|
||||
if (garrisonHero)
|
||||
logAi->debug("with %s and %d", garrisonHero->getNameTranslated(), int(swapCommand.getLockingReason()));
|
||||
|
||||
if(garrisonHero && swapCommand.getLockingReason() == HeroLockedReason::DEFENCE)
|
||||
{
|
||||
auto defenderRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(garrisonHero);
|
||||
@ -1029,6 +1137,9 @@ public:
|
||||
evaluationContext.movementCost += mpLeft;
|
||||
evaluationContext.movementCostByRole[defenderRole] += mpLeft;
|
||||
evaluationContext.heroRole = defenderRole;
|
||||
evaluationContext.isDefend = true;
|
||||
evaluationContext.armyInvolvement = garrisonHero->getArmyStrength();
|
||||
logAi->debug("evaluationContext.isDefend: %d", evaluationContext.isDefend);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1072,8 +1183,14 @@ public:
|
||||
evaluationContext.goldReward += 7 * bi.dailyIncome[EGameResID::GOLD] / 2; // 7 day income but half we already have
|
||||
evaluationContext.heroRole = HeroRole::MAIN;
|
||||
evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount;
|
||||
evaluationContext.goldCost += bi.buildCostWithPrerequisites[EGameResID::GOLD];
|
||||
int32_t cost = bi.buildCost[EGameResID::GOLD];
|
||||
evaluationContext.goldCost += cost;
|
||||
evaluationContext.closestWayRatio = 1;
|
||||
evaluationContext.buildingCost += bi.buildCostWithPrerequisites;
|
||||
if (bi.id == BuildingID::MARKETPLACE || bi.dailyIncome[EGameResID::WOOD] > 0)
|
||||
evaluationContext.isTradeBuilding = true;
|
||||
|
||||
logAi->trace("Building costs for %s : %s MarketValue: %d",bi.toString(), evaluationContext.buildingCost.toString(), evaluationContext.buildingCost.marketValue());
|
||||
|
||||
if(bi.creatureID != CreatureID::NONE)
|
||||
{
|
||||
@ -1100,7 +1217,18 @@ public:
|
||||
else if(bi.id >= BuildingID::MAGES_GUILD_1 && bi.id <= BuildingID::MAGES_GUILD_5)
|
||||
{
|
||||
evaluationContext.skillReward += 2 * (bi.id - BuildingID::MAGES_GUILD_1);
|
||||
for (auto hero : evaluationContext.evaluator.ai->cb->getHeroesInfo())
|
||||
{
|
||||
evaluationContext.armyInvolvement += hero->getArmyCost();
|
||||
}
|
||||
}
|
||||
int sameTownBonus = 0;
|
||||
for (auto town : evaluationContext.evaluator.ai->cb->getTownsInfo())
|
||||
{
|
||||
if (buildThis.town->getFaction() == town->getFaction())
|
||||
sameTownBonus += town->getTownLevel();
|
||||
}
|
||||
evaluationContext.armyReward *= sameTownBonus;
|
||||
|
||||
if(evaluationContext.goldReward)
|
||||
{
|
||||
@ -1120,7 +1248,7 @@ public:
|
||||
|
||||
uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const
|
||||
{
|
||||
if(ai->buildAnalyzer->hasAnyBuilding(town->getFaction(), bi.id))
|
||||
if(ai->buildAnalyzer->hasAnyBuilding(town->getFactionID(), bi.id))
|
||||
return 0;
|
||||
|
||||
auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);
|
||||
@ -1162,6 +1290,7 @@ EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal
|
||||
for(auto subgoal : parts)
|
||||
{
|
||||
context.goldCost += subgoal->goldCost;
|
||||
context.buildingCost += subgoal->buildingCost;
|
||||
|
||||
for(auto builder : evaluationContextBuilders)
|
||||
{
|
||||
@ -1172,7 +1301,7 @@ EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal
|
||||
return context;
|
||||
}
|
||||
|
||||
float PriorityEvaluator::evaluate(Goals::TSubgoal task)
|
||||
float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
||||
{
|
||||
auto evaluationContext = buildEvaluationContext(task);
|
||||
|
||||
@ -1185,36 +1314,256 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
|
||||
|
||||
double result = 0;
|
||||
|
||||
try
|
||||
if (ai->settings->isUseFuzzy())
|
||||
{
|
||||
armyLossPersentageVariable->setValue(evaluationContext.armyLossPersentage);
|
||||
heroRoleVariable->setValue(evaluationContext.heroRole);
|
||||
mainTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::MAIN]);
|
||||
scoutTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::SCOUT]);
|
||||
goldRewardVariable->setValue(goldRewardPerTurn);
|
||||
armyRewardVariable->setValue(evaluationContext.armyReward);
|
||||
armyGrowthVariable->setValue(evaluationContext.armyGrowth);
|
||||
skillRewardVariable->setValue(evaluationContext.skillReward);
|
||||
dangerVariable->setValue(evaluationContext.danger);
|
||||
rewardTypeVariable->setValue(rewardType);
|
||||
closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio);
|
||||
strategicalValueVariable->setValue(evaluationContext.strategicalValue);
|
||||
goldPressureVariable->setValue(ai->buildAnalyzer->getGoldPressure());
|
||||
goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->getFreeResources()[EGameResID::GOLD] + (float)ai->buildAnalyzer->getDailyIncome()[EGameResID::GOLD] + 1.0f));
|
||||
turnVariable->setValue(evaluationContext.turn);
|
||||
fearVariable->setValue(evaluationContext.enemyHeroDangerRatio);
|
||||
float fuzzyResult = 0;
|
||||
try
|
||||
{
|
||||
armyLossPersentageVariable->setValue(evaluationContext.armyLossPersentage);
|
||||
heroRoleVariable->setValue(evaluationContext.heroRole);
|
||||
mainTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::MAIN]);
|
||||
scoutTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::SCOUT]);
|
||||
goldRewardVariable->setValue(goldRewardPerTurn);
|
||||
armyRewardVariable->setValue(evaluationContext.armyReward);
|
||||
armyGrowthVariable->setValue(evaluationContext.armyGrowth);
|
||||
skillRewardVariable->setValue(evaluationContext.skillReward);
|
||||
dangerVariable->setValue(evaluationContext.danger);
|
||||
rewardTypeVariable->setValue(rewardType);
|
||||
closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio);
|
||||
strategicalValueVariable->setValue(evaluationContext.strategicalValue);
|
||||
goldPressureVariable->setValue(ai->buildAnalyzer->getGoldPressure());
|
||||
goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->getFreeResources()[EGameResID::GOLD] + (float)ai->buildAnalyzer->getDailyIncome()[EGameResID::GOLD] + 1.0f));
|
||||
turnVariable->setValue(evaluationContext.turn);
|
||||
fearVariable->setValue(evaluationContext.enemyHeroDangerRatio);
|
||||
|
||||
engine->process();
|
||||
engine->process();
|
||||
|
||||
result = value->getValue();
|
||||
fuzzyResult = value->getValue();
|
||||
}
|
||||
catch (fl::Exception& fe)
|
||||
{
|
||||
logAi->error("evaluate VisitTile: %s", fe.getWhat());
|
||||
}
|
||||
result = fuzzyResult;
|
||||
}
|
||||
catch(fl::Exception & fe)
|
||||
else
|
||||
{
|
||||
logAi->error("evaluate VisitTile: %s", fe.getWhat());
|
||||
float score = 0;
|
||||
float maxWillingToLose = ai->cb->getTownsInfo().empty() || (evaluationContext.isDefend && evaluationContext.threatTurns == 0) ? 1 : 0.25;
|
||||
|
||||
bool arriveNextWeek = false;
|
||||
if (ai->cb->getDate(Date::DAY_OF_WEEK) + evaluationContext.turn > 7)
|
||||
arriveNextWeek = true;
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, explorePriority: %d isDefend: %d",
|
||||
priorityTier,
|
||||
task->toString(),
|
||||
evaluationContext.armyLossPersentage,
|
||||
(int)evaluationContext.turn,
|
||||
evaluationContext.movementCostByRole[HeroRole::MAIN],
|
||||
evaluationContext.movementCostByRole[HeroRole::SCOUT],
|
||||
goldRewardPerTurn,
|
||||
evaluationContext.goldCost,
|
||||
evaluationContext.armyReward,
|
||||
evaluationContext.armyGrowth,
|
||||
evaluationContext.skillReward,
|
||||
evaluationContext.danger,
|
||||
evaluationContext.threatTurns,
|
||||
evaluationContext.threat,
|
||||
evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout",
|
||||
evaluationContext.strategicalValue,
|
||||
evaluationContext.conquestValue,
|
||||
evaluationContext.closestWayRatio,
|
||||
evaluationContext.enemyHeroDangerRatio,
|
||||
evaluationContext.explorePriority,
|
||||
evaluationContext.isDefend);
|
||||
#endif
|
||||
|
||||
switch (priorityTier)
|
||||
{
|
||||
case PriorityTier::INSTAKILL: //Take towns / kill heroes in immediate reach
|
||||
{
|
||||
if (evaluationContext.turn > 0)
|
||||
return 0;
|
||||
if(evaluationContext.conquestValue > 0)
|
||||
score = 1000;
|
||||
if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
|
||||
return 0;
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
score *= evaluationContext.closestWayRatio;
|
||||
if (evaluationContext.movementCost > 0)
|
||||
score /= evaluationContext.movementCost;
|
||||
break;
|
||||
}
|
||||
case PriorityTier::INSTADEFEND: //Defend immediately threatened towns
|
||||
{
|
||||
if (evaluationContext.isDefend && evaluationContext.threatTurns == 0 && evaluationContext.turn == 0)
|
||||
score = evaluationContext.armyInvolvement;
|
||||
if (evaluationContext.isEnemy && maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
score *= evaluationContext.closestWayRatio;
|
||||
break;
|
||||
}
|
||||
case PriorityTier::KILL: //Take towns / kill heroes that are further away
|
||||
{
|
||||
if (evaluationContext.turn > 0 && evaluationContext.isHero)
|
||||
return 0;
|
||||
if (arriveNextWeek && evaluationContext.isEnemy)
|
||||
return 0;
|
||||
if (evaluationContext.conquestValue > 0)
|
||||
score = 1000;
|
||||
if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
|
||||
return 0;
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
score *= evaluationContext.closestWayRatio;
|
||||
if (evaluationContext.movementCost > 0)
|
||||
score /= evaluationContext.movementCost;
|
||||
break;
|
||||
}
|
||||
case PriorityTier::UPGRADE:
|
||||
{
|
||||
if (!evaluationContext.isArmyUpgrade)
|
||||
return 0;
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1)
|
||||
return 0;
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
score = 1000;
|
||||
score *= evaluationContext.closestWayRatio;
|
||||
if (evaluationContext.movementCost > 0)
|
||||
score /= evaluationContext.movementCost;
|
||||
break;
|
||||
}
|
||||
case PriorityTier::HIGH_PRIO_EXPLORE:
|
||||
{
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1)
|
||||
return 0;
|
||||
if (evaluationContext.explorePriority != 1)
|
||||
return 0;
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
score = 1000;
|
||||
score *= evaluationContext.closestWayRatio;
|
||||
if (evaluationContext.movementCost > 0)
|
||||
score /= evaluationContext.movementCost;
|
||||
break;
|
||||
}
|
||||
case PriorityTier::HUNTER_GATHER: //Collect guarded stuff
|
||||
{
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend)
|
||||
return 0;
|
||||
if (evaluationContext.buildingCost.marketValue() > 0)
|
||||
return 0;
|
||||
if (evaluationContext.isDefend && (evaluationContext.enemyHeroDangerRatio < 1 || evaluationContext.threatTurns > 0 || evaluationContext.turn > 0))
|
||||
return 0;
|
||||
if (evaluationContext.explorePriority == 3)
|
||||
return 0;
|
||||
if (evaluationContext.isArmyUpgrade)
|
||||
return 0;
|
||||
if ((evaluationContext.enemyHeroDangerRatio > 0 && arriveNextWeek) || evaluationContext.enemyHeroDangerRatio > 1)
|
||||
return 0;
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
score += evaluationContext.strategicalValue * 1000;
|
||||
score += evaluationContext.goldReward;
|
||||
score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05;
|
||||
score += evaluationContext.armyReward;
|
||||
score += evaluationContext.armyGrowth;
|
||||
score -= evaluationContext.goldCost;
|
||||
score -= evaluationContext.armyInvolvement * evaluationContext.armyLossPersentage;
|
||||
if (score > 0)
|
||||
{
|
||||
score = 1000;
|
||||
score *= evaluationContext.closestWayRatio;
|
||||
if (evaluationContext.movementCost > 0)
|
||||
score /= evaluationContext.movementCost;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PriorityTier::LOW_PRIO_EXPLORE:
|
||||
{
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1)
|
||||
return 0;
|
||||
if (evaluationContext.explorePriority != 3)
|
||||
return 0;
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
score = 1000;
|
||||
score *= evaluationContext.closestWayRatio;
|
||||
if (evaluationContext.movementCost > 0)
|
||||
score /= evaluationContext.movementCost;
|
||||
break;
|
||||
}
|
||||
case PriorityTier::DEFEND: //Defend whatever if nothing else is to do
|
||||
{
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.isExchange)
|
||||
return 0;
|
||||
if (evaluationContext.isDefend || evaluationContext.isArmyUpgrade)
|
||||
score = 1000;
|
||||
score *= evaluationContext.closestWayRatio;
|
||||
score /= (evaluationContext.turn + 1);
|
||||
break;
|
||||
}
|
||||
case PriorityTier::BUILDINGS: //For buildings and buying army
|
||||
{
|
||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||
return 0;
|
||||
//If we already have locked resources, we don't look at other buildings
|
||||
if (ai->getLockedResources().marketValue() > 0)
|
||||
return 0;
|
||||
score += evaluationContext.conquestValue * 1000;
|
||||
score += evaluationContext.strategicalValue * 1000;
|
||||
score += evaluationContext.goldReward;
|
||||
score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05;
|
||||
score += evaluationContext.armyReward;
|
||||
score += evaluationContext.armyGrowth;
|
||||
if (evaluationContext.buildingCost.marketValue() > 0)
|
||||
{
|
||||
if (!evaluationContext.isTradeBuilding && ai->getFreeResources()[EGameResID::WOOD] - evaluationContext.buildingCost[EGameResID::WOOD] < 5 && ai->buildAnalyzer->getDailyIncome()[EGameResID::WOOD] < 1)
|
||||
{
|
||||
logAi->trace("Should make sure to build market-place instead of %s", task->toString());
|
||||
for (auto town : ai->cb->getTownsInfo())
|
||||
{
|
||||
if (!town->hasBuiltSomeTradeBuilding())
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
score += 1000;
|
||||
auto resourcesAvailable = evaluationContext.evaluator.ai->getFreeResources();
|
||||
auto income = ai->buildAnalyzer->getDailyIncome();
|
||||
if(ai->buildAnalyzer->isGoldPressureHigh())
|
||||
score /= evaluationContext.buildingCost.marketValue();
|
||||
if (!resourcesAvailable.canAfford(evaluationContext.buildingCost))
|
||||
{
|
||||
TResources needed = evaluationContext.buildingCost - resourcesAvailable;
|
||||
needed.positive();
|
||||
int turnsTo = needed.maxPurchasableCount(income);
|
||||
if (turnsTo == INT_MAX)
|
||||
return 0;
|
||||
else
|
||||
score /= turnsTo;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend && vstd::isAlmostZero(evaluationContext.conquestValue))
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
result = score;
|
||||
//TODO: Figure out the root cause for why evaluationContext.closestWayRatio has become -nan(ind).
|
||||
if (std::isnan(result))
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, danger: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, result %f",
|
||||
logAi->trace("priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, result %f",
|
||||
priorityTier,
|
||||
task->toString(),
|
||||
evaluationContext.armyLossPersentage,
|
||||
(int)evaluationContext.turn,
|
||||
@ -1223,9 +1572,14 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
|
||||
goldRewardPerTurn,
|
||||
evaluationContext.goldCost,
|
||||
evaluationContext.armyReward,
|
||||
evaluationContext.armyGrowth,
|
||||
evaluationContext.skillReward,
|
||||
evaluationContext.danger,
|
||||
evaluationContext.threatTurns,
|
||||
evaluationContext.threat,
|
||||
evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout",
|
||||
evaluationContext.strategicalValue,
|
||||
evaluationContext.conquestValue,
|
||||
evaluationContext.closestWayRatio,
|
||||
evaluationContext.enemyHeroDangerRatio,
|
||||
result);
|
||||
|
@ -41,6 +41,7 @@ public:
|
||||
float getResourceRequirementStrength(int resType) const;
|
||||
float getResourceRequirementStrength(const TResources & res) const;
|
||||
float getStrategicalValue(const CGObjectInstance * target, const CGHeroInstance * hero = nullptr) const;
|
||||
float getConquestValue(const CGObjectInstance* target) const;
|
||||
float getTotalResourceRequirementStrength(int resType) const;
|
||||
float evaluateWitchHutSkillScore(const CGObjectInstance * hut, const CGHeroInstance * hero, HeroRole role) const;
|
||||
float getSkillReward(const CGObjectInstance * target, const CGHeroInstance * hero, HeroRole role) const;
|
||||
@ -48,7 +49,7 @@ public:
|
||||
uint64_t getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const;
|
||||
const HitMapInfo & getEnemyHeroDanger(const int3 & tile, uint8_t turn) const;
|
||||
uint64_t townArmyGrowth(const CGTownInstance * town) const;
|
||||
uint64_t getManaRecoveryArmyReward(const CGHeroInstance * hero) const;
|
||||
float getManaRecoveryArmyReward(const CGHeroInstance * hero) const;
|
||||
};
|
||||
|
||||
struct DLL_EXPORT EvaluationContext
|
||||
@ -65,10 +66,24 @@ struct DLL_EXPORT EvaluationContext
|
||||
int32_t goldCost;
|
||||
float skillReward;
|
||||
float strategicalValue;
|
||||
float conquestValue;
|
||||
HeroRole heroRole;
|
||||
uint8_t turn;
|
||||
RewardEvaluator evaluator;
|
||||
float enemyHeroDangerRatio;
|
||||
float threat;
|
||||
float armyInvolvement;
|
||||
int defenseValue;
|
||||
bool isDefend;
|
||||
int threatTurns;
|
||||
TResources buildingCost;
|
||||
bool involvesSailing;
|
||||
bool isTradeBuilding;
|
||||
bool isExchange;
|
||||
bool isArmyUpgrade;
|
||||
bool isHero;
|
||||
bool isEnemy;
|
||||
int explorePriority;
|
||||
|
||||
EvaluationContext(const Nullkiller * ai);
|
||||
|
||||
@ -91,7 +106,20 @@ public:
|
||||
~PriorityEvaluator();
|
||||
void initVisitTile();
|
||||
|
||||
float evaluate(Goals::TSubgoal task);
|
||||
float evaluate(Goals::TSubgoal task, int priorityTier = BUILDINGS);
|
||||
|
||||
enum PriorityTier : int32_t
|
||||
{
|
||||
BUILDINGS = 0,
|
||||
INSTAKILL,
|
||||
INSTADEFEND,
|
||||
KILL,
|
||||
UPGRADE,
|
||||
HIGH_PRIO_EXPLORE,
|
||||
HUNTER_GATHER,
|
||||
LOW_PRIO_EXPLORE,
|
||||
DEFEND
|
||||
};
|
||||
|
||||
private:
|
||||
const Nullkiller * ai;
|
||||
|
@ -11,6 +11,8 @@
|
||||
#include <limits>
|
||||
|
||||
#include "Settings.h"
|
||||
|
||||
#include "../../../lib/constants/StringConstants.h"
|
||||
#include "../../../lib/mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
|
||||
@ -22,56 +24,39 @@
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
Settings::Settings()
|
||||
Settings::Settings(int difficultyLevel)
|
||||
: maxRoamingHeroes(8),
|
||||
mainHeroTurnDistanceLimit(10),
|
||||
scoutHeroTurnDistanceLimit(5),
|
||||
maxGoldPressure(0.3f),
|
||||
maxGoldPressure(0.3f),
|
||||
retreatThresholdRelative(0.3),
|
||||
retreatThresholdAbsolute(10000),
|
||||
safeAttackRatio(1.1),
|
||||
maxpass(10),
|
||||
pathfinderBucketsCount(1),
|
||||
pathfinderBucketSize(32),
|
||||
allowObjectGraph(true),
|
||||
useTroopsFromGarrisons(false),
|
||||
openMap(true)
|
||||
openMap(true),
|
||||
useFuzzy(false)
|
||||
{
|
||||
JsonNode node = JsonUtils::assembleFromFiles("config/ai/nkai/nkai-settings");
|
||||
const std::string & difficultyName = GameConstants::DIFFICULTY_NAMES[difficultyLevel];
|
||||
const JsonNode & rootNode = JsonUtils::assembleFromFiles("config/ai/nkai/nkai-settings");
|
||||
const JsonNode & node = rootNode[difficultyName];
|
||||
|
||||
if(node.Struct()["maxRoamingHeroes"].isNumber())
|
||||
{
|
||||
maxRoamingHeroes = node.Struct()["maxRoamingHeroes"].Integer();
|
||||
}
|
||||
|
||||
if(node.Struct()["mainHeroTurnDistanceLimit"].isNumber())
|
||||
{
|
||||
mainHeroTurnDistanceLimit = node.Struct()["mainHeroTurnDistanceLimit"].Integer();
|
||||
}
|
||||
|
||||
if(node.Struct()["scoutHeroTurnDistanceLimit"].isNumber())
|
||||
{
|
||||
scoutHeroTurnDistanceLimit = node.Struct()["scoutHeroTurnDistanceLimit"].Integer();
|
||||
}
|
||||
|
||||
if(node.Struct()["maxpass"].isNumber())
|
||||
{
|
||||
maxpass = node.Struct()["maxpass"].Integer();
|
||||
}
|
||||
|
||||
if(node.Struct()["maxGoldPressure"].isNumber())
|
||||
{
|
||||
maxGoldPressure = node.Struct()["maxGoldPressure"].Float();
|
||||
}
|
||||
|
||||
if(!node.Struct()["allowObjectGraph"].isNull())
|
||||
{
|
||||
allowObjectGraph = node.Struct()["allowObjectGraph"].Bool();
|
||||
}
|
||||
|
||||
if(!node.Struct()["openMap"].isNull())
|
||||
{
|
||||
openMap = node.Struct()["openMap"].Bool();
|
||||
}
|
||||
|
||||
if(!node.Struct()["useTroopsFromGarrisons"].isNull())
|
||||
{
|
||||
useTroopsFromGarrisons = node.Struct()["useTroopsFromGarrisons"].Bool();
|
||||
}
|
||||
maxRoamingHeroes = node["maxRoamingHeroes"].Integer();
|
||||
mainHeroTurnDistanceLimit = node["mainHeroTurnDistanceLimit"].Integer();
|
||||
scoutHeroTurnDistanceLimit = node["scoutHeroTurnDistanceLimit"].Integer();
|
||||
maxpass = node["maxpass"].Integer();
|
||||
pathfinderBucketsCount = node["pathfinderBucketsCount"].Integer();
|
||||
pathfinderBucketSize = node["pathfinderBucketSize"].Integer();
|
||||
maxGoldPressure = node["maxGoldPressure"].Float();
|
||||
retreatThresholdRelative = node["retreatThresholdRelative"].Float();
|
||||
retreatThresholdAbsolute = node["retreatThresholdAbsolute"].Float();
|
||||
safeAttackRatio = node["safeAttackRatio"].Float();
|
||||
allowObjectGraph = node["allowObjectGraph"].Bool();
|
||||
openMap = node["openMap"].Bool();
|
||||
useFuzzy = node["useFuzzy"].Bool();
|
||||
useTroopsFromGarrisons = node["useTroopsFromGarrisons"].Bool();
|
||||
}
|
||||
}
|
||||
|
@ -25,21 +25,33 @@ namespace NKAI
|
||||
int mainHeroTurnDistanceLimit;
|
||||
int scoutHeroTurnDistanceLimit;
|
||||
int maxpass;
|
||||
int pathfinderBucketsCount;
|
||||
int pathfinderBucketSize;
|
||||
float maxGoldPressure;
|
||||
float retreatThresholdRelative;
|
||||
float retreatThresholdAbsolute;
|
||||
float safeAttackRatio;
|
||||
bool allowObjectGraph;
|
||||
bool useTroopsFromGarrisons;
|
||||
bool openMap;
|
||||
bool useFuzzy;
|
||||
|
||||
public:
|
||||
Settings();
|
||||
explicit Settings(int difficultyLevel);
|
||||
|
||||
int getMaxPass() const { return maxpass; }
|
||||
float getMaxGoldPressure() const { return maxGoldPressure; }
|
||||
float getRetreatThresholdRelative() const { return retreatThresholdRelative; }
|
||||
float getRetreatThresholdAbsolute() const { return retreatThresholdAbsolute; }
|
||||
float getSafeAttackRatio() const { return safeAttackRatio; }
|
||||
int getMaxRoamingHeroes() const { return maxRoamingHeroes; }
|
||||
int getMainHeroTurnDistanceLimit() const { return mainHeroTurnDistanceLimit; }
|
||||
int getScoutHeroTurnDistanceLimit() const { return scoutHeroTurnDistanceLimit; }
|
||||
int getPathfinderBucketsCount() const { return pathfinderBucketsCount; }
|
||||
int getPathfinderBucketSize() const { return pathfinderBucketSize; }
|
||||
bool isObjectGraphAllowed() const { return allowObjectGraph; }
|
||||
bool isGarrisonTroopsUsageAllowed() const { return useTroopsFromGarrisons; }
|
||||
bool isOpenMap() const { return openMap; }
|
||||
bool isUseFuzzy() const { return useFuzzy; }
|
||||
};
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ namespace Goals
|
||||
bool isAbstract; SETTER(bool, isAbstract)
|
||||
int value; SETTER(int, value)
|
||||
ui64 goldCost; SETTER(ui64, goldCost)
|
||||
TResources buildingCost; SETTER(TResources, buildingCost)
|
||||
int resID; SETTER(int, resID)
|
||||
int objid; SETTER(int, objid)
|
||||
int aid; SETTER(int, aid)
|
||||
|
@ -53,6 +53,9 @@ void AdventureSpellCast::accept(AIGateway * ai)
|
||||
throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated());
|
||||
}
|
||||
|
||||
if (hero->inTownGarrison)
|
||||
ai->myCb->swapGarrisonHero(hero->visitedTown);
|
||||
|
||||
auto wait = cb->waitTillRealize;
|
||||
|
||||
cb->waitTillRealize = true;
|
||||
|
@ -23,7 +23,7 @@ BuildThis::BuildThis(BuildingID Bid, const CGTownInstance * tid)
|
||||
: ElementarGoal(Goals::BUILD_STRUCTURE)
|
||||
{
|
||||
buildingInfo = BuildingInfo(
|
||||
tid->town->buildings.at(Bid),
|
||||
tid->getTown()->buildings.at(Bid),
|
||||
nullptr,
|
||||
CreatureID::NONE,
|
||||
tid,
|
||||
@ -52,7 +52,7 @@ void BuildThis::accept(AIGateway * ai)
|
||||
if(cb->canBuildStructure(town, b) == EBuildingState::ALLOWED)
|
||||
{
|
||||
logAi->debug("Player %d will build %s in town of %s at %s",
|
||||
ai->playerID, town->town->buildings.at(b)->getNameTranslated(), town->getNameTranslated(), town->pos.toString());
|
||||
ai->playerID, town->getTown()->buildings.at(b)->getNameTranslated(), town->getNameTranslated(), town->anchorPos().toString());
|
||||
cb->buildBuilding(town, b);
|
||||
|
||||
return;
|
||||
|
@ -31,7 +31,7 @@ namespace Goals
|
||||
{
|
||||
objid = obj->id.getNum();
|
||||
tile = obj->visitablePos();
|
||||
name = obj->typeName;
|
||||
name = obj->getTypeName();
|
||||
}
|
||||
|
||||
bool operator==(const CaptureObject & other) const override;
|
||||
|
@ -90,9 +90,12 @@ void ExchangeSwapTownHeroes::accept(AIGateway * ai)
|
||||
|
||||
if(!town->garrisonHero)
|
||||
{
|
||||
while(upperArmy->stacksCount() != 0)
|
||||
if (!garrisonHero->canBeMergedWith(*town))
|
||||
{
|
||||
cb->dismissCreature(upperArmy, upperArmy->Slots().begin()->first);
|
||||
while (upperArmy->stacksCount() != 0)
|
||||
{
|
||||
cb->dismissCreature(upperArmy, upperArmy->Slots().begin()->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ ExecuteHeroChain::ExecuteHeroChain(const AIPath & path, const CGObjectInstance *
|
||||
{
|
||||
hero = path.targetHero;
|
||||
tile = path.targetTile();
|
||||
closestWayRatio = 1;
|
||||
|
||||
if(obj)
|
||||
{
|
||||
@ -30,7 +31,7 @@ ExecuteHeroChain::ExecuteHeroChain(const AIPath & path, const CGObjectInstance *
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
targetName = obj->getObjectName() + tile.toString();
|
||||
#else
|
||||
targetName = obj->typeName + tile.toString();
|
||||
targetName = obj->getTypeName() + tile.toString();
|
||||
#endif
|
||||
}
|
||||
else
|
||||
@ -85,6 +86,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
|
||||
ai->nullkiller->setActive(chainPath.targetHero, tile);
|
||||
ai->nullkiller->setTargetObject(objid);
|
||||
ai->nullkiller->objectClusterizer->reset();
|
||||
|
||||
auto targetObject = ai->myCb->getObj(static_cast<ObjectInstanceID>(objid), false);
|
||||
|
||||
|
@ -73,6 +73,7 @@ void RecruitHero::accept(AIGateway * ai)
|
||||
std::unique_lock lockGuard(ai->nullkiller->aiStateMutex);
|
||||
|
||||
ai->nullkiller->heroManager->update();
|
||||
ai->nullkiller->objectClusterizer->reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,7 @@ namespace Goals
|
||||
}
|
||||
|
||||
std::string toString() const override;
|
||||
const CGHeroInstance* getHero() const override { return heroToBuy; }
|
||||
void accept(AIGateway * ai) override;
|
||||
};
|
||||
}
|
||||
|
@ -36,16 +36,12 @@ std::string StayAtTown::toString() const
|
||||
{
|
||||
return "Stay at town " + town->getNameTranslated()
|
||||
+ " hero " + hero->getNameTranslated()
|
||||
+ ", mana: " + std::to_string(hero->mana);
|
||||
+ ", mana: " + std::to_string(hero->mana)
|
||||
+ " / " + std::to_string(hero->manaLimit());
|
||||
}
|
||||
|
||||
void StayAtTown::accept(AIGateway * ai)
|
||||
{
|
||||
if(hero->visitedTown != town)
|
||||
{
|
||||
logAi->error("Hero %s expected visiting town %s", hero->getNameTranslated(), town->getNameTranslated());
|
||||
}
|
||||
|
||||
ai->nullkiller->lockHero(hero, HeroLockedReason::DEFENCE);
|
||||
}
|
||||
|
||||
|
@ -175,7 +175,7 @@ void ExplorationHelper::scanTile(const int3 & tile)
|
||||
continue;
|
||||
}
|
||||
|
||||
if(isSafeToVisit(hero, path.heroArmy, path.getTotalDanger()))
|
||||
if(isSafeToVisit(hero, path.heroArmy, path.getTotalDanger(), ai->settings->getSafeAttackRatio()))
|
||||
{
|
||||
bestGoal = goal;
|
||||
bestValue = ourValue;
|
||||
|
@ -39,17 +39,17 @@ const uint64_t CHAIN_MAX_DEPTH = 4;
|
||||
|
||||
const bool DO_NOT_SAVE_TO_COMMITTED_TILES = false;
|
||||
|
||||
AISharedStorage::AISharedStorage(int3 sizes)
|
||||
AISharedStorage::AISharedStorage(int3 sizes, int numChains)
|
||||
{
|
||||
if(!shared){
|
||||
shared.reset(new boost::multi_array<AIPathNode, 4>(
|
||||
boost::extents[sizes.z][sizes.x][sizes.y][AIPathfinding::NUM_CHAINS]));
|
||||
boost::extents[sizes.z][sizes.x][sizes.y][numChains]));
|
||||
|
||||
nodes = shared;
|
||||
|
||||
foreach_tile_pos([&](const int3 & pos)
|
||||
{
|
||||
for(auto i = 0; i < AIPathfinding::NUM_CHAINS; i++)
|
||||
for(auto i = 0; i < numChains; i++)
|
||||
{
|
||||
auto & node = get(pos)[i];
|
||||
|
||||
@ -92,8 +92,18 @@ void AIPathNode::addSpecialAction(std::shared_ptr<const SpecialAction> action)
|
||||
}
|
||||
}
|
||||
|
||||
int AINodeStorage::getBucketCount() const
|
||||
{
|
||||
return ai->settings->getPathfinderBucketsCount();
|
||||
}
|
||||
|
||||
int AINodeStorage::getBucketSize() const
|
||||
{
|
||||
return ai->settings->getPathfinderBucketSize();
|
||||
}
|
||||
|
||||
AINodeStorage::AINodeStorage(const Nullkiller * ai, const int3 & Sizes)
|
||||
: sizes(Sizes), ai(ai), cb(ai->cb.get()), nodes(Sizes)
|
||||
: sizes(Sizes), ai(ai), cb(ai->cb.get()), nodes(Sizes, ai->settings->getPathfinderBucketSize() * ai->settings->getPathfinderBucketsCount())
|
||||
{
|
||||
accessibility = std::make_unique<boost::multi_array<EPathAccessibility, 4>>(
|
||||
boost::extents[sizes.z][sizes.x][sizes.y][EPathfindingLayer::NUM_LAYERS]);
|
||||
@ -130,10 +140,10 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
|
||||
for(pos.y = 0; pos.y < sizes.y; ++pos.y)
|
||||
{
|
||||
const TerrainTile & tile = gs->map->getTile(pos);
|
||||
if (!tile.terType->isPassable())
|
||||
if (!tile.getTerrain()->isPassable())
|
||||
continue;
|
||||
|
||||
if (tile.terType->isWater())
|
||||
if (tile.isWater())
|
||||
{
|
||||
resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
|
||||
if (useFlying)
|
||||
@ -169,8 +179,8 @@ std::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
|
||||
const EPathfindingLayer layer,
|
||||
const ChainActor * actor)
|
||||
{
|
||||
int bucketIndex = ((uintptr_t)actor + static_cast<uint32_t>(layer)) % AIPathfinding::BUCKET_COUNT;
|
||||
int bucketOffset = bucketIndex * AIPathfinding::BUCKET_SIZE;
|
||||
int bucketIndex = ((uintptr_t)actor + static_cast<uint32_t>(layer)) % ai->settings->getPathfinderBucketsCount();
|
||||
int bucketOffset = bucketIndex * ai->settings->getPathfinderBucketSize();
|
||||
auto chains = nodes.get(pos);
|
||||
|
||||
if(blocked(pos, layer))
|
||||
@ -178,7 +188,7 @@ std::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for(auto i = AIPathfinding::BUCKET_SIZE - 1; i >= 0; i--)
|
||||
for(auto i = ai->settings->getPathfinderBucketSize() - 1; i >= 0; i--)
|
||||
{
|
||||
AIPathNode & node = chains[i + bucketOffset];
|
||||
|
||||
@ -486,8 +496,8 @@ public:
|
||||
AINodeStorage & storage, const std::vector<int3> & tiles, uint64_t chainMask, int heroChainTurn)
|
||||
:existingChains(), newChains(), delayedWork(), storage(storage), chainMask(chainMask), heroChainTurn(heroChainTurn), heroChain(), tiles(tiles)
|
||||
{
|
||||
existingChains.reserve(AIPathfinding::NUM_CHAINS);
|
||||
newChains.reserve(AIPathfinding::NUM_CHAINS);
|
||||
existingChains.reserve(storage.getBucketCount() * storage.getBucketSize());
|
||||
newChains.reserve(storage.getBucketCount() * storage.getBucketSize());
|
||||
}
|
||||
|
||||
void execute(const tbb::blocked_range<size_t>& r)
|
||||
@ -719,6 +729,7 @@ void HeroChainCalculationTask::calculateHeroChain(
|
||||
if(node->action == EPathNodeAction::BATTLE
|
||||
|| node->action == EPathNodeAction::TELEPORT_BATTLE
|
||||
|| node->action == EPathNodeAction::TELEPORT_NORMAL
|
||||
|| node->action == EPathNodeAction::DISEMBARK
|
||||
|| node->action == EPathNodeAction::TELEPORT_BLOCKING_VISIT)
|
||||
{
|
||||
continue;
|
||||
@ -961,7 +972,7 @@ void AINodeStorage::setHeroes(std::map<const CGHeroInstance *, HeroRole> heroes)
|
||||
// do not allow our own heroes in garrison to act on map
|
||||
if(hero.first->getOwner() == ai->playerID
|
||||
&& hero.first->inTownGarrison
|
||||
&& (ai->isHeroLocked(hero.first) || ai->heroManager->heroCapReached()))
|
||||
&& (ai->isHeroLocked(hero.first) || ai->heroManager->heroCapReached(false)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -1196,6 +1207,11 @@ void AINodeStorage::calculateTownPortal(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (targetTown->visitingHero
|
||||
&& (targetTown->visitingHero.get()->getFactionID() != actor->hero->getFactionID()
|
||||
|| targetTown->getUpperArmy()->stacksCount()))
|
||||
continue;
|
||||
|
||||
auto nodeOptional = townPortalFinder.createTownPortalNode(targetTown);
|
||||
|
||||
if(nodeOptional)
|
||||
@ -1418,6 +1434,10 @@ void AINodeStorage::calculateChainInfo(std::vector<AIPath> & paths, const int3 &
|
||||
path.heroArmy = node.actor->creatureSet;
|
||||
path.armyLoss = node.armyLoss;
|
||||
path.targetObjectDanger = ai->dangerEvaluator->evaluateDanger(pos, path.targetHero, !node.actor->allowBattle);
|
||||
for (auto pathNode : path.nodes)
|
||||
{
|
||||
path.targetObjectDanger = std::max(ai->dangerEvaluator->evaluateDanger(pathNode.coord, path.targetHero, !node.actor->allowBattle), path.targetObjectDanger);
|
||||
}
|
||||
|
||||
if(path.targetObjectDanger > 0)
|
||||
{
|
||||
@ -1564,7 +1584,7 @@ uint8_t AIPath::turn() const
|
||||
|
||||
uint64_t AIPath::getHeroStrength() const
|
||||
{
|
||||
return targetHero->getFightingStrength() * getHeroArmyStrengthWithCommander(targetHero, heroArmy);
|
||||
return targetHero->getHeroStrength() * getHeroArmyStrengthWithCommander(targetHero, heroArmy);
|
||||
}
|
||||
|
||||
uint64_t AIPath::getTotalDanger() const
|
||||
|
@ -29,9 +29,6 @@ namespace NKAI
|
||||
{
|
||||
namespace AIPathfinding
|
||||
{
|
||||
const int BUCKET_COUNT = 3;
|
||||
const int BUCKET_SIZE = 7;
|
||||
const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
|
||||
const int CHAIN_MAX_DEPTH = 4;
|
||||
}
|
||||
|
||||
@ -157,7 +154,7 @@ public:
|
||||
static boost::mutex locker;
|
||||
static uint32_t version;
|
||||
|
||||
AISharedStorage(int3 mapSize);
|
||||
AISharedStorage(int3 sizes, int numChains);
|
||||
~AISharedStorage();
|
||||
|
||||
STRONG_INLINE
|
||||
@ -197,6 +194,9 @@ public:
|
||||
bool selectFirstActor();
|
||||
bool selectNextActor();
|
||||
|
||||
int getBucketCount() const;
|
||||
int getBucketSize() const;
|
||||
|
||||
std::vector<CGPathNode *> getInitialNodes() override;
|
||||
|
||||
virtual void calculateNeighbours(
|
||||
@ -298,7 +298,7 @@ public:
|
||||
|
||||
inline int getBucket(const ChainActor * actor) const
|
||||
{
|
||||
return ((uintptr_t)actor * 395) % AIPathfinding::BUCKET_COUNT;
|
||||
return ((uintptr_t)actor * 395) % getBucketCount();
|
||||
}
|
||||
|
||||
void calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours);
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include "Rules/AIMovementAfterDestinationRule.h"
|
||||
#include "Rules/AIMovementToDestinationRule.h"
|
||||
#include "Rules/AIPreviousNodeRule.h"
|
||||
#include "../Engine//Nullkiller.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
|
||||
#include "../../../lib/pathfinder/CPathfinder.h"
|
||||
|
||||
|
@ -46,7 +46,7 @@ ChainActor::ChainActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t
|
||||
initialMovement = hero->movementPointsRemaining();
|
||||
initialTurn = 0;
|
||||
armyValue = getHeroArmyStrengthWithCommander(hero, hero);
|
||||
heroFightingStrength = hero->getFightingStrength();
|
||||
heroFightingStrength = hero->getHeroStrength();
|
||||
tiCache.reset(new TurnInfo(hero));
|
||||
}
|
||||
|
||||
@ -182,7 +182,7 @@ ExchangeResult HeroActor::tryExchangeNoLock(const ChainActor * specialActor, con
|
||||
return &actor == specialActor;
|
||||
});
|
||||
|
||||
result.actor = &(dynamic_cast<HeroActor *>(result.actor)->specialActors[index]);
|
||||
result.actor = &(dynamic_cast<HeroActor *>(result.actor)->specialActors.at(index));
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -440,7 +440,7 @@ int DwellingActor::getInitialTurn(bool waitForGrowth, int dayOfWeek)
|
||||
|
||||
std::string DwellingActor::toString() const
|
||||
{
|
||||
return dwelling->typeName + dwelling->visitablePos().toString();
|
||||
return dwelling->getTypeName() + dwelling->visitablePos().toString();
|
||||
}
|
||||
|
||||
CCreatureSet * DwellingActor::getDwellingCreatures(const CGDwelling * dwelling, bool waitForGrowth)
|
||||
|
@ -113,7 +113,7 @@ public:
|
||||
static const int SPECIAL_ACTORS_COUNT = 7;
|
||||
|
||||
private:
|
||||
ChainActor specialActors[SPECIAL_ACTORS_COUNT];
|
||||
std::array<ChainActor, SPECIAL_ACTORS_COUNT> specialActors;
|
||||
std::unique_ptr<HeroExchangeMap> exchangeMap;
|
||||
|
||||
void setupSpecialActors();
|
||||
|
@ -15,7 +15,6 @@
|
||||
|
||||
#include "../../lib/UnlockGuard.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/mapObjects/CQuest.h"
|
||||
#include "../../lib/mapping/CMapDefines.h"
|
||||
@ -187,7 +186,7 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
|
||||
{
|
||||
// TODO: Such information should be provided by pathfinder
|
||||
// Tile must be free or with unoccupied boat
|
||||
if(!t->blocked)
|
||||
if(!t->blocked())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -248,8 +247,8 @@ bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2)
|
||||
|
||||
bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2)
|
||||
{
|
||||
auto art1 = a1->artType;
|
||||
auto art2 = a2->artType;
|
||||
auto art1 = a1->getType();
|
||||
auto art2 = a2->getType();
|
||||
|
||||
if(art1->getPrice() == art2->getPrice())
|
||||
return art1->valOfBonuses(BonusType::PRIMARY_SKILL) > art2->valOfBonuses(BonusType::PRIMARY_SKILL);
|
||||
|
@ -25,11 +25,9 @@ using crstring = const std::string &;
|
||||
using dwellingContent = std::pair<ui32, std::vector<CreatureID>>;
|
||||
|
||||
const int ACTUAL_RESOURCE_COUNT = 7;
|
||||
const int ALLOWED_ROAMING_HEROES = 8;
|
||||
|
||||
//implementation-dependent
|
||||
extern const double SAFE_ATTACK_CONSTANT;
|
||||
extern const int GOLD_RESERVE;
|
||||
|
||||
extern thread_local CCallback * cb;
|
||||
extern thread_local VCAI * ai;
|
||||
|
@ -36,7 +36,7 @@ std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, c
|
||||
{
|
||||
for(auto & i : armyPtr->Slots())
|
||||
{
|
||||
auto cre = dynamic_cast<const CCreature*>(i.second->type);
|
||||
auto cre = dynamic_cast<const CCreature*>(i.second->getType());
|
||||
auto & slotInfp = creToPower[cre];
|
||||
|
||||
slotInfp.creature = cre;
|
||||
|
@ -23,13 +23,13 @@ bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!vstd::contains(t->town->buildings, building))
|
||||
if (!vstd::contains(t->getTown()->buildings, building))
|
||||
return false; // no such building in town
|
||||
|
||||
if (t->hasBuilt(building)) //Already built? Shouldn't happen in general
|
||||
return true;
|
||||
|
||||
const CBuilding * buildPtr = t->town->buildings.at(building);
|
||||
const CBuilding * buildPtr = t->getTown()->buildings.at(building);
|
||||
|
||||
auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID)
|
||||
{
|
||||
@ -51,7 +51,7 @@ bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID
|
||||
|
||||
for (const auto & buildID : toBuild)
|
||||
{
|
||||
const CBuilding * b = t->town->buildings.at(buildID);
|
||||
const CBuilding * b = t->getTown()->buildings.at(buildID);
|
||||
|
||||
EBuildingState canBuild = cb->canBuildStructure(t, buildID);
|
||||
if (canBuild == EBuildingState::ALLOWED)
|
||||
@ -220,7 +220,7 @@ bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
|
||||
|
||||
//at the end, try to get and build any extra buildings with nonstandard slots (for example HotA 3rd level dwelling)
|
||||
std::vector<BuildingID> extraBuildings;
|
||||
for (auto buildingInfo : t->town->buildings)
|
||||
for (auto buildingInfo : t->getTown()->buildings)
|
||||
{
|
||||
if (buildingInfo.first > BuildingID::DWELL_UP2_FIRST)
|
||||
extraBuildings.push_back(buildingInfo.first);
|
||||
|
@ -56,7 +56,7 @@ TSubgoal BuildThis::whatToDoToAchieve()
|
||||
case EBuildingState::ALLOWED:
|
||||
case EBuildingState::NO_RESOURCES:
|
||||
{
|
||||
auto res = town->town->buildings.at(BuildingID(bid))->resources;
|
||||
auto res = town->getTown()->buildings.at(BuildingID(bid))->resources;
|
||||
return ai->ah->whatToDo(res, iAmElementar()); //realize immediately or gather resources
|
||||
}
|
||||
break;
|
||||
|
@ -162,7 +162,7 @@ TGoalVec CompleteQuest::missionArmy() const
|
||||
|
||||
for(auto creature : q.quest->mission.creatures)
|
||||
{
|
||||
solutions.push_back(sptr(GatherTroops(creature.type->getId(), creature.count)));
|
||||
solutions.push_back(sptr(GatherTroops(creature.getId(), creature.count)));
|
||||
}
|
||||
|
||||
return solutions;
|
||||
|
@ -46,7 +46,7 @@ TSubgoal FindObj::whatToDoToAchieve()
|
||||
}
|
||||
}
|
||||
}
|
||||
if(o && ai->isAccessible(o->pos)) //we don't use isAccessibleForHero as we don't know which hero it is
|
||||
if(o && ai->isAccessible(o->visitablePos())) //we don't use isAccessibleForHero as we don't know which hero it is
|
||||
return sptr(VisitObj(o->id.getNum()));
|
||||
else
|
||||
return sptr(Explore());
|
||||
|
@ -88,13 +88,13 @@ TGoalVec GatherTroops::getAllPossibleSubgoals()
|
||||
}
|
||||
|
||||
auto creature = VLC->creatures()->getByIndex(objid);
|
||||
if(t->getFaction() == creature->getFaction()) //TODO: how to force AI to build unupgraded creatures? :O
|
||||
if(t->getFactionID() == creature->getFactionID()) //TODO: how to force AI to build unupgraded creatures? :O
|
||||
{
|
||||
auto tryFindCreature = [&]() -> std::optional<std::vector<CreatureID>>
|
||||
{
|
||||
if(vstd::isValidIndex(t->town->creatures, creature->getLevel() - 1))
|
||||
if(vstd::isValidIndex(t->getTown()->creatures, creature->getLevel() - 1))
|
||||
{
|
||||
auto itr = t->town->creatures.begin();
|
||||
auto itr = t->getTown()->creatures.begin();
|
||||
std::advance(itr, creature->getLevel() - 1);
|
||||
return make_optional(*itr);
|
||||
}
|
||||
@ -109,7 +109,7 @@ TGoalVec GatherTroops::getAllPossibleSubgoals()
|
||||
if(upgradeNumber < 0)
|
||||
continue;
|
||||
|
||||
BuildingID bid(BuildingID::DWELL_FIRST + creature->getLevel() - 1 + upgradeNumber * t->town->creatures.size());
|
||||
BuildingID bid(BuildingID::DWELL_FIRST + creature->getLevel() - 1 + upgradeNumber * t->getTown()->creatures.size());
|
||||
if(t->hasBuilt(bid) && ai->ah->freeResources().canAfford(creature->getFullRecruitCost())) //this assumes only creatures with dwellings are assigned to faction
|
||||
{
|
||||
solutions.push_back(sptr(BuyArmy(t, creature->getAIValue() * this->value).setobjid(objid)));
|
||||
|
@ -12,7 +12,7 @@
|
||||
#include "../../lib/GameConstants.h"
|
||||
#include "../../lib/VCMI_Lib.h"
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/mapObjects/CompoundMapObjectID.h"
|
||||
#include "../../lib/mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
@ -68,7 +68,7 @@ std::optional<int> MapObjectsEvaluator::getObjectValue(const CGObjectInstance *
|
||||
{
|
||||
//special case handling: in-game heroes have hero ID as object subID, but when reading configs available hero object subID's are hero classes
|
||||
auto hero = dynamic_cast<const CGHeroInstance*>(obj);
|
||||
return getObjectValue(obj->ID, hero->type->heroClass->getIndex());
|
||||
return getObjectValue(obj->ID, hero->getHeroClassID());
|
||||
}
|
||||
else if(obj->ID == Obj::PRISON)
|
||||
{
|
||||
@ -92,7 +92,7 @@ std::optional<int> MapObjectsEvaluator::getObjectValue(const CGObjectInstance *
|
||||
else if(obj->ID == Obj::ARTIFACT)
|
||||
{
|
||||
auto artifactObject = dynamic_cast<const CGArtifact *>(obj);
|
||||
switch(artifactObject->storedArtifact->artType->aClass)
|
||||
switch(artifactObject->storedArtifact->getType()->aClass)
|
||||
{
|
||||
case CArtifact::EartClass::ART_TREASURE:
|
||||
return 2000;
|
||||
|
@ -46,10 +46,10 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
|
||||
for(pos.y=0; pos.y < sizes.y; ++pos.y)
|
||||
{
|
||||
const TerrainTile & tile = gs->map->getTile(pos);
|
||||
if(!tile.terType->isPassable())
|
||||
if(!tile.getTerrain()->isPassable())
|
||||
continue;
|
||||
|
||||
if(tile.terType->isWater())
|
||||
if(tile.getTerrain()->isWater())
|
||||
{
|
||||
resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
|
||||
if(useFlying)
|
||||
|
@ -14,8 +14,6 @@
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/mapObjects/MapObjects.h"
|
||||
|
||||
#define GOLD_RESERVE (10000); //at least we'll be able to reach capitol
|
||||
|
||||
ResourceObjective::ResourceObjective(const TResources & Res, Goals::TSubgoal Goal)
|
||||
: resources(Res), goal(Goal)
|
||||
{
|
||||
|
@ -20,7 +20,6 @@
|
||||
#include "../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../lib/mapObjects/ObjectTemplate.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/IGameSettings.h"
|
||||
#include "../../lib/gameState/CGameState.h"
|
||||
#include "../../lib/bonuses/Limiters.h"
|
||||
@ -732,7 +731,7 @@ void VCAI::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstance *
|
||||
//you can't request action from action-response thread
|
||||
requestActionASAP([=]()
|
||||
{
|
||||
if(removableUnits && !cb->getStartInfo()->isSteadwickFallCampaignMission())
|
||||
if(removableUnits && !cb->getStartInfo()->isRestorationOfErathiaCampaign())
|
||||
pickBestCreatures(down, up);
|
||||
|
||||
answerQuery(queryID, 0);
|
||||
@ -1032,7 +1031,7 @@ void VCAI::mainLoop()
|
||||
|
||||
void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
|
||||
{
|
||||
LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->pos.toString());
|
||||
LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->anchorPos().toString());
|
||||
switch(obj->ID)
|
||||
{
|
||||
case Obj::TOWN:
|
||||
@ -1181,7 +1180,7 @@ void VCAI::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * ot
|
||||
//FIXME: why are the above possible to be null?
|
||||
|
||||
bool emptySlotFound = false;
|
||||
for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
|
||||
for(auto slot : artifact->getType()->getPossibleSlots().at(target->bearerType()))
|
||||
{
|
||||
if(target->isPositionFree(slot) && artifact->canBePutAt(target, slot, true)) //combined artifacts are not always allowed to move
|
||||
{
|
||||
@ -1194,7 +1193,7 @@ void VCAI::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * ot
|
||||
}
|
||||
if(!emptySlotFound) //try to put that atifact in already occupied slot
|
||||
{
|
||||
for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
|
||||
for(auto slot : artifact->getType()->getPossibleSlots().at(target->bearerType()))
|
||||
{
|
||||
auto otherSlot = target->getSlot(slot);
|
||||
if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one
|
||||
@ -1315,8 +1314,6 @@ bool VCAI::canRecruitAnyHero(const CGTownInstance * t) const
|
||||
return false;
|
||||
if(cb->getResourceAmount(EGameResID::GOLD) < GameConstants::HERO_GOLD_COST) //TODO: use ResourceManager
|
||||
return false;
|
||||
if(cb->getHeroesInfo().size() >= ALLOWED_ROAMING_HEROES)
|
||||
return false;
|
||||
if(cb->getHeroesInfo().size() >= cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
|
||||
return false;
|
||||
if(!cb->getAvailableHeroes(t).size())
|
||||
@ -1417,11 +1414,11 @@ void VCAI::wander(HeroPtr h)
|
||||
//TODO pick the truly best
|
||||
const CGTownInstance * t = *boost::max_element(townsNotReachable, compareReinforcements);
|
||||
logAi->debug("%s can't reach any town, we'll try to make our way to %s at %s", h->getNameTranslated(), t->getNameTranslated(), t->visitablePos().toString());
|
||||
int3 pos1 = h->pos;
|
||||
int3 posBefore = h->visitablePos();
|
||||
striveToGoal(sptr(Goals::ClearWayTo(t->visitablePos()).sethero(h))); //TODO: drop "strive", add to mainLoop
|
||||
//if out hero is stuck, we may need to request another hero to clear the way we see
|
||||
|
||||
if(pos1 == h->pos && h == primaryHero()) //hero can't move
|
||||
if(posBefore == h->visitablePos() && h == primaryHero()) //hero can't move
|
||||
{
|
||||
if(canRecruitAnyHero(t))
|
||||
recruitHero(t);
|
||||
@ -1471,7 +1468,7 @@ void VCAI::wander(HeroPtr h)
|
||||
{
|
||||
auto chosenObject = cb->getObjInstance(ObjectInstanceID(bestObjectGoal->objid));
|
||||
if(chosenObject != nullptr)
|
||||
logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->pos.toString());
|
||||
logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->anchorPos().toString());
|
||||
}
|
||||
else
|
||||
logAi->debug("Trying to realize goal of type %s as part of wandering.", bestObjectGoal->name());
|
||||
@ -1994,8 +1991,8 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
|
||||
void VCAI::buildStructure(const CGTownInstance * t, BuildingID building)
|
||||
{
|
||||
auto name = t->town->buildings.at(building)->getNameTranslated();
|
||||
logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->pos.toString());
|
||||
auto name = t->getTown()->buildings.at(building)->getNameTranslated();
|
||||
logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->anchorPos().toString());
|
||||
cb->buildBuilding(t, building); //just do this;
|
||||
}
|
||||
|
||||
@ -2081,7 +2078,7 @@ void VCAI::tryRealize(Goals::BuildThis & g)
|
||||
if (cb->canBuildStructure(t, b) == EBuildingState::ALLOWED)
|
||||
{
|
||||
logAi->debug("Player %d will build %s in town of %s at %s",
|
||||
playerID, t->town->buildings.at(b)->getNameTranslated(), t->getNameTranslated(), t->pos.toString());
|
||||
playerID, t->getTown()->buildings.at(b)->getNameTranslated(), t->getNameTranslated(), t->anchorPos().toString());
|
||||
cb->buildBuilding(t, b);
|
||||
throw goalFulfilledException(sptr(g));
|
||||
}
|
||||
@ -2819,7 +2816,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
|
||||
{
|
||||
for(auto slot : h->Slots())
|
||||
{
|
||||
if(slot.second->type->hasUpgrades())
|
||||
if(slot.second->getType()->hasUpgrades())
|
||||
return true; //TODO: check price?
|
||||
}
|
||||
return false;
|
||||
|
@ -46,6 +46,7 @@ const std::vector<std::vector<std::string>> contributors = {
|
||||
{ "Developing", "", "vmarkovtsev", "" },
|
||||
{ "Developing", "Tom Zielinski", "Warmonger", "Warmonger@vp.pl" },
|
||||
{ "Developing", "Xiaomin Ding", "", "dingding303@gmail.com" },
|
||||
{ "Developing", "Fenghuang Rumeng", "kdmcser", "zqtndfj@gmail.com" },
|
||||
|
||||
{ "Testing", "Ben Yan", "by003", "benyan9110@gmail.com," },
|
||||
{ "Testing", "", "Misiokles", "" },
|
||||
|
113
CCallback.cpp
113
CCallback.cpp
@ -18,31 +18,31 @@
|
||||
#include "lib/mapObjects/CGHeroInstance.h"
|
||||
#include "lib/mapObjects/CGTownInstance.h"
|
||||
#include "lib/texts/CGeneralTextHandler.h"
|
||||
#include "lib/CHeroHandler.h"
|
||||
#include "lib/CArtHandler.h"
|
||||
#include "lib/GameConstants.h"
|
||||
#include "lib/CPlayerState.h"
|
||||
#include "lib/UnlockGuard.h"
|
||||
#include "lib/battle/BattleInfo.h"
|
||||
#include "lib/networkPacks/PacksForServer.h"
|
||||
#include "lib/networkPacks/SaveLocalState.h"
|
||||
|
||||
bool CCallback::teleportHero(const CGHeroInstance *who, const CGTownInstance *where)
|
||||
{
|
||||
CastleTeleportHero pack(who->id, where->id, 1);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CCallback::moveHero(const CGHeroInstance *h, const int3 & destination, bool transit)
|
||||
{
|
||||
MoveHero pack({destination}, h->id, transit);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
}
|
||||
|
||||
void CCallback::moveHero(const CGHeroInstance *h, const std::vector<int3> & path, bool transit)
|
||||
{
|
||||
MoveHero pack(path, h->id, transit);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
}
|
||||
|
||||
int CCallback::selectionMade(int selection, QueryID queryID)
|
||||
@ -61,7 +61,7 @@ int CCallback::sendQueryReply(std::optional<int32_t> reply, QueryID queryID)
|
||||
|
||||
QueryReply pack(queryID, reply);
|
||||
pack.player = *player;
|
||||
return sendRequest(&pack);
|
||||
return sendRequest(pack);
|
||||
}
|
||||
|
||||
void CCallback::recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level)
|
||||
@ -71,7 +71,7 @@ void CCallback::recruitCreatures(const CGDwelling * obj, const CArmedInstance *
|
||||
return;
|
||||
|
||||
RecruitCreatures pack(obj->id, dst->id, ID, amount, level);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
}
|
||||
|
||||
bool CCallback::dismissCreature(const CArmedInstance *obj, SlotID stackPos)
|
||||
@ -80,14 +80,14 @@ bool CCallback::dismissCreature(const CArmedInstance *obj, SlotID stackPos)
|
||||
return false;
|
||||
|
||||
DisbandCreature pack(stackPos,obj->id);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CCallback::upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID)
|
||||
{
|
||||
UpgradeCreature pack(stackPos,obj->id,newID);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -95,54 +95,54 @@ void CCallback::endTurn()
|
||||
{
|
||||
logGlobal->trace("Player %d ended his turn.", player->getNum());
|
||||
EndTurn pack;
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
}
|
||||
int CCallback::swapCreatures(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
|
||||
{
|
||||
ArrangeStacks pack(1,p1,p2,s1->id,s2->id,0);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CCallback::mergeStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
|
||||
{
|
||||
ArrangeStacks pack(2,p1,p2,s1->id,s2->id,0);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CCallback::splitStack(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2, int val)
|
||||
{
|
||||
ArrangeStacks pack(3,p1,p2,s1->id,s2->id,val);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CCallback::bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot)
|
||||
{
|
||||
BulkMoveArmy pack(srcArmy, destArmy, srcSlot);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CCallback::bulkSplitStack(ObjectInstanceID armyId, SlotID srcSlot, int howMany)
|
||||
{
|
||||
BulkSplitStack pack(armyId, srcSlot, howMany);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CCallback::bulkSmartSplitStack(ObjectInstanceID armyId, SlotID srcSlot)
|
||||
{
|
||||
BulkSmartSplitStack pack(armyId, srcSlot);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CCallback::bulkMergeStacks(ObjectInstanceID armyId, SlotID srcSlot)
|
||||
{
|
||||
BulkMergeStacks pack(armyId, srcSlot);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -151,7 +151,7 @@ bool CCallback::dismissHero(const CGHeroInstance *hero)
|
||||
if(player!=hero->tempOwner) return false;
|
||||
|
||||
DismissHero pack(hero->id);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -160,7 +160,7 @@ bool CCallback::swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation
|
||||
ExchangeArtifacts ea;
|
||||
ea.src = l1;
|
||||
ea.dst = l2;
|
||||
sendRequest(&ea);
|
||||
sendRequest(ea);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -175,13 +175,13 @@ bool CCallback::swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation
|
||||
void CCallback::assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)
|
||||
{
|
||||
AssembleArtifacts aa(heroID, artifactSlot, assemble, assembleTo);
|
||||
sendRequest(&aa);
|
||||
sendRequest(aa);
|
||||
}
|
||||
|
||||
void CCallback::bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap, bool equipped, bool backpack)
|
||||
{
|
||||
BulkExchangeArtifacts bma(srcHero, dstHero, swap, equipped, backpack);
|
||||
sendRequest(&bma);
|
||||
sendRequest(bma);
|
||||
}
|
||||
|
||||
void CCallback::scrollBackpackArtifacts(ObjectInstanceID hero, bool left)
|
||||
@ -189,19 +189,37 @@ void CCallback::scrollBackpackArtifacts(ObjectInstanceID hero, bool left)
|
||||
ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SCROLL_RIGHT);
|
||||
if(left)
|
||||
mba.cmd = ManageBackpackArtifacts::ManageCmd::SCROLL_LEFT;
|
||||
sendRequest(&mba);
|
||||
sendRequest(mba);
|
||||
}
|
||||
|
||||
void CCallback::sortBackpackArtifactsBySlot(const ObjectInstanceID hero)
|
||||
{
|
||||
ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SORT_BY_SLOT);
|
||||
sendRequest(mba);
|
||||
}
|
||||
|
||||
void CCallback::sortBackpackArtifactsByCost(const ObjectInstanceID hero)
|
||||
{
|
||||
ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SORT_BY_COST);
|
||||
sendRequest(mba);
|
||||
}
|
||||
|
||||
void CCallback::sortBackpackArtifactsByClass(const ObjectInstanceID hero)
|
||||
{
|
||||
ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SORT_BY_CLASS);
|
||||
sendRequest(mba);
|
||||
}
|
||||
|
||||
void CCallback::manageHeroCostume(ObjectInstanceID hero, size_t costumeIndex, bool saveCostume)
|
||||
{
|
||||
ManageEquippedArtifacts mea(hero, costumeIndex, saveCostume);
|
||||
sendRequest(&mea);
|
||||
sendRequest(mea);
|
||||
}
|
||||
|
||||
void CCallback::eraseArtifactByClient(const ArtifactLocation & al)
|
||||
{
|
||||
EraseArtifactByClient ea(al);
|
||||
sendRequest(&ea);
|
||||
sendRequest(ea);
|
||||
}
|
||||
|
||||
bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
|
||||
@ -213,7 +231,7 @@ bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
|
||||
return false;
|
||||
|
||||
BuildStructure pack(town->id,buildingID);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -223,7 +241,7 @@ bool CCallback::visitTownBuilding(const CGTownInstance *town, BuildingID buildin
|
||||
return false;
|
||||
|
||||
VisitTownBuilding pack(town->id, buildingID);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -232,10 +250,10 @@ void CBattleCallback::battleMakeSpellAction(const BattleID & battleID, const Bat
|
||||
assert(action.actionType == EActionType::HERO_SPELL);
|
||||
MakeAction mca(action);
|
||||
mca.battleID = battleID;
|
||||
sendRequest(&mca);
|
||||
sendRequest(mca);
|
||||
}
|
||||
|
||||
int CBattleCallback::sendRequest(const CPackForServer * request)
|
||||
int CBattleCallback::sendRequest(const CPackForServer & request)
|
||||
{
|
||||
int requestID = cl->sendRequest(request, *getPlayerID());
|
||||
if(waitTillRealize)
|
||||
@ -249,12 +267,18 @@ int CBattleCallback::sendRequest(const CPackForServer * request)
|
||||
return requestID;
|
||||
}
|
||||
|
||||
void CCallback::spellResearch( const CGTownInstance *town, SpellID spellAtSlot, bool accepted )
|
||||
{
|
||||
SpellResearch pack(town->id, spellAtSlot, accepted);
|
||||
sendRequest(pack);
|
||||
}
|
||||
|
||||
void CCallback::swapGarrisonHero( const CGTownInstance *town )
|
||||
{
|
||||
if(town->tempOwner == *player || (town->garrisonHero && town->garrisonHero->tempOwner == *player ))
|
||||
{
|
||||
GarrisonHeroSwap pack(town->id);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,7 +287,7 @@ void CCallback::buyArtifact(const CGHeroInstance *hero, ArtifactID aid)
|
||||
if(hero->tempOwner != *player) return;
|
||||
|
||||
BuyArtifact pack(hero->id,aid);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
}
|
||||
|
||||
void CCallback::trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero)
|
||||
@ -280,13 +304,13 @@ void CCallback::trade(const ObjectInstanceID marketId, EMarketMode mode, const s
|
||||
pack.r1 = id1;
|
||||
pack.r2 = id2;
|
||||
pack.val = val1;
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
}
|
||||
|
||||
void CCallback::setFormation(const CGHeroInstance * hero, EArmyFormation mode)
|
||||
{
|
||||
SetFormation pack(hero->id, mode);
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
}
|
||||
|
||||
void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero)
|
||||
@ -294,9 +318,18 @@ void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroIn
|
||||
assert(townOrTavern);
|
||||
assert(hero);
|
||||
|
||||
HireHero pack(hero->getHeroType(), townOrTavern->id, nextHero);
|
||||
HireHero pack(hero->getHeroTypeID(), townOrTavern->id, nextHero);
|
||||
pack.player = *player;
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
}
|
||||
|
||||
void CCallback::saveLocalState(const JsonNode & data)
|
||||
{
|
||||
SaveLocalState state;
|
||||
state.data = data;
|
||||
state.player = *player;
|
||||
|
||||
sendRequest(state);
|
||||
}
|
||||
|
||||
void CCallback::save( const std::string &fname )
|
||||
@ -310,7 +343,7 @@ void CCallback::gamePause(bool pause)
|
||||
{
|
||||
GamePause pack;
|
||||
pack.player = *player;
|
||||
sendRequest(&pack);
|
||||
sendRequest(pack);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -324,14 +357,14 @@ void CCallback::sendMessage(const std::string &mess, const CGObjectInstance * cu
|
||||
PlayerMessage pm(mess, currentObject? currentObject->id : ObjectInstanceID(-1));
|
||||
if(player)
|
||||
pm.player = *player;
|
||||
sendRequest(&pm);
|
||||
sendRequest(pm);
|
||||
}
|
||||
|
||||
void CCallback::buildBoat( const IShipyard *obj )
|
||||
{
|
||||
BuildBoat bb;
|
||||
bb.objid = dynamic_cast<const CGObjectInstance*>(obj)->id;
|
||||
sendRequest(&bb);
|
||||
sendRequest(bb);
|
||||
}
|
||||
|
||||
CCallback::CCallback(CGameState * GS, std::optional<PlayerColor> Player, CClient * C)
|
||||
@ -373,7 +406,7 @@ void CCallback::dig( const CGObjectInstance *hero )
|
||||
{
|
||||
DigWithHero dwh;
|
||||
dwh.id = hero->id;
|
||||
sendRequest(&dwh);
|
||||
sendRequest(dwh);
|
||||
}
|
||||
|
||||
void CCallback::castSpell(const CGHeroInstance *hero, SpellID spellID, const int3 &pos)
|
||||
@ -382,7 +415,7 @@ void CCallback::castSpell(const CGHeroInstance *hero, SpellID spellID, const int
|
||||
cas.hid = hero->id;
|
||||
cas.sid = spellID;
|
||||
cas.pos = pos;
|
||||
sendRequest(&cas);
|
||||
sendRequest(cas);
|
||||
}
|
||||
|
||||
int CCallback::mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
|
||||
@ -415,7 +448,7 @@ void CBattleCallback::battleMakeUnitAction(const BattleID & battleID, const Batt
|
||||
MakeAction ma;
|
||||
ma.ba = action;
|
||||
ma.battleID = battleID;
|
||||
sendRequest(&ma);
|
||||
sendRequest(ma);
|
||||
}
|
||||
|
||||
void CBattleCallback::battleMakeTacticAction(const BattleID & battleID, const BattleAction & action )
|
||||
@ -424,7 +457,7 @@ void CBattleCallback::battleMakeTacticAction(const BattleID & battleID, const Ba
|
||||
MakeAction ma;
|
||||
ma.ba = action;
|
||||
ma.battleID = battleID;
|
||||
sendRequest(&ma);
|
||||
sendRequest(ma);
|
||||
}
|
||||
|
||||
std::optional<BattleAction> CBattleCallback::makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState)
|
||||
|
12
CCallback.h
12
CCallback.h
@ -78,6 +78,7 @@ public:
|
||||
virtual bool visitTownBuilding(const CGTownInstance *town, BuildingID buildingID)=0;
|
||||
virtual void recruitCreatures(const CGDwelling *obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1)=0;
|
||||
virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made
|
||||
virtual void spellResearch(const CGTownInstance *town, SpellID spellAtSlot, bool accepted)=0;
|
||||
virtual void swapGarrisonHero(const CGTownInstance *town)=0;
|
||||
|
||||
virtual void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero)=0; //mode==0: sell val1 units of id1 resource for id2 resiurce
|
||||
@ -92,10 +93,14 @@ public:
|
||||
//virtual bool swapArtifacts(const CGHeroInstance * hero1, ui16 pos1, const CGHeroInstance * hero2, ui16 pos2)=0; //swaps artifacts between two given heroes
|
||||
virtual bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2)=0;
|
||||
virtual void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) = 0;
|
||||
virtual void sortBackpackArtifactsBySlot(const ObjectInstanceID hero) = 0;
|
||||
virtual void sortBackpackArtifactsByCost(const ObjectInstanceID hero) = 0;
|
||||
virtual void sortBackpackArtifactsByClass(const ObjectInstanceID hero) = 0;
|
||||
virtual void manageHeroCostume(ObjectInstanceID hero, size_t costumeIndex, bool saveCostume) = 0;
|
||||
virtual void assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0;
|
||||
virtual void eraseArtifactByClient(const ArtifactLocation & al)=0;
|
||||
virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0;
|
||||
virtual void saveLocalState(const JsonNode & data)=0;
|
||||
virtual void endTurn()=0;
|
||||
virtual void buyArtifact(const CGHeroInstance *hero, ArtifactID aid)=0; //used to buy artifacts in towns (including spell book in the guild and war machines in blacksmith)
|
||||
virtual void setFormation(const CGHeroInstance * hero, EArmyFormation mode)=0;
|
||||
@ -123,7 +128,7 @@ class CBattleCallback : public IBattleCallback
|
||||
std::optional<PlayerColor> player;
|
||||
|
||||
protected:
|
||||
int sendRequest(const CPackForServer * request); //returns requestID (that'll be matched to requestID in PackageApplied)
|
||||
int sendRequest(const CPackForServer & request); //returns requestID (that'll be matched to requestID in PackageApplied)
|
||||
CClient *cl;
|
||||
|
||||
public:
|
||||
@ -179,6 +184,9 @@ public:
|
||||
void assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override;
|
||||
void bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap, bool equipped = true, bool backpack = true) override;
|
||||
void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) override;
|
||||
void sortBackpackArtifactsBySlot(const ObjectInstanceID hero) override;
|
||||
void sortBackpackArtifactsByCost(const ObjectInstanceID hero) override;
|
||||
void sortBackpackArtifactsByClass(const ObjectInstanceID hero) override;
|
||||
void manageHeroCostume(ObjectInstanceID hero, size_t costumeIdx, bool saveCostume) override;
|
||||
void eraseArtifactByClient(const ArtifactLocation & al) override;
|
||||
bool buildBuilding(const CGTownInstance *town, BuildingID buildingID) override;
|
||||
@ -186,7 +194,9 @@ public:
|
||||
void recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1) override;
|
||||
bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override;
|
||||
bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override;
|
||||
void saveLocalState(const JsonNode & data) override;
|
||||
void endTurn() override;
|
||||
void spellResearch(const CGTownInstance *town, SpellID spellAtSlot, bool accepted) override;
|
||||
void swapGarrisonHero(const CGTownInstance *town) override;
|
||||
void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override;
|
||||
void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override;
|
||||
|
@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DEPS_FILENAME=dependencies-android-32
|
||||
. CI/android/before_install.sh
|
@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DEPS_FILENAME=dependencies-android-64
|
||||
. CI/android/before_install.sh
|
@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/25.2.9519653" >> $GITHUB_ENV
|
||||
|
||||
brew install ninja
|
||||
|
||||
. CI/install_conan_dependencies.sh "$DEPS_FILENAME"
|
4
CI/before_install/android.sh
Normal file
4
CI/before_install/android.sh
Normal file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install ninja-build
|
@ -1,6 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
sudo apt remove needrestart
|
||||
sudo apt-get update
|
||||
|
||||
# Dependencies
|
||||
@ -9,6 +8,6 @@ sudo apt-get update
|
||||
# - debian build settings at debian/control
|
||||
sudo apt-get install libboost-dev libboost-filesystem-dev libboost-system-dev libboost-thread-dev libboost-program-options-dev libboost-locale-dev libboost-iostreams-dev \
|
||||
libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \
|
||||
qtbase5-dev \
|
||||
qtbase5-dev qttools5-dev \
|
||||
ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev \
|
||||
libminizip-dev libfuzzylite-dev qttools5-dev libsqlite3-dev # Optional dependencies
|
||||
libminizip-dev libfuzzylite-dev libsqlite3-dev # Optional dependencies
|
@ -1,9 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
sudo apt remove needrestart
|
||||
sudo apt-get update
|
||||
|
||||
# Dependencies
|
||||
# In case of change in dependencies list please also update:
|
||||
# - developer docs at docs/developer/Building_Linux.md
|
||||
# - debian build settings at debian/control
|
||||
sudo apt-get install libboost-dev libboost-filesystem-dev libboost-system-dev libboost-thread-dev libboost-program-options-dev libboost-locale-dev libboost-iostreams-dev \
|
||||
libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \
|
||||
qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools \
|
2
CI/mac/before_install.sh → CI/before_install/macos.sh
Executable file → Normal file
2
CI/mac/before_install.sh → CI/before_install/macos.sh
Executable file → Normal file
@ -3,5 +3,3 @@
|
||||
echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV
|
||||
|
||||
brew install ninja
|
||||
|
||||
. CI/install_conan_dependencies.sh "$DEPS_FILENAME"
|
7
CI/before_install/mingw.sh
Normal file
7
CI/before_install/mingw.sh
Normal file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install ninja-build mingw-w64 nsis
|
||||
|
||||
sudo update-alternatives --set i686-w64-mingw32-g++ /usr/bin/i686-w64-mingw32-g++-posix
|
||||
sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix
|
17
CI/before_install/msvc.sh
Normal file
17
CI/before_install/msvc.sh
Normal file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
MSVC_INSTALL_PATH=$(vswhere -latest -property installationPath)
|
||||
echo "MSVC_INSTALL_PATH = $MSVC_INSTALL_PATH"
|
||||
echo "Installed toolset versions:"
|
||||
ls -vr "$MSVC_INSTALL_PATH/VC/Tools/MSVC"
|
||||
|
||||
TOOLS_DIR=$(ls -vr "$MSVC_INSTALL_PATH/VC/Tools/MSVC/" | head -1)
|
||||
DUMPBIN_PATH="$MSVC_INSTALL_PATH/VC/Tools/MSVC/$TOOLS_DIR/bin/Hostx64/x64/dumpbin.exe"
|
||||
|
||||
# This command should work as well, but for some reason it is *extremely* slow on the Github CI (~7 minutes)
|
||||
#DUMPBIN_PATH=$(vswhere -latest -find **/dumpbin.exe | head -n 1)
|
||||
|
||||
echo "TOOLS_DIR = $TOOLS_DIR"
|
||||
echo "DUMPBIN_PATH = $DUMPBIN_PATH"
|
||||
|
||||
dirname "$DUMPBIN_PATH" > "$GITHUB_PATH"
|
@ -10,7 +10,7 @@ STRIP={{ target_host }}-strip
|
||||
{%- endmacro -%}
|
||||
|
||||
{% macro generate_env_win32(target_host) -%}
|
||||
CONAN_SYSTEM_LIBRARY_LOCATION=/usr/lib/gcc/{{ target_host }}/10-posix/
|
||||
CONAN_SYSTEM_LIBRARY_LOCATION=/usr/lib/gcc/{{ target_host }}/13-posix/
|
||||
RC={{ target_host }}-windres
|
||||
{%- endmacro -%}
|
||||
|
||||
|
280
CI/example.markdownlint-cli2.jsonc
Normal file
280
CI/example.markdownlint-cli2.jsonc
Normal file
@ -0,0 +1,280 @@
|
||||
{
|
||||
"config" : {
|
||||
"default" : true,
|
||||
|
||||
// MD001/heading-increment : Heading levels should only increment by one level at a time : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md001.md
|
||||
"MD001": false,
|
||||
|
||||
// MD003/heading-style : Heading style : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md003.md
|
||||
"MD003": {
|
||||
"style": "atx"
|
||||
},
|
||||
|
||||
// MD004/ul-style : Unordered list style : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md004.md
|
||||
"MD004": false,
|
||||
// FIXME: enable and consider fixing
|
||||
//{
|
||||
// "style": "consistent"
|
||||
//},
|
||||
|
||||
// MD005/list-indent : Inconsistent indentation for list items at the same level : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md005.md
|
||||
"MD005": true,
|
||||
|
||||
// MD007/ul-indent : Unordered list indentation : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md007.md
|
||||
"MD007": {
|
||||
// Spaces for indent
|
||||
"indent": 2,
|
||||
// Whether to indent the first level of the list
|
||||
"start_indented": false,
|
||||
// Spaces for first level indent (when start_indented is set)
|
||||
"start_indent": 0
|
||||
},
|
||||
|
||||
// MD009/no-trailing-spaces : Trailing spaces : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md009.md
|
||||
"MD009": {
|
||||
// Spaces for line break
|
||||
"br_spaces": 2,
|
||||
// Allow spaces for empty lines in list items
|
||||
"list_item_empty_lines": false,
|
||||
// Include unnecessary breaks
|
||||
"strict": false
|
||||
},
|
||||
|
||||
// MD010/no-hard-tabs : Hard tabs : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md010.md
|
||||
"MD010": {
|
||||
// Include code blocks
|
||||
"code_blocks": false,
|
||||
// Fenced code languages to ignore
|
||||
"ignore_code_languages": [],
|
||||
// Number of spaces for each hard tab
|
||||
"spaces_per_tab": 4
|
||||
},
|
||||
|
||||
// MD011/no-reversed-links : Reversed link syntax : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md011.md
|
||||
"MD011": true,
|
||||
|
||||
// MD012/no-multiple-blanks : Multiple consecutive blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md012.md
|
||||
"MD012": {
|
||||
// Consecutive blank lines
|
||||
"maximum": 1
|
||||
},
|
||||
|
||||
// MD013/line-length : Line length : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md013.md
|
||||
"MD013": false,
|
||||
|
||||
// MD014/commands-show-output : Dollar signs used before commands without showing output : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md014.md
|
||||
"MD014": true,
|
||||
|
||||
// MD018/no-missing-space-atx : No space after hash on atx style heading : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md018.md
|
||||
"MD018": true,
|
||||
|
||||
// MD019/no-multiple-space-atx : Multiple spaces after hash on atx style heading : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md019.md
|
||||
"MD019": true,
|
||||
|
||||
// MD020/no-missing-space-closed-atx : No space inside hashes on closed atx style heading : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md020.md
|
||||
"MD020": true,
|
||||
|
||||
// MD021/no-multiple-space-closed-atx : Multiple spaces inside hashes on closed atx style heading : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md021.md
|
||||
"MD021": true,
|
||||
|
||||
// MD022/blanks-around-headings : Headings should be surrounded by blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md022.md
|
||||
"MD022": {
|
||||
// Blank lines above heading
|
||||
"lines_above": 1,
|
||||
// Blank lines below heading
|
||||
"lines_below": 1
|
||||
},
|
||||
|
||||
// MD023/heading-start-left : Headings must start at the beginning of the line : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md023.md
|
||||
"MD023": true,
|
||||
|
||||
// MD024/no-duplicate-heading : Multiple headings with the same content : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md024.md
|
||||
"MD024": false,
|
||||
// FIXME: false positives?
|
||||
//{
|
||||
// // Only check sibling headings
|
||||
// "allow_different_nesting": true,
|
||||
// // Only check sibling headings
|
||||
// "siblings_only": true
|
||||
//},
|
||||
|
||||
// MD025/single-title/single-h1 : Multiple top-level headings in the same document : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md025.md
|
||||
"MD025": {
|
||||
// Heading level
|
||||
"level": 1,
|
||||
// RegExp for matching title in front matter
|
||||
"front_matter_title": "^\\s*title\\s*[:=]"
|
||||
},
|
||||
|
||||
// MD026/no-trailing-punctuation : Trailing punctuation in heading : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md026.md
|
||||
"MD026": {
|
||||
// Punctuation characters
|
||||
"punctuation": ".,;:!。,;:!"
|
||||
},
|
||||
|
||||
// MD027/no-multiple-space-blockquote : Multiple spaces after blockquote symbol : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md027.md
|
||||
"MD027": true,
|
||||
|
||||
// MD028/no-blanks-blockquote : Blank line inside blockquote : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md028.md
|
||||
"MD028": true,
|
||||
|
||||
// MD029/ol-prefix : Ordered list item prefix : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md029.md
|
||||
"MD029": false,
|
||||
// FIXME: false positives or broken formatting
|
||||
//{
|
||||
// // List style
|
||||
// "style": "ordered"
|
||||
//},
|
||||
|
||||
// MD030/list-marker-space : Spaces after list markers : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md030.md
|
||||
"MD030": {
|
||||
// Spaces for single-line unordered list items
|
||||
"ul_single": 1,
|
||||
// Spaces for single-line ordered list items
|
||||
"ol_single": 1,
|
||||
// Spaces for multi-line unordered list items
|
||||
"ul_multi": 1,
|
||||
// Spaces for multi-line ordered list items
|
||||
"ol_multi": 1
|
||||
},
|
||||
|
||||
// MD031/blanks-around-fences : Fenced code blocks should be surrounded by blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md031.md
|
||||
"MD031": {
|
||||
// Include list items
|
||||
"list_items": false
|
||||
},
|
||||
|
||||
// MD032/blanks-around-lists : Lists should be surrounded by blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md032.md
|
||||
"MD032": true,
|
||||
|
||||
// MD033/no-inline-html : Inline HTML : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md033.md
|
||||
"MD033": false,
|
||||
// FIXME: enable and consider fixing
|
||||
//{
|
||||
// // Allowed elements
|
||||
// "allowed_elements": []
|
||||
//},
|
||||
|
||||
// MD034/no-bare-urls : Bare URL used : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md034.md
|
||||
"MD034": true,
|
||||
|
||||
// MD035/hr-style : Horizontal rule style : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md035.md
|
||||
"MD035": {
|
||||
// Horizontal rule style
|
||||
"style": "consistent"
|
||||
},
|
||||
|
||||
// MD036/no-emphasis-as-heading : Emphasis used instead of a heading : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md036.md
|
||||
"MD036": false,
|
||||
// FIXME: enable and consider fixing
|
||||
// {
|
||||
// // Punctuation characters
|
||||
// "punctuation": ".,;:!?。,;:!?"
|
||||
// },
|
||||
|
||||
// MD037/no-space-in-emphasis : Spaces inside emphasis markers : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md037.md
|
||||
"MD037": true,
|
||||
|
||||
// MD038/no-space-in-code : Spaces inside code span elements : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md038.md
|
||||
"MD038": true,
|
||||
|
||||
// MD039/no-space-in-links : Spaces inside link text : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md039.md
|
||||
"MD039": true,
|
||||
|
||||
// MD040/fenced-code-language : Fenced code blocks should have a language specified : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md040.md
|
||||
"MD040": false,
|
||||
// FIXME: enable and consider fixing
|
||||
//{
|
||||
//// List of languages
|
||||
// "allowed_languages": [ "cpp", "json5", "sh" ],
|
||||
//// Require language only
|
||||
// "language_only": true
|
||||
//},
|
||||
|
||||
// MD041/first-line-heading/first-line-h1 : First line in a file should be a top-level heading : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md041.md
|
||||
"MD041": {
|
||||
// Heading level
|
||||
"level": 1,
|
||||
// RegExp for matching title in front matter
|
||||
"front_matter_title": "^\\s*title\\s*[:=]"
|
||||
},
|
||||
|
||||
// MD042/no-empty-links : No empty links : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md042.md
|
||||
"MD042": true,
|
||||
|
||||
// MD043/required-headings : Required heading structure : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md043.md
|
||||
"MD043": false,
|
||||
|
||||
// MD044/proper-names : Proper names should have the correct capitalization : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md044.md
|
||||
"MD044": false,
|
||||
|
||||
// MD045/no-alt-text : Images should have alternate text (alt text) : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md045.md
|
||||
"MD045": false,
|
||||
|
||||
// MD046/code-block-style : Code block style : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md046.md
|
||||
"MD046": {
|
||||
// Block style
|
||||
"style": "fenced"
|
||||
},
|
||||
|
||||
// MD047/single-trailing-newline : Files should end with a single newline character : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md047.md
|
||||
"MD047": true,
|
||||
|
||||
// MD048/code-fence-style : Code fence style : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md048.md
|
||||
"MD048": {
|
||||
// Code fence style
|
||||
"style": "backtick"
|
||||
},
|
||||
|
||||
// MD049/emphasis-style : Emphasis style : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md049.md
|
||||
"MD049": {
|
||||
// Emphasis style
|
||||
"style": "asterisk"
|
||||
},
|
||||
|
||||
// MD050/strong-style : Strong style : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md050.md
|
||||
"MD050": {
|
||||
// Strong style
|
||||
"style": "asterisk"
|
||||
},
|
||||
|
||||
|
||||
|
||||
// MD051/link-fragments : Link fragments should be valid : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md051.md
|
||||
"MD051": true,
|
||||
|
||||
// MD052/reference-links-images : Reference links and images should use a label that is defined : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md052.md
|
||||
"MD052": {
|
||||
// Include shortcut syntax
|
||||
"shortcut_syntax": false
|
||||
},
|
||||
|
||||
// MD053/link-image-reference-definitions : Link and image reference definitions should be needed : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md053.md
|
||||
"MD053": {
|
||||
// Ignored definitions
|
||||
"ignored_definitions": [
|
||||
"//"
|
||||
]
|
||||
},
|
||||
|
||||
// MD054/link-image-style : Link and image style : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md054.md
|
||||
"MD054": {
|
||||
// Allow autolinks
|
||||
"autolink": true,
|
||||
// Allow inline links and images
|
||||
"inline": true,
|
||||
// Allow full reference links and images
|
||||
"full": true,
|
||||
// Allow collapsed reference links and images
|
||||
"collapsed": true,
|
||||
// Allow shortcut reference links and images
|
||||
"shortcut": true,
|
||||
// Allow URLs as inline links
|
||||
"url_inline": true
|
||||
},
|
||||
|
||||
// MD058 - Tables should be surrounded by blank lines
|
||||
"MD058" : true
|
||||
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
RELEASE_TAG="1.2"
|
||||
RELEASE_TAG="1.3"
|
||||
FILENAME="$1"
|
||||
DOWNLOAD_URL="https://github.com/vcmi/vcmi-dependencies/releases/download/$RELEASE_TAG/$FILENAME.txz"
|
||||
|
||||
|
7
CI/install_vcpkg_dependencies.sh
Normal file
7
CI/install_vcpkg_dependencies.sh
Normal file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
RELEASE_TAG="v1.8"
|
||||
FILENAME="dependencies-$1"
|
||||
DOWNLOAD_URL="https://github.com/vcmi/vcmi-deps-windows/releases/download/$RELEASE_TAG/$FILENAME.txz"
|
||||
|
||||
curl -L "$DOWNLOAD_URL" | tar -xf - --xz
|
@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV
|
||||
|
||||
. CI/install_conan_dependencies.sh "dependencies-ios"
|
@ -1 +0,0 @@
|
||||
#!/bin/sh
|
@ -1 +0,0 @@
|
||||
#!/bin/sh
|
@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DEPS_FILENAME=dependencies-mac-arm
|
||||
. CI/mac/before_install.sh
|
@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DEPS_FILENAME=dependencies-mac-intel
|
||||
. CI/mac/before_install.sh
|
@ -1,14 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install ninja-build mingw-w64 nsis
|
||||
sudo update-alternatives --set i686-w64-mingw32-g++ /usr/bin/i686-w64-mingw32-g++-posix
|
||||
|
||||
# Workaround for getting new MinGW headers on Ubuntu 22.04.
|
||||
# Remove it once MinGW headers version in repository will be 10.0 at least
|
||||
curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-common_10.0.0-3_all.deb \
|
||||
&& sudo dpkg -i mingw-w64-common_10.0.0-3_all.deb;
|
||||
curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-i686-dev_10.0.0-3_all.deb \
|
||||
&& sudo dpkg -i mingw-w64-i686-dev_10.0.0-3_all.deb;
|
||||
|
||||
. CI/install_conan_dependencies.sh "dependencies-mingw-32"
|
@ -1,14 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install ninja-build mingw-w64 nsis
|
||||
sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix
|
||||
|
||||
# Workaround for getting new MinGW headers on Ubuntu 22.04.
|
||||
# Remove it once MinGW headers version in repository will be 10.0 at least
|
||||
curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-common_10.0.0-3_all.deb \
|
||||
&& sudo dpkg -i mingw-w64-common_10.0.0-3_all.deb;
|
||||
curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-x86-64-dev_10.0.0-3_all.deb \
|
||||
&& sudo dpkg -i mingw-w64-x86-64-dev_10.0.0-3_all.deb;
|
||||
|
||||
. CI/install_conan_dependencies.sh "dependencies-mingw"
|
@ -1,10 +0,0 @@
|
||||
curl -LfsS -o "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z" \
|
||||
"https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.7/vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z"
|
||||
7z x "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z"
|
||||
|
||||
#rm -r -f vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug
|
||||
#mkdir -p vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin
|
||||
#cp vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/bin/* vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin
|
||||
|
||||
DUMPBIN_DIR=$(vswhere -latest -find **/dumpbin.exe | head -n 1)
|
||||
dirname "$DUMPBIN_DIR" > $GITHUB_PATH
|
@ -1,6 +0,0 @@
|
||||
cd %APPVEYOR_BUILD_FOLDER%
|
||||
cd build_%VCMI_BUILD_PLATFORM%
|
||||
|
||||
cmake --build . --config %VCMI_BUILD_CONFIGURATION% -- /maxcpucount:2
|
||||
|
||||
cpack
|
@ -1,5 +0,0 @@
|
||||
cd %APPVEYOR_BUILD_FOLDER%
|
||||
cd build_%VCMI_BUILD_PLATFORM%
|
||||
|
||||
echo Building with coverity...
|
||||
cov-build.exe --dir cov-int cmake --build . --config %VCMI_BUILD_CONFIGURATION% -- /maxcpucount:2
|
@ -1,17 +0,0 @@
|
||||
7z a "$Env:APPVEYOR_BUILD_FOLDER\$Env:APPVEYOR_PROJECT_NAME.zip" "$Env:APPVEYOR_BUILD_FOLDER\build_$Env:VCMI_BUILD_PLATFORM\cov-int\"
|
||||
|
||||
# cf. http://stackoverflow.com/a/25045154/335418
|
||||
Remove-item alias:curl
|
||||
|
||||
Write-Host "Uploading Coverity analysis result..." -ForegroundColor "Green"
|
||||
|
||||
curl --silent --show-error `
|
||||
--output curl-out.txt `
|
||||
--form token="$Env:coverity_token" `
|
||||
--form email="$Env:coverity_email" `
|
||||
--form "file=@$Env:APPVEYOR_BUILD_FOLDER\$Env:APPVEYOR_PROJECT_NAME.zip" `
|
||||
--form version="$Env:APPVEYOR_REPO_COMMIT" `
|
||||
--form description="CI server scheduled build." `
|
||||
https://scan.coverity.com/builds?project=vcmi%2Fvcmi
|
||||
|
||||
cat .\curl-out.txt
|
@ -180,11 +180,6 @@ else()
|
||||
add_definitions(-DVCMI_NO_EXTRA_VERSION)
|
||||
endif(ENABLE_GITVERSION)
|
||||
|
||||
# Precompiled header configuration
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0 )
|
||||
set(ENABLE_PCH OFF) # broken
|
||||
endif()
|
||||
|
||||
if(ENABLE_PCH)
|
||||
macro(enable_pch name)
|
||||
target_precompile_headers(${name} PRIVATE $<$<COMPILE_LANGUAGE:CXX>:<StdInc.h$<ANGLE-R>>)
|
||||
@ -328,7 +323,6 @@ if(MINGW OR MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4244") # 4244: conversion from 'xxx' to 'yyy', possible loss of data
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267") # 4267: conversion from 'xxx' to 'yyy', possible loss of data
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4275") # 4275: non dll-interface class 'xxx' used as base for dll-interface class
|
||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4800") # 4800: implicit conversion from 'xxx' to bool. Possible information loss
|
||||
|
||||
if(ENABLE_STRICT_COMPILATION)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /WX") # Treats all compiler warnings as errors
|
||||
@ -361,13 +355,6 @@ if(MINGW OR MSVC)
|
||||
if(ICONV_FOUND)
|
||||
set(SYSTEM_LIBS ${SYSTEM_LIBS} iconv)
|
||||
endif()
|
||||
|
||||
# Prevent compiler issues when building Debug
|
||||
# Assembler might fail with "too many sections"
|
||||
# With big-obj or 64-bit build will take hours
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Og")
|
||||
endif()
|
||||
endif(MINGW)
|
||||
endif(MINGW OR MSVC)
|
||||
|
||||
@ -486,25 +473,30 @@ if(NOT FORCE_BUNDLED_MINIZIP)
|
||||
endif()
|
||||
|
||||
if (ENABLE_CLIENT)
|
||||
set(FFMPEG_COMPONENTS avutil swscale avformat avcodec)
|
||||
if(APPLE_IOS AND NOT USING_CONAN)
|
||||
list(APPEND FFMPEG_COMPONENTS swresample)
|
||||
endif()
|
||||
find_package(ffmpeg COMPONENTS ${FFMPEG_COMPONENTS})
|
||||
find_package(ffmpeg COMPONENTS avutil swscale avformat avcodec swresample)
|
||||
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(SDL2_image REQUIRED)
|
||||
if(TARGET SDL2_image::SDL2_image)
|
||||
add_library(SDL2::Image ALIAS SDL2_image::SDL2_image)
|
||||
endif()
|
||||
if(TARGET SDL2_image::SDL2_image-static)
|
||||
add_library(SDL2::Image ALIAS SDL2_image::SDL2_image-static)
|
||||
endif()
|
||||
find_package(SDL2_mixer REQUIRED)
|
||||
if(TARGET SDL2_mixer::SDL2_mixer)
|
||||
add_library(SDL2::Mixer ALIAS SDL2_mixer::SDL2_mixer)
|
||||
endif()
|
||||
if(TARGET SDL2_mixer::SDL2_mixer-static)
|
||||
add_library(SDL2::Mixer ALIAS SDL2_mixer::SDL2_mixer-static)
|
||||
endif()
|
||||
find_package(SDL2_ttf REQUIRED)
|
||||
if(TARGET SDL2_ttf::SDL2_ttf)
|
||||
add_library(SDL2::TTF ALIAS SDL2_ttf::SDL2_ttf)
|
||||
endif()
|
||||
if(TARGET SDL2_ttf::SDL2_ttf-static)
|
||||
add_library(SDL2::TTF ALIAS SDL2_ttf::SDL2_ttf-static)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ENABLE_LOBBY)
|
||||
@ -666,6 +658,10 @@ if(NOT TARGET minizip::minizip)
|
||||
add_library(minizip::minizip ALIAS minizip)
|
||||
endif()
|
||||
|
||||
if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
|
||||
add_subdirectory(vcmiqt)
|
||||
endif()
|
||||
|
||||
if(ENABLE_LAUNCHER)
|
||||
add_subdirectory(launcher)
|
||||
endif()
|
||||
@ -727,7 +723,7 @@ endif()
|
||||
|
||||
if(WIN32)
|
||||
if(TBB_FOUND AND MSVC)
|
||||
install_vcpkg_imported_tgt(TBB::tbb)
|
||||
install_vcpkg_imported_tgt(TBB::tbb)
|
||||
endif()
|
||||
|
||||
if(USING_CONAN)
|
||||
@ -737,7 +733,9 @@ if(WIN32)
|
||||
${dep_files}
|
||||
"${CMAKE_SYSROOT}/bin/*.dll"
|
||||
"${CMAKE_SYSROOT}/lib/*.dll"
|
||||
"${CONAN_SYSTEM_LIBRARY_LOCATION}/*.dll")
|
||||
"${CONAN_SYSTEM_LIBRARY_LOCATION}/libgcc_s_dw2-1.dll" # for 32-bit only?
|
||||
"${CONAN_SYSTEM_LIBRARY_LOCATION}/libgcc_s_seh-1.dll" # for 64-bit only?
|
||||
"${CONAN_SYSTEM_LIBRARY_LOCATION}/libstdc++-6.dll")
|
||||
else()
|
||||
file(GLOB dep_files
|
||||
${dep_files}
|
||||
|
@ -134,7 +134,9 @@
|
||||
"description": "VCMI Windows Ninja using MinGW",
|
||||
"inherits": "default-release",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"CMAKE_C_COMPILER": "gcc",
|
||||
"CMAKE_CXX_COMPILER": "g++"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -154,6 +156,19 @@
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-msvc-release-x86",
|
||||
"displayName": "Windows x86 RelWithDebInfo",
|
||||
"description": "VCMI RelWithDebInfo build",
|
||||
"inherits": "default-release",
|
||||
"generator": "Visual Studio 17 2022",
|
||||
"cacheVariables": {
|
||||
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
|
||||
"CMAKE_POLICY_DEFAULT_CMP0091": "NEW",
|
||||
"FORCE_BUNDLED_MINIZIP": "ON",
|
||||
"CMAKE_GENERATOR_PLATFORM": "WIN32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-msvc-release-ccache",
|
||||
"displayName": "Windows x64 RelWithDebInfo with ccache",
|
||||
@ -382,6 +397,11 @@
|
||||
"configurePreset": "windows-msvc-release",
|
||||
"inherits": "default-release"
|
||||
},
|
||||
{
|
||||
"name": "windows-msvc-release-x86",
|
||||
"configurePreset": "windows-msvc-release-x86",
|
||||
"inherits": "default-release"
|
||||
},
|
||||
{
|
||||
"name": "windows-msvc-release-ccache",
|
||||
"configurePreset": "windows-msvc-release-ccache",
|
||||
|
1664
ChangeLog.md
1664
ChangeLog.md
File diff suppressed because it is too large
Load Diff
5
Global.h
5
Global.h
@ -154,7 +154,10 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
|
||||
#endif
|
||||
#define BOOST_THREAD_DONT_PROVIDE_THREAD_DESTRUCTOR_CALLS_TERMINATE_IF_JOINABLE 1
|
||||
//need to link boost thread dynamically to avoid https://stackoverflow.com/questions/35978572/boost-thread-interupt-does-not-work-when-crossing-a-dll-boundary
|
||||
#define BOOST_THREAD_USE_DLL //for example VCAI::finish() may freeze on thread join after interrupt when linking this statically
|
||||
//for example VCAI::finish() may freeze on thread join after interrupt when linking this statically
|
||||
#ifndef BOOST_THREAD_USE_DLL
|
||||
# define BOOST_THREAD_USE_DLL
|
||||
#endif
|
||||
#define BOOST_BIND_NO_PLACEHOLDERS
|
||||
|
||||
#if BOOST_VERSION >= 106600
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user