Преглед на файлове

Merge branch 'vcmi:develop' into quickspell

Laserlicht преди 1 година
родител
ревизия
b568e2f628
променени са 100 файла, в които са добавени 1062 реда и са изтрити 643 реда
  1. 48 134
      .github/workflows/github.yml
  2. 1 0
      .gitignore
  3. 0 2
      .travis.yml
  4. 1 1
      AI/BattleAI/BattleAI.cpp
  5. 1 1
      AI/BattleAI/BattleAI.h
  6. 4 1
      AI/BattleAI/BattleEvaluator.cpp
  7. 4 4
      AI/BattleAI/BattleExchangeVariant.cpp
  8. 1 1
      AI/BattleAI/BattleExchangeVariant.h
  9. 1 1
      AI/BattleAI/ThreatMap.cpp
  10. 1 1
      AI/BattleAI/ThreatMap.h
  11. 10 10
      AI/Nullkiller/AIGateway.cpp
  12. 1 1
      AI/Nullkiller/AIGateway.h
  13. 18 4
      AI/Nullkiller/AIUtility.cpp
  14. 1 2
      AI/Nullkiller/AIUtility.h
  15. 1 1
      AI/Nullkiller/Analyzers/ArmyManager.h
  16. 4 4
      AI/Nullkiller/Analyzers/BuildAnalyzer.cpp
  17. 1 1
      AI/Nullkiller/Analyzers/BuildAnalyzer.h
  18. 2 1
      AI/Nullkiller/Analyzers/HeroManager.cpp
  19. 1 1
      AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp
  20. 1 1
      AI/Nullkiller/Behaviors/DefenceBehavior.cpp
  21. 9 0
      AI/Nullkiller/Behaviors/StartupBehavior.cpp
  22. 1 1
      AI/Nullkiller/Engine/Nullkiller.cpp
  23. 164 37
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  24. 2 1
      AI/Nullkiller/Engine/PriorityEvaluator.h
  25. 2 2
      AI/Nullkiller/Goals/BuyArmy.cpp
  26. 1 1
      AI/Nullkiller/Goals/CaptureObject.h
  27. 6 6
      AI/Nullkiller/Goals/ExecuteHeroChain.cpp
  28. 29 29
      AI/Nullkiller/Helpers/ExplorationHelper.cpp
  29. 0 1
      AI/Nullkiller/Helpers/ExplorationHelper.h
  30. 26 28
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  31. 10 6
      AI/Nullkiller/Pathfinding/AINodeStorage.h
  32. 2 1
      AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp
  33. 1 1
      AI/Nullkiller/Pathfinding/GraphPaths.cpp
  34. 1 1
      AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp
  35. 1 1
      AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp
  36. 9 0
      AI/StupidAI/StupidAI.cpp
  37. 1 1
      AI/VCAI/Goals/BuildThis.cpp
  38. 1 1
      AI/VCAI/Goals/ClearWayTo.cpp
  39. 1 1
      AI/VCAI/Goals/VisitHero.cpp
  40. 1 1
      AI/VCAI/Goals/Win.cpp
  41. 2 3
      AI/VCAI/Pathfinding/AINodeStorage.cpp
  42. 1 1
      AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp
  43. 3 3
      AI/VCAI/ResourceManager.cpp
  44. 2 2
      AI/VCAI/ResourceManager.h
  45. 11 11
      AI/VCAI/VCAI.cpp
  46. 1 1
      AI/VCAI/VCAI.h
  47. 2 2
      CCallback.cpp
  48. 3 3
      CCallback.h
  49. 21 21
      CI/NSIS.template.in
  50. 3 3
      CMakeLists.txt
  51. 2 1
      CMakePresets.json
  52. 70 22
      ChangeLog.md
  53. 114 25
      Mods/vcmi/config/vcmi/czech.json
  54. 1 0
      Mods/vcmi/config/vcmi/english.json
  55. 1 0
      Mods/vcmi/config/vcmi/german.json
  56. 2 2
      android/vcmi-app/src/main/java/org/libsdl/app/SDLActivity.java
  57. 166 0
      client/ArtifactsUIController.cpp
  58. 42 0
      client/ArtifactsUIController.h
  59. 1 1
      client/CGameInfo.h
  60. 2 2
      client/CMT.cpp
  61. 1 1
      client/CMT.h
  62. 2 0
      client/CMakeLists.txt
  63. 16 76
      client/CPlayerInterface.cpp
  64. 5 7
      client/CPlayerInterface.h
  65. 4 4
      client/CServerHandler.cpp
  66. 1 1
      client/ClientCommandManager.h
  67. 0 1
      client/ClientNetPackVisitors.h
  68. 29 29
      client/NetPacksClient.cpp
  69. 7 1
      client/PlayerLocalState.cpp
  70. 19 4
      client/ServerRunner.cpp
  71. 3 3
      client/ServerRunner.h
  72. 2 2
      client/adventureMap/AdventureMapInterface.cpp
  73. 1 1
      client/adventureMap/CInGameConsole.cpp
  74. 1 1
      client/adventureMap/CInGameConsole.h
  75. 1 1
      client/adventureMap/CList.cpp
  76. 3 3
      client/adventureMap/CMinimap.cpp
  77. 1 1
      client/battle/BattleActionsController.h
  78. 16 16
      client/battle/BattleAnimationClasses.cpp
  79. 2 2
      client/battle/BattleAnimationClasses.h
  80. 10 10
      client/battle/BattleFieldController.cpp
  81. 2 2
      client/battle/BattleFieldController.h
  82. 1 1
      client/battle/BattleInterface.h
  83. 2 2
      client/battle/BattleProjectileController.cpp
  84. 5 5
      client/battle/BattleSiegeController.cpp
  85. 1 1
      client/battle/BattleSiegeController.h
  86. 1 1
      client/battle/BattleStacksController.cpp
  87. 1 1
      client/battle/CreatureAnimation.cpp
  88. 2 2
      client/eventsSDL/InputSourceGameController.cpp
  89. 1 1
      client/eventsSDL/InputSourceGameController.h
  90. 3 0
      client/eventsSDL/InputSourceTouch.cpp
  91. 1 1
      client/globalLobby/GlobalLobbyLoginWindow.cpp
  92. 1 1
      client/gui/CIntObject.h
  93. 1 1
      client/gui/EventDispatcher.cpp
  94. 1 1
      client/gui/EventsReceiver.h
  95. 1 1
      client/gui/FramerateManager.h
  96. 1 1
      client/gui/InterfaceObjectConfigurable.h
  97. 1 1
      client/lobby/CBonusSelection.cpp
  98. 1 1
      client/lobby/CSelectionBase.cpp
  99. 80 57
      client/lobby/OptionsTab.cpp
  100. 11 3
      client/lobby/OptionsTab.h

+ 48 - 134
.github/workflows/github.yml

@@ -90,14 +90,14 @@ jobs:
           - platform: android-32
           - platform: android-32
             os: macos-14
             os: macos-14
             extension: apk
             extension: apk
-            preset: android-daily-release
+            preset: android-conan-ninja-release
             conan_profile: android-32
             conan_profile: android-32
             conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT
             conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT
             artifact_platform: armeabi-v7a
             artifact_platform: armeabi-v7a
           - platform: android-64
           - platform: android-64
             os: macos-14
             os: macos-14
             extension: apk
             extension: apk
-            preset: android-daily-release
+            preset: android-conan-ninja-release
             conan_profile: android-64
             conan_profile: android-64
             conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT
             conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT
             artifact_platform: arm64-v8a
             artifact_platform: arm64-v8a
@@ -111,22 +111,6 @@ jobs:
       with:
       with:
         submodules: recursive
         submodules: recursive
 
 
-    - name: Ensure LF line endings
-      if: ${{ startsWith(matrix.preset, 'linux-clang-test') }}
-      run: |
-        find . -path ./.git -prune -o -path ./AI/FuzzyLite -prune -o -path ./test/googletest \
-        -o -path ./osx  -prune -o -type f \
-        -not -name '*.png' -and -not -name '*.vcxproj*' -and -not -name '*.props' -and -not -name '*.wav' -and -not -name '*.webm' -and -not -name '*.ico' -and -not -name '*.bat' -print0 | \
-        { ! xargs -0 grep -l -z -P '\r\n'; }
-
-    - name: Validate JSON
-      # the Python yaml module doesn't seem to work on mac-arm
-      # also, running it on multiple presets is redundant and slightly increases already long CI built times
-      if: ${{ startsWith(matrix.preset, 'linux-clang-test') }}
-      run: |
-        sudo apt install python3-jstyleson
-        python3 CI/linux-qt6/validate_json.py
-
     - name: Dependencies
     - name: Dependencies
       run: source '${{github.workspace}}/CI/${{matrix.platform}}/before_install.sh'
       run: source '${{github.workspace}}/CI/${{matrix.platform}}/before_install.sh'
       env:
       env:
@@ -210,6 +194,9 @@ jobs:
         if [[ ${{matrix.preset}} == linux-gcc-test ]]
         if [[ ${{matrix.preset}} == linux-gcc-test ]]
         then
         then
             cmake -DENABLE_CCACHE:BOOL=ON -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 --preset ${{ matrix.preset }}
             cmake -DENABLE_CCACHE:BOOL=ON -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 --preset ${{ matrix.preset }}
+        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 [[ ${{matrix.platform}} != msvc ]]
         then
         then
             cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }}
             cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }}
@@ -220,6 +207,9 @@ jobs:
     - name: Build
     - name: Build
       run: |
       run: |
         cmake --build --preset ${{matrix.preset}}
         cmake --build --preset ${{matrix.preset}}
+      env:
+        ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
+        ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
 
 
     - name: Test
     - name: Test
       env:
       env:
@@ -247,12 +237,6 @@ jobs:
           && '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' '${{github.workspace}}' "$(ls '${{ env.VCMI_PACKAGE_FILE_NAME }}'.*)"
           && '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' '${{github.workspace}}' "$(ls '${{ env.VCMI_PACKAGE_FILE_NAME }}'.*)"
         rm -rf _CPack_Packages
         rm -rf _CPack_Packages
 
 
-    - name: Additional logs
-      if: ${{ failure() && steps.cpack.outcome == 'failure' && matrix.platform == 'msvc' }}
-      run: |
-        cat '${{github.workspace}}/out/build/${{matrix.preset}}/_CPack_Packages/win32/NSIS/project.nsi'
-        cat '${{github.workspace}}/out/build/${{matrix.preset}}/_CPack_Packages/win32/NSIS/NSISOutput.log'
-
     - name: Artifacts
     - name: Artifacts
       if: ${{ matrix.pack == 1 }}
       if: ${{ matrix.pack == 1 }}
       uses: actions/upload-artifact@v4
       uses: actions/upload-artifact@v4
@@ -265,10 +249,15 @@ jobs:
       if: ${{ startsWith(matrix.platform, 'android') }}
       if: ${{ startsWith(matrix.platform, 'android') }}
       run: |
       run: |
         builtApkPath="$(ls ${{ github.workspace }}/out/build/${{ matrix.preset }}/android-build/vcmi-app/build/outputs/apk/release/*.${{ matrix.extension }})"
         builtApkPath="$(ls ${{ github.workspace }}/out/build/${{ matrix.preset }}/android-build/vcmi-app/build/outputs/apk/release/*.${{ matrix.extension }})"
+        builtAabPath="$(ls ${{ github.workspace }}/out/build/${{ matrix.preset }}/android-build/vcmi-app/build/outputs/bundle/release/*.aab)"
         ANDROID_APK_PATH="${{ github.workspace }}/$VCMI_PACKAGE_FILE_NAME.${{ matrix.extension }}"
         ANDROID_APK_PATH="${{ github.workspace }}/$VCMI_PACKAGE_FILE_NAME.${{ matrix.extension }}"
+        ANDROID_AAB_PATH="${{ github.workspace }}/$VCMI_PACKAGE_FILE_NAME.aab"
         mv "$builtApkPath" "$ANDROID_APK_PATH"
         mv "$builtApkPath" "$ANDROID_APK_PATH"
+        mv "$builtAabPath" "$ANDROID_AAB_PATH"
         echo "ANDROID_APK_PATH=$ANDROID_APK_PATH" >> $GITHUB_ENV
         echo "ANDROID_APK_PATH=$ANDROID_APK_PATH" >> $GITHUB_ENV
-    - name: Android artifacts
+        echo "ANDROID_AAB_PATH=$ANDROID_AAB_PATH" >> $GITHUB_ENV
+
+    - name: Android apk artifacts
       if: ${{ startsWith(matrix.platform, 'android') }}
       if: ${{ startsWith(matrix.platform, 'android') }}
       uses: actions/upload-artifact@v4
       uses: actions/upload-artifact@v4
       with:
       with:
@@ -276,21 +265,21 @@ jobs:
         path: |
         path: |
           ${{ env.ANDROID_APK_PATH }}
           ${{ env.ANDROID_APK_PATH }}
 
 
-    - name: Symbols
-      if: ${{ matrix.platform == 'msvc' }}
+    - name: Android aab artifacts
+      if: ${{ startsWith(matrix.platform, 'android') }}
       uses: actions/upload-artifact@v4
       uses: actions/upload-artifact@v4
       with:
       with:
-        name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} - symbols
+        name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} - aab
         path: |
         path: |
-            ${{github.workspace}}/**/*.pdb
+          ${{ env.ANDROID_AAB_PATH }}
 
 
-    - name: Android JNI ${{matrix.platform}}
-      if: ${{ startsWith(matrix.platform, 'android') && github.ref == 'refs/heads/master' }}
+    - name: Symbols
+      if: ${{ matrix.platform == 'msvc' }}
       uses: actions/upload-artifact@v4
       uses: actions/upload-artifact@v4
       with:
       with:
-        name: Android JNI ${{matrix.platform}}
+        name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} - symbols
         path: |
         path: |
-          ${{github.workspace}}/out/build/${{matrix.preset}}/android-build/libs
+            ${{github.workspace}}/**/*.pdb
 
 
     - name: Upload build
     - 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.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' }}
@@ -304,107 +293,6 @@ jobs:
         DEPLOY_RSA: ${{ secrets.DEPLOY_RSA }}
         DEPLOY_RSA: ${{ secrets.DEPLOY_RSA }}
         PACKAGE_EXTENSION: ${{ matrix.extension }}
         PACKAGE_EXTENSION: ${{ matrix.extension }}
 
 
-  # copy-pasted mostly
-  bundle_release:
-
-    needs: build
-    if: always() && github.ref == 'refs/heads/master'
-    strategy:
-      matrix:
-        include:
-          - platform: android-32
-            os: macos-14
-            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@v4
-      with:
-        submodules: recursive
-
-    - name: Dependencies
-      run: source '${{github.workspace}}/CI/${{matrix.platform}}/before_install.sh'
-      env:
-        VCMI_BUILD_PLATFORM: x64
-
-    - uses: actions/setup-python@v5
-      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
-
-    - uses: actions/setup-java@v4
-      if: ${{ startsWith(matrix.platform, 'android') }}
-      with:
-        distribution: 'temurin'
-        java-version: '11'
-
-    - 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_GOLDMASTER="$VCMI_PACKAGE_GOLDMASTER" >> $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}}
-      env:
-        ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
-        ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
-
-    - name: Download libs x64
-      uses: actions/download-artifact@v4
-      with:
-        name: Android JNI android-64
-        path: ${{ github.workspace }}/out/build/${{ matrix.preset }}/android-build/libs
-
-    - name: Create Android package
-      run: |
-        cd out/build/${{ matrix.preset }}/android-build
-        ./gradlew bundleRelease --info
-        echo ANDROID_APK_PATH="$(ls ${{ github.workspace }}/out/build/${{ matrix.preset }}/android-build/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@v4
-      with:
-        name: ${{ env.VCMI_PACKAGE_FILE_NAME }}
-        path: |
-          ${{ env.ANDROID_APK_PATH }}
-
-
   deploy-src:
   deploy-src:
     if: always() && github.ref == 'refs/heads/master'
     if: always() && github.ref == 'refs/heads/master'
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
@@ -434,3 +322,29 @@ jobs:
             name: ${{ env.VCMI_PACKAGE_FILE_NAME }}
             name: ${{ env.VCMI_PACKAGE_FILE_NAME }}
             path: |
             path: |
               ./release.tar.gz
               ./release.tar.gz
+
+  validate-code:
+    if: always()
+    runs-on: ubuntu-24.04
+    defaults:
+      run:
+        shell: bash
+    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 \
+            -o -path ./osx  -prune -o -type f \
+            -not -name '*.png' -and -not -name '*.vcxproj*' -and -not -name '*.props' -and -not -name '*.wav' -and -not -name '*.webm' -and -not -name '*.ico' -and -not -name '*.bat' -print0 | \
+            { ! xargs -0 grep -l -z -P '\r\n'; }
+
+        - name: Validate JSON
+          run: |
+            sudo apt install python3-jstyleson
+            python3 CI/linux-qt6/validate_json.py

+ 1 - 0
.gitignore

@@ -43,6 +43,7 @@ VCMI_VS11.sdf
 *.ipch
 *.ipch
 VCMI_VS11.opensdf
 VCMI_VS11.opensdf
 .DS_Store
 .DS_Store
+.directory
 CMakeUserPresets.json
 CMakeUserPresets.json
 compile_commands.json
 compile_commands.json
 fuzzylite.pc
 fuzzylite.pc

+ 0 - 2
.travis.yml

@@ -51,8 +51,6 @@ notifications:
     - [email protected]
     - [email protected]
     on_success: change
     on_success: change
     on_failure: always
     on_failure: always
-  slack:
-    secure: KHXFe14FFKtw5mErWbj730+utqy7i/3AUobWfAMAGvWI5sJYlhbBU+KvvCoD2SlRQg3mQqgwVw8NBJF1Mffs7WcRmrFFFmuMqZxFLAfKBd3T0CxWpAGfnfNgDmlfV4OfEgQWk1pakEPOymhxbbmLUuCjykZDuTcioxAk0UAHDwY=
 
 
 before_install:
 before_install:
 - test $TRAVIS_BRANCH != coverity_scan -o ${TRAVIS_JOB_NUMBER##*.} = 1 || exit 0
 - test $TRAVIS_BRANCH != coverity_scan -o ${TRAVIS_JOB_NUMBER##*.} = 1 || exit 0

+ 1 - 1
AI/BattleAI/BattleAI.cpp

@@ -176,7 +176,7 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
 		movesSkippedByDefense = 0;
 		movesSkippedByDefense = 0;
 	}
 	}
 
 
-	logAi->trace("BattleAI decission made in %lld", timeElapsed(start));
+	logAi->trace("BattleAI decision made in %lld", timeElapsed(start));
 
 
 	cb->battleMakeUnitAction(battleID, result);
 	cb->battleMakeUnitAction(battleID, result);
 }
 }

+ 1 - 1
AI/BattleAI/BattleAI.h

@@ -50,7 +50,7 @@ struct CurrentOffensivePotential
 		return ourPotential - enemyPotential;
 		return ourPotential - enemyPotential;
 	}
 	}
 };
 };
-*/ // These lines may be usefull but they are't used in the code.
+*/ // These lines may be useful but they are't used in the code.
 
 
 class CBattleAI : public CBattleGameInterface
 class CBattleAI : public CBattleGameInterface
 {
 {

+ 4 - 1
AI/BattleAI/BattleEvaluator.cpp

@@ -196,7 +196,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
 		}
 		}
 	}
 	}
 
 
-	//ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code.
+	//ThreatMap threatsToUs(stack); // These lines may be useful but they are't used in the code.
 	if(moveTarget.scorePerTurn > score)
 	if(moveTarget.scorePerTurn > score)
 	{
 	{
 		score = moveTarget.score;
 		score = moveTarget.score;
@@ -623,6 +623,9 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
 
 
 				for(const auto & unit : allUnits)
 				for(const auto & unit : allUnits)
 				{
 				{
+					if (!unit->isValidTarget())
+						continue;
+					
 					auto newHealth = unit->getAvailableHealth();
 					auto newHealth = unit->getAvailableHealth();
 					auto oldHealth = vstd::find_or(healthOfStack, unit->unitId(), 0); // old health value may not exist for newly summoned units
 					auto oldHealth = vstd::find_or(healthOfStack, unit->unitId(), 0); // old health value may not exist for newly summoned units
 
 

+ 4 - 4
AI/BattleAI/BattleExchangeVariant.cpp

@@ -13,7 +13,7 @@
 
 
 AttackerValue::AttackerValue()
 AttackerValue::AttackerValue()
 	: value(0),
 	: value(0),
-	isRetalitated(false)
+	isRetaliated(false)
 {
 {
 }
 }
 
 
@@ -57,14 +57,14 @@ float BattleExchangeVariant::trackAttack(
 
 
 				attackValue -= attackerDamageReduce;
 				attackValue -= attackerDamageReduce;
 				dpsScore.ourDamageReduce += attackerDamageReduce;
 				dpsScore.ourDamageReduce += attackerDamageReduce;
-				attackerValue[unitToUpdate->unitId()].isRetalitated = true;
+				attackerValue[unitToUpdate->unitId()].isRetaliated = true;
 
 
 				unitToUpdate->damage(retaliationDamage);
 				unitToUpdate->damage(retaliationDamage);
 				defender->afterAttack(false, true);
 				defender->afterAttack(false, true);
 
 
 #if BATTLE_TRACE_LEVEL>=1
 #if BATTLE_TRACE_LEVEL>=1
 				logAi->trace(
 				logAi->trace(
-					"%s -> %s, ap retalitation, %s, dps: %2f, score: %2f",
+					"%s -> %s, ap retaliation, %s, dps: %2f, score: %2f",
 					defender->getDescription(),
 					defender->getDescription(),
 					unitToUpdate->getDescription(),
 					unitToUpdate->getDescription(),
 					ap.attack.shooting ? "shot" : "mellee",
 					ap.attack.shooting ? "shot" : "mellee",
@@ -185,7 +185,7 @@ float BattleExchangeVariant::trackAttack(
 		if(isOurAttack)
 		if(isOurAttack)
 		{
 		{
 			dpsScore.ourDamageReduce += attackerDamageReduce;
 			dpsScore.ourDamageReduce += attackerDamageReduce;
-			attackerValue[attacker->unitId()].isRetalitated = true;
+			attackerValue[attacker->unitId()].isRetaliated = true;
 		}
 		}
 		else
 		else
 		{
 		{

+ 1 - 1
AI/BattleAI/BattleExchangeVariant.h

@@ -45,7 +45,7 @@ struct BattleScore
 struct AttackerValue
 struct AttackerValue
 {
 {
 	float value;
 	float value;
-	bool isRetalitated;
+	bool isRetaliated;
 	BattleHex position;
 	BattleHex position;
 
 
 	AttackerValue();
 	AttackerValue();

+ 1 - 1
AI/BattleAI/ThreatMap.cpp

@@ -70,4 +70,4 @@ ThreatMap::ThreatMap(const CStack *Endangered) : endangered(Endangered)
 		});
 		});
 	}
 	}
 }
 }
-*/ // These lines may be usefull but they are't used in the code.
+*/ // These lines may be useful but they are't used in the code.

+ 1 - 1
AI/BattleAI/ThreatMap.h

@@ -22,4 +22,4 @@ public:
 	std::array<int, GameConstants::BFIELD_SIZE> sufferedDamage;
 	std::array<int, GameConstants::BFIELD_SIZE> sufferedDamage;
 
 
 	ThreatMap(const CStack *Endangered);
 	ThreatMap(const CStack *Endangered);
-};*/ // These lines may be usefull but they are't used in the code.
+};*/ // These lines may be useful but they are't used in the code.

+ 10 - 10
AI/Nullkiller/AIGateway.cpp

@@ -500,7 +500,7 @@ void AIGateway::objectPropertyChanged(const SetObjectProperty * sop)
 			if(relations == PlayerRelations::ENEMIES)
 			if(relations == PlayerRelations::ENEMIES)
 			{
 			{
 				//we want to visit objects owned by oppponents
 				//we want to visit objects owned by oppponents
-				//addVisitableObj(obj); // TODO: Remove once save compatability broken. In past owned objects were removed from this set
+				//addVisitableObj(obj); // TODO: Remove once save compatibility broken. In past owned objects were removed from this set
 				nullkiller->memory->markObjectUnvisited(obj);
 				nullkiller->memory->markObjectUnvisited(obj);
 			}
 			}
 			else if(relations == PlayerRelations::SAME_PLAYER && obj->ID == Obj::TOWN)
 			else if(relations == PlayerRelations::SAME_PLAYER && obj->ID == Obj::TOWN)
@@ -649,7 +649,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
 				auto ratio = static_cast<float>(danger) / hero->getTotalStrength();
 				auto ratio = static_cast<float>(danger) / hero->getTotalStrength();
 
 
 				answer = topObj->id == goalObjectID; // no if we do not aim to visit this object
 				answer = topObj->id == goalObjectID; // no if we do not aim to visit this object
-				logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), hero.name, ratio);
+				logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), hero.name(), ratio);
 
 
 				if(cb->getObj(goalObjectID, false))
 				if(cb->getObj(goalObjectID, false))
 				{
 				{
@@ -705,7 +705,7 @@ void AIGateway::showTeleportDialog(const CGHeroInstance * hero, TeleportChannelI
 	NET_EVENT_HANDLER;
 	NET_EVENT_HANDLER;
 	status.addQuery(askID, boost::str(boost::format("Teleport dialog query with %d exits") % exits.size()));
 	status.addQuery(askID, boost::str(boost::format("Teleport dialog query with %d exits") % exits.size()));
 
 
-	int choosenExit = -1;
+	int chosenExit = -1;
 	if(impassable)
 	if(impassable)
 	{
 	{
 		nullkiller->memory->knownTeleportChannels[channel]->passability = TeleportChannel::IMPASSABLE;
 		nullkiller->memory->knownTeleportChannels[channel]->passability = TeleportChannel::IMPASSABLE;
@@ -714,14 +714,14 @@ void AIGateway::showTeleportDialog(const CGHeroInstance * hero, TeleportChannelI
 	{
 	{
 		auto neededExit = std::make_pair(destinationTeleport, destinationTeleportPos);
 		auto neededExit = std::make_pair(destinationTeleport, destinationTeleportPos);
 		if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, neededExit))
 		if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, neededExit))
-			choosenExit = vstd::find_pos(exits, neededExit);
+			chosenExit = vstd::find_pos(exits, neededExit);
 	}
 	}
 
 
 	for(auto exit : exits)
 	for(auto exit : exits)
 	{
 	{
 		if(status.channelProbing() && exit.first == destinationTeleport)
 		if(status.channelProbing() && exit.first == destinationTeleport)
 		{
 		{
-			choosenExit = vstd::find_pos(exits, exit);
+			chosenExit = vstd::find_pos(exits, exit);
 			break;
 			break;
 		}
 		}
 		else
 		else
@@ -739,7 +739,7 @@ void AIGateway::showTeleportDialog(const CGHeroInstance * hero, TeleportChannelI
 
 
 	requestActionASAP([=]()
 	requestActionASAP([=]()
 	{
 	{
-		answerQuery(askID, choosenExit);
+		answerQuery(askID, chosenExit);
 	});
 	});
 }
 }
 
 
@@ -1452,7 +1452,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
 	if(cb->getResourceAmount(GameResID(g.resID)) >= g.value) //goal is already fulfilled. Why we need this check, anyway?
 	if(cb->getResourceAmount(GameResID(g.resID)) >= g.value) //goal is already fulfilled. Why we need this check, anyway?
 		throw goalFulfilledException(sptr(g));
 		throw goalFulfilledException(sptr(g));
 
 
-	int accquiredResources = 0;
+	int acquiredResources = 0;
 	if(const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(g.objid), false))
 	if(const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(g.objid), false))
 	{
 	{
 		if(const auto * m = dynamic_cast<const IMarket*>(obj))
 		if(const auto * m = dynamic_cast<const IMarket*>(obj))
@@ -1472,8 +1472,8 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
 				if (toGive) //don't try to sell 0 resources
 				if (toGive) //don't try to sell 0 resources
 				{
 				{
 					cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
 					cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
-					accquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
-					logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, accquiredResources, g.resID, obj->getObjectName());
+					acquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
+					logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, acquiredResources, g.resID, obj->getObjectName());
 				}
 				}
 				if (cb->getResourceAmount(GameResID(g.resID)))
 				if (cb->getResourceAmount(GameResID(g.resID)))
 					throw goalFulfilledException(sptr(g)); //we traded all we needed
 					throw goalFulfilledException(sptr(g)); //we traded all we needed
@@ -1553,7 +1553,7 @@ void AIGateway::requestActionASAP(std::function<void()> whatToDo)
 
 
 void AIGateway::lostHero(HeroPtr h)
 void AIGateway::lostHero(HeroPtr h)
 {
 {
-	logAi->debug("I lost my hero %s. It's best to forget and move on.", h.name);
+	logAi->debug("I lost my hero %s. It's best to forget and move on.", h.name());
 }
 }
 
 
 void AIGateway::answerQuery(QueryID queryID, int selection)
 void AIGateway::answerQuery(QueryID queryID, int selection)

+ 1 - 1
AI/Nullkiller/AIGateway.h

@@ -95,7 +95,7 @@ public:
 	AIGateway();
 	AIGateway();
 	virtual ~AIGateway();
 	virtual ~AIGateway();
 
 
-	//TODO: extract to apropriate goals
+	//TODO: extract to appropriate goals
 	void tryRealize(Goals::DigAtTile & g);
 	void tryRealize(Goals::DigAtTile & g);
 	void tryRealize(Goals::Trade & g);
 	void tryRealize(Goals::Trade & g);
 
 

+ 18 - 4
AI/Nullkiller/AIUtility.cpp

@@ -67,7 +67,6 @@ HeroPtr::HeroPtr(const CGHeroInstance * H)
 	}
 	}
 
 
 	h = H;
 	h = H;
-	name = h->getNameTranslated();
 	hid = H->id;
 	hid = H->id;
 //	infosCount[ai->playerID][hid]++;
 //	infosCount[ai->playerID][hid]++;
 }
 }
@@ -89,6 +88,14 @@ bool HeroPtr::operator<(const HeroPtr & rhs) const
 	return hid < rhs.hid;
 	return hid < rhs.hid;
 }
 }
 
 
+std::string HeroPtr::name() const
+{
+	if (h)
+		return h->getNameTextID();
+	else
+		return "<NO HERO>";
+}
+
 const CGHeroInstance * HeroPtr::get(bool doWeExpectNull) const
 const CGHeroInstance * HeroPtr::get(bool doWeExpectNull) const
 {
 {
 	return get(cb, doWeExpectNull);
 	return get(cb, doWeExpectNull);
@@ -423,9 +430,16 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
 		return false;
 		return false;
 	}
 	}
 
 
-	if(obj->wasVisited(h)) //it must pointer to hero instance, heroPtr calls function wasVisited(ui8 player);
+	if(obj->wasVisited(h))
 		return false;
 		return false;
 
 
+	auto rewardable = dynamic_cast<const Rewardable::Interface *>(obj);
+
+	if(rewardable && rewardable->getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT).empty())
+	{
+		return false;
+	}
+
 	return true;
 	return true;
 }
 }
 
 
@@ -434,9 +448,9 @@ bool townHasFreeTavern(const CGTownInstance * town)
 	if(!town->hasBuilt(BuildingID::TAVERN)) return false;
 	if(!town->hasBuilt(BuildingID::TAVERN)) return false;
 	if(!town->visitingHero) return true;
 	if(!town->visitingHero) return true;
 
 
-	bool canMoveVisitingHeroToGarnison = !town->getUpperArmy()->stacksCount();
+	bool canMoveVisitingHeroToGarrison = !town->getUpperArmy()->stacksCount();
 
 
-	return canMoveVisitingHeroToGarnison;
+	return canMoveVisitingHeroToGarrison;
 }
 }
 
 
 uint64_t getHeroArmyStrengthWithCommander(const CGHeroInstance * hero, const CCreatureSet * heroArmy)
 uint64_t getHeroArmyStrengthWithCommander(const CGHeroInstance * hero, const CCreatureSet * heroArmy)

+ 1 - 2
AI/Nullkiller/AIUtility.h

@@ -87,8 +87,7 @@ struct DLL_EXPORT HeroPtr
 	ObjectInstanceID hid;
 	ObjectInstanceID hid;
 
 
 public:
 public:
-	std::string name;
-
+	std::string name() const;
 
 
 	HeroPtr();
 	HeroPtr();
 	HeroPtr(const CGHeroInstance * H);
 	HeroPtr(const CGHeroInstance * H);

+ 1 - 1
AI/Nullkiller/Analyzers/ArmyManager.h

@@ -32,7 +32,7 @@ struct SlotInfo
 struct ArmyUpgradeInfo
 struct ArmyUpgradeInfo
 {
 {
 	std::vector<SlotInfo> resultingArmy;
 	std::vector<SlotInfo> resultingArmy;
-	uint64_t upgradeValue = 0;
+	int64_t upgradeValue = 0;
 	TResources upgradeCost;
 	TResources upgradeCost;
 
 
 	void addArmyToBuy(std::vector<SlotInfo> army);
 	void addArmyToBuy(std::vector<SlotInfo> army);

+ 4 - 4
AI/Nullkiller/Analyzers/BuildAnalyzer.cpp

@@ -267,7 +267,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
 
 
 				BuildingInfo prerequisite = getBuildingOrPrerequisite(town, missingBuildings[0], excludeDwellingDependencies);
 				BuildingInfo prerequisite = getBuildingOrPrerequisite(town, missingBuildings[0], excludeDwellingDependencies);
 
 
-				prerequisite.buildCostWithPrerequisits += info.buildCost;
+				prerequisite.buildCostWithPrerequisites += info.buildCost;
 				prerequisite.creatureCost = info.creatureCost;
 				prerequisite.creatureCost = info.creatureCost;
 				prerequisite.creatureGrows = info.creatureGrows;
 				prerequisite.creatureGrows = info.creatureGrows;
 				prerequisite.creatureLevel = info.creatureLevel;
 				prerequisite.creatureLevel = info.creatureLevel;
@@ -340,7 +340,7 @@ void TownDevelopmentInfo::addExistingDwelling(const BuildingInfo & existingDwell
 
 
 void TownDevelopmentInfo::addBuildingToBuild(const BuildingInfo & nextToBuild)
 void TownDevelopmentInfo::addBuildingToBuild(const BuildingInfo & nextToBuild)
 {
 {
-	townDevelopmentCost += nextToBuild.buildCostWithPrerequisits;
+	townDevelopmentCost += nextToBuild.buildCostWithPrerequisites;
 
 
 	if(nextToBuild.canBuild)
 	if(nextToBuild.canBuild)
 	{
 	{
@@ -361,7 +361,7 @@ BuildingInfo::BuildingInfo()
 	creatureGrows = 0;
 	creatureGrows = 0;
 	creatureID = CreatureID::NONE;
 	creatureID = CreatureID::NONE;
 	buildCost = 0;
 	buildCost = 0;
-	buildCostWithPrerequisits = 0;
+	buildCostWithPrerequisites = 0;
 	prerequisitesCount = 0;
 	prerequisitesCount = 0;
 	name.clear();
 	name.clear();
 	armyStrength = 0;
 	armyStrength = 0;
@@ -376,7 +376,7 @@ BuildingInfo::BuildingInfo(
 {
 {
 	id = building->bid;
 	id = building->bid;
 	buildCost = building->resources;
 	buildCost = building->resources;
-	buildCostWithPrerequisits = building->resources;
+	buildCostWithPrerequisites = building->resources;
 	dailyIncome = building->produce;
 	dailyIncome = building->produce;
 	exists = town->hasBuilt(id);
 	exists = town->hasBuilt(id);
 	prerequisitesCount = 1;
 	prerequisitesCount = 1;

+ 1 - 1
AI/Nullkiller/Analyzers/BuildAnalyzer.h

@@ -22,7 +22,7 @@ class DLL_EXPORT BuildingInfo
 public:
 public:
 	BuildingID id;
 	BuildingID id;
 	TResources buildCost;
 	TResources buildCost;
-	TResources buildCostWithPrerequisits;
+	TResources buildCostWithPrerequisites;
 	int creatureGrows;
 	int creatureGrows;
 	uint8_t creatureLevel;
 	uint8_t creatureLevel;
 	TResources creatureCost;
 	TResources creatureCost;

+ 2 - 1
AI/Nullkiller/Analyzers/HeroManager.cpp

@@ -176,7 +176,7 @@ int HeroManager::selectBestSkill(const HeroPtr & hero, const std::vector<Seconda
 
 
 		logAi->trace(
 		logAi->trace(
 			"Hero %s is proposed to learn %d with score %f",
 			"Hero %s is proposed to learn %d with score %f",
-			hero.name,
+			hero.name(),
 			skills[i].toEnum(),
 			skills[i].toEnum(),
 			score);
 			score);
 	}
 	}
@@ -204,6 +204,7 @@ float HeroManager::getFightingStrengthCached(const CGHeroInstance * hero) const
 {
 {
 	auto cached = knownFightingStrength.find(hero->id);
 	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->getFightingStrength();
 }
 }
 
 

+ 1 - 1
AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp

@@ -212,7 +212,7 @@ void CaptureObjectsBehavior::decomposeObjects(
 				vstd::concatenate(tasksLocal, getVisitGoals(paths, nullkiller, objToVisit, specificObjects));
 				vstd::concatenate(tasksLocal, getVisitGoals(paths, nullkiller, objToVisit, specificObjects));
 			}
 			}
 
 
-			std::lock_guard<std::mutex> lock(sync);
+			std::lock_guard<std::mutex> lock(sync); // FIXME: consider using tbb::parallel_reduce instead to avoid mutex overhead
 			vstd::concatenate(result, tasksLocal);
 			vstd::concatenate(result, tasksLocal);
 		});
 		});
 }
 }

+ 1 - 1
AI/Nullkiller/Behaviors/DefenceBehavior.cpp

@@ -240,7 +240,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 			if(path.turn() <= threat.turn - 2)
 			if(path.turn() <= threat.turn - 2)
 			{
 			{
 #if NKAI_TRACE_LEVEL >= 1
 #if NKAI_TRACE_LEVEL >= 1
-				logAi->trace("Defer defence of %s by %s because he has enough time to reach the town next trun",
+				logAi->trace("Defer defence of %s by %s because he has enough time to reach the town next turn",
 					town->getObjectName(),
 					town->getObjectName(),
 					path.targetHero->getObjectName());
 					path.targetHero->getObjectName());
 #endif
 #endif

+ 9 - 0
AI/Nullkiller/Behaviors/StartupBehavior.cpp

@@ -79,6 +79,15 @@ bool needToRecruitHero(const Nullkiller * ai, const CGTownInstance * startupTown
 		bool isGoldPile = dynamic_cast<const CGResource *>(obj)
 		bool isGoldPile = dynamic_cast<const CGResource *>(obj)
 			&& dynamic_cast<const CGResource *>(obj)->resourceID() == EGameResID::GOLD;
 			&& dynamic_cast<const CGResource *>(obj)->resourceID() == EGameResID::GOLD;
 
 
+		auto rewardable = dynamic_cast<const Rewardable::Interface *>(obj);
+
+		if(rewardable)
+		{
+			for(auto & info : rewardable->configuration.info)
+				if(info.reward.resources[EGameResID::GOLD] > 0)
+					isGoldPile = true;
+		}
+
 		if(isGoldPile
 		if(isGoldPile
 			|| obj->ID == Obj::TREASURE_CHEST
 			|| obj->ID == Obj::TREASURE_CHEST
 			|| obj->ID == Obj::CAMPFIRE
 			|| obj->ID == Obj::CAMPFIRE

+ 1 - 1
AI/Nullkiller/Engine/Nullkiller.cpp

@@ -399,7 +399,7 @@ void Nullkiller::makeTurn()
 
 
 		auto selectedTasks = buildPlan(bestTasks);
 		auto selectedTasks = buildPlan(bestTasks);
 
 
-		logAi->debug("Decission madel in %ld", timeElapsed(start));
+		logAi->debug("Decision madel in %ld", timeElapsed(start));
 
 
 		if(selectedTasks.empty())
 		if(selectedTasks.empty())
 		{
 		{

+ 164 - 37
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -33,7 +33,7 @@
 namespace NKAI
 namespace NKAI
 {
 {
 
 
-#define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
+#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
 #define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
 const float MIN_CRITICAL_VALUE = 2.0f;
 const float MIN_CRITICAL_VALUE = 2.0f;
 
 
@@ -137,16 +137,19 @@ TResources getCreatureBankResources(const CGObjectInstance * target, const CGHer
 	return sum > 1 ? result / sum : result;
 	return sum > 1 ? result / sum : result;
 }
 }
 
 
-uint64_t getResourcesGoldReward(const TResources & res)
+int32_t getResourcesGoldReward(const TResources & res)
 {
 {
-	int nonGoldResources = res[EGameResID::GEMS]
-		+ res[EGameResID::SULFUR]
-		+ res[EGameResID::WOOD]
-		+ res[EGameResID::ORE]
-		+ res[EGameResID::CRYSTAL]
-		+ res[EGameResID::MERCURY];
-
-	return res[EGameResID::GOLD] + 100 * nonGoldResources;
+	int32_t result = 0;
+
+	for(EGameResID r = EGameResID(0); r < EGameResID::COUNT; r.advance(1))
+	{
+		if(res[r] > 0)
+		{
+			result += r == EGameResID::GOLD ? res[r] : res[r] * 100;
+		}
+	}
+
+	return result;
 }
 }
 
 
 uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
 uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
@@ -250,9 +253,9 @@ int getDwellingArmyCost(const CGObjectInstance * target)
 	return cost;
 	return cost;
 }
 }
 
 
-static uint64_t evaluateArtifactArmyValue(const CArtifactInstance * art)
+static uint64_t evaluateArtifactArmyValue(const CArtifact * art)
 {
 {
-	if(art->artType->getId() == ArtifactID::SPELL_SCROLL)
+	if(art->getId() == ArtifactID::SPELL_SCROLL)
 		return 1500;
 		return 1500;
 
 
 	auto statsValue =
 	auto statsValue =
@@ -267,7 +270,7 @@ static uint64_t evaluateArtifactArmyValue(const CArtifactInstance * art)
 
 
 	auto classValue = 0;
 	auto classValue = 0;
 
 
-	switch(art->artType->aClass)
+	switch(art->aClass)
 	{
 	{
 	case CArtifact::EartClass::ART_MINOR:
 	case CArtifact::EartClass::ART_MINOR:
 		classValue = 1000;
 		classValue = 1000;
@@ -315,7 +318,7 @@ uint64_t RewardEvaluator::getArmyReward(
 	case Obj::WARRIORS_TOMB:
 	case Obj::WARRIORS_TOMB:
 		return 1000;
 		return 1000;
 	case Obj::ARTIFACT:
 	case Obj::ARTIFACT:
-		return evaluateArtifactArmyValue(dynamic_cast<const CGArtifact *>(target)->storedArtifact);
+		return evaluateArtifactArmyValue(dynamic_cast<const CGArtifact *>(target)->storedArtifact->artType);
 	case Obj::DRAGON_UTOPIA:
 	case Obj::DRAGON_UTOPIA:
 		return 10000;
 		return 10000;
 	case Obj::HERO:
 	case Obj::HERO:
@@ -328,8 +331,46 @@ uint64_t RewardEvaluator::getArmyReward(
 	case Obj::MAGIC_SPRING:
 	case Obj::MAGIC_SPRING:
 		return getManaRecoveryArmyReward(hero);
 		return getManaRecoveryArmyReward(hero);
 	default:
 	default:
-		return 0;
+		break;
 	}
 	}
+
+	auto rewardable = dynamic_cast<const Rewardable::Interface *>(target);
+
+	if(rewardable)
+	{
+		auto totalValue = 0;
+		
+		for(int index : rewardable->getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT))
+		{
+			auto & info = rewardable->configuration.info[index];
+
+			auto rewardValue = 0;
+
+			if(!info.reward.artifacts.empty())
+			{
+				for(auto artID : info.reward.artifacts)
+				{
+					const CArtifact * art = dynamic_cast<const CArtifact *>(VLC->artifacts()->getById(artID));
+
+					rewardValue += evaluateArtifactArmyValue(art);
+				}
+			}
+
+			if(!info.reward.creatures.empty())
+			{
+				for(auto stackInfo : info.reward.creatures)
+				{
+					rewardValue += stackInfo.getType()->getAIValue() * stackInfo.getCount();
+				}
+			}
+
+			totalValue += rewardValue > 0 ? rewardValue / (info.reward.artifacts.size() + info.reward.creatures.size()) : 0;
+		}
+
+		return totalValue;
+	}
+
+	return 0;
 }
 }
 
 
 uint64_t RewardEvaluator::getArmyGrowth(
 uint64_t RewardEvaluator::getArmyGrowth(
@@ -473,7 +514,24 @@ uint64_t RewardEvaluator::getManaRecoveryArmyReward(const CGHeroInstance * hero)
 	return ai->heroManager->getMagicStrength(hero) * 10000 * (1.0f - std::sqrt(static_cast<float>(hero->mana) / hero->manaLimit()));
 	return ai->heroManager->getMagicStrength(hero) * 10000 * (1.0f - std::sqrt(static_cast<float>(hero->mana) / hero->manaLimit()));
 }
 }
 
 
-float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) const
+float RewardEvaluator::getResourceRequirementStrength(const TResources & res) const
+{
+	float sum = 0.0f;
+
+	for(TResources::nziterator it(res); it.valid(); it++)
+	{
+		//Evaluate resources used for construction. Gold is evaluated separately.
+		if(it->resType != EGameResID::GOLD)
+		{
+			sum += 0.1f * it->resVal * getResourceRequirementStrength(it->resType)
+				+ 0.05f * it->resVal * getTotalResourceRequirementStrength(it->resType);
+		}
+	}
+
+	return sum;
+}
+
+float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target, const CGHeroInstance * hero) const
 {
 {
 	if(!target)
 	if(!target)
 		return 0;
 		return 0;
@@ -491,24 +549,17 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
 	case Obj::RESOURCE:
 	case Obj::RESOURCE:
 	{
 	{
 		auto resource = dynamic_cast<const CGResource *>(target);
 		auto resource = dynamic_cast<const CGResource *>(target);
-		return resource->resourceID() == EGameResID::GOLD
-			? 0
-			: 0.2f * getTotalResourceRequirementStrength(resource->resourceID()) + 0.4f * getResourceRequirementStrength(resource->resourceID());
+		TResources res;
+		res[resource->resourceID()] = resource->amount;
+		
+		return getResourceRequirementStrength(res);
 	}
 	}
 
 
 	case Obj::CREATURE_BANK:
 	case Obj::CREATURE_BANK:
 	{
 	{
 		auto resourceReward = getCreatureBankResources(target, nullptr);
 		auto resourceReward = getCreatureBankResources(target, nullptr);
-		float sum = 0.0f;
-		for (TResources::nziterator it (resourceReward); it.valid(); it++)
-		{
-			//Evaluate resources used for construction. Gold is evaluated separately.
-			if (it->resType != EGameResID::GOLD)
-			{
-				sum += 0.1f * it->resVal * getResourceRequirementStrength(it->resType);
-			}
-		}
-		return sum;
+		
+		return getResourceRequirementStrength(resourceReward);
 	}
 	}
 
 
 	case Obj::TOWN:
 	case Obj::TOWN:
@@ -547,8 +598,24 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
 		return 0.6f;
 		return 0.6f;
 
 
 	default:
 	default:
-		return 0;
+		break;
 	}
 	}
+
+	auto rewardable = dynamic_cast<const Rewardable::Interface *>(target);
+
+	if(rewardable && hero)
+	{
+		auto resourceReward = 0.0f;
+
+		for(int index : rewardable->getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT))
+		{
+			resourceReward += getResourceRequirementStrength(rewardable->configuration.info[index].reward.resources);
+		}
+
+		return resourceReward;
+	}
+
+	return 0;
 }
 }
 
 
 float RewardEvaluator::evaluateWitchHutSkillScore(const CGObjectInstance * hut, const CGHeroInstance * hero, HeroRole role) const
 float RewardEvaluator::evaluateWitchHutSkillScore(const CGObjectInstance * hut, const CGHeroInstance * hero, HeroRole role) const
@@ -593,11 +660,11 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
 	case Obj::ARENA:
 	case Obj::ARENA:
 		return 2;
 		return 2;
 	case Obj::SHRINE_OF_MAGIC_INCANTATION:
 	case Obj::SHRINE_OF_MAGIC_INCANTATION:
-		return 0.2f;
+		return 0.25f;
 	case Obj::SHRINE_OF_MAGIC_GESTURE:
 	case Obj::SHRINE_OF_MAGIC_GESTURE:
-		return 0.3f;
+		return 1.0f;
 	case Obj::SHRINE_OF_MAGIC_THOUGHT:
 	case Obj::SHRINE_OF_MAGIC_THOUGHT:
-		return 0.5f;
+		return 2.0f;
 	case Obj::LIBRARY_OF_ENLIGHTENMENT:
 	case Obj::LIBRARY_OF_ENLIGHTENMENT:
 		return 8;
 		return 8;
 	case Obj::WITCH_HUT:
 	case Obj::WITCH_HUT:
@@ -606,14 +673,56 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
 		//Can contains experience, spells, or skills (only on custom maps)
 		//Can contains experience, spells, or skills (only on custom maps)
 		return 2.5f;
 		return 2.5f;
 	case Obj::PYRAMID:
 	case Obj::PYRAMID:
-		return 3.0f;
+		return 6.0f;
 	case Obj::HERO:
 	case Obj::HERO:
 		return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
 		return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
 			? enemyHeroEliminationSkillRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->level
 			? enemyHeroEliminationSkillRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->level
 			: 0;
 			: 0;
+
 	default:
 	default:
-		return 0;
+		break;
+	}
+
+	auto rewardable = dynamic_cast<const Rewardable::Interface *>(target);
+
+	if(rewardable)
+	{
+		auto totalValue = 0.0f;
+
+		for(int index : rewardable->getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT))
+		{
+			auto & info = rewardable->configuration.info[index];
+
+			auto rewardValue = 0.0f;
+
+			if(!info.reward.spells.empty())
+			{
+				for(auto spellID : info.reward.spells)
+				{
+					const spells::Spell * spell = VLC->spells()->getById(spellID);
+						
+					if(hero->canLearnSpell(spell) && !hero->spellbookContainsSpell(spellID))
+					{
+						rewardValue += std::sqrt(spell->getLevel()) / 4.0f;
+					}
+				}
+
+				totalValue += rewardValue / info.reward.spells.size();
+			}
+
+			if(!info.reward.primary.empty())
+			{
+				for(auto value : info.reward.primary)
+				{
+					totalValue += value;
+				}
+			}
+		}
+
+		return totalValue;
 	}
 	}
+
+	return 0;
 }
 }
 
 
 const HitMapInfo & RewardEvaluator::getEnemyHeroDanger(const int3 & tile, uint8_t turn) const
 const HitMapInfo & RewardEvaluator::getEnemyHeroDanger(const int3 & tile, uint8_t turn) const
@@ -697,8 +806,26 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
 			? heroEliminationBonus + enemyArmyEliminationGoldRewardRatio * getArmyCost(dynamic_cast<const CGHeroInstance *>(target))
 			? heroEliminationBonus + enemyArmyEliminationGoldRewardRatio * getArmyCost(dynamic_cast<const CGHeroInstance *>(target))
 			: 0;
 			: 0;
 	default:
 	default:
-		return 0;
+		break;
 	}
 	}
+
+	auto rewardable = dynamic_cast<const Rewardable::Interface *>(target);
+
+	if(rewardable)
+	{
+		auto goldReward = 0;
+
+		for(int index : rewardable->getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT))
+		{
+			auto & info = rewardable->configuration.info[index];
+
+			goldReward += getResourcesGoldReward(info.reward.resources);
+		}
+
+		return goldReward;
+	}
+
+	return 0;
 }
 }
 
 
 class HeroExchangeEvaluator : public IEvaluationContextBuilder
 class HeroExchangeEvaluator : public IEvaluationContextBuilder
@@ -1000,7 +1127,7 @@ public:
 		evaluationContext.goldReward += 7 * bi.dailyIncome[EGameResID::GOLD] / 2; // 7 day income but half we already have
 		evaluationContext.goldReward += 7 * bi.dailyIncome[EGameResID::GOLD] / 2; // 7 day income but half we already have
 		evaluationContext.heroRole = HeroRole::MAIN;
 		evaluationContext.heroRole = HeroRole::MAIN;
 		evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount;
 		evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount;
-		evaluationContext.goldCost += bi.buildCostWithPrerequisits[EGameResID::GOLD];
+		evaluationContext.goldCost += bi.buildCostWithPrerequisites[EGameResID::GOLD];
 		evaluationContext.closestWayRatio = 1;
 		evaluationContext.closestWayRatio = 1;
 
 
 		if(bi.creatureID != CreatureID::NONE)
 		if(bi.creatureID != CreatureID::NONE)

+ 2 - 1
AI/Nullkiller/Engine/PriorityEvaluator.h

@@ -39,7 +39,8 @@ public:
 	int getGoldCost(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army) const;
 	int getGoldCost(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army) const;
 	float getEnemyHeroStrategicalValue(const CGHeroInstance * enemy) const;
 	float getEnemyHeroStrategicalValue(const CGHeroInstance * enemy) const;
 	float getResourceRequirementStrength(int resType) const;
 	float getResourceRequirementStrength(int resType) const;
-	float getStrategicalValue(const CGObjectInstance * target) const;
+	float getResourceRequirementStrength(const TResources & res) const;
+	float getStrategicalValue(const CGObjectInstance * target, const CGHeroInstance * hero = nullptr) const;
 	float getTotalResourceRequirementStrength(int resType) const;
 	float getTotalResourceRequirementStrength(int resType) const;
 	float evaluateWitchHutSkillScore(const CGObjectInstance * hut, const CGHeroInstance * hero, HeroRole role) const;
 	float evaluateWitchHutSkillScore(const CGObjectInstance * hut, const CGHeroInstance * hero, HeroRole role) const;
 	float getSkillReward(const CGObjectInstance * target, const CGHeroInstance * hero, HeroRole role) const;
 	float getSkillReward(const CGObjectInstance * target, const CGHeroInstance * hero, HeroRole role) const;

+ 2 - 2
AI/Nullkiller/Goals/BuyArmy.cpp

@@ -34,13 +34,13 @@ void BuyArmy::accept(AIGateway * ai)
 	ui64 valueBought = 0;
 	ui64 valueBought = 0;
 	//buy the stacks with largest AI value
 	//buy the stacks with largest AI value
 
 
-	auto upgradeSuccessfull = ai->makePossibleUpgrades(town);
+	auto upgradeSuccessful = ai->makePossibleUpgrades(town);
 
 
 	auto armyToBuy = ai->nullkiller->armyManager->getArmyAvailableToBuy(town->getUpperArmy(), town);
 	auto armyToBuy = ai->nullkiller->armyManager->getArmyAvailableToBuy(town->getUpperArmy(), town);
 
 
 	if(armyToBuy.empty())
 	if(armyToBuy.empty())
 	{
 	{
-		if(upgradeSuccessfull)
+		if(upgradeSuccessful)
 			return;
 			return;
 
 
 		throw cannotFulfillGoalException("No creatures to buy.");
 		throw cannotFulfillGoalException("No creatures to buy.");

+ 1 - 1
AI/Nullkiller/Goals/CaptureObject.h

@@ -31,7 +31,7 @@ namespace Goals
 		{
 		{
 			objid = obj->id.getNum();
 			objid = obj->id.getNum();
 			tile = obj->visitablePos();
 			tile = obj->visitablePos();
-			name = obj->getObjectName();
+			name = obj->typeName;
 		}
 		}
 
 
 		bool operator==(const CaptureObject & other) const override;
 		bool operator==(const CaptureObject & other) const override;

+ 6 - 6
AI/Nullkiller/Goals/ExecuteHeroChain.cpp

@@ -26,7 +26,7 @@ ExecuteHeroChain::ExecuteHeroChain(const AIPath & path, const CGObjectInstance *
 	if(obj)
 	if(obj)
 	{
 	{
 		objid = obj->id.getNum();
 		objid = obj->id.getNum();
-		targetName = obj->getObjectName() + tile.toString();
+		targetName = obj->typeName + tile.toString();
 	}
 	}
 	else
 	else
 	{
 	{
@@ -106,7 +106,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 
 
 		if(!heroPtr.validAndSet())
 		if(!heroPtr.validAndSet())
 		{
 		{
-			logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.name);
+			logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.name());
 
 
 			return;
 			return;
 		}
 		}
@@ -143,7 +143,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 					
 					
 					if(!heroPtr.validAndSet())
 					if(!heroPtr.validAndSet())
 					{
 					{
-						logAi->error("Hero %s was lost trying to execute special action. Exit hero chain.", heroPtr.name);
+						logAi->error("Hero %s was lost trying to execute special action. Exit hero chain.", heroPtr.name());
 
 
 						return;
 						return;
 					}
 					}
@@ -204,7 +204,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 					{
 					{
 						if(!heroPtr.validAndSet())
 						if(!heroPtr.validAndSet())
 						{
 						{
-							logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.name);
+							logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.name());
 
 
 							return;
 							return;
 						}
 						}
@@ -234,7 +234,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 			if(node->turns == 0)
 			if(node->turns == 0)
 			{
 			{
 				logAi->error(
 				logAi->error(
-					"Unable to complete chain. Expected hero %s to arive to %s but he is at %s",
+					"Unable to complete chain. Expected hero %s to arrive to %s but he is at %s",
 					hero->getNameTranslated(),
 					hero->getNameTranslated(),
 					node->coord.toString(),
 					node->coord.toString(),
 					hero->visitablePos().toString());
 					hero->visitablePos().toString());
@@ -250,7 +250,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 		{
 		{
 			if(!heroPtr.validAndSet())
 			if(!heroPtr.validAndSet())
 			{
 			{
-				logAi->debug("Hero %s was killed while attempting to reach %s", heroPtr.name, node->coord.toString());
+				logAi->debug("Hero %s was killed while attempting to reach %s", heroPtr.name(), node->coord.toString());
 
 
 				return;
 				return;
 			}
 			}

+ 29 - 29
AI/Nullkiller/Helpers/ExplorationHelper.cpp

@@ -70,11 +70,8 @@ bool ExplorationHelper::scanMap()
 	int3 mapSize = cbp->getMapSize();
 	int3 mapSize = cbp->getMapSize();
 	int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y);
 	int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y);
 
 
-	std::vector<int3> from;
-	std::vector<int3> to;
-
-	from.reserve(perimeter);
-	to.reserve(perimeter);
+	std::vector<int3> edgeTiles;
+	edgeTiles.reserve(perimeter);
 
 
 	foreach_tile_pos([&](const int3 & pos)
 	foreach_tile_pos([&](const int3 & pos)
 		{
 		{
@@ -91,13 +88,13 @@ bool ExplorationHelper::scanMap()
 					});
 					});
 
 
 				if(hasInvisibleNeighbor)
 				if(hasInvisibleNeighbor)
-					from.push_back(pos);
+					edgeTiles.push_back(pos);
 			}
 			}
 		});
 		});
 
 
 	logAi->debug("Exploration scan visible area perimeter for hero %s", hero->getNameTranslated());
 	logAi->debug("Exploration scan visible area perimeter for hero %s", hero->getNameTranslated());
 
 
-	for(const int3 & tile : from)
+	for(const int3 & tile : edgeTiles)
 	{
 	{
 		scanTile(tile);
 		scanTile(tile);
 	}
 	}
@@ -108,19 +105,36 @@ bool ExplorationHelper::scanMap()
 	}
 	}
 
 
 	allowDeadEndCancellation = false;
 	allowDeadEndCancellation = false;
+	logAi->debug("Exploration scan all possible tiles for hero %s", hero->getNameTranslated());
+
+	boost::multi_array<ui8, 3> potentialTiles = ts->fogOfWarMap;
+	std::vector<int3> tilesToExploreFrom = edgeTiles;
 
 
+	// WARNING: POTENTIAL BUG
+	// AI attempts to move to any tile within sight radius to reveal some new tiles
+	// however sight radius is circular, while this method assumes square radius
+	// standing on the edge of a square will NOT reveal tile in opposite corner
 	for(int i = 0; i < sightRadius; i++)
 	for(int i = 0; i < sightRadius; i++)
 	{
 	{
-		getVisibleNeighbours(from, to);
-		vstd::concatenate(from, to);
-		vstd::removeDuplicates(from);
-	}
+		std::vector<int3> newTilesToExploreFrom;
 
 
-	logAi->debug("Exploration scan all possible tiles for hero %s", hero->getNameTranslated());
+		for(const int3 & tile : tilesToExploreFrom)
+		{
+			foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour)
+			{
+				if(potentialTiles[neighbour.z][neighbour.x][neighbour.y])
+				{
+					newTilesToExploreFrom.push_back(neighbour);
+					potentialTiles[neighbour.z][neighbour.x][neighbour.y] = false;
+				}
+			});
+		}
+		for(const int3 & tile : newTilesToExploreFrom)
+		{
+			scanTile(tile);
+		}
 
 
-	for(const int3 & tile : from)
-	{
-		scanTile(tile);
+		std::swap(tilesToExploreFrom, newTilesToExploreFrom);
 	}
 	}
 
 
 	return !bestGoal->invalid();
 	return !bestGoal->invalid();
@@ -172,20 +186,6 @@ void ExplorationHelper::scanTile(const int3 & tile)
 	}
 	}
 }
 }
 
 
-void ExplorationHelper::getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out) const
-{
-	for(const int3 & tile : tiles)
-	{
-		foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour)
-			{
-				if(ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y])
-				{
-					out.push_back(neighbour);
-				}
-			});
-	}
-}
-
 int ExplorationHelper::howManyTilesWillBeDiscovered(const int3 & pos) const
 int ExplorationHelper::howManyTilesWillBeDiscovered(const int3 & pos) const
 {
 {
 	int ret = 0;
 	int ret = 0;

+ 0 - 1
AI/Nullkiller/Helpers/ExplorationHelper.h

@@ -46,7 +46,6 @@ public:
 private:
 private:
 	void scanTile(const int3 & tile);
 	void scanTile(const int3 & tile);
 	bool hasReachableNeighbor(const int3 & pos) const;
 	bool hasReachableNeighbor(const int3 & pos) const;
-	void getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out) const;
 };
 };
 
 
 }
 }

+ 26 - 28
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -27,8 +27,8 @@ namespace NKAI
 std::shared_ptr<boost::multi_array<AIPathNode, 4>> AISharedStorage::shared;
 std::shared_ptr<boost::multi_array<AIPathNode, 4>> AISharedStorage::shared;
 uint64_t AISharedStorage::version = 0;
 uint64_t AISharedStorage::version = 0;
 boost::mutex AISharedStorage::locker;
 boost::mutex AISharedStorage::locker;
-std::set<int3> commitedTiles;
-std::set<int3> commitedTilesInitial;
+std::set<int3> committedTiles;
+std::set<int3> committedTilesInitial;
 
 
 
 
 const uint64_t FirstActorMask = 1;
 const uint64_t FirstActorMask = 1;
@@ -36,7 +36,7 @@ const uint64_t MIN_ARMY_STRENGTH_FOR_CHAIN = 5000;
 const uint64_t MIN_ARMY_STRENGTH_FOR_NEXT_ACTOR = 1000;
 const uint64_t MIN_ARMY_STRENGTH_FOR_NEXT_ACTOR = 1000;
 const uint64_t CHAIN_MAX_DEPTH = 4;
 const uint64_t CHAIN_MAX_DEPTH = 4;
 
 
-const bool DO_NOT_SAVE_TO_COMMITED_TILES = false;
+const bool DO_NOT_SAVE_TO_COMMITTED_TILES = false;
 
 
 AISharedStorage::AISharedStorage(int3 sizes)
 AISharedStorage::AISharedStorage(int3 sizes)
 {
 {
@@ -94,7 +94,7 @@ void AIPathNode::addSpecialAction(std::shared_ptr<const SpecialAction> action)
 AINodeStorage::AINodeStorage(const Nullkiller * ai, const int3 & Sizes)
 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)
 {
 {
-	accesibility = std::make_unique<boost::multi_array<EPathAccessibility, 4>>(
+	accessibility = std::make_unique<boost::multi_array<EPathAccessibility, 4>>(
 		boost::extents[sizes.z][sizes.x][sizes.y][EPathfindingLayer::NUM_LAYERS]);
 		boost::extents[sizes.z][sizes.x][sizes.y][EPathfindingLayer::NUM_LAYERS]);
 
 
 	dangerEvaluator.reset(new FuzzyHelper(ai));
 	dangerEvaluator.reset(new FuzzyHelper(ai));
@@ -157,7 +157,7 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
 void AINodeStorage::clear()
 void AINodeStorage::clear()
 {
 {
 	actors.clear();
 	actors.clear();
-	commitedTiles.clear();
+	committedTiles.clear();
 	heroChainPass = EHeroChainPass::INITIAL;
 	heroChainPass = EHeroChainPass::INITIAL;
 	heroChainTurn = 0;
 	heroChainTurn = 0;
 	heroChainMaxTurns = 1;
 	heroChainMaxTurns = 1;
@@ -276,7 +276,7 @@ void AINodeStorage::commit(
 	int turn, 
 	int turn, 
 	int movementLeft, 
 	int movementLeft, 
 	float cost,
 	float cost,
-	bool saveToCommited) const
+	bool saveToCommitted) const
 {
 {
 	destination->action = action;
 	destination->action = action;
 	destination->setCost(cost);
 	destination->setCost(cost);
@@ -290,7 +290,7 @@ void AINodeStorage::commit(
 
 
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 	logAi->trace(
 	logAi->trace(
-		"Commited %s -> %s, layer: %d, cost: %f, turn: %s, mp: %d, hero: %s, mask: %x, army: %lld",
+		"Committed %s -> %s, layer: %d, cost: %f, turn: %s, mp: %d, hero: %s, mask: %x, army: %lld",
 		source->coord.toString(),
 		source->coord.toString(),
 		destination->coord.toString(),
 		destination->coord.toString(),
 		destination->layer,
 		destination->layer,
@@ -302,9 +302,9 @@ void AINodeStorage::commit(
 		destination->actor->armyValue);
 		destination->actor->armyValue);
 #endif
 #endif
 
 
-	if(saveToCommited && destination->turns <= heroChainTurn)
+	if(saveToCommitted && destination->turns <= heroChainTurn)
 	{
 	{
-		commitedTiles.insert(destination->coord);
+		committedTiles.insert(destination->coord);
 	}
 	}
 
 
 	if(destination->turns == source->turns)
 	if(destination->turns == source->turns)
@@ -320,11 +320,9 @@ void AINodeStorage::calculateNeighbours(
 	const PathfinderConfig * pathfinderConfig,
 	const PathfinderConfig * pathfinderConfig,
 	const CPathfinderHelper * pathfinderHelper)
 	const CPathfinderHelper * pathfinderHelper)
 {
 {
-	std::vector<int3> accessibleNeighbourTiles;
+	NeighbourTilesVector accessibleNeighbourTiles;
 
 
 	result.clear();
 	result.clear();
-	accessibleNeighbourTiles.reserve(8);
-
 	pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);
 	pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);
 
 
 	const AIPathNode * srcNode = getAINode(source.node);
 	const AIPathNode * srcNode = getAINode(source.node);
@@ -376,7 +374,7 @@ bool AINodeStorage::increaseHeroChainTurnLimit()
 		return false;
 		return false;
 
 
 	heroChainTurn++;
 	heroChainTurn++;
-	commitedTiles.clear();
+	committedTiles.clear();
 
 
 	for(auto layer : phisycalLayers)
 	for(auto layer : phisycalLayers)
 	{
 	{
@@ -386,7 +384,7 @@ bool AINodeStorage::increaseHeroChainTurnLimit()
 				{
 				{
 					if(node.turns <= heroChainTurn && node.action != EPathNodeAction::UNKNOWN)
 					if(node.turns <= heroChainTurn && node.action != EPathNodeAction::UNKNOWN)
 					{
 					{
-						commitedTiles.insert(pos);
+						committedTiles.insert(pos);
 						return true;
 						return true;
 					}
 					}
 
 
@@ -547,7 +545,7 @@ bool AINodeStorage::calculateHeroChain()
 	heroChainPass = EHeroChainPass::CHAIN;
 	heroChainPass = EHeroChainPass::CHAIN;
 	heroChain.clear();
 	heroChain.clear();
 
 
-	std::vector<int3> data(commitedTiles.begin(), commitedTiles.end());
+	std::vector<int3> data(committedTiles.begin(), committedTiles.end());
 
 
 	if(data.size() > 100)
 	if(data.size() > 100)
 	{
 	{
@@ -578,7 +576,7 @@ bool AINodeStorage::calculateHeroChain()
 		task.flushResult(heroChain);
 		task.flushResult(heroChain);
 	}
 	}
 
 
-	commitedTiles.clear();
+	committedTiles.clear();
 
 
 	return !heroChain.empty();
 	return !heroChain.empty();
 }
 }
@@ -594,7 +592,7 @@ bool AINodeStorage::selectFirstActor()
 	});
 	});
 
 
 	chainMask = strongest->chainMask;
 	chainMask = strongest->chainMask;
-	commitedTilesInitial = commitedTiles;
+	committedTilesInitial = committedTiles;
 
 
 	return true;
 	return true;
 }
 }
@@ -629,7 +627,7 @@ bool AINodeStorage::selectNextActor()
 			return false;
 			return false;
 
 
 		chainMask = nextActor->get()->chainMask;
 		chainMask = nextActor->get()->chainMask;
-		commitedTiles = commitedTilesInitial;
+		committedTiles = committedTilesInitial;
 
 
 		return true;
 		return true;
 	}
 	}
@@ -656,7 +654,7 @@ void HeroChainCalculationTask::cleanupInefectiveChains(std::vector<ExchangeCandi
 		if(isNotEffective)
 		if(isNotEffective)
 		{
 		{
 			logAi->trace(
 			logAi->trace(
-				"Skip exchange %s[%x] -> %s[%x] at %s is ineficient",
+				"Skip exchange %s[%x] -> %s[%x] at %s is inefficient",
 				chainInfo.otherParent->actor->toString(), 
 				chainInfo.otherParent->actor->toString(), 
 				chainInfo.otherParent->actor->chainMask,
 				chainInfo.otherParent->actor->chainMask,
 				chainInfo.carrierParent->actor->toString(),
 				chainInfo.carrierParent->actor->toString(),
@@ -756,7 +754,7 @@ void HeroChainCalculationTask::calculateHeroChain(
 			if(hasLessMp && hasLessExperience)
 			if(hasLessMp && hasLessExperience)
 			{
 			{
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
-				logAi->trace("Exchange at %s is ineficient. Blocked.", carrier->coord.toString());
+				logAi->trace("Exchange at %s is inefficient. Blocked.", carrier->coord.toString());
 #endif
 #endif
 				return;
 				return;
 			}
 			}
@@ -825,7 +823,7 @@ void HeroChainCalculationTask::addHeroChain(const std::vector<ExchangeCandidate>
 			chainInfo.turns,
 			chainInfo.turns,
 			chainInfo.moveRemains, 
 			chainInfo.moveRemains, 
 			chainInfo.getCost(),
 			chainInfo.getCost(),
-			DO_NOT_SAVE_TO_COMMITED_TILES);
+			DO_NOT_SAVE_TO_COMMITTED_TILES);
 
 
 		if(carrier->specialAction || carrier->chainOther)
 		if(carrier->specialAction || carrier->chainOther)
 		{
 		{
@@ -1029,7 +1027,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
 	return neighbours;
 	return neighbours;
 }
 }
 
 
-struct TowmPortalFinder
+struct TownPortalFinder
 {
 {
 	const std::vector<CGPathNode *> & initialNodes;
 	const std::vector<CGPathNode *> & initialNodes;
 	MasteryLevel::Type townPortalSkillLevel;
 	MasteryLevel::Type townPortalSkillLevel;
@@ -1042,7 +1040,7 @@ struct TowmPortalFinder
 	SpellID spellID;
 	SpellID spellID;
 	const CSpell * townPortal;
 	const CSpell * townPortal;
 
 
-	TowmPortalFinder(
+	TownPortalFinder(
 		const ChainActor * actor,
 		const ChainActor * actor,
 		const std::vector<CGPathNode *> & initialNodes,
 		const std::vector<CGPathNode *> & initialNodes,
 		std::vector<const CGTownInstance *> targetTowns,
 		std::vector<const CGTownInstance *> targetTowns,
@@ -1119,7 +1117,7 @@ struct TowmPortalFinder
 				bestNode->turns,
 				bestNode->turns,
 				bestNode->moveRemains - movementNeeded,
 				bestNode->moveRemains - movementNeeded,
 				movementCost,
 				movementCost,
-				DO_NOT_SAVE_TO_COMMITED_TILES);
+				DO_NOT_SAVE_TO_COMMITTED_TILES);
 
 
 			node->theNodeBefore = bestNode;
 			node->theNodeBefore = bestNode;
 			node->addSpecialAction(std::make_shared<AIPathfinding::TownPortalAction>(targetTown));
 			node->addSpecialAction(std::make_shared<AIPathfinding::TownPortalAction>(targetTown));
@@ -1148,7 +1146,7 @@ void AINodeStorage::calculateTownPortal(
 		return; // no towns no need to run loop further
 		return; // no towns no need to run loop further
 	}
 	}
 
 
-	TowmPortalFinder townPortalFinder(actor, initialNodes, towns, this);
+	TownPortalFinder townPortalFinder(actor, initialNodes, towns, this);
 
 
 	if(townPortalFinder.actorCanCastTownPortal())
 	if(townPortalFinder.actorCanCastTownPortal())
 	{
 	{
@@ -1281,7 +1279,7 @@ bool AINodeStorage::isOtherChainBetter(
 		{
 		{
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 			logAi->trace(
 			logAi->trace(
-				"Block ineficient battle move %s->%s, hero: %s[%X], army %lld, mp diff: %i",
+				"Block inefficient battle move %s->%s, hero: %s[%X], army %lld, mp diff: %i",
 				source->coord.toString(),
 				source->coord.toString(),
 				candidateNode.coord.toString(),
 				candidateNode.coord.toString(),
 				candidateNode.actor->hero->getNameTranslated(),
 				candidateNode.actor->hero->getNameTranslated(),
@@ -1305,7 +1303,7 @@ bool AINodeStorage::isOtherChainBetter(
 	{
 	{
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 		logAi->trace(
 		logAi->trace(
-			"Block ineficient move because of stronger army %s->%s, hero: %s[%X], army %lld, mp diff: %i",
+			"Block inefficient move because of stronger army %s->%s, hero: %s[%X], army %lld, mp diff: %i",
 			source->coord.toString(),
 			source->coord.toString(),
 			candidateNode.coord.toString(),
 			candidateNode.coord.toString(),
 			candidateNode.actor->hero->getNameTranslated(),
 			candidateNode.actor->hero->getNameTranslated(),
@@ -1331,7 +1329,7 @@ bool AINodeStorage::isOtherChainBetter(
 
 
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 			logAi->trace(
 			logAi->trace(
-				"Block ineficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
+				"Block inefficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
 				source->coord.toString(),
 				source->coord.toString(),
 				candidateNode.coord.toString(),
 				candidateNode.coord.toString(),
 				candidateNode.actor->hero->getNameTranslated(),
 				candidateNode.actor->hero->getNameTranslated(),

+ 10 - 6
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -23,6 +23,8 @@ constexpr int NKAI_GRAPH_TRACE_LEVEL = 0;
 #include "Actions/SpecialAction.h"
 #include "Actions/SpecialAction.h"
 #include "Actors.h"
 #include "Actors.h"
 
 
+#include <boost/container/small_vector.hpp>
+
 namespace NKAI
 namespace NKAI
 {
 {
 namespace AIPathfinding
 namespace AIPathfinding
@@ -85,7 +87,9 @@ struct AIPathNodeInfo
 
 
 struct AIPath
 struct AIPath
 {
 {
-	std::vector<AIPathNodeInfo> nodes;
+	using NodesVector = boost::container::small_vector<AIPathNodeInfo, 16>;
+
+	NodesVector nodes;
 	uint64_t targetObjectDanger;
 	uint64_t targetObjectDanger;
 	uint64_t armyLoss;
 	uint64_t armyLoss;
 	uint64_t targetObjectArmyLoss;
 	uint64_t targetObjectArmyLoss;
@@ -165,7 +169,7 @@ class AINodeStorage : public INodeStorage
 private:
 private:
 	int3 sizes;
 	int3 sizes;
 
 
-	std::unique_ptr<boost::multi_array<EPathAccessibility, 4>> accesibility;
+	std::unique_ptr<boost::multi_array<EPathAccessibility, 4>> accessibility;
 
 
 	const CPlayerSpecificInfoCallback * cb;
 	const CPlayerSpecificInfoCallback * cb;
 	const Nullkiller * ai;
 	const Nullkiller * ai;
@@ -214,7 +218,7 @@ public:
 		int turn,
 		int turn,
 		int movementLeft,
 		int movementLeft,
 		float cost,
 		float cost,
-		bool saveToCommited = true) const;
+		bool saveToCommitted = true) const;
 
 
 	inline const AIPathNode * getAINode(const CGPathNode * node) const
 	inline const AIPathNode * getAINode(const CGPathNode * node) const
 	{
 	{
@@ -257,7 +261,7 @@ public:
 		const AIPathNode & candidateNode,
 		const AIPathNode & candidateNode,
 		const AIPathNode & other) const;
 		const AIPathNode & other) const;
 
 
-	bool isMovementIneficient(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
+	bool isMovementInefficient(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
 	{
 	{
 		return hasBetterChain(source, destination);
 		return hasBetterChain(source, destination);
 	}
 	}
@@ -287,12 +291,12 @@ public:
 
 
 	inline EPathAccessibility getAccessibility(const int3 & tile, EPathfindingLayer layer) const
 	inline EPathAccessibility getAccessibility(const int3 & tile, EPathfindingLayer layer) const
 	{
 	{
-		return (*this->accesibility)[tile.z][tile.x][tile.y][layer];
+		return (*this->accessibility)[tile.z][tile.x][tile.y][layer];
 	}
 	}
 
 
 	inline void resetTile(const int3 & tile, EPathfindingLayer layer, EPathAccessibility tileAccessibility)
 	inline void resetTile(const int3 & tile, EPathfindingLayer layer, EPathAccessibility tileAccessibility)
 	{
 	{
-		(*this->accesibility)[tile.z][tile.x][tile.y][layer] = tileAccessibility;
+		(*this->accessibility)[tile.z][tile.x][tile.y][layer] = tileAccessibility;
 	}
 	}
 
 
 	inline int getBucket(const ChainActor * actor) const
 	inline int getBucket(const ChainActor * actor) const

+ 2 - 1
AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp

@@ -141,7 +141,8 @@ namespace AIPathfinding
 	{
 	{
 		SpellID summonBoat = SpellID::SUMMON_BOAT;
 		SpellID summonBoat = SpellID::SUMMON_BOAT;
 
 
-		return hero->getSpellCost(summonBoat.toSpell());
+		// FIXME: this should be hero->getSpellCost, however currently queries to bonus system are too slow
+		return summonBoat.toSpell()->getCost(0);
 	}
 	}
 }
 }
 
 

+ 1 - 1
AI/Nullkiller/Pathfinding/GraphPaths.cpp

@@ -118,7 +118,7 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil
 
 
 					targetNode.specialAction = compositeAction;
 					targetNode.specialAction = compositeAction;
 
 
-					auto targetGraphNode = graph.getNode(target);
+					const auto & targetGraphNode = graph.getNode(target);
 
 
 					if(targetGraphNode.objID.hasValue())
 					if(targetGraphNode.objID.hasValue())
 					{
 					{

+ 1 - 1
AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp

@@ -164,7 +164,7 @@ namespace AIPathfinding
 			if(hero->canCastThisSpell(summonBoatSpell)
 			if(hero->canCastThisSpell(summonBoatSpell)
 				&& hero->getSpellSchoolLevel(summonBoatSpell) >= MasteryLevel::ADVANCED)
 				&& hero->getSpellSchoolLevel(summonBoatSpell) >= MasteryLevel::ADVANCED)
 			{
 			{
-				// TODO: For lower school level we might need to check the existance of some boat
+				// TODO: For lower school level we might need to check the existence of some boat
 				summonableVirtualBoats[hero] = std::make_shared<SummonBoatAction>();
 				summonableVirtualBoats[hero] = std::make_shared<SummonBoatAction>();
 			}
 			}
 		}
 		}

+ 1 - 1
AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp

@@ -34,7 +34,7 @@ namespace AIPathfinding
 		const PathfinderConfig * pathfinderConfig,
 		const PathfinderConfig * pathfinderConfig,
 		CPathfinderHelper * pathfinderHelper) const
 		CPathfinderHelper * pathfinderHelper) const
 	{
 	{
-		if(nodeStorage->isMovementIneficient(source, destination))
+		if(nodeStorage->isMovementInefficient(source, destination))
 		{
 		{
 			destination.node->locked = true;
 			destination.node->locked = true;
 			destination.blocked = true;
 			destination.blocked = true;

+ 9 - 0
AI/StupidAI/StupidAI.cpp

@@ -77,6 +77,15 @@ public:
 		// FIXME: provide distance info for Jousting bonus
 		// FIXME: provide distance info for Jousting bonus
 		DamageEstimation retal;
 		DamageEstimation retal;
 		DamageEstimation dmg = cb->getBattle(battleID)->battleEstimateDamage(ourStack, s, 0, &retal);
 		DamageEstimation dmg = cb->getBattle(battleID)->battleEstimateDamage(ourStack, s, 0, &retal);
+		// Clip damage dealt to total stack health
+		auto totalHealth = s->getTotalHealth();
+		vstd::amin(dmg.damage.min, totalHealth);
+		vstd::amin(dmg.damage.max, totalHealth);
+
+		auto ourHealth = s->getTotalHealth();
+		vstd::amin(retal.damage.min, ourHealth);
+		vstd::amin(retal.damage.max, ourHealth);
+
 		adi = static_cast<int>((dmg.damage.min + dmg.damage.max) / 2);
 		adi = static_cast<int>((dmg.damage.min + dmg.damage.max) / 2);
 		adr = static_cast<int>((retal.damage.min + retal.damage.max) / 2);
 		adr = static_cast<int>((retal.damage.min + retal.damage.max) / 2);
 	}
 	}

+ 1 - 1
AI/VCAI/Goals/BuildThis.cpp

@@ -41,7 +41,7 @@ TSubgoal BuildThis::whatToDoToAchieve()
 			{
 			{
 			case EBuildingState::ALLOWED:
 			case EBuildingState::ALLOWED:
 				town = candidateTown;
 				town = candidateTown;
-				break; //TODO: look for prerequisites? this is not our reponsibility
+				break; //TODO: look for prerequisites? this is not our responsibility
 			default:
 			default:
 				continue;
 				continue;
 			}
 			}

+ 1 - 1
AI/VCAI/Goals/ClearWayTo.cpp

@@ -73,7 +73,7 @@ TGoalVec ClearWayTo::getAllPossibleSubgoals()
 	if(ret.empty())
 	if(ret.empty())
 	{
 	{
 		logAi->warn("There is no known way to clear the way to tile %s", tile.toString());
 		logAi->warn("There is no known way to clear the way to tile %s", tile.toString());
-		throw goalFulfilledException(sptr(ClearWayTo(tile))); //make sure asigned hero gets unlocked
+		throw goalFulfilledException(sptr(ClearWayTo(tile))); //make sure assigned hero gets unlocked
 	}
 	}
 
 
 	return ret;
 	return ret;

+ 1 - 1
AI/VCAI/Goals/VisitHero.cpp

@@ -53,7 +53,7 @@ TSubgoal VisitHero::whatToDoToAchieve()
 
 
 bool VisitHero::fulfillsMe(TSubgoal goal)
 bool VisitHero::fulfillsMe(TSubgoal goal)
 {
 {
-	//TODO: VisitObj shoudl not be used for heroes, but...
+	//TODO: VisitObj should not be used for heroes, but...
 	if(goal->goalType == VISIT_TILE)
 	if(goal->goalType == VISIT_TILE)
 	{
 	{
 		auto obj = cb->getObj(ObjectInstanceID(objid));
 		auto obj = cb->getObj(ObjectInstanceID(objid));

+ 1 - 1
AI/VCAI/Goals/Win.cpp

@@ -74,7 +74,7 @@ TSubgoal Win::whatToDoToAchieve()
 		case EventCondition::HAVE_BUILDING:
 		case EventCondition::HAVE_BUILDING:
 		{
 		{
 			// TODO build other buildings apart from Grail
 			// TODO build other buildings apart from Grail
-			// goal.objectType = buidingID to build
+			// goal.objectType = buildingID to build
 			// goal.object = optional, town in which building should be built
 			// goal.object = optional, town in which building should be built
 			// Represents "Improve town" condition from H3 (but unlike H3 it consists from 2 separate conditions)
 			// Represents "Improve town" condition from H3 (but unlike H3 it consists from 2 separate conditions)
 
 

+ 2 - 3
AI/VCAI/Pathfinding/AINodeStorage.cpp

@@ -162,10 +162,9 @@ void AINodeStorage::calculateNeighbours(
 	const PathfinderConfig * pathfinderConfig,
 	const PathfinderConfig * pathfinderConfig,
 	const CPathfinderHelper * pathfinderHelper)
 	const CPathfinderHelper * pathfinderHelper)
 {
 {
-	std::vector<int3> accessibleNeighbourTiles;
+	NeighbourTilesVector accessibleNeighbourTiles;
 
 
 	result.clear();
 	result.clear();
-	accessibleNeighbourTiles.reserve(8);
 
 
 	pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);
 	pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);
 
 
@@ -309,7 +308,7 @@ bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNode
 			{
 			{
 #ifdef VCMI_TRACE_PATHFINDER
 #ifdef VCMI_TRACE_PATHFINDER
 				logAi->trace(
 				logAi->trace(
-					"Block ineficient move %s:->%s, mask=%i, mp diff: %i",
+					"Block inefficient move %s:->%s, mask=%i, mp diff: %i",
 					source.coord.toString(),
 					source.coord.toString(),
 					destination.coord.toString(),
 					destination.coord.toString(),
 					destinationNode->chainMask,
 					destinationNode->chainMask,

+ 1 - 1
AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp

@@ -79,7 +79,7 @@ namespace AIPathfinding
 		if(hero->canCastThisSpell(summonBoatSpell)
 		if(hero->canCastThisSpell(summonBoatSpell)
 			&& hero->getSpellSchoolLevel(summonBoatSpell) >= MasteryLevel::ADVANCED)
 			&& hero->getSpellSchoolLevel(summonBoatSpell) >= MasteryLevel::ADVANCED)
 		{
 		{
-			// TODO: For lower school level we might need to check the existance of some boat
+			// TODO: For lower school level we might need to check the existence of some boat
 			summonableVirtualBoat.reset(new SummonBoatAction());
 			summonableVirtualBoat.reset(new SummonBoatAction());
 		}
 		}
 	}
 	}

+ 3 - 3
AI/VCAI/ResourceManager.cpp

@@ -66,7 +66,7 @@ TResources ResourceManager::estimateIncome() const
 	return ret;
 	return ret;
 }
 }
 
 
-void ResourceManager::reserveResoures(const TResources & res, Goals::TSubgoal goal)
+void ResourceManager::reserveResources(const TResources & res, Goals::TSubgoal goal)
 {
 {
 	if (!goal->invalid())
 	if (!goal->invalid())
 		tryPush(ResourceObjective(res, goal));
 		tryPush(ResourceObjective(res, goal));
@@ -315,7 +315,7 @@ bool ResourceManager::removeOutdatedObjectives(std::function<bool(const Goals::T
 TResources ResourceManager::reservedResources() const
 TResources ResourceManager::reservedResources() const
 {
 {
 	TResources res;
 	TResources res;
-	for (auto it : queue) //substract the value of reserved goals
+	for (auto it : queue) //subtract the value of reserved goals
 		res += it.resources;
 		res += it.resources;
 	return res;
 	return res;
 }
 }
@@ -323,7 +323,7 @@ TResources ResourceManager::reservedResources() const
 TResources ResourceManager::freeResources() const
 TResources ResourceManager::freeResources() const
 {
 {
 	TResources myRes = cb->getResourceAmount();
 	TResources myRes = cb->getResourceAmount();
-	myRes -= reservedResources(); //substract the value of reserved goals
+	myRes -= reservedResources(); //subtract the value of reserved goals
 
 
 	for (auto & val : myRes)
 	for (auto & val : myRes)
 		vstd::amax(val, 0); //never negative
 		vstd::amax(val, 0); //never negative

+ 2 - 2
AI/VCAI/ResourceManager.h

@@ -24,7 +24,7 @@ struct DLL_EXPORT ResourceObjective
 	ResourceObjective(const TResources &res, Goals::TSubgoal goal);
 	ResourceObjective(const TResources &res, Goals::TSubgoal goal);
 	bool operator < (const ResourceObjective &ro) const;
 	bool operator < (const ResourceObjective &ro) const;
 
 
-	TResources resources; //how many resoures do we need
+	TResources resources; //how many resources do we need
 	Goals::TSubgoal goal; //what for (build, gather army etc...)
 	Goals::TSubgoal goal; //what for (build, gather army etc...)
 };
 };
 
 
@@ -79,7 +79,7 @@ public:
 	bool notifyGoalCompleted(Goals::TSubgoal goal) override;
 	bool notifyGoalCompleted(Goals::TSubgoal goal) override;
 
 
 protected: //not-const actions only for AI
 protected: //not-const actions only for AI
-	virtual void reserveResoures(const TResources & res, Goals::TSubgoal goal = Goals::TSubgoal());
+	virtual void reserveResources(const TResources & res, Goals::TSubgoal goal = Goals::TSubgoal());
 	virtual bool updateGoal(Goals::TSubgoal goal); //new goal must have same properties but different priority
 	virtual bool updateGoal(Goals::TSubgoal goal); //new goal must have same properties but different priority
 	virtual bool tryPush(const ResourceObjective &o);
 	virtual bool tryPush(const ResourceObjective &o);
 
 

+ 11 - 11
AI/VCAI/VCAI.cpp

@@ -571,7 +571,7 @@ void VCAI::objectPropertyChanged(const SetObjectProperty * sop)
 			auto obj = myCb->getObj(sop->id, false);
 			auto obj = myCb->getObj(sop->id, false);
 			if(obj)
 			if(obj)
 			{
 			{
-				addVisitableObj(obj); // TODO: Remove once save compatability broken. In past owned objects were removed from this set
+				addVisitableObj(obj); // TODO: Remove once save compatibility broken. In past owned objects were removed from this set
 				vstd::erase_if_present(alreadyVisited, obj);
 				vstd::erase_if_present(alreadyVisited, obj);
 			}
 			}
 		}
 		}
@@ -682,7 +682,7 @@ void VCAI::showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID cha
 	status.addQuery(askID, boost::str(boost::format("Teleport dialog query with %d exits")
 	status.addQuery(askID, boost::str(boost::format("Teleport dialog query with %d exits")
 																			% exits.size()));
 																			% exits.size()));
 
 
-	int choosenExit = -1;
+	int chosenExit = -1;
 	if(impassable)
 	if(impassable)
 	{
 	{
 		knownTeleportChannels[channel]->passability = TeleportChannel::IMPASSABLE;
 		knownTeleportChannels[channel]->passability = TeleportChannel::IMPASSABLE;
@@ -691,14 +691,14 @@ void VCAI::showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID cha
 	{
 	{
 		auto neededExit = std::make_pair(destinationTeleport, destinationTeleportPos);
 		auto neededExit = std::make_pair(destinationTeleport, destinationTeleportPos);
 		if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, neededExit))
 		if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, neededExit))
-			choosenExit = vstd::find_pos(exits, neededExit);
+			chosenExit = vstd::find_pos(exits, neededExit);
 	}
 	}
 
 
 	for(auto exit : exits)
 	for(auto exit : exits)
 	{
 	{
 		if(status.channelProbing() && exit.first == destinationTeleport)
 		if(status.channelProbing() && exit.first == destinationTeleport)
 		{
 		{
-			choosenExit = vstd::find_pos(exits, exit);
+			chosenExit = vstd::find_pos(exits, exit);
 			break;
 			break;
 		}
 		}
 		else
 		else
@@ -716,7 +716,7 @@ void VCAI::showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID cha
 
 
 	requestActionASAP([=]()
 	requestActionASAP([=]()
 	{
 	{
-		answerQuery(askID, choosenExit);
+		answerQuery(askID, chosenExit);
 	});
 	});
 }
 }
 
 
@@ -942,7 +942,7 @@ void VCAI::mainLoop()
 		while (possibleGoals.size())
 		while (possibleGoals.size())
 		{
 		{
 			//allow assign goals to heroes with 0 movement, but don't realize them
 			//allow assign goals to heroes with 0 movement, but don't realize them
-			//maybe there are beter ones left
+			//maybe there are better ones left
 
 
 			auto bestGoal = fh->chooseSolution(possibleGoals);
 			auto bestGoal = fh->chooseSolution(possibleGoals);
 			if (bestGoal->hero) //lock this hero to fulfill goal
 			if (bestGoal->hero) //lock this hero to fulfill goal
@@ -1543,7 +1543,7 @@ void VCAI::completeGoal(Goals::TSubgoal goal)
 		auto it = lockedHeroes.find(h);
 		auto it = lockedHeroes.find(h);
 		if(it != lockedHeroes.end())
 		if(it != lockedHeroes.end())
 		{
 		{
-			if(it->second == goal || it->second->fulfillsMe(goal)) //FIXME this is overspecified, fulfillsMe shoudl be complete
+			if(it->second == goal || it->second->fulfillsMe(goal)) //FIXME this is overspecified, fulfillsMe should be complete
 			{
 			{
 				logAi->debug(goal->completeMessage());
 				logAi->debug(goal->completeMessage());
 				lockedHeroes.erase(it); //goal fulfilled, free hero
 				lockedHeroes.erase(it); //goal fulfilled, free hero
@@ -1735,7 +1735,7 @@ const CGObjectInstance * VCAI::lookForArt(ArtifactID aid) const
 
 
 	return nullptr;
 	return nullptr;
 
 
-	//TODO what if more than one artifact is available? return them all or some slection criteria
+	//TODO what if more than one artifact is available? return them all or some selection criteria
 }
 }
 
 
 bool VCAI::isAccessible(const int3 & pos) const
 bool VCAI::isAccessible(const int3 & pos) const
@@ -2110,7 +2110,7 @@ void VCAI::tryRealize(Goals::Trade & g) //trade
 	if(ah->freeResources()[g.resID] >= g.value) //goal is already fulfilled. Why we need this check, anyway?
 	if(ah->freeResources()[g.resID] >= g.value) //goal is already fulfilled. Why we need this check, anyway?
 		throw goalFulfilledException(sptr(g));
 		throw goalFulfilledException(sptr(g));
 
 
-	int accquiredResources = 0;
+	int acquiredResources = 0;
 	if(const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(g.objid), false))
 	if(const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(g.objid), false))
 	{
 	{
 		if(const auto * m = dynamic_cast<const IMarket*>(obj))
 		if(const auto * m = dynamic_cast<const IMarket*>(obj))
@@ -2130,8 +2130,8 @@ void VCAI::tryRealize(Goals::Trade & g) //trade
 				if (toGive) //don't try to sell 0 resources
 				if (toGive) //don't try to sell 0 resources
 				{
 				{
 					cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
 					cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
-					accquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
-					logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, accquiredResources, g.resID, obj->getObjectName());
+					acquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
+					logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, acquiredResources, g.resID, obj->getObjectName());
 				}
 				}
 				if (ah->freeResources()[g.resID] >= g.value)
 				if (ah->freeResources()[g.resID] >= g.value)
 					throw goalFulfilledException(sptr(g)); //we traded all we needed
 					throw goalFulfilledException(sptr(g)); //we traded all we needed

+ 1 - 1
AI/VCAI/VCAI.h

@@ -83,7 +83,7 @@ public:
 	//std::vector<const CGObjectInstance *> visitedThisWeek; //only OPWs
 	//std::vector<const CGObjectInstance *> visitedThisWeek; //only OPWs
 	std::map<HeroPtr, std::set<const CGTownInstance *>> townVisitsThisWeek;
 	std::map<HeroPtr, std::set<const CGTownInstance *>> townVisitsThisWeek;
 
 
-	//part of mainLoop, but accessible from outisde
+	//part of mainLoop, but accessible from outside
 	std::vector<Goals::TSubgoal> basicGoals;
 	std::vector<Goals::TSubgoal> basicGoals;
 	Goals::TGoalVec goalsToRemove;
 	Goals::TGoalVec goalsToRemove;
 	Goals::TGoalVec goalsToAdd;
 	Goals::TGoalVec goalsToAdd;

+ 2 - 2
CCallback.cpp

@@ -173,9 +173,9 @@ bool CCallback::swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation
  * @param assembleTo If assemble is true, this represents the artifact ID of the combination
  * @param assembleTo If assemble is true, this represents the artifact ID of the combination
  * artifact to assemble to. Otherwise it's not used.
  * artifact to assemble to. Otherwise it's not used.
  */
  */
-void CCallback::assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)
+void CCallback::assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)
 {
 {
-	AssembleArtifacts aa(hero->id, artifactSlot, assemble, assembleTo);
+	AssembleArtifacts aa(heroID, artifactSlot, assemble, assembleTo);
 	sendRequest(&aa);
 	sendRequest(&aa);
 }
 }
 
 

+ 3 - 3
CCallback.h

@@ -69,7 +69,7 @@ public:
 	//hero
 	//hero
 	virtual void moveHero(const CGHeroInstance *h, const std::vector<int3> & path, bool transit) =0; //moves hero alongside provided path
 	virtual void moveHero(const CGHeroInstance *h, const std::vector<int3> & path, bool transit) =0; //moves hero alongside provided path
 	virtual void moveHero(const CGHeroInstance *h, const int3 & destination, bool transit) =0; //moves hero alongside provided path
 	virtual void moveHero(const CGHeroInstance *h, const int3 & destination, bool transit) =0; //moves hero alongside provided path
-	virtual bool dismissHero(const CGHeroInstance * hero)=0; //dismisses given hero; true - successfuly, false - not successfuly
+	virtual bool dismissHero(const CGHeroInstance * hero)=0; //dismisses given hero; true - successfully, false - not successfully
 	virtual void dig(const CGObjectInstance *hero)=0;
 	virtual void dig(const CGObjectInstance *hero)=0;
 	virtual void castSpell(const CGHeroInstance *hero, SpellID spellID, const int3 &pos = int3(-1, -1, -1))=0; //cast adventure map spell
 	virtual void castSpell(const CGHeroInstance *hero, SpellID spellID, const int3 &pos = int3(-1, -1, -1))=0; //cast adventure map spell
 
 
@@ -93,7 +93,7 @@ public:
 	virtual bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2)=0;
 	virtual bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2)=0;
 	virtual void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) = 0;
 	virtual void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) = 0;
 	virtual void manageHeroCostume(ObjectInstanceID hero, size_t costumeIndex, bool saveCostume) = 0;
 	virtual void manageHeroCostume(ObjectInstanceID hero, size_t costumeIndex, bool saveCostume) = 0;
-	virtual void assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0;
+	virtual void assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0;
 	virtual void eraseArtifactByClient(const ArtifactLocation & al)=0;
 	virtual void eraseArtifactByClient(const ArtifactLocation & al)=0;
 	virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0;
 	virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0;
 	virtual void endTurn()=0;
 	virtual void endTurn()=0;
@@ -176,7 +176,7 @@ public:
 	int bulkMergeStacks(ObjectInstanceID armyId, SlotID srcSlot) override;
 	int bulkMergeStacks(ObjectInstanceID armyId, SlotID srcSlot) override;
 	bool dismissHero(const CGHeroInstance * hero) override;
 	bool dismissHero(const CGHeroInstance * hero) override;
 	bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2) override;
 	bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2) override;
-	void assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override;
+	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 bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap, bool equipped = true, bool backpack = true) override;
 	void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) override;
 	void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) override;
 	void manageHeroCostume(ObjectInstanceID hero, size_t costumeIdx, bool saveCostume) override;
 	void manageHeroCostume(ObjectInstanceID hero, size_t costumeIdx, bool saveCostume) override;

+ 21 - 21
CI/NSIS.template.in

@@ -486,15 +486,15 @@ Done:
 	Exch $R1
 	Exch $R1
 FunctionEnd
 FunctionEnd
 
 
-Function ConditionalAddToRegisty
+Function ConditionalAddToRegistry
   Pop $0
   Pop $0
   Pop $1
   Pop $1
-  StrCmp "$0" "" ConditionalAddToRegisty_EmptyString
+  StrCmp "$0" "" ConditionalAddToRegistry_EmptyString
     WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" \
     WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" \
     "$1" "$0"
     "$1" "$0"
     ;MessageBox MB_OK "Set Registry: '$1' to '$0'"
     ;MessageBox MB_OK "Set Registry: '$1' to '$0'"
     DetailPrint "Set install registry entry: '$1' to '$0'"
     DetailPrint "Set install registry entry: '$1' to '$0'"
-  ConditionalAddToRegisty_EmptyString:
+  ConditionalAddToRegistry_EmptyString:
 FunctionEnd
 FunctionEnd
 
 
 ;--------------------------------
 ;--------------------------------
@@ -646,44 +646,44 @@ Section "-Core installation"
   WriteUninstaller "$INSTDIR\Uninstall.exe"
   WriteUninstaller "$INSTDIR\Uninstall.exe"
   Push "DisplayName"
   Push "DisplayName"
   Push "@CPACK_NSIS_DISPLAY_NAME@"
   Push "@CPACK_NSIS_DISPLAY_NAME@"
-  Call ConditionalAddToRegisty
+  Call ConditionalAddToRegistry
   Push "DisplayVersion"
   Push "DisplayVersion"
   Push "@CPACK_PACKAGE_VERSION@"
   Push "@CPACK_PACKAGE_VERSION@"
-  Call ConditionalAddToRegisty
+  Call ConditionalAddToRegistry
   Push "Publisher"
   Push "Publisher"
   Push "@CPACK_PACKAGE_VENDOR@"
   Push "@CPACK_PACKAGE_VENDOR@"
-  Call ConditionalAddToRegisty
+  Call ConditionalAddToRegistry
   Push "UninstallString"
   Push "UninstallString"
   Push "$INSTDIR\Uninstall.exe"
   Push "$INSTDIR\Uninstall.exe"
-  Call ConditionalAddToRegisty
+  Call ConditionalAddToRegistry
   Push "NoRepair"
   Push "NoRepair"
   Push "1"
   Push "1"
-  Call ConditionalAddToRegisty
+  Call ConditionalAddToRegistry
 
 
   !ifdef CPACK_NSIS_ADD_REMOVE
   !ifdef CPACK_NSIS_ADD_REMOVE
   ;Create add/remove functionality
   ;Create add/remove functionality
   Push "ModifyPath"
   Push "ModifyPath"
   Push "$INSTDIR\AddRemove.exe"
   Push "$INSTDIR\AddRemove.exe"
-  Call ConditionalAddToRegisty
+  Call ConditionalAddToRegistry
   !else
   !else
   Push "NoModify"
   Push "NoModify"
   Push "1"
   Push "1"
-  Call ConditionalAddToRegisty
+  Call ConditionalAddToRegistry
   !endif
   !endif
 
 
   ; Optional registration
   ; Optional registration
   Push "DisplayIcon"
   Push "DisplayIcon"
   Push "$INSTDIR\@CPACK_NSIS_INSTALLED_ICON_NAME@"
   Push "$INSTDIR\@CPACK_NSIS_INSTALLED_ICON_NAME@"
-  Call ConditionalAddToRegisty
+  Call ConditionalAddToRegistry
   Push "HelpLink"
   Push "HelpLink"
   Push "@CPACK_NSIS_HELP_LINK@"
   Push "@CPACK_NSIS_HELP_LINK@"
-  Call ConditionalAddToRegisty
+  Call ConditionalAddToRegistry
   Push "URLInfoAbout"
   Push "URLInfoAbout"
   Push "@CPACK_NSIS_URL_INFO_ABOUT@"
   Push "@CPACK_NSIS_URL_INFO_ABOUT@"
-  Call ConditionalAddToRegisty
+  Call ConditionalAddToRegistry
   Push "Contact"
   Push "Contact"
   Push "@CPACK_NSIS_CONTACT@"
   Push "@CPACK_NSIS_CONTACT@"
-  Call ConditionalAddToRegisty
+  Call ConditionalAddToRegistry
   !insertmacro MUI_INSTALLOPTIONS_READ $INSTALL_DESKTOP "NSIS.InstallOptions.ini" "Field 5" "State"
   !insertmacro MUI_INSTALLOPTIONS_READ $INSTALL_DESKTOP "NSIS.InstallOptions.ini" "Field 5" "State"
   !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
   !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
 
 
@@ -701,19 +701,19 @@ Section "-Core installation"
   ; Write special uninstall registry entries
   ; Write special uninstall registry entries
   Push "StartMenu"
   Push "StartMenu"
   Push "$STARTMENU_FOLDER"
   Push "$STARTMENU_FOLDER"
-  Call ConditionalAddToRegisty
+  Call ConditionalAddToRegistry
   Push "DoNotAddToPath"
   Push "DoNotAddToPath"
   Push "$DO_NOT_ADD_TO_PATH"
   Push "$DO_NOT_ADD_TO_PATH"
-  Call ConditionalAddToRegisty
+  Call ConditionalAddToRegistry
   Push "AddToPathAllUsers"
   Push "AddToPathAllUsers"
   Push "$ADD_TO_PATH_ALL_USERS"
   Push "$ADD_TO_PATH_ALL_USERS"
-  Call ConditionalAddToRegisty
+  Call ConditionalAddToRegistry
   Push "AddToPathCurrentUser"
   Push "AddToPathCurrentUser"
   Push "$ADD_TO_PATH_CURRENT_USER"
   Push "$ADD_TO_PATH_CURRENT_USER"
-  Call ConditionalAddToRegisty
+  Call ConditionalAddToRegistry
   Push "InstallToDesktop"
   Push "InstallToDesktop"
   Push "$INSTALL_DESKTOP"
   Push "$INSTALL_DESKTOP"
-  Call ConditionalAddToRegisty
+  Call ConditionalAddToRegistry
 
 
   !insertmacro MUI_STARTMENU_WRITE_END
   !insertmacro MUI_STARTMENU_WRITE_END
 
 
@@ -848,7 +848,7 @@ Section "Uninstall"
 @CPACK_NSIS_DELETE_ICONS@
 @CPACK_NSIS_DELETE_ICONS@
 @CPACK_NSIS_DELETE_ICONS_EXTRA@
 @CPACK_NSIS_DELETE_ICONS_EXTRA@
 
 
-  ;Delete empty start menu parent diretories
+  ;Delete empty start menu parent directories
   StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP"
   StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP"
 
 
   startMenuDeleteLoop:
   startMenuDeleteLoop:
@@ -867,7 +867,7 @@ Section "Uninstall"
   Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
   Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
 @CPACK_NSIS_DELETE_ICONS_EXTRA@
 @CPACK_NSIS_DELETE_ICONS_EXTRA@
 
 
-  ;Delete empty start menu parent diretories
+  ;Delete empty start menu parent directories
   StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP"
   StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP"
 
 
   secondStartMenuDeleteLoop:
   secondStartMenuDeleteLoop:

+ 3 - 3
CMakeLists.txt

@@ -1,6 +1,6 @@
 # Minimum required version greatly affect CMake behavior
 # Minimum required version greatly affect CMake behavior
 # So cmake_minimum_required must be called before the project()
 # So cmake_minimum_required must be called before the project()
-# 3.16.0 is used since it's used by our currently oldest suppored system: Ubuntu-20.04
+# 3.16.0 is used since it's used by our currently oldest supported system: Ubuntu-20.04
 cmake_minimum_required(VERSION 3.16.0)
 cmake_minimum_required(VERSION 3.16.0)
 
 
 project(VCMI)
 project(VCMI)
@@ -58,7 +58,7 @@ option(ENABLE_CCACHE "Speed up recompilation by caching previous compilations" O
 # Platform-specific options
 # Platform-specific options
 
 
 if(ANDROID)
 if(ANDROID)
-	set(ANDROID_TARGET_SDK_VERSION "33" CACHE STRING "Android target SDK version")
+	set(ANDROID_TARGET_SDK_VERSION "34" CACHE STRING "Android target SDK version")
 	set(ANDROIDDEPLOYQT_OPTIONS "" CACHE STRING "Additional androiddeployqt options separated by semi-colon")
 	set(ANDROIDDEPLOYQT_OPTIONS "" CACHE STRING "Additional androiddeployqt options separated by semi-colon")
 	set(ANDROID_GRADLE_PROPERTIES "" CACHE STRING "Additional Gradle properties separated by semi-colon")
 	set(ANDROID_GRADLE_PROPERTIES "" CACHE STRING "Additional Gradle properties separated by semi-colon")
 
 
@@ -519,7 +519,7 @@ if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
 	endif()
 	endif()
 endif()
 endif()
 
 
-if(ENABLE_NULLKILLER_AI AND ENABLE_CLIENT)
+if(ENABLE_CLIENT)
 	find_package(TBB REQUIRED)
 	find_package(TBB REQUIRED)
 endif()
 endif()
 
 

+ 2 - 1
CMakePresets.json

@@ -292,7 +292,8 @@
                 "default-release"
                 "default-release"
             ],
             ],
             "cacheVariables": {
             "cacheVariables": {
-                "CMAKE_BUILD_TYPE": "RelWithDebInfo"
+                "CMAKE_BUILD_TYPE": "RelWithDebInfo",
+                "ANDROIDDEPLOYQT_OPTIONS" : "--aab"
             }
             }
         },
         },
         {
         {

+ 70 - 22
ChangeLog.md

@@ -1,3 +1,50 @@
+# 1.5.3 -> 1.5.4
+
+### Stability
+* Fixed a possible crash when clicking on an adventure map when another player is taking a turn in multiplayer mode.
+* Failure to extract a mod will now display an error message instead of a silent crash.
+* Fixed crash on opening town hall screen of a town from a mod with invalid building identifier
+* Fixed crash when faerie dragons die after casting Ice Ring on themselves.
+
+### Mechanics
+* The scholar will now correctly upgrade a skill if the visiting hero has offered a skill at either the basic or advanced level.
+* Hero now reveals Fog of War when receiving new or upgraded secondary skills (such as scouting).
+* AI will now always act after all human players during simturns instead of acting after host player
+
+### Interface
+* Pressing the up and down keys on the town screen will now move to the next or previous town instead of scrolling through the list of towns.
+* Long text in scenario name and highscore screen now shortened to fit the interface
+* Game now moves cursor to tap event position when using software cursor with touch screen input
+* Right-click popup on spell scroll campaign bonus now shows spell name instead of artefact name
+* Damage estimation tooltip will no longer show damage greater than the targeted unit's health.
+
+### Random Maps Generator
+* Generator will try to place roads even further away from zone borders
+* Fixed rare crash when placing two quest artefacts in the same location at the same time
+
+### AI
+* Improved performance of Nullkiller AI
+* Stupid AI no longer overestimates damage when killing entire unit
+* Fixed a bug leading to Battle AI not using spells when sieging town with Citadel or Castle built
+* Fixed an unsigned integer overflow that caused the Nullkiller AI to overestimate the total army strength after merging two armies.
+
+### Launcher
+* Added button to reset touchscreen tutorial on mobile systems
+* Launcher will now warn if player selects Gog Galaxy installer instead of offline installer
+* Launcher will now ask for the .bin file first as it is usually listed first in the file system view
+* Extraction failure now displays error message instead of crashing
+* Launcher will now use the header signature to check the file type instead of the extension when using the gog.com installer.
+* Fixed broken controller sensitivity configuration options
+* Fixed manual file installation on Android
+
+### Map Editor
+* Icons and translations now embedded in executable file
+
+### Modding
+* Improved bonus format validation
+* Validator now reports valid values for enumeration fields
+* Fixed missing addInfo field for bonuses that use the BONUS_OWNER_UPDATER propagation updater.
+
 # 1.5.2 -> 1.5.3
 # 1.5.2 -> 1.5.3
 
 
 ### Stability
 ### Stability
@@ -40,12 +87,13 @@
 * Fixed wrong order of activating mods in chain when installing multiple mods at once
 * Fixed wrong order of activating mods in chain when installing multiple mods at once
 * Mod list no longer shows mod version column. Version is now only shown in the mod description.
 * Mod list no longer shows mod version column. Version is now only shown in the mod description.
 * Launcher will now skip the Heroes 3 data import step if data has been found automatically
 * Launcher will now skip the Heroes 3 data import step if data has been found automatically
-* Fixed inport of existing data files on iOS. This option now requires iOS 13 or later
+* Fixed import of existing data files on iOS. This option now requires iOS 13 or later
 * Fixed import using offline installer on iOS.
 * Fixed import using offline installer on iOS.
 * Buttons to open data directories in the Help tab are now hidden on mobile systems if they can't be opened with file browser
 * Buttons to open data directories in the Help tab are now hidden on mobile systems if they can't be opened with file browser
 * Added the configuration files directory to the Help tab as it is located separately on Linux systems
 * Added the configuration files directory to the Help tab as it is located separately on Linux systems
 * Removed H3 data language selection during setup in favor of auto-detection
 * Removed H3 data language selection during setup in favor of auto-detection
 * Replaced checkboxes with toggle buttons for easier of access on touchscreens.
 * Replaced checkboxes with toggle buttons for easier of access on touchscreens.
+* Icons and translations now embedded in executable file
 * Added interface for configuring several previously existing but inaccessible options in Launcher:
 * Added interface for configuring several previously existing but inaccessible options in Launcher:
     * Selection of input tolerance precision for all input types
     * Selection of input tolerance precision for all input types
     * Relative cursor mode for mobile systems (was only available on Android)
     * Relative cursor mode for mobile systems (was only available on Android)
@@ -321,7 +369,7 @@
 
 
 ### Battles
 ### Battles
 * Added option to enable unlimited combat replays during game setup
 * Added option to enable unlimited combat replays during game setup
-* Added option to instantly end battle using quick combat (shotcut: 'e')
+* Added option to instantly end battle using quick combat (shortcut: 'e')
 * Added option to replace auto-combat button action with instant end using quick combat
 * Added option to replace auto-combat button action with instant end using quick combat
 * Battles against AI players can now be done using quick combat
 * Battles against AI players can now be done using quick combat
 * Disabling battle queue will now correctly reposition hero statistics preview popup
 * Disabling battle queue will now correctly reposition hero statistics preview popup
@@ -885,7 +933,7 @@
 ### GENERAL:
 ### GENERAL:
 * Fixed framerate drops on hero movement with active hota mod
 * Fixed framerate drops on hero movement with active hota mod
 * Fade-out animations will now be skipped when instant hero movement speed is used
 * Fade-out animations will now be skipped when instant hero movement speed is used
-* Restarting loaded campaing scenario will now correctly reapply starting bonus
+* Restarting loaded campaign scenario will now correctly reapply starting bonus
 * Reverted FPS limit on mobile systems back to 60 fps
 * Reverted FPS limit on mobile systems back to 60 fps
 * Fixed loading of translations for maps and campaigns
 * Fixed loading of translations for maps and campaigns
 * Fixed loading of preconfigured starting army for heroes with preconfigured spells
 * Fixed loading of preconfigured starting army for heroes with preconfigured spells
@@ -1051,7 +1099,7 @@
 * Battle opening sound can now be skipped with mouse click
 * Battle opening sound can now be skipped with mouse click
 * Fixed movement through moat of double-hexed units
 * Fixed movement through moat of double-hexed units
 * Fixed removal of Land Mines and Fire Walls
 * Fixed removal of Land Mines and Fire Walls
-* Obstacles will now corectly show up either below or above unit
+* Obstacles will now correctly show up either below or above unit
 * It is now possible to teleport a unit through destroyed walls
 * It is now possible to teleport a unit through destroyed walls
 * Added distinct overlay image for showing movement range of highlighted unit
 * Added distinct overlay image for showing movement range of highlighted unit
 * Added overlay for displaying shooting range penalties of units
 * Added overlay for displaying shooting range penalties of units
@@ -1171,7 +1219,7 @@
 * RMG will no longer place shipyards or boats at very small lakes
 * RMG will no longer place shipyards or boats at very small lakes
 * Fixed placement of shipyards in invalid locations
 * Fixed placement of shipyards in invalid locations
 * Fixed potential game hang on generation of random map
 * Fixed potential game hang on generation of random map
-* RMG will now generate addditional monolith pairs to create required number of zone connections
+* RMG will now generate additional monolith pairs to create required number of zone connections
 * RMG will try to place Subterranean Gates as far away from other objects (including each other) as possible
 * RMG will try to place Subterranean Gates as far away from other objects (including each other) as possible
 * RMG will now try to place objects as far as possible in both zones sharing a guard, not only the first one.
 * RMG will now try to place objects as far as possible in both zones sharing a guard, not only the first one.
 * Use only one template for an object in zone
 * Use only one template for an object in zone
@@ -1195,7 +1243,7 @@
 * Added option to show amount of creatures as numeric range rather than adjective
 * Added option to show amount of creatures as numeric range rather than adjective
 * Added option to show map grid
 * Added option to show map grid
 * Map swipe is no longer exclusive for phones and can be enabled on desktop platforms
 * Map swipe is no longer exclusive for phones and can be enabled on desktop platforms
-* Added more graduated settigns for hero movement speed
+* Added more graduated settings for hero movement speed
 * Map scrolling is now more graduated and scrolls with pixel-level precision
 * Map scrolling is now more graduated and scrolls with pixel-level precision
 * Hero movement speed now matches H3
 * Hero movement speed now matches H3
 * Improved performance of adventure map rendering
 * Improved performance of adventure map rendering
@@ -1214,7 +1262,7 @@
 * Fixed white status bar on server connection screen
 * Fixed white status bar on server connection screen
 * Buttons in battle window now correctly show tooltip in status bar
 * Buttons in battle window now correctly show tooltip in status bar
 * Fixed cursor image during enemy turn in combat
 * Fixed cursor image during enemy turn in combat
-* Game will no longer promt to assemble artifacts if they fall into backpack
+* Game will no longer prompt to assemble artifacts if they fall into backpack
 * It is now possible to use in-game console for vcmi commands
 * It is now possible to use in-game console for vcmi commands
 * Stacks sized 1000-9999 units will not be displayed as "1k"
 * Stacks sized 1000-9999 units will not be displayed as "1k"
 * It is now possible to select destination town for Town Portal via double-click
 * It is now possible to select destination town for Town Portal via double-click
@@ -1300,7 +1348,7 @@
 * Dragon Breath attack now correctly uses different attack animation if multiple targets are hit
 * Dragon Breath attack now correctly uses different attack animation if multiple targets are hit
 * Petrification: implemented visual effect
 * Petrification: implemented visual effect
 * Paralyze: added visual effect
 * Paralyze: added visual effect
-* Blind: Stacks will no longer retailate on attack that blinds them
+* Blind: Stacks will no longer retaliate on attack that blinds them
 * Demon Summon: Added animation effect for summoning
 * Demon Summon: Added animation effect for summoning
 * Fire shield will no longer trigger on non-adjacent attacks, e.g. from Dragon Breath
 * Fire shield will no longer trigger on non-adjacent attacks, e.g. from Dragon Breath
 * Weakness now has correct visual effect 
 * Weakness now has correct visual effect 
@@ -1479,7 +1527,7 @@
     * treasury
     * treasury
 
 
 ### SOUND:
 ### SOUND:
-* Fixed many mising or wrong pickup and visit sounds for map objects
+* Fixed many missing or wrong pickup and visit sounds for map objects
 * All map objects now have ambient sounds identical to OH3
 * All map objects now have ambient sounds identical to OH3
 
 
 ### RANDOM MAP GENERATOR:
 ### RANDOM MAP GENERATOR:
@@ -1532,7 +1580,7 @@
 * New console commands:
 * New console commands:
     * gosolo - AI take control over human players and vice versa
     * gosolo - AI take control over human players and vice versa
     * controlai - give control of one or all AIs to player
     * controlai - give control of one or all AIs to player
-    * set hideSystemMessages on/off - supress server messages in chat
+    * set hideSystemMessages on/off - suppress server messages in chat
 
 
 ### BATTLES:
 ### BATTLES:
 * Drawbridge mechanics implemented (animation still missing)
 * Drawbridge mechanics implemented (animation still missing)
@@ -1574,7 +1622,7 @@
     * View Earth
     * View Earth
     * Visions
     * Visions
     * Disguise
     * Disguise
-* Implemented CURE spell negative dispell effect
+* Implemented CURE spell negative dispel effect
 * Added LOCATION target for spells castable on any hex with new target modifiers
 * Added LOCATION target for spells castable on any hex with new target modifiers
 
 
 ### BATTLES:
 ### BATTLES:
@@ -1597,7 +1645,7 @@
     * VCMI can now be compiled with SDL2
     * VCMI can now be compiled with SDL2
     * Movies will use ffmpeg library
     * Movies will use ffmpeg library
     * change boost::bind to std::bind 
     * change boost::bind to std::bind 
-    * removed boost::asign 
+    * removed boost::assign 
     * Updated FuzzyLite to 5.0 
     * Updated FuzzyLite to 5.0 
 * Multiplayer load support was implemented through command-line options
 * Multiplayer load support was implemented through command-line options
 
 
@@ -1646,7 +1694,7 @@
 * Zone placement
 * Zone placement
 * Zone borders and connections, fractalized paths inside zones
 * Zone borders and connections, fractalized paths inside zones
 * Guard generation
 * Guard generation
-* Treasue piles generation (so far only few removable objects)
+* Treasure piles generation (so far only few removable objects)
 
 
 ### MODS:
 ### MODS:
 * Support for submods - mod may have their own "submods" located in <modname>/Mods directory
 * Support for submods - mod may have their own "submods" located in <modname>/Mods directory
@@ -1753,7 +1801,7 @@
 * Improved json validation. Now it support most of features from latest json schema draft.
 * Improved json validation. Now it support most of features from latest json schema draft.
 * Icons use path to icon instead of image indexes.
 * Icons use path to icon instead of image indexes.
 * It is possible to edit data of another mod or H3 data via mods.
 * It is possible to edit data of another mod or H3 data via mods.
-* Mods can access only ID's from dependenies, virtual "core" mod and itself (optional for some mods compatibility)
+* Mods can access only ID's from dependencies, virtual "core" mod and itself (optional for some mods compatibility)
 * Removed no longer needed field "projectile spins"
 * Removed no longer needed field "projectile spins"
 * Heroes: split heroes.json in manner similar to creatures\factions; string ID's for H3 heroes; h3 hero classes and artifacts can be modified via json.
 * Heroes: split heroes.json in manner similar to creatures\factions; string ID's for H3 heroes; h3 hero classes and artifacts can be modified via json.
 
 
@@ -1870,7 +1918,7 @@
 * Fixed a possible freeze when exchanging resources at marketplace
 * Fixed a possible freeze when exchanging resources at marketplace
 
 
 ### BATTLE AI:
 ### BATTLE AI:
-* It is possible to select a battle AI module used by VCMI by typing into the console "setBattleAI <name>". The names of avaialble modules are "StupidAI" and "BattleAI". BattleAI may be a little smarter but less stable. By the default, StupidAI will be used, as in previous releases.
+* It is possible to select a battle AI module used by VCMI by typing into the console "setBattleAI <name>". The names of available modules are "StupidAI" and "BattleAI". BattleAI may be a little smarter but less stable. By the default, StupidAI will be used, as in previous releases.
 * New battle AI module: "BattleAI" that is smarter and capable of casting some offensive and enchantment spells
 * New battle AI module: "BattleAI" that is smarter and capable of casting some offensive and enchantment spells
 
 
 # 0.88 -> 0.89 (Jun 01 2012)
 # 0.88 -> 0.89 (Jun 01 2012)
@@ -1963,7 +2011,7 @@
     * No wall penalty
     * No wall penalty
     * Enchanter
     * Enchanter
     * Bind
     * Bind
-    * Dispell helpful spells
+    * Dispel helpful spells
 
 
 # 0.85 -> 0.86 (Sep 01 2011)
 # 0.85 -> 0.86 (Sep 01 2011)
 
 
@@ -2085,7 +2133,7 @@
     * Spell damage specialities (Deemer), fixed bonus (Ciele)
     * Spell damage specialities (Deemer), fixed bonus (Ciele)
     * Secondary skill bonuses
     * Secondary skill bonuses
     * Creature Upgrades (Gelu)
     * Creature Upgrades (Gelu)
-    * Resorce generation
+    * Resource generation
     * Starting Skill (Adrienne)
     * Starting Skill (Adrienne)
 
 
 ### TOWNS:
 ### TOWNS:
@@ -2224,7 +2272,7 @@ http://bugs.vcmi.eu/changelog_page.php?version_id=14
 * Clicking on the border no longer opens an empty info windows
 * Clicking on the border no longer opens an empty info windows
 
 
 ### HERO WINDOW:
 ### HERO WINDOW:
-* Improved artifact moving. Available slots are higlighted. Moved artifact is bound to mouse cursor. 
+* Improved artifact moving. Available slots are highlighted. Moved artifact is bound to mouse cursor. 
 
 
 ### TOWNS:
 ### TOWNS:
 * new special town structures supported:
 * new special town structures supported:
@@ -2237,7 +2285,7 @@ http://bugs.vcmi.eu/changelog_page.php?version_id=14
 ### OBJECTS:
 ### OBJECTS:
 * External dwellings increase town growth
 * External dwellings increase town growth
 * Right-click info window for castles and garrisons you do not own shows a rough amount of creatures instead of none
 * Right-click info window for castles and garrisons you do not own shows a rough amount of creatures instead of none
-* Scholar won't give unavaliable spells anymore.
+* Scholar won't give unavailable spells anymore.
 
 
 A lot of of various bugfixes and improvements:
 A lot of of various bugfixes and improvements:
 http://bugs.vcmi.eu/changelog_page.php?version_id=2
 http://bugs.vcmi.eu/changelog_page.php?version_id=2
@@ -2257,7 +2305,7 @@ http://bugs.vcmi.eu/changelog_page.php?version_id=2
 * a few fixes for shipyard window
 * a few fixes for shipyard window
 
 
 ### ADVENTURE INTERFACE:
 ### ADVENTURE INTERFACE:
-* Cursor shows if tile is accesible and how many turns away
+* Cursor shows if tile is accessible and how many turns away
 * moving hero with arrow keys / numpad
 * moving hero with arrow keys / numpad
 * fixed Next Hero button behaviour
 * fixed Next Hero button behaviour
 * fixed Surface/Underground switch button in higher resolutions
 * fixed Surface/Underground switch button in higher resolutions
@@ -2426,7 +2474,7 @@ http://bugs.vcmi.eu/changelog_page.php?version_id=2
 * Diplomacy secondary skill support
 * Diplomacy secondary skill support
 * timed events won't cause resources amount to be negative
 * timed events won't cause resources amount to be negative
 * support for sorcery secondary skill
 * support for sorcery secondary skill
-* reduntant quotation marks from artifact descriptions are removed
+* redundant quotation marks from artifact descriptions are removed
 * no income at the first day
 * no income at the first day
 
 
 ### ADVENTURE INTERFACE:
 ### ADVENTURE INTERFACE:
@@ -2954,7 +3002,7 @@ And a lot of minor fixes
 * [feature] picked artifacts are added to hero's backpack
 * [feature] picked artifacts are added to hero's backpack
 * [feature] possibility of choosing player to play
 * [feature] possibility of choosing player to play
 * [bugfix] ZELP.TXT file *should* be handled correctly even it is non-english
 * [bugfix] ZELP.TXT file *should* be handled correctly even it is non-english
-* [bugfix] fixed crashbug in reading defs with negativ left/right margins
+* [bugfix] fixed crashbug in reading defs with negative left/right margins
 * [bugfix] improved randomization
 * [bugfix] improved randomization
 * [bugfix] pathfinder can't be cheated (what caused errors)
 * [bugfix] pathfinder can't be cheated (what caused errors)
 
 

+ 114 - 25
Mods/vcmi/config/vcmi/czech.json

@@ -49,10 +49,10 @@
 	"vcmi.radialWheel.heroSwapArtifacts" : "Vyměnit artefakty s jiným hrdinou",
 	"vcmi.radialWheel.heroSwapArtifacts" : "Vyměnit artefakty s jiným hrdinou",
 	"vcmi.radialWheel.heroDismiss" : "Propustit hrdinu",
 	"vcmi.radialWheel.heroDismiss" : "Propustit hrdinu",
 
 
-	"vcmi.radialWheel.moveTop" : "Move to top",
-	"vcmi.radialWheel.moveUp" : "Move up",
-	"vcmi.radialWheel.moveDown" : "Move down",
-	"vcmi.radialWheel.moveBottom" : "Move to bottom",
+	"vcmi.radialWheel.moveTop" : "Přesunout nahoru",
+	"vcmi.radialWheel.moveUp" : "Posunout výše",
+	"vcmi.radialWheel.moveDown" : "Posunout níže",
+	"vcmi.radialWheel.moveBottom" : "Přesunout dolů",
 
 
 	"vcmi.spellBook.search" : "hledat...",
 	"vcmi.spellBook.search" : "hledat...",
 
 
@@ -62,24 +62,88 @@
 	"vcmi.mainMenu.serverClosing" : "Zavírání...",
 	"vcmi.mainMenu.serverClosing" : "Zavírání...",
 	"vcmi.mainMenu.hostTCP" : "Pořádat hru TCP/IP",
 	"vcmi.mainMenu.hostTCP" : "Pořádat hru TCP/IP",
 	"vcmi.mainMenu.joinTCP" : "Připojit se do hry TCP/IP",
 	"vcmi.mainMenu.joinTCP" : "Připojit se do hry TCP/IP",
-	"vcmi.mainMenu.playerName" : "Hráč",
-	
+
 	"vcmi.lobby.filepath" : "Název souboru",
 	"vcmi.lobby.filepath" : "Název souboru",
 	"vcmi.lobby.creationDate" : "Datum vytvoření",
 	"vcmi.lobby.creationDate" : "Datum vytvoření",
 	"vcmi.lobby.scenarioName" : "Název scénáře",
 	"vcmi.lobby.scenarioName" : "Název scénáře",
 	"vcmi.lobby.mapPreview" : "Náhled mapy",
 	"vcmi.lobby.mapPreview" : "Náhled mapy",
 	"vcmi.lobby.noPreview" : "bez náhledu",
 	"vcmi.lobby.noPreview" : "bez náhledu",
 	"vcmi.lobby.noUnderground" : "bez podzemí",
 	"vcmi.lobby.noUnderground" : "bez podzemí",
-
+	"vcmi.lobby.sortDate" : "Řadit mapy dle data změny",
+	"vcmi.lobby.backToLobby" : "Vrátit se do předsíně",
+	
+	"vcmi.lobby.login.title" : "Online předsíň VCMI",
+	"vcmi.lobby.login.username" : "Uživatelské jméno:",
+	"vcmi.lobby.login.connecting" : "Připojování...",
+	"vcmi.lobby.login.error" : "Chyba při připojování: %s",
+	"vcmi.lobby.login.create" : "Nový účet",
+	"vcmi.lobby.login.login" : "Přihlásit se",
+	"vcmi.lobby.login.as" : "Přilásit se jako %s",
+	"vcmi.lobby.header.rooms" : "Herní místnosti - %d",
+	"vcmi.lobby.header.channels" : "Kanály konverzace",
+	"vcmi.lobby.header.chat.global" : "Globální konverzace hry - %s", // %s -> language name
+	"vcmi.lobby.header.chat.match" : "Konverzace předchozí hry %s", // %s -> game start date & time
+	"vcmi.lobby.header.chat.player" : "Soukromá konverzace s %s", // %s -> nickname of another player
+	"vcmi.lobby.header.history" : "Vaše předchozí hry",
+	"vcmi.lobby.header.players" : "Online hráči - %d",
+	"vcmi.lobby.match.solo" : "Hra jednoho hráče",
+	"vcmi.lobby.match.duel" : "Hra s %s", // %s -> nickname of another player
+	"vcmi.lobby.match.multi" : "%d hráčů",
+	"vcmi.lobby.room.create" : "Vytvořit novou místnost",
+	"vcmi.lobby.room.players.limit" : "Omezení počtu hráčů",
+	"vcmi.lobby.room.description.public" : "Jakýkoliv hráč se může připojit do veřejné místnosti.",
+	"vcmi.lobby.room.description.private" : "Pouze pozvaní hráči se mohou připojit do soukromé místnosti.",
+	"vcmi.lobby.room.description.new" : "Pro start hry vyberte scénář, nebo nastavte náhodnou mapu.",
+	"vcmi.lobby.room.description.load" : "Pro start hry načtěte uloženou hru.",
+	"vcmi.lobby.room.description.limit" : "Až %d hráčů se může připojit do vaší místnosti (včetně vás).",
+	"vcmi.lobby.invite.header" : "Pozvat hráče",
+	"vcmi.lobby.invite.notification" : "Pozval vás hráč do jejich soukromé místnosti. Nyní se do ní můžete připojit.",
+	"vcmi.lobby.preview.title" : "Připojit se do herní místnosti",
+	"vcmi.lobby.preview.subtitle" : "Hra na %s, pořádána %s", //TL Note: 1) name of map or RMG template 2) nickname of game host
+	"vcmi.lobby.preview.version" : "Verze hry:",
+	"vcmi.lobby.preview.players" : "Hráči:",
+	"vcmi.lobby.preview.mods" : "Použité modifikace:",
+	"vcmi.lobby.preview.allowed" : "Připojit se do herní místnosti?",
+	"vcmi.lobby.preview.error.header" : "Nelze se připojit do této herní místnosti.",
+	"vcmi.lobby.preview.error.playing" : "Nejdříve musíte opustit vaši současnou hru.",
+	"vcmi.lobby.preview.error.full" : "Místnost je již plná.",
+	"vcmi.lobby.preview.error.busy" : "Místnost již nepřijímá nové hráče.",
+	"vcmi.lobby.preview.error.invite" : "Nebyl jste pozván do této mísnosti.",
+	"vcmi.lobby.preview.error.mods" : "Použváte jinou sadu modifikací.",
+	"vcmi.lobby.preview.error.version" : "Používáte jinou verzi VCMI.",
+	"vcmi.lobby.room.new" : "Nová hra",
+	"vcmi.lobby.room.load" : "Načíst hru",
+	"vcmi.lobby.room.type" : "Druh místnosti",
+	"vcmi.lobby.room.mode" : "Herní režim",
+	"vcmi.lobby.room.state.public" : "Veřejná",
+	"vcmi.lobby.room.state.private" : "Soukromá",
+	"vcmi.lobby.room.state.busy" : "Ve hře",
+	"vcmi.lobby.room.state.invited" : "Pozvaný",
+	"vcmi.lobby.mod.state.compatible" : "Kompatibilní",
+	"vcmi.lobby.mod.state.disabled" : "Musí být povolena",
+	"vcmi.lobby.mod.state.version" : "Neshoda verze",
+	"vcmi.lobby.mod.state.excessive" : "Musí být zakázána",
+	"vcmi.lobby.mod.state.missing" : "Není nainstalována",
+	"vcmi.lobby.pvp.coin.hover" : "Mince",
+	"vcmi.lobby.pvp.coin.help" : "Hodí mincí",
+	"vcmi.lobby.pvp.randomTown.hover" : "Náhodné město",
+	"vcmi.lobby.pvp.randomTown.help" : "Napsat náhodné město do konvezace",
+	"vcmi.lobby.pvp.randomTownVs.hover" : "Náhodné město vs.",
+	"vcmi.lobby.pvp.randomTownVs.help" : "Napsat 2 náhodná města do konvezace",
+	"vcmi.lobby.pvp.versus" : "vs.",
+
+	"vcmi.client.errors.invalidMap" : "{Neplatná mapa nebo kampaň}\n\nChyba při startu hry! Vybraná mapa nebo kampaň může být neplatná nebo poškozená. Důvod:\n%s",
 	"vcmi.client.errors.missingCampaigns" : "{Chybějící datové soubory}\n\nDatové soubory kampaně nebyly nalezeny! Možná máte nekompletní nebo poškozené datové soubory Heroes 3. Prosíme, přeinstalujte hru.",
 	"vcmi.client.errors.missingCampaigns" : "{Chybějící datové soubory}\n\nDatové soubory kampaně nebyly nalezeny! Možná máte nekompletní nebo poškozené datové soubory Heroes 3. Prosíme, přeinstalujte hru.",
+	"vcmi.server.errors.disconnected" : "{Chyba sítě}\n\nPřipojení k hernímu serveru bylo ztraceno!",
 	"vcmi.server.errors.existingProcess" : "Již běží jiný server VCMI. Prosím, ukončete ho před startem nové hry.",
 	"vcmi.server.errors.existingProcess" : "Již běží jiný server VCMI. Prosím, ukončete ho před startem nové hry.",
 	"vcmi.server.errors.modsToEnable"    : "{Následující modifikace jsou nutné pro načtení hry}",
 	"vcmi.server.errors.modsToEnable"    : "{Následující modifikace jsou nutné pro načtení hry}",
 	"vcmi.server.errors.modsToDisable"   : "{Následující modifikace musí být zakázány}",
 	"vcmi.server.errors.modsToDisable"   : "{Následující modifikace musí být zakázány}",
-	"vcmi.server.confirmReconnect"       : "Chcete se připojit k poslední relaci?",
 	"vcmi.server.errors.modNoDependency" : "Nelze načíst modifikaci {'%s'}!\n Závisí na modifikaci {'%s'}, která není aktivní!\n",
 	"vcmi.server.errors.modNoDependency" : "Nelze načíst modifikaci {'%s'}!\n Závisí na modifikaci {'%s'}, která není aktivní!\n",
 	"vcmi.server.errors.modConflict" : "Nelze načíst modifikaci {'%s'}!\n Je v kolizi s aktivní modifikací {'%s'}!\n",
 	"vcmi.server.errors.modConflict" : "Nelze načíst modifikaci {'%s'}!\n Je v kolizi s aktivní modifikací {'%s'}!\n",
 	"vcmi.server.errors.unknownEntity" : "Nelze načíst uloženou pozici! Neznámá entita '%s' nalezena v uložené pozici! Uložná pozice nemusí být kompatibilní s aktuálními verzemi modifikací!",
 	"vcmi.server.errors.unknownEntity" : "Nelze načíst uloženou pozici! Neznámá entita '%s' nalezena v uložené pozici! Uložná pozice nemusí být kompatibilní s aktuálními verzemi modifikací!",
 
 
+	"vcmi.dimensionDoor.seaToLandError" : "It's not possible to teleport from sea to land or vice versa with a Dimension Door.", //TODO
+
 	"vcmi.settingsMainWindow.generalTab.hover" : "Obecné",
 	"vcmi.settingsMainWindow.generalTab.hover" : "Obecné",
 	"vcmi.settingsMainWindow.generalTab.help"     : "Přepne na kartu obecných nastavení, která obsahuje nastavení související s obecným chováním klienta hry.",
 	"vcmi.settingsMainWindow.generalTab.help"     : "Přepne na kartu obecných nastavení, která obsahuje nastavení související s obecným chováním klienta hry.",
 	"vcmi.settingsMainWindow.battleTab.hover" : "Bitva",
 	"vcmi.settingsMainWindow.battleTab.hover" : "Bitva",
@@ -182,6 +246,18 @@
 	"vcmi.battleWindow.damageEstimation.kills" : "%d zahyne",
 	"vcmi.battleWindow.damageEstimation.kills" : "%d zahyne",
 	"vcmi.battleWindow.damageEstimation.kills.1" : "%d zahyne",
 	"vcmi.battleWindow.damageEstimation.kills.1" : "%d zahyne",
 
 
+	"vcmi.battleWindow.damageRetaliation.will" : "Zahyne ",
+	"vcmi.battleWindow.damageRetaliation.may" : "Možná zahyne ",
+	"vcmi.battleWindow.damageRetaliation.never" : "Nezahyne.",
+	"vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).",
+	"vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).",
+	
+	"vcmi.battleWindow.killed" : "Zabito", //TODO
+	"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s were killed by accurate shots!",
+	"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s was killed with an accurate shot!",
+	"vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s were killed by accurate shots!",
+	"vcmi.battleWindow.endWithAutocombat" : "Are you sure you wish to end the battle with auto combat?",
+
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Použít výsledek bitvy",
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Použít výsledek bitvy",
 
 
 	"vcmi.tutorialWindow.title" : "Úvod ovládání dotykem",
 	"vcmi.tutorialWindow.title" : "Úvod ovládání dotykem",
@@ -244,18 +320,18 @@
 	"vcmi.optionsTab.turnOptions.hover" : "Možnosti tahu",
 	"vcmi.optionsTab.turnOptions.hover" : "Možnosti tahu",
 	"vcmi.optionsTab.turnOptions.help" : "Vyberte odpočítávadlo tahů a nastavení souběžných tahů",
 	"vcmi.optionsTab.turnOptions.help" : "Vyberte odpočítávadlo tahů a nastavení souběžných tahů",
 
 
-	"vcmi.optionsTab.chessFieldBase.hover" : "Base timer",
-	"vcmi.optionsTab.chessFieldTurn.hover" : "Turn timer",
-	"vcmi.optionsTab.chessFieldBattle.hover" : "Battle timer",
-	"vcmi.optionsTab.chessFieldUnit.hover" : "Unit timer",
-	"vcmi.optionsTab.chessFieldBase.help" : "Used when {Turn Timer} reaches 0. Set once at game start. On reaching zero, ends current turn. Any ongoing combat with end with a loss.",
-	"vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Used out of combat or when {Battle Timer} runs out. Reset each turn. Leftover added to {Base Timer} at turn's end.",
-	"vcmi.optionsTab.chessFieldTurnDiscard.help" : "Used out of combat or when {Battle Timer} runs out. Reset each turn. Any unspent time is lost",
-	"vcmi.optionsTab.chessFieldBattle.help" : "Used in battles with AI or in pvp combat when {Unit Timer} runs out. Reset at start of each combat.",
-	"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Used when selecting unit action in pvp combat. Leftover added to {Battle Timer} at end of unit turn.",
-	"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Used when selecting unit action in pvp combat. Reset at start of each unit's turn. Any unspent time is lost",
+	"vcmi.optionsTab.chessFieldBase.hover" : "Základní časovač",
+	"vcmi.optionsTab.chessFieldTurn.hover" : "Časovač tahu",
+	"vcmi.optionsTab.chessFieldBattle.hover" : "Časovač bitvy",
+	"vcmi.optionsTab.chessFieldUnit.hover" : "Časovač jednotky",
+	"vcmi.optionsTab.chessFieldBase.help" : "Použit při poklesnutí {Časovače bitvy} na 0. Nastaveno jednou při začátku hry. Při poklesu na nulu skončí tah. Jákákoliv trvající bitva skončí prohrou.",
+	"vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Použit mimo bitvu nebo když {Časovač bitvy} vyprší. Resetuje se každý tah. Přebytečný čas je přidán do {Základního časovače} na konci tahu.",
+	"vcmi.optionsTab.chessFieldTurnDiscard.help" : "Použit mimo bitvu nebo když {Časovač bitvy} vyprší. Resetuje se každý tah. Jakýkoliv přebytečný čas je ztracen.",
+	"vcmi.optionsTab.chessFieldBattle.help" : "Použit v bitvách s AI nebo v pvp soubojích při vypršení {Časovače jednotky}. Resetuje se startu každé bitvy.",
+	"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Použit při vybírání úkonu jednotky. Přebytečný čas je přidán do {Časovače bitvy} na konci tahu jednotky.",
+	"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Použit při vybírání úkonu jednotky. Resetuje se na začátku tahu každé jednotky. Jakýkoliv přebytečný čas je ztracen.",
 
 
-	"vcmi.optionsTab.accumulate" : "Accumulate",
+	"vcmi.optionsTab.accumulate" : "Akumulovat",
 
 
 	"vcmi.optionsTab.simturnsTitle" : "Souběžné tahy",
 	"vcmi.optionsTab.simturnsTitle" : "Souběžné tahy",
 	"vcmi.optionsTab.simturnsMin.hover" : "Alespoň po",
 	"vcmi.optionsTab.simturnsMin.hover" : "Alespoň po",
@@ -302,6 +378,14 @@
 	"vcmi.optionsTab.simturns.months.1" : " %d měsíc",
 	"vcmi.optionsTab.simturns.months.1" : " %d měsíc",
 	"vcmi.optionsTab.simturns.months.2" : " %d měsíce",
 	"vcmi.optionsTab.simturns.months.2" : " %d měsíce",
 
 
+	"vcmi.optionsTab.extraOptions.hover" : "Další možnosti",
+	"vcmi.optionsTab.extraOptions.help" : "Další herní možnosti",
+
+	"vcmi.optionsTab.cheatAllowed.hover" : "Povolit cheaty",
+	"vcmi.optionsTab.unlimitedReplay.hover" : "Unlimited battle replay",
+	"vcmi.optionsTab.cheatAllowed.help" : "{Povolit cheaty}\nPovolí zadávání cheatů během hry.",
+	"vcmi.optionsTab.unlimitedReplay.help" : "{Unlimited battle replay}\nNo limit of replaying battles.",
+
 	// Custom victory conditions for H3 campaigns and HotA maps
 	// Custom victory conditions for H3 campaigns and HotA maps
 	"vcmi.map.victoryCondition.daysPassed.toOthers" : "Nepřítel zvládl přežít do této chvíle. Vítězství je jeho!",
 	"vcmi.map.victoryCondition.daysPassed.toOthers" : "Nepřítel zvládl přežít do této chvíle. Vítězství je jeho!",
 	"vcmi.map.victoryCondition.daysPassed.toSelf" : "Gratulace! Zvládli jste přežít. Vítězství je vaše!",
 	"vcmi.map.victoryCondition.daysPassed.toSelf" : "Gratulace! Zvládli jste přežít. Vítězství je vaše!",
@@ -333,9 +417,9 @@
 	"core.bonus.AIR_IMMUNITY.description": "Imunní všem kouzlům školy vzdušné magie",
 	"core.bonus.AIR_IMMUNITY.description": "Imunní všem kouzlům školy vzdušné magie",
 	"core.bonus.ATTACKS_ALL_ADJACENT.name": "Útok okolo",
 	"core.bonus.ATTACKS_ALL_ADJACENT.name": "Útok okolo",
 	"core.bonus.ATTACKS_ALL_ADJACENT.description": "Útočí na všechny sousední jednotky",
 	"core.bonus.ATTACKS_ALL_ADJACENT.description": "Útočí na všechny sousední jednotky",
-	"core.bonus.BLOCKS_RETALIATION.name": "Žádná odplata",
+	"core.bonus.BLOCKS_RETALIATION.name": "Žádná odveta",
 	"core.bonus.BLOCKS_RETALIATION.description": "Nepřítel nemůže zaútočit zpět",
 	"core.bonus.BLOCKS_RETALIATION.description": "Nepřítel nemůže zaútočit zpět",
-	"core.bonus.BLOCKS_RANGED_RETALIATION.name": "Žádná odplata na dálku",
+	"core.bonus.BLOCKS_RANGED_RETALIATION.name": "Žádná odveta na dálku",
 	"core.bonus.BLOCKS_RANGED_RETALIATION.description": "Nepřítel nemůže zaútočit zpět útokem na dálku",
 	"core.bonus.BLOCKS_RANGED_RETALIATION.description": "Nepřítel nemůže zaútočit zpět útokem na dálku",
 	"core.bonus.CATAPULT.name": "Katapult",
 	"core.bonus.CATAPULT.name": "Katapult",
 	"core.bonus.CATAPULT.description": "Útočí na ochranné hradby",
 	"core.bonus.CATAPULT.description": "Útočí na ochranné hradby",
@@ -364,17 +448,20 @@
 	"core.bonus.ENCHANTED.name": "Očarovaný",
 	"core.bonus.ENCHANTED.name": "Očarovaný",
 	"core.bonus.ENCHANTED.description": "Trvale ovlivněm kouzlem ${subtype.spell}",
 	"core.bonus.ENCHANTED.description": "Trvale ovlivněm kouzlem ${subtype.spell}",
 	"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Nevšímá si ${val} % bodů obrany",
 	"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Nevšímá si ${val} % bodů obrany",
+	"core.bonus.ENEMY_ATTACK_REDUCTION.description": "When being attacked, ${val}% of the attacker's attack is ignored",
 	"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Pří útoku nebude brát v potaz ${val}% bodů obrany obránce",
 	"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Pří útoku nebude brát v potaz ${val}% bodů obrany obránce",
 	"core.bonus.FIRE_IMMUNITY.name": "Ohnivá odolnost",
 	"core.bonus.FIRE_IMMUNITY.name": "Ohnivá odolnost",
 	"core.bonus.FIRE_IMMUNITY.description": "Imunní všem kouzlům školy ohnivé magie",
 	"core.bonus.FIRE_IMMUNITY.description": "Imunní všem kouzlům školy ohnivé magie",
 	"core.bonus.FIRE_SHIELD.name": "Ohnivý štít (${val}%)",
 	"core.bonus.FIRE_SHIELD.name": "Ohnivý štít (${val}%)",
 	"core.bonus.FIRE_SHIELD.description": "Odrazí část zranení útoku zblízka",
 	"core.bonus.FIRE_SHIELD.description": "Odrazí část zranení útoku zblízka",
 	"core.bonus.FIRST_STRIKE.name": "První úder",
 	"core.bonus.FIRST_STRIKE.name": "První úder",
-	"core.bonus.FIRST_STRIKE.description": "Tato jednotka útočí zpět ještě než je na ni zaútočeno",
+	"core.bonus.FIRST_STRIKE.description": "Tato jednotka provede odvetu ještě než je na ni zaútočeno",
 	"core.bonus.FEAR.name": "Strach",
 	"core.bonus.FEAR.name": "Strach",
 	"core.bonus.FEAR.description": "Způsobí strach nepřátelskému oddílu",
 	"core.bonus.FEAR.description": "Způsobí strach nepřátelskému oddílu",
 	"core.bonus.FEARLESS.name": "Nebojácnost",
 	"core.bonus.FEARLESS.name": "Nebojácnost",
 	"core.bonus.FEARLESS.description": "Odolnost proti strachu",
 	"core.bonus.FEARLESS.description": "Odolnost proti strachu",
+	"core.bonus.FEROCITY.name": "Ferocity", //TODO
+	"core.bonus.FEROCITY.description": "Attacks ${val} additional times if killed anybody",
 	"core.bonus.FLYING.name": "Letec",
 	"core.bonus.FLYING.name": "Letec",
 	"core.bonus.FLYING.description": "Při pohybu létá (přes překážky)",
 	"core.bonus.FLYING.description": "Při pohybu létá (přes překážky)",
 	"core.bonus.FREE_SHOOTING.name": "Blízké výstřely",
 	"core.bonus.FREE_SHOOTING.name": "Blízké výstřely",
@@ -421,7 +508,7 @@
 	"core.bonus.NON_LIVING.description": "Imunní vůči mnohým efektům",
 	"core.bonus.NON_LIVING.description": "Imunní vůči mnohým efektům",
 	"core.bonus.RANDOM_SPELLCASTER.name": "Náhodný kouzelník",
 	"core.bonus.RANDOM_SPELLCASTER.name": "Náhodný kouzelník",
 	"core.bonus.RANDOM_SPELLCASTER.description": "Může seslat náhodné kouzlo",
 	"core.bonus.RANDOM_SPELLCASTER.description": "Může seslat náhodné kouzlo",
-	"core.bonus.RANGED_RETALIATION.name": "Vzdálená msta",
+	"core.bonus.RANGED_RETALIATION.name": "Vzdálená odveta",
 	"core.bonus.RANGED_RETALIATION.description": "Může provést protiútok na dálku",
 	"core.bonus.RANGED_RETALIATION.description": "Může provést protiútok na dálku",
 	"core.bonus.RECEPTIVE.name": "Přijímavý",
 	"core.bonus.RECEPTIVE.name": "Přijímavý",
 	"core.bonus.RECEPTIVE.description": "Není imunní vůči přátelským kouzlům",
 	"core.bonus.RECEPTIVE.description": "Není imunní vůči přátelským kouzlům",
@@ -429,6 +516,8 @@
 	"core.bonus.REBIRTH.description": "${val}% oddílu se po smrti znovu narodí",
 	"core.bonus.REBIRTH.description": "${val}% oddílu se po smrti znovu narodí",
 	"core.bonus.RETURN_AFTER_STRIKE.name": "Útok a návrat",
 	"core.bonus.RETURN_AFTER_STRIKE.name": "Útok a návrat",
 	"core.bonus.RETURN_AFTER_STRIKE.description": "Navrátí se po útoku na blízko",
 	"core.bonus.RETURN_AFTER_STRIKE.description": "Navrátí se po útoku na blízko",
+	"core.bonus.REVENGE.name": "Msta",
+	"core.bonus.REVENGE.description": "Deals extra damage based on attacker's lost health in battle", //TODO
 	"core.bonus.SHOOTER.name": "Střelec",
 	"core.bonus.SHOOTER.name": "Střelec",
 	"core.bonus.SHOOTER.description": "Jednotka může střílet",
 	"core.bonus.SHOOTER.description": "Jednotka může střílet",
 	"core.bonus.SHOOTS_ALL_ADJACENT.name": "Střílí okolo",
 	"core.bonus.SHOOTS_ALL_ADJACENT.name": "Střílí okolo",
@@ -452,7 +541,7 @@
 	"core.bonus.SUMMON_GUARDIANS.name": "Povolat strážce",
 	"core.bonus.SUMMON_GUARDIANS.name": "Povolat strážce",
 	"core.bonus.SUMMON_GUARDIANS.description": "Na začátku bitvy povolá ${subtype.creature} (${val}%)",
 	"core.bonus.SUMMON_GUARDIANS.description": "Na začátku bitvy povolá ${subtype.creature} (${val}%)",
 	"core.bonus.SYNERGY_TARGET.name": "Synergizable", // TODO
 	"core.bonus.SYNERGY_TARGET.name": "Synergizable", // TODO
-	"core.bonus.SYNERGY_TARGET.description": "This creature is vulnerable to synergy effect",
+	"core.bonus.SYNERGY_TARGET.description": "This creature is vulnerable to synergy effect", //TODO
 	"core.bonus.TWO_HEX_ATTACK_BREATH.name": "Dech",
 	"core.bonus.TWO_HEX_ATTACK_BREATH.name": "Dech",
 	"core.bonus.TWO_HEX_ATTACK_BREATH.description": "Dechový útok (dosah do dvou polí)",
 	"core.bonus.TWO_HEX_ATTACK_BREATH.description": "Dechový útok (dosah do dvou polí)",
 	"core.bonus.THREE_HEADED_ATTACK.name": "Tříhlavý útok",
 	"core.bonus.THREE_HEADED_ATTACK.name": "Tříhlavý útok",
@@ -462,7 +551,7 @@
 	"core.bonus.UNDEAD.name": "Nemrtvý",
 	"core.bonus.UNDEAD.name": "Nemrtvý",
 	"core.bonus.UNDEAD.description": "Jednotka je nemrtvá",
 	"core.bonus.UNDEAD.description": "Jednotka je nemrtvá",
 	"core.bonus.UNLIMITED_RETALIATIONS.name": "Neomezené odvety",
 	"core.bonus.UNLIMITED_RETALIATIONS.name": "Neomezené odvety",
-	"core.bonus.UNLIMITED_RETALIATIONS.description": "Může se mstít za neomezený počet útoků",
+	"core.bonus.UNLIMITED_RETALIATIONS.description": "Může provést odvetu za neomezený počet útoků",
 	"core.bonus.WATER_IMMUNITY.name": "Vodní odolnost",
 	"core.bonus.WATER_IMMUNITY.name": "Vodní odolnost",
 	"core.bonus.WATER_IMMUNITY.description": "Imunní všem kouzlům školy vodní magie",
 	"core.bonus.WATER_IMMUNITY.description": "Imunní všem kouzlům školy vodní magie",
 	"core.bonus.WIDE_BREATH.name": "Široký dech",
 	"core.bonus.WIDE_BREATH.name": "Široký dech",

+ 1 - 0
Mods/vcmi/config/vcmi/english.json

@@ -72,6 +72,7 @@
 	"vcmi.lobby.noUnderground" : "no underground",
 	"vcmi.lobby.noUnderground" : "no underground",
 	"vcmi.lobby.sortDate" : "Sorts maps by change date",
 	"vcmi.lobby.sortDate" : "Sorts maps by change date",
 	"vcmi.lobby.backToLobby" : "Return to lobby",
 	"vcmi.lobby.backToLobby" : "Return to lobby",
+	"vcmi.lobby.author" : "Author",
 	
 	
 	"vcmi.lobby.login.title" : "VCMI Online Lobby",
 	"vcmi.lobby.login.title" : "VCMI Online Lobby",
 	"vcmi.lobby.login.username" : "Username:",
 	"vcmi.lobby.login.username" : "Username:",

+ 1 - 0
Mods/vcmi/config/vcmi/german.json

@@ -72,6 +72,7 @@
 	"vcmi.lobby.noUnderground" : "Kein Untergrund",
 	"vcmi.lobby.noUnderground" : "Kein Untergrund",
 	"vcmi.lobby.sortDate" : "Ordnet Karten nach Änderungsdatum",
 	"vcmi.lobby.sortDate" : "Ordnet Karten nach Änderungsdatum",
 	"vcmi.lobby.backToLobby" : "Zur Lobby zurückkehren",
 	"vcmi.lobby.backToLobby" : "Zur Lobby zurückkehren",
+	"vcmi.lobby.author" : "Author",
 	
 	
 	"vcmi.lobby.login.title" : "VCMI Online Lobby",
 	"vcmi.lobby.login.title" : "VCMI Online Lobby",
 	"vcmi.lobby.login.username" : "Benutzername:",
 	"vcmi.lobby.login.username" : "Benutzername:",

+ 2 - 2
android/vcmi-app/src/main/java/org/libsdl/app/SDLActivity.java

@@ -89,7 +89,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
                 | InputDevice.SOURCE_CLASS_POSITION
                 | InputDevice.SOURCE_CLASS_POSITION
                 | InputDevice.SOURCE_CLASS_TRACKBALL);
                 | InputDevice.SOURCE_CLASS_TRACKBALL);
 
 
-        if (s2 != 0) cls += "Some_Unkown";
+        if (s2 != 0) cls += "Some_Unknown";
 
 
         s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class;
         s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class;
 
 
@@ -163,7 +163,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
         if (s == FLAG_TAINTED) src += " FLAG_TAINTED";
         if (s == FLAG_TAINTED) src += " FLAG_TAINTED";
         s2 &= ~FLAG_TAINTED;
         s2 &= ~FLAG_TAINTED;
 
 
-        if (s2 != 0) src += " Some_Unkown";
+        if (s2 != 0) src += " Some_Unknown";
 
 
         Log.v(TAG, prefix + "int=" + s_copy + " CLASS={" + cls + " } source(s):" + src);
         Log.v(TAG, prefix + "int=" + s_copy + " CLASS={" + cls + " } source(s):" + src);
     }
     }

+ 166 - 0
client/ArtifactsUIController.cpp

@@ -0,0 +1,166 @@
+/*
+ * ArtifactsUIController.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+ 
+#include "StdInc.h"
+#include "ArtifactsUIController.h"
+#include "CGameInfo.h"
+#include "CPlayerInterface.h"
+
+#include "../CCallback.h"
+#include "../lib/ArtifactUtils.h"
+#include "../lib/CGeneralTextHandler.h"
+#include "../lib/mapObjects/CGHeroInstance.h"
+
+#include "gui/CGuiHandler.h"
+#include "gui/WindowHandler.h"
+#include "widgets/CComponent.h"
+#include "windows/CWindowWithArtifacts.h"
+
+ArtifactsUIController::ArtifactsUIController()
+{
+	numOfMovedArts = 0;
+}
+
+bool ArtifactsUIController::askToAssemble(const ArtifactLocation & al, const bool onlyEquipped, const bool checkIgnored)
+{
+	if(auto hero = LOCPLINT->cb->getHero(al.artHolder))
+	{
+		if(hero->getArt(al.slot) == nullptr)
+		{
+			logGlobal->error("artifact location %d points to nothing", al.slot.num);
+			return false;
+		}
+		return askToAssemble(hero, al.slot, onlyEquipped, checkIgnored);
+	}
+	return false;
+}
+
+bool ArtifactsUIController::askToAssemble(const CGHeroInstance * hero, const ArtifactPosition & slot,
+	const bool onlyEquipped, const bool checkIgnored)
+{
+	assert(hero);
+	const auto art = hero->getArt(slot);
+	assert(art);
+
+	if(hero->tempOwner != LOCPLINT->playerID)
+		return false;
+
+	if(numOfArtsAskAssembleSession != 0)
+		numOfArtsAskAssembleSession--;
+	auto assemblyPossibilities = ArtifactUtils::assemblyPossibilities(hero, art->getTypeId(), onlyEquipped);
+	if(!assemblyPossibilities.empty())
+	{
+		auto askThread = new boost::thread([this, hero, art, slot, assemblyPossibilities, checkIgnored]() -> void
+			{
+				boost::mutex::scoped_lock askLock(askAssembleArtifactMutex);
+				for(const auto combinedArt : assemblyPossibilities)
+				{
+					boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
+					if(checkIgnored)
+					{
+						if(vstd::contains(ignoredArtifacts, combinedArt->getId()))
+							continue;
+						ignoredArtifacts.emplace(combinedArt->getId());
+					}
+
+					bool assembleConfirmed = false;
+					MetaString message = MetaString::createFromTextID(art->artType->getDescriptionTextID());
+					message.appendEOL();
+					message.appendEOL();
+					message.appendRawString(CGI->generaltexth->allTexts[732]); // You possess all of the components needed to assemble the
+					message.replaceName(ArtifactID(combinedArt->getId()));
+					LOCPLINT->showYesNoDialog(message.toString(), [&assembleConfirmed, hero, slot, combinedArt]()
+						{
+							assembleConfirmed = true;
+							LOCPLINT->cb.get()->assembleArtifacts(hero->id, slot, true, combinedArt->getId());
+						}, nullptr, {std::make_shared<CComponent>(ComponentType::ARTIFACT, combinedArt->getId())});
+
+					LOCPLINT->waitWhileDialog();
+					if(assembleConfirmed)
+						break;
+				}
+			});
+		askThread->detach();
+		return true;
+	}
+	return false;
+}
+
+bool ArtifactsUIController::askToDisassemble(const CGHeroInstance * hero, const ArtifactPosition & slot)
+{
+	assert(hero);
+	const auto art = hero->getArt(slot);
+	assert(art);
+
+	if(hero->tempOwner != LOCPLINT->playerID)
+		return false;
+
+	if(art->isCombined())
+	{
+		if(ArtifactUtils::isSlotBackpack(slot) && !ArtifactUtils::isBackpackFreeSlots(hero, art->artType->getConstituents().size() - 1))
+			return false;
+
+		MetaString message = MetaString::createFromTextID(art->artType->getDescriptionTextID());
+		message.appendEOL();
+		message.appendEOL();
+		message.appendRawString(CGI->generaltexth->allTexts[733]); // Do you wish to disassemble this artifact?
+		LOCPLINT->showYesNoDialog(message.toString(), [hero, slot]()
+			{
+				LOCPLINT->cb->assembleArtifacts(hero->id, slot, false, ArtifactID());
+			}, nullptr);
+		return true;
+	}
+	return false;
+}
+
+void ArtifactsUIController::artifactRemoved()
+{
+	for(const auto & artWin : GH.windows().findWindows<CWindowWithArtifacts>())
+		artWin->update();
+	LOCPLINT->waitWhileDialog();
+}
+
+void ArtifactsUIController::artifactMoved()
+{
+	// If a bulk transfer has arrived, then redrawing only the last art movement.
+	if(numOfMovedArts != 0)
+		numOfMovedArts--;
+
+	if(numOfMovedArts == 0)
+		for(const auto & artWin : GH.windows().findWindows<CWindowWithArtifacts>())
+		{
+			artWin->update();
+		}
+	LOCPLINT->waitWhileDialog();
+}
+
+void ArtifactsUIController::bulkArtMovementStart(size_t totalNumOfArts, size_t possibleAssemblyNumOfArts)
+{
+	assert(totalNumOfArts >= possibleAssemblyNumOfArts);
+	numOfMovedArts = totalNumOfArts;
+	if(numOfArtsAskAssembleSession == 0)
+	{
+		// Do not start the next session until the previous one is finished
+		numOfArtsAskAssembleSession = possibleAssemblyNumOfArts;
+		ignoredArtifacts.clear();
+	}
+}
+
+void ArtifactsUIController::artifactAssembled()
+{
+	for(const auto & artWin : GH.windows().findWindows<CWindowWithArtifacts>())
+		artWin->update();
+}
+
+void ArtifactsUIController::artifactDisassembled()
+{
+	for(const auto & artWin : GH.windows().findWindows<CWindowWithArtifacts>())
+		artWin->update();
+}

+ 42 - 0
client/ArtifactsUIController.h

@@ -0,0 +1,42 @@
+/*
+ * ArtifactsUIController.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+ #pragma once
+
+#include "../lib/constants/EntityIdentifiers.h"
+#include "../lib/networkPacks/ArtifactLocation.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CGHeroInstance;
+
+VCMI_LIB_NAMESPACE_END
+
+class ArtifactsUIController
+{
+	size_t numOfMovedArts;
+	size_t numOfArtsAskAssembleSession;
+	std::set<ArtifactID> ignoredArtifacts;
+
+	boost::mutex askAssembleArtifactMutex;
+
+public:
+	ArtifactsUIController();
+	bool askToAssemble(const ArtifactLocation & al, const bool onlyEquipped = false, const bool checkIgnored = false);
+	bool askToAssemble(const CGHeroInstance * hero, const ArtifactPosition & slot, const bool onlyEquipped = false,
+		const bool checkIgnored = false);
+	bool askToDisassemble(const CGHeroInstance * hero, const ArtifactPosition & slot);
+
+	void artifactRemoved();
+	void artifactMoved();
+	void bulkArtMovementStart(size_t totalNumOfArts, size_t possibleAssemblyNumOfArts);
+	void artifactAssembled();
+	void artifactDisassembled();
+};
+ 

+ 1 - 1
client/CGameInfo.h

@@ -55,7 +55,7 @@ public:
 extern CClientState * CCS;
 extern CClientState * CCS;
 
 
 /// CGameInfo class
 /// CGameInfo class
-/// for allowing different functions for accessing game informations
+/// for allowing different functions for accessing game information
 class CGameInfo final : public Services
 class CGameInfo final : public Services
 {
 {
 public:
 public:

+ 2 - 2
client/CMT.cpp

@@ -282,7 +282,7 @@ int main(int argc, char * argv[])
 		GH.init();
 		GH.init();
 
 
 	CCS = new CClientState();
 	CCS = new CClientState();
-	CGI = new CGameInfo(); //contains all global informations about game (texts, lodHandlers, map handler etc.)
+	CGI = new CGameInfo(); //contains all global information about game (texts, lodHandlers, map handler etc.)
 	CSH = new CServerHandler();
 	CSH = new CServerHandler();
 	
 	
 	// Initialize video
 	// Initialize video
@@ -525,7 +525,7 @@ void handleQuit(bool ask)
 
 
 void handleFatalError(const std::string & message, bool terminate)
 void handleFatalError(const std::string & message, bool terminate)
 {
 {
-	logGlobal->error("FATAL ERROR ENCOUTERED, VCMI WILL NOW TERMINATE");
+	logGlobal->error("FATAL ERROR ENCOUNTERED, VCMI WILL NOW TERMINATE");
 	logGlobal->error("Reason: %s", message);
 	logGlobal->error("Reason: %s", message);
 
 
 	std::string messageToShow = "Fatal error! " + message;
 	std::string messageToShow = "Fatal error! " + message;

+ 1 - 1
client/CMT.h

@@ -22,6 +22,6 @@ extern SDL_Surface *screenBuf; // points to screen (if only advmapint is present
 
 
 void handleQuit(bool ask = true);
 void handleQuit(bool ask = true);
 
 
-/// Notify user about encoutered fatal error and terminate the game
+/// Notify user about encountered fatal error and terminate the game
 /// TODO: decide on better location for this method
 /// TODO: decide on better location for this method
 [[noreturn]] void handleFatalError(const std::string & message, bool terminate);
 [[noreturn]] void handleFatalError(const std::string & message, bool terminate);

+ 2 - 0
client/CMakeLists.txt

@@ -168,6 +168,7 @@ set(client_SRCS
 	windows/settings/BattleOptionsTab.cpp
 	windows/settings/BattleOptionsTab.cpp
 	windows/settings/AdventureOptionsTab.cpp
 	windows/settings/AdventureOptionsTab.cpp
 
 
+	ArtifactsUIController.cpp
 	CGameInfo.cpp
 	CGameInfo.cpp
 	CMT.cpp
 	CMT.cpp
 	CPlayerInterface.cpp
 	CPlayerInterface.cpp
@@ -371,6 +372,7 @@ set(client_HEADERS
 	windows/settings/BattleOptionsTab.h
 	windows/settings/BattleOptionsTab.h
 	windows/settings/AdventureOptionsTab.h
 	windows/settings/AdventureOptionsTab.h
 
 
+	ArtifactsUIController.h
 	CGameInfo.h
 	CGameInfo.h
 	CMT.h
 	CMT.h
 	CPlayerInterface.h
 	CPlayerInterface.h

+ 16 - 76
client/CPlayerInterface.cpp

@@ -66,7 +66,6 @@
 
 
 #include "../CCallback.h"
 #include "../CCallback.h"
 
 
-#include "../lib/CArtHandler.h"
 #include "../lib/CConfigHandler.h"
 #include "../lib/CConfigHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/CHeroHandler.h"
@@ -132,7 +131,9 @@ struct HeroObjectRetriever
 
 
 CPlayerInterface::CPlayerInterface(PlayerColor Player):
 CPlayerInterface::CPlayerInterface(PlayerColor Player):
 	localState(std::make_unique<PlayerLocalState>(*this)),
 	localState(std::make_unique<PlayerLocalState>(*this)),
-	movementController(std::make_unique<HeroMovementController>())
+	movementController(std::make_unique<HeroMovementController>()),
+	artifactController(std::make_unique<ArtifactsUIController>())
+	
 {
 {
 	logGlobal->trace("\tHuman player interface for player %s being constructed", Player.toString());
 	logGlobal->trace("\tHuman player interface for player %s being constructed", Player.toString());
 	GH.defActionsDef = 0;
 	GH.defActionsDef = 0;
@@ -148,7 +149,6 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player):
 	isAutoFightOn = false;
 	isAutoFightOn = false;
 	isAutoFightEndBattle = false;
 	isAutoFightEndBattle = false;
 	ignoreEvents = false;
 	ignoreEvents = false;
-	numOfMovedArts = 0;
 }
 }
 
 
 CPlayerInterface::~CPlayerInterface()
 CPlayerInterface::~CPlayerInterface()
@@ -1125,7 +1125,7 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
 	std::vector<int> tempList;
 	std::vector<int> tempList;
 	tempList.reserve(objectGuiOrdered.size());
 	tempList.reserve(objectGuiOrdered.size());
 
 
-	for(auto item : objectGuiOrdered)
+	for(const auto & item : objectGuiOrdered)
 		tempList.push_back(item.getNum());
 		tempList.push_back(item.getNum());
 
 
 	CComponent localIconC(icon);
 	CComponent localIconC(icon);
@@ -1134,7 +1134,7 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
 	localIconC.removeChild(localIcon.get(), false);
 	localIconC.removeChild(localIcon.get(), false);
 
 
 	std::vector<std::shared_ptr<IImage>> images;
 	std::vector<std::shared_ptr<IImage>> images;
-	for(auto & obj : objectGuiOrdered)
+	for(const auto & obj : objectGuiOrdered)
 	{
 	{
 		if(!settings["general"]["enableUiEnhancements"].Bool())
 		if(!settings["general"]["enableUiEnhancements"].Bool())
 			break;
 			break;
@@ -1213,13 +1213,14 @@ void CPlayerInterface::heroBonusChanged( const CGHeroInstance *hero, const Bonus
 
 
 void CPlayerInterface::moveHero( const CGHeroInstance *h, const CGPath& path )
 void CPlayerInterface::moveHero( const CGHeroInstance *h, const CGPath& path )
 {
 {
+	LOG_TRACE(logGlobal);
+	if (!LOCPLINT->makingTurn)
+		return;
+
 	assert(h);
 	assert(h);
 	assert(!showingDialog->isBusy());
 	assert(!showingDialog->isBusy());
 	assert(dialogs.empty());
 	assert(dialogs.empty());
 
 
-	LOG_TRACE(logGlobal);
-	if (!LOCPLINT->makingTurn)
-		return;
 	if (!h)
 	if (!h)
 		return; //can't find hero
 		return; //can't find hero
 
 
@@ -1251,35 +1252,6 @@ void CPlayerInterface::showGarrisonDialog( const CArmedInstance *up, const CGHer
 	GH.windows().pushWindow(cgw);
 	GH.windows().pushWindow(cgw);
 }
 }
 
 
-/**
- * Shows the dialog that appears when right-clicking an artifact that can be assembled
- * into a combinational one on an artifact screen. Does not require the combination of
- * artifacts to be legal.
- */
-void CPlayerInterface::showArtifactAssemblyDialog(const Artifact * artifact, const Artifact * assembledArtifact, CFunctionList<void()> onYes)
-{
-	std::string text = artifact->getDescriptionTranslated();
-	text += "\n\n";
-	std::vector<std::shared_ptr<CComponent>> scs;
-
-	if(assembledArtifact)
-	{
-		// You possess all of the components to...
-		text += boost::str(boost::format(CGI->generaltexth->allTexts[732]) % assembledArtifact->getNameTranslated());
-
-		// Picture of assembled artifact at bottom.
-		auto sc = std::make_shared<CComponent>(ComponentType::ARTIFACT, assembledArtifact->getId());
-		scs.push_back(sc);
-	}
-	else
-	{
-		// Do you wish to disassemble this artifact?
-		text += CGI->generaltexth->allTexts[733];
-	}
-
-	showYesNoDialog(text, onYes, nullptr, scs);
-}
-
 void CPlayerInterface::requestRealized( PackageApplied *pa )
 void CPlayerInterface::requestRealized( PackageApplied *pa )
 {
 {
 	if(pa->packType == CTypeList::getInstance().getTypeID<MoveHero>(nullptr))
 	if(pa->packType == CTypeList::getInstance().getTypeID<MoveHero>(nullptr))
@@ -1737,17 +1709,7 @@ void CPlayerInterface::showShipyardDialogOrProblemPopup(const IShipyard *obj)
 
 
 void CPlayerInterface::askToAssembleArtifact(const ArtifactLocation &al)
 void CPlayerInterface::askToAssembleArtifact(const ArtifactLocation &al)
 {
 {
-	if(auto hero = cb->getHero(al.artHolder))
-	{
-		auto art = hero->getArt(al.slot);
-		if(art == nullptr)
-		{
-			logGlobal->error("artifact location %d points to nothing",
-							 al.slot.num);
-			return;
-		}
-		ArtifactUtilsClient::askToAssemble(hero, al.slot);
-	}
+	artifactController->askToAssemble(al, true, true);
 }
 }
 
 
 void CPlayerInterface::artifactPut(const ArtifactLocation &al)
 void CPlayerInterface::artifactPut(const ArtifactLocation &al)
@@ -1760,55 +1722,33 @@ void CPlayerInterface::artifactRemoved(const ArtifactLocation &al)
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	adventureInt->onHeroChanged(cb->getHero(al.artHolder));
 	adventureInt->onHeroChanged(cb->getHero(al.artHolder));
-
-	for(auto artWin : GH.windows().findWindows<CWindowWithArtifacts>())
-		artWin->artifactRemoved(al);
-
-	waitWhileDialog();
+	artifactController->artifactRemoved();
 }
 }
 
 
 void CPlayerInterface::artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst)
 void CPlayerInterface::artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst)
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	adventureInt->onHeroChanged(cb->getHero(dst.artHolder));
 	adventureInt->onHeroChanged(cb->getHero(dst.artHolder));
-
-	// If a bulk transfer has arrived, then redrawing only the last art movement.
-	if(numOfMovedArts != 0)
-		numOfMovedArts--;
-
-	for(auto artWin : GH.windows().findWindows<CWindowWithArtifacts>())
-	{
-		artWin->artifactMoved(src, dst);
-		if(numOfMovedArts == 0)
-		{
-			artWin->update();
-			artWin->redraw();
-		}
-	}
-	waitWhileDialog();
+	artifactController->artifactMoved();
 }
 }
 
 
-void CPlayerInterface::bulkArtMovementStart(size_t numOfArts)
+void CPlayerInterface::bulkArtMovementStart(size_t totalNumOfArts, size_t possibleAssemblyNumOfArts)
 {
 {
-	numOfMovedArts = numOfArts;
+	artifactController->bulkArtMovementStart(totalNumOfArts, possibleAssemblyNumOfArts);
 }
 }
 
 
 void CPlayerInterface::artifactAssembled(const ArtifactLocation &al)
 void CPlayerInterface::artifactAssembled(const ArtifactLocation &al)
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	adventureInt->onHeroChanged(cb->getHero(al.artHolder));
 	adventureInt->onHeroChanged(cb->getHero(al.artHolder));
-
-	for(auto artWin : GH.windows().findWindows<CWindowWithArtifacts>())
-		artWin->artifactAssembled(al);
+	artifactController->artifactAssembled();
 }
 }
 
 
 void CPlayerInterface::artifactDisassembled(const ArtifactLocation &al)
 void CPlayerInterface::artifactDisassembled(const ArtifactLocation &al)
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	adventureInt->onHeroChanged(cb->getHero(al.artHolder));
 	adventureInt->onHeroChanged(cb->getHero(al.artHolder));
-
-	for(auto artWin : GH.windows().findWindows<CWindowWithArtifacts>())
-		artWin->artifactDisassembled(al);
+	artifactController->artifactDisassembled();
 }
 }
 
 
 void CPlayerInterface::waitForAllDialogs()
 void CPlayerInterface::waitForAllDialogs()

+ 5 - 7
client/CPlayerInterface.h

@@ -9,6 +9,8 @@
  */
  */
 #pragma once
 #pragma once
 
 
+#include "ArtifactsUIController.h"
+
 #include "../lib/FunctionList.h"
 #include "../lib/FunctionList.h"
 #include "../lib/CGameInterface.h"
 #include "../lib/CGameInterface.h"
 #include "gui/CIntObject.h"
 #include "gui/CIntObject.h"
@@ -16,9 +18,7 @@
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
 class Artifact;
 class Artifact;
-
 struct TryMoveHero;
 struct TryMoveHero;
-class CGHeroInstance;
 class CStack;
 class CStack;
 class CCreature;
 class CCreature;
 struct CGPath;
 struct CGPath;
@@ -59,14 +59,13 @@ namespace boost
 class CPlayerInterface : public CGameInterface, public IUpdateable
 class CPlayerInterface : public CGameInterface, public IUpdateable
 {
 {
 	bool ignoreEvents;
 	bool ignoreEvents;
-	size_t numOfMovedArts;
-
 	int autosaveCount;
 	int autosaveCount;
 
 
 	std::list<std::shared_ptr<CInfoWindow>> dialogs; //queue of dialogs awaiting to be shown (not currently shown!)
 	std::list<std::shared_ptr<CInfoWindow>> dialogs; //queue of dialogs awaiting to be shown (not currently shown!)
 
 
 	std::unique_ptr<HeroMovementController> movementController;
 	std::unique_ptr<HeroMovementController> movementController;
 public: // TODO: make private
 public: // TODO: make private
+	std::unique_ptr<ArtifactsUIController> artifactController;
 	std::shared_ptr<Environment> env;
 	std::shared_ptr<Environment> env;
 
 
 	std::unique_ptr<PlayerLocalState> localState;
 	std::unique_ptr<PlayerLocalState> localState;
@@ -98,7 +97,7 @@ protected: // Call-ins from server, should not be called directly, but only via
 	void artifactPut(const ArtifactLocation &al) override;
 	void artifactPut(const ArtifactLocation &al) override;
 	void artifactRemoved(const ArtifactLocation &al) override;
 	void artifactRemoved(const ArtifactLocation &al) override;
 	void artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst) override;
 	void artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst) override;
-	void bulkArtMovementStart(size_t numOfArts) override;
+	void bulkArtMovementStart(size_t totalNumOfArts, size_t possibleAssemblyNumOfArts) override;
 	void artifactAssembled(const ArtifactLocation &al) override;
 	void artifactAssembled(const ArtifactLocation &al) override;
 	void askToAssembleArtifact(const ArtifactLocation & dst) override;
 	void askToAssembleArtifact(const ArtifactLocation & dst) override;
 	void artifactDisassembled(const ArtifactLocation &al) override;
 	void artifactDisassembled(const ArtifactLocation &al) override;
@@ -142,7 +141,7 @@ protected: // Call-ins from server, should not be called directly, but only via
 	void objectRemovedAfter() override;
 	void objectRemovedAfter() override;
 	void playerBlocked(int reason, bool start) override;
 	void playerBlocked(int reason, bool start) override;
 	void gameOver(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult) override;
 	void gameOver(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult) override;
-	void playerStartsTurn(PlayerColor player) override; //called before yourTurn on active itnerface
+	void playerStartsTurn(PlayerColor player) override; //called before yourTurn on active interface
 	void playerEndsTurn(PlayerColor player) override;
 	void playerEndsTurn(PlayerColor player) override;
 	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
 	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
 
 
@@ -180,7 +179,6 @@ public: // public interface for use by client via LOCPLINT access
 	void showShipyardDialog(const IShipyard *obj) override; //obj may be town or shipyard;
 	void showShipyardDialog(const IShipyard *obj) override; //obj may be town or shipyard;
 
 
 	void showHeroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2);
 	void showHeroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2);
-	void showArtifactAssemblyDialog(const Artifact * artifact, const Artifact * assembledArtifact, CFunctionList<void()> onYes);
 	void waitWhileDialog();
 	void waitWhileDialog();
 	void waitForAllDialogs();
 	void waitForAllDialogs();
 	void openTownWindow(const CGTownInstance * town); //shows townscreen
 	void openTownWindow(const CGTownInstance * town); //shows townscreen

+ 4 - 4
client/CServerHandler.cpp

@@ -238,9 +238,9 @@ void CServerHandler::startLocalServerAndConnect(bool connectToLobby)
 	si->difficulty = lastDifficulty.Integer();
 	si->difficulty = lastDifficulty.Integer();
 
 
 	logNetwork->trace("\tStarting local server");
 	logNetwork->trace("\tStarting local server");
-	serverRunner->start(getLocalPort(), connectToLobby, si);
+	uint16_t srvport = serverRunner->start(getLocalPort(), connectToLobby, si);
 	logNetwork->trace("\tConnecting to local server");
 	logNetwork->trace("\tConnecting to local server");
-	connectToServer(getLocalHostname(), getLocalPort());
+	connectToServer(getLocalHostname(), srvport);
 	logNetwork->trace("\tWaiting for connection");
 	logNetwork->trace("\tWaiting for connection");
 }
 }
 
 
@@ -648,14 +648,14 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta
 	if(CMM)
 	if(CMM)
 		CMM->disable();
 		CMM->disable();
 
 
-	campaignScoreCalculator = nullptr;
-
 	switch(si->mode)
 	switch(si->mode)
 	{
 	{
 	case EStartMode::NEW_GAME:
 	case EStartMode::NEW_GAME:
 		client->newGame(gameState);
 		client->newGame(gameState);
 		break;
 		break;
 	case EStartMode::CAMPAIGN:
 	case EStartMode::CAMPAIGN:
+		if(si->campState->conqueredScenarios().empty())
+			campaignScoreCalculator.reset();
 		client->newGame(gameState);
 		client->newGame(gameState);
 		break;
 		break;
 	case EStartMode::LOAD_GAME:
 	case EStartMode::LOAD_GAME:

+ 1 - 1
client/ClientCommandManager.h

@@ -66,7 +66,7 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a
 	// Export file into Extracted directory
 	// Export file into Extracted directory
 	void handleExtractCommand(std::istringstream& singleWordBuffer);
 	void handleExtractCommand(std::istringstream& singleWordBuffer);
 
 
-	// Print in console the current bonuses for curent army
+	// Print in console the current bonuses for current army
 	void handleBonusesCommand(std::istringstream & singleWordBuffer);
 	void handleBonusesCommand(std::istringstream & singleWordBuffer);
 
 
 	// Get what artifact is present on artifact slot with specified ID for hero with specified ID
 	// Get what artifact is present on artifact slot with specified ID for hero with specified ID

+ 0 - 1
client/ClientNetPackVisitors.h

@@ -48,7 +48,6 @@ public:
 	void visitBulkSmartRebalanceStacks(BulkSmartRebalanceStacks & pack) override;
 	void visitBulkSmartRebalanceStacks(BulkSmartRebalanceStacks & pack) override;
 	void visitPutArtifact(PutArtifact & pack) override;
 	void visitPutArtifact(PutArtifact & pack) override;
 	void visitEraseArtifact(EraseArtifact & pack) override;
 	void visitEraseArtifact(EraseArtifact & pack) override;
-	void visitMoveArtifact(MoveArtifact & pack) override;
 	void visitBulkMoveArtifacts(BulkMoveArtifacts & pack) override;
 	void visitBulkMoveArtifacts(BulkMoveArtifacts & pack) override;
 	void visitAssembledArtifact(AssembledArtifact & pack) override;
 	void visitAssembledArtifact(AssembledArtifact & pack) override;
 	void visitDisassembledArtifact(DisassembledArtifact & pack) override;
 	void visitDisassembledArtifact(DisassembledArtifact & pack) override;

+ 29 - 29
client/NetPacksClient.cpp

@@ -118,7 +118,7 @@ void callBattleInterfaceIfPresentForBothSides(CClient & cl, const BattleID & bat
 
 
 void ApplyClientNetPackVisitor::visitSetResources(SetResources & pack)
 void ApplyClientNetPackVisitor::visitSetResources(SetResources & pack)
 {
 {
-	//todo: inform on actual resource set transfered
+	//todo: inform on actual resource set transferred
 	callInterfaceIfPresent(cl, pack.player, &IGameEventsReceiver::receivedResource);
 	callInterfaceIfPresent(cl, pack.player, &IGameEventsReceiver::receivedResource);
 }
 }
 
 
@@ -296,45 +296,45 @@ void ApplyClientNetPackVisitor::visitEraseArtifact(EraseArtifact & pack)
 	callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::artifactRemoved, pack.al);
 	callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::artifactRemoved, pack.al);
 }
 }
 
 
-void ApplyClientNetPackVisitor::visitMoveArtifact(MoveArtifact & pack)
+void ApplyClientNetPackVisitor::visitBulkMoveArtifacts(BulkMoveArtifacts & pack)
 {
 {
-	auto moveArtifact = [this, &pack](PlayerColor player) -> void
+	const auto dstOwner = cl.getOwner(pack.dstArtHolder);
+	const auto applyMove = [this, &pack, dstOwner](std::vector<BulkMoveArtifacts::LinkedSlots> & artsPack)
 	{
 	{
-		callInterfaceIfPresent(cl, player, &IGameEventsReceiver::artifactMoved, pack.src, pack.dst);
-		if(pack.askAssemble)
-			callInterfaceIfPresent(cl, player, &IGameEventsReceiver::askToAssembleArtifact, pack.dst);
-	};
+		for(const auto & slotToMove : artsPack)
+		{
+			const auto srcLoc = ArtifactLocation(pack.srcArtHolder, slotToMove.srcPos);
+			const auto dstLoc = ArtifactLocation(pack.dstArtHolder, slotToMove.dstPos);
 
 
-	moveArtifact(pack.interfaceOwner);
-	if(pack.interfaceOwner != cl.getOwner(pack.dst.artHolder))
-		moveArtifact(cl.getOwner(pack.dst.artHolder));
+			callInterfaceIfPresent(cl, pack.interfaceOwner, &IGameEventsReceiver::artifactMoved, srcLoc, dstLoc);
+			if(slotToMove.askAssemble)
+				callInterfaceIfPresent(cl, pack.interfaceOwner, &IGameEventsReceiver::askToAssembleArtifact, dstLoc);
+			if(pack.interfaceOwner != dstOwner)
+				callInterfaceIfPresent(cl, dstOwner, &IGameEventsReceiver::artifactMoved, srcLoc, dstLoc);
 
 
-	cl.invalidatePaths(); // hero might have equipped/unequipped Angel Wings
-}
+			cl.invalidatePaths(); // hero might have equipped/unequipped Angel Wings
+		}
+	};
 
 
-void ApplyClientNetPackVisitor::visitBulkMoveArtifacts(BulkMoveArtifacts & pack)
-{
-	auto applyMove = [this, &pack](std::vector<BulkMoveArtifacts::LinkedSlots> & artsPack) -> void
+	size_t possibleAssemblyNumOfArts = 0;
+	const auto calcPossibleAssemblyNumOfArts = [&possibleAssemblyNumOfArts](const auto & slotToMove)
 	{
 	{
-		for(auto & slotToMove : artsPack)
-		{
-			auto srcLoc = ArtifactLocation(pack.srcArtHolder, slotToMove.srcPos);
-			auto dstLoc = ArtifactLocation(pack.dstArtHolder, slotToMove.dstPos);
-			MoveArtifact ma(pack.interfaceOwner, srcLoc, dstLoc, pack.askAssemble);
-			visitMoveArtifact(ma);
-		}
+		if(slotToMove.askAssemble)
+			possibleAssemblyNumOfArts++;
 	};
 	};
+	std::for_each(pack.artsPack0.cbegin(), pack.artsPack0.cend(), calcPossibleAssemblyNumOfArts);
+	std::for_each(pack.artsPack1.cbegin(), pack.artsPack1.cend(), calcPossibleAssemblyNumOfArts);
 
 
-	auto srcOwner = cl.getOwner(pack.srcArtHolder);
-	auto dstOwner = cl.getOwner(pack.dstArtHolder);
 
 
 	// Begin a session of bulk movement of arts. It is not necessary but useful for the client optimization.
 	// Begin a session of bulk movement of arts. It is not necessary but useful for the client optimization.
-	callInterfaceIfPresent(cl, srcOwner, &IGameEventsReceiver::bulkArtMovementStart, pack.artsPack0.size() + pack.artsPack1.size());
-	if(srcOwner != dstOwner)
-		callInterfaceIfPresent(cl, dstOwner, &IGameEventsReceiver::bulkArtMovementStart, pack.artsPack0.size() + pack.artsPack1.size());
+	callInterfaceIfPresent(cl, pack.interfaceOwner, &IGameEventsReceiver::bulkArtMovementStart,
+		pack.artsPack0.size() + pack.artsPack1.size(), possibleAssemblyNumOfArts);
+	if(pack.interfaceOwner != dstOwner)
+		callInterfaceIfPresent(cl, dstOwner, &IGameEventsReceiver::bulkArtMovementStart,
+			pack.artsPack0.size() + pack.artsPack1.size(), possibleAssemblyNumOfArts);
 
 
 	applyMove(pack.artsPack0);
 	applyMove(pack.artsPack0);
-	if(pack.swap)
+	if(!pack.artsPack1.empty())
 		applyMove(pack.artsPack1);
 		applyMove(pack.artsPack1);
 }
 }
 
 
@@ -844,7 +844,7 @@ void ApplyFirstClientNetPackVisitor::visitBattleAttack(BattleAttack & pack)
 {
 {
 	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleAttack, pack.battleID, &pack);
 	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleAttack, pack.battleID, &pack);
 
 
-	// battleStacksAttacked should be excuted before BattleAttack.applyGs() to play animation before damaging unit
+	// battleStacksAttacked should be executed before BattleAttack.applyGs() to play animation before damaging unit
 	// so this has to be here instead of ApplyClientNetPackVisitor::visitBattleAttack()
 	// so this has to be here instead of ApplyClientNetPackVisitor::visitBattleAttack()
 	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleStacksAttacked, pack.battleID, pack.bsa, pack.shot());
 	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleStacksAttacked, pack.battleID, pack.bsa, pack.shot());
 }
 }

+ 7 - 1
client/PlayerLocalState.cpp

@@ -168,7 +168,7 @@ void PlayerLocalState::setSelection(const CArmedInstance * selection)
 
 
 	currentSelection = selection;
 	currentSelection = selection;
 
 
-	if (selection)
+	if (adventureInt && selection)
 		adventureInt->onSelectionChanged(selection);
 		adventureInt->onSelectionChanged(selection);
 }
 }
 
 
@@ -212,6 +212,9 @@ void PlayerLocalState::addWanderingHero(const CGHeroInstance * hero)
 	assert(hero);
 	assert(hero);
 	assert(!vstd::contains(wanderingHeroes, hero));
 	assert(!vstd::contains(wanderingHeroes, hero));
 	wanderingHeroes.push_back(hero);
 	wanderingHeroes.push_back(hero);
+
+	if (currentSelection == nullptr)
+		setSelection(hero);
 }
 }
 
 
 void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero)
 void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero)
@@ -260,6 +263,9 @@ void PlayerLocalState::addOwnedTown(const CGTownInstance * town)
 	assert(town);
 	assert(town);
 	assert(!vstd::contains(ownedTowns, town));
 	assert(!vstd::contains(ownedTowns, town));
 	ownedTowns.push_back(town);
 	ownedTowns.push_back(town);
+
+	if (currentSelection == nullptr)
+		setSelection(town);
 }
 }
 
 
 void PlayerLocalState::removeOwnedTown(const CGTownInstance * town)
 void PlayerLocalState::removeOwnedTown(const CGTownInstance * town)

+ 19 - 4
client/ServerRunner.cpp

@@ -20,22 +20,35 @@
 #include <boost/process/io.hpp>
 #include <boost/process/io.hpp>
 #endif
 #endif
 
 
+#include <future>
+
 ServerThreadRunner::ServerThreadRunner() = default;
 ServerThreadRunner::ServerThreadRunner() = default;
 ServerThreadRunner::~ServerThreadRunner() = default;
 ServerThreadRunner::~ServerThreadRunner() = default;
 
 
-void ServerThreadRunner::start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo)
+uint16_t ServerThreadRunner::start(uint16_t cfgport, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo)
 {
 {
-	server = std::make_unique<CVCMIServer>(port, connectToLobby, true);
+	// cfgport may be 0 -- the real port is returned after calling prepare()
+	server = std::make_unique<CVCMIServer>(cfgport, true);
 
 
 	if (startingInfo)
 	if (startingInfo)
 	{
 	{
 		server->si = startingInfo; //Else use default
 		server->si = startingInfo; //Else use default
 	}
 	}
 
 
-	threadRunLocalServer = boost::thread([this]{
+	std::promise<uint16_t> promise;
+
+	threadRunLocalServer = boost::thread([this, connectToLobby, &promise]{
 		setThreadName("runServer");
 		setThreadName("runServer");
+		uint16_t port = server->prepare(connectToLobby);
+		promise.set_value(port);
 		server->run();
 		server->run();
 	});
 	});
+
+	logNetwork->trace("Waiting for server port...");
+	auto srvport = promise.get_future().get();
+	logNetwork->debug("Server port: %d", srvport);
+
+	return srvport;
 }
 }
 
 
 void ServerThreadRunner::shutdown()
 void ServerThreadRunner::shutdown()
@@ -73,7 +86,7 @@ int ServerProcessRunner::exitCode()
 	return child->exit_code();
 	return child->exit_code();
 }
 }
 
 
-void ServerProcessRunner::start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo)
+uint16_t ServerProcessRunner::start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo)
 {
 {
 	boost::filesystem::path serverPath = VCMIDirs::get().serverPath();
 	boost::filesystem::path serverPath = VCMIDirs::get().serverPath();
 	boost::filesystem::path logPath = VCMIDirs::get().userLogsPath() / "server_log.txt";
 	boost::filesystem::path logPath = VCMIDirs::get().userLogsPath() / "server_log.txt";
@@ -88,6 +101,8 @@ void ServerProcessRunner::start(uint16_t port, bool connectToLobby, std::shared_
 
 
 	if (ec)
 	if (ec)
 		throw std::runtime_error("Failed to start server! Reason: " + ec.message());
 		throw std::runtime_error("Failed to start server! Reason: " + ec.message());
+
+	return port;
 }
 }
 
 
 #endif
 #endif

+ 3 - 3
client/ServerRunner.h

@@ -20,7 +20,7 @@ class CVCMIServer;
 class IServerRunner
 class IServerRunner
 {
 {
 public:
 public:
-	virtual void start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) = 0;
+	virtual uint16_t start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) = 0;
 	virtual void shutdown() = 0;
 	virtual void shutdown() = 0;
 	virtual void wait() = 0;
 	virtual void wait() = 0;
 	virtual int exitCode() = 0;
 	virtual int exitCode() = 0;
@@ -34,7 +34,7 @@ class ServerThreadRunner : public IServerRunner, boost::noncopyable
 	std::unique_ptr<CVCMIServer> server;
 	std::unique_ptr<CVCMIServer> server;
 	boost::thread threadRunLocalServer;
 	boost::thread threadRunLocalServer;
 public:
 public:
-	void start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) override;
+	uint16_t start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) override;
 	void shutdown() override;
 	void shutdown() override;
 	void wait() override;
 	void wait() override;
 	int exitCode() override;
 	int exitCode() override;
@@ -56,7 +56,7 @@ class ServerProcessRunner : public IServerRunner, boost::noncopyable
 	std::unique_ptr<boost::process::child> child;
 	std::unique_ptr<boost::process::child> child;
 
 
 public:
 public:
-	void start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) override;
+	uint16_t start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) override;
 	void shutdown() override;
 	void shutdown() override;
 	void wait() override;
 	void wait() override;
 	int exitCode() override;
 	int exitCode() override;

+ 2 - 2
client/adventureMap/AdventureMapInterface.cpp

@@ -395,8 +395,6 @@ void AdventureMapInterface::adjustActiveness()
 
 
 void AdventureMapInterface::onCurrentPlayerChanged(PlayerColor playerID)
 void AdventureMapInterface::onCurrentPlayerChanged(PlayerColor playerID)
 {
 {
-	LOCPLINT->localState->setSelection(nullptr);
-
 	if (playerID == currentPlayerID)
 	if (playerID == currentPlayerID)
 		return;
 		return;
 
 
@@ -446,6 +444,8 @@ void AdventureMapInterface::onPlayerTurnStarted(PlayerColor playerID)
 		LOCPLINT->localState->setSelection(LOCPLINT->localState->getWanderingHero(0));
 		LOCPLINT->localState->setSelection(LOCPLINT->localState->getWanderingHero(0));
 	}
 	}
 
 
+	centerOnObject(LOCPLINT->localState->getCurrentArmy());
+
 	//show new day animation and sound on infobar, except for 1st day of the game
 	//show new day animation and sound on infobar, except for 1st day of the game
 	if (LOCPLINT->cb->getDate(Date::DAY) != 1)
 	if (LOCPLINT->cb->getDate(Date::DAY) != 1)
 		widget->getInfoBar()->showDate();
 		widget->getInfoBar()->showDate();

+ 1 - 1
client/adventureMap/CInGameConsole.cpp

@@ -221,7 +221,7 @@ void CInGameConsole::keyPressed (EShortcut key)
 	}
 	}
 }
 }
 
 
-void CInGameConsole::textInputed(const std::string & inputtedText)
+void CInGameConsole::textInputted(const std::string & inputtedText)
 {
 {
 	if (LOCPLINT->cingconsole != this)
 	if (LOCPLINT->cingconsole != this)
 		return;
 		return;

+ 1 - 1
client/adventureMap/CInGameConsole.h

@@ -50,7 +50,7 @@ public:
 	void show(Canvas & to) override;
 	void show(Canvas & to) override;
 	void showAll(Canvas & to) override;
 	void showAll(Canvas & to) override;
 	void keyPressed(EShortcut key) override;
 	void keyPressed(EShortcut key) override;
-	void textInputed(const std::string & enteredText) override;
+	void textInputted(const std::string & enteredText) override;
 	void textEdited(const std::string & enteredText) override;
 	void textEdited(const std::string & enteredText) override;
 	bool captureThisKey(EShortcut key) override;
 	bool captureThisKey(EShortcut key) override;
 
 

+ 1 - 1
client/adventureMap/CList.cpp

@@ -380,7 +380,7 @@ std::shared_ptr<CIntObject> CTownList::CTownItem::genSelection()
 
 
 void CTownList::CTownItem::update()
 void CTownList::CTownItem::update()
 {
 {
-	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->builded >= CGI->settings()->getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
+	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= CGI->settings()->getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
 
 
 	picture->setFrame(iconIndex + 2);
 	picture->setFrame(iconIndex + 2);
 	redraw();
 	redraw();

+ 3 - 3
client/adventureMap/CMinimap.cpp

@@ -92,9 +92,9 @@ CMinimap::CMinimap(const Rect & position)
 {
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
 
-	double maxSideLenghtSrc = std::max(LOCPLINT->cb->getMapSize().x, LOCPLINT->cb->getMapSize().y);
-	double maxSideLenghtDst = std::max(position.w, position.h);
-	double resize = maxSideLenghtSrc / maxSideLenghtDst;
+	double maxSideLengthSrc = std::max(LOCPLINT->cb->getMapSize().x, LOCPLINT->cb->getMapSize().y);
+	double maxSideLengthDst = std::max(position.w, position.h);
+	double resize = maxSideLengthSrc / maxSideLengthDst;
 	Point newMinimapSize = Point(LOCPLINT->cb->getMapSize().x/ resize, LOCPLINT->cb->getMapSize().y / resize);
 	Point newMinimapSize = Point(LOCPLINT->cb->getMapSize().x/ resize, LOCPLINT->cb->getMapSize().y / resize);
 	Point offset = Point((std::max(newMinimapSize.x, newMinimapSize.y) - newMinimapSize.x) / 2, (std::max(newMinimapSize.x, newMinimapSize.y) - newMinimapSize.y) / 2);
 	Point offset = Point((std::max(newMinimapSize.x, newMinimapSize.y) - newMinimapSize.x) / 2, (std::max(newMinimapSize.x, newMinimapSize.y) - newMinimapSize.y) / 2);
 
 

+ 1 - 1
client/battle/BattleActionsController.h

@@ -120,6 +120,6 @@ public:
 	const std::vector<PossiblePlayerBattleAction> & getPossibleActions() const;
 	const std::vector<PossiblePlayerBattleAction> & getPossibleActions() const;
 	void removePossibleAction(PossiblePlayerBattleAction);
 	void removePossibleAction(PossiblePlayerBattleAction);
 	
 	
-	/// inserts possible action in the beggining in order to prioritize it
+	/// inserts possible action in the beginning in order to prioritize it
 	void pushFrontPossibleAction(PossiblePlayerBattleAction);
 	void pushFrontPossibleAction(PossiblePlayerBattleAction);
 };
 };

+ 16 - 16
client/battle/BattleAnimationClasses.cpp

@@ -355,9 +355,9 @@ bool MovementAnimation::init()
 		myAnim->setType(ECreatureAnimType::MOVING);
 		myAnim->setType(ECreatureAnimType::MOVING);
 	}
 	}
 
 
-	if (moveSoundHander == -1)
+	if (moveSoundHandler == -1)
 	{
 	{
-		moveSoundHander = CCS->soundh->playSound(stack->unitType()->sounds.move, -1);
+		moveSoundHandler = CCS->soundh->playSound(stack->unitType()->sounds.move, -1);
 	}
 	}
 
 
 	Point begPosition = owner.stacksController->getStackPositionAtHex(prevHex, stack);
 	Point begPosition = owner.stacksController->getStackPositionAtHex(prevHex, stack);
@@ -398,12 +398,12 @@ void MovementAnimation::tick(uint32_t msPassed)
 		myAnim->pos.moveTo(coords);
 		myAnim->pos.moveTo(coords);
 
 
 		// true if creature haven't reached the final destination hex
 		// true if creature haven't reached the final destination hex
-		if ((curentMoveIndex + 1) < destTiles.size())
+		if ((currentMoveIndex + 1) < destTiles.size())
 		{
 		{
 			// update the next hex field which has to be reached by the stack
 			// update the next hex field which has to be reached by the stack
-			curentMoveIndex++;
+			currentMoveIndex++;
 			prevHex = nextHex;
 			prevHex = nextHex;
-			nextHex = destTiles[curentMoveIndex];
+			nextHex = destTiles[currentMoveIndex];
 
 
 			// request re-initialization
 			// request re-initialization
 			initialized = false;
 			initialized = false;
@@ -417,18 +417,18 @@ MovementAnimation::~MovementAnimation()
 {
 {
 	assert(stack);
 	assert(stack);
 
 
-	if(moveSoundHander != -1)
-		CCS->soundh->stopSound(moveSoundHander);
+	if(moveSoundHandler != -1)
+		CCS->soundh->stopSound(moveSoundHandler);
 }
 }
 
 
 MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *stack, std::vector<BattleHex> _destTiles, int _distance)
 MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *stack, std::vector<BattleHex> _destTiles, int _distance)
 	: StackMoveAnimation(owner, stack, stack->getPosition(), _destTiles.front()),
 	: StackMoveAnimation(owner, stack, stack->getPosition(), _destTiles.front()),
 	  destTiles(_destTiles),
 	  destTiles(_destTiles),
-	  curentMoveIndex(0),
+	  currentMoveIndex(0),
 	  begX(0), begY(0),
 	  begX(0), begY(0),
 	  distanceX(0), distanceY(0),
 	  distanceX(0), distanceY(0),
 	  progressPerSecond(0.0),
 	  progressPerSecond(0.0),
-	  moveSoundHander(-1),
+	  moveSoundHandler(-1),
 	  progress(0.0)
 	  progress(0.0)
 {
 {
 	logAnim->debug("Created MovementAnimation for %s", stack->getName());
 	logAnim->debug("Created MovementAnimation for %s", stack->getName());
@@ -649,7 +649,7 @@ void RangedAttackAnimation::setAnimationGroup()
 	Point shooterPos = stackAnimation(attackingStack)->pos.topLeft();
 	Point shooterPos = stackAnimation(attackingStack)->pos.topLeft();
 	Point shotTarget = owner.stacksController->getStackPositionAtHex(dest, defendingStack);
 	Point shotTarget = owner.stacksController->getStackPositionAtHex(dest, defendingStack);
 
 
-	//maximal angle in radians between straight horizontal line and shooting line for which shot is considered to be straight (absoulte value)
+	//maximal angle in radians between straight horizontal line and shooting line for which shot is considered to be straight (absolute value)
 	static const double straightAngle = 0.2;
 	static const double straightAngle = 0.2;
 
 
 	double projectileAngle = -atan2(shotTarget.y - shooterPos.y, std::abs(shotTarget.x - shooterPos.x));
 	double projectileAngle = -atan2(shotTarget.y - shooterPos.y, std::abs(shotTarget.x - shooterPos.x));
@@ -672,18 +672,18 @@ void RangedAttackAnimation::initializeProjectile()
 
 
 	if (getGroup() == getUpwardsGroup())
 	if (getGroup() == getUpwardsGroup())
 	{
 	{
-		shotOrigin.x += ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier;
-		shotOrigin.y += shooterInfo->animation.upperRightMissleOffsetY;
+		shotOrigin.x += ( -25 + shooterInfo->animation.upperRightMissileOffsetX ) * multiplier;
+		shotOrigin.y += shooterInfo->animation.upperRightMissileOffsetY;
 	}
 	}
 	else if (getGroup() == getDownwardsGroup())
 	else if (getGroup() == getDownwardsGroup())
 	{
 	{
-		shotOrigin.x += ( -25 + shooterInfo->animation.lowerRightMissleOffsetX ) * multiplier;
-		shotOrigin.y += shooterInfo->animation.lowerRightMissleOffsetY;
+		shotOrigin.x += ( -25 + shooterInfo->animation.lowerRightMissileOffsetX ) * multiplier;
+		shotOrigin.y += shooterInfo->animation.lowerRightMissileOffsetY;
 	}
 	}
 	else if (getGroup() == getForwardGroup())
 	else if (getGroup() == getForwardGroup())
 	{
 	{
-		shotOrigin.x += ( -25 + shooterInfo->animation.rightMissleOffsetX ) * multiplier;
-		shotOrigin.y += shooterInfo->animation.rightMissleOffsetY;
+		shotOrigin.x += ( -25 + shooterInfo->animation.rightMissileOffsetX ) * multiplier;
+		shotOrigin.y += shooterInfo->animation.rightMissileOffsetY;
 	}
 	}
 	else
 	else
 	{
 	{

+ 2 - 2
client/battle/BattleAnimationClasses.h

@@ -141,10 +141,10 @@ protected:
 class MovementAnimation : public StackMoveAnimation
 class MovementAnimation : public StackMoveAnimation
 {
 {
 private:
 private:
-	int moveSoundHander; // sound handler used when moving a unit
+	int moveSoundHandler; // sound handler used when moving a unit
 
 
 	std::vector<BattleHex> destTiles; //full path, includes already passed hexes
 	std::vector<BattleHex> destTiles; //full path, includes already passed hexes
-	ui32 curentMoveIndex; // index of nextHex in destTiles
+	ui32 currentMoveIndex; // index of nextHex in destTiles
 
 
 	double begX, begY; // starting position
 	double begX, begY; // starting position
 	double distanceX, distanceY; // full movement distance, may be negative if creture moves topleft
 	double distanceX, distanceY; // full movement distance, may be negative if creture moves topleft

+ 10 - 10
client/battle/BattleFieldController.cpp

@@ -84,7 +84,7 @@ namespace HexMasks
 
 
 std::map<int, int> hexEdgeMaskToFrameIndex;
 std::map<int, int> hexEdgeMaskToFrameIndex;
 
 
-// Maps HexEdgesMask to "Frame" indexes for range highligt images
+// Maps HexEdgesMask to "Frame" indexes for range highlight images
 void initializeHexEdgeMaskToFrameIndex()
 void initializeHexEdgeMaskToFrameIndex()
 {
 {
 	hexEdgeMaskToFrameIndex[HexMasks::empty] = 0;
 	hexEdgeMaskToFrameIndex[HexMasks::empty] = 0;
@@ -548,12 +548,12 @@ std::vector<std::shared_ptr<IImage>> BattleFieldController::calculateRangeLimitH
 	return output;
 	return output;
 }
 }
 
 
-void BattleFieldController::calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr<CAnimation> rangeLimitImages, std::vector<BattleHex> & rangeLimitHexes, std::vector<std::shared_ptr<IImage>> & rangeLimitHexesHighligts)
+void BattleFieldController::calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr<CAnimation> rangeLimitImages, std::vector<BattleHex> & rangeLimitHexes, std::vector<std::shared_ptr<IImage>> & rangeLimitHexesHighlights)
 {
 {
 		std::vector<BattleHex> rangeHexes = getRangeHexes(hoveredHex, distance);
 		std::vector<BattleHex> rangeHexes = getRangeHexes(hoveredHex, distance);
 		rangeLimitHexes = getRangeLimitHexes(hoveredHex, rangeHexes, distance);
 		rangeLimitHexes = getRangeLimitHexes(hoveredHex, rangeHexes, distance);
 		std::vector<std::vector<BattleHex::EDir>> rangeLimitNeighbourDirections = getOutsideNeighbourDirectionsForLimitHexes(rangeHexes, rangeLimitHexes);
 		std::vector<std::vector<BattleHex::EDir>> rangeLimitNeighbourDirections = getOutsideNeighbourDirectionsForLimitHexes(rangeHexes, rangeLimitHexes);
-		rangeLimitHexesHighligts = calculateRangeLimitHighlightImages(rangeLimitNeighbourDirections, rangeLimitImages);
+		rangeLimitHexesHighlights = calculateRangeLimitHighlightImages(rangeLimitNeighbourDirections, rangeLimitImages);
 }
 }
 
 
 void BattleFieldController::flipRangeLimitImagesIntoPositions(std::shared_ptr<CAnimation> images)
 void BattleFieldController::flipRangeLimitImagesIntoPositions(std::shared_ptr<CAnimation> images)
@@ -581,8 +581,8 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 	std::vector<BattleHex> rangedFullDamageLimitHexes;
 	std::vector<BattleHex> rangedFullDamageLimitHexes;
 	std::vector<BattleHex> shootingRangeLimitHexes;
 	std::vector<BattleHex> shootingRangeLimitHexes;
 
 
-	std::vector<std::shared_ptr<IImage>> rangedFullDamageLimitHexesHighligts;
-	std::vector<std::shared_ptr<IImage>> shootingRangeLimitHexesHighligts;
+	std::vector<std::shared_ptr<IImage>> rangedFullDamageLimitHexesHighlights;
+	std::vector<std::shared_ptr<IImage>> shootingRangeLimitHexesHighlights;
 
 
 	std::set<BattleHex> hoveredStackMovementRangeHexes = getMovementRangeForHoveredStack();
 	std::set<BattleHex> hoveredStackMovementRangeHexes = getMovementRangeForHoveredStack();
 	std::set<BattleHex> hoveredSpellHexes = getHighlightedHexesForSpellRange();
 	std::set<BattleHex> hoveredSpellHexes = getHighlightedHexesForSpellRange();
@@ -598,11 +598,11 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 	{
 	{
 		// calculate array with highlight images for ranged full damage limit
 		// calculate array with highlight images for ranged full damage limit
 		auto rangedFullDamageDistance = hoveredStack->getRangedFullDamageDistance();
 		auto rangedFullDamageDistance = hoveredStack->getRangedFullDamageDistance();
-		calculateRangeLimitAndHighlightImages(rangedFullDamageDistance, rangedFullDamageLimitImages, rangedFullDamageLimitHexes, rangedFullDamageLimitHexesHighligts);
+		calculateRangeLimitAndHighlightImages(rangedFullDamageDistance, rangedFullDamageLimitImages, rangedFullDamageLimitHexes, rangedFullDamageLimitHexesHighlights);
 
 
 		// calculate array with highlight images for shooting range limit
 		// calculate array with highlight images for shooting range limit
 		auto shootingRangeDistance = hoveredStack->getShootingRangeDistance();
 		auto shootingRangeDistance = hoveredStack->getShootingRangeDistance();
-		calculateRangeLimitAndHighlightImages(shootingRangeDistance, shootingRangeLimitImages, shootingRangeLimitHexes, shootingRangeLimitHexesHighligts);
+		calculateRangeLimitAndHighlightImages(shootingRangeDistance, shootingRangeLimitImages, shootingRangeLimitHexes, shootingRangeLimitHexesHighlights);
 	}
 	}
 
 
 	auto const & hoveredMouseHexes = hoveredHex != BattleHex::INVALID && owner.actionsController->currentActionSpellcasting(getHoveredHex()) ? hoveredSpellHexes : hoveredMoveHexes;
 	auto const & hoveredMouseHexes = hoveredHex != BattleHex::INVALID && owner.actionsController->currentActionSpellcasting(getHoveredHex()) ? hoveredSpellHexes : hoveredMoveHexes;
@@ -635,11 +635,11 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 		}
 		}
 		if(hexInRangedFullDamageLimit)
 		if(hexInRangedFullDamageLimit)
 		{
 		{
-			showHighlightedHex(canvas, rangedFullDamageLimitHexesHighligts[hexIndexInRangedFullDamageLimit], hex, false);
+			showHighlightedHex(canvas, rangedFullDamageLimitHexesHighlights[hexIndexInRangedFullDamageLimit], hex, false);
 		}
 		}
 		if(hexInShootingRangeLimit)
 		if(hexInShootingRangeLimit)
 		{
 		{
-			showHighlightedHex(canvas, shootingRangeLimitHexesHighligts[hexIndexInShootingRangeLimit], hex, false);
+			showHighlightedHex(canvas, shootingRangeLimitHexesHighlights[hexIndexInShootingRangeLimit], hex, false);
 		}
 		}
 	}
 	}
 }
 }
@@ -865,7 +865,7 @@ bool BattleFieldController::isTileAttackable(const BattleHex & number) const
 
 
 void BattleFieldController::updateAccessibleHexes()
 void BattleFieldController::updateAccessibleHexes()
 {
 {
-	auto accessibility = owner.getBattle()->getAccesibility();
+	auto accessibility = owner.getBattle()->getAccessibility();
 
 
 	for(int i = 0; i < accessibility.size(); i++)
 	for(int i = 0; i < accessibility.size(); i++)
 		stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE || (accessibility[i] == EAccessibility::SIDE_COLUMN));
 		stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE || (accessibility[i] == EAccessibility::SIDE_COLUMN));

+ 2 - 2
client/battle/BattleFieldController.h

@@ -73,7 +73,7 @@ class BattleFieldController : public CIntObject
 	/// calculate if a hex is in range limit and return its index in range
 	/// calculate if a hex is in range limit and return its index in range
 	bool IsHexInRangeLimit(BattleHex hex, std::vector<BattleHex> & rangeLimitHexes, int * hexIndexInRangeLimit);
 	bool IsHexInRangeLimit(BattleHex hex, std::vector<BattleHex> & rangeLimitHexes, int * hexIndexInRangeLimit);
 
 
-	/// get an array that has for each hex in range, an aray with all directions where an ouside neighbour hex exists
+	/// get an array that has for each hex in range, an array with all directions where an outside neighbour hex exists
 	std::vector<std::vector<BattleHex::EDir>> getOutsideNeighbourDirectionsForLimitHexes(std::vector<BattleHex> rangeHexes, std::vector<BattleHex> rangeLimitHexes);
 	std::vector<std::vector<BattleHex::EDir>> getOutsideNeighbourDirectionsForLimitHexes(std::vector<BattleHex> rangeHexes, std::vector<BattleHex> rangeLimitHexes);
 
 
 	/// calculates what image to use as range limit, depending on the direction of neighbors
 	/// calculates what image to use as range limit, depending on the direction of neighbors
@@ -82,7 +82,7 @@ class BattleFieldController : public CIntObject
 	std::vector<std::shared_ptr<IImage>> calculateRangeLimitHighlightImages(std::vector<std::vector<BattleHex::EDir>> hexesNeighbourDirections, std::shared_ptr<CAnimation> limitImages);
 	std::vector<std::shared_ptr<IImage>> calculateRangeLimitHighlightImages(std::vector<std::vector<BattleHex::EDir>> hexesNeighbourDirections, std::shared_ptr<CAnimation> limitImages);
 
 
 	/// calculates all hexes for a range limit and what images to be shown as highlight for each of the hexes
 	/// calculates all hexes for a range limit and what images to be shown as highlight for each of the hexes
-	void calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr<CAnimation> rangeLimitImages, std::vector<BattleHex> & rangeLimitHexes, std::vector<std::shared_ptr<IImage>> & rangeLimitHexesHighligts);
+	void calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr<CAnimation> rangeLimitImages, std::vector<BattleHex> & rangeLimitHexes, std::vector<std::shared_ptr<IImage>> & rangeLimitHexesHighlights);
 
 
 	/// to reduce the number of source images used, some images will be used as flipped versions of preloaded ones
 	/// to reduce the number of source images used, some images will be used as flipped versions of preloaded ones
 	void flipRangeLimitImagesIntoPositions(std::shared_ptr<CAnimation> images);
 	void flipRangeLimitImagesIntoPositions(std::shared_ptr<CAnimation> images);

+ 1 - 1
client/battle/BattleInterface.h

@@ -206,7 +206,7 @@ public:
 	void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
 	void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
 	void stackAttacking(const StackAttackInfo & attackInfo); //called when stack with id ID is attacking something on hex dest
 	void stackAttacking(const StackAttackInfo & attackInfo); //called when stack with id ID is attacking something on hex dest
 	void newRoundFirst();
 	void newRoundFirst();
-	void newRound(); //caled when round is ended;
+	void newRound(); //called when round is ended;
 	void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls
 	void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls
 	void battleFinished(const BattleResult& br, QueryID queryID); //called when battle is finished - battleresult window should be printed
 	void battleFinished(const BattleResult& br, QueryID queryID); //called when battle is finished - battleresult window should be printed
 	void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell
 	void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell

+ 2 - 2
client/battle/BattleProjectileController.cpp

@@ -161,7 +161,7 @@ const CCreature & BattleProjectileController::getShooter(const CStack * stack) c
 	if(creature->getId() == CreatureID::ARROW_TOWERS)
 	if(creature->getId() == CreatureID::ARROW_TOWERS)
 		creature = owner.siegeController->getTurretCreature();
 		creature = owner.siegeController->getTurretCreature();
 
 
-	if(creature->animation.missleFrameAngles.empty())
+	if(creature->animation.missileFrameAngles.empty())
 	{
 	{
 		logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead...", creature->getNameSingularTranslated());
 		logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead...", creature->getNameSingularTranslated());
 		creature = CreatureID(CreatureID::ARCHER).toCreature();
 		creature = CreatureID(CreatureID::ARCHER).toCreature();
@@ -277,7 +277,7 @@ int BattleProjectileController::computeProjectileFrameID( Point from, Point dest
 {
 {
 	const CCreature & creature = getShooter(stack);
 	const CCreature & creature = getShooter(stack);
 
 
-	auto & angles = creature.animation.missleFrameAngles;
+	auto & angles = creature.animation.missileFrameAngles;
 	auto animation = getProjectileImage(stack);
 	auto animation = getProjectileImage(stack);
 
 
 	// only frames below maxFrame are usable: anything  higher is either no present or we don't know when it should be used
 	// only frames below maxFrame are usable: anything  higher is either no present or we don't know when it should be used

+ 5 - 5
client/battle/BattleSiegeController.cpp

@@ -126,7 +126,7 @@ ImagePath BattleSiegeController::getBattleBackgroundName() const
 	return ImagePath::builtinTODO(prefix + "BACK.BMP");
 	return ImagePath::builtinTODO(prefix + "BACK.BMP");
 }
 }
 
 
-bool BattleSiegeController::getWallPieceExistance(EWallVisual::EWallVisual what) const
+bool BattleSiegeController::getWallPieceExistence(EWallVisual::EWallVisual what) const
 {
 {
 	//FIXME: use this instead of buildings test?
 	//FIXME: use this instead of buildings test?
 	//ui8 siegeLevel = owner.curInt->cb->battleGetSiegeLevel();
 	//ui8 siegeLevel = owner.curInt->cb->battleGetSiegeLevel();
@@ -179,7 +179,7 @@ BattleSiegeController::BattleSiegeController(BattleInterface & owner, const CGTo
 		if ( g == EWallVisual::GATE ) // gate is initially closed and has no image to display in this state
 		if ( g == EWallVisual::GATE ) // gate is initially closed and has no image to display in this state
 			continue;
 			continue;
 
 
-		if ( !getWallPieceExistance(EWallVisual::EWallVisual(g)) )
+		if ( !getWallPieceExistence(EWallVisual::EWallVisual(g)) )
 			continue;
 			continue;
 
 
 		wallPieceImages[g] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::REINFORCED));
 		wallPieceImages[g] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::REINFORCED));
@@ -256,10 +256,10 @@ void BattleSiegeController::gateStateChanged(const EGateState state)
 
 
 void BattleSiegeController::showAbsoluteObstacles(Canvas & canvas)
 void BattleSiegeController::showAbsoluteObstacles(Canvas & canvas)
 {
 {
-	if (getWallPieceExistance(EWallVisual::MOAT))
+	if (getWallPieceExistence(EWallVisual::MOAT))
 		showWallPiece(canvas, EWallVisual::MOAT);
 		showWallPiece(canvas, EWallVisual::MOAT);
 
 
-	if (getWallPieceExistance(EWallVisual::MOAT_BANK))
+	if (getWallPieceExistence(EWallVisual::MOAT_BANK))
 		showWallPiece(canvas, EWallVisual::MOAT_BANK);
 		showWallPiece(canvas, EWallVisual::MOAT_BANK);
 }
 }
 
 
@@ -292,7 +292,7 @@ void BattleSiegeController::collectRenderableObjects(BattleRenderer & renderer)
 	{
 	{
 		auto wallPiece = EWallVisual::EWallVisual(i);
 		auto wallPiece = EWallVisual::EWallVisual(i);
 
 
-		if ( !getWallPieceExistance(wallPiece))
+		if ( !getWallPieceExistence(wallPiece))
 			continue;
 			continue;
 
 
 		if ( getWallPiecePosition(wallPiece) == BattleHex::INVALID)
 		if ( getWallPiecePosition(wallPiece) == BattleHex::INVALID)

+ 1 - 1
client/battle/BattleSiegeController.h

@@ -83,7 +83,7 @@ class BattleSiegeController
 	BattleHex getWallPiecePosition(EWallVisual::EWallVisual what) const;
 	BattleHex getWallPiecePosition(EWallVisual::EWallVisual what) const;
 
 
 	/// returns true if chosen wall piece should be present in current battle
 	/// returns true if chosen wall piece should be present in current battle
-	bool getWallPieceExistance(EWallVisual::EWallVisual what) const;
+	bool getWallPieceExistence(EWallVisual::EWallVisual what) const;
 
 
 	void showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what);
 	void showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what);
 
 

+ 1 - 1
client/battle/BattleStacksController.cpp

@@ -438,7 +438,7 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
 		// defender need to face in direction opposited to out attacker
 		// defender need to face in direction opposited to out attacker
 		bool needsReverse = shouldAttackFacingRight(attackedInfo.attacker, attackedInfo.defender) == facingRight(attackedInfo.defender);
 		bool needsReverse = shouldAttackFacingRight(attackedInfo.attacker, attackedInfo.defender) == facingRight(attackedInfo.defender);
 
 
-		// FIXME: this check is better, however not usable since stacksAreAttacked is called after net pack is applyed - petrification is already removed
+		// FIXME: this check is better, however not usable since stacksAreAttacked is called after net pack is applied - petrification is already removed
 		// if (needsReverse && !attackedInfo.defender->isFrozen())
 		// if (needsReverse && !attackedInfo.defender->isFrozen())
 		if (needsReverse && stackAnimation[attackedInfo.defender->unitId()]->getType() != ECreatureAnimType::FROZEN)
 		if (needsReverse && stackAnimation[attackedInfo.defender->unitId()]->getType() != ECreatureAnimType::FROZEN)
 		{
 		{

+ 1 - 1
client/battle/CreatureAnimation.cpp

@@ -372,7 +372,7 @@ void CreatureAnimation::nextFrame(Canvas & canvas, const ColorFilter & shifter,
 		IImage::SpecialPalette SpecialPalette;
 		IImage::SpecialPalette SpecialPalette;
 		genSpecialPalette(SpecialPalette);
 		genSpecialPalette(SpecialPalette);
 
 
-		image->setSpecialPallete(SpecialPalette, IImage::SPECIAL_PALETTE_MASK_CREATURES);
+		image->setSpecialPalette(SpecialPalette, IImage::SPECIAL_PALETTE_MASK_CREATURES);
 		image->adjustPalette(shifter, IImage::SPECIAL_PALETTE_MASK_CREATURES);
 		image->adjustPalette(shifter, IImage::SPECIAL_PALETTE_MASK_CREATURES);
 
 
 		canvas.draw(image, pos.topLeft(), Rect(0, 0, pos.w, pos.h));
 		canvas.draw(image, pos.topLeft(), Rect(0, 0, pos.w, pos.h));

+ 2 - 2
client/eventsSDL/InputSourceGameController.cpp

@@ -39,7 +39,7 @@ InputSourceGameController::InputSourceGameController():
 	scrollAxisValueY(0),
 	scrollAxisValueY(0),
 	scrollPlanDisX(0.0),
 	scrollPlanDisX(0.0),
 	scrollPlanDisY(0.0),
 	scrollPlanDisY(0.0),
-	configTriggerTreshold(settings["input"]["controllerTriggerTreshold"].Float()),
+	configTriggerThreshold(settings["input"]["controllerTriggerThreshold"].Float()),
 	configAxisDeadZone(settings["input"]["controllerAxisDeadZone"].Float()),
 	configAxisDeadZone(settings["input"]["controllerAxisDeadZone"].Float()),
 	configAxisFullZone(settings["input"]["controllerAxisFullZone"].Float()),
 	configAxisFullZone(settings["input"]["controllerAxisFullZone"].Float()),
 	configAxisSpeed(settings["input"]["controllerAxisSpeed"].Float()),
 	configAxisSpeed(settings["input"]["controllerAxisSpeed"].Float()),
@@ -142,7 +142,7 @@ double InputSourceGameController::getRealAxisValue(int value) const
 
 
 void InputSourceGameController::dispatchAxisShortcuts(const std::vector<EShortcut> & shortcutsVector, SDL_GameControllerAxis axisID, int axisValue)
 void InputSourceGameController::dispatchAxisShortcuts(const std::vector<EShortcut> & shortcutsVector, SDL_GameControllerAxis axisID, int axisValue)
 {
 {
-	if(getRealAxisValue(axisValue) > configTriggerTreshold)
+	if(getRealAxisValue(axisValue) > configTriggerThreshold)
 	{
 	{
 		if(!pressedAxes.count(axisID))
 		if(!pressedAxes.count(axisID))
 		{
 		{

+ 1 - 1
client/eventsSDL/InputSourceGameController.h

@@ -39,7 +39,7 @@ class InputSourceGameController
 	double scrollPlanDisX;
 	double scrollPlanDisX;
 	double scrollPlanDisY;
 	double scrollPlanDisY;
 
 
-	const double configTriggerTreshold;
+	const double configTriggerThreshold;
 	const double configAxisDeadZone;
 	const double configAxisDeadZone;
 	const double configAxisFullZone;
 	const double configAxisFullZone;
 	const double configAxisSpeed;
 	const double configAxisSpeed;

+ 3 - 0
client/eventsSDL/InputSourceTouch.cpp

@@ -58,6 +58,9 @@ InputSourceTouch::InputSourceTouch()
 
 
 void InputSourceTouch::handleEventFingerMotion(const SDL_TouchFingerEvent & tfinger)
 void InputSourceTouch::handleEventFingerMotion(const SDL_TouchFingerEvent & tfinger)
 {
 {
+	if (CCS && CCS->curh && settings["video"]["cursor"].String() == "software" && state != TouchState::RELATIVE_MODE)
+		CCS->curh->cursorMove(GH.getCursorPosition().x, GH.getCursorPosition().y);
+
 	switch(state)
 	switch(state)
 	{
 	{
 		case TouchState::RELATIVE_MODE:
 		case TouchState::RELATIVE_MODE:

+ 1 - 1
client/globalLobby/GlobalLobbyLoginWindow.cpp

@@ -65,7 +65,7 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow()
 	{
 	{
 		buttonLogin->block(true);
 		buttonLogin->block(true);
 		toggleMode->setSelected(0);
 		toggleMode->setSelected(0);
-		onLoginModeChanged(0); // call it manually to disable widgets - toggleMode will not emit this call if this is currenly selected option
+		onLoginModeChanged(0); // call it manually to disable widgets - toggleMode will not emit this call if this is currently selected option
 	}
 	}
 	else
 	else
 		toggleMode->setSelected(1);
 		toggleMode->setSelected(1);

+ 1 - 1
client/gui/CIntObject.h

@@ -79,7 +79,7 @@ public:
 
 
 	/// deactivates if needed, blocks all automatic activity, allows only disposal
 	/// deactivates if needed, blocks all automatic activity, allows only disposal
 	void disable();
 	void disable();
-	/// activates if needed, all activity enabled (Warning: may not be symetric with disable if recActions was limited!)
+	/// activates if needed, all activity enabled (Warning: may not be symmetric with disable if recActions was limited!)
 	void enable();
 	void enable();
 	/// deactivates or activates UI element based on flag
 	/// deactivates or activates UI element based on flag
 	void setEnabled(bool on);
 	void setEnabled(bool on);

+ 1 - 1
client/gui/EventDispatcher.cpp

@@ -304,7 +304,7 @@ void EventDispatcher::dispatchTextInput(const std::string & text)
 {
 {
 	for(auto it : textInterested)
 	for(auto it : textInterested)
 	{
 	{
-		it->textInputed(text);
+		it->textInputted(text);
 	}
 	}
 }
 }
 
 

+ 1 - 1
client/gui/EventsReceiver.h

@@ -67,7 +67,7 @@ public:
 	/// Called when UI element gesture status changes
 	/// Called when UI element gesture status changes
 	virtual void gesture(bool on, const Point & initialPosition, const Point & finalPosition) {}
 	virtual void gesture(bool on, const Point & initialPosition, const Point & finalPosition) {}
 
 
-	virtual void textInputed(const std::string & enteredText) {}
+	virtual void textInputted(const std::string & enteredText) {}
 	virtual void textEdited(const std::string & enteredText) {}
 	virtual void textEdited(const std::string & enteredText) {}
 
 
 	virtual void keyPressed(EShortcut key) {}
 	virtual void keyPressed(EShortcut key) {}

+ 1 - 1
client/gui/FramerateManager.h

@@ -22,7 +22,7 @@ class FramerateManager
 	Duration targetFrameTime;
 	Duration targetFrameTime;
 	TimePoint lastTimePoint;
 	TimePoint lastTimePoint;
 
 
-	/// index of last measured frome in lastFrameTimes array
+	/// index of last measured from in lastFrameTimes array
 	ui32 lastFrameIndex;
 	ui32 lastFrameIndex;
 
 
 	bool vsyncEnabled;
 	bool vsyncEnabled;

+ 1 - 1
client/gui/InterfaceObjectConfigurable.h

@@ -41,7 +41,7 @@ public:
 	InterfaceObjectConfigurable(const JsonNode & config, int used=0, Point offset=Point());
 	InterfaceObjectConfigurable(const JsonNode & config, int used=0, Point offset=Point());
 
 
 protected:
 protected:
-	/// Set blocked status for all buttons assotiated with provided shortcut
+	/// Set blocked status for all buttons associated with provided shortcut
 	void setShortcutBlocked(EShortcut shortcut, bool isBlocked);
 	void setShortcutBlocked(EShortcut shortcut, bool isBlocked);
 
 
 	/// Registers provided callback to be called whenever specified shortcut is triggered
 	/// Registers provided callback to be called whenever specified shortcut is triggered

+ 1 - 1
client/lobby/CBonusSelection.cpp

@@ -214,7 +214,7 @@ void CBonusSelection::createBonusesIcons()
 			break;
 			break;
 		case CampaignBonusType::SPELL_SCROLL:
 		case CampaignBonusType::SPELL_SCROLL:
 			desc.appendLocalString(EMetaText::GENERAL_TXT, 716);
 			desc.appendLocalString(EMetaText::GENERAL_TXT, 716);
-			desc.replaceName(ArtifactID(bonDescs[i].info2));
+			desc.replaceName(SpellID(bonDescs[i].info2));
 			break;
 			break;
 		case CampaignBonusType::PRIMARY_SKILL:
 		case CampaignBonusType::PRIMARY_SKILL:
 		{
 		{

+ 1 - 1
client/lobby/CSelectionBase.cpp

@@ -136,7 +136,7 @@ InfoCard::InfoCard()
 
 
 	labelSaveDate = std::make_shared<CLabel>(310, 38, FONT_SMALL, ETextAlignment::BOTTOMRIGHT, Colors::WHITE);
 	labelSaveDate = std::make_shared<CLabel>(310, 38, FONT_SMALL, ETextAlignment::BOTTOMRIGHT, Colors::WHITE);
 	labelMapSize = std::make_shared<CLabel>(333, 56, FONT_TINY, ETextAlignment::CENTER, Colors::WHITE);
 	labelMapSize = std::make_shared<CLabel>(333, 56, FONT_TINY, ETextAlignment::CENTER, Colors::WHITE);
-	mapName = std::make_shared<CLabel>(26, 39, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW);
+	mapName = std::make_shared<CLabel>(26, 39, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, "", 285);
 	Rect descriptionRect(26, 149, 320, 115);
 	Rect descriptionRect(26, 149, 320, 115);
 	mapDescription = std::make_shared<CTextBox>("", descriptionRect, 1);
 	mapDescription = std::make_shared<CTextBox>("", descriptionRect, 1);
 	playerListBg = std::make_shared<CPicture>(ImagePath::builtin("CHATPLUG.bmp"), 16, 276);
 	playerListBg = std::make_shared<CPicture>(ImagePath::builtin("CHATPLUG.bmp"), 16, 276);

+ 80 - 57
client/lobby/OptionsTab.cpp

@@ -402,7 +402,7 @@ void OptionsTab::CPlayerOptionTooltipBox::genBonusWindow()
 	textBonusDescription = std::make_shared<CTextBox>(getDescription(), Rect(10, 100, pos.w - 20, 70), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
 	textBonusDescription = std::make_shared<CTextBox>(getDescription(), Rect(10, 100, pos.w - 20, 70), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
 }
 }
 
 
-OptionsTab::SelectionWindow::SelectionWindow(const PlayerColor & color, SelType _type)
+OptionsTab::SelectionWindow::SelectionWindow(const PlayerColor & color, SelType _type, int sliderPos)
 	: CWindowObject(BORDERED), color(color)
 	: CWindowObject(BORDERED), color(color)
 {
 {
 	addUsedEvents(LCLICK | SHOW_POPUP);
 	addUsedEvents(LCLICK | SHOW_POPUP);
@@ -434,15 +434,18 @@ OptionsTab::SelectionWindow::SelectionWindow(const PlayerColor & color, SelType
 	if(initialFaction.isValid())
 	if(initialFaction.isValid())
 		allowedBonus.push_back(PlayerStartingBonus::RESOURCE);
 		allowedBonus.push_back(PlayerStartingBonus::RESOURCE);
 
 
-	recreate();
+	recreate(sliderPos);
 }
 }
 
 
-int OptionsTab::SelectionWindow::calcLines(FactionID faction)
+std::tuple<int, int> OptionsTab::SelectionWindow::calcLines(FactionID faction)
 {
 {
-	double additionalItems = 1; // random
+	int additionalItems = 1; // random
 
 
 	if(!faction.isValid())
 	if(!faction.isValid())
-		return std::ceil(((double)allowedFactions.size() + additionalItems) / elementsPerLine);
+		return std::make_tuple(
+			std::ceil(((double)allowedFactions.size() + additionalItems) / MAX_ELEM_PER_LINES),
+			(allowedFactions.size() + additionalItems) % MAX_ELEM_PER_LINES
+		);
 
 
 	int count = 0;
 	int count = 0;
 	for(auto & elemh : allowedHeroes)
 	for(auto & elemh : allowedHeroes)
@@ -452,7 +455,10 @@ int OptionsTab::SelectionWindow::calcLines(FactionID faction)
 			count++;
 			count++;
 	}
 	}
 
 
-	return std::ceil(std::max((double)count + additionalItems, (double)allowedFactions.size() + additionalItems) / (double)elementsPerLine);
+	return std::make_tuple(
+		std::ceil(((double)count + additionalItems) / MAX_ELEM_PER_LINES),
+		(count + additionalItems) % MAX_ELEM_PER_LINES
+	);
 }
 }
 
 
 void OptionsTab::SelectionWindow::apply()
 void OptionsTab::SelectionWindow::apply()
@@ -482,13 +488,17 @@ void OptionsTab::SelectionWindow::setSelection()
 
 
 void OptionsTab::SelectionWindow::reopen()
 void OptionsTab::SelectionWindow::reopen()
 {
 {
-	auto window = std::shared_ptr<SelectionWindow>(new SelectionWindow(color, type));
-	close();
-	if(CSH->isMyColor(color) || CSH->isHost())
-		GH.windows().pushWindow(window);
+	if(type == SelType::HERO && SEL->getStartInfo()->playerInfos.find(color)->second.castle == FactionID::RANDOM)
+		close();
+	else{
+		auto window = std::shared_ptr<SelectionWindow>(new SelectionWindow(color, type, slider ? slider->getValue() : 0));
+		close();
+		if(CSH->isMyColor(color) || CSH->isHost())
+			GH.windows().pushWindow(window);
+	}
 }
 }
 
 
-void OptionsTab::SelectionWindow::recreate()
+void OptionsTab::SelectionWindow::recreate(int sliderPos)
 {
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 
 
@@ -497,32 +507,19 @@ void OptionsTab::SelectionWindow::recreate()
 		elementsPerLine = allowedBonus.size();
 		elementsPerLine = allowedBonus.size();
 	else
 	else
 	{
 	{
-		// try to make squarish
-		if(type == SelType::TOWN)
-			elementsPerLine = floor(sqrt(allowedFactions.size()));
-		if(type == SelType::HERO)
-		{
-			int count = 0;
-			for(auto & elem : allowedHeroes)
-			{
-				const CHero * type = elem.toHeroType();
-				if(type->heroClass->faction == selectedFaction)
-				{
-					count++;
-				}
-			}
-			elementsPerLine = floor(sqrt(count));
-		}
-
-		amountLines = calcLines((type > SelType::TOWN) ? selectedFaction : FactionID::RANDOM);
+		std::tie(amountLines, elementsPerLine) = calcLines((type > SelType::TOWN) ? selectedFaction : FactionID::RANDOM);
+		if(amountLines > 1 || elementsPerLine == 0)
+			elementsPerLine = MAX_ELEM_PER_LINES;
 	}
 	}
 
 
 	int x = (elementsPerLine) * (ICON_BIG_WIDTH-1);
 	int x = (elementsPerLine) * (ICON_BIG_WIDTH-1);
-	int y = (amountLines) * (ICON_BIG_HEIGHT-1);
+	int y = (std::min(amountLines, MAX_LINES)) * (ICON_BIG_HEIGHT-1);
+
+	int sliderWidth = ((amountLines > MAX_LINES) ? 16 : 0);
 
 
-	pos = Rect(0, 0, x, y);
+	pos = Rect(pos.x, pos.y, x + sliderWidth, y);
 
 
-	backgroundTexture = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), pos);
+	backgroundTexture = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w - sliderWidth, pos.h));
 	backgroundTexture->playerColored(PlayerColor(1));
 	backgroundTexture->playerColored(PlayerColor(1));
 	updateShadow();
 	updateShadow();
 
 
@@ -532,7 +529,15 @@ void OptionsTab::SelectionWindow::recreate()
 		genContentHeroes();
 		genContentHeroes();
 	if(type == SelType::BONUS)
 	if(type == SelType::BONUS)
 		genContentBonus();
 		genContentBonus();
-	genContentGrid(amountLines);
+	genContentGrid(std::min(amountLines, MAX_LINES));
+
+	if(!slider && amountLines > MAX_LINES)
+	{
+		slider = std::make_shared<CSlider>(Point(x, 0), y, std::bind(&OptionsTab::SelectionWindow::sliderMove, this, _1), MAX_LINES, amountLines, 0, Orientation::VERTICAL, CSlider::BLUE);
+		slider->setPanningStep(ICON_BIG_HEIGHT);
+		slider->setScrollBounds(Rect(-pos.w + slider->pos.w, 0, x + slider->pos.w, y));
+		slider->scrollTo(sliderPos);
+	}
 
 
 	center();
 	center();
 }
 }
@@ -570,22 +575,26 @@ void OptionsTab::SelectionWindow::genContentFactions()
 	if(selectedFaction == FactionID::RANDOM)
 	if(selectedFaction == FactionID::RANDOM)
 		components.push_back(std::make_shared<CPicture>(ImagePath::builtin("lobby/townBorderSmallActivated"), 6, (ICON_SMALL_HEIGHT/2)));
 		components.push_back(std::make_shared<CPicture>(ImagePath::builtin("lobby/townBorderSmallActivated"), 6, (ICON_SMALL_HEIGHT/2)));
 
 
+	factions.clear();
 	for(auto & elem : allowedFactions)
 	for(auto & elem : allowedFactions)
 	{
 	{
 		int x = i % elementsPerLine;
 		int x = i % elementsPerLine;
-		int y = i / elementsPerLine;
+		int y = (i / elementsPerLine) - (slider ? slider->getValue() : 0);
 
 
 		PlayerSettings set = PlayerSettings();
 		PlayerSettings set = PlayerSettings();
 		set.castle = elem;
 		set.castle = elem;
 
 
 		CPlayerSettingsHelper helper = CPlayerSettingsHelper(set, SelType::TOWN);
 		CPlayerSettingsHelper helper = CPlayerSettingsHelper(set, SelType::TOWN);
 
 
+		factions.push_back(elem);
+		i++;
+
+		if(y < 0 || y > MAX_LINES - 1)
+			continue;
+			
 		components.push_back(std::make_shared<CAnimImage>(helper.getImageName(true), helper.getImageIndex(true), 0, x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
 		components.push_back(std::make_shared<CAnimImage>(helper.getImageName(true), helper.getImageIndex(true), 0, x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
 		components.push_back(std::make_shared<CPicture>(ImagePath::builtin(selectedFaction == elem ? "lobby/townBorderBigActivated" : "lobby/townBorderBig"), x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
 		components.push_back(std::make_shared<CPicture>(ImagePath::builtin(selectedFaction == elem ? "lobby/townBorderBigActivated" : "lobby/townBorderBig"), x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
 		drawOutlinedText(x * (ICON_BIG_WIDTH-1) + TEXT_POS_X, y * (ICON_BIG_HEIGHT-1) + TEXT_POS_Y, (selectedFaction == elem) ? Colors::YELLOW : Colors::WHITE, helper.getName());
 		drawOutlinedText(x * (ICON_BIG_WIDTH-1) + TEXT_POS_X, y * (ICON_BIG_HEIGHT-1) + TEXT_POS_Y, (selectedFaction == elem) ? Colors::YELLOW : Colors::WHITE, helper.getName());
-		factions.push_back(elem);
-
-		i++;
 	}
 	}
 }
 }
 
 
@@ -602,33 +611,36 @@ void OptionsTab::SelectionWindow::genContentHeroes()
 	if(selectedHero == HeroTypeID::RANDOM)
 	if(selectedHero == HeroTypeID::RANDOM)
 		components.push_back(std::make_shared<CPicture>(ImagePath::builtin("lobby/townBorderSmallActivated"), 6, (ICON_SMALL_HEIGHT/2)));
 		components.push_back(std::make_shared<CPicture>(ImagePath::builtin("lobby/townBorderSmallActivated"), 6, (ICON_SMALL_HEIGHT/2)));
 
 
+	heroes.clear();
 	for(auto & elem : allowedHeroes)
 	for(auto & elem : allowedHeroes)
 	{
 	{
 		const CHero * type = elem.toHeroType();
 		const CHero * type = elem.toHeroType();
 
 
-		if(type->heroClass->faction == selectedFaction)
-		{
+		if(type->heroClass->faction != selectedFaction)
+			continue;
 
 
-			int x = i % elementsPerLine;
-			int y = i / elementsPerLine;
+		int x = i % elementsPerLine;
+		int y = (i / elementsPerLine) - (slider ? slider->getValue() : 0);
 
 
-			PlayerSettings set = PlayerSettings();
-			set.hero = elem;
+		PlayerSettings set = PlayerSettings();
+		set.hero = elem;
+
+		CPlayerSettingsHelper helper = CPlayerSettingsHelper(set, SelType::HERO);
 
 
-			CPlayerSettingsHelper helper = CPlayerSettingsHelper(set, SelType::HERO);
+		heroes.push_back(elem);
+		i++;
 
 
-			components.push_back(std::make_shared<CAnimImage>(helper.getImageName(true), helper.getImageIndex(true), 0, x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
-			drawOutlinedText(x * (ICON_BIG_WIDTH-1) + TEXT_POS_X, y * (ICON_BIG_HEIGHT-1) + TEXT_POS_Y, (selectedHero == elem) ? Colors::YELLOW : Colors::WHITE, helper.getName());
-			ImagePath image = ImagePath::builtin("lobby/townBorderBig");
-			if(selectedHero == elem)
-				image = ImagePath::builtin("lobby/townBorderBigActivated");
-			if(unusableHeroes.count(elem))
-				image = ImagePath::builtin("lobby/townBorderBigGrayedOut");
-			components.push_back(std::make_shared<CPicture>(image, x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
-			heroes.push_back(elem);
+		if(y < 0 || y > MAX_LINES - 1)
+			continue;
 
 
-			i++;
-		}
+		components.push_back(std::make_shared<CAnimImage>(helper.getImageName(true), helper.getImageIndex(true), 0, x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
+		drawOutlinedText(x * (ICON_BIG_WIDTH-1) + TEXT_POS_X, y * (ICON_BIG_HEIGHT-1) + TEXT_POS_Y, (selectedHero == elem) ? Colors::YELLOW : Colors::WHITE, helper.getName());
+		ImagePath image = ImagePath::builtin("lobby/townBorderBig");
+		if(selectedHero == elem)
+			image = ImagePath::builtin("lobby/townBorderBigActivated");
+		if(unusableHeroes.count(elem))
+			image = ImagePath::builtin("lobby/townBorderBigGrayedOut");
+		components.push_back(std::make_shared<CPicture>(image, x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
 	}
 	}
 }
 }
 
 
@@ -659,7 +671,7 @@ void OptionsTab::SelectionWindow::genContentBonus()
 int OptionsTab::SelectionWindow::getElement(const Point & cursorPosition)
 int OptionsTab::SelectionWindow::getElement(const Point & cursorPosition)
 {
 {
 	int x = (cursorPosition.x - pos.x) / (ICON_BIG_WIDTH-1);
 	int x = (cursorPosition.x - pos.x) / (ICON_BIG_WIDTH-1);
-	int y = (cursorPosition.y - pos.y) / (ICON_BIG_HEIGHT-1);
+	int y = (cursorPosition.y - pos.y) / (ICON_BIG_HEIGHT-1) + (slider ? slider->getValue() : 0);
 
 
 	return x + y * elementsPerLine;
 	return x + y * elementsPerLine;
 }
 }
@@ -741,6 +753,14 @@ void OptionsTab::SelectionWindow::setElement(int elem, bool doApply)
 		apply();
 		apply();
 }
 }
 
 
+void OptionsTab::SelectionWindow::sliderMove(int slidPos)
+{
+	if(!slider)
+		return; // ignore spurious call when slider is being created
+	recreate();
+	redraw();
+}
+
 bool OptionsTab::SelectionWindow::receiveEvent(const Point & position, int eventType) const
 bool OptionsTab::SelectionWindow::receiveEvent(const Point & position, int eventType) const
 {
 {
 	return true;  // capture click also outside of window
 	return true;  // capture click also outside of window
@@ -748,6 +768,9 @@ bool OptionsTab::SelectionWindow::receiveEvent(const Point & position, int event
 
 
 void OptionsTab::SelectionWindow::clickReleased(const Point & cursorPosition)
 void OptionsTab::SelectionWindow::clickReleased(const Point & cursorPosition)
 {
 {
+	if(slider && slider->pos.isInside(cursorPosition))
+		return;
+
 	if(!pos.isInside(cursorPosition))
 	if(!pos.isInside(cursorPosition))
 	{
 	{
 		close();
 		close();
@@ -761,7 +784,7 @@ void OptionsTab::SelectionWindow::clickReleased(const Point & cursorPosition)
 
 
 void OptionsTab::SelectionWindow::showPopupWindow(const Point & cursorPosition)
 void OptionsTab::SelectionWindow::showPopupWindow(const Point & cursorPosition)
 {
 {
-	if(!pos.isInside(cursorPosition))
+	if(!pos.isInside(cursorPosition) || (slider && slider->pos.isInside(cursorPosition)))
 		return;
 		return;
 
 
 	int elem = getElement(cursorPosition);
 	int elem = getElement(cursorPosition);

+ 11 - 3
client/lobby/OptionsTab.h

@@ -26,6 +26,7 @@ class CAnimImage;
 class CComponentBox;
 class CComponentBox;
 class CTextBox;
 class CTextBox;
 class CButton;
 class CButton;
+class CSlider;
 
 
 class FilledTexturePlayerColored;
 class FilledTexturePlayerColored;
 
 
@@ -105,8 +106,13 @@ private:
 		const int TEXT_POS_X = 29;
 		const int TEXT_POS_X = 29;
 		const int TEXT_POS_Y = 56;
 		const int TEXT_POS_Y = 56;
 
 
+		const int MAX_LINES = 5;
+		const int MAX_ELEM_PER_LINES = 5;
+
 		int elementsPerLine;
 		int elementsPerLine;
 
 
+		std::shared_ptr<CSlider> slider;
+
 		PlayerColor color;
 		PlayerColor color;
 		SelType type;
 		SelType type;
 
 
@@ -134,13 +140,15 @@ private:
 		void genContentBonus();
 		void genContentBonus();
 
 
 		void drawOutlinedText(int x, int y, ColorRGBA color, std::string text);
 		void drawOutlinedText(int x, int y, ColorRGBA color, std::string text);
-		int calcLines(FactionID faction);
+		std::tuple<int, int> calcLines(FactionID faction);
 		void apply();
 		void apply();
-		void recreate();
+		void recreate(int sliderPos = 0);
 		void setSelection();
 		void setSelection();
 		int getElement(const Point & cursorPosition);
 		int getElement(const Point & cursorPosition);
 		void setElement(int element, bool doApply);
 		void setElement(int element, bool doApply);
 
 
+		void sliderMove(int slidPos);
+
 		bool receiveEvent(const Point & position, int eventType) const override;
 		bool receiveEvent(const Point & position, int eventType) const override;
 		void clickReleased(const Point & cursorPosition) override;
 		void clickReleased(const Point & cursorPosition) override;
 		void showPopupWindow(const Point & cursorPosition) override;
 		void showPopupWindow(const Point & cursorPosition) override;
@@ -148,7 +156,7 @@ private:
 	public:
 	public:
 		void reopen();
 		void reopen();
 
 
-		SelectionWindow(const PlayerColor & color, SelType _type);
+		SelectionWindow(const PlayerColor & color, SelType _type, int sliderPos = 0);
 	};
 	};
 
 
 	/// Image with current town/hero/bonus
 	/// Image with current town/hero/bonus

Някои файлове не бяха показани, защото твърде много файлове са промени