1
0
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:
Dydzio 2024-12-03 17:27:24 +01:00
commit e9be46af98
1025 changed files with 27590 additions and 17644 deletions

View File

@ -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.

View File

@ -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'

View File

@ -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)
{

View File

@ -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
}
}

View File

@ -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);

View File

@ -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)

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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);

View File

@ -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();

View File

@ -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;
}

View File

@ -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

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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)));
}
}
}
}
}

View File

@ -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(

View File

@ -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(

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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(

View File

@ -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;

View File

@ -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({

View File

@ -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()
{

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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; }
};
}

View File

@ -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)

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -73,6 +73,7 @@ void RecruitHero::accept(AIGateway * ai)
std::unique_lock lockGuard(ai->nullkiller->aiStateMutex);
ai->nullkiller->heroManager->update();
ai->nullkiller->objectClusterizer->reset();
}
}

View File

@ -44,6 +44,7 @@ namespace Goals
}
std::string toString() const override;
const CGHeroInstance* getHero() const override { return heroToBuy; }
void accept(AIGateway * ai) override;
};
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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"

View File

@ -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)

View File

@ -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();

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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());

View File

@ -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)));

View File

@ -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;

View File

@ -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)

View File

@ -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)
{

View File

@ -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;

View File

@ -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", "" },

View File

@ -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)

View File

@ -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;

View File

@ -1,4 +0,0 @@
#!/usr/bin/env bash
DEPS_FILENAME=dependencies-android-32
. CI/android/before_install.sh

View File

@ -1,4 +0,0 @@
#!/usr/bin/env bash
DEPS_FILENAME=dependencies-android-64
. CI/android/before_install.sh

View File

@ -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"

View File

@ -0,0 +1,4 @@
#!/bin/sh
sudo apt-get update
sudo apt-get install ninja-build

View File

@ -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

View File

@ -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 \

View 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"

View 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
View 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"

View File

@ -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 -%}

View 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
}
}

View File

@ -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"

View 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

View File

@ -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"

View File

@ -1 +0,0 @@
#!/bin/sh

View File

@ -1 +0,0 @@
#!/bin/sh

View File

@ -1,4 +0,0 @@
#!/usr/bin/env bash
DEPS_FILENAME=dependencies-mac-arm
. CI/mac/before_install.sh

View File

@ -1,4 +0,0 @@
#!/usr/bin/env bash
DEPS_FILENAME=dependencies-mac-intel
. CI/mac/before_install.sh

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -1,6 +0,0 @@
cd %APPVEYOR_BUILD_FOLDER%
cd build_%VCMI_BUILD_PLATFORM%
cmake --build . --config %VCMI_BUILD_CONFIGURATION% -- /maxcpucount:2
cpack

View File

@ -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

View File

@ -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

View File

@ -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}

View File

@ -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",

File diff suppressed because it is too large Load Diff

View File

@ -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