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