From 4d34c83c0bad39df8e13147c8a167156e735fbb5 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sat, 15 Apr 2023 11:47:24 +0300 Subject: [PATCH 01/26] Android: building aab for release --- .github/workflows/github.yml | 114 +++++++++++++++++- CI/android/android-release.jks | Bin 0 -> 2241 bytes ...ing.properties => dailySigning.properties} | 0 CI/android/releaseSigning.properties | 2 + android/vcmi-app/build.gradle | 45 ++++--- 5 files changed, 144 insertions(+), 17 deletions(-) create mode 100644 CI/android/android-release.jks rename CI/android/{signing.properties => dailySigning.properties} (100%) create mode 100644 CI/android/releaseSigning.properties diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 42a7c9ef4..4b1a4d876 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -130,7 +130,7 @@ jobs: preset: android-conan-ninja-release conan_profile: android-64 conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT - artifact_platform: aarch64-v8a + artifact_platform: arm64-v8a runs-on: ${{ matrix.os }} defaults: run: @@ -225,6 +225,7 @@ jobs: name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} path: | ${{github.workspace}}/out/build/${{matrix.preset}}/${{ env.VCMI_PACKAGE_FILE_NAME }}.${{ matrix.extension }} + - name: Android artifacts if: ${{ startsWith(matrix.platform, 'android') }} uses: actions/upload-artifact@v3 @@ -233,6 +234,14 @@ jobs: path: | ${{ env.ANDROID_APK_PATH }} + - name: Android JNI ${{matrix.platform}} + if: ${{ startsWith(matrix.platform, 'android') && github.ref == 'refs/heads/master' }} + uses: actions/upload-artifact@v3 + with: + name: Android JNI ${{matrix.platform}} + path: | + ${{ github.workspace }}/android/vcmi-app/src/main/jniLibs + - name: Upload build if: ${{ (matrix.pack == 1 || startsWith(matrix.platform, 'android')) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc' }} continue-on-error: true @@ -254,3 +263,106 @@ jobs: env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} if: always() + + # copy-pasted mostly + bundle_release: + + needs: build + if: always() && github.ref == 'refs/heads/master' + strategy: + matrix: + include: + - platform: android-32 + os: ubuntu-22.04 + extension: aab + preset: android-conan-ninja-release + conan_profile: android-32 + conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT + artifact_platform: aab + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Dependencies + run: source '${{github.workspace}}/CI/${{matrix.platform}}/before_install.sh' + env: + VCMI_BUILD_PLATFORM: x64 + + - uses: actions/setup-python@v4 + if: "${{ matrix.conan_profile != '' }}" + with: + python-version: '3.10' + - name: Conan setup + if: "${{ matrix.conan_profile != '' }}" + run: | + pip3 install 'conan<2.0' + conan profile new default --detect + conan install . \ + --install-folder=conan-generated \ + --no-imports \ + --build=never \ + --profile:build=default \ + --profile:host=CI/conan/${{ matrix.conan_profile }} \ + ${{ matrix.conan_options }} + env: + GENERATE_ONLY_BUILT_CONFIG: 1 + + - name: Git branch name + id: git-branch-name + uses: EthanSK/git-branch-name-action@v1 + + - name: Build Number + run: | + source '${{github.workspace}}/CI/get_package_name.sh' + if [ '${{ matrix.artifact_platform }}' ]; then + VCMI_PACKAGE_FILE_NAME+="-${{ matrix.artifact_platform }}" + fi + echo VCMI_PACKAGE_FILE_NAME="$VCMI_PACKAGE_FILE_NAME" >> $GITHUB_ENV + echo VCMI_PACKAGE_NAME_SUFFIX="$VCMI_PACKAGE_NAME_SUFFIX" >> $GITHUB_ENV + echo VCMI_PACKAGE_GITVERSION="$VCMI_PACKAGE_GITVERSION" >> $GITHUB_ENV + env: + PULL_REQUEST: ${{ github.event.pull_request.number }} + + - name: CMake Preset + run: | + cmake --preset ${{ matrix.preset }} + + - name: Build Preset + run: | + cmake --build --preset ${{matrix.preset}} + + - name: Download libs x64 + uses: actions/download-artifact@v3 + with: + name: Android JNI android-64 + path: ${{ github.workspace }}/android/vcmi-app/src/main/jniLibs/ + + - name: Create android package + run: | + cd android + ./gradlew bundleRelease --info + echo ANDROID_APK_PATH="$(ls ${{ github.workspace }}/android/vcmi-app/build/outputs/bundle/release/*.aab)" >> $GITHUB_ENV + env: + ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }} + ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} + + - name: Android artifacts + uses: actions/upload-artifact@v3 + with: + name: ${{ env.VCMI_PACKAGE_FILE_NAME }} + path: | + ${{ env.ANDROID_APK_PATH }} + + - uses: act10ns/slack@v1 + with: + status: ${{ job.status }} + channel: '#notifications' + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + if: always() diff --git a/CI/android/android-release.jks b/CI/android/android-release.jks new file mode 100644 index 0000000000000000000000000000000000000000..7ff98871cf7c1f1f130241aac496f29137efc6cf GIT binary patch literal 2241 zcmchYc{J1uAI9f53uBG4l%>_*_t zeUo6Ko<#+LU;vT`-9rj;@v3nFP(T$d3;-|?ln9-VmG9P)Z73YoaTqi@VC@x7dek^a z(&01Ija3Qgj7-x-qI(k9k(bkL(R7jlm~abY0iSp}VcF2{z2eanXnLah+vXK7OcKZ2 zxI3NY9O^7ntI@$AT(B1{)D&f&LS*)Ju|qwmqbIW6pB(fa46xD?h4uvlF;!vSwyD0h z9}^$9pxR%Zn3<+b1$(!~=xJ?62u@skQko|9on(rezd!0}krcsLa7gwqKaM*UJgQQ_ zA~(=!?G*)zS#de_hXyomI>MLyhn9P+u=n-n3wR3*>nu#9k0FFc`*&0AH}LsF&xo2u zH>A&3&Mho9=)Zn%{aqJshG<6N6!ENrWQlq%9l32aW{b#j7DOkc-ChAwOiy@*iwu!G zke~=ywOcD*x{;Wq&999yw0S$%l|T2J&9NX@nO+>t7-&6Qd|X_AVp`{s>ET>#`$EGwU#s)e?cu>-!34-1wuu{_VNPb=Y>s7Spzc{ zNn%s4)|Q`1r5B(CecpME+GbFSo*Br$r}n<%0M@;oy9acSeKMmt6?SCj9-S%DpXD}K z%iXNAw0F&fHIy#LnQR~oWo(fQxSv2twJ#8hKi{3w*hj%wl|-}BUl8nDzBr_C!YO(r z3Qt7~4S4U!^hiaTUNv-=@(<6Px_8|xX(+|-9nnY~yL`2iTBa}%bR2J_j4CuhVftdWqGQ;oj1; zoIr;x1J7ZmTh8yPnP#;O{B_{2OYoT4&g+ zN^CPc2#>F-#?zBvSqfl}&Y3-n!CIxnKL51we)8&zk`ZON;Zh1FH7c$$pnp!RH_Xr5 z{n1Dz-0WmZJ^#*}Cw#|GX7jeEWR#BY;dMAHX29kYqWsz}MRQ9eMkjGyG)N-AatL3E z__i_lRrXPKeKG@5!w7Ddd1{t2JvMf(hwwr5=D?N7rb&I%OmIhUzh#js!A7pJ7IVxG z7TwH+M+rMoSg(EVX0qP?NSW>7c>e&BBkL@tF}KMI1F_Tgu#W8fd@dQ(m&` zF27!`R2S)M-fTpcRnjvqGl6+@|HN;6sQSvf|4B5z-$*xw+hriM(M~xmEO6j6;uH z$H(ZYTC!yR`grr9j(nXXA)XFf%cxV%4;g$GN_r<3#T+H~f-^6UX02B-v+TY|HLiv5 z=t#Ms;j(15+NN}7Ed z1%q0*5A(}{R7?j>~nJ&Q1BO&z2@2n2~n5+UJ8 zBKTS%1Pp+|P_vSd9i#vkk7~SS{DuGk@WDXH0F==FVnQ6DT!*+FvHnE@z~&TFi($yOz=7i)Yi+LM#ttWaoH znrNO3C`Waxxe4`n%Ph^Sz;fAGd>!{9!*{tLTD>jP7t28F~<4H3AJH^QjO# z#`$AZN64}LpZ>I@VG{3y2OrJ%d`@W>5?tlUa#gkhuRsTL-zl&kxDiLzJ}C;6nBja` zZGUx}S32%ok;yfRcfu+nVm#M05Y)wGy4O+V&&&ZiTy!x1VKFg^T2vQuXF^@qTj5Cs zmBa~(Ym8W|>_umYeZ#_`y-qL@ zyUhuB9UPgU@_2Yc-gWWF$>zH&W@>H{!IIvqb98aTZxNk0VEMVF;WWN$%;M>#?K=m1 zFB@6rCAY6*N7eYSHU%7sjpr2um#VKcKHm~eN_^Rs*wRYP3!Z91SY9xsf&E1*!%~elI~Ri literal 0 HcmV?d00001 diff --git a/CI/android/signing.properties b/CI/android/dailySigning.properties similarity index 100% rename from CI/android/signing.properties rename to CI/android/dailySigning.properties diff --git a/CI/android/releaseSigning.properties b/CI/android/releaseSigning.properties new file mode 100644 index 000000000..28d2af07c --- /dev/null +++ b/CI/android/releaseSigning.properties @@ -0,0 +1,2 @@ +STORE_FILE=android-release.jks +KEY_ALIAS=vcmi \ No newline at end of file diff --git a/android/vcmi-app/build.gradle b/android/vcmi-app/build.gradle index 4933f7b8f..7fb4c6ff6 100644 --- a/android/vcmi-app/build.gradle +++ b/android/vcmi-app/build.gradle @@ -10,14 +10,16 @@ android { applicationId "is.xyz.vcmi" minSdk 19 targetSdk 31 - versionCode 1103 - versionName "1.1" + versionCode 1200 + versionName "1.2" setProperty("archivesBaseName", "vcmi") } signingConfigs { releaseSigning - LoadSigningConfig() + dailySigning + LoadSigningConfig("releaseSigning") + LoadSigningConfig("dailySigning") } buildTypes { @@ -46,6 +48,7 @@ android { daily { initWith release applicationIdSuffix '.daily' + signingConfig signingConfigs.dailySigning manifestPlaceholders = [ applicationLabel: 'VCMI daily', ] @@ -118,38 +121,48 @@ def ResolveGitInfo() { CommandOutput("git", ["describe", "--match=", "--always", "--abbrev=7"], ".") } -def SigningPropertiesPath(final basePath) { - return file("${basePath}/signing.properties") +def SigningPropertiesPath(final basePath, final signingConfigKey) { + return file("${basePath}/${signingConfigKey}.properties") } def SigningKeystorePath(final basePath, final keystoreFileName) { return file("${basePath}/${keystoreFileName}") } -def LoadSigningConfig() { +def LoadSigningConfig(final signingConfigKey) { final def projectRoot = "${project.projectDir}/../../CI/android" final def props = new Properties() - final def propFile = SigningPropertiesPath(projectRoot) + final def propFile = SigningPropertiesPath(projectRoot, signingConfigKey) + + def signingConfig = android.signingConfigs.getAt(signingConfigKey) + if (propFile.canRead()) { props.load(new FileInputStream(propFile)) if (props != null && props.containsKey('STORE_FILE') - && props.containsKey('STORE_PASSWORD') - && props.containsKey('KEY_ALIAS') - && props.containsKey('KEY_PASSWORD')) { + && props.containsKey('KEY_ALIAS')) { - android.signingConfigs.releaseSigning.storeFile = SigningKeystorePath(projectRoot, props['STORE_FILE']) - android.signingConfigs.releaseSigning.storePassword = props['STORE_PASSWORD'] - android.signingConfigs.releaseSigning.keyAlias = props['KEY_ALIAS'] - android.signingConfigs.releaseSigning.keyPassword = props['KEY_PASSWORD'] + signingConfig.storeFile = SigningKeystorePath(projectRoot, props['STORE_FILE']) + signingConfig.storePassword = props['STORE_PASSWORD'] + signingConfig.keyAlias = props['KEY_ALIAS'] + + if(props.containsKey('STORE_PASSWORD')) + signingConfig.storePassword = props['STORE_PASSWORD'] + else + signingConfig.storePassword = System.getenv("ANDROID_STORE_PASSWORD") + + if(props.containsKey('KEY_PASSWORD')) + signingConfig.keyPassword = props['KEY_PASSWORD'] + else + signingConfig.keyPassword = System.getenv("ANDROID_KEY_PASSWORD") } else { println("Some props from signing file are missing") - android.buildTypes.release.signingConfig = null + android.signingConfigs.putAt(signingConfigKey, null) } } else { println("file with signing properties is missing") - android.buildTypes.release.signingConfig = null + android.signingConfigs.putAt(signingConfigKey, null) } } From 7c6247f4991db7ba021515294c5333dd16427f07 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 15 Apr 2023 20:02:10 +0300 Subject: [PATCH 02/26] Be less strict in detecting languages to catch different versions of H3 --- launcher/languages.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/languages.cpp b/launcher/languages.cpp index cf8e1b4dc..0009ac158 100644 --- a/launcher/languages.cpp +++ b/launcher/languages.cpp @@ -44,7 +44,7 @@ QString Languages::getHeroesDataLanguage() QString language = QString::fromStdString(settings["session"]["language"].String()); double deviation = settings["session"]["languageDeviation"].Float(); - if(deviation > 0.05) + if(deviation > 0.1) return QString(); return language; } From 49538b6e2d6a8dcfcede4c0ca408eceb8bb941af Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 15 Apr 2023 20:02:59 +0300 Subject: [PATCH 03/26] Handle case of user selecting "Data" folder of H3 install After all, we do ask user to select location of Heroes III data... --- launcher/firstLaunch/firstlaunch_moc.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/launcher/firstLaunch/firstlaunch_moc.cpp b/launcher/firstLaunch/firstlaunch_moc.cpp index 52aebfe1b..4f371cc54 100644 --- a/launcher/firstLaunch/firstlaunch_moc.cpp +++ b/launcher/firstLaunch/firstlaunch_moc.cpp @@ -264,6 +264,18 @@ void FirstLaunchView::copyHeroesData() if(!sourceRoot.exists()) return; + if (sourceRoot.dirName().compare("data", Qt::CaseInsensitive) == 0) + { + // We got Data folder. Possibly user selected "Data" folder of Heroes III install. Check whether valid data might exist 1 level above + + QStringList dirData = sourceRoot.entryList({"data"}, QDir::Filter::Dirs); + if (dirData.empty()) + { + // This is "Data" folder without any "Data" folders inside. Try to check for data 1 level above + sourceRoot.cdUp(); + } + } + QStringList dirData = sourceRoot.entryList({"data"}, QDir::Filter::Dirs); QStringList dirMaps = sourceRoot.entryList({"maps"}, QDir::Filter::Dirs); QStringList dirMp3 = sourceRoot.entryList({"mp3"}, QDir::Filter::Dirs); From f9173b556b30205bce3ac49e941691b41087a86e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 15 Apr 2023 20:04:00 +0300 Subject: [PATCH 04/26] Do not block setup if language detection failed --- launcher/firstLaunch/firstlaunch_moc.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/firstLaunch/firstlaunch_moc.cpp b/launcher/firstLaunch/firstlaunch_moc.cpp index 4f371cc54..10253d26e 100644 --- a/launcher/firstLaunch/firstlaunch_moc.cpp +++ b/launcher/firstLaunch/firstlaunch_moc.cpp @@ -190,6 +190,7 @@ void FirstLaunchView::heroesDataMissing() ui->labelDataCopy->setVisible(true); ui->labelDataFound->setVisible(false); + ui->pushButtonDataNext->setEnabled(true); if(hasVCMIBuilderScript) { @@ -218,6 +219,7 @@ void FirstLaunchView::heroesDataDetected() } ui->labelDataFound->setVisible(true); + ui->pushButtonDataNext->setEnabled(true); heroesLanguageUpdate(); } @@ -247,7 +249,6 @@ void FirstLaunchView::heroesLanguageUpdate() ui->labelDataFailure->setVisible(!success); ui->labelDataSuccess->setVisible(success); - ui->pushButtonDataNext->setEnabled(success); } void FirstLaunchView::forceHeroesLanguage(const QString & language) From d94ce22e70e6207994c1bcade3064b9f4c37f440 Mon Sep 17 00:00:00 2001 From: Konstantin P Date: Wed, 19 Apr 2023 12:40:46 +0300 Subject: [PATCH 05/26] vcmi: fix terrain penalties Fixes #2028 --- lib/mapObjects/CGHeroInstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 1726794b6..b2f2c7171 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -76,7 +76,7 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile & dest, const TerrainTile & f !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType->getIndex())) //no special movement bonus { - ret = VLC->heroh->terrCosts[from.terType->getId()]; + ret = VLC->terrainTypeHandler->getById(dest.terType->getId())->moveCost; ret -= ti->valOfBonuses(Bonus::ROUGH_TERRAIN_DISCOUNT); if(ret < GameConstants::BASE_MOVEMENT_COST) ret = GameConstants::BASE_MOVEMENT_COST; From 16bed465b60e8e9d0a076a01190c8545711fb65c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 19 Apr 2023 23:08:00 +0300 Subject: [PATCH 06/26] Bump to versions 1.2.1 --- ChangeLog.md | 5 +++++ cmake_modules/VersionDefinition.cmake | 2 +- debian/changelog | 8 +++++++- launcher/eu.vcmi.VCMI.metainfo.xml | 1 + 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 0f3f0ec46..2a3d2965d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,8 @@ +# 1.2.0 -> 1.2.1 + +### GENERAL: +* Fixed movement cost penalty from terrain + # 1.1.1 -> 1.2.0 ### GENERAL: diff --git a/cmake_modules/VersionDefinition.cmake b/cmake_modules/VersionDefinition.cmake index e66c42bd4..eb75e1797 100644 --- a/cmake_modules/VersionDefinition.cmake +++ b/cmake_modules/VersionDefinition.cmake @@ -1,6 +1,6 @@ set(VCMI_VERSION_MAJOR 1) set(VCMI_VERSION_MINOR 2) -set(VCMI_VERSION_PATCH 0) +set(VCMI_VERSION_PATCH 1) add_definitions( -DVCMI_VERSION_MAJOR=${VCMI_VERSION_MAJOR} -DVCMI_VERSION_MINOR=${VCMI_VERSION_MINOR} diff --git a/debian/changelog b/debian/changelog index f3f6e2b8d..184956dbc 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,9 +1,15 @@ +vcmi (1.2.1) jammy; urgency=medium + + * New upstream release + + -- Ivan Savenko Fri, 28 Apr 2023 16:00:00 +0200 + vcmi (1.2.0) jammy; urgency=medium * New upstream release -- Ivan Savenko Fri, 14 Apr 2023 16:00:00 +0200 - + vcmi (1.1.1) jammy; urgency=medium * New upstream release diff --git a/launcher/eu.vcmi.VCMI.metainfo.xml b/launcher/eu.vcmi.VCMI.metainfo.xml index 7ede06f19..68e43122a 100644 --- a/launcher/eu.vcmi.VCMI.metainfo.xml +++ b/launcher/eu.vcmi.VCMI.metainfo.xml @@ -38,6 +38,7 @@ https://github.com/vcmi/vcmi/issues https://vcmi.eu/faq/ + From f593c2cf6ea5ea42c7e89fc42f9d2325a7cb4c98 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 19 Apr 2023 23:08:21 +0300 Subject: [PATCH 07/26] Fixed handling of utf-8 in code on msvc --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 84fe7a797..f9d2cc2d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -246,6 +246,7 @@ if(MINGW OR MSVC) add_definitions(-D_CRT_SECURE_NO_WARNINGS) add_definitions(-D_SCL_SECURE_NO_WARNINGS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4250") # 4250: 'class1' : inherits 'class2::member' via dominance set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4251") # 4251: class 'xxx' needs to have dll-interface to be used by clients of class 'yyy' From 01af231084d5ed23dfad5571eb633624d56f0cf2 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 19 Apr 2023 23:10:34 +0300 Subject: [PATCH 08/26] Fixed Json conversion on Qt6 --- ChangeLog.md | 2 ++ launcher/firstLaunch/firstlaunch_moc.cpp | 2 +- launcher/jsonutils.cpp | 10 +++++----- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 2a3d2965d..ea15567f8 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -2,6 +2,8 @@ ### GENERAL: * Fixed movement cost penalty from terrain +* Fixed blocked progress in Launcher on language detection failure +* Launcher will now correctly handle selection of Data directory in H3 install # 1.1.1 -> 1.2.0 diff --git a/launcher/firstLaunch/firstlaunch_moc.cpp b/launcher/firstLaunch/firstlaunch_moc.cpp index 10253d26e..2836cf41f 100644 --- a/launcher/firstLaunch/firstlaunch_moc.cpp +++ b/launcher/firstLaunch/firstlaunch_moc.cpp @@ -190,7 +190,7 @@ void FirstLaunchView::heroesDataMissing() ui->labelDataCopy->setVisible(true); ui->labelDataFound->setVisible(false); - ui->pushButtonDataNext->setEnabled(true); + ui->pushButtonDataNext->setEnabled(false); if(hasVCMIBuilderScript) { diff --git a/launcher/jsonutils.cpp b/launcher/jsonutils.cpp index 9b144cb8a..6906da1dc 100644 --- a/launcher/jsonutils.cpp +++ b/launcher/jsonutils.cpp @@ -96,14 +96,14 @@ JsonNode toJson(QVariant object) { JsonNode ret; - if(object.canConvert()) - ret.Struct() = VariantToMap(object.toMap()); - else if(object.canConvert()) - ret.Vector() = VariantToList(object.toList()); - else if(object.userType() == QMetaType::QString) + if(object.userType() == QMetaType::QString) ret.String() = object.toString().toUtf8().data(); else if(object.userType() == QMetaType::Bool) ret.Bool() = object.toBool(); + else if(object.canConvert()) + ret.Struct() = VariantToMap(object.toMap()); + else if(object.canConvert()) + ret.Vector() = VariantToList(object.toList()); else if(object.canConvert()) ret.Integer() = object.toInt(); else if(object.canConvert()) From 5a5aced67e8a1eb3ad5d356a705eaf0313286e84 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 19 Apr 2023 23:11:17 +0300 Subject: [PATCH 09/26] Fixed empty Black Market on game start --- ChangeLog.md | 1 + lib/mapObjects/CGMarket.cpp | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index ea15567f8..651dc3df9 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -2,6 +2,7 @@ ### GENERAL: * Fixed movement cost penalty from terrain +* Fixed empty Black Market on game start * Fixed blocked progress in Launcher on language detection failure * Launcher will now correctly handle selection of Data directory in H3 install diff --git a/lib/mapObjects/CGMarket.cpp b/lib/mapObjects/CGMarket.cpp index 762088784..71204e86c 100644 --- a/lib/mapObjects/CGMarket.cpp +++ b/lib/mapObjects/CGMarket.cpp @@ -279,10 +279,10 @@ void CGBlackMarket::newTurn(CRandomGenerator & rand) const { int resetPeriod = VLC->settings()->getInteger(EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD); - if(resetPeriod == 0) //check if feature changing OH3 behavior is enabled - return; + bool isFirstDay = cb->getDate(Date::DAY) == 1; + bool regularResetTriggered = resetPeriod != 0 && ((cb->getDate(Date::DAY)-1) % resetPeriod) != 0; - if (((cb->getDate(Date::DAY)-1) % resetPeriod) != 0) + if (!isFirstDay && !regularResetTriggered) return; SetAvailableArtifacts saa; From 3d30e04184e4785ff45e5487fa2a9877f27ac8f3 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 19 Apr 2023 23:11:44 +0300 Subject: [PATCH 10/26] Fixed centering of scenario information window --- ChangeLog.md | 1 + client/lobby/CScenarioInfoScreen.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 651dc3df9..c70d5b847 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,7 @@ ### GENERAL: * Fixed movement cost penalty from terrain * Fixed empty Black Market on game start +* Fixed centering of scenario information window * Fixed blocked progress in Launcher on language detection failure * Launcher will now correctly handle selection of Data directory in H3 install diff --git a/client/lobby/CScenarioInfoScreen.cpp b/client/lobby/CScenarioInfoScreen.cpp index a64676a4f..6296f0b18 100644 --- a/client/lobby/CScenarioInfoScreen.cpp +++ b/client/lobby/CScenarioInfoScreen.cpp @@ -26,6 +26,10 @@ CScenarioInfoScreen::CScenarioInfoScreen() { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + pos.w = 800; + pos.h = 600; + pos = center(); + localSi = new StartInfo(*LOCPLINT->cb->getStartInfo()); localMi = new CMapInfo(); localMi->mapHeader = std::unique_ptr(new CMapHeader(*LOCPLINT->cb->getMapHeader())); From b517adfb250f84f13fe381dd7a2195d7c7430074 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 19 Apr 2023 23:12:30 +0300 Subject: [PATCH 11/26] Fixed crash on empty save game list after filtering --- ChangeLog.md | 1 + client/lobby/CSavingScreen.cpp | 7 +++---- client/lobby/CSavingScreen.h | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index c70d5b847..69b2b9d66 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -4,6 +4,7 @@ * Fixed movement cost penalty from terrain * Fixed empty Black Market on game start * Fixed centering of scenario information window +* Fixed crash on empty save game list after filtering * Fixed blocked progress in Launcher on language detection failure * Launcher will now correctly handle selection of Data directory in H3 install diff --git a/client/lobby/CSavingScreen.cpp b/client/lobby/CSavingScreen.cpp index daf154349..59741d916 100644 --- a/client/lobby/CSavingScreen.cpp +++ b/client/lobby/CSavingScreen.cpp @@ -31,8 +31,6 @@ CSavingScreen::CSavingScreen() { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; center(pos); - // TODO: we should really use std::shared_ptr for passing StartInfo around. - localSi = new StartInfo(*LOCPLINT->cb->getStartInfo()); localMi = std::make_shared(); localMi->mapHeader = std::unique_ptr(new CMapHeader(*LOCPLINT->cb->getMapHeader())); @@ -52,7 +50,9 @@ const CMapInfo * CSavingScreen::getMapInfo() const StartInfo * CSavingScreen::getStartInfo() { - return localSi; + if (localMi) + return localMi->scenarioOptionsOfSave; + return LOCPLINT->cb->getStartInfo(); } void CSavingScreen::changeSelection(std::shared_ptr to) @@ -61,7 +61,6 @@ void CSavingScreen::changeSelection(std::shared_ptr to) return; localMi = to; - localSi = localMi->scenarioOptionsOfSave; card->changeSelection(); } diff --git a/client/lobby/CSavingScreen.h b/client/lobby/CSavingScreen.h index b7ac05012..e99809407 100644 --- a/client/lobby/CSavingScreen.h +++ b/client/lobby/CSavingScreen.h @@ -23,7 +23,6 @@ class CSelectionBase; class CSavingScreen : public CSelectionBase { public: - const StartInfo * localSi; std::shared_ptr localMi; CSavingScreen(); From b3207f346556f106208b06a793b1eaea94e6d9bf Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 19 Apr 2023 23:14:23 +0300 Subject: [PATCH 12/26] RMG tab will now show all templates but will block invalid map sizes --- ChangeLog.md | 2 ++ client/lobby/RandomMapTab.cpp | 26 +++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 69b2b9d66..4a0640ef3 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,8 @@ ### GENERAL: * Fixed movement cost penalty from terrain * Fixed empty Black Market on game start +* RMG settings will now show all existing in game templates and not just those suitable for current settings +* RMG settings (map size and two-level maps) that are not compatible with current template will be blocked * Fixed centering of scenario information window * Fixed crash on empty save game list after filtering * Fixed blocked progress in Launcher on language detection failure diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index 339e838df..9c90f5157 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -242,9 +242,29 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr opts) } if(auto w = widget("groupMapSize")) + { + for(auto toggle : w->buttons) + { + if(auto button = std::dynamic_pointer_cast(toggle.second)) + { + const auto & mapSizes = getPossibleMapSizes(); + int3 size( mapSizes[toggle.first], mapSizes[toggle.first], 1 + mapGenOptions->getHasTwoLevels()); + + bool sizeAllowed = !mapGenOptions->getMapTemplate() || mapGenOptions->getMapTemplate()->matchesSize(size); + button->block(!sizeAllowed); + } + } w->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth())); + } if(auto w = widget("buttonTwoLevels")) + { + int3 size( opts->getWidth(), opts->getWidth(), 2); + + bool undergoundAllowed = !mapGenOptions->getMapTemplate() || mapGenOptions->getMapTemplate()->matchesSize(size); + w->setSelected(opts->getHasTwoLevels()); + w->block(!undergoundAllowed); + } if(auto w = widget("groupMaxPlayers")) { w->setSelected(opts->getPlayerCount()); @@ -408,7 +428,11 @@ TemplatesDropBox::TemplatesDropBox(RandomMapTab & randomMapTab, int3 size): REGISTER_BUILDER("templateListItem", &TemplatesDropBox::buildListItem); curItems = VLC->tplh->getTemplates(); - vstd::erase_if(curItems, [size](const CRmgTemplate * t){return !t->matchesSize(size);}); + + boost::range::sort(curItems, [](const CRmgTemplate * a, const CRmgTemplate * b){ + return a->getName() < b->getName(); + }); + curItems.insert(curItems.begin(), nullptr); //default template const JsonNode config(ResourceID("config/widgets/randomMapTemplateWidget.json")); From bfed62d3a26ffd48500a55e9fd0392a7e326f73c Mon Sep 17 00:00:00 2001 From: Necr0o0 Date: Wed, 19 Apr 2023 11:09:00 +0200 Subject: [PATCH 13/26] Fixed #2022 storing event in map editor Fixing issue "Event message isn't stored #2022" by adding message in Pandora setProperty ( cause CGEvent inherits CGPandoraBox) --- mapeditor/inspector/inspector.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 74a3aa836..a4bbb62ad 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -472,6 +472,9 @@ void Inspector::setProperty(CGLighthouse * o, const QString & key, const QVarian void Inspector::setProperty(CGPandoraBox * o, const QString & key, const QVariant & value) { if(!o) return; + + if(key == "Message") + o->message = value.toString().toStdString(); } void Inspector::setProperty(CGEvent * o, const QString & key, const QVariant & value) From d9939f0918b2303266a4a4df26f4033b4df0dcf9 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 20 Apr 2023 13:08:55 +0300 Subject: [PATCH 14/26] Bad morale can no longer happen after waiting --- server/CGameHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index cf0ed56d5..0058a244b 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -6600,9 +6600,9 @@ void CGameHandler::runBattle() if(!removeGhosts.changedStacks.empty()) sendAndApply(&removeGhosts); - //check for bad morale => freeze + // check for bad morale => freeze int nextStackMorale = next->MoraleVal(); - if (nextStackMorale < 0) + if(!next->hadMorale && !next->waited() && nextStackMorale < 0) { auto diceSize = VLC->settings()->getVector(EGameSettings::COMBAT_BAD_MORALE_DICE); size_t diceIndex = std::min(diceSize.size()-1, -nextStackMorale); From 9882c37ce3f3c1293209a0ead5d510924963b0a0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 20 Apr 2023 13:10:03 +0300 Subject: [PATCH 15/26] Good morale can no longer happen after battle is over --- ChangeLog.md | 2 ++ server/CGameHandler.cpp | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 4a0640ef3..cce97429c 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,8 @@ ### GENERAL: * Fixed movement cost penalty from terrain * Fixed empty Black Market on game start +* Fixed bad morale happening after waiting +* Fixed good morale happening after defeating last enemy unit * RMG settings will now show all existing in game templates and not just those suitable for current settings * RMG settings (map size and two-level maps) that are not compatible with current template will be blocked * Fixed centering of scenario information window diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 0058a244b..1ece80214 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -6788,12 +6788,13 @@ void CGameHandler::runBattle() { //check for good morale nextStackMorale = next->MoraleVal(); - if(!next->hadMorale //only one extra move per turn possible + if( !battleResult.get() + && !next->hadMorale && !next->defending && !next->waited() && !next->fear - && next->alive() - && nextStackMorale > 0) + && next->alive() + && nextStackMorale > 0) { auto diceSize = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE); size_t diceIndex = std::min(diceSize.size()-1, nextStackMorale); From 4dc16b9ff7e3e03bd83f41245128f52f21ed3741 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 20 Apr 2023 13:16:45 +0300 Subject: [PATCH 16/26] Adventure map spells are no longer visible on units in battle --- ChangeLog.md | 1 + lib/CStack.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index cce97429c..11e9942eb 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -5,6 +5,7 @@ * Fixed empty Black Market on game start * Fixed bad morale happening after waiting * Fixed good morale happening after defeating last enemy unit +* Adventure map spells are no longer visible on units in battle * RMG settings will now show all existing in game templates and not just those suitable for current settings * RMG settings (map size and two-level maps) that are not compatible with current template will be blocked * Fixed centering of scenario information window diff --git a/lib/CStack.cpp b/lib/CStack.cpp index 44439c615..d51d0325b 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -133,7 +133,7 @@ std::vector CStack::activeSpells() const CSelector selector = Selector::sourceType()(Bonus::SPELL_EFFECT) .And(CSelector([](const Bonus * b)->bool { - return b->type != Bonus::NONE; + return b->type != Bonus::NONE && SpellID(b->sid).toSpell() && !SpellID(b->sid).toSpell()->isAdventure(); })); TConstBonusListPtr spellEffects = getBonuses(selector, Selector::all, cachingStr.str()); From 5c9ae8aafc5f11cfec27f198b8d30121955fab2a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 20 Apr 2023 14:15:08 +0300 Subject: [PATCH 17/26] Implemented spell range overlay for Dimension Door and Scuttle Boat --- ChangeLog.md | 1 + Mods/vcmi/Data/debug/cached.png | Bin 96 -> 0 bytes Mods/vcmi/Data/debug/spellRange.png | Bin 0 -> 96 bytes client/adventureMap/CAdvMapInt.cpp | 16 ++++++++++++++-- client/mapView/IMapRendererContext.h | 4 ++++ client/mapView/MapRenderer.cpp | 17 ++++++++++++----- client/mapView/MapRenderer.h | 15 +++------------ client/mapView/MapRendererContext.cpp | 16 ++++++++++++++++ client/mapView/MapRendererContext.h | 4 ++++ client/mapView/MapViewController.cpp | 1 + 10 files changed, 55 insertions(+), 19 deletions(-) delete mode 100644 Mods/vcmi/Data/debug/cached.png create mode 100644 Mods/vcmi/Data/debug/spellRange.png diff --git a/ChangeLog.md b/ChangeLog.md index 11e9942eb..3845f9ed2 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,7 @@ # 1.2.0 -> 1.2.1 ### GENERAL: +* Implemented spell range overlay for Dimension Door and Scuttle Boat * Fixed movement cost penalty from terrain * Fixed empty Black Market on game start * Fixed bad morale happening after waiting diff --git a/Mods/vcmi/Data/debug/cached.png b/Mods/vcmi/Data/debug/cached.png deleted file mode 100644 index 7f7dbefae3fc08e885548dfaea9f9998bba42588..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnL3?x0byx0z;m;-!5T!HlRD%)E?im@cfFPOv6 nHXsDZ;r4WK45?sDmUsf>EMQ;^(VBN0$YStx^>bP0l+XkKU@;U( diff --git a/Mods/vcmi/Data/debug/spellRange.png b/Mods/vcmi/Data/debug/spellRange.png new file mode 100644 index 0000000000000000000000000000000000000000..4618c58c0098a5ad0b8c10d58e8318ad7c71af4b GIT binary patch literal 96 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnL3?x0byx0z;m;-!5T!HlRD%)E?im@cfFSucC nT(ldI!|mzf7*fHQEb#=$S-`*;qBZY0kj3EX>gTe~DWM4fh3^$T literal 0 HcmV?d00001 diff --git a/client/adventureMap/CAdvMapInt.cpp b/client/adventureMap/CAdvMapInt.cpp index e9f1d7b4a..b1f2ca435 100644 --- a/client/adventureMap/CAdvMapInt.cpp +++ b/client/adventureMap/CAdvMapInt.cpp @@ -1076,8 +1076,11 @@ void CAdvMapInt::onTileLeftClicked(const int3 &mapPos) const CGObjectInstance *topBlocking = getActiveObject(mapPos); int3 selPos = selection->getSightCenter(); - if(spellBeingCasted && isInScreenRange(selPos, mapPos)) + if(spellBeingCasted) { + if (!isInScreenRange(selPos, mapPos)) + return; + const TerrainTile *heroTile = LOCPLINT->cb->getTile(selPos); switch(spellBeingCasted->id) @@ -1179,11 +1182,15 @@ void CAdvMapInt::onTileHovered(const int3 &mapPos) switch(spellBeingCasted->id) { case SpellID::SCUTTLE_BOAT: - if(objAtTile && objAtTile->ID == Obj::BOAT) + { + int3 hpos = selection->getSightCenter(); + + if(objAtTile && objAtTile->ID == Obj::BOAT && isInScreenRange(hpos, mapPos)) CCS->curh->set(Cursor::Map::SCUTTLE_BOAT); else CCS->curh->set(Cursor::Map::POINTER); return; + } case SpellID::DIMENSION_DOOR: { const TerrainTile * t = LOCPLINT->cb->getTile(mapPos, false); @@ -1342,6 +1349,8 @@ void CAdvMapInt::enterCastingMode(const CSpell * sp) { assert(sp->id == SpellID::SCUTTLE_BOAT || sp->id == SpellID::DIMENSION_DOOR); spellBeingCasted = sp; + Settings config = settings.write["session"]["showSpellRange"]; + config->Bool() = true; deactivate(); terrain->activate(); @@ -1356,6 +1365,9 @@ void CAdvMapInt::leaveCastingMode(bool cast, int3 dest) terrain->deactivate(); activate(); + Settings config = settings.write["session"]["showSpellRange"]; + config->Bool() = false; + if(cast) LOCPLINT->cb->castSpell(curHero(), id, dest); else diff --git a/client/mapView/IMapRendererContext.h b/client/mapView/IMapRendererContext.h index 4f5cf7917..049204c08 100644 --- a/client/mapView/IMapRendererContext.h +++ b/client/mapView/IMapRendererContext.h @@ -86,4 +86,8 @@ public: virtual bool showGrid() const = 0; virtual bool showVisitable() const = 0; virtual bool showBlocked() const = 0; + + /// if true, spell range for teleport / scuttle boat will be visible + virtual bool showSpellRange(const int3 & position) const = 0; + }; diff --git a/client/mapView/MapRenderer.cpp b/client/mapView/MapRenderer.cpp index 86d67dcff..9b89e4937 100644 --- a/client/mapView/MapRenderer.cpp +++ b/client/mapView/MapRenderer.cpp @@ -584,15 +584,16 @@ uint8_t MapRendererObjects::checksum(IMapRendererContext & context, const int3 & return 0xff-1; } -MapRendererDebug::MapRendererDebug() +MapRendererOverlay::MapRendererOverlay() : imageGrid(IImage::createFromFile("debug/grid", EImageBlitMode::ALPHA)) , imageBlocked(IImage::createFromFile("debug/blocked", EImageBlitMode::ALPHA)) , imageVisitable(IImage::createFromFile("debug/visitable", EImageBlitMode::ALPHA)) + , imageSpellRange(IImage::createFromFile("debug/spellRange", EImageBlitMode::ALPHA)) { } -void MapRendererDebug::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates) +void MapRendererOverlay::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates) { if(context.showGrid()) target.draw(imageGrid, Point(0,0)); @@ -618,9 +619,12 @@ void MapRendererDebug::renderTile(IMapRendererContext & context, Canvas & target if (context.showVisitable() && visitable) target.draw(imageVisitable, Point(0,0)); } + + if (context.showSpellRange(coordinates)) + target.draw(imageSpellRange, Point(0,0)); } -uint8_t MapRendererDebug::checksum(IMapRendererContext & context, const int3 & coordinates) +uint8_t MapRendererOverlay::checksum(IMapRendererContext & context, const int3 & coordinates) { uint8_t result = 0; @@ -633,6 +637,9 @@ uint8_t MapRendererDebug::checksum(IMapRendererContext & context, const int3 & c if (context.showGrid()) result += 4; + if (context.showSpellRange(coordinates)) + result += 8; + return result; } @@ -766,7 +773,7 @@ MapRenderer::TileChecksum MapRenderer::getTileChecksum(IMapRendererContext & con result[3] = rendererRoad.checksum(context, coordinates); result[4] = rendererObjects.checksum(context, coordinates); result[5] = rendererPath.checksum(context, coordinates); - result[6] = rendererDebug.checksum(context, coordinates); + result[6] = rendererOverlay.checksum(context, coordinates); if(!context.isVisible(coordinates)) result[7] = rendererFow.checksum(context, coordinates); @@ -800,7 +807,7 @@ void MapRenderer::renderTile(IMapRendererContext & context, Canvas & target, con rendererObjects.renderTile(context, target, coordinates); rendererPath.renderTile(context, target, coordinates); - rendererDebug.renderTile(context, target, coordinates); + rendererOverlay.renderTile(context, target, coordinates); if(!context.isVisible(coordinates)) rendererFow.renderTile(context, target, coordinates); diff --git a/client/mapView/MapRenderer.h b/client/mapView/MapRenderer.h index c1fb78d86..2ee036f88 100644 --- a/client/mapView/MapRenderer.h +++ b/client/mapView/MapRenderer.h @@ -129,21 +129,12 @@ public: void renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates); }; -class MapRendererDebug +class MapRendererOverlay { std::shared_ptr imageGrid; std::shared_ptr imageVisitable; std::shared_ptr imageBlocked; -public: - MapRendererDebug(); - - uint8_t checksum(IMapRendererContext & context, const int3 & coordinates); - void renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates); -}; - -class MapRendererOverlay -{ - std::unique_ptr iconsStorage; + std::shared_ptr imageSpellRange; public: MapRendererOverlay(); @@ -160,7 +151,7 @@ class MapRenderer MapRendererFow rendererFow; MapRendererObjects rendererObjects; MapRendererPath rendererPath; - MapRendererDebug rendererDebug; + MapRendererOverlay rendererOverlay; public: using TileChecksum = std::array; diff --git a/client/mapView/MapRendererContext.cpp b/client/mapView/MapRendererContext.cpp index 4e3c95d5b..d885b02f6 100644 --- a/client/mapView/MapRendererContext.cpp +++ b/client/mapView/MapRendererContext.cpp @@ -22,6 +22,7 @@ #include "../../lib/CPathfinder.h" #include "../../lib/Point.h" #include "../../lib/mapObjects/CGHeroInstance.h" +#include "../../lib/spells/CSpellHandler.h" #include "../../lib/mapping/CMap.h" MapRendererBaseContext::MapRendererBaseContext(const MapRendererContextState & viewState) @@ -199,6 +200,11 @@ bool MapRendererBaseContext::showBlocked() const return false; } +bool MapRendererBaseContext::showSpellRange(const int3 & position) const +{ + return false; +} + MapRendererAdventureContext::MapRendererAdventureContext(const MapRendererContextState & viewState) : MapRendererBaseContext(viewState) { @@ -266,6 +272,16 @@ bool MapRendererAdventureContext::showBlocked() const return settingShowBlocked; } +bool MapRendererAdventureContext::showSpellRange(const int3 & position) const +{ + if (!settingSpellRange) + return false; + + auto hero = adventureInt->curHero(); + + return !isInScreenRange(hero->getSightCenter(), position); +} + MapRendererAdventureTransitionContext::MapRendererAdventureTransitionContext(const MapRendererContextState & viewState) : MapRendererAdventureContext(viewState) { diff --git a/client/mapView/MapRendererContext.h b/client/mapView/MapRendererContext.h index 0a299fbac..ee8061535 100644 --- a/client/mapView/MapRendererContext.h +++ b/client/mapView/MapRendererContext.h @@ -58,6 +58,7 @@ public: bool showGrid() const override; bool showVisitable() const override; bool showBlocked() const override; + bool showSpellRange(const int3 & position) const override; }; class MapRendererAdventureContext : public MapRendererBaseContext @@ -67,6 +68,7 @@ public: bool settingShowGrid = false; bool settingShowVisitable = false; bool settingShowBlocked = false; + bool settingSpellRange= false; bool settingsAdventureObjectAnimation = true; bool settingsAdventureTerrainAnimation = true; @@ -80,6 +82,8 @@ public: bool showGrid() const override; bool showVisitable() const override; bool showBlocked() const override; + + bool showSpellRange(const int3 & position) const override; }; class MapRendererAdventureTransitionContext : public MapRendererAdventureContext diff --git a/client/mapView/MapViewController.cpp b/client/mapView/MapViewController.cpp index 2f0fd06bd..ced9e1d80 100644 --- a/client/mapView/MapViewController.cpp +++ b/client/mapView/MapViewController.cpp @@ -154,6 +154,7 @@ void MapViewController::updateBefore(uint32_t timeDelta) adventureContext->settingShowGrid = settings["gameTweaks"]["showGrid"].Bool(); adventureContext->settingShowVisitable = settings["session"]["showVisitable"].Bool(); adventureContext->settingShowBlocked = settings["session"]["showBlocked"].Bool(); + adventureContext->settingSpellRange = settings["session"]["showSpellRange"].Bool(); } } From 6df85be4b3bde68a51524e1f265cf537bfadcf0f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 20 Apr 2023 18:12:10 +0300 Subject: [PATCH 18/26] Fixed death animation of Efreeti killed by petrification attack --- ChangeLog.md | 1 + client/battle/BattleAnimationClasses.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 3845f9ed2..99bdd07bc 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,7 @@ * Fixed empty Black Market on game start * Fixed bad morale happening after waiting * Fixed good morale happening after defeating last enemy unit +* Fixed death animation of Efreeti killed by petrification attack * Adventure map spells are no longer visible on units in battle * RMG settings will now show all existing in game templates and not just those suitable for current settings * RMG settings (map size and two-level maps) that are not compatible with current template will be blocked diff --git a/client/battle/BattleAnimationClasses.cpp b/client/battle/BattleAnimationClasses.cpp index 96ffffbd4..dbb7c1705 100644 --- a/client/battle/BattleAnimationClasses.cpp +++ b/client/battle/BattleAnimationClasses.cpp @@ -137,7 +137,7 @@ bool StackActionAnimation::init() StackActionAnimation::~StackActionAnimation() { - if (stack->isFrozen()) + if (stack->isFrozen() && currGroup != ECreatureAnimType::DEATH && currGroup != ECreatureAnimType::DEATH_RANGED) myAnim->setType(ECreatureAnimType::HOLDING); else myAnim->setType(nextGroup); From f245080cba7a52a3242e50f8f7c5e236383bc092 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 20 Apr 2023 19:24:13 +0300 Subject: [PATCH 19/26] Fix missing error message on casting spell in hotseat --- ChangeLog.md | 3 ++- client/windows/CSpellWindow.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 99bdd07bc..93f0b7c9a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -8,12 +8,13 @@ * Fixed good morale happening after defeating last enemy unit * Fixed death animation of Efreeti killed by petrification attack * Adventure map spells are no longer visible on units in battle +* Attempt to cast spell with no valid targets in hotseat will show appropriate error message * RMG settings will now show all existing in game templates and not just those suitable for current settings * RMG settings (map size and two-level maps) that are not compatible with current template will be blocked * Fixed centering of scenario information window * Fixed crash on empty save game list after filtering * Fixed blocked progress in Launcher on language detection failure -* Launcher will now correctly handle selection of Data directory in H3 install +* Launcher will now correctly handle selection of Ddata directory in H3 install # 1.1.1 -> 1.2.0 diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index 6150ab154..1bc1327e9 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -509,7 +509,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState) auto spellCost = owner->myInt->cb->getSpellCost(mySpell, owner->myHero); if(spellCost > owner->myHero->mana) //insufficient mana { - owner->myInt->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[206]) % spellCost % owner->myHero->mana)); + LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[206]) % spellCost % owner->myHero->mana)); return; } @@ -529,7 +529,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState) if((combatSpell ^ inCombat) || inCastle) { std::vector> hlp(1, std::make_shared(CComponent::spell, mySpell->id, 0)); - owner->myInt->showInfoDialog(mySpell->getDescriptionTranslated(schoolLevel), hlp); + LOCPLINT->showInfoDialog(mySpell->getDescriptionTranslated(schoolLevel), hlp); } else if(combatSpell) { @@ -544,9 +544,9 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState) std::vector texts; problem.getAll(texts); if(!texts.empty()) - owner->myInt->showInfoDialog(texts.front()); + LOCPLINT->showInfoDialog(texts.front()); else - owner->myInt->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.spellUnknownProblem")); + LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.spellUnknownProblem")); } } else //adventure spell From 24c6587072d3c703a3465aa64271b42f9dc37ad8 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 20 Apr 2023 19:56:35 +0300 Subject: [PATCH 20/26] Fixed crash on leaving to main menu from battle in hotseat mode --- ChangeLog.md | 1 + client/Client.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 93f0b7c9a..3f6982b8b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -7,6 +7,7 @@ * Fixed bad morale happening after waiting * Fixed good morale happening after defeating last enemy unit * Fixed death animation of Efreeti killed by petrification attack +* Fixed crash on leaving to main menu from battle in hotseat mode * Adventure map spells are no longer visible on units in battle * Attempt to cast spell with no valid targets in hotseat will show appropriate error message * RMG settings will now show all existing in game templates and not just those suitable for current settings diff --git a/client/Client.cpp b/client/Client.cpp index 686395719..1160a00d1 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -374,6 +374,7 @@ void CClient::endGame() //threads cleanup has to be after gs cleanup and before battleints cleanup to stop tacticThread cleanThreads(); + CPlayerInterface::battleInt.reset(); playerint.clear(); battleints.clear(); battleCallbacks.clear(); From eff158a9a815c9d35197c5fa361c8ee21b0166d7 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sun, 23 Apr 2023 11:38:33 +0300 Subject: [PATCH 21/26] Fix killing hero with grail --- server/CGameHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index cf0ed56d5..41e218e29 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -789,12 +789,12 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance * heroAttacker, con sendMoveArtifact(art, &ma); } } - while(!finishingBattle->loserHero->artifactsInBackpack.empty()) + for(int slotNumber = finishingBattle->loserHero->artifactsInBackpack.size() - 1; slotNumber >= 0; slotNumber--) { //we assume that no big artifacts can be found MoveArtifact ma; ma.src = ArtifactLocation(finishingBattle->loserHero, - ArtifactPosition(GameConstants::BACKPACK_START)); //backpack automatically shifts arts to beginning + ArtifactPosition(GameConstants::BACKPACK_START + slotNumber)); //backpack automatically shifts arts to beginning const CArtifactInstance * art = ma.src.getArt(); if (art->artType->getId() != ArtifactID::GRAIL) //grail may not be won { From d2530f66bf4e86c300b7aaa207dc3e9da2bdd9c9 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sat, 22 Apr 2023 18:37:27 +0300 Subject: [PATCH 22/26] #2018 - fix shipyard usage for NKAI, allied shipyard should work now --- AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp | 2 +- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 14 +++++++------- AI/Nullkiller/Engine/Nullkiller.cpp | 2 +- AI/Nullkiller/Engine/Nullkiller.h | 1 + .../Pathfinding/Rules/AILayerTransitionRule.cpp | 6 ++---- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp index 8717900b6..e63e26c3a 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp @@ -209,7 +209,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const { captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects()); - if(tasks.empty()) + if(tasks.empty() || ai->nullkiller->getScanDepth() == ScanDepth::FULL) captureObjects(ai->nullkiller->objectClusterizer->getFarObjects()); } diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index 78c8fe261..6d1a51a92 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -58,13 +58,6 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town); auto treats = { treatNode.maximumDanger, treatNode.fastestDanger }; - if(!treatNode.fastestDanger.hero) - { - logAi->trace("No treat found for town %s", town->getNameTranslated()); - - return; - } - int dayOfWeek = cb->getDate(Date::DAY_OF_WEEK); if(town->garrisonHero) @@ -91,6 +84,13 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta return; } + + if(!treatNode.fastestDanger.hero) + { + logAi->trace("No treat found for town %s", town->getNameTranslated()); + + return; + } uint64_t reinforcement = ai->nullkiller->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town); diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index b4e85fc97..c5aa3324f 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -117,7 +117,7 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositi void Nullkiller::resetAiState() { lockedResources = TResources(); - scanDepth = ScanDepth::SMALL; + scanDepth = ScanDepth::FULL; playerID = ai->playerID; lockedHeroes.clear(); dangerHitMap->reset(); diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index fb3bb5cc1..c28529386 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -88,6 +88,7 @@ public: int32_t getFreeGold() const { return getFreeResources()[Res::GOLD]; } void lockResources(const TResources & res); const TResources & getLockedResources() const { return lockedResources; } + ScanDepth getScanDepth() const { return scanDepth; } private: void resetAiState(); diff --git a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp index bce2a4284..f3143a239 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp @@ -53,15 +53,13 @@ namespace AIPathfinding for(const CGTownInstance * t : cb->getTownsInfo()) { - // do not allow ally shipyards because of bug - if(t->hasBuilt(BuildingID::SHIPYARD) && t->getOwner() == ai->playerID) + if(t->hasBuilt(BuildingID::SHIPYARD)) shipyards.push_back(t); } for(const CGObjectInstance * obj : ai->memory->visitableObjs) { - // do not allow ally shipyards because of bug - if(obj->ID != Obj::TOWN && obj->getOwner() == ai->playerID) //towns were handled in the previous loop + if(obj->ID != Obj::TOWN) //towns were handled in the previous loop { if(const IShipyard * shipyard = IShipyard::castFrom(obj)) shipyards.push_back(shipyard); From f2506dd196e6c47108b6f9c915802ca0290b23c5 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sat, 22 Apr 2023 14:47:02 +0300 Subject: [PATCH 23/26] Fix hero count calculation for resourceful ai mod --- AI/Nullkiller/AIGateway.cpp | 40 ---------------- AI/Nullkiller/AIGateway.h | 5 -- AI/Nullkiller/Analyzers/HeroManager.cpp | 47 +++++++++++++++++++ AI/Nullkiller/Analyzers/HeroManager.h | 8 +++- .../Behaviors/RecruitHeroBehavior.cpp | 2 +- AI/Nullkiller/Behaviors/StartupBehavior.cpp | 2 +- AI/Nullkiller/Goals/RecruitHero.cpp | 2 - AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 2 +- 8 files changed, 57 insertions(+), 51 deletions(-) diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 516a33514..b27f75fb0 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -1059,27 +1059,6 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re } } -bool AIGateway::canRecruitAnyHero(const CGTownInstance * t) const -{ - //TODO: make gathering gold, building tavern or conquering town (?) possible subgoals - if(!t) - t = findTownWithTavern(); - - if(!t || !townHasFreeTavern(t)) - return false; - - if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST) //TODO: use ResourceManager - return false; - if(cb->getHeroesInfo().size() >= ALLOWED_ROAMING_HEROES) - return false; - if(cb->getHeroesInfo().size() >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)) - return false; - if(!cb->getAvailableHeroes(t).size()) - return false; - - return true; -} - void AIGateway::battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side) { NET_EVENT_HANDLER; @@ -1155,16 +1134,6 @@ void AIGateway::addVisitableObj(const CGObjectInstance * obj) } } -HeroPtr AIGateway::getHeroWithGrail() const -{ - for(const CGHeroInstance * h : cb->getHeroesInfo()) - { - if(h->hasArt(ArtifactID::GRAIL)) - return h; - } - return nullptr; -} - bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h) { if(h->inTownGarrison && h->visitedTown) @@ -1432,15 +1401,6 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade } } -const CGTownInstance * AIGateway::findTownWithTavern() const -{ - for(const CGTownInstance * t : cb->getTownsInfo()) - if(townHasFreeTavern(t)) - return t; - - return nullptr; -} - void AIGateway::endTurn() { logAi->info("Player %d (%s) ends turn", playerID, playerID.getStr()); diff --git a/AI/Nullkiller/AIGateway.h b/AI/Nullkiller/AIGateway.h index 1443392f3..489aecf9d 100644 --- a/AI/Nullkiller/AIGateway.h +++ b/AI/Nullkiller/AIGateway.h @@ -198,11 +198,6 @@ public: void retrieveVisitableObjs(); virtual std::vector getFlaggedObjects() const; - HeroPtr getHeroWithGrail() const; - - const CGTownInstance * findTownWithTavern() const; - bool canRecruitAnyHero(const CGTownInstance * t = NULL) const; - void requestSent(const CPackForServer * pack, int requestID) override; void answerQuery(QueryID queryID, int selection); //special function that can be called ONLY from game events handling thread and will send request ASAP diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index 572a88694..116411633 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -12,6 +12,8 @@ #include "../Engine/Nullkiller.h" #include "../../../lib/mapObjects/MapObjects.h" #include "../../../lib/CHeroHandler.h" +#include "../../../lib/GameSettings.h" +#include "../../../lib/CGameState.h" namespace NKAI { @@ -179,6 +181,51 @@ float HeroManager::evaluateHero(const CGHeroInstance * hero) const return evaluateFightingStrength(hero); } +bool HeroManager::canRecruitHero(const CGTownInstance * town) const +{ + if(!town) + town = findTownWithTavern(); + + if(!town || !townHasFreeTavern(town)) + return false; + + if(cb->getResourceAmount(EGameResID::GOLD) < GameConstants::HERO_GOLD_COST) + return false; + + const bool includeGarnisoned = true; + int heroCount = cb->getHeroCount(ai->playerID, includeGarnisoned); + + if(heroCount >= ALLOWED_ROAMING_HEROES) + return false; + + if(heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)) + return false; + + if(!cb->getAvailableHeroes(town).size()) + return false; + + return true; +} + +const CGTownInstance * HeroManager::findTownWithTavern() const +{ + for(const CGTownInstance * t : cb->getTownsInfo()) + if(townHasFreeTavern(t)) + return t; + + return nullptr; +} + +const CGHeroInstance * HeroManager::findHeroWithGrail() const +{ + for(const CGHeroInstance * h : cb->getHeroesInfo()) + { + if(h->hasArt(ArtifactID::GRAIL)) + return h; + } + return nullptr; +} + SecondarySkillScoreMap::SecondarySkillScoreMap(std::map scoreMap) :scoreMap(scoreMap) { diff --git a/AI/Nullkiller/Analyzers/HeroManager.h b/AI/Nullkiller/Analyzers/HeroManager.h index 459604bef..9c98443f3 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.h +++ b/AI/Nullkiller/Analyzers/HeroManager.h @@ -30,6 +30,8 @@ public: virtual void update() = 0; virtual float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const = 0; virtual float evaluateHero(const CGHeroInstance * hero) const = 0; + virtual bool canRecruitHero(const CGTownInstance * t = nullptr) const = 0; + virtual const CGHeroInstance * findHeroWithGrail() const = 0; }; class DLL_EXPORT ISecondarySkillRule @@ -57,20 +59,24 @@ private: static SecondarySkillEvaluator scountSkillsScores; CCallback * cb; //this is enough, but we downcast from CCallback + const Nullkiller * ai; std::map heroRoles; public: - HeroManager(CCallback * CB, const Nullkiller * ai) : cb(CB) {} + HeroManager(CCallback * CB, const Nullkiller * ai) : cb(CB), ai(ai) {} const std::map & getHeroRoles() const override; HeroRole getHeroRole(const HeroPtr & hero) const override; int selectBestSkill(const HeroPtr & hero, const std::vector & skills) const override; void update() override; float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const override; float evaluateHero(const CGHeroInstance * hero) const override; + bool canRecruitHero(const CGTownInstance * t = nullptr) const override; + const CGHeroInstance * findHeroWithGrail() const override; private: float evaluateFightingStrength(const CGHeroInstance * hero) const; float evaluateSpeciality(const CGHeroInstance * hero) const; + const CGTownInstance * findTownWithTavern() const; }; // basic skill scores. missing skills will have score of 0 diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index 82a146549..8e6e9c1eb 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -53,7 +53,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const for(auto town : towns) { - if(ai->canRecruitAnyHero(town)) + if(ai->nullkiller->heroManager->canRecruitHero(town)) { auto availableHeroes = cb->getAvailableHeroes(town); diff --git a/AI/Nullkiller/Behaviors/StartupBehavior.cpp b/AI/Nullkiller/Behaviors/StartupBehavior.cpp index c05c31726..06647e5d2 100644 --- a/AI/Nullkiller/Behaviors/StartupBehavior.cpp +++ b/AI/Nullkiller/Behaviors/StartupBehavior.cpp @@ -66,7 +66,7 @@ const CGHeroInstance * getNearestHero(const CGTownInstance * town) bool needToRecruitHero(const CGTownInstance * startupTown) { - if(!ai->canRecruitAnyHero(startupTown)) + if(!ai->nullkiller->heroManager->canRecruitHero(startupTown)) return false; if(!startupTown->garrisonHero && !startupTown->visitingHero) diff --git a/AI/Nullkiller/Goals/RecruitHero.cpp b/AI/Nullkiller/Goals/RecruitHero.cpp index d6cfe2908..ab596f801 100644 --- a/AI/Nullkiller/Goals/RecruitHero.cpp +++ b/AI/Nullkiller/Goals/RecruitHero.cpp @@ -33,8 +33,6 @@ void RecruitHero::accept(AIGateway * ai) { auto t = town; - if(!t) t = ai->findTownWithTavern(); - if(!t) { throw cannotFulfillGoalException("No town to recruit hero!"); diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 8fd97a975..7d6f2c1e2 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -1093,7 +1093,7 @@ void AINodeStorage::calculateTownPortal( if(nodeOptional) { #if NKAI_PATHFINDER_TRACE_LEVEL >= 1 - logAi->trace("Adding town portal node at %s", targetTown->name); + logAi->trace("Adding town portal node at %s", targetTown->getObjectName()); #endif output.push_back(nodeOptional.get()); } From fa156568c48348cbd034e039d0f0c366c28d5c35 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 24 Apr 2023 16:58:15 +0300 Subject: [PATCH 24/26] Fixes according to review --- AI/Nullkiller/Analyzers/HeroManager.cpp | 2 +- ChangeLog.md | 1 + client/mapView/MapRendererContext.cpp | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index 116411633..3322976af 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -189,7 +189,7 @@ bool HeroManager::canRecruitHero(const CGTownInstance * town) const if(!town || !townHasFreeTavern(town)) return false; - if(cb->getResourceAmount(EGameResID::GOLD) < GameConstants::HERO_GOLD_COST) + if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST) return false; const bool includeGarnisoned = true; diff --git a/ChangeLog.md b/ChangeLog.md index 3f6982b8b..0bce56f4f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -16,6 +16,7 @@ * Fixed crash on empty save game list after filtering * Fixed blocked progress in Launcher on language detection failure * Launcher will now correctly handle selection of Ddata directory in H3 install +* Map editor will now correctly save message property for events and pandoras # 1.1.1 -> 1.2.0 diff --git a/client/mapView/MapRendererContext.cpp b/client/mapView/MapRendererContext.cpp index d885b02f6..3060e41c5 100644 --- a/client/mapView/MapRendererContext.cpp +++ b/client/mapView/MapRendererContext.cpp @@ -279,6 +279,9 @@ bool MapRendererAdventureContext::showSpellRange(const int3 & position) const auto hero = adventureInt->curHero(); + if (!hero) + return false; + return !isInScreenRange(hero->getSightCenter(), position); } From d3c1c92a03737d3ce39eb4d1416a3c4f6564c7fb Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 24 Apr 2023 17:16:55 +0300 Subject: [PATCH 25/26] Do not update minimap using puzzle map data --- client/mapView/MapView.cpp | 3 ++- client/mapView/MapViewController.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/client/mapView/MapView.cpp b/client/mapView/MapView.cpp index a666e23fa..dd5e3d750 100644 --- a/client/mapView/MapView.cpp +++ b/client/mapView/MapView.cpp @@ -160,6 +160,7 @@ void MapView::onViewMapActivated() PuzzleMapView::PuzzleMapView(const Point & offset, const Point & dimensions, const int3 & tileToCenter) : BasicMapView(offset, dimensions) { - controller->setViewCenter(tileToCenter); controller->activatePuzzleMapContext(tileToCenter); + controller->setViewCenter(tileToCenter); + } diff --git a/client/mapView/MapViewController.cpp b/client/mapView/MapViewController.cpp index ced9e1d80..099ba4979 100644 --- a/client/mapView/MapViewController.cpp +++ b/client/mapView/MapViewController.cpp @@ -63,7 +63,7 @@ void MapViewController::setViewCenter(const Point & position, int level) model->setViewCenter(betterPosition); model->setLevel(vstd::clamp(level, 0, context->getMapSize().z)); - if(adventureInt) // may be called before adventureInt is initialized + if(adventureInt && !puzzleMapContext) // may be called before adventureInt is initialized adventureInt->onMapViewMoved(model->getTilesTotalRect(), model->getLevel()); } From 3ac713d6a8a3a3edef44b0fbfb089f5b558bea3e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 24 Apr 2023 17:17:12 +0300 Subject: [PATCH 26/26] Fix crash on dereferencing nullptr --- client/lobby/CLobbyScreen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lobby/CLobbyScreen.cpp b/client/lobby/CLobbyScreen.cpp index dbe54e0cb..7c862d30f 100644 --- a/client/lobby/CLobbyScreen.cpp +++ b/client/lobby/CLobbyScreen.cpp @@ -197,7 +197,7 @@ void CLobbyScreen::updateAfterStateChange() } } - if(curTab == tabRand && CSH->si->mapGenOptions) + if(curTab && curTab == tabRand && CSH->si->mapGenOptions) tabRand->setMapGenOptions(CSH->si->mapGenOptions); }