Browse Source

Merge branch 'develop' into sod-fly

Dydzio 1 year ago
parent
commit
365fedc7e8
100 changed files with 1451 additions and 660 deletions
  1. 11 0
      .github/dependabot.yml
  2. 44 95
      .github/workflows/github.yml
  3. 1 0
      .gitignore
  4. 6 3
      AI/BattleAI/AttackPossibility.cpp
  5. 2 1
      AI/BattleAI/BattleAI.cpp
  6. 7 4
      AI/BattleAI/BattleEvaluator.cpp
  7. 2 2
      AI/BattleAI/StackWithBonuses.cpp
  8. 2 2
      AI/EmptyAI/CEmptyAI.cpp
  9. 2 2
      AI/EmptyAI/CEmptyAI.h
  10. 31 28
      AI/Nullkiller/AIGateway.cpp
  11. 4 4
      AI/Nullkiller/AIGateway.h
  12. 1 3
      AI/Nullkiller/AIUtility.cpp
  13. 5 4
      AI/Nullkiller/AIUtility.h
  14. 9 9
      AI/Nullkiller/Analyzers/ArmyManager.cpp
  15. 5 5
      AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp
  16. 3 1
      AI/Nullkiller/Engine/FuzzyEngines.cpp
  17. 8 2
      AI/Nullkiller/Engine/FuzzyEngines.h
  18. 1 1
      AI/Nullkiller/Engine/FuzzyHelper.cpp
  19. 4 0
      AI/Nullkiller/Engine/Nullkiller.cpp
  20. 1 0
      AI/Nullkiller/Engine/Nullkiller.h
  21. 21 4
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  22. 2 2
      AI/Nullkiller/Goals/BuyArmy.cpp
  23. 1 1
      AI/Nullkiller/Goals/CGoal.h
  24. 2 2
      AI/Nullkiller/Goals/CompleteQuest.cpp
  25. 21 4
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  26. 11 2
      AI/Nullkiller/Pathfinding/AINodeStorage.h
  27. 8 7
      AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.cpp
  28. 2 1
      AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h
  29. 6 6
      AI/Nullkiller/Pathfinding/Actors.cpp
  30. 1 1
      AI/Nullkiller/Pathfinding/Actors.h
  31. 12 0
      AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp
  32. 15 13
      AI/StupidAI/StupidAI.cpp
  33. 2 2
      AI/VCAI/AIUtility.h
  34. 19 20
      AI/VCAI/BuildingManager.cpp
  35. 3 1
      AI/VCAI/FuzzyEngines.cpp
  36. 8 2
      AI/VCAI/FuzzyEngines.h
  37. 1 1
      AI/VCAI/FuzzyHelper.cpp
  38. 1 1
      AI/VCAI/Goals/AbstractGoal.h
  39. 1 1
      AI/VCAI/Goals/CGoal.h
  40. 2 1
      AI/VCAI/Goals/CollectRes.cpp
  41. 2 2
      AI/VCAI/Goals/CompleteQuest.cpp
  42. 1 3
      AI/VCAI/Pathfinding/AINodeStorage.cpp
  43. 2 2
      AI/VCAI/ResourceManager.h
  44. 16 26
      AI/VCAI/VCAI.cpp
  45. 5 5
      AI/VCAI/VCAI.h
  46. 3 2
      CCallback.cpp
  47. 2 2
      CCallback.h
  48. 45 32
      CMakeLists.txt
  49. 47 0
      CMakePresets.json
  50. 84 1
      ChangeLog.md
  51. 4 0
      Global.h
  52. 17 7
      Mods/vcmi/config/vcmi/chinese.json
  53. 25 9
      Mods/vcmi/config/vcmi/english.json
  54. 21 7
      Mods/vcmi/config/vcmi/german.json
  55. 86 70
      Mods/vcmi/config/vcmi/polish.json
  56. 223 48
      Mods/vcmi/config/vcmi/spanish.json
  57. 7 0
      Mods/vcmi/config/vcmi/ukrainian.json
  58. 2 2
      android/vcmi-app/build.gradle
  59. 71 0
      android/vcmi-app/src/main/res/values-es/strings.xml
  60. 1 4
      client/CGameInfo.cpp
  61. 16 15
      client/CGameInfo.h
  62. 18 18
      client/CMT.cpp
  63. 10 4
      client/CMakeLists.txt
  64. 29 0
      client/CMusicHandler.cpp
  65. 1 0
      client/CMusicHandler.h
  66. 46 16
      client/CPlayerInterface.cpp
  67. 3 2
      client/CPlayerInterface.h
  68. 22 4
      client/CServerHandler.cpp
  69. 8 1
      client/CServerHandler.h
  70. 31 7
      client/CVideoHandler.cpp
  71. 9 3
      client/CVideoHandler.h
  72. 38 31
      client/Client.cpp
  73. 2 2
      client/Client.h
  74. 7 5
      client/ClientCommandManager.cpp
  75. 21 9
      client/NetPacksClient.cpp
  76. 4 0
      client/NetPacksLobbyClient.cpp
  77. 2 2
      client/PlayerLocalState.h
  78. 1 1
      client/adventureMap/AdventureMapInterface.cpp
  79. 10 6
      client/adventureMap/AdventureOptions.cpp
  80. 1 1
      client/adventureMap/AdventureOptions.h
  81. 2 1
      client/adventureMap/CInfoBar.cpp
  82. 1 1
      client/adventureMap/CList.cpp
  83. 6 5
      client/battle/BattleActionsController.cpp
  84. 4 4
      client/battle/BattleInterface.cpp
  85. 3 5
      client/battle/BattleInterfaceClasses.cpp
  86. 1 1
      client/battle/BattleInterfaceClasses.h
  87. 5 2
      client/battle/BattleStacksController.cpp
  88. 94 19
      client/battle/BattleWindow.cpp
  89. 8 0
      client/battle/BattleWindow.h
  90. 14 29
      client/eventsSDL/InputSourceText.cpp
  91. 1 1
      client/gui/InterfaceObjectConfigurable.cpp
  92. 1 0
      client/gui/Shortcut.h
  93. 2 0
      client/gui/ShortcutHandler.cpp
  94. 4 0
      client/lobby/CBonusSelection.cpp
  95. 1 0
      client/lobby/CBonusSelection.h
  96. 33 4
      client/lobby/CLobbyScreen.cpp
  97. 1 1
      client/lobby/CSelectionBase.cpp
  98. 3 0
      client/lobby/CSelectionBase.h
  99. 18 0
      client/lobby/ExtraOptionsTab.cpp
  100. 18 0
      client/lobby/ExtraOptionsTab.h

+ 11 - 0
.github/dependabot.yml

@@ -0,0 +1,11 @@
+# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot#example-dependabotyml-file-for-github-actions
+# Set update schedule for GitHub Actions
+
+version: 2
+updates:
+
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      # Check for updates to GitHub Actions every day
+      interval: "daily"

+ 44 - 95
.github/workflows/github.yml

@@ -6,9 +6,8 @@ on:
       - features/*
       - features/*
       - beta
       - beta
       - master
       - master
+      - develop
   pull_request:
   pull_request:
-  schedule:
-    - cron: '0 2 * * *'
   workflow_dispatch:
   workflow_dispatch:
 
 
 env:
 env:
@@ -16,55 +15,7 @@ env:
   BUILD_TYPE: Release
   BUILD_TYPE: Release
 
 
 jobs:
 jobs:
-  check_last_build:
-    if: github.event.schedule != ''
-    runs-on: ubuntu-latest
-    outputs:
-      skip_build: ${{ steps.check_if_built.outputs.skip_build }}
-    defaults:
-      run:
-        shell: bash
-    steps:
-      - name: Get repo name
-        id: get_repo_name
-        run: echo "::set-output name=value::${GITHUB_REPOSITORY#*/}"
-      - name: Get last successful build for ${{ github.sha }}
-        uses: octokit/[email protected]
-        id: get_last_scheduled_run
-        with:
-          route: GET /repos/{owner}/{repo}/actions/runs
-          owner: ${{ github.repository_owner }}
-          repo: ${{ steps.get_repo_name.outputs.value }}
-          status: success
-          per_page: 1
-          head_sha: ${{ github.sha }}
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-      - name: Check if successful build of the current commit exists
-        id: check_if_built
-        run: |
-          if [ ${{ fromJson(steps.get_last_scheduled_run.outputs.data).total_count }} -gt 0 ]; then
-            echo '::set-output name=skip_build::1'
-          else
-            echo '::set-output name=skip_build::0'
-          fi
-      - name: Cancel current run
-        if: steps.check_if_built.outputs.skip_build == 1
-        uses: octokit/[email protected]
-        with:
-          route: POST /repos/{owner}/{repo}/actions/runs/{run_id}/cancel
-          owner: ${{ github.repository_owner }}
-          repo: ${{ steps.get_repo_name.outputs.value }}
-          run_id: ${{ github.run_id }}
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-      - name: Wait for the run to be cancelled
-        if: steps.check_if_built.outputs.skip_build == 1
-        run: sleep 60
-
   build:
   build:
-    needs: check_last_build
-    if: always() && needs.check_last_build.skip_build != 1
     strategy:
     strategy:
       matrix:
       matrix:
         include:
         include:
@@ -73,8 +24,8 @@ jobs:
             test: 0
             test: 0
             preset: linux-clang-test
             preset: linux-clang-test
           - platform: linux
           - platform: linux
-            os: ubuntu-20.04
-            test: 0
+            os: ubuntu-22.04
+            test: 1
             preset: linux-gcc-test
             preset: linux-gcc-test
           - platform: linux
           - platform: linux
             os: ubuntu-20.04
             os: ubuntu-20.04
@@ -156,7 +107,7 @@ jobs:
         shell: bash
         shell: bash
 
 
     steps:
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
       with:
         submodules: recursive
         submodules: recursive
 
 
@@ -195,9 +146,9 @@ jobs:
         max-size: "5G"
         max-size: "5G"
         verbose: 2
         verbose: 2
 
 
-    - name: Ccache for everything but PRs
+    - name: Ccache for vcmi/vcmi's develop branch
       uses: hendrikmuhs/[email protected]
       uses: hendrikmuhs/[email protected]
-      if: ${{ github.event.number == '' }}
+      if: ${{ github.event.number == '' && github.ref == 'refs/heads/develop' }}
       with:
       with:
         key: ${{ matrix.preset }}-no-PR
         key: ${{ matrix.preset }}-no-PR
         restore-keys: |
         restore-keys: |
@@ -206,7 +157,17 @@ jobs:
         max-size: "5G"
         max-size: "5G"
         verbose: 2
         verbose: 2
 
 
-    - uses: actions/setup-python@v4
+    - name: Prepare Heroes 3 data
+      env:
+        HEROES_3_DATA_PASSWORD: ${{ secrets.HEROES_3_DATA_PASSWORD }}
+      if: ${{ env.HEROES_3_DATA_PASSWORD != '' && matrix.test == 1 }}
+      run: |
+        wget --progress=dot:giga https://github.com/vcmi-mods/vcmi-test-data/releases/download/v1.0/h3_assets.zip
+        7za x h3_assets.zip -p$HEROES_3_DATA_PASSWORD
+        mkdir -p ~/.local/share/vcmi/
+        mv h3_assets/* ~/.local/share/vcmi/
+
+    - uses: actions/setup-python@v5
       if: "${{ matrix.conan_profile != '' }}"
       if: "${{ matrix.conan_profile != '' }}"
       with:
       with:
         python-version: '3.10'
         python-version: '3.10'
@@ -226,10 +187,6 @@ jobs:
       env:
       env:
         GENERATE_ONLY_BUILT_CONFIG: 1
         GENERATE_ONLY_BUILT_CONFIG: 1
 
 
-    - name: Git branch name
-      id: git-branch-name
-      uses: EthanSK/git-branch-name-action@v1
-
     - name: Build Number
     - name: Build Number
       run: |
       run: |
         source '${{github.workspace}}/CI/get_package_name.sh'
         source '${{github.workspace}}/CI/get_package_name.sh'
@@ -242,31 +199,42 @@ jobs:
       env:
       env:
         PULL_REQUEST: ${{ github.event.pull_request.number }}
         PULL_REQUEST: ${{ github.event.pull_request.number }}
 
 
-    - name: CMake Preset with ccache
+    - name: Configure
       run: |
       run: |
-        cmake -DCMAKE_CXX_COMPILER_LAUNCHER=ccache --preset ${{ matrix.preset }}
+        if [[ ${{matrix.preset}} == linux-gcc-test ]]; then GCC13=1; fi
+        cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }} ${GCC13:+-DCMAKE_C_COMPILER=gcc-13 -DCMAKE_CXX_COMPILER=g++-13}
 
 
-    - name: Build Preset
+    - name: Build
       run: |
       run: |
         cmake --build --preset ${{matrix.preset}}
         cmake --build --preset ${{matrix.preset}}
 
 
     - name: Test
     - name: Test
-      if: ${{ matrix.test == 1 }}
+      env:
+        HEROES_3_DATA_PASSWORD: ${{ secrets.HEROES_3_DATA_PASSWORD }}
+      if: ${{ env.HEROES_3_DATA_PASSWORD != '' && matrix.test == 1 }}
       run: |
       run: |
         ctest --preset ${{matrix.preset}}
         ctest --preset ${{matrix.preset}}
 
 
+    - name: Kill XProtect to work around CPack issue on macOS 
+      if: ${{ startsWith(matrix.platform, 'mac') }}
+      run: |
+        # Cf. https://github.com/actions/runner-images/issues/7522#issuecomment-1556766641
+        echo Killing...; sudo pkill -9 XProtect >/dev/null || true;
+        echo "Waiting..."; counter=0; while pgrep XProtect && ((counter < 20)); do sleep 3; ((counter++)); done
+        pgrep XProtect || true
+
     - name: Pack
     - name: Pack
       id: cpack
       id: cpack
       if: ${{ matrix.pack == 1 }}
       if: ${{ matrix.pack == 1 }}
       run: |
       run: |
         cd '${{github.workspace}}/out/build/${{matrix.preset}}'
         cd '${{github.workspace}}/out/build/${{matrix.preset}}'
         CPACK_PATH=`which -a cpack | grep -m1 -v -i chocolatey`
         CPACK_PATH=`which -a cpack | grep -m1 -v -i chocolatey`
-        "$CPACK_PATH" -C ${{matrix.pack_type}} ${{ matrix.cpack_args }}
+        counter=0; until "$CPACK_PATH" -C ${{matrix.pack_type}} ${{ matrix.cpack_args }} || ((counter > 20)); do sleep 3; ((counter++)); done
         test -f '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' \
         test -f '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' \
           && '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' '${{github.workspace}}' "$(ls '${{ env.VCMI_PACKAGE_FILE_NAME }}'.*)"
           && '${{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: Create android package
+    - name: Create Android package
       if: ${{ startsWith(matrix.platform, 'android') }}
       if: ${{ startsWith(matrix.platform, 'android') }}
       run: |
       run: |
         cd android
         cd android
@@ -281,7 +249,7 @@ jobs:
 
 
     - name: Artifacts
     - name: Artifacts
       if: ${{ matrix.pack == 1 }}
       if: ${{ matrix.pack == 1 }}
-      uses: actions/upload-artifact@v3
+      uses: actions/upload-artifact@v4
       with:
       with:
         name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
         name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
         path: |
         path: |
@@ -289,7 +257,7 @@ jobs:
           
           
     - name: Android artifacts
     - name: Android artifacts
       if: ${{ startsWith(matrix.platform, 'android') }}
       if: ${{ startsWith(matrix.platform, 'android') }}
-      uses: actions/upload-artifact@v3
+      uses: actions/upload-artifact@v4
       with:
       with:
         name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
         name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
         path: |
         path: |
@@ -297,7 +265,7 @@ jobs:
           
           
     - name: Symbols
     - name: Symbols
       if: ${{ matrix.platform == 'msvc' }}
       if: ${{ matrix.platform == 'msvc' }}
-      uses: actions/upload-artifact@v3
+      uses: actions/upload-artifact@v4
       with:
       with:
         name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} - symbols
         name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} - symbols
         path: |
         path: |
@@ -305,7 +273,7 @@ jobs:
 
 
     - name: Android JNI ${{matrix.platform}}
     - name: Android JNI ${{matrix.platform}}
       if: ${{ startsWith(matrix.platform, 'android') && github.ref == 'refs/heads/master' }}
       if: ${{ startsWith(matrix.platform, 'android') && github.ref == 'refs/heads/master' }}
-      uses: actions/upload-artifact@v3
+      uses: actions/upload-artifact@v4
       with:
       with:
         name: Android JNI ${{matrix.platform}}
         name: Android JNI ${{matrix.platform}}
         path: |
         path: |
@@ -324,14 +292,6 @@ jobs:
       env:
       env:
         DEPLOY_RSA: ${{ secrets.DEPLOY_RSA }}
         DEPLOY_RSA: ${{ secrets.DEPLOY_RSA }}
         PACKAGE_EXTENSION: ${{ matrix.extension }}
         PACKAGE_EXTENSION: ${{ matrix.extension }}
-
-    - uses: act10ns/slack@v1
-      with:
-        status: ${{ job.status }}
-        channel: '#notifications'
-      env:
-        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
-      if: always()
   
   
   # copy-pasted mostly
   # copy-pasted mostly
   bundle_release:
   bundle_release:
@@ -354,7 +314,7 @@ jobs:
         shell: bash
         shell: bash
 
 
     steps:
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
       with:
         submodules: recursive
         submodules: recursive
 
 
@@ -363,10 +323,11 @@ jobs:
       env:
       env:
         VCMI_BUILD_PLATFORM: x64
         VCMI_BUILD_PLATFORM: x64
 
 
-    - uses: actions/setup-python@v4
+    - uses: actions/setup-python@v5
       if: "${{ matrix.conan_profile != '' }}"
       if: "${{ matrix.conan_profile != '' }}"
       with:
       with:
         python-version: '3.10'
         python-version: '3.10'
+
     - name: Conan setup
     - name: Conan setup
       if: "${{ matrix.conan_profile != '' }}"
       if: "${{ matrix.conan_profile != '' }}"
       run: |
       run: |
@@ -382,10 +343,6 @@ jobs:
       env:
       env:
         GENERATE_ONLY_BUILT_CONFIG: 1
         GENERATE_ONLY_BUILT_CONFIG: 1
 
 
-    - name: Git branch name
-      id: git-branch-name
-      uses: EthanSK/git-branch-name-action@v1
-
     - name: Build Number
     - name: Build Number
       run: |
       run: |
         source '${{github.workspace}}/CI/get_package_name.sh'
         source '${{github.workspace}}/CI/get_package_name.sh'
@@ -407,12 +364,12 @@ jobs:
         cmake --build --preset ${{matrix.preset}}
         cmake --build --preset ${{matrix.preset}}
 
 
     - name: Download libs x64
     - name: Download libs x64
-      uses: actions/download-artifact@v3
+      uses: actions/download-artifact@v4
       with:
       with:
         name: Android JNI android-64
         name: Android JNI android-64
         path: ${{ github.workspace }}/android/vcmi-app/src/main/jniLibs/
         path: ${{ github.workspace }}/android/vcmi-app/src/main/jniLibs/
  
  
-    - name: Create android package
+    - name: Create Android package
       run: |
       run: |
         cd android
         cd android
         ./gradlew bundleRelease --info
         ./gradlew bundleRelease --info
@@ -422,16 +379,8 @@ jobs:
         ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
         ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
 
 
     - name: Android artifacts
     - name: Android artifacts
-      uses: actions/upload-artifact@v3
+      uses: actions/upload-artifact@v4
       with:
       with:
         name: ${{ env.VCMI_PACKAGE_FILE_NAME }}
         name: ${{ env.VCMI_PACKAGE_FILE_NAME }}
         path: |
         path: |
           ${{ env.ANDROID_APK_PATH }}
           ${{ env.ANDROID_APK_PATH }}
-
-    - uses: act10ns/slack@v1
-      with:
-        status: ${{ job.status }}
-        channel: '#notifications'
-      env:
-        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
-      if: always()

+ 1 - 0
.gitignore

@@ -41,6 +41,7 @@ VCMI_VS11.sdf
 VCMI_VS11.opensdf
 VCMI_VS11.opensdf
 .DS_Store
 .DS_Store
 CMakeUserPresets.json
 CMakeUserPresets.json
+compile_commands.json
 
 
 # Visual Studio
 # Visual Studio
 *.suo
 *.suo

+ 6 - 3
AI/BattleAI/AttackPossibility.cpp

@@ -33,7 +33,8 @@ void DamageCache::buildDamageCache(std::shared_ptr<HypotheticBattle> hb, int sid
 			return u->isValidTarget();
 			return u->isValidTarget();
 		});
 		});
 
 
-	std::vector<const battle::Unit *> ourUnits, enemyUnits;
+	std::vector<const battle::Unit *> ourUnits;
+	std::vector<const battle::Unit *> enemyUnits;
 
 
 	for(auto stack : stacks)
 	for(auto stack : stacks)
 	{
 	{
@@ -295,8 +296,10 @@ AttackPossibility AttackPossibility::evaluate(
 
 
 			for(int i = 0; i < totalAttacks; i++)
 			for(int i = 0; i < totalAttacks; i++)
 			{
 			{
-				int64_t damageDealt, damageReceived;
-				float defenderDamageReduce, attackerDamageReduce;
+				int64_t damageDealt;
+				int64_t damageReceived;
+				float defenderDamageReduce;
+				float attackerDamageReduce;
 
 
 				DamageEstimation retaliation;
 				DamageEstimation retaliation;
 				auto attackDmg = state->battleEstimateDamage(ap.attack, &retaliation);
 				auto attackDmg = state->battleEstimateDamage(ap.attack, &retaliation);

+ 2 - 1
AI/BattleAI/BattleAI.cpp

@@ -90,7 +90,8 @@ void CBattleAI::yourTacticPhase(const BattleID & battleID, int distance)
 static float getStrengthRatio(std::shared_ptr<CBattleInfoCallback> cb, int side)
 static float getStrengthRatio(std::shared_ptr<CBattleInfoCallback> cb, int side)
 {
 {
 	auto stacks = cb->battleGetAllStacks();
 	auto stacks = cb->battleGetAllStacks();
-	auto our = 0, enemy = 0;
+	auto our = 0;
+	auto enemy = 0;
 
 
 	for(auto stack : stacks)
 	for(auto stack : stacks)
 	{
 	{

+ 7 - 4
AI/BattleAI/BattleEvaluator.cpp

@@ -22,6 +22,8 @@
 #include "../../lib/battle/BattleStateInfoForRetreat.h"
 #include "../../lib/battle/BattleStateInfoForRetreat.h"
 #include "../../lib/battle/CObstacleInstance.h"
 #include "../../lib/battle/CObstacleInstance.h"
 #include "../../lib/battle/BattleAction.h"
 #include "../../lib/battle/BattleAction.h"
+#include "../../lib/CRandomGenerator.h"
+
 
 
 // TODO: remove
 // TODO: remove
 // Eventually only IBattleInfoCallback and battle::Unit should be used,
 // Eventually only IBattleInfoCallback and battle::Unit should be used,
@@ -350,10 +352,11 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
 	LOGL("Casting spells sounds like fun. Let's see...");
 	LOGL("Casting spells sounds like fun. Let's see...");
 	//Get all spells we can cast
 	//Get all spells we can cast
 	std::vector<const CSpell*> possibleSpells;
 	std::vector<const CSpell*> possibleSpells;
-	vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [hero, this](const CSpell *s) -> bool
-	{
-		return s->canBeCast(cb->getBattle(battleID).get(), spells::Mode::HERO, hero);
-	});
+
+	for (auto const & s : VLC->spellh->objects)
+		if (s->canBeCast(cb->getBattle(battleID).get(), spells::Mode::HERO, hero))
+			possibleSpells.push_back(s.get());
+
 	LOGFL("I can cast %d spells.", possibleSpells.size());
 	LOGFL("I can cast %d spells.", possibleSpells.size());
 
 
 	vstd::erase_if(possibleSpells, [](const CSpell *s)
 	vstd::erase_if(possibleSpells, [](const CSpell *s)

+ 2 - 2
AI/BattleAI/StackWithBonuses.cpp

@@ -134,7 +134,7 @@ SlotID StackWithBonuses::unitSlot() const
 TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const CSelector & limit,
 TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const CSelector & limit,
 	const CBonusSystemNode * root, const std::string & cachingStr) const
 	const CBonusSystemNode * root, const std::string & cachingStr) const
 {
 {
-	TBonusListPtr ret = std::make_shared<BonusList>();
+	auto ret = std::make_shared<BonusList>();
 	TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, root, cachingStr);
 	TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, root, cachingStr);
 
 
 	vstd::copy_if(*originalList, std::back_inserter(*ret), [this](const std::shared_ptr<Bonus> & b)
 	vstd::copy_if(*originalList, std::back_inserter(*ret), [this](const std::shared_ptr<Bonus> & b)
@@ -356,7 +356,7 @@ void HypotheticBattle::addUnit(uint32_t id, const JsonNode & data)
 {
 {
 	battle::UnitInfo info;
 	battle::UnitInfo info;
 	info.load(id, data);
 	info.load(id, data);
-	std::shared_ptr<StackWithBonuses> newUnit = std::make_shared<StackWithBonuses>(this, info);
+	auto newUnit = std::make_shared<StackWithBonuses>(this, info);
 	stackStates[newUnit->unitId()] = newUnit;
 	stackStates[newUnit->unitId()] = newUnit;
 }
 }
 
 

+ 2 - 2
AI/EmptyAI/CEmptyAI.cpp

@@ -14,11 +14,11 @@
 #include "../../lib/CStack.h"
 #include "../../lib/CStack.h"
 #include "../../lib/battle/BattleAction.h"
 #include "../../lib/battle/BattleAction.h"
 
 
-void CEmptyAI::saveGame(BinarySerializer & h, const int version)
+void CEmptyAI::saveGame(BinarySerializer & h)
 {
 {
 }
 }
 
 
-void CEmptyAI::loadGame(BinaryDeserializer & h, const int version)
+void CEmptyAI::loadGame(BinaryDeserializer & h)
 {
 {
 }
 }
 
 

+ 2 - 2
AI/EmptyAI/CEmptyAI.h

@@ -19,8 +19,8 @@ class CEmptyAI : public CGlobalAI
 	std::shared_ptr<CCallback> cb;
 	std::shared_ptr<CCallback> cb;
 
 
 public:
 public:
-	virtual void saveGame(BinarySerializer & h, const int version) override;
-	virtual void loadGame(BinaryDeserializer & h, const int version) override;
+	virtual void saveGame(BinarySerializer & h) override;
+	virtual void loadGame(BinaryDeserializer & h) override;
 
 
 	void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
 	void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
 	void yourTurn(QueryID queryID) override;
 	void yourTurn(QueryID queryID) override;

+ 31 - 28
AI/Nullkiller/AIGateway.cpp

@@ -64,7 +64,7 @@ struct SetGlobalState
 };
 };
 
 
 
 
-#define SET_GLOBAL_STATE(ai) SetGlobalState _hlpSetState(ai);
+#define SET_GLOBAL_STATE(ai) SetGlobalState _hlpSetState(ai)
 
 
 #define NET_EVENT_HANDLER SET_GLOBAL_STATE(this)
 #define NET_EVENT_HANDLER SET_GLOBAL_STATE(this)
 #define MAKING_TURN SET_GLOBAL_STATE(this)
 #define MAKING_TURN SET_GLOBAL_STATE(this)
@@ -101,7 +101,7 @@ void AIGateway::heroMoved(const TryMoveHero & details, bool verbose)
 	if(!hero)
 	if(!hero)
 		validateObject(details.id); //enemy hero may have left visible area
 		validateObject(details.id); //enemy hero may have left visible area
 
 
-	const int3 from = hero ? hero->convertToVisitablePos(details.start) : (details.start - int3(0,1,0));;
+	const int3 from = hero ? hero->convertToVisitablePos(details.start) : (details.start - int3(0,1,0));
 	const int3 to   = hero ? hero->convertToVisitablePos(details.end)   : (details.end   - int3(0,1,0));
 	const int3 to   = hero ? hero->convertToVisitablePos(details.end)   : (details.end   - int3(0,1,0));
 
 
 	const CGObjectInstance * o1 = vstd::frontOrNull(cb->getVisitableObjs(from, verbose));
 	const CGObjectInstance * o1 = vstd::frontOrNull(cb->getVisitableObjs(from, verbose));
@@ -586,11 +586,18 @@ void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, s
 
 
 	requestActionASAP([=]()
 	requestActionASAP([=]()
 	{ 
 	{ 
+		int sel = 0;
+
 		if(hPtr.validAndSet())
 		if(hPtr.validAndSet())
 		{
 		{
+			std::unique_lock<std::mutex> lockGuard(nullkiller->aiStateMutex);
+
 			nullkiller->heroManager->update();
 			nullkiller->heroManager->update();
-			answerQuery(queryID, nullkiller->heroManager->selectBestSkill(hPtr, skills));
+
+			sel = nullkiller->heroManager->selectBestSkill(hPtr, skills);
 		}
 		}
+
+		answerQuery(queryID, sel);
 	});
 	});
 }
 }
 
 
@@ -661,14 +668,18 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
 		if(selection) //select from multiple components -> take the last one (they're indexed [1-size])
 		if(selection) //select from multiple components -> take the last one (they're indexed [1-size])
 			sel = components.size();
 			sel = components.size();
 
 
-		// TODO: Find better way to understand it is Chest of Treasures
-		if(hero.validAndSet()
-			&& components.size() == 2
-			&& components.front().type == ComponentType::RESOURCE
-			&& (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN
-			|| nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE))
 		{
 		{
-			sel = 1; // for now lets pick gold from a chest.
+				std::unique_lock<std::mutex> mxLock(nullkiller->aiStateMutex);
+
+				// TODO: Find better way to understand it is Chest of Treasures
+				if(hero.validAndSet()
+					&& components.size() == 2
+					&& components.front().type == ComponentType::RESOURCE
+					&& (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN
+						|| nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE))
+				{
+					sel = 1; // for now lets pick gold from a chest.
+				}
 		}
 		}
 
 
 		answerQuery(askID, sel);
 		answerQuery(askID, sel);
@@ -747,27 +758,25 @@ void AIGateway::showMapObjectSelectDialog(QueryID askID, const Component & icon,
 	requestActionASAP([=](){ answerQuery(askID, selectedObject.getNum()); });
 	requestActionASAP([=](){ answerQuery(askID, selectedObject.getNum()); });
 }
 }
 
 
-void AIGateway::saveGame(BinarySerializer & h, const int version)
+void AIGateway::saveGame(BinarySerializer & h)
 {
 {
-	LOG_TRACE_PARAMS(logAi, "version '%i'", version);
 	NET_EVENT_HANDLER;
 	NET_EVENT_HANDLER;
 	nullkiller->memory->removeInvisibleObjects(myCb.get());
 	nullkiller->memory->removeInvisibleObjects(myCb.get());
 
 
-	CAdventureAI::saveGame(h, version);
-	serializeInternal(h, version);
+	CAdventureAI::saveGame(h);
+	serializeInternal(h);
 }
 }
 
 
-void AIGateway::loadGame(BinaryDeserializer & h, const int version)
+void AIGateway::loadGame(BinaryDeserializer & h)
 {
 {
-	LOG_TRACE_PARAMS(logAi, "version '%i'", version);
 	//NET_EVENT_HANDLER;
 	//NET_EVENT_HANDLER;
 
 
 	#if 0
 	#if 0
 	//disabled due to issue 2890
 	//disabled due to issue 2890
 	registerGoals(h);
 	registerGoals(h);
 	#endif // 0
 	#endif // 0
-	CAdventureAI::loadGame(h, version);
-	serializeInternal(h, version);
+	CAdventureAI::loadGame(h);
+	serializeInternal(h);
 }
 }
 
 
 bool AIGateway::makePossibleUpgrades(const CArmedInstance * obj)
 bool AIGateway::makePossibleUpgrades(const CArmedInstance * obj)
@@ -859,6 +868,8 @@ void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h
 		{
 		{
 			makePossibleUpgrades(h.get());
 			makePossibleUpgrades(h.get());
 
 
+			std::unique_lock<std::mutex>  lockGuard(nullkiller->aiStateMutex);
+
 			if(!h->visitedTown->garrisonHero || !nullkiller->isHeroLocked(h->visitedTown->garrisonHero))
 			if(!h->visitedTown->garrisonHero || !nullkiller->isHeroLocked(h->visitedTown->garrisonHero))
 				moveCreaturesToHero(h->visitedTown);
 				moveCreaturesToHero(h->visitedTown);
 
 
@@ -1120,15 +1131,6 @@ void AIGateway::battleEnd(const BattleID & battleID, const BattleResult * br, Qu
 	logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.toString(), (won ? "won" : "lost"), battlename);
 	logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.toString(), (won ? "won" : "lost"), battlename);
 	battlename.clear();
 	battlename.clear();
 
 
-	if (queryID != QueryID::NONE)
-	{
-		status.addQuery(queryID, "Combat result dialog");
-		const int confirmAction = 0;
-		requestActionASAP([=]()
-		{
-			answerQuery(queryID, confirmAction);
-		});
-	}
 	CAdventureAI::battleEnd(battleID, br, queryID);
 	CAdventureAI::battleEnd(battleID, br, queryID);
 }
 }
 
 
@@ -1413,7 +1415,8 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
 				if(res.getNum() == g.resID) //sell any other resource
 				if(res.getNum() == g.resID) //sell any other resource
 					continue;
 					continue;
 
 
-				int toGive, toGet;
+				int toGive;
+				int toGet;
 				m->getOffer(res, g.resID, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
 				m->getOffer(res, g.resID, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
 				toGive = static_cast<int>(toGive * (it->resVal / toGive)); //round down
 				toGive = static_cast<int>(toGive * (it->resVal / toGive)); //round down
 				//TODO trade only as much as needed
 				//TODO trade only as much as needed

+ 4 - 4
AI/Nullkiller/AIGateway.h

@@ -62,7 +62,7 @@ public:
 	void heroVisit(const CGObjectInstance * obj, bool started);
 	void heroVisit(const CGObjectInstance * obj, bool started);
 
 
 
 
-	template<typename Handler> void serialize(Handler & h, const int version)
+	template<typename Handler> void serialize(Handler & h)
 	{
 	{
 		h & battle;
 		h & battle;
 		h & remainingQueries;
 		h & remainingQueries;
@@ -119,8 +119,8 @@ public:
 	void showGarrisonDialog(const CArmedInstance * up, const CGHeroInstance * down, bool removableUnits, QueryID queryID) override; //all stacks operations between these objects become allowed, interface has to call onEnd when done
 	void showGarrisonDialog(const CArmedInstance * up, const CGHeroInstance * down, bool removableUnits, QueryID queryID) override; //all stacks operations between these objects become allowed, interface has to call onEnd when done
 	void showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
 	void showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
 	void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
 	void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
-	void saveGame(BinarySerializer & h, const int version) override; //saving
-	void loadGame(BinaryDeserializer & h, const int version) override; //loading
+	void saveGame(BinarySerializer & h) override; //saving
+	void loadGame(BinaryDeserializer & h) override; //loading
 	void finish() override;
 	void finish() override;
 
 
 	void availableCreaturesChanged(const CGDwelling * town) override;
 	void availableCreaturesChanged(const CGDwelling * town) override;
@@ -203,7 +203,7 @@ public:
 	//special function that can be called ONLY from game events handling thread and will send request ASAP
 	//special function that can be called ONLY from game events handling thread and will send request ASAP
 	void requestActionASAP(std::function<void()> whatToDo);
 	void requestActionASAP(std::function<void()> whatToDo);
 
 
-	template<typename Handler> void serializeInternal(Handler & h, const int version)
+	template<typename Handler> void serializeInternal(Handler & h)
 	{
 	{
 		h & nullkiller->memory->knownTeleportChannels;
 		h & nullkiller->memory->knownTeleportChannels;
 		h & nullkiller->memory->knownSubterraneanGates;
 		h & nullkiller->memory->knownSubterraneanGates;

+ 1 - 3
AI/Nullkiller/AIUtility.cpp

@@ -276,12 +276,10 @@ creInfo infoFromDC(const dwellingContent & dc)
 	ci.creID = dc.second.size() ? dc.second.back() : CreatureID(-1); //should never be accessed
 	ci.creID = dc.second.size() ? dc.second.back() : CreatureID(-1); //should never be accessed
 	if (ci.creID != CreatureID::NONE)
 	if (ci.creID != CreatureID::NONE)
 	{
 	{
-		ci.cre = VLC->creatures()->getById(ci.creID);
-		ci.level = ci.cre->getLevel(); //this is creature tier, while tryRealize expects dwelling level. Ignore.
+		ci.level = ci.creID.toCreature()->getLevel(); //this is creature tier, while tryRealize expects dwelling level. Ignore.
 	}
 	}
 	else
 	else
 	{
 	{
-		ci.cre = nullptr;
 		ci.level = 0;
 		ci.level = 0;
 	}
 	}
 	return ci;
 	return ci;

+ 5 - 4
AI/Nullkiller/AIUtility.h

@@ -60,7 +60,9 @@ struct creInfo;
 class AIGateway;
 class AIGateway;
 class Nullkiller;
 class Nullkiller;
 
 
-const int GOLD_MINE_PRODUCTION = 1000, WOOD_ORE_MINE_PRODUCTION = 2, RESOURCE_MINE_PRODUCTION = 1;
+const int GOLD_MINE_PRODUCTION = 1000;
+const int WOOD_ORE_MINE_PRODUCTION = 2;
+const int RESOURCE_MINE_PRODUCTION = 1;
 const int ACTUAL_RESOURCE_COUNT = 7;
 const int ACTUAL_RESOURCE_COUNT = 7;
 const int ALLOWED_ROAMING_HEROES = 8;
 const int ALLOWED_ROAMING_HEROES = 8;
 
 
@@ -113,7 +115,7 @@ public:
 	bool validAndSet() const;
 	bool validAndSet() const;
 
 
 
 
-	template<typename Handler> void serialize(Handler & h, const int version)
+	template<typename Handler> void serialize(Handler & h)
 	{
 	{
 		h & this->h;
 		h & this->h;
 		h & hid;
 		h & hid;
@@ -145,7 +147,7 @@ struct ObjectIdRef
 	bool operator<(const ObjectIdRef & rhs) const;
 	bool operator<(const ObjectIdRef & rhs) const;
 
 
 
 
-	template<typename Handler> void serialize(Handler & h, const int version)
+	template<typename Handler> void serialize(Handler & h)
 	{
 	{
 		h & id;
 		h & id;
 	}
 	}
@@ -161,7 +163,6 @@ struct creInfo
 {
 {
 	int count;
 	int count;
 	CreatureID creID;
 	CreatureID creID;
-	const Creature * cre;
 	int level;
 	int level;
 };
 };
 creInfo infoFromDC(const dwellingContent & dc);
 creInfo infoFromDC(const dwellingContent & dc);

+ 9 - 9
AI/Nullkiller/Analyzers/ArmyManager.cpp

@@ -63,9 +63,9 @@ std::vector<SlotInfo> ArmyManager::toSlotInfo(std::vector<creInfo> army) const
 	{
 	{
 		SlotInfo slot;
 		SlotInfo slot;
 
 
-		slot.creature = VLC->creh->objects[i.cre->getId()];
+		slot.creature = i.creID.toCreature();
 		slot.count = i.count;
 		slot.count = i.count;
-		slot.power = evaluateStackPower(i.cre, i.count);
+		slot.power = evaluateStackPower(i.creID.toCreature(), i.count);
 
 
 		result.push_back(slot);
 		result.push_back(slot);
 	}
 	}
@@ -128,7 +128,7 @@ class TemporaryArmy : public CArmedInstance
 public:
 public:
 	void armyChanged() override {}
 	void armyChanged() override {}
 	TemporaryArmy()
 	TemporaryArmy()
-		:CArmedInstance(true)
+		:CArmedInstance(nullptr, true)
 	{
 	{
 	}
 	}
 };
 };
@@ -259,7 +259,7 @@ std::shared_ptr<CCreatureSet> ArmyManager::getArmyAvailableToBuyAsCCreatureSet(
 		if(!ci.count || ci.creID == CreatureID::NONE)
 		if(!ci.count || ci.creID == CreatureID::NONE)
 			continue;
 			continue;
 
 
-		vstd::amin(ci.count, availableRes / ci.cre->getFullRecruitCost()); //max count we can afford
+		vstd::amin(ci.count, availableRes / ci.creID.toCreature()->getFullRecruitCost()); //max count we can afford
 
 
 		if(!ci.count)
 		if(!ci.count)
 			continue;
 			continue;
@@ -270,7 +270,7 @@ std::shared_ptr<CCreatureSet> ArmyManager::getArmyAvailableToBuyAsCCreatureSet(
 			break;
 			break;
 
 
 		army->setCreature(dst, ci.creID, ci.count);
 		army->setCreature(dst, ci.creID, ci.count);
-		availableRes -= ci.cre->getFullRecruitCost() * ci.count;
+		availableRes -= ci.creID.toCreature()->getFullRecruitCost() * ci.count;
 	}
 	}
 
 
 	return army;
 	return army;
@@ -287,7 +287,7 @@ ui64 ArmyManager::howManyReinforcementsCanBuy(
 
 
 	for(const creInfo & ci : army)
 	for(const creInfo & ci : army)
 	{
 	{
-		aivalue += ci.count * ci.cre->getAIValue();
+		aivalue += ci.count * ci.creID.toCreature()->getAIValue();
 	}
 	}
 
 
 	return aivalue;
 	return aivalue;
@@ -320,7 +320,7 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
 
 
 		if(i < GameConstants::CREATURES_PER_TOWN && countGrowth)
 		if(i < GameConstants::CREATURES_PER_TOWN && countGrowth)
 		{
 		{
-			ci.count += town ? town->creatureGrowth(i) : ci.cre->getGrowth();
+			ci.count += town ? town->creatureGrowth(i) : ci.creID.toCreature()->getGrowth();
 		}
 		}
 
 
 		if(!ci.count) continue;
 		if(!ci.count) continue;
@@ -334,13 +334,13 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
 				freeHeroSlots--; //new slot will be occupied
 				freeHeroSlots--; //new slot will be occupied
 		}
 		}
 
 
-		vstd::amin(ci.count, availableRes / ci.cre->getFullRecruitCost()); //max count we can afford
+		vstd::amin(ci.count, availableRes / ci.creID.toCreature()->getFullRecruitCost()); //max count we can afford
 
 
 		if(!ci.count) continue;
 		if(!ci.count) continue;
 
 
 		ci.level = i; //this is important for Dungeon Summoning Portal
 		ci.level = i; //this is important for Dungeon Summoning Portal
 		creaturesInDwellings.push_back(ci);
 		creaturesInDwellings.push_back(ci);
-		availableRes -= ci.cre->getFullRecruitCost() * ci.count;
+		availableRes -= ci.creID.toCreature()->getFullRecruitCost() * ci.count;
 	}
 	}
 
 
 	return creaturesInDwellings;
 	return creaturesInDwellings;

+ 5 - 5
AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp

@@ -11,6 +11,7 @@
 #include "DangerHitMapAnalyzer.h"
 #include "DangerHitMapAnalyzer.h"
 
 
 #include "../Engine/Nullkiller.h"
 #include "../Engine/Nullkiller.h"
+#include "../../../lib/CRandomGenerator.h"
 
 
 namespace NKAI
 namespace NKAI
 {
 {
@@ -165,7 +166,7 @@ void DangerHitMapAnalyzer::calculateTileOwners()
 
 
 	auto addTownHero = [&](const CGTownInstance * town)
 	auto addTownHero = [&](const CGTownInstance * town)
 	{
 	{
-			auto townHero = new CGHeroInstance();
+			auto townHero = new CGHeroInstance(town->cb);
 			CRandomGenerator rng;
 			CRandomGenerator rng;
 			auto visitablePos = town->visitablePos();
 			auto visitablePos = town->visitablePos();
 			
 			
@@ -265,8 +266,9 @@ uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath &
 {
 {
 	int3 tile = path.targetTile();
 	int3 tile = path.targetTile();
 	int turn = path.turn();
 	int turn = path.turn();
-	const HitMapNode & info = hitMap[tile.x][tile.y][tile.z];
 
 
+	const auto& info = getTileThreat(tile);
+	
 	return (info.fastestDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.fastestDanger.danger))
 	return (info.fastestDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.fastestDanger.danger))
 		|| (info.maximumDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.maximumDanger.danger));
 		|| (info.maximumDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.maximumDanger.danger));
 }
 }
@@ -280,9 +282,7 @@ const HitMapNode & DangerHitMapAnalyzer::getObjectThreat(const CGObjectInstance
 
 
 const HitMapNode & DangerHitMapAnalyzer::getTileThreat(const int3 & tile) const
 const HitMapNode & DangerHitMapAnalyzer::getTileThreat(const int3 & tile) const
 {
 {
-	const HitMapNode & info = hitMap[tile.x][tile.y][tile.z];
-
-	return info;
+	return hitMap[tile.x][tile.y][tile.z];
 }
 }
 
 
 const std::set<const CGObjectInstance *> empty = {};
 const std::set<const CGObjectInstance *> empty = {};

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

@@ -39,7 +39,9 @@ void engineBase::addRule(const std::string & txt)
 
 
 struct armyStructure
 struct armyStructure
 {
 {
-	float walkers, shooters, flyers;
+	float walkers;
+	float shooters;
+	float flyers;
 	ui32 maxSpeed;
 	ui32 maxSpeed;
 };
 };
 
 

+ 8 - 2
AI/Nullkiller/Engine/FuzzyEngines.h

@@ -41,8 +41,14 @@ public:
 	TacticalAdvantageEngine();
 	TacticalAdvantageEngine();
 	float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us
 	float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us
 private:
 private:
-	fl::InputVariable * ourWalkers, *ourShooters, *ourFlyers, *enemyWalkers, *enemyShooters, *enemyFlyers;
-	fl::InputVariable * ourSpeed, *enemySpeed;
+	fl::InputVariable * ourWalkers;
+	fl::InputVariable * ourShooters;
+	fl::InputVariable * ourFlyers;
+	fl::InputVariable * enemyWalkers;
+	fl::InputVariable * enemyShooters;
+	fl::InputVariable * enemyFlyers;
+	fl::InputVariable * ourSpeed;
+	fl::InputVariable * enemySpeed;
 	fl::InputVariable * bankPresent;
 	fl::InputVariable * bankPresent;
 	fl::InputVariable * castleWalls;
 	fl::InputVariable * castleWalls;
 	fl::OutputVariable * threat;
 	fl::OutputVariable * threat;

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

@@ -30,7 +30,7 @@ ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
 
 
 	ui64 totalStrength = 0;
 	ui64 totalStrength = 0;
 	ui8 totalChance = 0;
 	ui8 totalChance = 0;
-	for(auto config : bankInfo->getPossibleGuards())
+	for(auto config : bankInfo->getPossibleGuards(bank->cb))
 	{
 	{
 		totalStrength += config.second.totalStrength * config.first;
 		totalStrength += config.second.totalStrength * config.first;
 		totalChance += config.first;
 		totalChance += config.first;

+ 4 - 0
AI/Nullkiller/Engine/Nullkiller.cpp

@@ -115,6 +115,8 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositi
 
 
 void Nullkiller::resetAiState()
 void Nullkiller::resetAiState()
 {
 {
+	std::unique_lock<std::mutex> lockGuard(aiStateMutex);
+
 	lockedResources = TResources();
 	lockedResources = TResources();
 	scanDepth = ScanDepth::MAIN_FULL;
 	scanDepth = ScanDepth::MAIN_FULL;
 	playerID = ai->playerID;
 	playerID = ai->playerID;
@@ -127,6 +129,8 @@ void Nullkiller::updateAiState(int pass, bool fast)
 {
 {
 	boost::this_thread::interruption_point();
 	boost::this_thread::interruption_point();
 
 
+	std::unique_lock<std::mutex> lockGuard(aiStateMutex);
+
 	auto start = std::chrono::high_resolution_clock::now();
 	auto start = std::chrono::high_resolution_clock::now();
 
 
 	activeHero = nullptr;
 	activeHero = nullptr;

+ 1 - 0
AI/Nullkiller/Engine/Nullkiller.h

@@ -73,6 +73,7 @@ public:
 	std::unique_ptr<ArmyFormation> armyFormation;
 	std::unique_ptr<ArmyFormation> armyFormation;
 	PlayerColor playerID;
 	PlayerColor playerID;
 	std::shared_ptr<CCallback> cb;
 	std::shared_ptr<CCallback> cb;
+	std::mutex aiStateMutex;
 
 
 	Nullkiller();
 	Nullkiller();
 	void init(std::shared_ptr<CCallback> cb, PlayerColor playerID);
 	void init(std::shared_ptr<CCallback> cb, PlayerColor playerID);

+ 21 - 4
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -137,11 +137,23 @@ 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)
+{
+	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;
+}
+
 uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
 uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
 {
 {
 	auto objectInfo = target->getObjectHandler()->getObjectInfo(target->appearance);
 	auto objectInfo = target->getObjectHandler()->getObjectInfo(target->appearance);
 	CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
 	CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
-	auto creatures = bankInfo->getPossibleCreaturesReward();
+	auto creatures = bankInfo->getPossibleCreaturesReward(target->cb);
 	uint64_t result = 0;
 	uint64_t result = 0;
 
 
 	const auto& slots = hero->Slots();
 	const auto& slots = hero->Slots();
@@ -236,7 +248,7 @@ int getDwellingArmyCost(const CGObjectInstance * target)
 	return cost;
 	return cost;
 }
 }
 
 
-uint64_t evaluateArtifactArmyValue(CArtifactInstance * art)
+static uint64_t evaluateArtifactArmyValue(const CArtifactInstance * art)
 {
 {
 	if(art->artType->getId() == ArtifactID::SPELL_SCROLL)
 	if(art->artType->getId() == ArtifactID::SPELL_SCROLL)
 		return 1500;
 		return 1500;
@@ -491,7 +503,7 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
 			//Evaluate resources used for construction. Gold is evaluated separately.
 			//Evaluate resources used for construction. Gold is evaluated separately.
 			if (it->resType != EGameResID::GOLD)
 			if (it->resType != EGameResID::GOLD)
 			{
 			{
-				sum += 0.1f * getResourceRequirementStrength(it->resType);
+				sum += 0.1f * it->resVal * getResourceRequirementStrength(it->resType);
 			}
 			}
 		}
 		}
 		return sum;
 		return sum;
@@ -529,6 +541,9 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
 			? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance *>(target))
 			? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance *>(target))
 			: 0;
 			: 0;
 
 
+	case Obj::KEYMASTER:
+		return 0.6f;
+
 	default:
 	default:
 		return 0;
 		return 0;
 	}
 	}
@@ -588,6 +603,8 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
 	case Obj::PANDORAS_BOX:
 	case Obj::PANDORAS_BOX:
 		//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:
+		return 3.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
@@ -660,7 +677,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
 	case Obj::WAGON:
 	case Obj::WAGON:
 		return 100;
 		return 100;
 	case Obj::CREATURE_BANK:
 	case Obj::CREATURE_BANK:
-		return getCreatureBankResources(target, hero)[EGameResID::GOLD];
+		return getResourcesGoldReward(getCreatureBankResources(target, hero));
 	case Obj::CRYPT:
 	case Obj::CRYPT:
 	case Obj::DERELICT_SHIP:
 	case Obj::DERELICT_SHIP:
 		return 3000;
 		return 3000;

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

@@ -54,12 +54,12 @@ void BuyArmy::accept(AIGateway * ai)
 		if(objid != CreatureID::NONE && ci.creID.getNum() != objid)
 		if(objid != CreatureID::NONE && ci.creID.getNum() != objid)
 			continue;
 			continue;
 
 
-		vstd::amin(ci.count, res / ci.cre->getFullRecruitCost());
+		vstd::amin(ci.count, res / ci.creID.toCreature()->getFullRecruitCost());
 
 
 		if(ci.count)
 		if(ci.count)
 		{
 		{
 			cb->recruitCreatures(town, town->getUpperArmy(), ci.creID, ci.count, ci.level);
 			cb->recruitCreatures(town, town->getUpperArmy(), ci.creID, ci.count, ci.level);
-			valueBought += ci.count * ci.cre->getAIValue();
+			valueBought += ci.count * ci.creID.toCreature()->getAIValue();
 		}
 		}
 	}
 	}
 
 

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

@@ -37,7 +37,7 @@ namespace Goals
 		{
 		{
 			return new T(static_cast<T const &>(*this)); //casting enforces template instantiation
 			return new T(static_cast<T const &>(*this)); //casting enforces template instantiation
 		}
 		}
-		template<typename Handler> void serialize(Handler & h, const int version)
+		template<typename Handler> void serialize(Handler & h)
 		{
 		{
 			h & static_cast<AbstractGoal &>(*this);
 			h & static_cast<AbstractGoal &>(*this);
 			//h & goalType & isElementar & isAbstract & priority;
 			//h & goalType & isElementar & isAbstract & priority;

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

@@ -98,7 +98,7 @@ std::string CompleteQuest::questToString() const
 		return "inactive quest";
 		return "inactive quest";
 
 
 	MetaString ms;
 	MetaString ms;
-	q.quest->getRolloverText(ms, false);
+	q.quest->getRolloverText(q.obj->cb, ms, false);
 
 
 	return ms.toString();
 	return ms.toString();
 }
 }
@@ -210,7 +210,7 @@ TGoalVec CompleteQuest::missionResources() const
 
 
 TGoalVec CompleteQuest::missionDestroyObj() const
 TGoalVec CompleteQuest::missionDestroyObj() const
 {
 {
-	auto obj = cb->getObjByQuestIdentifier(q.quest->killTarget);
+	auto obj = cb->getObj(q.quest->killTarget);
 
 
 	if(!obj)
 	if(!obj)
 		return CaptureObjectsBehavior(q.obj).decompose();
 		return CaptureObjectsBehavior(q.obj).decompose();

+ 21 - 4
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -35,6 +35,8 @@ 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;
+
 AISharedStorage::AISharedStorage(int3 sizes)
 AISharedStorage::AISharedStorage(int3 sizes)
 {
 {
 	if(!shared){
 	if(!shared){
@@ -234,6 +236,7 @@ void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, EPath
 		heroNode.specialAction.reset();
 		heroNode.specialAction.reset();
 		heroNode.armyLoss = 0;
 		heroNode.armyLoss = 0;
 		heroNode.chainOther = nullptr;
 		heroNode.chainOther = nullptr;
+		heroNode.dayFlags = DayFlags::NONE;
 		heroNode.update(coord, layer, accessibility);
 		heroNode.update(coord, layer, accessibility);
 	}
 	}
 }
 }
@@ -265,7 +268,8 @@ void AINodeStorage::commit(
 	EPathNodeAction action, 
 	EPathNodeAction action, 
 	int turn, 
 	int turn, 
 	int movementLeft, 
 	int movementLeft, 
-	float cost) const
+	float cost,
+	bool saveToCommited) const
 {
 {
 	destination->action = action;
 	destination->action = action;
 	destination->setCost(cost);
 	destination->setCost(cost);
@@ -291,10 +295,15 @@ void AINodeStorage::commit(
 		destination->actor->armyValue);
 		destination->actor->armyValue);
 #endif
 #endif
 
 
-	if(destination->turns <= heroChainTurn)
+	if(saveToCommited && destination->turns <= heroChainTurn)
 	{
 	{
 		commitedTiles.insert(destination->coord);
 		commitedTiles.insert(destination->coord);
 	}
 	}
+
+	if(destination->turns == source->turns)
+	{
+		destination->dayFlags = source->dayFlags;
+	}
 }
 }
 
 
 std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
 std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
@@ -778,7 +787,14 @@ void HeroChainCalculationTask::addHeroChain(const std::vector<ExchangeCandidate>
 			continue;
 			continue;
 		}
 		}
 
 
-		storage.commit(exchangeNode, carrier, carrier->action, chainInfo.turns, chainInfo.moveRemains, chainInfo.getCost());
+		storage.commit(
+			exchangeNode,
+			carrier,
+			carrier->action,
+			chainInfo.turns,
+			chainInfo.moveRemains, 
+			chainInfo.getCost(),
+			DO_NOT_SAVE_TO_COMMITED_TILES);
 
 
 		if(carrier->specialAction || carrier->chainOther)
 		if(carrier->specialAction || carrier->chainOther)
 		{
 		{
@@ -1070,7 +1086,8 @@ struct TowmPortalFinder
 				EPathNodeAction::TELEPORT_NORMAL,
 				EPathNodeAction::TELEPORT_NORMAL,
 				bestNode->turns,
 				bestNode->turns,
 				bestNode->moveRemains - movementNeeded,
 				bestNode->moveRemains - movementNeeded,
-				movementCost);
+				movementCost,
+				DO_NOT_SAVE_TO_COMMITED_TILES);
 
 
 			node->theNodeBefore = bestNode;
 			node->theNodeBefore = bestNode;
 			node->addSpecialAction(std::make_shared<AIPathfinding::TownPortalAction>(targetTown));
 			node->addSpecialAction(std::make_shared<AIPathfinding::TownPortalAction>(targetTown));

+ 11 - 2
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -41,11 +41,19 @@ namespace AIPathfinding
 	const int CHAIN_MAX_DEPTH = 4;
 	const int CHAIN_MAX_DEPTH = 4;
 }
 }
 
 
+enum DayFlags : ui8
+{
+	NONE = 0,
+	FLY_CAST = 1,
+	WATER_WALK_CAST = 2
+};
+
 struct AIPathNode : public CGPathNode
 struct AIPathNode : public CGPathNode
 {
 {
 	uint64_t danger;
 	uint64_t danger;
 	uint64_t armyLoss;
 	uint64_t armyLoss;
-	int32_t manaCost;
+	int16_t manaCost;
+	DayFlags dayFlags;
 	const AIPathNode * chainOther;
 	const AIPathNode * chainOther;
 	std::shared_ptr<const SpecialAction> specialAction;
 	std::shared_ptr<const SpecialAction> specialAction;
 	const ChainActor * actor;
 	const ChainActor * actor;
@@ -200,7 +208,8 @@ public:
 		EPathNodeAction action,
 		EPathNodeAction action,
 		int turn,
 		int turn,
 		int movementLeft,
 		int movementLeft,
-		float cost) const;
+		float cost,
+		bool saveToCommited = true) const;
 
 
 	inline const AIPathNode * getAINode(const CGPathNode * node) const
 	inline const AIPathNode * getAINode(const CGPathNode * node) const
 	{
 	{

+ 8 - 7
AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.cpp

@@ -22,18 +22,18 @@ namespace NKAI
 
 
 namespace AIPathfinding
 namespace AIPathfinding
 {
 {
-	AdventureCastAction::AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero)
-		:spellToCast(spellToCast), hero(hero)
+	AdventureCastAction::AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero, DayFlags flagsToAdd)
+		:spellToCast(spellToCast), hero(hero), flagsToAdd(flagsToAdd)
 	{
 	{
 		manaCost = hero->getSpellCost(spellToCast.toSpell());
 		manaCost = hero->getSpellCost(spellToCast.toSpell());
 	}
 	}
 
 
 	WaterWalkingAction::WaterWalkingAction(const CGHeroInstance * hero)
 	WaterWalkingAction::WaterWalkingAction(const CGHeroInstance * hero)
-		:AdventureCastAction(SpellID::WATER_WALK, hero)
+		:AdventureCastAction(SpellID::WATER_WALK, hero, DayFlags::WATER_WALK_CAST)
 	{ }
 	{ }
 
 
 	AirWalkingAction::AirWalkingAction(const CGHeroInstance * hero)
 	AirWalkingAction::AirWalkingAction(const CGHeroInstance * hero)
-		: AdventureCastAction(SpellID::FLY, hero)
+		: AdventureCastAction(SpellID::FLY, hero, DayFlags::FLY_CAST)
 	{
 	{
 	}
 	}
 
 
@@ -41,11 +41,12 @@ namespace AIPathfinding
 		const CGHeroInstance * hero,
 		const CGHeroInstance * hero,
 		CDestinationNodeInfo & destination,
 		CDestinationNodeInfo & destination,
 		const PathNodeInfo & source,
 		const PathNodeInfo & source,
-		AIPathNode * dstMode,
+		AIPathNode * dstNode,
 		const AIPathNode * srcNode) const
 		const AIPathNode * srcNode) const
 	{
 	{
-		dstMode->manaCost = srcNode->manaCost + manaCost;
-		dstMode->theNodeBefore = source.node;
+		dstNode->manaCost = srcNode->manaCost + manaCost;
+		dstNode->theNodeBefore = source.node;
+		dstNode->dayFlags = static_cast<DayFlags>(dstNode->dayFlags | flagsToAdd);
 	}
 	}
 
 
 	void AdventureCastAction::execute(const CGHeroInstance * hero) const
 	void AdventureCastAction::execute(const CGHeroInstance * hero) const

+ 2 - 1
AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h

@@ -24,9 +24,10 @@ namespace AIPathfinding
 		SpellID spellToCast;
 		SpellID spellToCast;
 		const CGHeroInstance * hero;
 		const CGHeroInstance * hero;
 		int manaCost;
 		int manaCost;
+		DayFlags flagsToAdd;
 
 
 	public:
 	public:
-		AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero);
+		AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero, DayFlags flagsToAdd = DayFlags::NONE);
 
 
 		virtual void execute(const CGHeroInstance * hero) const override;
 		virtual void execute(const CGHeroInstance * hero) const override;
 
 

+ 6 - 6
AI/Nullkiller/Pathfinding/Actors.cpp

@@ -327,7 +327,7 @@ ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other)
 			return result;
 			return result;
 		}
 		}
 
 
-		HeroActor * exchanged = new HeroActor(actor, other, newArmy, ai);
+		auto * exchanged = new HeroActor(actor, other, newArmy, ai);
 
 
 		exchanged->armyCost += newArmy->armyCost;
 		exchanged->armyCost += newArmy->armyCost;
 		result.actor = exchanged;
 		result.actor = exchanged;
@@ -342,7 +342,7 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
 	const CGObjectInstance * upgrader,
 	const CGObjectInstance * upgrader,
 	TResources resources) const
 	TResources resources) const
 {
 {
-	HeroExchangeArmy * target = new HeroExchangeArmy();
+	auto * target = new HeroExchangeArmy();
 	auto upgradeInfo = ai->armyManager->calculateCreaturesUpgrade(army, upgrader, resources);
 	auto upgradeInfo = ai->armyManager->calculateCreaturesUpgrade(army, upgrader, resources);
 
 
 	if(upgradeInfo.upgradeValue)
 	if(upgradeInfo.upgradeValue)
@@ -373,10 +373,10 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
 
 
 		for(auto & creatureToBuy : buyArmy)
 		for(auto & creatureToBuy : buyArmy)
 		{
 		{
-			auto targetSlot = target->getSlotFor(dynamic_cast<const CCreature*>(creatureToBuy.cre));
+			auto targetSlot = target->getSlotFor(creatureToBuy.creID.toCreature());
 
 
 			target->addToSlot(targetSlot, creatureToBuy.creID, creatureToBuy.count);
 			target->addToSlot(targetSlot, creatureToBuy.creID, creatureToBuy.count);
-			target->armyCost += creatureToBuy.cre->getFullRecruitCost() * creatureToBuy.count;
+			target->armyCost += creatureToBuy.creID.toCreature()->getFullRecruitCost() * creatureToBuy.count;
 			target->requireBuyArmy = true;
 			target->requireBuyArmy = true;
 		}
 		}
 	}
 	}
@@ -393,7 +393,7 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
 
 
 HeroExchangeArmy * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const
 HeroExchangeArmy * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const
 {
 {
-	HeroExchangeArmy * target = new HeroExchangeArmy();
+	auto * target = new HeroExchangeArmy();
 	auto bestArmy = ai->armyManager->getBestArmy(actor->hero, army1, army2);
 	auto bestArmy = ai->armyManager->getBestArmy(actor->hero, army1, army2);
 
 
 	for(auto & slotInfo : bestArmy)
 	for(auto & slotInfo : bestArmy)
@@ -445,7 +445,7 @@ std::string DwellingActor::toString() const
 
 
 CCreatureSet * DwellingActor::getDwellingCreatures(const CGDwelling * dwelling, bool waitForGrowth)
 CCreatureSet * DwellingActor::getDwellingCreatures(const CGDwelling * dwelling, bool waitForGrowth)
 {
 {
-	CCreatureSet * dwellingCreatures = new CCreatureSet();
+	auto * dwellingCreatures = new CCreatureSet();
 
 
 	for(auto & creatureInfo : dwelling->creatures)
 	for(auto & creatureInfo : dwelling->creatures)
 	{
 	{

+ 1 - 1
AI/Nullkiller/Pathfinding/Actors.h

@@ -31,7 +31,7 @@ public:
 	virtual bool needsLastStack() const override;
 	virtual bool needsLastStack() const override;
 	std::shared_ptr<SpecialAction> getActorAction() const;
 	std::shared_ptr<SpecialAction> getActorAction() const;
 
 
-	HeroExchangeArmy(): CArmedInstance(true), requireBuyArmy(false) {}
+	HeroExchangeArmy(): CArmedInstance(nullptr, true), requireBuyArmy(false) {}
 };
 };
 
 
 struct ExchangeResult
 struct ExchangeResult

+ 12 - 0
AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp

@@ -61,6 +61,12 @@ namespace AIPathfinding
 
 
 		if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::WATER)
 		if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::WATER)
 		{
 		{
+			if(nodeStorage->getAINode(source.node)->dayFlags & DayFlags::WATER_WALK_CAST)
+			{
+				destination.blocked = false;
+				return;
+			}
+
 			auto action = waterWalkingActions.find(nodeStorage->getHero(source.node));
 			auto action = waterWalkingActions.find(nodeStorage->getHero(source.node));
 
 
 			if(action != waterWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL))
 			if(action != waterWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL))
@@ -73,6 +79,12 @@ namespace AIPathfinding
 
 
 		if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::AIR)
 		if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::AIR)
 		{
 		{
+			if(nodeStorage->getAINode(source.node)->dayFlags & DayFlags::FLY_CAST)
+			{
+				destination.blocked = false;
+				return;
+			}
+
 			auto action = airWalkingActions.find(nodeStorage->getHero(source.node));
 			auto action = airWalkingActions.find(nodeStorage->getHero(source.node));
 
 
 			if(action != airWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL))
 			if(action != airWalkingActions.end() && tryUseSpecialAction(destination, source, action->second, EPathNodeAction::NORMAL))

+ 15 - 13
AI/StupidAI/StupidAI.cpp

@@ -15,8 +15,7 @@
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/battle/BattleAction.h"
 #include "../../lib/battle/BattleAction.h"
 #include "../../lib/battle/BattleInfo.h"
 #include "../../lib/battle/BattleInfo.h"
-
-static std::shared_ptr<CBattleCallback> cbc;
+#include "../../lib/CRandomGenerator.h"
 
 
 CStupidAI::CStupidAI()
 CStupidAI::CStupidAI()
 	: side(-1)
 	: side(-1)
@@ -41,7 +40,7 @@ void CStupidAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
 {
 {
 	print("init called, saving ptr to IBattleCallback");
 	print("init called, saving ptr to IBattleCallback");
 	env = ENV;
 	env = ENV;
-	cbc = cb = CB;
+	cb = CB;
 
 
 	wasWaitingForRealize = CB->waitTillRealize;
 	wasWaitingForRealize = CB->waitTillRealize;
 	wasUnlockingGs = CB->unlockGsWhenWaiting;
 	wasUnlockingGs = CB->unlockGsWhenWaiting;
@@ -68,15 +67,16 @@ class EnemyInfo
 {
 {
 public:
 public:
 	const CStack * s;
 	const CStack * s;
-	int adi, adr;
+	int adi;
+	int adr;
 	std::vector<BattleHex> attackFrom; //for melee fight
 	std::vector<BattleHex> attackFrom; //for melee fight
 	EnemyInfo(const CStack * _s) : s(_s), adi(0), adr(0)
 	EnemyInfo(const CStack * _s) : s(_s), adi(0), adr(0)
 	{}
 	{}
-	void calcDmg(const BattleID & battleID, const CStack * ourStack)
+	void calcDmg(std::shared_ptr<CBattleCallback> cb, const BattleID & battleID, const CStack * ourStack)
 	{
 	{
 		// FIXME: provide distance info for Jousting bonus
 		// FIXME: provide distance info for Jousting bonus
 		DamageEstimation retal;
 		DamageEstimation retal;
-		DamageEstimation dmg = cbc->getBattle(battleID)->battleEstimateDamage(ourStack, s, 0, &retal);
+		DamageEstimation dmg = cb->getBattle(battleID)->battleEstimateDamage(ourStack, s, 0, &retal);
 		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);
 	}
 	}
@@ -92,14 +92,14 @@ bool isMoreProfitable(const EnemyInfo &ei1, const EnemyInfo& ei2)
 	return (ei1.adi-ei1.adr) < (ei2.adi - ei2.adr);
 	return (ei1.adi-ei1.adr) < (ei2.adi - ei2.adr);
 }
 }
 
 
-static bool willSecondHexBlockMoreEnemyShooters(const BattleID & battleID, const BattleHex &h1, const BattleHex &h2)
+static bool willSecondHexBlockMoreEnemyShooters(std::shared_ptr<CBattleCallback> cb, const BattleID & battleID, const BattleHex &h1, const BattleHex &h2)
 {
 {
 	int shooters[2] = {0}; //count of shooters on hexes
 	int shooters[2] = {0}; //count of shooters on hexes
 
 
 	for(int i = 0; i < 2; i++)
 	for(int i = 0; i < 2; i++)
 	{
 	{
 		for (auto & neighbour : (i ? h2 : h1).neighbouringTiles())
 		for (auto & neighbour : (i ? h2 : h1).neighbouringTiles())
-			if(const auto * s = cbc->getBattle(battleID)->battleGetUnitByPos(neighbour))
+			if(const auto * s = cb->getBattle(battleID)->battleGetUnitByPos(neighbour))
 				if(s->isShooter())
 				if(s->isShooter())
 					shooters[i]++;
 					shooters[i]++;
 	}
 	}
@@ -117,7 +117,9 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
 	//boost::this_thread::sleep_for(boost::chrono::seconds(2));
 	//boost::this_thread::sleep_for(boost::chrono::seconds(2));
 	print("activeStack called for " + stack->nodeName());
 	print("activeStack called for " + stack->nodeName());
 	ReachabilityInfo dists = cb->getBattle(battleID)->getReachability(stack);
 	ReachabilityInfo dists = cb->getBattle(battleID)->getReachability(stack);
-	std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
+	std::vector<EnemyInfo> enemiesShootable;
+	std::vector<EnemyInfo> enemiesReachable;
+	std::vector<EnemyInfo> enemiesUnreachable;
 
 
 	if(stack->creatureId() == CreatureID::CATAPULT)
 	if(stack->creatureId() == CreatureID::CATAPULT)
 	{
 	{
@@ -152,7 +154,7 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
 			{
 			{
 				if(CStack::isMeleeAttackPossible(stack, s, hex))
 				if(CStack::isMeleeAttackPossible(stack, s, hex))
 				{
 				{
-					std::vector<EnemyInfo>::iterator i = std::find(enemiesReachable.begin(), enemiesReachable.end(), s);
+					auto i = std::find(enemiesReachable.begin(), enemiesReachable.end(), s);
 					if(i == enemiesReachable.end())
 					if(i == enemiesReachable.end())
 					{
 					{
 						enemiesReachable.push_back(s);
 						enemiesReachable.push_back(s);
@@ -169,10 +171,10 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
 	}
 	}
 
 
 	for ( auto & enemy : enemiesReachable )
 	for ( auto & enemy : enemiesReachable )
-		enemy.calcDmg(battleID, stack);
+		enemy.calcDmg(cb, battleID, stack);
 
 
 	for ( auto & enemy : enemiesShootable )
 	for ( auto & enemy : enemiesShootable )
-		enemy.calcDmg(battleID, stack);
+		enemy.calcDmg(cb, battleID, stack);
 
 
 	if(enemiesShootable.size())
 	if(enemiesShootable.size())
 	{
 	{
@@ -183,7 +185,7 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
 	else if(enemiesReachable.size())
 	else if(enemiesReachable.size())
 	{
 	{
 		const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable);
 		const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable);
-		BattleHex targetHex = *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), [&](auto a, auto b) { return willSecondHexBlockMoreEnemyShooters(battleID, a, b);});
+		BattleHex targetHex = *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), [&](auto a, auto b) { return willSecondHexBlockMoreEnemyShooters(cb, battleID, a, b);});
 
 
 		cb->battleMakeUnitAction(battleID, BattleAction::makeMeleeAttack(stack, ei.s->getPosition(), targetHex));
 		cb->battleMakeUnitAction(battleID, BattleAction::makeMeleeAttack(stack, ei.s->getPosition(), targetHex));
 		return;
 		return;

+ 2 - 2
AI/VCAI/AIUtility.h

@@ -70,7 +70,7 @@ public:
 	bool validAndSet() const;
 	bool validAndSet() const;
 
 
 
 
-	template<typename Handler> void serialize(Handler & h, const int version)
+	template<typename Handler> void serialize(Handler & h)
 	{
 	{
 		h & this->h;
 		h & this->h;
 		h & hid;
 		h & hid;
@@ -102,7 +102,7 @@ struct ObjectIdRef
 	bool operator<(const ObjectIdRef & rhs) const;
 	bool operator<(const ObjectIdRef & rhs) const;
 
 
 
 
-	template<typename Handler> void serialize(Handler & h, const int version)
+	template<typename Handler> void serialize(Handler & h)
 	{
 	{
 		h & id;
 		h & id;
 	}
 	}

+ 19 - 20
AI/VCAI/BuildingManager.cpp

@@ -139,17 +139,17 @@ void BuildingManager::setAI(VCAI * AI)
 //Set of buildings for different goals. Does not include any prerequisites.
 //Set of buildings for different goals. Does not include any prerequisites.
 static const std::vector<BuildingID> essential = { BuildingID::TAVERN, BuildingID::TOWN_HALL };
 static const std::vector<BuildingID> essential = { BuildingID::TAVERN, BuildingID::TOWN_HALL };
 static const std::vector<BuildingID> basicGoldSource = { BuildingID::TOWN_HALL, BuildingID::CITY_HALL };
 static const std::vector<BuildingID> basicGoldSource = { BuildingID::TOWN_HALL, BuildingID::CITY_HALL };
+static const std::vector<BuildingID> defence = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE };
 static const std::vector<BuildingID> capitolAndRequirements = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::CAPITOL };
 static const std::vector<BuildingID> capitolAndRequirements = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::CAPITOL };
 static const std::vector<BuildingID> unitsSource = { BuildingID::DWELL_LVL_1, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3,
 static const std::vector<BuildingID> unitsSource = { BuildingID::DWELL_LVL_1, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3,
 BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7 };
 BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7 };
 static const std::vector<BuildingID> unitsUpgrade = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP,
 static const std::vector<BuildingID> unitsUpgrade = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP,
 BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP };
 BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP };
-static const std::vector<BuildingID> unitGrowth = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1,
-BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR };
+static const std::vector<BuildingID> unitGrowth = { BuildingID::HORDE_1, BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR };
 static const std::vector<BuildingID> _spells = { BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
 static const std::vector<BuildingID> _spells = { BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
 BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5 };
 BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5 };
-static const std::vector<BuildingID> extra = { BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3,
-BuildingID::SPECIAL_4, BuildingID::SHIPYARD }; // all remaining buildings
+static const std::vector<BuildingID> extra = { BuildingID::MARKETPLACE, BuildingID::BLACKSMITH, BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, 
+BuildingID::SPECIAL_3, BuildingID::SPECIAL_4, BuildingID::SHIPYARD }; // all remaining buildings
 
 
 bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
 bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
 {
 {
@@ -172,33 +172,32 @@ bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
 	if(tryBuildAnyStructure(t, essential))
 	if(tryBuildAnyStructure(t, essential))
 		return true;
 		return true;
 
 
-	//the more gold the better and less problems later //TODO: what about building mage guild / marketplace etc. with city hall disabled in editor?
-	if(tryBuildNextStructure(t, basicGoldSource))
-		return true;
-
-	//workaround for mantis #2696 - build capitol with separate algorithm if it is available
-	if(vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) && getMaxPossibleGoldBuilding(t) == BuildingID::CAPITOL)
+	if (cb->getDate(Date::DAY_OF_WEEK) < 5) // first 4 days of week - try to focus on dwellings
 	{
 	{
-		if(tryBuildNextStructure(t, capitolAndRequirements))
+		if (tryBuildNextStructure(t, unitsSource, 4))
 			return true;
 			return true;
 	}
 	}
 
 
-	if(!t->hasBuilt(BuildingID::FORT)) //in vast majority of situations fort is top priority building if we already have city hall, TODO: unite with unitGrowth building chain
-		if(tryBuildThisStructure(t, BuildingID::FORT))
+	if (cb->getDate(Date::DAY_OF_WEEK) > 4) // last 3 days of week - try to focus on growth by building Fort/Citadel/Castle
+	{
+		if (tryBuildNextStructure(t, defence, 3))
 			return true;
 			return true;
+	}
 
 
-
-
-	if (cb->getDate(Date::DAY_OF_WEEK) > 6) // last 2 days of week - try to focus on growth
+	if (t->hasBuilt(BuildingID::CASTLE))
 	{
 	{
-		if (tryBuildNextStructure(t, unitGrowth, 2))
+		if (tryBuildAnyStructure(t, unitGrowth))
 			return true;
 			return true;
 	}
 	}
 
 
-	//try building dwellings
-	if (t->hasBuilt(BuildingID::FORT))
+	//try to make City Hall
+	if (tryBuildNextStructure(t, basicGoldSource))
+		return true;
+
+	//workaround for mantis #2696 - build capitol with separate algorithm if it is available
+	if(vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) && getMaxPossibleGoldBuilding(t) == BuildingID::CAPITOL)
 	{
 	{
-		if (tryBuildAnyStructure(t, unitsSource, 8 - cb->getDate(Date::DAY_OF_WEEK)))
+		if(tryBuildNextStructure(t, capitolAndRequirements))
 			return true;
 			return true;
 	}
 	}
 
 

+ 3 - 1
AI/VCAI/FuzzyEngines.cpp

@@ -37,7 +37,9 @@ void engineBase::addRule(const std::string & txt)
 
 
 struct armyStructure
 struct armyStructure
 {
 {
-	float walkers, shooters, flyers;
+	float walkers;
+	float shooters;
+	float flyers;
 	ui32 maxSpeed;
 	ui32 maxSpeed;
 };
 };
 
 

+ 8 - 2
AI/VCAI/FuzzyEngines.h

@@ -38,8 +38,14 @@ public:
 	TacticalAdvantageEngine();
 	TacticalAdvantageEngine();
 	float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us
 	float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us
 private:
 private:
-	fl::InputVariable * ourWalkers, *ourShooters, *ourFlyers, *enemyWalkers, *enemyShooters, *enemyFlyers;
-	fl::InputVariable * ourSpeed, *enemySpeed;
+	fl::InputVariable * ourWalkers;
+	fl::InputVariable * ourShooters;
+	fl::InputVariable * ourFlyers;
+	fl::InputVariable * enemyWalkers;
+	fl::InputVariable * enemyShooters;
+	fl::InputVariable * enemyFlyers;
+	fl::InputVariable * ourSpeed;
+	fl::InputVariable * enemySpeed;
 	fl::InputVariable * bankPresent;
 	fl::InputVariable * bankPresent;
 	fl::InputVariable * castleWalls;
 	fl::InputVariable * castleWalls;
 	fl::OutputVariable * threat;
 	fl::OutputVariable * threat;

+ 1 - 1
AI/VCAI/FuzzyHelper.cpp

@@ -72,7 +72,7 @@ ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
 
 
 	ui64 totalStrength = 0;
 	ui64 totalStrength = 0;
 	ui8 totalChance = 0;
 	ui8 totalChance = 0;
-	for(auto config : bankInfo->getPossibleGuards())
+	for(auto config : bankInfo->getPossibleGuards(bank->cb))
 	{
 	{
 		totalStrength += config.second.totalStrength * config.first;
 		totalStrength += config.second.totalStrength * config.first;
 		totalChance += config.first;
 		totalChance += config.first;

+ 1 - 1
AI/VCAI/Goals/AbstractGoal.h

@@ -176,7 +176,7 @@ namespace Goals
 			return !(*this == g);
 			return !(*this == g);
 		}
 		}
 
 
-		template<typename Handler> void serialize(Handler & h, const int version)
+		template<typename Handler> void serialize(Handler & h)
 		{
 		{
 			h & goalType;
 			h & goalType;
 			h & isElementar;
 			h & isElementar;

+ 1 - 1
AI/VCAI/Goals/CGoal.h

@@ -69,7 +69,7 @@ namespace Goals
 
 
 			return ptr;
 			return ptr;
 		}
 		}
-		template<typename Handler> void serialize(Handler & h, const int version)
+		template<typename Handler> void serialize(Handler & h)
 		{
 		{
 			h & static_cast<AbstractGoal &>(*this);
 			h & static_cast<AbstractGoal &>(*this);
 			//h & goalType & isElementar & isAbstract & priority;
 			//h & goalType & isElementar & isAbstract & priority;

+ 2 - 1
AI/VCAI/Goals/CollectRes.cpp

@@ -169,7 +169,8 @@ TSubgoal CollectRes::whatToDoToTrade()
 		{
 		{
 			if (i.getNum() == resID)
 			if (i.getNum() == resID)
 				continue;
 				continue;
-			int toGive = -1, toReceive = -1;
+			int toGive = -1;
+			int toReceive = -1;
 			m->getOffer(i, resID, toGive, toReceive, EMarketMode::RESOURCE_RESOURCE);
 			m->getOffer(i, resID, toGive, toReceive, EMarketMode::RESOURCE_RESOURCE);
 			assert(toGive > 0 && toReceive > 0);
 			assert(toGive > 0 && toReceive > 0);
 			howManyCanWeBuy += toReceive * (ai->ah->freeResources()[i] / toGive);
 			howManyCanWeBuy += toReceive * (ai->ah->freeResources()[i] / toGive);

+ 2 - 2
AI/VCAI/Goals/CompleteQuest.cpp

@@ -103,7 +103,7 @@ std::string CompleteQuest::questToString() const
 		return "inactive quest";
 		return "inactive quest";
 
 
 	MetaString ms;
 	MetaString ms;
-	q.quest->getRolloverText(ms, false);
+	q.quest->getRolloverText(q.obj->cb, ms, false);
 
 
 	return ms.toString();
 	return ms.toString();
 }
 }
@@ -241,7 +241,7 @@ TGoalVec CompleteQuest::missionDestroyObj() const
 {
 {
 	TGoalVec solutions;
 	TGoalVec solutions;
 
 
-	auto obj = cb->getObjByQuestIdentifier(q.quest->killTarget);
+	auto obj = cb->getObj(q.quest->killTarget);
 
 
 	if(!obj)
 	if(!obj)
 		return ai->ah->howToVisitObj(q.obj);
 		return ai->ah->howToVisitObj(q.obj);

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

@@ -321,9 +321,7 @@ bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNode
 
 
 bool AINodeStorage::isTileAccessible(const int3 & pos, const EPathfindingLayer layer) const
 bool AINodeStorage::isTileAccessible(const int3 & pos, const EPathfindingLayer layer) const
 {
 {
-	const AIPathNode & node = nodes[layer][pos.z][pos.x][pos.y][0];
-
-	return node.action != EPathNodeAction::UNKNOWN;
+	return nodes[layer][pos.z][pos.x][pos.y][0].action != EPathNodeAction::UNKNOWN;
 }
 }
 
 
 std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand) const
 std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand) const

+ 2 - 2
AI/VCAI/ResourceManager.h

@@ -28,7 +28,7 @@ struct DLL_EXPORT ResourceObjective
 	Goals::TSubgoal goal; //what for (build, gather army etc...)
 	Goals::TSubgoal goal; //what for (build, gather army etc...)
 
 
 	 //TODO: register?
 	 //TODO: register?
-	template<typename Handler> void serializeInternal(Handler & h, const int version)
+	template<typename Handler> void serializeInternal(Handler & h)
 	{
 	{
 		h & resources;
 		h & resources;
 		//h & goal; //FIXME: goal serialization is broken
 		//h & goal; //FIXME: goal serialization is broken
@@ -105,7 +105,7 @@ private:
 	void dumpToLog() const;
 	void dumpToLog() const;
 
 
 	//TODO: register?
 	//TODO: register?
-	template<typename Handler> void serializeInternal(Handler & h, const int version)
+	template<typename Handler> void serializeInternal(Handler & h)
 	{
 	{
 		h & saving;
 		h & saving;
 		h & queue;
 		h & queue;

+ 16 - 26
AI/VCAI/VCAI.cpp

@@ -22,7 +22,6 @@
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/GameSettings.h"
 #include "../../lib/GameSettings.h"
 #include "../../lib/gameState/CGameState.h"
 #include "../../lib/gameState/CGameState.h"
-#include "../../lib/bonuses/CBonusSystemNode.h"
 #include "../../lib/bonuses/Limiters.h"
 #include "../../lib/bonuses/Limiters.h"
 #include "../../lib/bonuses/Updaters.h"
 #include "../../lib/bonuses/Updaters.h"
 #include "../../lib/bonuses/Propagators.h"
 #include "../../lib/bonuses/Propagators.h"
@@ -66,7 +65,7 @@ struct SetGlobalState
 };
 };
 
 
 
 
-#define SET_GLOBAL_STATE(ai) SetGlobalState _hlpSetState(ai);
+#define SET_GLOBAL_STATE(ai) SetGlobalState _hlpSetState(ai)
 
 
 #define NET_EVENT_HANDLER SET_GLOBAL_STATE(this)
 #define NET_EVENT_HANDLER SET_GLOBAL_STATE(this)
 #define MAKING_TURN SET_GLOBAL_STATE(this)
 #define MAKING_TURN SET_GLOBAL_STATE(this)
@@ -104,7 +103,7 @@ void VCAI::heroMoved(const TryMoveHero & details, bool verbose)
 	validateObject(details.id);
 	validateObject(details.id);
 	auto hero = cb->getHero(details.id);
 	auto hero = cb->getHero(details.id);
 
 
-	const int3 from = hero ? hero->convertToVisitablePos(details.start) : (details.start - int3(0,1,0));;
+	const int3 from = hero ? hero->convertToVisitablePos(details.start) : (details.start - int3(0,1,0));
 	const int3 to   = hero ? hero->convertToVisitablePos(details.end)   : (details.end   - int3(0,1,0));
 	const int3 to   = hero ? hero->convertToVisitablePos(details.end)   : (details.end   - int3(0,1,0));
 
 
 	const CGObjectInstance * o1 = vstd::frontOrNull(cb->getVisitableObjs(from, verbose));
 	const CGObjectInstance * o1 = vstd::frontOrNull(cb->getVisitableObjs(from, verbose));
@@ -312,7 +311,8 @@ void VCAI::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, Q
 
 
 	requestActionASAP([=]()
 	requestActionASAP([=]()
 	{
 	{
-		float goalpriority1 = 0, goalpriority2 = 0;
+		float goalpriority1 = 0;
+		float goalpriority2 = 0;
 
 
 		auto firstGoal = getGoal(firstHero);
 		auto firstGoal = getGoal(firstHero);
 		if(firstGoal->goalType == Goals::GATHER_ARMY)
 		if(firstGoal->goalType == Goals::GATHER_ARMY)
@@ -746,9 +746,8 @@ void VCAI::showMapObjectSelectDialog(QueryID askID, const Component & icon, cons
 	requestActionASAP([=](){ answerQuery(askID, selectedObject.getNum()); });
 	requestActionASAP([=](){ answerQuery(askID, selectedObject.getNum()); });
 }
 }
 
 
-void VCAI::saveGame(BinarySerializer & h, const int version)
+void VCAI::saveGame(BinarySerializer & h)
 {
 {
-	LOG_TRACE_PARAMS(logAi, "version '%i'", version);
 	NET_EVENT_HANDLER;
 	NET_EVENT_HANDLER;
 	validateVisitableObjs();
 	validateVisitableObjs();
 
 
@@ -756,21 +755,20 @@ void VCAI::saveGame(BinarySerializer & h, const int version)
 	//disabled due to issue 2890
 	//disabled due to issue 2890
 	registerGoals(h);
 	registerGoals(h);
 	#endif // 0
 	#endif // 0
-	CAdventureAI::saveGame(h, version);
-	serializeInternal(h, version);
+	CAdventureAI::saveGame(h);
+	serializeInternal(h);
 }
 }
 
 
-void VCAI::loadGame(BinaryDeserializer & h, const int version)
+void VCAI::loadGame(BinaryDeserializer & h)
 {
 {
-	LOG_TRACE_PARAMS(logAi, "version '%i'", version);
 	//NET_EVENT_HANDLER;
 	//NET_EVENT_HANDLER;
 
 
 	#if 0
 	#if 0
 	//disabled due to issue 2890
 	//disabled due to issue 2890
 	registerGoals(h);
 	registerGoals(h);
 	#endif // 0
 	#endif // 0
-	CAdventureAI::loadGame(h, version);
-	serializeInternal(h, version);
+	CAdventureAI::loadGame(h);
+	serializeInternal(h);
 }
 }
 
 
 void makePossibleUpgrades(const CArmedInstance * obj)
 void makePossibleUpgrades(const CArmedInstance * obj)
@@ -1415,8 +1413,8 @@ void VCAI::wander(HeroPtr h)
 			auto compareReinforcements = [&](const CGTownInstance * lhs, const CGTownInstance * rhs) -> bool
 			auto compareReinforcements = [&](const CGTownInstance * lhs, const CGTownInstance * rhs) -> bool
 			{
 			{
 				const CGHeroInstance * hptr = h.get();
 				const CGHeroInstance * hptr = h.get();
-				auto r1 = ah->howManyReinforcementsCanGet(hptr, lhs),
-					r2 = ah->howManyReinforcementsCanGet(hptr, rhs);
+				auto r1 = ah->howManyReinforcementsCanGet(hptr, lhs);
+				auto r2 = ah->howManyReinforcementsCanGet(hptr, rhs);
 				if (r1 != r2)
 				if (r1 != r2)
 					return r1 < r2;
 					return r1 < r2;
 				else
 				else
@@ -1610,15 +1608,6 @@ void VCAI::battleEnd(const BattleID & battleID, const BattleResult * br, QueryID
 	logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.toString(), (won ? "won" : "lost"), battlename);
 	logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.toString(), (won ? "won" : "lost"), battlename);
 	battlename.clear();
 	battlename.clear();
 
 
-	if (queryID != QueryID::NONE)
-	{
-		status.addQuery(queryID, "Combat result dialog");
-		const int confirmAction = 0;
-		requestActionASAP([=]()
-		{
-			answerQuery(queryID, confirmAction);
-		});
-	}
 	CAdventureAI::battleEnd(battleID, br, queryID);
 	CAdventureAI::battleEnd(battleID, br, queryID);
 }
 }
 
 
@@ -2154,7 +2143,8 @@ void VCAI::tryRealize(Goals::Trade & g) //trade
 				if(res.getNum() == g.resID) //sell any other resource
 				if(res.getNum() == g.resID) //sell any other resource
 					continue;
 					continue;
 
 
-				int toGive, toGet;
+				int toGive;
+				int toGet;
 				m->getOffer(res, g.resID, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
 				m->getOffer(res, g.resID, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
 				toGive = static_cast<int>(toGive * (it->resVal / toGive)); //round down
 				toGive = static_cast<int>(toGive * (it->resVal / toGive)); //round down
 				//TODO trade only as much as needed
 				//TODO trade only as much as needed
@@ -2221,8 +2211,8 @@ void VCAI::tryRealize(Goals::BuyArmy & g)
 			*boost::max_element(creaturesInDwellings, [](const creInfo & lhs, const creInfo & rhs)
 			*boost::max_element(creaturesInDwellings, [](const creInfo & lhs, const creInfo & rhs)
 		{
 		{
 			//max value of creatures we can buy with our res
 			//max value of creatures we can buy with our res
-			int value1 = lhs.cre->getAIValue() * lhs.count,
-				value2 = rhs.cre->getAIValue() * rhs.count;
+			int value1 = lhs.cre->getAIValue() * lhs.count;
+			int value2 = rhs.cre->getAIValue() * rhs.count;
 
 
 			return value1 < value2;
 			return value1 < value2;
 		});
 		});

+ 5 - 5
AI/VCAI/VCAI.h

@@ -68,7 +68,7 @@ public:
 	void heroVisit(const CGObjectInstance * obj, bool started);
 	void heroVisit(const CGObjectInstance * obj, bool started);
 
 
 
 
-	template<typename Handler> void serialize(Handler & h, const int version)
+	template<typename Handler> void serialize(Handler & h)
 	{
 	{
 		h & battle;
 		h & battle;
 		h & remainingQueries;
 		h & remainingQueries;
@@ -152,8 +152,8 @@ public:
 	void showGarrisonDialog(const CArmedInstance * up, const CGHeroInstance * down, bool removableUnits, QueryID queryID) override; //all stacks operations between these objects become allowed, interface has to call onEnd when done
 	void showGarrisonDialog(const CArmedInstance * up, const CGHeroInstance * down, bool removableUnits, QueryID queryID) override; //all stacks operations between these objects become allowed, interface has to call onEnd when done
 	void showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
 	void showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
 	void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
 	void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
-	void saveGame(BinarySerializer & h, const int version) override; //saving
-	void loadGame(BinaryDeserializer & h, const int version) override; //loading
+	void saveGame(BinarySerializer & h) override; //saving
+	void loadGame(BinaryDeserializer & h) override; //loading
 	void finish() override;
 	void finish() override;
 
 
 	void availableCreaturesChanged(const CGDwelling * town) override;
 	void availableCreaturesChanged(const CGDwelling * town) override;
@@ -301,7 +301,7 @@ public:
 	}
 	}
 	#endif
 	#endif
 
 
-	template<typename Handler> void serializeInternal(Handler & h, const int version)
+	template<typename Handler> void serializeInternal(Handler & h)
 	{
 	{
 		h & knownTeleportChannels;
 		h & knownTeleportChannels;
 		h & knownSubterraneanGates;
 		h & knownSubterraneanGates;
@@ -341,7 +341,7 @@ public:
 							//we have to explicitly ignore invalid goal class type id
 							//we have to explicitly ignore invalid goal class type id
 							h & typeId;
 							h & typeId;
 							Goals::AbstractGoal ignored2;
 							Goals::AbstractGoal ignored2;
-							ignored2.serialize(h, version);
+							ignored2.serialize(h);
 						}
 						}
 					}
 					}
 				}
 				}

+ 3 - 2
CCallback.cpp

@@ -16,6 +16,7 @@
 #include "client/Client.h"
 #include "client/Client.h"
 #include "lib/mapping/CMap.h"
 #include "lib/mapping/CMap.h"
 #include "lib/mapObjects/CGHeroInstance.h"
 #include "lib/mapObjects/CGHeroInstance.h"
+#include "lib/mapObjects/CGTownInstance.h"
 #include "lib/CBuildingHandler.h"
 #include "lib/CBuildingHandler.h"
 #include "lib/CGeneralTextHandler.h"
 #include "lib/CGeneralTextHandler.h"
 #include "lib/CHeroHandler.h"
 #include "lib/CHeroHandler.h"
@@ -260,12 +261,12 @@ void CCallback::setFormation(const CGHeroInstance * hero, EArmyFormation mode)
 	sendRequest(&pack);
 	sendRequest(&pack);
 }
 }
 
 
-void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero)
+void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero)
 {
 {
 	assert(townOrTavern);
 	assert(townOrTavern);
 	assert(hero);
 	assert(hero);
 
 
-	HireHero pack(hero->getHeroType(), townOrTavern->id);
+	HireHero pack(hero->getHeroType(), townOrTavern->id, nextHero);
 	pack.player = *player;
 	pack.player = *player;
 	sendRequest(&pack);
 	sendRequest(&pack);
 }
 }

+ 2 - 2
CCallback.h

@@ -73,7 +73,7 @@ public:
 	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
 
 
 	//town
 	//town
-	virtual void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero)=0;
+	virtual void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero=HeroTypeID::NONE)=0;
 	virtual bool buildBuilding(const CGTownInstance *town, BuildingID buildingID)=0;
 	virtual bool buildBuilding(const CGTownInstance *town, BuildingID buildingID)=0;
 	virtual void recruitCreatures(const CGDwelling *obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1)=0;
 	virtual void recruitCreatures(const CGDwelling *obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1)=0;
 	virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made
 	virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made
@@ -185,7 +185,7 @@ public:
 	void trade(const IMarket * market, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override;
 	void trade(const IMarket * market, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override;
 	void trade(const IMarket * market, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr) override;
 	void trade(const IMarket * market, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr) override;
 	void setFormation(const CGHeroInstance * hero, EArmyFormation mode) override;
 	void setFormation(const CGHeroInstance * hero, EArmyFormation mode) override;
-	void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero) override;
+	void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero=HeroTypeID::NONE) override;
 	void save(const std::string &fname) override;
 	void save(const std::string &fname) override;
 	void sendMessage(const std::string &mess, const CGObjectInstance * currentObject = nullptr) override;
 	void sendMessage(const std::string &mess, const CGObjectInstance * currentObject = nullptr) override;
 	void gamePause(bool pause) override;
 	void gamePause(bool pause) override;

+ 45 - 32
CMakeLists.txt

@@ -74,33 +74,45 @@ option(ENABLE_MULTI_PROCESS_BUILDS "Enable /MP flag for MSVS solution" ON)
 option(COPY_CONFIG_ON_BUILD "Copies config folder into output directory at building phase" ON)
 option(COPY_CONFIG_ON_BUILD "Copies config folder into output directory at building phase" ON)
 option(ENABLE_STATIC_AI_LIBS "Add AI code into VCMI lib directly" ${staticAI})
 option(ENABLE_STATIC_AI_LIBS "Add AI code into VCMI lib directly" ${staticAI})
 
 
+option(ENABLE_COLORIZED_COMPILER_OUTPUT "Colorize compilation output (Clang/GNU)." ON)
+
+if(ENABLE_COLORIZED_COMPILER_OUTPUT)
+	if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+		add_compile_options(-fcolor-diagnostics)
+	elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+		add_compile_options(-fdiagnostics-color=always)
+	endif()
+endif()
+
 # Used for Snap packages and also useful for debugging
 # Used for Snap packages and also useful for debugging
 if(NOT APPLE_IOS AND NOT ANDROID)
 if(NOT APPLE_IOS AND NOT ANDROID)
 	option(ENABLE_MONOLITHIC_INSTALL "Install everything in single directory on Linux and Mac" OFF)
 	option(ENABLE_MONOLITHIC_INSTALL "Install everything in single directory on Linux and Mac" OFF)
 endif()
 endif()
 
 
-# On Linux, use -DCMAKE_CXX_COMPILER_LAUNCHER=ccache instead.
-# The XCode and MSVC builds each require some more configuration, which is enabled by the following option:
-if(MSVC OR (CMAKE_GENERATOR STREQUAL "Xcode"))
-  option(ENABLE_CCACHE "Speed up recompilation by caching previous compilations" OFF)
+option(ENABLE_CCACHE "Speed up recompilation by caching previous compilations" OFF)
+if(ENABLE_CCACHE)
+	find_program(CCACHE ccache REQUIRED)
+endif()
+
+# The XCode and MSVC builds each require some more configuration further down.
+if(ENABLE_CCACHE)
+	set(CMAKE_C_COMPILER_LAUNCHER "ccache")
+	set(CMAKE_CXX_COMPILER_LAUNCHER "ccache")
 endif()
 endif()
 
 
 if(ENABLE_CCACHE AND (CMAKE_GENERATOR STREQUAL "Xcode"))
 if(ENABLE_CCACHE AND (CMAKE_GENERATOR STREQUAL "Xcode"))
-	find_program(CCACHE ccache REQUIRED)
-	if(CCACHE)
-		# https://stackoverflow.com/a/36515503/2278742
-		# Set up wrapper scripts
-		configure_file(xcode/launch-c.in   xcode/launch-c)
-		configure_file(xcode/launch-cxx.in xcode/launch-cxx)
-		execute_process(COMMAND chmod a+rx
-												"${CMAKE_BINARY_DIR}/xcode/launch-c"
-												"${CMAKE_BINARY_DIR}/xcode/launch-cxx")
-		# Set Xcode project attributes to route compilation through our scripts
-		set(CMAKE_XCODE_ATTRIBUTE_CC         	"${CMAKE_BINARY_DIR}/xcode/launch-c")
-		set(CMAKE_XCODE_ATTRIBUTE_CXX        	"${CMAKE_BINARY_DIR}/xcode/launch-cxx")
-		set(CMAKE_XCODE_ATTRIBUTE_LD         	"${CMAKE_BINARY_DIR}/xcode/launch-c")
-		set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS 	"${CMAKE_BINARY_DIR}/xcode/launch-cxx")
-	endif()
+	# https://stackoverflow.com/a/36515503/2278742
+	# Set up wrapper scripts
+	configure_file(xcode/launch-c.in   xcode/launch-c)
+	configure_file(xcode/launch-cxx.in xcode/launch-cxx)
+	execute_process(COMMAND chmod a+rx
+											"${CMAKE_BINARY_DIR}/xcode/launch-c"
+											"${CMAKE_BINARY_DIR}/xcode/launch-cxx")
+	# Set Xcode project attributes to route compilation through our scripts
+	set(CMAKE_XCODE_ATTRIBUTE_CC         	"${CMAKE_BINARY_DIR}/xcode/launch-c")
+	set(CMAKE_XCODE_ATTRIBUTE_CXX        	"${CMAKE_BINARY_DIR}/xcode/launch-cxx")
+	set(CMAKE_XCODE_ATTRIBUTE_LD         	"${CMAKE_BINARY_DIR}/xcode/launch-c")
+	set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS 	"${CMAKE_BINARY_DIR}/xcode/launch-cxx")
 endif()
 endif()
 
 
 # Allow to pass package name from Travis CI
 # Allow to pass package name from Travis CI
@@ -266,19 +278,16 @@ if(MINGW OR MSVC)
 	if(MSVC)
 	if(MSVC)
 		if(ENABLE_CCACHE)
 		if(ENABLE_CCACHE)
 			# https://github.com/ccache/ccache/discussions/1154#discussioncomment-3611387
 			# https://github.com/ccache/ccache/discussions/1154#discussioncomment-3611387
-			find_program(CCACHE ccache REQUIRED)
-			if (CCACHE)
-				file(COPY_FILE
-					${CCACHE} ${CMAKE_BINARY_DIR}/cl.exe
-					ONLY_IF_DIFFERENT)
-
-				set(CMAKE_VS_GLOBALS
-					"CLToolExe=cl.exe"
-					"CLToolPath=${CMAKE_BINARY_DIR}"
-					"TrackFileAccess=false"
-					"UseMultiToolTask=true"
-				)
-			endif()
+			file(COPY_FILE
+				${CCACHE} ${CMAKE_BINARY_DIR}/cl.exe
+				ONLY_IF_DIFFERENT)
+
+			set(CMAKE_VS_GLOBALS
+				"CLToolExe=cl.exe"
+				"CLToolPath=${CMAKE_BINARY_DIR}"
+				"TrackFileAccess=false"
+				"UseMultiToolTask=true"
+			)
 		endif()
 		endif()
 
 
 		add_definitions(-DBOOST_ALL_NO_LIB)
 		add_definitions(-DBOOST_ALL_NO_LIB)
@@ -806,6 +815,10 @@ elseif(APPLE_MACOS AND NOT ENABLE_MONOLITHIC_INSTALL)
 	set(CPACK_MONOLITHIC_INSTALL 1)
 	set(CPACK_MONOLITHIC_INSTALL 1)
 	set(CPACK_GENERATOR "DragNDrop")
 	set(CPACK_GENERATOR "DragNDrop")
 	set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_SOURCE_DIR}/osx/dmg_background.png")
 	set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_SOURCE_DIR}/osx/dmg_background.png")
+	# Workaround for this issue:
+	# CPack Error: Error executing: /usr/bin/hdiutil detach "/Volumes/VCMI"
+	# https://github.com/actions/runner-images/issues/7522#issuecomment-1564467252
+	set(CPACK_COMMAND_HDIUTIL "/usr/bin/sudo /usr/bin/hdiutil")
 	# CMake code for CPACK_DMG_DS_STORE executed before DS_STORE_SETUP_SCRIPT
 	# CMake code for CPACK_DMG_DS_STORE executed before DS_STORE_SETUP_SCRIPT
 	# So we can keep both enabled and this shouldn't hurt
 	# So we can keep both enabled and this shouldn't hurt
 	# set(CPACK_DMG_DS_STORE "${CMAKE_SOURCE_DIR}/osx/dmg_DS_Store")
 	# set(CPACK_DMG_DS_STORE "${CMAKE_SOURCE_DIR}/osx/dmg_DS_Store")

+ 47 - 0
CMakePresets.json

@@ -65,6 +65,15 @@
                 "CMAKE_CXX_COMPILER": "/usr/bin/clang++"
                 "CMAKE_CXX_COMPILER": "/usr/bin/clang++"
             }
             }
         },
         },
+        {
+            "name": "linux-clang-release-ccache",
+            "displayName": "Clang x86_64-pc-linux-gnu with ccache",
+            "description": "VCMI Linux Clang with ccache",
+            "inherits": "linux-release",
+            "cacheVariables": {
+                "ENABLE_CCACHE": "ON"
+            }
+        },
         {
         {
             "name": "linux-gcc-release",
             "name": "linux-gcc-release",
             "displayName": "GCC x86_64-pc-linux-gnu",
             "displayName": "GCC x86_64-pc-linux-gnu",
@@ -76,6 +85,15 @@
                 "CMAKE_CXX_COMPILER": "/usr/bin/g++"
                 "CMAKE_CXX_COMPILER": "/usr/bin/g++"
             }
             }
         },
         },
+        {
+            "name": "linux-gcc-release-ccache",
+            "displayName": "GCC x86_64-pc-linux-gnu with ccache",
+            "description": "VCMI Linux GCC with ccache",
+            "inherits": "linux-release",
+            "cacheVariables": {
+                "ENABLE_CCACHE": "ON"
+            }
+        },
         {
         {
             "name": "linux-gcc-debug",
             "name": "linux-gcc-debug",
             "displayName": "GCC x86_64-pc-linux-gnu (debug)",
             "displayName": "GCC x86_64-pc-linux-gnu (debug)",
@@ -109,6 +127,15 @@
                 "CMAKE_CXX_COMPILER": "/usr/bin/g++"
                 "CMAKE_CXX_COMPILER": "/usr/bin/g++"
             }
             }
         },
         },
+        {
+            "name": "windows-mingw-release",
+            "displayName": "Windows x64 MinGW Release",
+            "description": "VCMI Windows Ninja using MinGW",
+            "inherits": "default-release",
+            "cacheVariables": {
+                "CMAKE_BUILD_TYPE": "Release"
+            }
+        },
         {
         {
             "name": "windows-msvc-release",
             "name": "windows-msvc-release",
             "displayName": "Windows x64 RelWithDebInfo",
             "displayName": "Windows x64 RelWithDebInfo",
@@ -284,6 +311,11 @@
             "configurePreset": "linux-clang-release",
             "configurePreset": "linux-clang-release",
             "inherits": "default-release"
             "inherits": "default-release"
         },
         },
+        {
+            "name": "linux-clang-release-ccache",
+            "configurePreset": "linux-clang-release-ccache",
+            "inherits": "linux-clang-release"
+        },
         {
         {
             "name": "linux-clang-test",
             "name": "linux-clang-test",
             "configurePreset": "linux-clang-test",
             "configurePreset": "linux-clang-test",
@@ -299,6 +331,11 @@
             "configurePreset": "linux-gcc-release",
             "configurePreset": "linux-gcc-release",
             "inherits": "default-release"
             "inherits": "default-release"
         },
         },
+        {
+            "name": "linux-gcc-release-ccache",
+            "configurePreset": "linux-gcc-release-ccache",
+            "inherits": "linux-gcc-release"
+        },
         {
         {
             "name": "linux-gcc-debug",
             "name": "linux-gcc-debug",
             "configurePreset": "linux-gcc-debug",
             "configurePreset": "linux-gcc-debug",
@@ -324,6 +361,11 @@
             "configurePreset": "macos-arm-conan-ninja-release",
             "configurePreset": "macos-arm-conan-ninja-release",
             "inherits": "default-release"
             "inherits": "default-release"
         },
         },
+        {
+            "name": "windows-mingw-release",
+            "configurePreset": "windows-mingw-release",
+            "inherits": "default-release"
+        },
         {
         {
             "name": "windows-msvc-release",
             "name": "windows-msvc-release",
             "configurePreset": "windows-msvc-release",
             "configurePreset": "windows-msvc-release",
@@ -410,6 +452,11 @@
             "configurePreset": "macos-ninja-release",
             "configurePreset": "macos-ninja-release",
             "inherits": "default-release"
             "inherits": "default-release"
         },
         },
+        {
+            "name": "windows-mingw-release",
+            "configurePreset": "windows-mingw-release",
+            "inherits": "default-release"
+        },
         {
         {
             "name": "windows-msvc-release",
             "name": "windows-msvc-release",
             "configurePreset": "windows-msvc-release",
             "configurePreset": "windows-msvc-release",

+ 84 - 1
ChangeLog.md

@@ -1,3 +1,81 @@
+# 1.4.5 -> 1.5.0
+
+### General
+* Added option to disable cheats in game
+
+### Interface
+* Town Portal dialog will now show town icons
+* Town Portal dialog will now show town info on right click
+* Town Portal dialog will center on town on clicking it
+* Town Portal dialog now uses same town ordering as in adventure map interface
+* Heroes can now be recruited from the tavern by double-clicking on them
+* Added status bar to the backpack window
+* Quick backpack window is now only available when enabled Interface enhancements
+* Fixed assembly of artifacts in the backpack when backpack is full
+* Attempt to use enemy turn replay feature will now show "Not implemented" message
+
+### Campaigns
+* Game will now correctly track who defeated the hero or wandering monsters for related quests and victory conditions
+* Birth of a Barbarian: Yog will now start the third scenario with Angelic Alliance in his inventory
+* Birth of a Barbarian: Heroes with Angelic Alliance components are now considered to be mission-critical and can't be dismissed or lost in combat
+* Birth of a Barbarian: Yog can no longer purchase spellbook from the Mage Guild
+* Birth of a Barbarian: Yog will no longer gain Spellpower or Knowledge when leveling up
+* Birth of a Barbarian: Scenarios with mission to deliver an artifact will no longer end after just defeating enemies
+* Gem will now have her class set to "Sorceress" in campaigns
+* Fixed missing names for heroes who have their names customized in map after being transferred to the next scenario
+* Artifact transfer will now work correctly if the hero holding the transferable artifact is not also transferring
+* Fixed crash on opening of some campaigns in the French version from gog.com
+* It is now possible to replay the intro movie from the scenario information window
+* When playing the intro video, the subtitles are now correctly synchronized with the audio
+
+### Battles
+* Added option to enable unlimited combat replays during game setup
+* Added option to instantly end battle using quick combat (shotcut: 'e')
+* 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
+* Disabling battle queue will now correctly reposition hero statistics preview popup
+* Fixed positioning of unit stack size label
+
+### Launcher
+* Added Spanish translation to launcher
+
+### Map Editor
+* Added Chinese translation to map editor
+
+### AI
+* Fixed possible crash on updating NKAI pathfinding data
+* Fixed counting mana usage cost of Fly spell
+* Added estimation of value of Pyramid and Cyclops Stockpile
+
+### Modding
+* Added new game setting that allows inviting heroes to taverns
+* Fixed reversed Overlord and Warlock classes mapping
+
+# 1.4.4 -> 1.4.5
+
+### Stability
+* Fixed crash on creature spellcasting
+* Fixed crash on unit entering magical obstacles such as quicksands
+* Fixed freeze on map loading on some systems
+* Fixed crash on attempt to start campaign with unsupported map
+* Fixed crash on opening creature information window with invalid SPELL_IMMUNITY bonus
+
+### Random Maps Generator
+* Fixed placement of guards sometimes resulting into open connection into third zone
+* Fixed rare crash on multithreaded access during placement of artifacts or wandering monsters
+
+### Map Editor
+* Fixed inspector using wrong editor for some values
+
+### AI
+* Fixed bug leading to AI not attacking wandering monsters in some cases
+* Fixed crash on using StupidAI for autocombat or for enemy players 
+
+# 1.4.3 -> 1.4.4
+
+### General
+* Fixed crash on generation of random maps
+
 # 1.4.2 -> 1.4.3
 # 1.4.2 -> 1.4.3
 
 
 ### General
 ### General
@@ -23,6 +101,7 @@
 * Fixed positioning of prologue and epilogue text during campaign scenario intros
 * Fixed positioning of prologue and epilogue text during campaign scenario intros
 
 
 ### Interface
 ### Interface
+* Added an option to hide adventure map window when town or battle window are open
 * Fixed switching between pages on small version of spellbook
 * Fixed switching between pages on small version of spellbook
 * Saves with long filenames are now truncated in the UI to prevent overflow.
 * Saves with long filenames are now truncated in the UI to prevent overflow.
 * Added option to sort saved games by change date
 * Added option to sort saved games by change date
@@ -32,7 +111,6 @@
 * Fixed incorrect cursor display when hovering over water objects accessible from shore
 * Fixed incorrect cursor display when hovering over water objects accessible from shore
 
 
 ### Stability
 ### Stability
-* Fixed possible creation of a duplicate hero in a random map when the player has chosen the starting hero.
 * Fixed a crash when using the 'vcmiobelisk' cheat more than once.
 * Fixed a crash when using the 'vcmiobelisk' cheat more than once.
 * Fixed crash when reaching level 201. The maximum level is now limited to 197.
 * Fixed crash when reaching level 201. The maximum level is now limited to 197.
 * Fixed crash when accessing a spell with an invalid SPELLCASTER bonus
 * Fixed crash when accessing a spell with an invalid SPELLCASTER bonus
@@ -43,6 +121,11 @@
 * Fixed crash on invalid creature in hero army due to outdated or broken mods
 * Fixed crash on invalid creature in hero army due to outdated or broken mods
 * Failure to initialise video subsystem now displays error message instead of silent crash
 * Failure to initialise video subsystem now displays error message instead of silent crash
 
 
+### Random Maps Generator
+* Fixed possible creation of a duplicate hero in a random map when the player has chosen the starting hero.
+* Fixed banning of quest artifacts on random maps
+* Fixed banning of heroes in prison on random maps
+
 ### Battles
 ### Battles
 * Battle turn queue now displays current turn
 * Battle turn queue now displays current turn
 * Added option to show unit statistics sidebar in battle
 * Added option to show unit statistics sidebar in battle

+ 4 - 0
Global.h

@@ -41,6 +41,10 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 #  define VCMI_UNIX
 #  define VCMI_UNIX
 #  define VCMI_XDG
 #  define VCMI_XDG
 #  define VCMI_FREEBSD
 #  define VCMI_FREEBSD
+#elif defined(__OpenBSD__)
+#  define VCMI_UNIX
+#  define VCMI_XDG
+#  define VCMI_OPENBSD
 #elif defined(__HAIKU__)
 #elif defined(__HAIKU__)
 #  define VCMI_UNIX
 #  define VCMI_UNIX
 #  define VCMI_XDG
 #  define VCMI_XDG

+ 17 - 7
Mods/vcmi/config/vcmi/chinese.json

@@ -54,6 +54,8 @@
 	"vcmi.radialWheel.moveDown" : "下移",
 	"vcmi.radialWheel.moveDown" : "下移",
 	"vcmi.radialWheel.moveBottom" : "移到底端",
 	"vcmi.radialWheel.moveBottom" : "移到底端",
 	
 	
+	"vcmi.spellBook.search" : "搜索中...",
+	
 	"vcmi.mainMenu.serverConnecting" : "连接中...",
 	"vcmi.mainMenu.serverConnecting" : "连接中...",
 	"vcmi.mainMenu.serverAddressEnter" : "使用地址:",
 	"vcmi.mainMenu.serverAddressEnter" : "使用地址:",
 	"vcmi.mainMenu.serverConnectionFailed" : "连接失败",
 	"vcmi.mainMenu.serverConnectionFailed" : "连接失败",
@@ -62,13 +64,14 @@
 	"vcmi.mainMenu.joinTCP" : "加入TCP/IP游戏",
 	"vcmi.mainMenu.joinTCP" : "加入TCP/IP游戏",
 	"vcmi.mainMenu.playerName" : "Player",
 	"vcmi.mainMenu.playerName" : "Player",
 
 
-	"vcmi.lobby.filename" : "文件名",
+	"vcmi.lobby.filepath" : "文件路径",
 	"vcmi.lobby.creationDate" : "创建时间",
 	"vcmi.lobby.creationDate" : "创建时间",
 	"vcmi.lobby.scenarioName" : "场景名称",
 	"vcmi.lobby.scenarioName" : "场景名称",
 	"vcmi.lobby.mapPreview" : "地图预览",
 	"vcmi.lobby.mapPreview" : "地图预览",
 	"vcmi.lobby.noPreview" : "无地上部分",
 	"vcmi.lobby.noPreview" : "无地上部分",
 	"vcmi.lobby.noUnderground" : "无地下部分",
 	"vcmi.lobby.noUnderground" : "无地下部分",
 
 
+	"vcmi.client.errors.missingCampaigns" : "{找不到数据文件}\n\n没有找到战役数据文件!你可能使用了不完整或损坏的英雄无敌3数据文件,请重新安装数据文件。",
 	"vcmi.server.errors.existingProcess"     : "一个VCMI进程已经在运行,启动新进程前请结束它。",
 	"vcmi.server.errors.existingProcess"     : "一个VCMI进程已经在运行,启动新进程前请结束它。",
 	"vcmi.server.errors.modsToEnable"    : "{需要启用的mod列表}",
 	"vcmi.server.errors.modsToEnable"    : "{需要启用的mod列表}",
 	"vcmi.server.errors.modsToDisable"   : "{需要禁用的mod列表}",
 	"vcmi.server.errors.modsToDisable"   : "{需要禁用的mod列表}",
@@ -90,9 +93,9 @@
 	"vcmi.systemOptions.townsGroup" : "城镇画面",
 	"vcmi.systemOptions.townsGroup" : "城镇画面",
 
 
 	"vcmi.systemOptions.fullscreenBorderless.hover" : "全屏 (无边框)",
 	"vcmi.systemOptions.fullscreenBorderless.hover" : "全屏 (无边框)",
-	"vcmi.systemOptions.fullscreenBorderless.help"  : "{全屏}\n\n选中时,VCMI将以全屏运行,否则运行在窗口模式。",
-	"vcmi.systemOptions.fullscreenExclusive.hover"  : "全屏 (边框)",
-	"vcmi.systemOptions.fullscreenExclusive.help"   : "{全屏}\n\n选中时,VCMI将以全屏运行,否则运行在窗口模式。",
+	"vcmi.systemOptions.fullscreenBorderless.help"  : "{全屏}\n\n选中时,VCMI将以无边框全屏模式运行。在这种模式下,游戏会使用和桌面一致的分辨率而非设置的分辨率。 ",
+	"vcmi.systemOptions.fullscreenExclusive.hover"  : "全屏 (独占)",
+	"vcmi.systemOptions.fullscreenExclusive.help"   : "{全屏}\n\n选中时,VCMI将以独占全屏模式运行。在这种模式下,游戏会将显示器分辨率改变为设置值。",
 	"vcmi.systemOptions.resolutionButton.hover" : "分辨率",
 	"vcmi.systemOptions.resolutionButton.hover" : "分辨率",
 	"vcmi.systemOptions.resolutionButton.help"  : "{分辨率选择}\n\n改变游戏内的分辨率,更改后需要重启游戏使其生效。",
 	"vcmi.systemOptions.resolutionButton.help"  : "{分辨率选择}\n\n改变游戏内的分辨率,更改后需要重启游戏使其生效。",
 	"vcmi.systemOptions.resolutionMenu.hover"   : "分辨率选择",
 	"vcmi.systemOptions.resolutionMenu.hover"   : "分辨率选择",
@@ -133,6 +136,8 @@
 	"vcmi.adventureOptions.leftButtonDrag.help" : "{左键拖动地图}\n\n启用后,按住左键移动鼠标将拖动冒险地图视图。",
 	"vcmi.adventureOptions.leftButtonDrag.help" : "{左键拖动地图}\n\n启用后,按住左键移动鼠标将拖动冒险地图视图。",
 	"vcmi.adventureOptions.smoothDragging.hover" : "平滑地图拖动",
 	"vcmi.adventureOptions.smoothDragging.hover" : "平滑地图拖动",
 	"vcmi.adventureOptions.smoothDragging.help" : "{平滑地图拖动}\n\n启用后,地图拖动会产生柔和的羽化效果。",
 	"vcmi.adventureOptions.smoothDragging.help" : "{平滑地图拖动}\n\n启用后,地图拖动会产生柔和的羽化效果。",
+	"vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "关闭淡入淡出特效",
+	"vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{关闭淡入淡出特效}\n\n启用后,跳过物体淡出或类似特效(资源收集,登船等)。设置此项能在渲染开销重时能够加快UI的响应,尤其是在PvP对战中。当移动速度被设置为最大时忽略此项设置。",
 	"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed6.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed6.hover": "",
@@ -161,8 +166,6 @@
 	"vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{显示生物射程限制}\n\n当您将鼠标悬停在其上时,显示射手的射程限制。",
 	"vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{显示生物射程限制}\n\n当您将鼠标悬停在其上时,显示射手的射程限制。",
 	"vcmi.battleOptions.showStickyHeroInfoWindows.hover": "显示英雄统计数据窗口",
 	"vcmi.battleOptions.showStickyHeroInfoWindows.hover": "显示英雄统计数据窗口",
 	"vcmi.battleOptions.showStickyHeroInfoWindows.help": "{显示英雄统计数据窗口}\n\n永久切换并显示主要统计数据和法术点的英雄统计数据窗口。",
 	"vcmi.battleOptions.showStickyHeroInfoWindows.help": "{显示英雄统计数据窗口}\n\n永久切换并显示主要统计数据和法术点的英雄统计数据窗口。",
-	"vcmi.battleOptions.enableAutocombatSpells.hover": "魔法",
-	"vcmi.battleOptions.enableAutocombatSpells.help": "{魔法}\n\n快速战斗时不会使用魔法。",
 	"vcmi.battleOptions.skipBattleIntroMusic.hover": "跳过战斗开始音乐",
 	"vcmi.battleOptions.skipBattleIntroMusic.hover": "跳过战斗开始音乐",
 	"vcmi.battleOptions.skipBattleIntroMusic.help": "{跳过战斗开始音乐}\n\n战斗开始音乐播放期间,你也能够进行操作。",
 	"vcmi.battleOptions.skipBattleIntroMusic.help": "{跳过战斗开始音乐}\n\n战斗开始音乐播放期间,你也能够进行操作。",
 	
 	
@@ -234,7 +237,7 @@
 	"vcmi.questLog.hideComplete.hover" : "隐藏完成任务",
 	"vcmi.questLog.hideComplete.hover" : "隐藏完成任务",
 	"vcmi.questLog.hideComplete.help"  : "隐藏所有完成的任务",
 	"vcmi.questLog.hideComplete.help"  : "隐藏所有完成的任务",
 
 
-	"vcmi.randomMapTab.widgets.defaultTemplate"      : "默认",
+	"vcmi.randomMapTab.widgets.randomTemplate"      : "(随机)",
 	"vcmi.randomMapTab.widgets.templateLabel"        : "模板",
 	"vcmi.randomMapTab.widgets.templateLabel"        : "模板",
 	"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "设定...",
 	"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "设定...",
 	"vcmi.randomMapTab.widgets.teamAlignmentsLabel"  : "同盟关系",
 	"vcmi.randomMapTab.widgets.teamAlignmentsLabel"  : "同盟关系",
@@ -302,6 +305,13 @@
 	"vcmi.optionsTab.simturns.months.1" : " %d 月",
 	"vcmi.optionsTab.simturns.months.1" : " %d 月",
 	"vcmi.optionsTab.simturns.months.2" : " %d 月",
 	"vcmi.optionsTab.simturns.months.2" : " %d 月",
 
 
+	"vcmi.optionsTab.extraOptions.hover" : "额外选项",
+	"vcmi.optionsTab.extraOptions.help" : "游戏的额外设置",
+
+	"vcmi.optionsTab.cheatAllowed.hover" : "允许作弊",
+	"vcmi.optionsTab.unlimitedReplay.hover" : "无限战斗回放",
+	"vcmi.optionsTab.cheatAllowed.help" : "{允许作弊}\n允许在游戏过程中输入作弊码。",
+	"vcmi.optionsTab.unlimitedReplay.help" : "{无限战斗回放}\n战斗回放没有任何限制。",
 
 
 	// Custom victory conditions for H3 campaigns and HotA maps
 	// Custom victory conditions for H3 campaigns and HotA maps
 	"vcmi.map.victoryCondition.daysPassed.toOthers" : "敌人依然存活至今,你失败了!",
 	"vcmi.map.victoryCondition.daysPassed.toOthers" : "敌人依然存活至今,你失败了!",

+ 25 - 9
Mods/vcmi/config/vcmi/english.json

@@ -13,13 +13,14 @@
 	"vcmi.adventureMap.monsterThreat.levels.10" : "Deadly",
 	"vcmi.adventureMap.monsterThreat.levels.10" : "Deadly",
 	"vcmi.adventureMap.monsterThreat.levels.11" : "Impossible",
 	"vcmi.adventureMap.monsterThreat.levels.11" : "Impossible",
 
 
-	"vcmi.adventureMap.confirmRestartGame"     : "Are you sure you want to restart the game?",
-	"vcmi.adventureMap.noTownWithMarket"       : "There are no available marketplaces!",
-	"vcmi.adventureMap.noTownWithTavern"       : "There are no available towns with taverns!",
-	"vcmi.adventureMap.spellUnknownProblem"    : "There is an unknown problem with this spell! No more information is available.",
-	"vcmi.adventureMap.playerAttacked"         : "Player has been attacked: %s",
-	"vcmi.adventureMap.moveCostDetails"        : "Movement points - Cost: %TURNS turns + %POINTS points, Remaining points: %REMAINING",
-	"vcmi.adventureMap.moveCostDetailsNoTurns" : "Movement points - Cost: %POINTS points, Remaining points: %REMAINING",
+	"vcmi.adventureMap.confirmRestartGame"               : "Are you sure you want to restart the game?",
+	"vcmi.adventureMap.noTownWithMarket"                 : "There are no available marketplaces!",
+	"vcmi.adventureMap.noTownWithTavern"                 : "There are no available towns with taverns!",
+	"vcmi.adventureMap.spellUnknownProblem"              : "There is an unknown problem with this spell! No more information is available.",
+	"vcmi.adventureMap.playerAttacked"                   : "Player has been attacked: %s",
+	"vcmi.adventureMap.moveCostDetails"                  : "Movement points - Cost: %TURNS turns + %POINTS points, Remaining points: %REMAINING",
+	"vcmi.adventureMap.moveCostDetailsNoTurns"           : "Movement points - Cost: %POINTS points, Remaining points: %REMAINING",
+	"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Sorry, replay opponent turn is not implemented yet!",
 
 
 	"vcmi.capitalColors.0" : "Red",
 	"vcmi.capitalColors.0" : "Red",
 	"vcmi.capitalColors.1" : "Blue",
 	"vcmi.capitalColors.1" : "Blue",
@@ -72,6 +73,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.client.errors.invalidMap" : "{Invalid map or campaign}\n\nFailed to start game! Selected map or campaign might be invalid or corrupted. Reason:\n%s",
 	"vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.",
 	"vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.",
 	"vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.",
 	"vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.",
 	"vcmi.server.errors.modsToEnable"    : "{Following mods are required}",
 	"vcmi.server.errors.modsToEnable"    : "{Following mods are required}",
@@ -170,8 +172,10 @@
 	"vcmi.battleOptions.showStickyHeroInfoWindows.hover": "Show heroes statistics windows",
 	"vcmi.battleOptions.showStickyHeroInfoWindows.hover": "Show heroes statistics windows",
 	"vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Show heroes statistics windows}\n\nPermanently toggle on heroes statistics windows that show primary stats and spell points.",
 	"vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Show heroes statistics windows}\n\nPermanently toggle on heroes statistics windows that show primary stats and spell points.",
 	"vcmi.battleOptions.skipBattleIntroMusic.hover": "Skip Intro Music",
 	"vcmi.battleOptions.skipBattleIntroMusic.hover": "Skip Intro Music",
-	"vcmi.battleOptions.skipBattleIntroMusic.help": "{Skip Intro Music}\n\nAllow actions during the intro music that plays at the beginning of each battle.",
-	
+	"vcmi.battleOptions.skipBattleIntroMusic.help": "{Skip Intro Music}\n\nAllow actions during the intro music that plays at the beginning of each battle.",	
+	"vcmi.battleOptions.endWithAutocombat.hover": "Ends battle",
+	"vcmi.battleOptions.endWithAutocombat.help": "{Ends battle}\n\nAuto-Combat plays battle to end instant",
+
 	"vcmi.adventureMap.revisitObject.hover" : "Revisit Object",
 	"vcmi.adventureMap.revisitObject.hover" : "Revisit Object",
 	"vcmi.adventureMap.revisitObject.help" : "{Revisit Object}\n\nIf a hero currently stands on a Map Object, he can revisit the location.",
 	"vcmi.adventureMap.revisitObject.help" : "{Revisit Object}\n\nIf a hero currently stands on a Map Object, he can revisit the location.",
 
 
@@ -190,6 +194,7 @@
 	"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s were killed by accurate shots!",
 	"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.1" : "%d %s was killed with an accurate shot!",
 	"vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s were killed by accurate shots!",
 	"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" : "Apply battle result",
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Apply battle result",
 
 
@@ -232,6 +237,8 @@
 	"vcmi.heroWindow.openBackpack.hover" : "Open artifact backpack window",
 	"vcmi.heroWindow.openBackpack.hover" : "Open artifact backpack window",
 	"vcmi.heroWindow.openBackpack.help"  : "Opens window that allows easier artifact backpack management.",
 	"vcmi.heroWindow.openBackpack.help"  : "Opens window that allows easier artifact backpack management.",
 
 
+	"vcmi.tavernWindow.inviteHero"  : "Invite hero",
+
 	"vcmi.commanderWindow.artifactMessage" : "Do you want to return this artifact to the hero?",
 	"vcmi.commanderWindow.artifactMessage" : "Do you want to return this artifact to the hero?",
 
 
 	"vcmi.creatureWindow.showBonuses.hover"    : "Switch to bonuses view",
 	"vcmi.creatureWindow.showBonuses.hover"    : "Switch to bonuses view",
@@ -312,6 +319,14 @@
 	"vcmi.optionsTab.simturns.months.1" : " %d month",
 	"vcmi.optionsTab.simturns.months.1" : " %d month",
 	"vcmi.optionsTab.simturns.months.2" : " %d months",
 	"vcmi.optionsTab.simturns.months.2" : " %d months",
 
 
+	"vcmi.optionsTab.extraOptions.hover" : "Extra Options",
+	"vcmi.optionsTab.extraOptions.help" : "Additional settings for the game",
+
+	"vcmi.optionsTab.cheatAllowed.hover" : "Allow cheats",
+	"vcmi.optionsTab.unlimitedReplay.hover" : "Unlimited battle replay",
+	"vcmi.optionsTab.cheatAllowed.help" : "{Allow cheats}\nAllows the inputs of cheats during the game.",
+	"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" : "The enemy has managed to survive till this day. Victory is theirs!",
 	"vcmi.map.victoryCondition.daysPassed.toOthers" : "The enemy has managed to survive till this day. Victory is theirs!",
 	"vcmi.map.victoryCondition.daysPassed.toSelf" : "Congratulations! You have managed to survive. Victory is yours!",
 	"vcmi.map.victoryCondition.daysPassed.toSelf" : "Congratulations! You have managed to survive. Victory is yours!",
@@ -320,6 +335,7 @@
 	"vcmi.map.victoryCondition.collectArtifacts.message" : "Acquire Three Artifacts",
 	"vcmi.map.victoryCondition.collectArtifacts.message" : "Acquire Three Artifacts",
 	"vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Congratulations! All your enemies have been defeated and you have Angelic Alliance! Victory is yours!",
 	"vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Congratulations! All your enemies have been defeated and you have Angelic Alliance! Victory is yours!",
 	"vcmi.map.victoryCondition.angelicAlliance.message" : "Defeat All Enemies and create Angelic Alliance",
 	"vcmi.map.victoryCondition.angelicAlliance.message" : "Defeat All Enemies and create Angelic Alliance",
+	"vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "Alas, you have lost part of the Angelic Alliance. All is lost.",
 
 
 	// few strings from WoG used by vcmi
 	// few strings from WoG used by vcmi
 	"vcmi.stackExperience.description" : "» S t a c k   E x p e r i e n c e   D e t a i l s «\n\nCreature Type ................... : %s\nExperience Rank ................. : %s (%i)\nExperience Points ............... : %i\nExperience Points to Next Rank .. : %i\nMaximum Experience per Battle ... : %i%% (%i)\nNumber of Creatures in stack .... : %i\nMaximum New Recruits\n without losing current Rank .... : %i\nExperience Multiplier ........... : %.2f\nUpgrade Multiplier .............. : %.2f\nExperience after Rank 10 ........ : %i\nMaximum New Recruits to remain at\n Rank 10 if at Maximum Experience : %i",
 	"vcmi.stackExperience.description" : "» S t a c k   E x p e r i e n c e   D e t a i l s «\n\nCreature Type ................... : %s\nExperience Rank ................. : %s (%i)\nExperience Points ............... : %i\nExperience Points to Next Rank .. : %i\nMaximum Experience per Battle ... : %i%% (%i)\nNumber of Creatures in stack .... : %i\nMaximum New Recruits\n without losing current Rank .... : %i\nExperience Multiplier ........... : %.2f\nUpgrade Multiplier .............. : %.2f\nExperience after Rank 10 ........ : %i\nMaximum New Recruits to remain at\n Rank 10 if at Maximum Experience : %i",

+ 21 - 7
Mods/vcmi/config/vcmi/german.json

@@ -13,13 +13,14 @@
 	"vcmi.adventureMap.monsterThreat.levels.10" : "Tödlich",
 	"vcmi.adventureMap.monsterThreat.levels.10" : "Tödlich",
 	"vcmi.adventureMap.monsterThreat.levels.11" : "Unmöglich",
 	"vcmi.adventureMap.monsterThreat.levels.11" : "Unmöglich",
 
 
-	"vcmi.adventureMap.confirmRestartGame"     : "Seid Ihr sicher, dass Ihr das Spiel neu starten wollt?",
-	"vcmi.adventureMap.noTownWithMarket"       : "Kein Marktplatz verfügbar!",
-	"vcmi.adventureMap.noTownWithTavern"       : "Keine Stadt mit Taverne verfügbar!",
-	"vcmi.adventureMap.spellUnknownProblem"    : "Unbekanntes Problem mit diesem Zauberspruch, keine weiteren Informationen verfügbar.",
-	"vcmi.adventureMap.playerAttacked"         : "Spieler wurde attackiert: %s",
-	"vcmi.adventureMap.moveCostDetails"        : "Bewegungspunkte - Kosten: %TURNS Runden + %POINTS Punkte, Verbleibende Punkte: %REMAINING",
-	"vcmi.adventureMap.moveCostDetailsNoTurns" : "Bewegungspunkte - Kosten: %POINTS Punkte, Verbleibende Punkte: %REMAINING",
+	"vcmi.adventureMap.confirmRestartGame"               : "Seid Ihr sicher, dass Ihr das Spiel neu starten wollt?",
+	"vcmi.adventureMap.noTownWithMarket"                 : "Kein Marktplatz verfügbar!",
+	"vcmi.adventureMap.noTownWithTavern"                 : "Keine Stadt mit Taverne verfügbar!",
+	"vcmi.adventureMap.spellUnknownProblem"              : "Unbekanntes Problem mit diesem Zauberspruch, keine weiteren Informationen verfügbar.",
+	"vcmi.adventureMap.playerAttacked"                   : "Spieler wurde attackiert: %s",
+	"vcmi.adventureMap.moveCostDetails"                  : "Bewegungspunkte - Kosten: %TURNS Runden + %POINTS Punkte, Verbleibende Punkte: %REMAINING",
+	"vcmi.adventureMap.moveCostDetailsNoTurns"           : "Bewegungspunkte - Kosten: %POINTS Punkte, Verbleibende Punkte: %REMAINING",
+	"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Das Wiederholen des gegnerischen Zuges ist aktuell noch nicht implementiert!",
 
 
 	"vcmi.capitalColors.0" : "Rot",
 	"vcmi.capitalColors.0" : "Rot",
 	"vcmi.capitalColors.1" : "Blau",
 	"vcmi.capitalColors.1" : "Blau",
@@ -171,6 +172,8 @@
 	"vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Statistikfenster für Helden anzeigen}\n\nDauerhaftes Einschalten des Statistikfenster für Helden, das die primären Werte und Zauberpunkte anzeigt.",
 	"vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Statistikfenster für Helden anzeigen}\n\nDauerhaftes Einschalten des Statistikfenster für Helden, das die primären Werte und Zauberpunkte anzeigt.",
 	"vcmi.battleOptions.skipBattleIntroMusic.hover": "Intro-Musik überspringen",
 	"vcmi.battleOptions.skipBattleIntroMusic.hover": "Intro-Musik überspringen",
 	"vcmi.battleOptions.skipBattleIntroMusic.help": "{Intro-Musik überspringen}\n\n Überspringe die kurze Musik, die zu Beginn eines jeden Kampfes gespielt wird, bevor die Action beginnt. Kann auch durch Drücken der ESC-Taste übersprungen werden.",
 	"vcmi.battleOptions.skipBattleIntroMusic.help": "{Intro-Musik überspringen}\n\n Überspringe die kurze Musik, die zu Beginn eines jeden Kampfes gespielt wird, bevor die Action beginnt. Kann auch durch Drücken der ESC-Taste übersprungen werden.",
+	"vcmi.battleOptions.endWithAutocombat.hover": "Kampf beenden",
+	"vcmi.battleOptions.endWithAutocombat.help": "{Kampf beenden}\n\nAutokampf spielt den Kampf sofort zu Ende",
 	
 	
 	"vcmi.adventureMap.revisitObject.hover" : "Objekt erneut besuchen",
 	"vcmi.adventureMap.revisitObject.hover" : "Objekt erneut besuchen",
 	"vcmi.adventureMap.revisitObject.help" : "{Objekt erneut besuchen}\n\nSteht ein Held gerade auf einem Kartenobjekt, kann er den Ort erneut aufsuchen.",
 	"vcmi.adventureMap.revisitObject.help" : "{Objekt erneut besuchen}\n\nSteht ein Held gerade auf einem Kartenobjekt, kann er den Ort erneut aufsuchen.",
@@ -190,6 +193,7 @@
 	"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s wurden durch gezielte Schüsse getötet!",
 	"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s wurden durch gezielte Schüsse getötet!",
 	"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s wurde mit einem gezielten Schuss getötet!",
 	"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s wurde mit einem gezielten Schuss getötet!",
 	"vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s wurden durch gezielte Schüsse getötet!",
 	"vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s wurden durch gezielte Schüsse getötet!",
+	"vcmi.battleWindow.endWithAutocombat" : "Seid Ihr sicher, dass Ihr den Kampf mit Auto-Kampf beenden wollt?",
 
 
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Kampfergebnis übernehmen",
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Kampfergebnis übernehmen",
 
 
@@ -232,6 +236,8 @@
 	"vcmi.heroWindow.openBackpack.hover" : "Artefakt-Rucksack-Fenster öffnen",
 	"vcmi.heroWindow.openBackpack.hover" : "Artefakt-Rucksack-Fenster öffnen",
 	"vcmi.heroWindow.openBackpack.help"  : "Öffnet ein Fenster, das die Verwaltung des Artefakt-Rucksacks erleichtert",
 	"vcmi.heroWindow.openBackpack.help"  : "Öffnet ein Fenster, das die Verwaltung des Artefakt-Rucksacks erleichtert",
 
 
+	"vcmi.tavernWindow.inviteHero"  : "Helden einladen",
+
 	"vcmi.commanderWindow.artifactMessage" : "Möchtet Ihr diesen Artefakt dem Helden zurückgeben?",
 	"vcmi.commanderWindow.artifactMessage" : "Möchtet Ihr diesen Artefakt dem Helden zurückgeben?",
 
 
 	"vcmi.creatureWindow.showBonuses.hover"    : "Wechsle zur Bonus-Ansicht",
 	"vcmi.creatureWindow.showBonuses.hover"    : "Wechsle zur Bonus-Ansicht",
@@ -312,6 +318,14 @@
 	"vcmi.optionsTab.simturns.months.1" : "%d Monat",
 	"vcmi.optionsTab.simturns.months.1" : "%d Monat",
 	"vcmi.optionsTab.simturns.months.2" : "%d Monate",
 	"vcmi.optionsTab.simturns.months.2" : "%d Monate",
 
 
+	"vcmi.optionsTab.extraOptions.hover" : "Extra Optionen",
+	"vcmi.optionsTab.extraOptions.help" : "Zusätzliche Einstellungen für das Spiel",
+	
+	"vcmi.optionsTab.cheatAllowed.hover" : "Cheats erlauben",
+	"vcmi.optionsTab.unlimitedReplay.hover" : "Unbegrenzte Kampfwiederholung",
+	"vcmi.optionsTab.cheatAllowed.help" : "{Cheats erlauben}\nErlaubt die Eingabe von Cheats während des Spiels.",
+	"vcmi.optionsTab.unlimitedReplay.help" : "{Unbegrenzte Kampfwiederholung}\nKämpfe lassen sich unbegrenzt wiederholen.",
+
 	// Custom victory conditions for H3 campaigns and HotA maps
 	// Custom victory conditions for H3 campaigns and HotA maps
 	"vcmi.map.victoryCondition.daysPassed.toOthers" : "Der Feind hat es geschafft, bis zum heutigen Tag zu überleben. Der Sieg gehört ihm!",
 	"vcmi.map.victoryCondition.daysPassed.toOthers" : "Der Feind hat es geschafft, bis zum heutigen Tag zu überleben. Der Sieg gehört ihm!",
 	"vcmi.map.victoryCondition.daysPassed.toSelf" : "Herzlichen Glückwunsch! Ihr habt es geschafft, zu überleben. Der Sieg ist euer!",
 	"vcmi.map.victoryCondition.daysPassed.toSelf" : "Herzlichen Glückwunsch! Ihr habt es geschafft, zu überleben. Der Sieg ist euer!",

+ 86 - 70
Mods/vcmi/config/vcmi/polish.json

@@ -54,6 +54,8 @@
 	"vcmi.radialWheel.moveDown" : "Przenieś w dół",
 	"vcmi.radialWheel.moveDown" : "Przenieś w dół",
 	"vcmi.radialWheel.moveBottom" : "Przenieś na spód",
 	"vcmi.radialWheel.moveBottom" : "Przenieś na spód",
 
 
+	"vcmi.spellBook.search" : "szukaj...",
+
 	"vcmi.mainMenu.serverConnecting" : "Łączenie...",
 	"vcmi.mainMenu.serverConnecting" : "Łączenie...",
 	"vcmi.mainMenu.serverAddressEnter" : "Wprowadź adres:",
 	"vcmi.mainMenu.serverAddressEnter" : "Wprowadź adres:",
 	"vcmi.mainMenu.serverConnectionFailed" : "Połączenie nie powiodło się",
 	"vcmi.mainMenu.serverConnectionFailed" : "Połączenie nie powiodło się",
@@ -68,7 +70,9 @@
 	"vcmi.lobby.mapPreview" : "Podgląd mapy",
 	"vcmi.lobby.mapPreview" : "Podgląd mapy",
 	"vcmi.lobby.noPreview" : "brak podglądu",
 	"vcmi.lobby.noPreview" : "brak podglądu",
 	"vcmi.lobby.noUnderground" : "brak podziemi",
 	"vcmi.lobby.noUnderground" : "brak podziemi",
+	"vcmi.lobby.sortDate" : "Sortuj mapy według daty modyfikacji",
 
 
+	"vcmi.client.errors.missingCampaigns" : "{Brakujące pliki gry}\n\nPliki kampanii nie zostały znalezione! Możliwe że używasz niekompletnych lub uszkodzonych plików Heroes 3. Spróbuj ponownej instalacji plików gry.",
 	"vcmi.server.errors.existingProcess" : "Inny proces 'vcmiserver' został już uruchomiony, zakończ go nim przejdziesz dalej",
 	"vcmi.server.errors.existingProcess" : "Inny proces 'vcmiserver' został już uruchomiony, zakończ go nim przejdziesz dalej",
 	"vcmi.server.errors.modsToEnable"    : "{Następujące mody są wymagane do wczytania gry}",
 	"vcmi.server.errors.modsToEnable"    : "{Następujące mody są wymagane do wczytania gry}",
 	"vcmi.server.errors.modsToDisable"   : "{Następujące mody muszą zostać wyłączone}",
 	"vcmi.server.errors.modsToDisable"   : "{Następujące mody muszą zostać wyłączone}",
@@ -89,7 +93,7 @@
 	"vcmi.systemOptions.otherGroup" : "Inne ustawienia", // unused right now
 	"vcmi.systemOptions.otherGroup" : "Inne ustawienia", // unused right now
 	"vcmi.systemOptions.townsGroup" : "Ekran miasta",
 	"vcmi.systemOptions.townsGroup" : "Ekran miasta",
 
 
-	"vcmi.systemOptions.fullscreenBorderless.hover" : "Pełny ekran (borderless/okno pełnoekranowe)",
+	"vcmi.systemOptions.fullscreenBorderless.hover" : "Pełny ekran (bez ramek)",
 	"vcmi.systemOptions.fullscreenBorderless.help"  : "{Pełny ekran w trybie okna}\n\nPo wybraniu VCMI będzie działać w trybie okna pełnoekranowego. W tym trybie gra będzie zawsze używać rozdzielczości pulpitu, ignorując wybraną rozdzielczość.",
 	"vcmi.systemOptions.fullscreenBorderless.help"  : "{Pełny ekran w trybie okna}\n\nPo wybraniu VCMI będzie działać w trybie okna pełnoekranowego. W tym trybie gra będzie zawsze używać rozdzielczości pulpitu, ignorując wybraną rozdzielczość.",
 	"vcmi.systemOptions.fullscreenExclusive.hover"  : "Pełny ekran (tradycyjny)",
 	"vcmi.systemOptions.fullscreenExclusive.hover"  : "Pełny ekran (tradycyjny)",
 	"vcmi.systemOptions.fullscreenExclusive.help"   : "{Pełny ekran}\n\nPo wybraniu VCMI będzie działać w trybie ekskluzywnego pełnego ekranu. W tym trybie gra zmieni rozdzielczość monitora do wybranej rozdzielczości.",
 	"vcmi.systemOptions.fullscreenExclusive.help"   : "{Pełny ekran}\n\nPo wybraniu VCMI będzie działać w trybie ekskluzywnego pełnego ekranu. W tym trybie gra zmieni rozdzielczość monitora do wybranej rozdzielczości.",
@@ -104,7 +108,7 @@
 	"vcmi.systemOptions.longTouchButton.hover"   : "Czas długiego dotyku: %d ms", // Translation note: "ms" = "milliseconds"
 	"vcmi.systemOptions.longTouchButton.hover"   : "Czas długiego dotyku: %d ms", // Translation note: "ms" = "milliseconds"
 	"vcmi.systemOptions.longTouchButton.help"    : "{Czas długiego dotyku}\n\nPodczas używania ekranu dotykowego, wyskakujące okienka pojawią się po dotknięciu ekranu przez określony czas, w milisekundach",
 	"vcmi.systemOptions.longTouchButton.help"    : "{Czas długiego dotyku}\n\nPodczas używania ekranu dotykowego, wyskakujące okienka pojawią się po dotknięciu ekranu przez określony czas, w milisekundach",
 	"vcmi.systemOptions.longTouchMenu.hover"     : "Wybierz czas długiego dotyku",
 	"vcmi.systemOptions.longTouchMenu.hover"     : "Wybierz czas długiego dotyku",
-	"vcmi.systemOptions.longTouchMenu.help"      : "Zmienia czas wymagany do aktywacji długiego dotyku.",
+	"vcmi.systemOptions.longTouchMenu.help"      : "Nowy czas aktywacji długiego dotyku.",
 	"vcmi.systemOptions.longTouchMenu.entry"     : "%d milisekund",
 	"vcmi.systemOptions.longTouchMenu.entry"     : "%d milisekund",
 	"vcmi.systemOptions.framerateButton.hover"  : "Pokaż FPS",
 	"vcmi.systemOptions.framerateButton.hover"  : "Pokaż FPS",
 	"vcmi.systemOptions.framerateButton.help"   : "{Pokaż FPS}\n\n Przełącza widoczność licznika klatek na sekundę (FPS) w rogu okna gry.",
 	"vcmi.systemOptions.framerateButton.help"   : "{Pokaż FPS}\n\n Przełącza widoczność licznika klatek na sekundę (FPS) w rogu okna gry.",
@@ -114,8 +118,8 @@
 	"vcmi.systemOptions.enableUiEnhancementsButton.help"   : "{Ulepszenia interfejsu}\n\nWłącza różne ulepszenia interfejsu poprawiające wygodę rozgrywki. Takie jak przycisk sakwy bohatera itp. Wyłącz jeśli szukasz bardziej klasycznej wersji gry.",
 	"vcmi.systemOptions.enableUiEnhancementsButton.help"   : "{Ulepszenia interfejsu}\n\nWłącza różne ulepszenia interfejsu poprawiające wygodę rozgrywki. Takie jak przycisk sakwy bohatera itp. Wyłącz jeśli szukasz bardziej klasycznej wersji gry.",
 	"vcmi.systemOptions.enableLargeSpellbookButton.hover"  : "Duża księga zaklęć",
 	"vcmi.systemOptions.enableLargeSpellbookButton.hover"  : "Duża księga zaklęć",
 	"vcmi.systemOptions.enableLargeSpellbookButton.help"   : "{Duża księga zaklęć}\n\nWłącza dużą księgę czarów, która mieści więcej zaklęć na stronę. Animacja zmiany strony nie działa gdy ta opcja jest włączona.",
 	"vcmi.systemOptions.enableLargeSpellbookButton.help"   : "{Duża księga zaklęć}\n\nWłącza dużą księgę czarów, która mieści więcej zaklęć na stronę. Animacja zmiany strony nie działa gdy ta opcja jest włączona.",
-	"vcmi.systemOptions.audioMuteFocus.hover"  : "Wycisz przy zdezaktywowaniu",
-	"vcmi.systemOptions.audioMuteFocus.help"   : "{Wycisz przy zdezaktywowaniu}\n\nWycisz dźwięk gdy okno gry staje się nieaktywne. Wyjątkiem są dźwięki wiadomości i nowej tury.",
+	"vcmi.systemOptions.audioMuteFocus.hover"  : "Wycisz nieaktywną grę",
+	"vcmi.systemOptions.audioMuteFocus.help"   : "{Wycisz nieaktywną grę}\n\nWycisza dźwięk gdy okno gry staje się nieaktywne. Wyjątkiem są dźwięki wiadomości i nowej tury.",
 
 
 	"vcmi.adventureOptions.infoBarPick.hover" : "Pokaż komunikaty w panelu informacyjnym",
 	"vcmi.adventureOptions.infoBarPick.hover" : "Pokaż komunikaty w panelu informacyjnym",
 	"vcmi.adventureOptions.infoBarPick.help" : "{Pokaż komunikaty w panelu informacyjnym}\n\nGdy to możliwe, wiadomości z odwiedzania obiektów będą pokazywane w panelu informacyjnym zamiast w osobnym okienku.",
 	"vcmi.adventureOptions.infoBarPick.help" : "{Pokaż komunikaty w panelu informacyjnym}\n\nGdy to możliwe, wiadomości z odwiedzania obiektów będą pokazywane w panelu informacyjnym zamiast w osobnym okienku.",
@@ -139,6 +143,8 @@
 	"vcmi.adventureOptions.mapScrollSpeed1.help": "Ustaw szybkość przesuwania mapy na bardzo wolną.",
 	"vcmi.adventureOptions.mapScrollSpeed1.help": "Ustaw szybkość przesuwania mapy na bardzo wolną.",
 	"vcmi.adventureOptions.mapScrollSpeed5.help": "Ustaw szybkość przesuwania mapy na bardzo szybką.",
 	"vcmi.adventureOptions.mapScrollSpeed5.help": "Ustaw szybkość przesuwania mapy na bardzo szybką.",
 	"vcmi.adventureOptions.mapScrollSpeed6.help": "Ustaw szybkość przesuwania mapy na błyskawiczną.",
 	"vcmi.adventureOptions.mapScrollSpeed6.help": "Ustaw szybkość przesuwania mapy na błyskawiczną.",
+	"vcmi.adventureOptions.hideBackground.hover" : "Ukryj tło",
+	"vcmi.adventureOptions.hideBackground.help" : "{Ukryj tło}\n\nUkryj mapę przygody w tle i pokaż zastępczo teksturę.",
 
 
 	"vcmi.battleOptions.queueSizeLabel.hover": "Pokaż kolejkę ruchu jednostek",
 	"vcmi.battleOptions.queueSizeLabel.hover": "Pokaż kolejkę ruchu jednostek",
 	"vcmi.battleOptions.queueSizeNoneButton.hover": "BRAK",
 	"vcmi.battleOptions.queueSizeNoneButton.hover": "BRAK",
@@ -178,6 +184,10 @@
 	"vcmi.battleWindow.damageEstimation.damage.1" : "obrażenia: %d",
 	"vcmi.battleWindow.damageEstimation.damage.1" : "obrażenia: %d",
 	"vcmi.battleWindow.damageEstimation.kills" : "zginie: %d",
 	"vcmi.battleWindow.damageEstimation.kills" : "zginie: %d",
 	"vcmi.battleWindow.damageEstimation.kills.1" : "zginie: %d",
 	"vcmi.battleWindow.damageEstimation.kills.1" : "zginie: %d",
+	"vcmi.battleWindow.killed" : "Zabici",
+	"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s zostało zabitych poprzez celne strzały!",
+	"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s został zabity poprzez celny strzał!",
+	"vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s zostali zabici poprzez celne strzały!",
 
 
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Zatwierdź wynik bitwy",
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Zatwierdź wynik bitwy",
 
 
@@ -191,9 +201,9 @@
 	"vcmi.tutorialWindow.decription.AbortSpell" : "Naciśnij i przytrzymaj by anulować rzucenie zaklęcia.",
 	"vcmi.tutorialWindow.decription.AbortSpell" : "Naciśnij i przytrzymaj by anulować rzucenie zaklęcia.",
 
 
 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Pokaż dostępne stworzenia",
 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Pokaż dostępne stworzenia",
-	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Pokaż dostępne stworzenia}\n\n Pokazuje dostępne stworzenia zamiast tygodniowego przyrostu w podsumowaniu miasta (lewy dolny róg).",
+	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Pokaż dostępne stworzenia}\n\n Wyświetla dostępne stworzenia zamiast tygodniowego przyrostu w podsumowaniu miasta (lewy dolny róg).",
 	"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Pokaż tygodniowy przyrost stworzeń",
 	"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Pokaż tygodniowy przyrost stworzeń",
-	"vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Pokaż tygodniowy przyrost stworzeń}\n\n Shows creatures' weekly growth instead of avaialable amount in town summary (lewy dolny r óg).",
+	"vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Pokaż tygodniowy przyrost stworzeń}\n\n Wyświetla tygodniowy przyrost jednostek zamiast dostępnej ilości do zakupu (lewy dolny róg).",
 	"vcmi.otherOptions.compactTownCreatureInfo.hover": "Kompaktowa informacja o stworzeniu",
 	"vcmi.otherOptions.compactTownCreatureInfo.hover": "Kompaktowa informacja o stworzeniu",
 	"vcmi.otherOptions.compactTownCreatureInfo.help": "{Kompaktowa informacja o stworzeniu}\n\n Zmniejszona informacja o stworzeniu w podsumowaniu miasta.",
 	"vcmi.otherOptions.compactTownCreatureInfo.help": "{Kompaktowa informacja o stworzeniu}\n\n Zmniejszona informacja o stworzeniu w podsumowaniu miasta.",
 
 
@@ -239,31 +249,31 @@
 	"vcmi.randomMapTab.widgets.roadTypesLabel"       : "Typy dróg",
 	"vcmi.randomMapTab.widgets.roadTypesLabel"       : "Typy dróg",
 
 
 	"vcmi.optionsTab.turnOptions.hover" : "Ustawienia tur",
 	"vcmi.optionsTab.turnOptions.hover" : "Ustawienia tur",
-	"vcmi.optionsTab.turnOptions.help" : "Ustaw limity czasu (timery) oraz tury równoczesne",
+	"vcmi.optionsTab.turnOptions.help" : "Ustaw limity czasu oraz tury symultaniczne",
 	"vcmi.optionsTab.selectPreset" : "Szablonowe ustawienie",
 	"vcmi.optionsTab.selectPreset" : "Szablonowe ustawienie",
 
 
-	"vcmi.optionsTab.chessFieldBase.hover" : "Timer startowy",
-	"vcmi.optionsTab.chessFieldTurn.hover" : "Timer tury",
-	"vcmi.optionsTab.chessFieldBattle.hover" : "Timer bitwy",
-	"vcmi.optionsTab.chessFieldUnit.hover" : "Timer jednostki",
-	"vcmi.optionsTab.chessFieldBase.help" : "Używany gdy {Timer tury} osiągnie 0. Ustawiany raz przy starcie gry. Gdy osiągnie 0 tura się kończy, a trwająca bitwa zostanie przegrana.",
-	"vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Używany poza bitwą lub gdy {Timer bitwy} się wyczerpie. Odnawia się co turę. Nadwyżka czasu dodaje się do {Timera startowego} pod koniec tury.",
-	"vcmi.optionsTab.chessFieldTurnDiscard.help" : "Używany poza bitwą lub gdy {Timer bitwy} się wyczerpie. Odnawia się co turę. Niewykorzystany czas zostaje utracony.",
-	"vcmi.optionsTab.chessFieldBattle.help" : "Używany w bitwach z graczem AI lub gdy {Timer jednostki} się wyczerpie w bitwie pomiędzy graczami. Odnawia się przy starcie każdej bitwy.",
+	"vcmi.optionsTab.chessFieldBase.hover" : "Zegar startowy",
+	"vcmi.optionsTab.chessFieldTurn.hover" : "Zegar tury",
+	"vcmi.optionsTab.chessFieldBattle.hover" : "Zegar bitewny",
+	"vcmi.optionsTab.chessFieldUnit.hover" : "Zegar jednostki",
+	"vcmi.optionsTab.chessFieldBase.help" : "Odlicza czas gdy {zegar tury} osiągnie 0. Czas nie odnawia się. Koniec czasu oznacza zakończenie tury, a trwająca bitwa zostanie przegrana.",
+	"vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Używany poza bitwą lub gdy {Zegar bitewny} się wyczerpie. Odnawia się co turę. Nadwyżkę dodaje się do {Zegara startowego} pod koniec tury.",
+	"vcmi.optionsTab.chessFieldTurnDiscard.help" : "Używany poza bitwą lub gdy {Zegar bitewny} się wyczerpie. Odnawia się co turę. Niewykorzystany czas zostaje utracony.",
+	"vcmi.optionsTab.chessFieldBattle.help" : "Używany w bitwach z graczem AI lub gdy {Zegar jednostki} się wyczerpie w bitwie pomiędzy ludźmi. Odnawia się przy starcie każdej bitwy.",
 	"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Używany podczas oczekiwania na podjęcie akcji jednostką w bitwie pomiędzy ludźmi. Nadwyżka czasu dodaje się do {Timera bitwy}",
 	"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Używany podczas oczekiwania na podjęcie akcji jednostką w bitwie pomiędzy ludźmi. Nadwyżka czasu dodaje się do {Timera bitwy}",
 	"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Używany podczas oczekiwania na podjęcie akcji jednostką w bitwie pomiędzy ludźmi. Resetuje się przy rozpoczęciu akcji jednostką.",
 	"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Używany podczas oczekiwania na podjęcie akcji jednostką w bitwie pomiędzy ludźmi. Resetuje się przy rozpoczęciu akcji jednostką.",
 
 
 	"vcmi.optionsTab.accumulate" : "Akumuluj",
 	"vcmi.optionsTab.accumulate" : "Akumuluj",
 
 
-	"vcmi.optionsTab.simturnsTitle" : "Tury równoczesne / symultaniczne",
-	"vcmi.optionsTab.simturnsMin.hover" : "Co najmniej przez",
-	"vcmi.optionsTab.simturnsMax.hover" : "Maks. przez",
+	"vcmi.optionsTab.simturnsTitle" : "Tury symultaniczne",
+	"vcmi.optionsTab.simturnsMin.hover" : "Minimum",
+	"vcmi.optionsTab.simturnsMax.hover" : "Maksimum",
 	"vcmi.optionsTab.simturnsAI.hover" : "(Eksperymentalne) Równoczesne tury graczy AI",
 	"vcmi.optionsTab.simturnsAI.hover" : "(Eksperymentalne) Równoczesne tury graczy AI",
 	"vcmi.optionsTab.simturnsMin.help" : "Graj równocześnie przez określoną liczbę dni. Kontakt pomiędzy graczami do tego czasu jest zablokowany.",
 	"vcmi.optionsTab.simturnsMin.help" : "Graj równocześnie przez określoną liczbę dni. Kontakt pomiędzy graczami do tego czasu jest zablokowany.",
 	"vcmi.optionsTab.simturnsMax.help" : "Graj równocześnie przez określoną liczbę dni lub do momentu napotkania innego gracza.",
 	"vcmi.optionsTab.simturnsMax.help" : "Graj równocześnie przez określoną liczbę dni lub do momentu napotkania innego gracza.",
 	"vcmi.optionsTab.simturnsAI.help" : "{Równoczesne tury graczy AI}\nOpcja eksperymentalna. Pozwala graczom sterowanym przez komputer wykonywać akcje w tym samym czasie co gracz ludzki gdy jednoczesne tury są włączone.",
 	"vcmi.optionsTab.simturnsAI.help" : "{Równoczesne tury graczy AI}\nOpcja eksperymentalna. Pozwala graczom sterowanym przez komputer wykonywać akcje w tym samym czasie co gracz ludzki gdy jednoczesne tury są włączone.",
 
 
-	"vcmi.optionsTab.turnTime.select"     : "Predefiniowane schematy zegarów",
+	"vcmi.optionsTab.turnTime.select"     : "Gotowe schematy zegarów",
 	"vcmi.optionsTab.turnTime.unlimited"  : "Nieograniczony czas tury",
 	"vcmi.optionsTab.turnTime.unlimited"  : "Nieograniczony czas tury",
 	"vcmi.optionsTab.turnTime.classic.1"  : "Klasyczny: 1 minuta",
 	"vcmi.optionsTab.turnTime.classic.1"  : "Klasyczny: 1 minuta",
 	"vcmi.optionsTab.turnTime.classic.2"  : "Klasyczny: 2 minuty",
 	"vcmi.optionsTab.turnTime.classic.2"  : "Klasyczny: 2 minuty",
@@ -278,15 +288,15 @@
 	"vcmi.optionsTab.turnTime.chess.2"    : "Szach: 02:00 + 01:00 + 00:15 + 00:00",
 	"vcmi.optionsTab.turnTime.chess.2"    : "Szach: 02:00 + 01:00 + 00:15 + 00:00",
 	"vcmi.optionsTab.turnTime.chess.1"    : "Szach: 01:00 + 01:00 + 00:00 + 00:00",
 	"vcmi.optionsTab.turnTime.chess.1"    : "Szach: 01:00 + 01:00 + 00:00 + 00:00",
 
 
-	"vcmi.optionsTab.simturns.select"         : "Predefiniowane schematy tur sym.",
-	"vcmi.optionsTab.simturns.none"           : "Brak tur symultanicznych / równocz.",
+	"vcmi.optionsTab.simturns.select"         : "Gotowe schematy tur sym.",
+	"vcmi.optionsTab.simturns.none"           : "Brak tur symultanicznych",
 	"vcmi.optionsTab.simturns.tillContactMax" : "Tury sym.: Do kontaktu",
 	"vcmi.optionsTab.simturns.tillContactMax" : "Tury sym.: Do kontaktu",
-	"vcmi.optionsTab.simturns.tillContact1"   : "Tury sym.: 1 tydz., przerw. przy kontakcie",
-	"vcmi.optionsTab.simturns.tillContact2"   : "Tury sym.: 2 tyg., przerw. przy kontakcie",
-	"vcmi.optionsTab.simturns.tillContact4"   : "Tury sym.: 1 mies., przerw. przy kontakcie",
-	"vcmi.optionsTab.simturns.blocked1"       : "Tury sym.: 1 tydz., kontakt zablokowany",
-	"vcmi.optionsTab.simturns.blocked2"       : "Tury sym.: 2 tyg., kontakt zablokowany",
-	"vcmi.optionsTab.simturns.blocked4"       : "Tury sym.: 1 mies., kontakt zablokowany",
+	"vcmi.optionsTab.simturns.tillContact1"   : "Tury sym.: 1 tydz., kontakt: wł.",
+	"vcmi.optionsTab.simturns.tillContact2"   : "Tury sym.: 2 tyg., kontakt: wł.",
+	"vcmi.optionsTab.simturns.tillContact4"   : "Tury sym.: 1 mies., kontakt: wł.",
+	"vcmi.optionsTab.simturns.blocked1"       : "Tury sym.: 1 tydz., kontakt: wył.",
+	"vcmi.optionsTab.simturns.blocked2"       : "Tury sym.: 2 tyg., kontakt: wył.",
+	"vcmi.optionsTab.simturns.blocked4"       : "Tury sym.: 1 mies., kontakt: wył.",
 
 
 	// Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language
 	// Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language
 	// Using this information, VCMI will automatically select correct plural form for every possible amount
 	// Using this information, VCMI will automatically select correct plural form for every possible amount
@@ -312,12 +322,12 @@
 	//WoG strings translations missing here - should be taken from polish WoG translation
 	//WoG strings translations missing here - should be taken from polish WoG translation
 
 
 	"core.bonus.ADDITIONAL_ATTACK.name": "Podwójne Uderzenie",
 	"core.bonus.ADDITIONAL_ATTACK.name": "Podwójne Uderzenie",
-	"core.bonus.ADDITIONAL_ATTACK.description": "Atakuje podwójnie",
-	"core.bonus.ADDITIONAL_RETALIATION.name": "Dodatkowe kontrataki",
-	"core.bonus.ADDITIONAL_RETALIATION.description": "Może kontratakować ${val} dodatkowych razy",
-	"core.bonus.AIR_IMMUNITY.name": "Odporność na powietrze",
+	"core.bonus.ADDITIONAL_ATTACK.description": "Atakuje dwa razy",
+	"core.bonus.ADDITIONAL_RETALIATION.name": "Dodatkowy odwet",
+	"core.bonus.ADDITIONAL_RETALIATION.description": "${val} dodatkowy kontratak",
+	"core.bonus.AIR_IMMUNITY.name": "Odporność: Powietrze",
 	"core.bonus.AIR_IMMUNITY.description": "Odporny na wszystkie czary szkoły powietrza",
 	"core.bonus.AIR_IMMUNITY.description": "Odporny na wszystkie czary szkoły powietrza",
-	"core.bonus.ATTACKS_ALL_ADJACENT.name": "Atakuje wszystko dookoła",
+	"core.bonus.ATTACKS_ALL_ADJACENT.name": "Obrotowy atak",
 	"core.bonus.ATTACKS_ALL_ADJACENT.description": "Atakuje wszystkich sąsiadujących wrogów",
 	"core.bonus.ATTACKS_ALL_ADJACENT.description": "Atakuje wszystkich sąsiadujących wrogów",
 	"core.bonus.BLOCKS_RETALIATION.name": "Bez kontrataku",
 	"core.bonus.BLOCKS_RETALIATION.name": "Bez kontrataku",
 	"core.bonus.BLOCKS_RETALIATION.description": "Wróg nie może kontratakować",
 	"core.bonus.BLOCKS_RETALIATION.description": "Wróg nie może kontratakować",
@@ -325,11 +335,11 @@
 	"core.bonus.BLOCKS_RANGED_RETALIATION.description": "Wróg nie może kontratakować poprzez strzelanie",
 	"core.bonus.BLOCKS_RANGED_RETALIATION.description": "Wróg nie może kontratakować poprzez strzelanie",
 	"core.bonus.CATAPULT.name": "Katapulta",
 	"core.bonus.CATAPULT.name": "Katapulta",
 	"core.bonus.CATAPULT.description": "Atakuje mury obronne",
 	"core.bonus.CATAPULT.description": "Atakuje mury obronne",
-	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Zmniejsz koszt czarów (${val})",
-	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Zmniejsza koszt czaru bohatera",
-	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Tłumienie magii (${val})",
-	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Zwiększa koszt wrogich czarów",
-	"core.bonus.CHARGE_IMMUNITY.name": "Odporność na szarżę",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Profesja magii",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Zmniejsza o ${val} koszt czarów bohatera",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Tłumienie magii",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Zwiększa o ${val} koszt wrogich czarów",
+	"core.bonus.CHARGE_IMMUNITY.name": "Odporność: Szarża",
 	"core.bonus.CHARGE_IMMUNITY.description": "Odporny na szarżę czempionów",
 	"core.bonus.CHARGE_IMMUNITY.description": "Odporny na szarżę czempionów",
 	"core.bonus.DARKNESS.name": "Całun ciemności",
 	"core.bonus.DARKNESS.name": "Całun ciemności",
 	"core.bonus.DARKNESS.description": "Generuje ${val} wartości promienia mgły wojny",
 	"core.bonus.DARKNESS.description": "Generuje ${val} wartości promienia mgły wojny",
@@ -343,15 +353,17 @@
 	"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "${val}% szans na podwójne obrażenia",
 	"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "${val}% szans na podwójne obrażenia",
 	"core.bonus.DRAGON_NATURE.name": "Smok",
 	"core.bonus.DRAGON_NATURE.name": "Smok",
 	"core.bonus.DRAGON_NATURE.description": "Stworzenie posiada smoczą naturę",
 	"core.bonus.DRAGON_NATURE.description": "Stworzenie posiada smoczą naturę",
-	"core.bonus.EARTH_IMMUNITY.name": "Odporność na ziemię",
+	"core.bonus.EARTH_IMMUNITY.name": "Odporność: Ziemia",
 	"core.bonus.EARTH_IMMUNITY.description": "Odporny na wszystkie czary szkoły ziemi",
 	"core.bonus.EARTH_IMMUNITY.description": "Odporny na wszystkie czary szkoły ziemi",
 	"core.bonus.ENCHANTER.name": "Czarodziej",
 	"core.bonus.ENCHANTER.name": "Czarodziej",
-	"core.bonus.ENCHANTER.description": "Może rzucać masowy czar ${subtype.spell} każdej tury",
+	"core.bonus.ENCHANTER.description": "Rzuca czar ${subtype.spell}",
 	"core.bonus.ENCHANTED.name": "Zaczarowany",
 	"core.bonus.ENCHANTED.name": "Zaczarowany",
-	"core.bonus.ENCHANTED.description": "Pod wpływem trwałego ${subtype.spell}",
-	"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignoruje Obronę (${val}%)",
-	"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Ignoruje część obrony podczas ataku",
-	"core.bonus.FIRE_IMMUNITY.name": "Odporność na ogień",
+	"core.bonus.ENCHANTED.description": "Pod trwałym wpływem czaru ${subtype.spell}",
+	"core.bonus.ENEMY_ATTACK_REDUCTION.name": "Ignoruje Atak (${val}%)",
+	"core.bonus.ENEMY_ATTACK_REDUCTION.description": "Przy zostaniu zaatakowanym ignoruje ${val}% ataku wroga",
+	"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Osłabienie Obrony (${val}%)",
+	"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Osłabia obronę wroga podczas ataku",
+	"core.bonus.FIRE_IMMUNITY.name": "Odporność: Ogień",
 	"core.bonus.FIRE_IMMUNITY.description": "Odporny na wszystkie czary szkoły ognia",
 	"core.bonus.FIRE_IMMUNITY.description": "Odporny na wszystkie czary szkoły ognia",
 	"core.bonus.FIRE_SHIELD.name": "Ognista tarcza (${val}%)",
 	"core.bonus.FIRE_SHIELD.name": "Ognista tarcza (${val}%)",
 	"core.bonus.FIRE_SHIELD.description": "Odbija część obrażeń z walki wręcz",
 	"core.bonus.FIRE_SHIELD.description": "Odbija część obrażeń z walki wręcz",
@@ -361,26 +373,28 @@
 	"core.bonus.FEAR.description": "Wzbudza strach na wrogim stworzeniu",
 	"core.bonus.FEAR.description": "Wzbudza strach na wrogim stworzeniu",
 	"core.bonus.FEARLESS.name": "Nieustraszony",
 	"core.bonus.FEARLESS.name": "Nieustraszony",
 	"core.bonus.FEARLESS.description": "Odporny na strach",
 	"core.bonus.FEARLESS.description": "Odporny na strach",
+	"core.bonus.FEROCITY.name": "Dzikość",
+	"core.bonus.FEROCITY.description": "Dodatkowe ${val} ataków jeżeli zabito kogokolwiek",
 	"core.bonus.FLYING.name": "Lot",
 	"core.bonus.FLYING.name": "Lot",
 	"core.bonus.FLYING.description": "Może latać (ignoruje przeszkody)",
 	"core.bonus.FLYING.description": "Może latać (ignoruje przeszkody)",
 	"core.bonus.FREE_SHOOTING.name": "Bliski Strzał",
 	"core.bonus.FREE_SHOOTING.name": "Bliski Strzał",
 	"core.bonus.FREE_SHOOTING.description": "Może strzelać w zasięgu walki wręcz",
 	"core.bonus.FREE_SHOOTING.description": "Może strzelać w zasięgu walki wręcz",
 	"core.bonus.GARGOYLE.name": "Gargulec",
 	"core.bonus.GARGOYLE.name": "Gargulec",
-	"core.bonus.GARGOYLE.description": "Nie może być wskrzeszony lub uleczony",
-	"core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Zmniejsz obrażenia (${val}%)",
+	"core.bonus.GARGOYLE.description": "Nie może się wskrzesić i uleczyć",
+	"core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Redukcja obrażeń (${val}%)",
 	"core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Zmiejsza obrażenia fizyczne z dystansu lub walki wręcz",
 	"core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Zmiejsza obrażenia fizyczne z dystansu lub walki wręcz",
-	"core.bonus.HATE.name": "Nienawidzi ${subtype.creature}",
-	"core.bonus.HATE.description": "Zadaje ${val}% więcej obrażeń",
+	"core.bonus.HATE.name": "${subtype.creature}",
+	"core.bonus.HATE.description": "+${val}% dodatkowych obrażeń",
 	"core.bonus.HEALER.name": "Uzdrowiciel",
 	"core.bonus.HEALER.name": "Uzdrowiciel",
 	"core.bonus.HEALER.description": "Leczy sprzymierzone jednostki",
 	"core.bonus.HEALER.description": "Leczy sprzymierzone jednostki",
 	"core.bonus.HP_REGENERATION.name": "Regeneracja",
 	"core.bonus.HP_REGENERATION.name": "Regeneracja",
-	"core.bonus.HP_REGENERATION.description": "Leczy ${val} punktów zdrowia każdej rundy",
+	"core.bonus.HP_REGENERATION.description": "Leczy ${val} pkt. zdrowia co rundę",
 	"core.bonus.JOUSTING.name": "Szarża Czempiona",
 	"core.bonus.JOUSTING.name": "Szarża Czempiona",
 	"core.bonus.JOUSTING.description": "+${val}% obrażeń na przebytego heksa",
 	"core.bonus.JOUSTING.description": "+${val}% obrażeń na przebytego heksa",
-	"core.bonus.KING.name": "Król",
-	"core.bonus.KING.description": "Wrażliwy na czar POGROMCA stopnia zaawansowania ${val} lub wyższego",
-	"core.bonus.LEVEL_SPELL_IMMUNITY.name": "Odporność na czary 1-${val}",
-	"core.bonus.LEVEL_SPELL_IMMUNITY.description": "Odporny na czary 1-${val} poziomu",
+	"core.bonus.KING.name": "Król - wrażliwy na",
+	"core.bonus.KING.description": "czar POGROMCA stopnia ${val}+",
+	"core.bonus.LEVEL_SPELL_IMMUNITY.name": "Odporność: Czary 1-${val}",
+	"core.bonus.LEVEL_SPELL_IMMUNITY.description": "Odporny na czary 1-${val} poz.",
 	"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Ograniczony zasięg strzelania",
 	"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Ograniczony zasięg strzelania",
 	"core.bonus.LIMITED_SHOOTING_RANGE.description" : "Nie może strzelać do celów będących dalej niż ${val} heksów",
 	"core.bonus.LIMITED_SHOOTING_RANGE.description" : "Nie może strzelać do celów będących dalej niż ${val} heksów",
 	"core.bonus.LIFE_DRAIN.name": "Wysysa życie (${val}%)",
 	"core.bonus.LIFE_DRAIN.name": "Wysysa życie (${val}%)",
@@ -391,17 +405,17 @@
 	"core.bonus.MANA_DRAIN.description": "Wysysa ${val} many każdej tury",
 	"core.bonus.MANA_DRAIN.description": "Wysysa ${val} many każdej tury",
 	"core.bonus.MAGIC_MIRROR.name": "Magiczne Zwierciadło (${val}%)",
 	"core.bonus.MAGIC_MIRROR.name": "Magiczne Zwierciadło (${val}%)",
 	"core.bonus.MAGIC_MIRROR.description": "${val}% szans na odbicie ofensywnego czaru do wroga",
 	"core.bonus.MAGIC_MIRROR.description": "${val}% szans na odbicie ofensywnego czaru do wroga",
-	"core.bonus.MAGIC_RESISTANCE.name": "Odporność na Magię(${val}%)",
-	"core.bonus.MAGIC_RESISTANCE.description": "${val}% szans na przeciwstawienie się wrogiemu czarowi",
-	"core.bonus.MIND_IMMUNITY.name": "Odporność na czasy umysłu",
+	"core.bonus.MAGIC_RESISTANCE.name": "Odporność: Magia(${val}%)",
+	"core.bonus.MAGIC_RESISTANCE.description": "${val}% szans na unik wrogiego czaru",
+	"core.bonus.MIND_IMMUNITY.name": "Odporność: Czary umysłu",
 	"core.bonus.MIND_IMMUNITY.description": "Odporny na czary typu umysłu",
 	"core.bonus.MIND_IMMUNITY.description": "Odporny na czary typu umysłu",
 	"core.bonus.NO_DISTANCE_PENALTY.name": "Brak ograniczeń za odległość",
 	"core.bonus.NO_DISTANCE_PENALTY.name": "Brak ograniczeń za odległość",
 	"core.bonus.NO_DISTANCE_PENALTY.description": "Pełne obrażenia z każdego zasięgu",
 	"core.bonus.NO_DISTANCE_PENALTY.description": "Pełne obrażenia z każdego zasięgu",
-	"core.bonus.NO_MELEE_PENALTY.name": "Brak ograniczeń za walkę wręcz",
+	"core.bonus.NO_MELEE_PENALTY.name": "Bez ograniczeń",
 	"core.bonus.NO_MELEE_PENALTY.description": "Stworzenie nie ma kar w walce wręcz",
 	"core.bonus.NO_MELEE_PENALTY.description": "Stworzenie nie ma kar w walce wręcz",
 	"core.bonus.NO_MORALE.name": "Neutralne Morale",
 	"core.bonus.NO_MORALE.name": "Neutralne Morale",
-	"core.bonus.NO_MORALE.description": "Stworzenie jest odporne na efekty morale",
-	"core.bonus.NO_WALL_PENALTY.name": "Brak kar za strzelanie przez przeszkody",
+	"core.bonus.NO_MORALE.description": "Odporność na efekty morale",
+	"core.bonus.NO_WALL_PENALTY.name": "Bez przeszkód",
 	"core.bonus.NO_WALL_PENALTY.description": "Pełne obrażenia podczas oblężenia",
 	"core.bonus.NO_WALL_PENALTY.description": "Pełne obrażenia podczas oblężenia",
 	"core.bonus.NON_LIVING.name": "Nie żyjący",
 	"core.bonus.NON_LIVING.name": "Nie żyjący",
 	"core.bonus.NON_LIVING.description": "Niewrażliwość na wiele efektów",
 	"core.bonus.NON_LIVING.description": "Niewrażliwość na wiele efektów",
@@ -415,6 +429,8 @@
 	"core.bonus.REBIRTH.description": "${val}% stworzeń powstanie po śmierci",
 	"core.bonus.REBIRTH.description": "${val}% stworzeń powstanie po śmierci",
 	"core.bonus.RETURN_AFTER_STRIKE.name": "Atak i Powrót",
 	"core.bonus.RETURN_AFTER_STRIKE.name": "Atak i Powrót",
 	"core.bonus.RETURN_AFTER_STRIKE.description": "Wraca po ataku wręcz",
 	"core.bonus.RETURN_AFTER_STRIKE.description": "Wraca po ataku wręcz",
+	"core.bonus.REVENGE.name": "Odwet",
+	"core.bonus.REVENGE.description": "Zadaje dodatkowe obrażenia zależne od strat własnych oddziału",
 	"core.bonus.SHOOTER.name": "Dystansowy",
 	"core.bonus.SHOOTER.name": "Dystansowy",
 	"core.bonus.SHOOTER.description": "Stworzenie może strzelać",
 	"core.bonus.SHOOTER.description": "Stworzenie może strzelać",
 	"core.bonus.SHOOTS_ALL_ADJACENT.name": "Ostrzeliwuje wszystko dookoła",
 	"core.bonus.SHOOTS_ALL_ADJACENT.name": "Ostrzeliwuje wszystko dookoła",
@@ -423,20 +439,20 @@
 	"core.bonus.SOUL_STEAL.description": "Zdobywa ${val} nowych stworzeń za każdego zabitego wroga",
 	"core.bonus.SOUL_STEAL.description": "Zdobywa ${val} nowych stworzeń za każdego zabitego wroga",
 	"core.bonus.SPELLCASTER.name": "Czarodziej",
 	"core.bonus.SPELLCASTER.name": "Czarodziej",
 	"core.bonus.SPELLCASTER.description": "Może rzucić ${subtype.spell}",
 	"core.bonus.SPELLCASTER.description": "Może rzucić ${subtype.spell}",
-	"core.bonus.SPELL_AFTER_ATTACK.name": "Rzuca czar po ataku",
-	"core.bonus.SPELL_AFTER_ATTACK.description": "${val}% szans aby rzucić ${subtype.spell} po ataku",
-	"core.bonus.SPELL_BEFORE_ATTACK.name": "Rzuca czar przed atakiem",
-	"core.bonus.SPELL_BEFORE_ATTACK.description": "${val}% szans aby rzucić ${subtype.spell} przed atakiem",
-	"core.bonus.SPELL_DAMAGE_REDUCTION.name": "Odporność na czary",
+	"core.bonus.SPELL_AFTER_ATTACK.name": "${val}% szans na czar",
+	"core.bonus.SPELL_AFTER_ATTACK.description": "${subtype.spell} po ataku",
+	"core.bonus.SPELL_BEFORE_ATTACK.name": "${val}% szans na czar",
+	"core.bonus.SPELL_BEFORE_ATTACK.description": "${subtype.spell} przed atakiem",
+	"core.bonus.SPELL_DAMAGE_REDUCTION.name": "Obrona przed magią",
 	"core.bonus.SPELL_DAMAGE_REDUCTION.description": "Obrażenia od czarów są zmniejszone o ${val}%.",
 	"core.bonus.SPELL_DAMAGE_REDUCTION.description": "Obrażenia od czarów są zmniejszone o ${val}%.",
-	"core.bonus.SPELL_IMMUNITY.name": "Odporność na czar",
-	"core.bonus.SPELL_IMMUNITY.description": "Odporny na ${subtype.spell}",
+	"core.bonus.SPELL_IMMUNITY.name": "Odporność: Zaklęcie",
+	"core.bonus.SPELL_IMMUNITY.description": "${subtype.spell}",
 	"core.bonus.SPELL_LIKE_ATTACK.name": "Atak czaropodobny",
 	"core.bonus.SPELL_LIKE_ATTACK.name": "Atak czaropodobny",
 	"core.bonus.SPELL_LIKE_ATTACK.description": "Atakuje z użyciem ${subtype.spell}",
 	"core.bonus.SPELL_LIKE_ATTACK.description": "Atakuje z użyciem ${subtype.spell}",
-	"core.bonus.SPELL_RESISTANCE_AURA.name": "Aura Odporności",
-	"core.bonus.SPELL_RESISTANCE_AURA.description": "Pobliskie stworzenia otrzymują ${val}% magicznej odporności",
-	"core.bonus.SUMMON_GUARDIANS.name": "Wezwij strażników",
-	"core.bonus.SUMMON_GUARDIANS.description": "Na początku walki wzywa ${subtype.creature} (${val}%)",
+	"core.bonus.SPELL_RESISTANCE_AURA.name": "O ${val}% słabszy",
+	"core.bonus.SPELL_RESISTANCE_AURA.description": "efekt czarów dla pobl. stwor.",
+	"core.bonus.SUMMON_GUARDIANS.name": "Wzywa na początku walki",
+	"core.bonus.SUMMON_GUARDIANS.description": "${subtype.creature} (${val}%)",
 	"core.bonus.SYNERGY_TARGET.name": "Synergiczny",
 	"core.bonus.SYNERGY_TARGET.name": "Synergiczny",
 	"core.bonus.SYNERGY_TARGET.description": "To stworzenie jest podatne na efekt synergii",
 	"core.bonus.SYNERGY_TARGET.description": "To stworzenie jest podatne na efekt synergii",
 	"core.bonus.TWO_HEX_ATTACK_BREATH.name": "Zionięcie",
 	"core.bonus.TWO_HEX_ATTACK_BREATH.name": "Zionięcie",
@@ -449,7 +465,7 @@
 	"core.bonus.UNDEAD.description": "Stworzenie jest nieumarłe",
 	"core.bonus.UNDEAD.description": "Stworzenie jest nieumarłe",
 	"core.bonus.UNLIMITED_RETALIATIONS.name": "Nieskończone kontrataki",
 	"core.bonus.UNLIMITED_RETALIATIONS.name": "Nieskończone kontrataki",
 	"core.bonus.UNLIMITED_RETALIATIONS.description": "Kontratakuje nieskończoną ilość razy",
 	"core.bonus.UNLIMITED_RETALIATIONS.description": "Kontratakuje nieskończoną ilość razy",
-	"core.bonus.WATER_IMMUNITY.name": "Odporność na wodę",
+	"core.bonus.WATER_IMMUNITY.name": "Odporność: Woda",
 	"core.bonus.WATER_IMMUNITY.description": "Odporny na wszystkie czary szkoły wody",
 	"core.bonus.WATER_IMMUNITY.description": "Odporny na wszystkie czary szkoły wody",
 	"core.bonus.WIDE_BREATH.name": "Szerokie zionięcie",
 	"core.bonus.WIDE_BREATH.name": "Szerokie zionięcie",
 	"core.bonus.WIDE_BREATH.description": "Szeroki atak zionięciem (wiele heksów)"
 	"core.bonus.WIDE_BREATH.description": "Szeroki atak zionięciem (wiele heksów)"

+ 223 - 48
Mods/vcmi/config/vcmi/spanish.json

@@ -30,9 +30,55 @@
 	"vcmi.capitalColors.6" : "Turquesa",
 	"vcmi.capitalColors.6" : "Turquesa",
 	"vcmi.capitalColors.7" : "Rosa",
 	"vcmi.capitalColors.7" : "Rosa",
 
 
-	"vcmi.server.errors.existingProcess" : "Otro proceso de vcmiserver está en ejecución, por favor termínalo primero",
-	"vcmi.server.errors.modsToEnable"    : "{Mods necesarios para cargar el juego}",
-	"vcmi.server.confirmReconnect"       : "¿Conectar a la última sesión?",
+	"vcmi.heroOverview.startingArmy" : "Unidades iniciales",
+	"vcmi.heroOverview.warMachine" : "Máquinas de Guerra",
+	"vcmi.heroOverview.secondarySkills" : "Habilidades secundarias",
+	"vcmi.heroOverview.spells" : "Hechizos",
+
+	"vcmi.radialWheel.mergeSameUnit" : "Mezclar criaturas idénticas",
+	"vcmi.radialWheel.fillSingleUnit" : "Rellenar con criaturas individuales",
+	"vcmi.radialWheel.splitSingleUnit" : "Separar una sola criatura",
+	"vcmi.radialWheel.splitUnitEqually" : "Dividir criaturas por igual",
+	"vcmi.radialWheel.moveUnit" : "Trasladar criaturas a otro ejército",
+	"vcmi.radialWheel.splitUnit" : "Dividir criatura a otra ranura",
+
+	"vcmi.radialWheel.heroGetArmy" : "Obtener ejército de otro héroe",
+	"vcmi.radialWheel.heroSwapArmy" : "Intercambiar ejército con otro héroe",
+	"vcmi.radialWheel.heroExchange" : "intercambio entre héroes",
+	"vcmi.radialWheel.heroGetArtifacts" : "Obtener artefactos de otro héroe",
+	"vcmi.radialWheel.heroSwapArtifacts" : "Intercambiar artefactos con otro héroe",
+	"vcmi.radialWheel.heroDismiss" : "Despedir al héroe",
+
+	"vcmi.radialWheel.moveTop" : "Mover arriba",
+	"vcmi.radialWheel.moveUp" : "Mover hacia arriba",
+	"vcmi.radialWheel.moveDown" : "Mover hacia abajo",
+	"vcmi.radialWheel.moveBottom" : "Mover abajo",
+
+	"vcmi.spellBook.search" : "buscar...",
+
+	"vcmi.mainMenu.serverConnecting" : "Conectando...",
+	"vcmi.mainMenu.serverAddressEnter" : "Ingrese la dirección:",
+	"vcmi.mainMenu.serverConnectionFailed" : "Falló la conexión",
+	"vcmi.mainMenu.serverClosing" : "Cerrando...",
+	"vcmi.mainMenu.hostTCP" : "Crear juego TCP/IP",
+	"vcmi.mainMenu.joinTCP" : "Unirse a juego TCP/IP",
+	"vcmi.mainMenu.playerName" : "Jugador",
+
+	"vcmi.lobby.filepath" : "Ruta del archivo",
+	"vcmi.lobby.creationDate" : "Fecha de creación",
+	"vcmi.lobby.scenarioName" : "Nombre del escenario",
+	"vcmi.lobby.mapPreview" : "Vista previa del mapa",
+	"vcmi.lobby.noPreview" : "sin vista previa",
+	"vcmi.lobby.noUnderground" : "sin subterráneo",
+
+	"vcmi.client.errors.missingCampaigns" : "{Archivos de datos faltantes}\n\n¡No se encontraron los archivos de datos de las campañas! Quizás estés utilizando archivos de datos incompletos o dañados de Heroes 3. Por favor, reinstala los datos del juego.",
+	"vcmi.server.errors.existingProcess" : "Otro servidor VCMI está en ejecución. Por favor, termínalo antes de comenzar un nuevo juego.",
+	"vcmi.server.errors.modsToEnable"    : "{Se requieren los siguientes mods}",
+	"vcmi.server.errors.modsToDisable"   : "{Deben desactivarse los siguientes mods}",
+	"vcmi.server.confirmReconnect"       : "¿Quieres reconectar a la última sesión?",
+	"vcmi.server.errors.modNoDependency" : "Error al cargar el mod {'%s'}.\n Depende del mod {'%s'}, que no está activo.\n",
+	"vcmi.server.errors.modConflict" : "Error al cargar el mod {'%s'}.\n Conflicto con el mod activo {'%s'}.\n",
+	"vcmi.server.errors.unknownEntity" : "Error al cargar la partida guardada. ¡Se encontró una entidad desconocida '%s' en la partida guardada! Es posible que la partida no sea compatible con la versión actualmente instalada de los mods.",
 
 
 	"vcmi.settingsMainWindow.generalTab.hover" : "General",
 	"vcmi.settingsMainWindow.generalTab.hover" : "General",
 	"vcmi.settingsMainWindow.generalTab.help"     : "Cambiar a la pestaña de opciones generales, que contiene ajustes relacionados con el comportamiento general del juego",
 	"vcmi.settingsMainWindow.generalTab.help"     : "Cambiar a la pestaña de opciones generales, que contiene ajustes relacionados con el comportamiento general del juego",
@@ -46,12 +92,33 @@
 	"vcmi.systemOptions.otherGroup" : "Otras configuraciones", // actualmente no utilizada
 	"vcmi.systemOptions.otherGroup" : "Otras configuraciones", // actualmente no utilizada
 	"vcmi.systemOptions.townsGroup" : "Pantalla de la ciudad",
 	"vcmi.systemOptions.townsGroup" : "Pantalla de la ciudad",
 
 
+	"vcmi.systemOptions.fullscreenBorderless.hover" : "Pantalla completa (sin bordes)",
+	"vcmi.systemOptions.fullscreenBorderless.help"  : "{Pantalla completa sin bordes}\n\nSi está seleccionado, VCMI se ejecutará en modo de pantalla completa sin bordes. En este modo, el juego siempre usará la misma resolución que el escritorio, ignorando la resolución seleccionada.",
+	"vcmi.systemOptions.fullscreenExclusive.hover"  : "Pantalla completa (exclusiva)",
+	"vcmi.systemOptions.fullscreenExclusive.help"   : "{Pantalla completa}\n\nSi está seleccionado, VCMI se ejecutará en modo de pantalla completa exclusiva. En este modo, el juego cambiará la resolución del monitor a la resolución seleccionada.",
 	"vcmi.systemOptions.resolutionButton.hover" : "Resolución: %wx%h",
 	"vcmi.systemOptions.resolutionButton.hover" : "Resolución: %wx%h",
-	"vcmi.systemOptions.resolutionButton.help"  : "{Seleccionar resolución}\n\n Cambia la resolución de la pantalla del juego. Se requiere reiniciar el juego para aplicar la nueva resolución.",
-	"vcmi.systemOptions.resolutionMenu.hover"   : "Seleccionar resolución",
-	"vcmi.systemOptions.resolutionMenu.help"    : "Cambia la resolución de la pantalla del juego.",
+	"vcmi.systemOptions.resolutionButton.help"  : "{Seleccionar Resolución}\n\nCambia la resolución de pantalla del juego.",
+	"vcmi.systemOptions.resolutionMenu.hover"   : "Seleccionar Resolución",
+	"vcmi.systemOptions.resolutionMenu.help"    : "Cambia la resolución de pantalla del juego.",
+	"vcmi.systemOptions.scalingButton.hover"   : "Escalado de interfaz: %p%",
+	"vcmi.systemOptions.scalingButton.help"    : "{Escalado de interfaz}\n\nCambia el escalado de la interfaz del juego.",
+	"vcmi.systemOptions.scalingMenu.hover"     : "Seleccionar Escalado de Interfaz",
+	"vcmi.systemOptions.scalingMenu.help"      : "Cambia el escalado de la interfaz del juego.",
+	"vcmi.systemOptions.longTouchButton.hover"   : "Intervalo de toque largo: %d ms", // Nota de traducción: "ms" = "milisegundos"
+	"vcmi.systemOptions.longTouchButton.help"    : "{Intervalo de toque largo}\n\nCuando se utiliza una pantalla táctil, las ventanas emergentes aparecerán después de tocar la pantalla durante la duración especificada, en milisegundos.",
+	"vcmi.systemOptions.longTouchMenu.hover"     : "Seleccionar Intervalo de Toque Largo",
+	"vcmi.systemOptions.longTouchMenu.help"      : "Cambia la duración del intervalo de toque largo.",
+	"vcmi.systemOptions.longTouchMenu.entry"     : "%d milisegundos",
 	"vcmi.systemOptions.framerateButton.hover"  : "Mostrar FPS",
 	"vcmi.systemOptions.framerateButton.hover"  : "Mostrar FPS",
-	"vcmi.systemOptions.framerateButton.help"   : "{Mostrar FPS}\n\n Muestra el contador de Frames Por Segundo en la esquina de la ventana del juego.",
+	"vcmi.systemOptions.framerateButton.help"   : "{Mostrar FPS}\n\nAlternar la visibilidad del contador de Frames Per Second en la esquina de la ventana del juego.",
+	"vcmi.systemOptions.hapticFeedbackButton.hover"  : "Retroalimentación háptica",
+	"vcmi.systemOptions.hapticFeedbackButton.help"   : "{Retroalimentación háptica}\n\nAlternar la retroalimentación háptica en las entradas táctiles.",
+	"vcmi.systemOptions.enableUiEnhancementsButton.hover"  : "Mejoras de la interfaz",
+	"vcmi.systemOptions.enableUiEnhancementsButton.help"   : "{Mejoras de la interfaz}\n\nAlternar diversas mejoras de interfaz de calidad de vida. Como un botón de mochila, etc. Desactiva para tener una experiencia más clásica.",
+	"vcmi.systemOptions.enableLargeSpellbookButton.hover"  : "Libro de hechizos grande",
+	"vcmi.systemOptions.enableLargeSpellbookButton.help"   : "{Libro de hechizos grande}\n\nPermite un libro de hechizos más grande que permite más hechizos por página. La animación de cambio de página del libro de hechizos no funciona con esta configuración habilitada.",
+	"vcmi.systemOptions.audioMuteFocus.hover"  : "Silenciar con inactividad",
+	"vcmi.systemOptions.audioMuteFocus.help"   : "{Silenciar con inactividad}\n\nSilenciar el audio cuando la ventana no está activa. Las excepciones son los mensajes en el juego y el sonido de turno nuevo.",
 
 
 	"vcmi.adventureOptions.infoBarPick.hover" : "Mostrar mensajes en el panel de información",
 	"vcmi.adventureOptions.infoBarPick.hover" : "Mostrar mensajes en el panel de información",
 	"vcmi.adventureOptions.infoBarPick.help" : "{Mostrar mensajes en el panel de información}\n\nSiempre que sea posible, los mensajes del juego sobre los objetos del mapa que se visiten se mostrarán en el panel de información, en lugar de aparecer en una ventana separada.",
 	"vcmi.adventureOptions.infoBarPick.help" : "{Mostrar mensajes en el panel de información}\n\nSiempre que sea posible, los mensajes del juego sobre los objetos del mapa que se visiten se mostrarán en el panel de información, en lugar de aparecer en una ventana separada.",
@@ -61,6 +128,16 @@
 	"vcmi.adventureOptions.forceMovementInfo.help": "{Mostrar siempre el coste de movimiento}\n\n Reemplaza la información predeterminada de la barra de estado con datos de puntos de movimiento sin necesidad de mantener presionado el botón ALT.",
 	"vcmi.adventureOptions.forceMovementInfo.help": "{Mostrar siempre el coste de movimiento}\n\n Reemplaza la información predeterminada de la barra de estado con datos de puntos de movimiento sin necesidad de mantener presionado el botón ALT.",
 	"vcmi.adventureOptions.showGrid.hover": "Mostrar cuadrícula",
 	"vcmi.adventureOptions.showGrid.hover": "Mostrar cuadrícula",
 	"vcmi.adventureOptions.showGrid.help": "{Mostrar cuadrícula}\n\n Muestra una superposición de cuadrícula que muestra las fronteras entre las casillas del mapa de aventuras.",
 	"vcmi.adventureOptions.showGrid.help": "{Mostrar cuadrícula}\n\n Muestra una superposición de cuadrícula que muestra las fronteras entre las casillas del mapa de aventuras.",
+	"vcmi.adventureOptions.borderScroll.hover" : "Desplazamiento de borde",
+	"vcmi.adventureOptions.borderScroll.help" : "{Desplazamiento de borde}\n\nDesplaza el mapa de aventuras cuando el cursor está adyacente al borde de la ventana. Puede desactivarse manteniendo presionada la tecla CTRL.",
+	"vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Gestión de criaturas en el panel de información",
+	"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Gestión de criaturas en el panel de información}\n\nPermite reorganizar criaturas en el panel de información en lugar de cambiar entre los componentes predeterminados.",
+	"vcmi.adventureOptions.leftButtonDrag.hover" : "Arrastrar mapa con clic izquierdo",
+	"vcmi.adventureOptions.leftButtonDrag.help" : "{Arrastrar mapa con clic izquierdo}\n\nCuando está habilitado, mover el ratón con el botón izquierdo presionado arrastrará la vista del mapa de aventuras.",
+	"vcmi.adventureOptions.smoothDragging.hover" : "Arrastre suave del mapa",
+	"vcmi.adventureOptions.smoothDragging.help" : "{Arrastre suave del mapa}\n\nCuando está habilitado, el arrastre del mapa tiene un efecto de deslizamiento moderno.",
+	"vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Omitir efectos de desvanecimiento",
+	"vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Omitir efectos de desvanecimiento}\n\nCuando está habilitado, omite el desvanecimiento de objetos y otros efectos similares (recolección de recursos, embarque en barco, etc.). Hace que la interfaz sea más reactiva en algunos casos a expensas de la estética. Especialmente útil en juegos JcJ. Para obtener la máxima velocidad de movimiento, la omisión está activa independientemente de esta configuración.",
 	"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed6.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed6.hover": "",
@@ -85,68 +162,166 @@
 	"vcmi.battleOptions.animationsSpeed6.help": "Establece la velocidad de animación como instantánea.",
 	"vcmi.battleOptions.animationsSpeed6.help": "Establece la velocidad de animación como instantánea.",
 	"vcmi.battleOptions.movementHighlightOnHover.hover": "Resaltado de movimiento al pasar el ratón",
 	"vcmi.battleOptions.movementHighlightOnHover.hover": "Resaltado de movimiento al pasar el ratón",
 	"vcmi.battleOptions.movementHighlightOnHover.help": "{Resaltado de movimiento al pasar el ratón}\n\nResalta el rango de movimiento de la unidad cuando el cursor esta sobre esta.",
 	"vcmi.battleOptions.movementHighlightOnHover.help": "{Resaltado de movimiento al pasar el ratón}\n\nResalta el rango de movimiento de la unidad cuando el cursor esta sobre esta.",
+	"vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Mostrar límites de alcance para tiradores",
+	"vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Mostrar límites de alcance para tiradores al pasar el ratón}\n\nMuestra los límites de alcance de los tiradores al pasar el ratón sobre ellos.",
+	"vcmi.battleOptions.showStickyHeroInfoWindows.hover": "Mostrar ventanas de estadísticas de héroes",
+	"vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Mostrar ventanas de estadísticas de héroes}\n\nAlternar permanentemente las ventanas de estadísticas de héroes que muestran estadísticas primarias y puntos de hechizo.",
 	"vcmi.battleOptions.skipBattleIntroMusic.hover": "Omitir música de introducción",
 	"vcmi.battleOptions.skipBattleIntroMusic.hover": "Omitir música de introducción",
-	"vcmi.battleOptions.skipBattleIntroMusic.help": "{Omitir música de introducción}\n\n Omitir la breve música que se reproduce al comienzo de cada batalla antes de que comience la acción. También se puede omitir presionando la tecla ESC.",
-	"vcmi.battleWindow.pressKeyToSkipIntro" : "Presiona una tecla para comenzar la batalla inmediatamente.",
+	"vcmi.battleOptions.skipBattleIntroMusic.help": "{Omitir música de introducción}\n\nPermitir acciones durante la música de introducción que se reproduce al comienzo de cada batalla.",
+
+	"vcmi.adventureMap.revisitObject.hover" : "Revisitar objeto",
+	"vcmi.adventureMap.revisitObject.help" : "{Revisitar objeto}\n\nSi un héroe se encuentra actualmente en un objeto del mapa, puede volver a visitar la ubicación.",
 
 
-	"vcmi.battleWindow.damageEstimation.melee" : "Ataque %CREATURE (%DAMAGE).",
-	"vcmi.battleWindow.damageEstimation.meleeKills" : "Ataque %CREATURE (%DAMAGE, %KILLS).",
-	"vcmi.battleWindow.damageEstimation.ranged" : "Disparo %CREATURE (%SHOTS, %DAMAGE).",
-	"vcmi.battleWindow.damageEstimation.rangedKills" : "Disparo %CREATURE (%SHOTS, %DAMAGE, %KILLS).",
+	"vcmi.battleWindow.pressKeyToSkipIntro" : "Presiona cualquier tecla para empezar la batalla inmediatamente",
+	"vcmi.battleWindow.damageEstimation.melee" : "Atacar %CREATURE (%DAÑO).",
+	"vcmi.battleWindow.damageEstimation.meleeKills" : "Atacar %CREATURE (%DAÑO, %BAJAS).",
+	"vcmi.battleWindow.damageEstimation.ranged" : "Disparar a %CREATURE (%DISPAROS, %DAÑO).",
+	"vcmi.battleWindow.damageEstimation.rangedKills" : "Disparar a %CREATURE (%DISPAROS, %DAÑO, %BAJAS).",
 	"vcmi.battleWindow.damageEstimation.shots" : "%d disparos restantes",
 	"vcmi.battleWindow.damageEstimation.shots" : "%d disparos restantes",
-	"vcmi.battleWindow.damageEstimation.shots.1" : "%d disparos restantes",
+	"vcmi.battleWindow.damageEstimation.shots.1" : "%d disparo restante",
 	"vcmi.battleWindow.damageEstimation.damage" : "%d daño",
 	"vcmi.battleWindow.damageEstimation.damage" : "%d daño",
 	"vcmi.battleWindow.damageEstimation.damage.1" : "%d daño",
 	"vcmi.battleWindow.damageEstimation.damage.1" : "%d daño",
 	"vcmi.battleWindow.damageEstimation.kills" : "%d perecerán",
 	"vcmi.battleWindow.damageEstimation.kills" : "%d perecerán",
-	"vcmi.battleWindow.damageEstimation.kills.1" : "%d perecerán",
+	"vcmi.battleWindow.damageEstimation.kills.1" : "%d perecerá",
 
 
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Aplicar resultado de la batalla",
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Aplicar resultado de la batalla",
 
 
-	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover": "Mostrar criaturas disponibles",
-	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help": "{Mostrar criaturas disponibles}\n\n Muestra las criaturas disponibles para comprar en lugar de su crecimiento en el resumen de la ciudad (esquina inferior izquierda).",
+	"vcmi.tutorialWindow.title" : "Introducción a la pantalla táctil",
+	"vcmi.tutorialWindow.decription.RightClick" : "Toca y mantén presionado el elemento en el que quieres hacer clic derecho. Toca el área libre para cerrar.",
+	"vcmi.tutorialWindow.decription.MapPanning" : "Toca y arrastra con un dedo para mover el mapa.",
+	"vcmi.tutorialWindow.decription.MapZooming" : "Hace zoom con dos dedos para cambiar el zoom del mapa.",
+	"vcmi.tutorialWindow.decription.RadialWheel" : "Deslizar abre la rueda radial para diversas acciones, como la gestión de criaturas/héroes y el pedido de ciudad.",
+	"vcmi.tutorialWindow.decription.BattleDirection" : "Para atacar desde una dirección específica, desliza en la dirección desde la cual se va a realizar el ataque.",
+	"vcmi.tutorialWindow.decription.BattleDirectionAbort" : "El gesto de dirección de ataque se puede cancelar si el dedo está lo suficientemente alejado.",
+	"vcmi.tutorialWindow.decription.AbortSpell" : "Toca y mantén presionado para cancelar un hechizo.",
+
+	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Mostrar criaturas disponibles",
+	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Mostrar criaturas disponibles}\n\nMuestra la cantidad de criaturas disponibles para comprar en lugar de su crecimiento en el resumen de la ciudad (esquina inferior izquierda de la pantalla de la ciudad).",
 	"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover": "Mostrar crecimiento semanal de criaturas",
 	"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover": "Mostrar crecimiento semanal de criaturas",
-	"vcmi.otherOptions.creatureGrowthAsDwellingLabel.help": "{Mostrar crecimiento semanal de criaturas}\n\n Muestra el crecimiento semanal de las criaturas en lugar de la cantidad disponible en el resumen de la ciudad (esquina inferior izquierda).",
+	"vcmi.otherOptions.creatureGrowthAsDwellingLabel.help": "{Mostrar crecimiento semanal de criaturas}\n\nMuestra el crecimiento semanal de las criaturas en lugar de la cantidad disponible en el resumen de la ciudad (esquina inferior izquierda de la pantalla de la ciudad).",
 	"vcmi.otherOptions.compactTownCreatureInfo.hover": "Información compacta de criaturas de la ciudad",
 	"vcmi.otherOptions.compactTownCreatureInfo.hover": "Información compacta de criaturas de la ciudad",
-	"vcmi.otherOptions.compactTownCreatureInfo.help": "{Información compacta de criaturas de la ciudad}\n\n Información más pequeña de las criaturas de la ciudad en el resumen de la ciudad.",
-
-	"vcmi.townHall.missingBase": "El edificio base %s debe ser construido primero",
-	"vcmi.townHall.noCreaturesToRecruit": "¡No hay criaturas para reclutar!",
-	"vcmi.townHall.greetingManaVortex": "A medida que te acercas a %s, tu cuerpo se llena de nueva energía. Has duplicado tus puntos de magia normales.",
-	"vcmi.townHall.greetingKnowledge": "Estudias los glifos en %s y obtienes una visión de cómo funcionan varias magias (+1 Conocimiento).",
-	"vcmi.townHall.greetingSpellPower": "%s te enseña nuevas formas de enfocar tus poderes mágicos (+1 Poder).",
-	"vcmi.townHall.greetingExperience": "Una visita a %s te enseña muchas habilidades nuevas (+1000 experiencia).",
-	"vcmi.townHall.greetingAttack": "El tiempo pasado en %s te permite aprender habilidades de combate más efectivas (+1 habilidad de Ataque).",
-	"vcmi.townHall.greetingDefence": "Pasando tiempo en %s, los guerreros experimentados allí te enseñan habilidades defensivas adicionales (+1 Defensa).",
-	"vcmi.townHall.hasNotProduced": "%s no ha producido nada todavía.",
-	"vcmi.townHall.hasProduced": "%s produjo %d %s esta semana.",
-	"vcmi.townHall.greetingCustomBonus": "%s te da +%d %s%s",
-	"vcmi.townHall.greetingCustomUntil": " hasta la próxima batalla.",
-	"vcmi.townHall.greetingInTownMagicWell": "%s ha restaurado tus puntos de magia al máximo.",
-
-	"vcmi.logicalExpressions.anyOf"  : "Cualquiera de los siguientes:",
-	"vcmi.logicalExpressions.allOf"  : "Todos los siguientes:",
-	"vcmi.logicalExpressions.noneOf" : "Ninguno de los siguientes:",
-
-	"vcmi.heroWindow.openCommander.hover" : "Abrir ventana de comandante",
-	"vcmi.heroWindow.openCommander.help"  : "Muestra información sobre el comandante de este héroe",
+	"vcmi.otherOptions.compactTownCreatureInfo.help": "{Información compacta de criaturas de la ciudad}\n\nMuestra información más pequeña para las criaturas de la ciudad en el resumen de la ciudad (esquina inferior izquierda de la pantalla de la ciudad).",
+
+	"vcmi.townHall.missingBase"             : "Primero se debe construir el edificio base %s",
+	"vcmi.townHall.noCreaturesToRecruit"    : "¡No hay criaturas para reclutar!",
+	"vcmi.townHall.greetingManaVortex"      : "Al acercarte a %s, tu cuerpo se llena de nueva energía. Has duplicado tus puntos de hechizo normales.",
+	"vcmi.townHall.greetingKnowledge"       : "Estudias los glifos en %s y obtienes una visión de los entresijos de varias magias (+1 conocimiento).",
+	"vcmi.townHall.greetingSpellPower"      : "El %s te enseña nuevas formas de enfocar tus poderes mágicos (+1 Poder).",
+	"vcmi.townHall.greetingExperience"      : "Una visita a %s te enseña muchas habilidades nuevas (+1000 Experiencia).",
+	"vcmi.townHall.greetingAttack"          : "El tiempo dedicado en %s te permite aprender habilidades de combate más efectivas (+1 habilidad de ataque).",
+	"vcmi.townHall.greetingDefence"         : "Pasando tiempo en %s, los guerreros experimentados allí te enseñan habilidades defensivas adicionales (+1 Defensa).",
+	"vcmi.townHall.hasNotProduced"          : "%s aún no ha producido nada.",
+	"vcmi.townHall.hasProduced"             : "%s ha producido %d %s esta semana.",
+	"vcmi.townHall.greetingCustomBonus"     : "%s te da +%d %s%s",
+	"vcmi.townHall.greetingCustomUntil"     : " hasta la próxima batalla.",
+	"vcmi.townHall.greetingInTownMagicWell" : "%s ha restaurado tus puntos de hechizo al máximo.",
+
+	"vcmi.logicalExpressions.anyOf"  : "Cualquiera de lo siguiente:",
+	"vcmi.logicalExpressions.allOf"  : "Todo lo siguiente:",
+	"vcmi.logicalExpressions.noneOf" : "Ninguno de lo siguiente:",
+
+	"vcmi.heroWindow.openCommander.hover" : "Abrir ventana de información del comandante",
+	"vcmi.heroWindow.openCommander.help"  : "Muestra detalles sobre el comandante de este héroe.",
+	"vcmi.heroWindow.openBackpack.hover" : "Abrir ventana de mochila de artefactos",
+	"vcmi.heroWindow.openBackpack.help"  : "Abre la ventana que facilita la gestión de la mochila de artefactos.",
 
 
 	"vcmi.commanderWindow.artifactMessage" : "¿Quieres devolver este artefacto al héroe?",
 	"vcmi.commanderWindow.artifactMessage" : "¿Quieres devolver este artefacto al héroe?",
 
 
 	"vcmi.creatureWindow.showBonuses.hover"    : "Cambiar a vista de bonificaciones",
 	"vcmi.creatureWindow.showBonuses.hover"    : "Cambiar a vista de bonificaciones",
-	"vcmi.creatureWindow.showBonuses.help"     : "Muestra todas las bonificaciones activas del comandante",
+	"vcmi.creatureWindow.showBonuses.help"     : "Mostrar todas las bonificaciones activas del comandante.",
 	"vcmi.creatureWindow.showSkills.hover"     : "Cambiar a vista de habilidades",
 	"vcmi.creatureWindow.showSkills.hover"     : "Cambiar a vista de habilidades",
-	"vcmi.creatureWindow.showSkills.help"      : "Muestra todas las habilidades aprendidas del comandante",
+	"vcmi.creatureWindow.showSkills.help"      : "Mostrar todas las habilidades aprendidas del comandante.",
 	"vcmi.creatureWindow.returnArtifact.hover" : "Devolver artefacto",
 	"vcmi.creatureWindow.returnArtifact.hover" : "Devolver artefacto",
-	"vcmi.creatureWindow.returnArtifact.help"  : "Usa este botón para devolver un artefacto del almacen al inventario del héroe",
+	"vcmi.creatureWindow.returnArtifact.help"  : "Haz clic en este botón para devolver el artefacto a la mochila del héroe.",
 
 
-	"vcmi.questLog.hideComplete.hover" : "Ocultar misiones completas",
-	"vcmi.questLog.hideComplete.help" : "Ocultar todas las misiones que ya han sido completadas",
+	"vcmi.questLog.hideComplete.hover" : "Ocultar misiones completadas",
+	"vcmi.questLog.hideComplete.help"  : "Ocultar todas las misiones completadas.",
 
 
+	"vcmi.randomMapTab.widgets.randomTemplate"      : "(Aleatorio)",
 	"vcmi.randomMapTab.widgets.templateLabel"        : "Plantilla",
 	"vcmi.randomMapTab.widgets.templateLabel"        : "Plantilla",
-	"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Configurar...",
+	"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Configuración...",
 	"vcmi.randomMapTab.widgets.teamAlignmentsLabel"  : "Alineaciones de equipos",
 	"vcmi.randomMapTab.widgets.teamAlignmentsLabel"  : "Alineaciones de equipos",
 	"vcmi.randomMapTab.widgets.roadTypesLabel"       : "Tipos de caminos",
 	"vcmi.randomMapTab.widgets.roadTypesLabel"       : "Tipos de caminos",
 
 
+	"vcmi.optionsTab.turnOptions.hover" : "Opciones de turno",
+	"vcmi.optionsTab.turnOptions.help" : "Seleccionar temporizador de turno y opciones de turnos simultáneos",
+	"vcmi.optionsTab.selectPreset" : "Preconfigurado",
+
+	"vcmi.optionsTab.chessFieldBase.hover" : "Cronómetro base",
+	"vcmi.optionsTab.chessFieldTurn.hover" : "Cronómetro de turno",
+	"vcmi.optionsTab.chessFieldBattle.hover" : "Cronómetro de batalla",
+	"vcmi.optionsTab.chessFieldUnit.hover" : "Cronómetro de unidad",
+	"vcmi.optionsTab.chessFieldBase.help" : "Se utiliza cuando el {Cronómetro de Turno} alcanza 0. Se establece una vez al inicio del juego. Al alcanzar cero, termina el turno actual. Cualquier combate en curso terminará con una derrota.",
+	"vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Se utiliza fuera de combate o cuando el {Cronómetro de Batalla} se agota. Se restablece cada turno. El tiempo restante se suma al {Temporizador Base} al final del turno.",
+	"vcmi.optionsTab.chessFieldTurnDiscard.help" : "Se utiliza fuera de combate o cuando el {Cronómetro de Batalla} se agota. Se restablece cada turno. Se pierde cualquier tiempo no utilizado.",
+	"vcmi.optionsTab.chessFieldBattle.help" : "Se utiliza en batallas con IA o en combates JcJ cuando el {Cronómetro de Unidad} se agota. Se restablece al inicio de cada combate.",
+	"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Se utiliza al seleccionar la acción de una unidad en combate JcJ. El tiempo restante se suma al {Temporizador de Batalla} al final del turno de la unidad.",
+	"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Se utiliza al seleccionar la acción de una unidad en combate JcJ. Se restablece al inicio del turno de cada unidad. Se pierde cualquier tiempo no utilizado.",
+
+	"vcmi.optionsTab.accumulate" : "Acumular",
+
+	"vcmi.optionsTab.simturnsTitle" : "Turnos simultáneos",
+	"vcmi.optionsTab.simturnsMin.hover" : "Al menos por",
+	"vcmi.optionsTab.simturnsMax.hover" : "Como máximo por",
+	"vcmi.optionsTab.simturnsAI.hover" : "(Experimental) Turnos simultáneos de la IA",
+	"vcmi.optionsTab.simturnsMin.help" : "Jugar simultáneamente durante el número especificado de días. Los contactos entre jugadores durante este período están bloqueados.",
+	"vcmi.optionsTab.simturnsMax.help" : "Jugar simultáneamente durante el número especificado de días o hasta el contacto con otro jugador.",
+	"vcmi.optionsTab.simturnsAI.help" : "{Turnos Simultáneos de IA}\nOpción experimental. Permite que los jugadores controlados por la IA actúen al mismo tiempo que el jugador humano cuando los turnos simultáneos están habilitados.",
+
+	"vcmi.optionsTab.turnTime.select"     : "Configuración del Cronómetro de turno",
+	"vcmi.optionsTab.turnTime.unlimited"  : "Tiempo de turno ilimitado",
+	"vcmi.optionsTab.turnTime.classic.1"  : "Cronómetro clásico: 1 minuto",
+	"vcmi.optionsTab.turnTime.classic.2"  : "Cronómetro clásico: 2 minutos",
+	"vcmi.optionsTab.turnTime.classic.5"  : "Cronómetro clásico: 5 minutos",
+	"vcmi.optionsTab.turnTime.classic.10" : "Cronómetro clásico: 10 minutos",
+	"vcmi.optionsTab.turnTime.classic.20" : "Cronómetro clásico: 20 minutos",
+	"vcmi.optionsTab.turnTime.classic.30" : "Cronómetro clásico: 30 minutos",
+	"vcmi.optionsTab.turnTime.chess.20"   : "Ajedrez: 20:00 + 10:00 + 02:00 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.16"   : "Ajedrez: 16:00 + 08:00 + 01:30 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.8"    : "Ajedrez: 08:00 + 04:00 + 01:00 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.4"    : "Ajedrez: 04:00 + 02:00 + 00:30 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.2"    : "Ajedrez: 02:00 + 01:00 + 00:15 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.1"    : "Ajedrez: 01:00 + 01:00 + 00:00 + 00:00",
+
+	"vcmi.optionsTab.simturns.select"         : "Configuración de turnos simultáneos",
+	"vcmi.optionsTab.simturns.none"           : "Sin turnos simultáneos",
+	"vcmi.optionsTab.simturns.tillContactMax" : "Turnos simultáneos: Hasta contactar",
+	"vcmi.optionsTab.simturns.tillContact1"   : "Turnos simultáneos: 1 semana, interrupción al contactar",
+	"vcmi.optionsTab.simturns.tillContact2"   : "Turnos simultáneos: 2 semanas, interrupción al contactar",
+	"vcmi.optionsTab.simturns.tillContact4"   : "Turnos simultáneos: 1 mes, interrupción al contactar",
+	"vcmi.optionsTab.simturns.blocked1"       : "Turnos simultáneos: 1 semana, contactos bloqueados",
+	"vcmi.optionsTab.simturns.blocked2"       : "Turnos simultáneos: 2 semanas, contactos bloqueados",
+	"vcmi.optionsTab.simturns.blocked4"       : "Turnos simultáneos: 1 mes, contactos bloqueados",
+
+	// Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language
+	// Using this information, VCMI will automatically select correct plural form for every possible amount
+	"vcmi.optionsTab.simturns.days.0" : " %d días",
+	"vcmi.optionsTab.simturns.days.1" : " %d día",
+	"vcmi.optionsTab.simturns.days.2" : " %d días",
+	"vcmi.optionsTab.simturns.weeks.0" : " %d semanas",
+	"vcmi.optionsTab.simturns.weeks.1" : " %d semana",
+	"vcmi.optionsTab.simturns.weeks.2" : " %d semanas",
+	"vcmi.optionsTab.simturns.months.0" : " %d meses",
+	"vcmi.optionsTab.simturns.months.1" : " %d mes",
+	"vcmi.optionsTab.simturns.months.2" : " %d meses",
+
+	"vcmi.optionsTab.extraOptions.hover" : "Opciones extra",
+	"vcmi.optionsTab.extraOptions.help" : "Opciones adicionales para el juego",
+
+	"vcmi.optionsTab.cheatAllowed.hover" : "Permitir trampas",
+	"vcmi.optionsTab.unlimitedReplay.hover" : "Repetición de batalla ilimitada",
+	"vcmi.optionsTab.cheatAllowed.help" : "{Permitir trampas}\nPermite la introducción de trucos durante el juego.",
+	"vcmi.optionsTab.unlimitedReplay.help" : "{Repetición de batalla ilimitada}\nSin límite de repetición de batallas.",
+
+	// Custom victory conditions for H3 campaigns and HotA maps
+	"vcmi.map.victoryCondition.daysPassed.toOthers" : "El enemigo ha logrado sobrevivir hasta hoy. ¡La victoria es suya!",
+	"vcmi.map.victoryCondition.daysPassed.toSelf" : "¡Felicidades! Has logrado sobrevivir. ¡La victoria es tuya!",
+	"vcmi.map.victoryCondition.eliminateMonsters.toOthers" : "El enemigo ha derrotado a todas las criaturas que asolaban esta tierra y reclama la victoria.",
+	"vcmi.map.victoryCondition.eliminateMonsters.toSelf" : "¡Felicidades! Has derrotado a todas las criaturas que asolaban esta tierra y puedes reclamar la victoria.",
+	"vcmi.map.victoryCondition.collectArtifacts.message" : "Adquirir tres artefactos",
+	"vcmi.map.victoryCondition.angelicAlliance.toSelf" : "¡Felicidades! Todos tus enemigos han sido derrotados y tienes la Alianza Angelical. ¡La victoria es tuya!",
+	"vcmi.map.victoryCondition.angelicAlliance.message" : "Derrota a todos los enemigos y crea la Alianza Angelical",
+
 	// few strings from WoG used by vcmi
 	// few strings from WoG used by vcmi
 	"vcmi.stackExperience.description" : "» D e t a l l e s  d e  E x p e r i e n c i a  d e l  G r u p o «\n\nTipo de Criatura ................ : %s\nRango de Experiencia ............ : %s (%i)\nPuntos de Experiencia ............ : %i\nPuntos de Experiencia para el\nSiguiente Rango ............... : %i\nExperiencia Máxima por Batalla .. : %i%% (%i)\nNúmero de Criaturas en el grupo .. : %i\nMáximo de Nuevos Reclutas sin\nPerder el Rango Actual ......... : %i\nMultiplicador de Experiencia .... : %.2f\nMultiplicador de Actualización .. : %.2f\nExperiencia después del Rango 10 : %i\nMáximo de Nuevos Reclutas para\nMantener el Rango 10 si\nEstá en la Experiencia Máxima : %i",
 	"vcmi.stackExperience.description" : "» D e t a l l e s  d e  E x p e r i e n c i a  d e l  G r u p o «\n\nTipo de Criatura ................ : %s\nRango de Experiencia ............ : %s (%i)\nPuntos de Experiencia ............ : %i\nPuntos de Experiencia para el\nSiguiente Rango ............... : %i\nExperiencia Máxima por Batalla .. : %i%% (%i)\nNúmero de Criaturas en el grupo .. : %i\nMáximo de Nuevos Reclutas sin\nPerder el Rango Actual ......... : %i\nMultiplicador de Experiencia .... : %.2f\nMultiplicador de Actualización .. : %.2f\nExperiencia después del Rango 10 : %i\nMáximo de Nuevos Reclutas para\nMantener el Rango 10 si\nEstá en la Experiencia Máxima : %i",
 	"vcmi.stackExperience.rank.0" : "Básico",
 	"vcmi.stackExperience.rank.0" : "Básico",
@@ -297,8 +472,8 @@
 	"core.bonus.TRANSMUTATION.description": "${val}% de probabilidad de transformar la unidad atacada en otro tipo",
 	"core.bonus.TRANSMUTATION.description": "${val}% de probabilidad de transformar la unidad atacada en otro tipo",
 	"core.bonus.UNDEAD.name": "No muerto",
 	"core.bonus.UNDEAD.name": "No muerto",
 	"core.bonus.UNDEAD.description": "La criatura es un no muerto",
 	"core.bonus.UNDEAD.description": "La criatura es un no muerto",
-	"core.bonus.UNLIMITED_RETALIATIONS.name": "Retaliaciones ilimitadas",
-	"core.bonus.UNLIMITED_RETALIATIONS.description": "Puede realizar un número ilimitado de ataques de represalia",
+	"core.bonus.UNLIMITED_RETALIATIONS.name": "Contraataques ilimitados",
+	"core.bonus.UNLIMITED_RETALIATIONS.description": "Puede realizar un número ilimitado de contraataques",
 	"core.bonus.WATER_IMMUNITY.name": "Inmunidad al agua",
 	"core.bonus.WATER_IMMUNITY.name": "Inmunidad al agua",
 	"core.bonus.WATER_IMMUNITY.description": "Inmune a todos los hechizos de la escuela del agua",
 	"core.bonus.WATER_IMMUNITY.description": "Inmune a todos los hechizos de la escuela del agua",
 	"core.bonus.WIDE_BREATH.name": "Aliento amplio",
 	"core.bonus.WIDE_BREATH.name": "Aliento amplio",

+ 7 - 0
Mods/vcmi/config/vcmi/ukrainian.json

@@ -70,6 +70,7 @@
 	"vcmi.lobby.mapPreview" : "Огляд мапи",
 	"vcmi.lobby.mapPreview" : "Огляд мапи",
 	"vcmi.lobby.noPreview" : "огляд недоступний",
 	"vcmi.lobby.noPreview" : "огляд недоступний",
 	"vcmi.lobby.noUnderground" : "немає підземелля",
 	"vcmi.lobby.noUnderground" : "немає підземелля",
+	"vcmi.lobby.sortDate" : "Сортувати мапи за датою зміни",
 
 
 	"vcmi.client.errors.missingCampaigns" : "{Не вистачає файлів даних}\n\nФайли даних кампаній не знайдено! Можливо, ви використовуєте неповні або пошкоджені файли даних Heroes 3. Будь ласка, перевстановіть дані гри.",
 	"vcmi.client.errors.missingCampaigns" : "{Не вистачає файлів даних}\n\nФайли даних кампаній не знайдено! Можливо, ви використовуєте неповні або пошкоджені файли даних Heroes 3. Будь ласка, перевстановіть дані гри.",
 	"vcmi.server.errors.existingProcess" : "Працює інший процес vcmiserver, будь ласка, спочатку завершіть його",
 	"vcmi.server.errors.existingProcess" : "Працює інший процес vcmiserver, будь ласка, спочатку завершіть його",
@@ -144,6 +145,8 @@
 	"vcmi.adventureOptions.mapScrollSpeed1.help": "Встановити швидкість розгортання мапи - дуже повільно",
 	"vcmi.adventureOptions.mapScrollSpeed1.help": "Встановити швидкість розгортання мапи - дуже повільно",
 	"vcmi.adventureOptions.mapScrollSpeed5.help": "Встановити швидкість розгортання мапи - дуже швидко",
 	"vcmi.adventureOptions.mapScrollSpeed5.help": "Встановити швидкість розгортання мапи - дуже швидко",
 	"vcmi.adventureOptions.mapScrollSpeed6.help": "Встановити швидкість розгортання мапи - миттєво",
 	"vcmi.adventureOptions.mapScrollSpeed6.help": "Встановити швидкість розгортання мапи - миттєво",
+	"vcmi.adventureOptions.hideBackground.hover" : "Приховувати тло",
+	"vcmi.adventureOptions.hideBackground.help" : "{Приховувати тло}\n\nПриховати мапу пригод на задньому тлі і показати замість неї текстуру.",
 
 
 	"vcmi.battleOptions.queueSizeLabel.hover": "Вигляд черги ходу істот",
 	"vcmi.battleOptions.queueSizeLabel.hover": "Вигляд черги ходу істот",
 	"vcmi.battleOptions.queueSizeNoneButton.hover": "ВИМК",
 	"vcmi.battleOptions.queueSizeNoneButton.hover": "ВИМК",
@@ -183,6 +186,10 @@
 	"vcmi.battleWindow.damageEstimation.damage.1" : "%d одиниця пошкодження",
 	"vcmi.battleWindow.damageEstimation.damage.1" : "%d одиниця пошкодження",
 	"vcmi.battleWindow.damageEstimation.kills" : "%d загинуть",
 	"vcmi.battleWindow.damageEstimation.kills" : "%d загинуть",
 	"vcmi.battleWindow.damageEstimation.kills.1" : "%d загине",
 	"vcmi.battleWindow.damageEstimation.kills.1" : "%d загине",
+	"vcmi.battleWindow.killed" : "Загинуло",
+	"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s було вбито влучними пострілами!",
+	"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s було вбито влучним пострілом!",
+	"vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s було вбито влучними пострілами!",
 
 
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Прийняти результат бою",
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Прийняти результат бою",
 
 

+ 2 - 2
android/vcmi-app/build.gradle

@@ -10,8 +10,8 @@ android {
 		applicationId "is.xyz.vcmi"
 		applicationId "is.xyz.vcmi"
 		minSdk 19
 		minSdk 19
 		targetSdk 33
 		targetSdk 33
-		versionCode 1430
-		versionName "1.4.3"
+		versionCode 1500
+		versionName "1.5.0"
 		setProperty("archivesBaseName", "vcmi")
 		setProperty("archivesBaseName", "vcmi")
 	}
 	}
 
 

+ 71 - 0
android/vcmi-app/src/main/res/values-es/strings.xml

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="url_project_page" translatable="false">https://vcmi.eu</string>
+    <string name="url_project_repo" translatable="false">https://github.com/vcmi/vcmi</string>
+    <string name="url_launcher_repo" translatable="false">https://github.com/vcmi/vcmi-android</string>
+    <string name="url_launcher_privacy" translatable="false">https://github.com/vcmi/vcmi/blob/master/docs/players/Privacy_Policy.md</string>
+
+    <string name="app_name">VCMI</string>
+    <string name="server_name">VCMI Servidor</string>
+    <string name="launcher_title">VCMI Lanzador</string>
+    <string name="launcher_btn_scale_title">Escalado de resolución</string>
+    <string name="launcher_btn_scale_subtitle_unknown">Seleccionado: desconocido</string>
+    <string name="launcher_btn_scale_subtitle">Seleccionado: %1$d%%</string>
+    <string name="launcher_btn_start_title">Iniciar VCMI</string>
+    <string name="launcher_btn_start_subtitle">version actual VCMI: %1$s</string>
+    <string name="launcher_btn_mods_title">Mods</string>
+    <string name="launcher_btn_mods_subtitle">Instalar nuevas facciones, Objectos, extras</string>
+    <string name="launcher_btn_language_title">Idioma</string>
+    <string name="launcher_btn_language_subtitle_unknown">Seleccionado: desconocido</string>
+    <string name="launcher_btn_language_subtitle">Seleccionado: %1$s</string>
+    <string name="launcher_btn_pointermode_title">Cambiar modo del puntero</string>
+    <string name="launcher_btn_pointermode_subtitle">Seleccionado: %1$s</string>
+    <string name="launcher_btn_pointermulti_title">Múltiplo de velocidad relativa del puntero</string>
+    <string name="launcher_btn_pointermulti_subtitle">Seleccionado: %1$s</string>
+    <string name="launcher_btn_sound_title">Sonido</string>
+    <string name="launcher_btn_music_title">Música</string>
+    <string name="launcher_btn_adventure_ai">Adventure AI</string>
+    <string name="launcher_btn_adventure_ai_title">Change adventure AI</string>
+    <string name="launcher_btn_import_title">Importar datos VCMI</string>
+    <string name="launcher_btn_import_description">Copiar ficheros VCMI al almacenamiento interno. Puedes importar datos antiguos a partir de la version 0.99 de VCMI o los ficheros de HOMM3.</string>
+    <string name="launcher_btn_export_title">Exportar datos VCMI</string>
+    <string name="launcher_btn_export_description">Hacer una copia interna de los datos VCMI antes de una desinstalación o sincronizar las partidas de la versión de escritorio. Tambien puedes acceder al almacenamiento interno directamente.</string>
+    <string name="launcher_progress_copy">Copiando %1$s</string>
+    <string name="launcher_version">Version del lanzador: %1$s</string>
+    <string name="launcher_error_vcmi_data_root_failed">No se puede crear la carpeta de datos VCMI en %1$s.</string>
+    <string name="launcher_error_h3_data_missing">No se encuentra la carpeta de datos en \'%1$s\'. Coloca tus ficheros de datos de HoMM3 en la carpeta o utiliza el boton de abajo para que se haga automáticamente. Quizás sea necesario reiniciar la aplicación.</string>
+    <string name="launcher_error_vcmi_data_internal_missing">No se pudieron encontrar ni extraer datos vcmi de los recursos de la aplicación. Intente reinstalar la aplicación.</string>
+    <string name="launcher_error_vcmi_data_internal_update">No se pudieron actualizar los datos de vcmi desde los recursos de la aplicación. Intente reinstalar la aplicación.</string>
+    <string name="launcher_error_permissions">Esta aplicación necesita permisos de escritura para utilizar el contenido almacenado en un almacenamiento externo.</string>
+    <string name="launcher_error_permission_broken">Los permisos no se pueden resolver correctamente</string>
+    <string name="mods_item_author_template">por %1$s</string>
+    <string name="misc_try_again">Volver a intentar</string>
+    <string name="launcher_section_init">Iniciar juego</string>
+    <string name="launcher_section_settings">Configuración</string>
+    <string name="menu_mods_download_repo">Descargar datos del repositorio</string>
+
+    <string name="misc_pointermode_normal">Normal</string>
+    <string name="misc_pointermode_relative">Relativo</string>
+    <string name="menu_launcher_about">Acerca</string>
+
+    <string name="mods_title">Mods detectados</string>
+    <string name="mods_failed_mod_loading">No se puede cargar el Mod en la carpeta \'%1$s\' </string>
+    <string name="mods_removal_title">Eliminando %1$s</string>
+    <string name="mods_removal_confirmation">Quieres eliminar %1$s</string>
+
+    <string name="about_title">Acerca de la applicación</string>
+    <string name="about_version_app">App versión: %1$s</string>
+    <string name="about_version_launcher">Versión del lanzador: %1$s</string>
+    <string name="about_section_project">Proyecto</string>
+    <string name="about_section_legal">Legal</string>
+    <string name="about_links_main">Página principal: %1$s</string>
+    <string name="about_links_repo">Repositorio del Proyecto: %1$s</string>
+    <string name="about_links_repo_launcher">Repositorio del lanzador: %1$s</string>
+    <string name="about_btn_authors">Autores</string>
+    <string name="about_btn_privacy">Política de privacidad: %1$s</string>
+    <string name="about_error_opening_url">No se pudo abrir la página web (no se encontró una aplicacion adecuada)</string>
+
+    <string name="dialog_authors_vcmi">VCMI autores</string>
+    <string name="dialog_authors_launcher">Lanzador autores</string>
+    <string name="launcher_error_config_saving_failed">No se puede guardar la configuración de VCMI; motivo: %1$s</string>
+</resources>

+ 1 - 4
client/CGameInfo.cpp

@@ -12,16 +12,13 @@
 
 
 #include "../lib/VCMI_Lib.h"
 #include "../lib/VCMI_Lib.h"
 
 
-const CGameInfo * CGI;
+CGameInfo * CGI;
 CClientState * CCS = nullptr;
 CClientState * CCS = nullptr;
 CServerHandler * CSH;
 CServerHandler * CSH;
 
 
 
 
 CGameInfo::CGameInfo()
 CGameInfo::CGameInfo()
 {
 {
-	generaltexth = nullptr;
-	mh = nullptr;
-	townh = nullptr;
 	globalServices = nullptr;
 	globalServices = nullptr;
 }
 }
 
 

+ 16 - 15
client/CGameInfo.h

@@ -56,7 +56,7 @@ extern CClientState * CCS;
 
 
 /// CGameInfo class
 /// CGameInfo class
 /// for allowing different functions for accessing game informations
 /// for allowing different functions for accessing game informations
-class CGameInfo : public Services
+class CGameInfo final : public Services
 {
 {
 public:
 public:
 	const ArtifactService * artifacts() const override;
 	const ArtifactService * artifacts() const override;
@@ -78,19 +78,20 @@ public:
 	const spells::effects::Registry * spellEffects() const override;
 	const spells::effects::Registry * spellEffects() const override;
 	spells::effects::Registry * spellEffects() override;
 	spells::effects::Registry * spellEffects() override;
 
 
-	ConstTransitivePtr<CModHandler> modh; //public?
-	ConstTransitivePtr<BattleFieldHandler> battleFieldHandler;
-	ConstTransitivePtr<CHeroHandler> heroh;
-	ConstTransitivePtr<CCreatureHandler> creh;
-	ConstTransitivePtr<CSpellHandler> spellh;
-	ConstTransitivePtr<CSkillHandler> skillh;
-	ConstTransitivePtr<CObjectHandler> objh;
-	ConstTransitivePtr<TerrainTypeHandler> terrainTypeHandler;
-	ConstTransitivePtr<CObjectClassesHandler> objtypeh;
-	ConstTransitivePtr<ObstacleHandler> obstacleHandler;
-	CGeneralTextHandler * generaltexth;
-	CMapHandler * mh;
-	CTownHandler * townh;
+	std::shared_ptr<const CModHandler> modh;
+	std::shared_ptr<const BattleFieldHandler> battleFieldHandler;
+	std::shared_ptr<const CHeroHandler> heroh;
+	std::shared_ptr<const CCreatureHandler> creh;
+	std::shared_ptr<const CSpellHandler> spellh;
+	std::shared_ptr<const CSkillHandler> skillh;
+	std::shared_ptr<const CObjectHandler> objh;
+	std::shared_ptr<const TerrainTypeHandler> terrainTypeHandler;
+	std::shared_ptr<const CObjectClassesHandler> objtypeh;
+	std::shared_ptr<const ObstacleHandler> obstacleHandler;
+	std::shared_ptr<const CGeneralTextHandler> generaltexth;
+	std::shared_ptr<const CTownHandler> townh;
+
+	std::shared_ptr<CMapHandler> mh;
 
 
 	void setFromLib();
 	void setFromLib();
 
 
@@ -98,4 +99,4 @@ public:
 private:
 private:
 	const Services * globalServices;
 	const Services * globalServices;
 };
 };
-extern const CGameInfo* CGI;
+extern CGameInfo* CGI;

+ 18 - 18
client/CMT.cpp

@@ -72,7 +72,7 @@ void init()
 	CStopWatch tmh;
 	CStopWatch tmh;
 
 
 	loadDLLClasses();
 	loadDLLClasses();
-	const_cast<CGameInfo*>(CGI)->setFromLib();
+	CGI->setFromLib();
 
 
 	logGlobal->info("Initializing VCMI_Lib: %d ms", tmh.getDiff());
 	logGlobal->info("Initializing VCMI_Lib: %d ms", tmh.getDiff());
 
 
@@ -182,7 +182,8 @@ int main(int argc, char * argv[])
 	}
 	}
 
 
 	// Init old logging system and new (temporary) logging system
 	// Init old logging system and new (temporary) logging system
-	CStopWatch total, pomtime;
+	CStopWatch total;
+	CStopWatch pomtime;
 	std::cout.flags(std::ios::unitbuf);
 	std::cout.flags(std::ios::unitbuf);
 #ifndef VCMI_IOS
 #ifndef VCMI_IOS
 	console = new CConsoleHandler();
 	console = new CConsoleHandler();
@@ -429,15 +430,15 @@ void playIntro()
 {
 {
 	auto audioData = CCS->videoh->getAudio(VideoPath::builtin("3DOLOGO.SMK"));
 	auto audioData = CCS->videoh->getAudio(VideoPath::builtin("3DOLOGO.SMK"));
 	int sound = CCS->soundh->playSound(audioData);
 	int sound = CCS->soundh->playSound(audioData);
-	if(CCS->videoh->openAndPlayVideo(VideoPath::builtin("3DOLOGO.SMK"), 0, 1, true, true))
+	if(CCS->videoh->openAndPlayVideo(VideoPath::builtin("3DOLOGO.SMK"), 0, 1, EVideoType::INTRO))
 	{
 	{
 		audioData = CCS->videoh->getAudio(VideoPath::builtin("NWCLOGO.SMK"));
 		audioData = CCS->videoh->getAudio(VideoPath::builtin("NWCLOGO.SMK"));
 		sound = CCS->soundh->playSound(audioData);
 		sound = CCS->soundh->playSound(audioData);
-		if (CCS->videoh->openAndPlayVideo(VideoPath::builtin("NWCLOGO.SMK"), 0, 1, true, true))
+		if (CCS->videoh->openAndPlayVideo(VideoPath::builtin("NWCLOGO.SMK"), 0, 1, EVideoType::INTRO))
 		{
 		{
 			audioData = CCS->videoh->getAudio(VideoPath::builtin("H3INTRO.SMK"));
 			audioData = CCS->videoh->getAudio(VideoPath::builtin("H3INTRO.SMK"));
 			sound = CCS->soundh->playSound(audioData);
 			sound = CCS->soundh->playSound(audioData);
-			CCS->videoh->openAndPlayVideo(VideoPath::builtin("H3INTRO.SMK"), 0, 1, true, true);
+			CCS->videoh->openAndPlayVideo(VideoPath::builtin("H3INTRO.SMK"), 0, 1, EVideoType::INTRO);
 		}
 		}
 	}
 	}
 	CCS->soundh->stopSound(sound);
 	CCS->soundh->stopSound(sound);
@@ -461,9 +462,9 @@ static void mainLoop()
 	{
 	{
 		if(CSH->client)
 		if(CSH->client)
 			CSH->endGameplay();
 			CSH->endGameplay();
-	}
 
 
-	GH.windows().clear();
+		GH.windows().clear();
+	}
 
 
 	CMM.reset();
 	CMM.reset();
 
 
@@ -523,25 +524,24 @@ void handleQuit(bool ask)
 	// FIXME: avoids crash if player attempts to close game while opening is still playing
 	// FIXME: avoids crash if player attempts to close game while opening is still playing
 	// use cursor handler as indicator that loading is not done yet
 	// use cursor handler as indicator that loading is not done yet
 	// proper solution would be to abort init thread (or wait for it to finish)
 	// proper solution would be to abort init thread (or wait for it to finish)
+	if(!ask)
+	{
+		quitApplication();
+		return;
+	}
+
 	if (!CCS->curh)
 	if (!CCS->curh)
 	{
 	{
 		quitRequestedDuringOpeningPlayback = true;
 		quitRequestedDuringOpeningPlayback = true;
 		return;
 		return;
 	}
 	}
 
 
-	if(ask)
-	{
-		CCS->curh->set(Cursor::Map::POINTER);
+	CCS->curh->set(Cursor::Map::POINTER);
 
 
-		if (LOCPLINT)
-			LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], quitApplication, nullptr);
-		else
-			CInfoWindow::showYesNoDialog(CGI->generaltexth->allTexts[69], {}, quitApplication, {}, PlayerColor(1));
-	}
+	if (LOCPLINT)
+		LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], quitApplication, nullptr);
 	else
 	else
-	{
-		quitApplication();
-	}
+		CInfoWindow::showYesNoDialog(CGI->generaltexth->allTexts[69], {}, quitApplication, {}, PlayerColor(1));
 }
 }
 
 
 void handleFatalError(const std::string & message, bool terminate)
 void handleFatalError(const std::string & message, bool terminate)

+ 10 - 4
client/CMakeLists.txt

@@ -52,6 +52,7 @@ set(client_SRCS
 	lobby/CScenarioInfoScreen.cpp
 	lobby/CScenarioInfoScreen.cpp
 	lobby/CSelectionBase.cpp
 	lobby/CSelectionBase.cpp
 	lobby/TurnOptionsTab.cpp
 	lobby/TurnOptionsTab.cpp
+	lobby/ExtraOptionsTab.cpp
 	lobby/OptionsTab.cpp
 	lobby/OptionsTab.cpp
 	lobby/OptionsTabBase.cpp
 	lobby/OptionsTabBase.cpp
 	lobby/RandomMapTab.cpp
 	lobby/RandomMapTab.cpp
@@ -95,14 +96,12 @@ set(client_SRCS
 	renderSDL/SDL_Extensions.cpp
 	renderSDL/SDL_Extensions.cpp
 
 
 	widgets/Buttons.cpp
 	widgets/Buttons.cpp
-	widgets/CAltar.cpp
 	widgets/CArtifactHolder.cpp
 	widgets/CArtifactHolder.cpp
 	widgets/CComponent.cpp
 	widgets/CComponent.cpp
 	widgets/CExchangeController.cpp
 	widgets/CExchangeController.cpp
 	widgets/CGarrisonInt.cpp
 	widgets/CGarrisonInt.cpp
 	widgets/CreatureCostBox.cpp
 	widgets/CreatureCostBox.cpp
 	widgets/ComboBox.cpp
 	widgets/ComboBox.cpp
-	widgets/CTradeBase.cpp
 	widgets/Images.cpp
 	widgets/Images.cpp
 	widgets/MiscWidgets.cpp
 	widgets/MiscWidgets.cpp
 	widgets/ObjectLists.cpp
 	widgets/ObjectLists.cpp
@@ -117,6 +116,10 @@ set(client_SRCS
 	widgets/CArtifactsOfHeroBackpack.cpp
 	widgets/CArtifactsOfHeroBackpack.cpp
 	widgets/CWindowWithArtifacts.cpp
 	widgets/CWindowWithArtifacts.cpp
 	widgets/RadialMenu.cpp
 	widgets/RadialMenu.cpp
+	widgets/markets/CAltarArtifacts.cpp
+	widgets/markets/CAltarCreatures.cpp
+	widgets/markets/CTradeBase.cpp
+	widgets/markets/TradePanels.cpp
 
 
 	windows/CAltarWindow.cpp
 	windows/CAltarWindow.cpp
 	windows/CCastleInterface.cpp
 	windows/CCastleInterface.cpp
@@ -215,6 +218,7 @@ set(client_HEADERS
 	lobby/CScenarioInfoScreen.h
 	lobby/CScenarioInfoScreen.h
 	lobby/CSelectionBase.h
 	lobby/CSelectionBase.h
 	lobby/TurnOptionsTab.h
 	lobby/TurnOptionsTab.h
+	lobby/ExtraOptionsTab.h
 	lobby/OptionsTab.h
 	lobby/OptionsTab.h
 	lobby/OptionsTabBase.h
 	lobby/OptionsTabBase.h
 	lobby/RandomMapTab.h
 	lobby/RandomMapTab.h
@@ -267,14 +271,12 @@ set(client_HEADERS
 	renderSDL/SDL_PixelAccess.h
 	renderSDL/SDL_PixelAccess.h
 
 
 	widgets/Buttons.h
 	widgets/Buttons.h
-	widgets/CAltar.h
 	widgets/CArtifactHolder.h
 	widgets/CArtifactHolder.h
 	widgets/CComponent.h
 	widgets/CComponent.h
 	widgets/CExchangeController.h
 	widgets/CExchangeController.h
 	widgets/CGarrisonInt.h
 	widgets/CGarrisonInt.h
 	widgets/CreatureCostBox.h
 	widgets/CreatureCostBox.h
 	widgets/ComboBox.h
 	widgets/ComboBox.h
-	widgets/CTradeBase.h
 	widgets/Images.h
 	widgets/Images.h
 	widgets/MiscWidgets.h
 	widgets/MiscWidgets.h
 	widgets/ObjectLists.h
 	widgets/ObjectLists.h
@@ -289,6 +291,10 @@ set(client_HEADERS
 	widgets/CArtifactsOfHeroBackpack.h
 	widgets/CArtifactsOfHeroBackpack.h
 	widgets/CWindowWithArtifacts.h
 	widgets/CWindowWithArtifacts.h
 	widgets/RadialMenu.h
 	widgets/RadialMenu.h
+	widgets/markets/CAltarArtifacts.h
+	widgets/markets/CAltarCreatures.h
+	widgets/markets/CTradeBase.h
+	widgets/markets/TradePanels.h
 
 
 	windows/CAltarWindow.h
 	windows/CAltarWindow.h
 	windows/CCastleInterface.h
 	windows/CCastleInterface.h

+ 29 - 0
client/CMusicHandler.cpp

@@ -183,6 +183,35 @@ void CSoundHandler::ambientStopSound(const AudioPath & soundId)
 	setChannelVolume(ambientChannels[soundId], volume);
 	setChannelVolume(ambientChannels[soundId], volume);
 }
 }
 
 
+uint32_t CSoundHandler::getSoundDurationMilliseconds(const AudioPath & sound)
+{
+	if (!initialized || sound.empty())
+		return 0;
+
+	auto resourcePath = sound.addPrefix("SOUNDS/");
+
+	if (!CResourceHandler::get()->existsResource(resourcePath))
+		return 0;
+
+	auto data = CResourceHandler::get()->load(resourcePath)->readAll();
+
+	SDL_AudioSpec spec;
+	uint32_t audioLen;
+	uint8_t *audioBuf;
+	uint32_t miliseconds = 0;
+
+	if(SDL_LoadWAV_RW(SDL_RWFromMem(data.first.get(), (int)data.second), 1, &spec, &audioBuf, &audioLen) != nullptr)
+	{
+		SDL_FreeWAV(audioBuf);
+		uint32_t sampleSize = SDL_AUDIO_BITSIZE(spec.format) / 8;
+		uint32_t sampleCount = audioLen / sampleSize;
+		uint32_t sampleLen = sampleCount / spec.channels;
+		miliseconds = 1000 * sampleLen / spec.freq;
+	}
+
+	return miliseconds ;
+}
+
 // Plays a sound, and return its channel so we can fade it out later
 // Plays a sound, and return its channel so we can fade it out later
 int CSoundHandler::playSound(soundBase::soundID soundID, int repeats)
 int CSoundHandler::playSound(soundBase::soundID soundID, int repeats)
 {
 {

+ 1 - 0
client/CMusicHandler.h

@@ -77,6 +77,7 @@ public:
 	void setChannelVolume(int channel, ui32 percent);
 	void setChannelVolume(int channel, ui32 percent);
 
 
 	// Sounds
 	// Sounds
+	uint32_t getSoundDurationMilliseconds(const AudioPath & sound);
 	int playSound(soundBase::soundID soundID, int repeats=0);
 	int playSound(soundBase::soundID soundID, int repeats=0);
 	int playSound(const AudioPath & sound, int repeats=0, bool cache=false);
 	int playSound(const AudioPath & sound, int repeats=0, bool cache=false);
 	int playSound(std::pair<std::unique_ptr<ui8 []>, si64> & data, int repeats=0, bool cache=false);
 	int playSound(std::pair<std::unique_ptr<ui8 []>, si64> & data, int repeats=0, bool cache=false);

+ 46 - 16
client/CPlayerInterface.cpp

@@ -43,6 +43,7 @@
 
 
 #include "render/CAnimation.h"
 #include "render/CAnimation.h"
 #include "render/IImage.h"
 #include "render/IImage.h"
+#include "render/IRenderHandler.h"
 
 
 #include "widgets/Buttons.h"
 #include "widgets/Buttons.h"
 #include "widgets/CComponent.h"
 #include "widgets/CComponent.h"
@@ -82,7 +83,6 @@
 #include "../lib/UnlockGuard.h"
 #include "../lib/UnlockGuard.h"
 #include "../lib/VCMIDirs.h"
 #include "../lib/VCMIDirs.h"
 
 
-#include "../lib/bonuses/CBonusSystemNode.h"
 #include "../lib/bonuses/Limiters.h"
 #include "../lib/bonuses/Limiters.h"
 #include "../lib/bonuses/Propagators.h"
 #include "../lib/bonuses/Propagators.h"
 #include "../lib/bonuses/Updaters.h"
 #include "../lib/bonuses/Updaters.h"
@@ -111,11 +111,7 @@
 // They all assume that interface mutex is locked.
 // They all assume that interface mutex is locked.
 #define EVENT_HANDLER_CALLED_BY_CLIENT
 #define EVENT_HANDLER_CALLED_BY_CLIENT
 
 
-#define BATTLE_EVENT_POSSIBLE_RETURN	\
-	if (LOCPLINT != this)				\
-		return;							\
-	if (isAutoFightOn && !battleInt)	\
-		return;
+#define BATTLE_EVENT_POSSIBLE_RETURN	if (LOCPLINT != this) return; if (isAutoFightOn && !battleInt) return
 
 
 CPlayerInterface * LOCPLINT;
 CPlayerInterface * LOCPLINT;
 
 
@@ -150,6 +146,7 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player):
 	firstCall = 1; //if loading will be overwritten in serialize
 	firstCall = 1; //if loading will be overwritten in serialize
 	autosaveCount = 0;
 	autosaveCount = 0;
 	isAutoFightOn = false;
 	isAutoFightOn = false;
+	isAutoFightEndBattle = false;
 	ignoreEvents = false;
 	ignoreEvents = false;
 	numOfMovedArts = 0;
 	numOfMovedArts = 0;
 }
 }
@@ -787,17 +784,20 @@ void CPlayerInterface::battleEnd(const BattleID & battleID, const BattleResult *
 
 
 		if(!battleInt)
 		if(!battleInt)
 		{
 		{
-			bool allowManualReplay = queryID != QueryID::NONE;
+			bool allowManualReplay = queryID != QueryID::NONE && !isAutoFightEndBattle;
 
 
 			auto wnd = std::make_shared<BattleResultWindow>(*br, *this, allowManualReplay);
 			auto wnd = std::make_shared<BattleResultWindow>(*br, *this, allowManualReplay);
 
 
-			if (allowManualReplay)
+			if (allowManualReplay || isAutoFightEndBattle)
 			{
 			{
 				wnd->resultCallback = [=](ui32 selection)
 				wnd->resultCallback = [=](ui32 selection)
 				{
 				{
 					cb->selectionMade(selection, queryID);
 					cb->selectionMade(selection, queryID);
 				};
 				};
 			}
 			}
+			
+			isAutoFightEndBattle = false;
+
 			GH.windows().pushWindow(wnd);
 			GH.windows().pushWindow(wnd);
 			// #1490 - during AI turn when quick combat is on, we need to display the message and wait for user to close it.
 			// #1490 - during AI turn when quick combat is on, we need to display the message and wait for user to close it.
 			// Otherwise NewTurn causes freeze.
 			// Otherwise NewTurn causes freeze.
@@ -1069,6 +1069,19 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 
 
+	std::vector<ObjectInstanceID> tmpObjects;
+	if(objects.size() && dynamic_cast<const CGTownInstance *>(cb->getObj(objects[0])))
+	{
+		// sorting towns (like in client)
+		std::vector <const CGTownInstance*> Towns = LOCPLINT->localState->getOwnedTowns();
+		for(auto town : Towns)
+			for(auto item : objects)
+				if(town == cb->getObj(item))
+					tmpObjects.push_back(item);
+	}
+	else // other object list than town
+		tmpObjects = objects;
+
 	auto selectCallback = [=](int selection)
 	auto selectCallback = [=](int selection)
 	{
 	{
 		cb->sendQueryReply(selection, askID);
 		cb->sendQueryReply(selection, askID);
@@ -1083,9 +1096,9 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
 	const std::string localDescription = description.toString();
 	const std::string localDescription = description.toString();
 
 
 	std::vector<int> tempList;
 	std::vector<int> tempList;
-	tempList.reserve(objects.size());
+	tempList.reserve(tmpObjects.size());
 
 
-	for(auto item : objects)
+	for(auto item : tmpObjects)
 		tempList.push_back(item.getNum());
 		tempList.push_back(item.getNum());
 
 
 	CComponent localIconC(icon);
 	CComponent localIconC(icon);
@@ -1093,8 +1106,24 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
 	std::shared_ptr<CIntObject> localIcon = localIconC.image;
 	std::shared_ptr<CIntObject> localIcon = localIconC.image;
 	localIconC.removeChild(localIcon.get(), false);
 	localIconC.removeChild(localIcon.get(), false);
 
 
-	std::shared_ptr<CObjectListWindow> wnd = std::make_shared<CObjectListWindow>(tempList, localIcon, localTitle, localDescription, selectCallback);
+	std::vector<std::shared_ptr<IImage>> images;
+	for(auto & obj : tmpObjects)
+	{
+		if(!settings["general"]["enableUiEnhancements"].Bool())
+			break;
+		const CGTownInstance * t = dynamic_cast<const CGTownInstance *>(cb->getObj(obj));
+		if(t)
+		{
+			std::shared_ptr<CAnimation> a = GH.renderHandler().loadAnimation(AnimationPath::builtin("ITPA"));
+			a->preload();
+			images.push_back(a->getImage(t->town->clientInfo.icons[t->hasFort()][false] + 2)->scaleFast(Point(35, 23)));
+		}
+	}
+
+	auto wnd = std::make_shared<CObjectListWindow>(tempList, localIcon, localTitle, localDescription, selectCallback, 0, images);
 	wnd->onExit = cancelCallback;
 	wnd->onExit = cancelCallback;
+	wnd->onPopup = [this, tmpObjects](int index) { CRClickPopup::createAndPush(cb->getObj(tmpObjects[index]), GH.getCursorPosition()); };
+	wnd->onClicked = [this, tmpObjects](int index) { adventureInt->centerOnObject(cb->getObj(tmpObjects[index])); GH.windows().totalRedraw(); };
 	GH.windows().pushWindow(wnd);
 	GH.windows().pushWindow(wnd);
 }
 }
 
 
@@ -1155,16 +1184,16 @@ void CPlayerInterface::heroBonusChanged( const CGHeroInstance *hero, const Bonus
 	}
 	}
 }
 }
 
 
-void CPlayerInterface::saveGame( BinarySerializer & h, const int version )
+void CPlayerInterface::saveGame( BinarySerializer & h )
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	localState->serialize(h, version);
+	localState->serialize(h);
 }
 }
 
 
-void CPlayerInterface::loadGame( BinaryDeserializer & h, const int version )
+void CPlayerInterface::loadGame( BinaryDeserializer & h )
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	localState->serialize(h, version);
+	localState->serialize(h);
 	firstCall = -1;
 	firstCall = -1;
 }
 }
 
 
@@ -1676,7 +1705,8 @@ void CPlayerInterface::showTavernWindow(const CGObjectInstance * object, const C
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	auto onWindowClosed = [this, queryID](){
 	auto onWindowClosed = [this, queryID](){
-		cb->selectionMade(0, queryID);
+		if (queryID != QueryID::NONE)
+			cb->selectionMade(0, queryID);
 	};
 	};
 	GH.windows().createAndPushWindow<CTavernWindow>(object, onWindowClosed);
 	GH.windows().createAndPushWindow<CTavernWindow>(object, onWindowClosed);
 }
 }

+ 3 - 2
client/CPlayerInterface.h

@@ -87,6 +87,7 @@ public: // TODO: make private
 	//During battle is quick combat mode is used
 	//During battle is quick combat mode is used
 	std::shared_ptr<CBattleGameInterface> autofightingAI; //AI that makes decisions
 	std::shared_ptr<CBattleGameInterface> autofightingAI; //AI that makes decisions
 	bool isAutoFightOn; //Flag, switch it to stop quick combat. Don't touch if there is no battle interface.
 	bool isAutoFightOn; //Flag, switch it to stop quick combat. Don't touch if there is no battle interface.
+	bool isAutoFightEndBattle; //Flag, if battle forced to end with autocombat
 
 
 protected: // Call-ins from server, should not be called directly, but only via GameInterface
 protected: // Call-ins from server, should not be called directly, but only via GameInterface
 
 
@@ -145,8 +146,8 @@ protected: // Call-ins from server, should not be called directly, but only via
 	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 itnerface
 	void playerEndsTurn(PlayerColor player) override;
 	void playerEndsTurn(PlayerColor player) override;
-	void saveGame(BinarySerializer & h, const int version) override; //saving
-	void loadGame(BinaryDeserializer & h, const int version) override; //loading
+	void saveGame(BinarySerializer & h) override; //saving
+	void loadGame(BinaryDeserializer & h) override; //loading
 	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
 	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
 
 
 	//for battles
 	//for battles

+ 22 - 4
client/CServerHandler.cpp

@@ -142,6 +142,8 @@ CServerHandler::CServerHandler()
 	registerTypesLobbyPacks(*applier);
 	registerTypesLobbyPacks(*applier);
 }
 }
 
 
+CServerHandler::~CServerHandler() = default;
+
 void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std::vector<std::string> * names)
 void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std::vector<std::string> * names)
 {
 {
 	hostClientId = -1;
 	hostClientId = -1;
@@ -260,6 +262,9 @@ void CServerHandler::justConnectToServer(const std::string & addr, const ui16 po
 					addr.size() ? addr : getHostAddress(),
 					addr.size() ? addr : getHostAddress(),
 					port ? port : getHostPort(),
 					port ? port : getHostPort(),
 					NAME, uuid);
 					NAME, uuid);
+
+			nextClient = std::make_unique<CClient>();
+			c->iser.cb = nextClient.get();
 		}
 		}
 		catch(std::runtime_error & error)
 		catch(std::runtime_error & error)
 		{
 		{
@@ -507,6 +512,13 @@ void CServerHandler::setTurnTimerInfo(const TurnTimerInfo & info) const
 	sendLobbyPack(lstt);
 	sendLobbyPack(lstt);
 }
 }
 
 
+void CServerHandler::setExtraOptionsInfo(const ExtraOptionsInfo & info) const
+{
+	LobbySetExtraOptions lseo;
+	lseo.extraOptionsInfo = info;
+	sendLobbyPack(lseo);
+}
+
 void CServerHandler::sendMessage(const std::string & txt) const
 void CServerHandler::sendMessage(const std::string & txt) const
 {
 {
 	std::istringstream readed;
 	std::istringstream readed;
@@ -526,7 +538,8 @@ void CServerHandler::sendMessage(const std::string & txt) const
 	}
 	}
 	else if(command == "!forcep")
 	else if(command == "!forcep")
 	{
 	{
-		std::string connectedId, playerColorId;
+		std::string connectedId;
+		std::string playerColorId;
 		readed >> connectedId;
 		readed >> connectedId;
 		readed >> playerColorId;
 		readed >> playerColorId;
 		if(connectedId.length() && playerColorId.length())
 		if(connectedId.length() && playerColorId.length())
@@ -604,7 +617,9 @@ bool CServerHandler::validateGameStart(bool allowOnlyAI) const
 void CServerHandler::sendStartGame(bool allowOnlyAI) const
 void CServerHandler::sendStartGame(bool allowOnlyAI) const
 {
 {
 	verifyStateBeforeStart(allowOnlyAI ? true : settings["session"]["onlyai"].Bool());
 	verifyStateBeforeStart(allowOnlyAI ? true : settings["session"]["onlyai"].Bool());
-	GH.windows().createAndPushWindow<CLoadingScreen>();
+
+	if(!settings["session"]["headless"].Bool())
+		GH.windows().createAndPushWindow<CLoadingScreen>();
 	
 	
 	LobbyStartGame lsg;
 	LobbyStartGame lsg;
 	if(client)
 	if(client)
@@ -628,7 +643,8 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta
 {
 {
 	if(CMM)
 	if(CMM)
 		CMM->disable();
 		CMM->disable();
-	client = new CClient();
+
+	std::swap(client, nextClient);
 
 
 	highScoreCalc = nullptr;
 	highScoreCalc = nullptr;
 
 
@@ -679,7 +695,7 @@ void CServerHandler::endGameplay(bool closeConnection, bool restart)
 	}
 	}
 
 
 	client->endGame();
 	client->endGame();
-	vstd::clear_pointer(client);
+	client.reset();
 
 
 	if(!restart)
 	if(!restart)
 	{
 	{
@@ -696,6 +712,8 @@ void CServerHandler::endGameplay(bool closeConnection, bool restart)
 	
 	
 	if(c)
 	if(c)
 	{
 	{
+		nextClient = std::make_unique<CClient>();
+		c->iser.cb = nextClient.get();
 		c->enterLobbyConnectionMode();
 		c->enterLobbyConnectionMode();
 		c->disableStackSendingByID();
 		c->disableStackSendingByID();
 	}
 	}

+ 8 - 1
client/CServerHandler.h

@@ -72,6 +72,7 @@ public:
 	virtual void setDifficulty(int to) const = 0;
 	virtual void setDifficulty(int to) const = 0;
 	virtual void setTurnTimerInfo(const TurnTimerInfo &) const = 0;
 	virtual void setTurnTimerInfo(const TurnTimerInfo &) const = 0;
 	virtual void setSimturnsInfo(const SimturnsInfo &) const = 0;
 	virtual void setSimturnsInfo(const SimturnsInfo &) const = 0;
+	virtual void setExtraOptionsInfo(const ExtraOptionsInfo & info) const = 0;
 	virtual void sendMessage(const std::string & txt) const = 0;
 	virtual void sendMessage(const std::string & txt) const = 0;
 	virtual void sendGuiAction(ui8 action) const = 0; // TODO: possibly get rid of it?
 	virtual void sendGuiAction(ui8 action) const = 0; // TODO: possibly get rid of it?
 	virtual void sendStartGame(bool allowOnlyAI = false) const = 0;
 	virtual void sendStartGame(bool allowOnlyAI = false) const = 0;
@@ -94,6 +95,10 @@ class CServerHandler : public IServerAPI, public LobbyInfo
 
 
 	std::shared_ptr<HighScoreCalculation> highScoreCalc;
 	std::shared_ptr<HighScoreCalculation> highScoreCalc;
 
 
+	/// temporary helper member that exists while game in lobby mode
+	/// required to correctly deserialize gamestate using client-side game callback
+	std::unique_ptr<CClient> nextClient;
+
 	void threadHandleConnection();
 	void threadHandleConnection();
 	void threadRunServer();
 	void threadRunServer();
 	void onServerFinished();
 	void onServerFinished();
@@ -115,13 +120,14 @@ public:
 	std::shared_ptr<boost::thread> threadRunLocalServer;
 	std::shared_ptr<boost::thread> threadRunLocalServer;
 
 
 	std::shared_ptr<CConnection> c;
 	std::shared_ptr<CConnection> c;
-	CClient * client;
+	std::unique_ptr<CClient> client;
 
 
 	CondSh<bool> campaignServerRestartLock;
 	CondSh<bool> campaignServerRestartLock;
 
 
 	static const std::string localhostAddress;
 	static const std::string localhostAddress;
 
 
 	CServerHandler();
 	CServerHandler();
+	~CServerHandler();
 	
 	
 	std::string getHostAddress() const;
 	std::string getHostAddress() const;
 	ui16 getHostPort() const;
 	ui16 getHostPort() const;
@@ -158,6 +164,7 @@ public:
 	void setDifficulty(int to) const override;
 	void setDifficulty(int to) const override;
 	void setTurnTimerInfo(const TurnTimerInfo &) const override;
 	void setTurnTimerInfo(const TurnTimerInfo &) const override;
 	void setSimturnsInfo(const SimturnsInfo &) const override;
 	void setSimturnsInfo(const SimturnsInfo &) const override;
+	void setExtraOptionsInfo(const ExtraOptionsInfo &) const override;
 	void sendMessage(const std::string & txt) const override;
 	void sendMessage(const std::string & txt) const override;
 	void sendGuiAction(ui8 action) const override;
 	void sendGuiAction(ui8 action) const override;
 	void sendRestartGame() const override;
 	void sendRestartGame() const override;

+ 31 - 7
client/CVideoHandler.cpp

@@ -101,8 +101,8 @@ bool CVideoPlayer::open(const VideoPath & fname, bool scale)
 }
 }
 
 
 // loop = to loop through the video
 // loop = to loop through the video
-// useOverlay = directly write to the screen.
-bool CVideoPlayer::open(const VideoPath & videoToOpen, bool loop, bool useOverlay, bool scale)
+// overlay = directly write to the screen.
+bool CVideoPlayer::open(const VideoPath & videoToOpen, bool loop, bool overlay, bool scale)
 {
 {
 	close();
 	close();
 
 
@@ -199,7 +199,7 @@ bool CVideoPlayer::open(const VideoPath & videoToOpen, bool loop, bool useOverla
 	}
 	}
 
 
 	// Allocate a place to put our YUV image on that screen
 	// Allocate a place to put our YUV image on that screen
-	if (useOverlay)
+	if (overlay)
 	{
 	{
 		texture = SDL_CreateTexture( mainRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STATIC, pos.w, pos.h);
 		texture = SDL_CreateTexture( mainRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STATIC, pos.w, pos.h);
 	}
 	}
@@ -624,7 +624,7 @@ Point CVideoPlayer::size()
 }
 }
 
 
 // Plays a video. Only works for overlays.
 // Plays a video. Only works for overlays.
-bool CVideoPlayer::playVideo(int x, int y, bool stopOnKey)
+bool CVideoPlayer::playVideo(int x, int y, bool stopOnKey, bool overlay)
 {
 {
 	// Note: either the windows player or the linux player is
 	// Note: either the windows player or the linux player is
 	// broken. Compensate here until the bug is found.
 	// broken. Compensate here until the bug is found.
@@ -647,7 +647,14 @@ bool CVideoPlayer::playVideo(int x, int y, bool stopOnKey)
 
 
 		SDL_Rect rect = CSDL_Ext::toSDL(pos);
 		SDL_Rect rect = CSDL_Ext::toSDL(pos);
 
 
-		SDL_RenderFillRect(mainRenderer, &rect);
+		if(overlay)
+		{
+			SDL_RenderFillRect(mainRenderer, &rect);
+		}
+		else
+		{
+			SDL_RenderClear(mainRenderer);
+		}
 		SDL_RenderCopy(mainRenderer, texture, nullptr, &rect);
 		SDL_RenderCopy(mainRenderer, texture, nullptr, &rect);
 		SDL_RenderPresent(mainRenderer);
 		SDL_RenderPresent(mainRenderer);
 
 
@@ -672,10 +679,27 @@ bool CVideoPlayer::playVideo(int x, int y, bool stopOnKey)
 	return true;
 	return true;
 }
 }
 
 
-bool CVideoPlayer::openAndPlayVideo(const VideoPath & name, int x, int y, bool stopOnKey, bool scale)
+bool CVideoPlayer::openAndPlayVideo(const VideoPath & name, int x, int y, EVideoType videoType)
 {
 {
+	bool scale;
+	bool stopOnKey;
+	bool overlay;
+
+	switch(videoType)
+	{
+		case EVideoType::INTRO:
+			stopOnKey = true;
+			scale = true;
+			overlay = false;
+			break;
+		case EVideoType::SPELLBOOK:
+		default:
+			stopOnKey = false;
+			scale = false;
+			overlay = true;
+	}
 	open(name, false, true, scale);
 	open(name, false, true, scale);
-	bool ret = playVideo(x, y,  stopOnKey);
+	bool ret = playVideo(x, y,  stopOnKey, overlay);
 	close();
 	close();
 	return ret;
 	return ret;
 }
 }

+ 9 - 3
client/CVideoHandler.h

@@ -15,6 +15,12 @@
 struct SDL_Surface;
 struct SDL_Surface;
 struct SDL_Texture;
 struct SDL_Texture;
 
 
+enum class EVideoType : ui8
+{
+	INTRO = 0, // use entire window: stopOnKey = true, scale = true, overlay = false
+	SPELLBOOK  // overlay video: stopOnKey = false, scale = false, overlay = true
+};
+
 class IVideoPlayer : boost::noncopyable
 class IVideoPlayer : boost::noncopyable
 {
 {
 public:
 public:
@@ -33,7 +39,7 @@ class IMainVideoPlayer : public IVideoPlayer
 public:
 public:
 	virtual ~IMainVideoPlayer() = default;
 	virtual ~IMainVideoPlayer() = default;
 	virtual void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true, std::function<void()> restart = nullptr){}
 	virtual void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true, std::function<void()> restart = nullptr){}
-	virtual bool openAndPlayVideo(const VideoPath & name, int x, int y, bool stopOnKey = false, bool scale = false)
+	virtual bool openAndPlayVideo(const VideoPath & name, int x, int y, EVideoType videoType)
 	{
 	{
 		return false;
 		return false;
 	}
 	}
@@ -90,7 +96,7 @@ class CVideoPlayer final : public IMainVideoPlayer
 	double frameTime;
 	double frameTime;
 	bool doLoop;				// loop through video
 	bool doLoop;				// loop through video
 
 
-	bool playVideo(int x, int y, bool stopOnKey);
+	bool playVideo(int x, int y, bool stopOnKey, bool overlay);
 	bool open(const VideoPath & fname, bool loop, bool useOverlay = false, bool scale = false);
 	bool open(const VideoPath & fname, bool loop, bool useOverlay = false, bool scale = false);
 public:
 public:
 	CVideoPlayer();
 	CVideoPlayer();
@@ -106,7 +112,7 @@ public:
 	void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true, std::function<void()> onVideoRestart = nullptr) override; //moves to next frame if appropriate, and blits it or blits only if redraw parameter is set true
 	void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true, std::function<void()> onVideoRestart = nullptr) override; //moves to next frame if appropriate, and blits it or blits only if redraw parameter is set true
 
 
 	// Opens video, calls playVideo, closes video; returns playVideo result (if whole video has been played)
 	// Opens video, calls playVideo, closes video; returns playVideo result (if whole video has been played)
-	bool openAndPlayVideo(const VideoPath & name, int x, int y, bool stopOnKey = false, bool scale = false) override;
+	bool openAndPlayVideo(const VideoPath & name, int x, int y, EVideoType videoType) override;
 
 
 	std::pair<std::unique_ptr<ui8 []>, si64> getAudio(const VideoPath & videoToOpen) override;
 	std::pair<std::unique_ptr<ui8 []>, si64> getAudio(const VideoPath & videoToOpen) override;
 
 

+ 38 - 31
client/Client.cpp

@@ -139,14 +139,10 @@ CClient::CClient()
 	waitingRequest.clear();
 	waitingRequest.clear();
 	applier = std::make_shared<CApplier<CBaseForCLApply>>();
 	applier = std::make_shared<CApplier<CBaseForCLApply>>();
 	registerTypesClientPacks(*applier);
 	registerTypesClientPacks(*applier);
-	IObjectInterface::cb = this;
 	gs = nullptr;
 	gs = nullptr;
 }
 }
 
 
-CClient::~CClient()
-{
-	IObjectInterface::cb = nullptr;
-}
+CClient::~CClient() = default;
 
 
 const Services * CClient::services() const
 const Services * CClient::services() const
 {
 {
@@ -177,8 +173,9 @@ void CClient::newGame(CGameState * initializedGameState)
 {
 {
 	CSH->th->update();
 	CSH->th->update();
 	CMapService mapService;
 	CMapService mapService;
-	gs = initializedGameState ? initializedGameState : new CGameState();
-	gs->preInit(VLC);
+	assert(initializedGameState);
+	gs = initializedGameState;
+	gs->preInit(VLC, this);
 	logNetwork->trace("\tCreating gamestate: %i", CSH->th->getDiff());
 	logNetwork->trace("\tCreating gamestate: %i", CSH->th->getDiff());
 	if(!initializedGameState)
 	if(!initializedGameState)
 	{
 	{
@@ -200,7 +197,7 @@ void CClient::loadGame(CGameState * initializedGameState)
 	logNetwork->info("Game state was transferred over network, loading.");
 	logNetwork->info("Game state was transferred over network, loading.");
 	gs = initializedGameState;
 	gs = initializedGameState;
 
 
-	gs->preInit(VLC);
+	gs->preInit(VLC, this);
 	gs->updateOnLoad(CSH->si.get());
 	gs->updateOnLoad(CSH->si.get());
 	logNetwork->info("Game loaded, initialize interfaces.");
 	logNetwork->info("Game loaded, initialize interfaces.");
 
 
@@ -228,7 +225,7 @@ void CClient::loadGame(CGameState * initializedGameState)
 			throw std::runtime_error("Cannot open client part of " + CSH->si->mapname);
 			throw std::runtime_error("Cannot open client part of " + CSH->si->mapname);
 
 
 		std::unique_ptr<CLoadFile> loader (new CLoadFile(clientSaveName));
 		std::unique_ptr<CLoadFile> loader (new CLoadFile(clientSaveName));
-		serialize(loader->serializer, loader->serializer.fileVersion);
+		serialize(loader->serializer, loader->serializer.version);
 
 
 		logNetwork->info("Client data loaded.");
 		logNetwork->info("Client data loaded.");
 	}
 	}
@@ -241,7 +238,7 @@ void CClient::loadGame(CGameState * initializedGameState)
 	initPlayerInterfaces();
 	initPlayerInterfaces();
 }
 }
 
 
-void CClient::serialize(BinarySerializer & h, const int version)
+void CClient::serialize(BinarySerializer & h)
 {
 {
 	assert(h.saving);
 	assert(h.saving);
 	ui8 players = static_cast<ui8>(playerint.size());
 	ui8 players = static_cast<ui8>(playerint.size());
@@ -254,20 +251,17 @@ void CClient::serialize(BinarySerializer & h, const int version)
 		h & i->first;
 		h & i->first;
 		h & i->second->dllName;
 		h & i->second->dllName;
 		h & i->second->human;
 		h & i->second->human;
-		i->second->saveGame(h, version);
+		i->second->saveGame(h);
 	}
 	}
 
 
 #if SCRIPTING_ENABLED
 #if SCRIPTING_ENABLED
-	if(version >= 800)
-	{
-		JsonNode scriptsState;
-		clientScripts->serializeState(h.saving, scriptsState);
-		h & scriptsState;
-	}
+	JsonNode scriptsState;
+	clientScripts->serializeState(h.saving, scriptsState);
+	h & scriptsState;
 #endif
 #endif
 }
 }
 
 
-void CClient::serialize(BinaryDeserializer & h, const int version)
+void CClient::serialize(BinaryDeserializer & h)
 {
 {
 	assert(!h.saving);
 	assert(!h.saving);
 	ui8 players = 0;
 	ui8 players = 0;
@@ -323,7 +317,7 @@ void CClient::serialize(BinaryDeserializer & h, const int version)
 
 
 		// loadGame needs to be called after initGameInterface to load paths correctly
 		// loadGame needs to be called after initGameInterface to load paths correctly
 		// initGameInterface is called in installNewPlayerInterface
 		// initGameInterface is called in installNewPlayerInterface
-		nInt->loadGame(h, version);
+		nInt->loadGame(h);
 
 
 		if (shouldResetInterface)
 		if (shouldResetInterface)
 		{
 		{
@@ -370,7 +364,7 @@ void CClient::endGame()
 		logNetwork->info("Ending current game!");
 		logNetwork->info("Ending current game!");
 		removeGUI();
 		removeGUI();
 
 
-		vstd::clear_pointer(const_cast<CGameInfo *>(CGI)->mh);
+		CGI->mh.reset();
 		vstd::clear_pointer(gs);
 		vstd::clear_pointer(gs);
 
 
 		logNetwork->info("Deleted mapHandler and gameState.");
 		logNetwork->info("Deleted mapHandler and gameState.");
@@ -392,7 +386,7 @@ void CClient::initMapHandler()
 	// During loading CPlayerInterface from serialized state it's depend on MH
 	// During loading CPlayerInterface from serialized state it's depend on MH
 	if(!settings["session"]["headless"].Bool())
 	if(!settings["session"]["headless"].Bool())
 	{
 	{
-		const_cast<CGameInfo *>(CGI)->mh = new CMapHandler(gs->map);
+		CGI->mh = std::make_shared<CMapHandler>(gs->map);
 		logNetwork->trace("Creating mapHandler: %d ms", CSH->th->getDiff());
 		logNetwork->trace("Creating mapHandler: %d ms", CSH->th->getDiff());
 	}
 	}
 
 
@@ -414,7 +408,7 @@ void CClient::initPlayerEnvironments()
 			hasHumanPlayer = true;
 			hasHumanPlayer = true;
 	}
 	}
 
 
-	if(!hasHumanPlayer)
+	if(!hasHumanPlayer && !settings["session"]["headless"].Bool())
 	{
 	{
 		Settings session = settings.write["session"];
 		Settings session = settings.write["session"];
 		session["spectate"].Bool() = true;
 		session["spectate"].Bool() = true;
@@ -439,7 +433,7 @@ void CClient::initPlayerInterfaces()
 		if(!vstd::contains(playerint, color))
 		if(!vstd::contains(playerint, color))
 		{
 		{
 			logNetwork->info("Preparing interface for player %s", color.toString());
 			logNetwork->info("Preparing interface for player %s", color.toString());
-			if(playerInfo.second.isControlledByAI())
+			if(playerInfo.second.isControlledByAI() || settings["session"]["onlyai"].Bool())
 			{
 			{
 				bool alliedToHuman = false;
 				bool alliedToHuman = false;
 				for(auto & allyInfo : gs->scenarioOps->playerInfos)
 				for(auto & allyInfo : gs->scenarioOps->playerInfos)
@@ -560,7 +554,8 @@ int CClient::sendRequest(const CPackForServer * request, PlayerColor player)
 
 
 void CClient::battleStarted(const BattleInfo * info)
 void CClient::battleStarted(const BattleInfo * info)
 {
 {
-	std::shared_ptr<CPlayerInterface> att, def;
+	std::shared_ptr<CPlayerInterface> att;
+	std::shared_ptr<CPlayerInterface> def;
 	auto & leftSide = info->sides[0];
 	auto & leftSide = info->sides[0];
 	auto & rightSide = info->sides[1];
 	auto & rightSide = info->sides[1];
 
 
@@ -590,14 +585,26 @@ void CClient::battleStarted(const BattleInfo * info)
 		def = std::dynamic_pointer_cast<CPlayerInterface>(playerint[rightSide.color]);
 		def = std::dynamic_pointer_cast<CPlayerInterface>(playerint[rightSide.color]);
 	
 	
 	//Remove player interfaces for auto battle (quickCombat option)
 	//Remove player interfaces for auto battle (quickCombat option)
-	if(att && att->isAutoFightOn)
+	if((att && att->isAutoFightOn) || (def && def->isAutoFightOn))
 	{
 	{
-		if (att->cb->getBattle(info->battleID)->battleGetTacticDist())
+		auto endTacticPhaseIfEligible = [info](const CPlayerInterface * interface)
 		{
 		{
-			auto side = att->cb->getBattle(info->battleID)->playerToSide(att->playerID);
-			auto action = BattleAction::makeEndOFTacticPhase(*side);
-			att->cb->battleMakeTacticAction(info->battleID, action);
-		}
+			if (interface->cb->getBattle(info->battleID)->battleGetTacticDist())
+			{
+				auto side = interface->cb->getBattle(info->battleID)->playerToSide(interface->playerID);
+
+				if(interface->playerID == info->sides[info->tacticsSide].color)
+				{
+					auto action = BattleAction::makeEndOFTacticPhase(*side);
+					interface->cb->battleMakeTacticAction(info->battleID, action);
+				}
+			}
+		};
+
+		if(att && att->isAutoFightOn)
+			endTacticPhaseIfEligible(att.get());
+		else // def && def->isAutoFightOn
+			endTacticPhaseIfEligible(def.get());
 
 
 		att.reset();
 		att.reset();
 		def.reset();
 		def.reset();
@@ -671,7 +678,7 @@ std::shared_ptr<const CPathsInfo> CClient::getPathsInfo(const CGHeroInstance * h
 
 
 	if(iter == std::end(pathCache))
 	if(iter == std::end(pathCache))
 	{
 	{
-		std::shared_ptr<CPathsInfo> paths = std::make_shared<CPathsInfo>(getMapSize(), h);
+		auto paths = std::make_shared<CPathsInfo>(getMapSize(), h);
 
 
 		gs->calculatePaths(h, *paths.get());
 		gs->calculatePaths(h, *paths.get());
 
 

+ 2 - 2
client/Client.h

@@ -131,8 +131,8 @@ public:
 
 
 	void newGame(CGameState * gameState);
 	void newGame(CGameState * gameState);
 	void loadGame(CGameState * gameState);
 	void loadGame(CGameState * gameState);
-	void serialize(BinarySerializer & h, const int version);
-	void serialize(BinaryDeserializer & h, const int version);
+	void serialize(BinarySerializer & h);
+	void serialize(BinaryDeserializer & h);
 
 
 	void save(const std::string & fname);
 	void save(const std::string & fname);
 	void endGame();
 	void endGame();

+ 7 - 5
client/ClientCommandManager.cpp

@@ -203,7 +203,7 @@ void ClientCommandManager::handleConvertTextCommand()
 		try
 		try
 		{
 		{
 			// load and drop loaded map - we only need loader to run over all maps
 			// load and drop loaded map - we only need loader to run over all maps
-			mapService.loadMap(mapName);
+			mapService.loadMap(mapName, nullptr);
 		}
 		}
 		catch(std::exception & e)
 		catch(std::exception & e)
 		{
 		{
@@ -216,7 +216,7 @@ void ClientCommandManager::handleConvertTextCommand()
 	{
 	{
 		auto state = CampaignHandler::getCampaign(campaignName.getName());
 		auto state = CampaignHandler::getCampaign(campaignName.getName());
 		for (auto const & part : state->allScenarios())
 		for (auto const & part : state->allScenarios())
-			state->getMap(part);
+			state->getMap(part, nullptr);
 	}
 	}
 
 
 	VLC->generaltexth->dumpAllTexts();
 	VLC->generaltexth->dumpAllTexts();
@@ -272,7 +272,7 @@ void ClientCommandManager::handleGetScriptsCommand()
 
 
 	boost::filesystem::create_directories(outPath);
 	boost::filesystem::create_directories(outPath);
 
 
-	for(auto & kv : VLC->scriptHandler->objects)
+	for(const auto & kv : VLC->scriptHandler->objects)
 	{
 	{
 		std::string name = kv.first;
 		std::string name = kv.first;
 		boost::algorithm::replace_all(name,":","_");
 		boost::algorithm::replace_all(name,":","_");
@@ -379,7 +379,8 @@ void ClientCommandManager::handleBonusesCommand(std::istringstream & singleWordB
 void ClientCommandManager::handleTellCommand(std::istringstream& singleWordBuffer)
 void ClientCommandManager::handleTellCommand(std::istringstream& singleWordBuffer)
 {
 {
 	std::string what;
 	std::string what;
-	int id1, id2;
+	int id1;
+	int id2;
 	singleWordBuffer >> what >> id1 >> id2;
 	singleWordBuffer >> what >> id1 >> id2;
 
 
 	if(what == "hs")
 	if(what == "hs")
@@ -399,7 +400,8 @@ void ClientCommandManager::handleMpCommand()
 
 
 void ClientCommandManager::handleSetCommand(std::istringstream& singleWordBuffer)
 void ClientCommandManager::handleSetCommand(std::istringstream& singleWordBuffer)
 {
 {
-	std::string what, value;
+	std::string what;
+	std::string value;
 	singleWordBuffer >> what;
 	singleWordBuffer >> what;
 
 
 	Settings config = settings.write["session"][what];
 	Settings config = settings.write["session"][what];

+ 21 - 9
client/NetPacksClient.cpp

@@ -39,6 +39,7 @@
 #include "../lib/StartInfo.h"
 #include "../lib/StartInfo.h"
 #include "../lib/CConfigHandler.h"
 #include "../lib/CConfigHandler.h"
 #include "../lib/mapObjects/CGMarket.h"
 #include "../lib/mapObjects/CGMarket.h"
+#include "../lib/mapObjects/CGTownInstance.h"
 #include "../lib/gameState/CGameState.h"
 #include "../lib/gameState/CGameState.h"
 #include "../lib/CStack.h"
 #include "../lib/CStack.h"
 #include "../lib/battle/BattleInfo.h"
 #include "../lib/battle/BattleInfo.h"
@@ -156,6 +157,9 @@ void ApplyClientNetPackVisitor::visitSetMana(SetMana & pack)
 	const CGHeroInstance *h = cl.getHero(pack.hid);
 	const CGHeroInstance *h = cl.getHero(pack.hid);
 	callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroManaPointsChanged, h);
 	callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroManaPointsChanged, h);
 
 
+	if(settings["session"]["headless"].Bool())
+		return;
+
 	for (auto window : GH.windows().findWindows<BattleWindow>())
 	for (auto window : GH.windows().findWindows<BattleWindow>())
 		window->heroManaPointsChanged(h);
 		window->heroManaPointsChanged(h);
 }
 }
@@ -466,7 +470,8 @@ void ApplyFirstClientNetPackVisitor::visitRemoveObject(RemoveObject & pack)
 			i->second->objectRemoved(o, pack.initiator);
 			i->second->objectRemoved(o, pack.initiator);
 	}
 	}
 
 
-	CGI->mh->waitForOngoingAnimations();
+	if(CGI->mh)
+		CGI->mh->waitForOngoingAnimations();
 }
 }
 
 
 void ApplyClientNetPackVisitor::visitRemoveObject(RemoveObject & pack)
 void ApplyClientNetPackVisitor::visitRemoveObject(RemoveObject & pack)
@@ -552,9 +557,11 @@ void ApplyClientNetPackVisitor::visitNewStructures(NewStructures & pack)
 	}
 	}
 
 
 	// invalidate section of map view with our object and force an update
 	// invalidate section of map view with our object and force an update
-	CGI->mh->onObjectInstantRemove(town, town->getOwner());
-	CGI->mh->onObjectInstantAdd(town, town->getOwner());
-
+	if(CGI->mh)
+	{
+		CGI->mh->onObjectInstantRemove(town, town->getOwner());
+		CGI->mh->onObjectInstantAdd(town, town->getOwner());
+	}
 }
 }
 void ApplyClientNetPackVisitor::visitRazeStructures(RazeStructures & pack)
 void ApplyClientNetPackVisitor::visitRazeStructures(RazeStructures & pack)
 {
 {
@@ -565,8 +572,11 @@ void ApplyClientNetPackVisitor::visitRazeStructures(RazeStructures & pack)
 	}
 	}
 
 
 	// invalidate section of map view with our object and force an update
 	// invalidate section of map view with our object and force an update
-	CGI->mh->onObjectInstantRemove(town, town->getOwner());
-	CGI->mh->onObjectInstantAdd(town, town->getOwner());
+	if(CGI->mh)
+	{
+		CGI->mh->onObjectInstantRemove(town, town->getOwner());
+		CGI->mh->onObjectInstantAdd(town, town->getOwner());
+	}
 }
 }
 
 
 void ApplyClientNetPackVisitor::visitSetAvailableCreatures(SetAvailableCreatures & pack)
 void ApplyClientNetPackVisitor::visitSetAvailableCreatures(SetAvailableCreatures & pack)
@@ -650,7 +660,7 @@ void ApplyFirstClientNetPackVisitor::visitSetObjectProperty(SetObjectProperty &
 	}
 	}
 
 
 	// invalidate section of map view with our object and force an update with new flag color
 	// invalidate section of map view with our object and force an update with new flag color
-	if (pack.what == ObjProperty::OWNER)
+	if (pack.what == ObjProperty::OWNER && CGI->mh)
 	{
 	{
 		auto object = gs.getObjInstance(pack.id);
 		auto object = gs.getObjInstance(pack.id);
 		CGI->mh->onObjectInstantRemove(object, object->getOwner());
 		CGI->mh->onObjectInstantRemove(object, object->getOwner());
@@ -667,7 +677,7 @@ void ApplyClientNetPackVisitor::visitSetObjectProperty(SetObjectProperty & pack)
 	}
 	}
 
 
 	// invalidate section of map view with our object and force an update with new flag color
 	// invalidate section of map view with our object and force an update with new flag color
-	if (pack.what == ObjProperty::OWNER)
+	if (pack.what == ObjProperty::OWNER && CGI->mh)
 	{
 	{
 		auto object = gs.getObjInstance(pack.id);
 		auto object = gs.getObjInstance(pack.id);
 		CGI->mh->onObjectInstantAdd(object, object->getOwner());
 		CGI->mh->onObjectInstantAdd(object, object->getOwner());
@@ -1022,7 +1032,9 @@ void ApplyClientNetPackVisitor::visitNewObject(NewObject & pack)
 		if(gs.isVisible(obj, i->first))
 		if(gs.isVisible(obj, i->first))
 			i->second->newObject(obj);
 			i->second->newObject(obj);
 	}
 	}
-	CGI->mh->waitForOngoingAnimations();
+
+	if(CGI->mh)
+		CGI->mh->waitForOngoingAnimations();
 }
 }
 
 
 void ApplyClientNetPackVisitor::visitSetAvailableArtifacts(SetAvailableArtifacts & pack)
 void ApplyClientNetPackVisitor::visitSetAvailableArtifacts(SetAvailableArtifacts & pack)

+ 4 - 0
client/NetPacksLobbyClient.cpp

@@ -16,6 +16,7 @@
 #include "lobby/OptionsTab.h"
 #include "lobby/OptionsTab.h"
 #include "lobby/RandomMapTab.h"
 #include "lobby/RandomMapTab.h"
 #include "lobby/TurnOptionsTab.h"
 #include "lobby/TurnOptionsTab.h"
+#include "lobby/ExtraOptionsTab.h"
 #include "lobby/SelectionTab.h"
 #include "lobby/SelectionTab.h"
 #include "lobby/CBonusSelection.h"
 #include "lobby/CBonusSelection.h"
 
 
@@ -99,6 +100,9 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyGuiAction(LobbyGuiAction & pack
 	case LobbyGuiAction::OPEN_TURN_OPTIONS:
 	case LobbyGuiAction::OPEN_TURN_OPTIONS:
 		lobby->toggleTab(lobby->tabTurnOptions);
 		lobby->toggleTab(lobby->tabTurnOptions);
 		break;
 		break;
+	case LobbyGuiAction::OPEN_EXTRA_OPTIONS:
+		lobby->toggleTab(lobby->tabExtraOptions);
+		break;
 	}
 	}
 }
 }
 
 

+ 2 - 2
client/PlayerLocalState.h

@@ -47,7 +47,7 @@ public:
 		int spellbookLastTabAdvmap = 4;
 		int spellbookLastTabAdvmap = 4;
 
 
 		template<typename Handler>
 		template<typename Handler>
-		void serialize(Handler & h, const int version)
+		void serialize(Handler & h)
 		{
 		{
 			h & spellbookLastPageBattle;
 			h & spellbookLastPageBattle;
 			h & spellbookLastPageAdvmap;
 			h & spellbookLastPageAdvmap;
@@ -94,7 +94,7 @@ public:
 	void setSelection(const CArmedInstance *sel);
 	void setSelection(const CArmedInstance *sel);
 
 
 	template<typename Handler>
 	template<typename Handler>
-	void serialize(Handler & h, int version)
+	void serialize(Handler & h)
 	{
 	{
 		//WARNING: this code is broken and not used. See CClient::loadGame
 		//WARNING: this code is broken and not used. See CClient::loadGame
 		std::map<const CGHeroInstance *, int3> pathsMap; //hero -> dest
 		std::map<const CGHeroInstance *, int3> pathsMap; //hero -> dest

+ 1 - 1
client/adventureMap/AdventureMapInterface.cpp

@@ -314,7 +314,7 @@ void AdventureMapInterface::onSelectionChanged(const CArmedInstance *sel)
 		auto town = dynamic_cast<const CGTownInstance*>(sel);
 		auto town = dynamic_cast<const CGTownInstance*>(sel);
 
 
 		widget->getInfoBar()->showTownSelection(town);
 		widget->getInfoBar()->showTownSelection(town);
-		widget->getTownList()->updateWidget();;
+		widget->getTownList()->updateWidget();
 		widget->getTownList()->select(town);
 		widget->getTownList()->select(town);
 		widget->getHeroList()->select(nullptr);
 		widget->getHeroList()->select(nullptr);
 		onHeroChanged(nullptr);
 		onHeroChanged(nullptr);

+ 10 - 6
client/adventureMap/AdventureOptions.cpp

@@ -23,6 +23,7 @@
 
 
 #include "../../CCallback.h"
 #include "../../CCallback.h"
 #include "../../lib/StartInfo.h"
 #include "../../lib/StartInfo.h"
+#include "../../lib/CGeneralTextHandler.h"
 
 
 AdventureOptions::AdventureOptions()
 AdventureOptions::AdventureOptions()
 	: CWindowObject(PLAYER_COLORED, ImagePath::builtin("ADVOPTS"))
 	: CWindowObject(PLAYER_COLORED, ImagePath::builtin("ADVOPTS"))
@@ -30,12 +31,7 @@ AdventureOptions::AdventureOptions()
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
 
 	viewWorld = std::make_shared<CButton>(Point(24, 23), AnimationPath::builtin("ADVVIEW.DEF"), CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_WORLD);
 	viewWorld = std::make_shared<CButton>(Point(24, 23), AnimationPath::builtin("ADVVIEW.DEF"), CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_WORLD);
-	viewWorld->addCallback( [] { LOCPLINT->viewWorldMap(); });
-
-	exit = std::make_shared<CButton>(Point(204, 313), AnimationPath::builtin("IOK6432.DEF"), CButton::tooltip(), std::bind(&AdventureOptions::close, this), EShortcut::GLOBAL_RETURN);
-
-	scenInfo = std::make_shared<CButton>(Point(24, 198), AnimationPath::builtin("ADVINFO.DEF"), CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_SCENARIO);
-	scenInfo->addCallback(AdventureOptions::showScenarioInfo);
+	viewWorld->addCallback([] { LOCPLINT->viewWorldMap(); });
 
 
 	puzzle = std::make_shared<CButton>(Point(24, 81), AnimationPath::builtin("ADVPUZ.DEF"), CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_PUZZLE);
 	puzzle = std::make_shared<CButton>(Point(24, 81), AnimationPath::builtin("ADVPUZ.DEF"), CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_PUZZLE);
 	puzzle->addCallback(std::bind(&CPlayerInterface::showPuzzleMap, LOCPLINT));
 	puzzle->addCallback(std::bind(&CPlayerInterface::showPuzzleMap, LOCPLINT));
@@ -45,6 +41,14 @@ AdventureOptions::AdventureOptions()
 		dig->addCallback(std::bind(&CPlayerInterface::tryDigging, LOCPLINT, h));
 		dig->addCallback(std::bind(&CPlayerInterface::tryDigging, LOCPLINT, h));
 	else
 	else
 		dig->block(true);
 		dig->block(true);
+
+	scenInfo = std::make_shared<CButton>(Point(24, 198), AnimationPath::builtin("ADVINFO.DEF"), CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_SCENARIO);
+	scenInfo->addCallback(AdventureOptions::showScenarioInfo);
+	
+	replay = std::make_shared<CButton>(Point(24, 257), AnimationPath::builtin("ADVTURN.DEF"), CButton::tooltip(), [&](){ close(); });
+	replay->addCallback([]{ LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.replayOpponentTurnNotImplemented")); });
+
+	exit = std::make_shared<CButton>(Point(203, 313), AnimationPath::builtin("IOK6432.DEF"), CButton::tooltip(), std::bind(&AdventureOptions::close, this), EShortcut::GLOBAL_RETURN);
 }
 }
 
 
 void AdventureOptions::showScenarioInfo()
 void AdventureOptions::showScenarioInfo()

+ 1 - 1
client/adventureMap/AdventureOptions.h

@@ -21,7 +21,7 @@ class AdventureOptions : public CWindowObject
 	std::shared_ptr<CButton> puzzle;
 	std::shared_ptr<CButton> puzzle;
 	std::shared_ptr<CButton> dig;
 	std::shared_ptr<CButton> dig;
 	std::shared_ptr<CButton> scenInfo;
 	std::shared_ptr<CButton> scenInfo;
-	/*std::shared_ptr<CButton> replay*/
+	std::shared_ptr<CButton> replay;
 
 
 public:
 public:
 	AdventureOptions();
 	AdventureOptions();

+ 2 - 1
client/adventureMap/CInfoBar.cpp

@@ -134,7 +134,8 @@ CInfoBar::VisibleGameStatusInfo::VisibleGameStatusInfo()
 			halls.at(hallLevel)++;
 			halls.at(hallLevel)++;
 	}
 	}
 
 
-	std::vector<PlayerColor> allies, enemies;
+	std::vector<PlayerColor> allies;
+	std::vector<PlayerColor> enemies;
 
 
 	//generate list of allies and enemies
 	//generate list of allies and enemies
 	for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++)
 	for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++)

+ 1 - 1
client/adventureMap/CList.cpp

@@ -263,7 +263,7 @@ void CHeroList::CHeroItem::showTooltip()
 
 
 std::string CHeroList::CHeroItem::getHoverText()
 std::string CHeroList::CHeroItem::getHoverText()
 {
 {
-	return boost::str(boost::format(CGI->generaltexth->allTexts[15]) % hero->getNameTranslated() % hero->type->heroClass->getNameTranslated());
+	return boost::str(boost::format(CGI->generaltexth->allTexts[15]) % hero->getNameTranslated() % hero->getClassNameTranslated());
 }
 }
 
 
 void CHeroList::CHeroItem::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
 void CHeroList::CHeroItem::gesture(bool on, const Point & initialPosition, const Point & finalPosition)

+ 6 - 5
client/battle/BattleActionsController.cpp

@@ -29,6 +29,7 @@
 #include "../../CCallback.h"
 #include "../../CCallback.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/CRandomGenerator.h"
 #include "../../lib/CStack.h"
 #include "../../lib/CStack.h"
 #include "../../lib/battle/BattleAction.h"
 #include "../../lib/battle/BattleAction.h"
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/spells/CSpellHandler.h"
@@ -750,7 +751,7 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B
 
 
 			if (!spellcastingModeActive())
 			if (!spellcastingModeActive())
 			{
 			{
-				if (action.spell().toSpell())
+				if (action.spell().hasValue())
 				{
 				{
 					owner.giveCommand(EActionType::MONSTER_SPELL, targetHex, action.spell());
 					owner.giveCommand(EActionType::MONSTER_SPELL, targetHex, action.spell());
 				}
 				}
@@ -887,17 +888,17 @@ void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterS
 	{
 	{
 		// faerie dragon can cast only one, randomly selected spell until their next move
 		// faerie dragon can cast only one, randomly selected spell until their next move
 		//TODO: faerie dragon type spell should be selected by server
 		//TODO: faerie dragon type spell should be selected by server
-		const auto * spellToCast = owner.getBattle()->getRandomCastedSpell(CRandomGenerator::getDefault(), casterStack).toSpell();
+		const auto spellToCast = owner.getBattle()->getRandomCastedSpell(CRandomGenerator::getDefault(), casterStack);
 
 
-		if (spellToCast)
-			creatureSpells.push_back(spellToCast);
+		if (spellToCast.hasValue())
+			creatureSpells.push_back(spellToCast.toSpell());
 	}
 	}
 
 
 	TConstBonusListPtr bl = casterStack->getBonuses(Selector::type()(BonusType::SPELLCASTER));
 	TConstBonusListPtr bl = casterStack->getBonuses(Selector::type()(BonusType::SPELLCASTER));
 
 
 	for(const auto & bonus : *bl)
 	for(const auto & bonus : *bl)
 	{
 	{
-		if (bonus->additionalInfo[0] <= 0)
+		if (bonus->additionalInfo[0] <= 0 && bonus->subtype.as<SpellID>().hasValue())
 			creatureSpells.push_back(bonus->subtype.as<SpellID>().toSpell());
 			creatureSpells.push_back(bonus->subtype.as<SpellID>().toSpell());
 	}
 	}
 }
 }

+ 4 - 4
client/battle/BattleInterface.cpp

@@ -352,13 +352,13 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
 	CCS->curh->set(Cursor::Combat::BLOCKED);
 	CCS->curh->set(Cursor::Combat::BLOCKED);
 
 
 	const SpellID spellID = sc->spellID;
 	const SpellID spellID = sc->spellID;
-	const CSpell * spell = spellID.toSpell();
-	auto targetedTile = sc->tile;
 
 
-	assert(spell);
-	if(!spell)
+	if(!spellID.hasValue())
 		return;
 		return;
 
 
+	const CSpell * spell = spellID.toSpell();
+	auto targetedTile = sc->tile;
+
 	const AudioPath & castSoundPath = spell->getCastSound();
 	const AudioPath & castSoundPath = spell->getCastSound();
 
 
 	if (!castSoundPath.empty())
 	if (!castSoundPath.empty())

+ 3 - 5
client/battle/BattleInterfaceClasses.cpp

@@ -449,12 +449,10 @@ void HeroInfoBasicPanel::show(Canvas & to)
 }
 }
 
 
 
 
-StackInfoBasicPanel::StackInfoBasicPanel(const CStack * stack, Point * position, bool initializeBackground)
+StackInfoBasicPanel::StackInfoBasicPanel(const CStack * stack, bool initializeBackground)
 	: CIntObject(0)
 	: CIntObject(0)
 {
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	if (position != nullptr)
-		moveTo(*position);
 
 
 	if(initializeBackground)
 	if(initializeBackground)
 	{
 	{
@@ -530,7 +528,7 @@ void StackInfoBasicPanel::initializeData(const CStack * stack)
 		if (hasGraphics)
 		if (hasGraphics)
 		{
 		{
 			//FIXME: support permanent duration
 			//FIXME: support permanent duration
-			int duration = stack->getBonusLocalFirst(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(effect)))->turnsRemain;
+			int duration = stack->getFirstBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(effect)))->turnsRemain;
 
 
 			icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SpellInt"), effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed));
 			icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SpellInt"), effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed));
 			if(settings["general"]["enableUiEnhancements"].Bool())
 			if(settings["general"]["enableUiEnhancements"].Bool())
@@ -585,7 +583,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 	exit = std::make_shared<CButton>(Point(384, 505), AnimationPath::builtin("iok6432.def"), std::make_pair("", ""), [&](){ bExitf();}, EShortcut::GLOBAL_ACCEPT);
 	exit = std::make_shared<CButton>(Point(384, 505), AnimationPath::builtin("iok6432.def"), std::make_pair("", ""), [&](){ bExitf();}, EShortcut::GLOBAL_ACCEPT);
 	exit->setBorderColor(Colors::METALLIC_GOLD);
 	exit->setBorderColor(Colors::METALLIC_GOLD);
 	
 	
-	if(allowReplay)
+	if(allowReplay || owner.cb->getStartInfo()->extraOptionsInfo.unlimitedReplay)
 	{
 	{
 		repeat = std::make_shared<CButton>(Point(24, 505), AnimationPath::builtin("icn6432.def"), std::make_pair("", ""), [&](){ bRepeatf();}, EShortcut::GLOBAL_CANCEL);
 		repeat = std::make_shared<CButton>(Point(24, 505), AnimationPath::builtin("icn6432.def"), std::make_pair("", ""), [&](){ bRepeatf();}, EShortcut::GLOBAL_CANCEL);
 		repeat->setBorderColor(Colors::METALLIC_GOLD);
 		repeat->setBorderColor(Colors::METALLIC_GOLD);

+ 1 - 1
client/battle/BattleInterfaceClasses.h

@@ -155,7 +155,7 @@ private:
 	std::vector<std::shared_ptr<CMultiLineLabel>> labelsMultiline;
 	std::vector<std::shared_ptr<CMultiLineLabel>> labelsMultiline;
 	std::vector<std::shared_ptr<CAnimImage>> icons;
 	std::vector<std::shared_ptr<CAnimImage>> icons;
 public:
 public:
-	StackInfoBasicPanel(const CStack * stack, Point * position, bool initializeBackground = true);
+	StackInfoBasicPanel(const CStack * stack, bool initializeBackground = true);
 
 
 	void show(Canvas & to) override;
 	void show(Canvas & to) override;
 
 

+ 5 - 2
client/battle/BattleStacksController.cpp

@@ -30,11 +30,14 @@
 #include "../render/Colors.h"
 #include "../render/Colors.h"
 #include "../render/Canvas.h"
 #include "../render/Canvas.h"
 #include "../render/IRenderHandler.h"
 #include "../render/IRenderHandler.h"
+#include "../render/Graphics.h"
+#include "../render/IFont.h"
 
 
 #include "../../CCallback.h"
 #include "../../CCallback.h"
 #include "../../lib/spells/ISpellMechanics.h"
 #include "../../lib/spells/ISpellMechanics.h"
 #include "../../lib/battle/BattleAction.h"
 #include "../../lib/battle/BattleAction.h"
 #include "../../lib/battle/BattleHex.h"
 #include "../../lib/battle/BattleHex.h"
+#include "../../lib/CRandomGenerator.h"
 #include "../../lib/CStack.h"
 #include "../../lib/CStack.h"
 #include "../../lib/CondSh.h"
 #include "../../lib/CondSh.h"
 #include "../../lib/TextOperations.h"
 #include "../../lib/TextOperations.h"
@@ -327,10 +330,10 @@ void BattleStacksController::showStackAmountBox(Canvas & canvas, const CStack *
 			boxPosition = owner.fieldController->hexPositionLocal(frontPos).center() + Point(-8, -14);
 			boxPosition = owner.fieldController->hexPositionLocal(frontPos).center() + Point(-8, -14);
 	}
 	}
 
 
-	Point textPosition = amountBG->dimensions()/2 + boxPosition + Point(0, 1);
+	Point textPosition = Point(amountBG->dimensions().x/2 + boxPosition.x, boxPosition.y + graphics->fonts[EFonts::FONT_TINY]->getLineHeight() - 6);
 
 
 	canvas.draw(amountBG, boxPosition);
 	canvas.draw(amountBG, boxPosition);
-	canvas.drawText(textPosition, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::CENTER, TextOperations::formatMetric(stack->getCount(), 4));
+	canvas.drawText(textPosition, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::TOPCENTER, TextOperations::formatMetric(stack->getCount(), 4));
 }
 }
 
 
 void BattleStacksController::showStack(Canvas & canvas, const CStack * stack)
 void BattleStacksController::showStack(Canvas & canvas, const CStack * stack)

+ 94 - 19
client/battle/BattleWindow.cpp

@@ -41,6 +41,8 @@
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/filesystem/ResourcePath.h"
 #include "../../lib/filesystem/ResourcePath.h"
 #include "../../lib/StartInfo.h"
 #include "../../lib/StartInfo.h"
+#include "../../lib/battle/BattleInfo.h"
+#include "../../lib/CPlayerState.h"
 #include "../windows/settings/SettingsMainWindow.h"
 #include "../windows/settings/SettingsMainWindow.h"
 
 
 BattleWindow::BattleWindow(BattleInterface & owner):
 BattleWindow::BattleWindow(BattleInterface & owner):
@@ -52,6 +54,12 @@ BattleWindow::BattleWindow(BattleInterface & owner):
 	pos.h = 600;
 	pos.h = 600;
 	pos = center();
 	pos = center();
 
 
+	PlayerColor defenderColor = owner.getBattle()->getBattle()->getSidePlayer(BattleSide::DEFENDER);
+	PlayerColor attackerColor = owner.getBattle()->getBattle()->getSidePlayer(BattleSide::ATTACKER);
+	bool isDefenderHuman = defenderColor.isValidPlayer() && LOCPLINT->cb->getStartInfo()->playerInfos.at(defenderColor).isControlledByHuman();
+	bool isAttackerHuman = attackerColor.isValidPlayer() && LOCPLINT->cb->getStartInfo()->playerInfos.at(attackerColor).isControlledByHuman();
+	onlyOnePlayerHuman = isDefenderHuman != isAttackerHuman;
+
 	REGISTER_BUILDER("battleConsole", &BattleWindow::buildBattleConsole);
 	REGISTER_BUILDER("battleConsole", &BattleWindow::buildBattleConsole);
 	
 	
 	const JsonNode config(JsonPath::builtin("config/widgets/BattleWindow2.json"));
 	const JsonNode config(JsonPath::builtin("config/widgets/BattleWindow2.json"));
@@ -60,6 +68,7 @@ BattleWindow::BattleWindow(BattleInterface & owner):
 	addShortcut(EShortcut::BATTLE_SURRENDER, std::bind(&BattleWindow::bSurrenderf, this));
 	addShortcut(EShortcut::BATTLE_SURRENDER, std::bind(&BattleWindow::bSurrenderf, this));
 	addShortcut(EShortcut::BATTLE_RETREAT, std::bind(&BattleWindow::bFleef, this));
 	addShortcut(EShortcut::BATTLE_RETREAT, std::bind(&BattleWindow::bFleef, this));
 	addShortcut(EShortcut::BATTLE_AUTOCOMBAT, std::bind(&BattleWindow::bAutofightf, this));
 	addShortcut(EShortcut::BATTLE_AUTOCOMBAT, std::bind(&BattleWindow::bAutofightf, this));
+	addShortcut(EShortcut::BATTLE_END_WITH_AUTOCOMBAT, std::bind(&BattleWindow::endWithAutocombat, this));
 	addShortcut(EShortcut::BATTLE_CAST_SPELL, std::bind(&BattleWindow::bSpellf, this));
 	addShortcut(EShortcut::BATTLE_CAST_SPELL, std::bind(&BattleWindow::bSpellf, this));
 	addShortcut(EShortcut::BATTLE_WAIT, std::bind(&BattleWindow::bWaitf, this));
 	addShortcut(EShortcut::BATTLE_WAIT, std::bind(&BattleWindow::bWaitf, this));
 	addShortcut(EShortcut::BATTLE_DEFEND, std::bind(&BattleWindow::bDefencef, this));
 	addShortcut(EShortcut::BATTLE_DEFEND, std::bind(&BattleWindow::bDefencef, this));
@@ -130,19 +139,13 @@ void BattleWindow::createStickyHeroInfoWindows()
 	{
 	{
 		InfoAboutHero info;
 		InfoAboutHero info;
 		info.initFromHero(owner.defendingHeroInstance, InfoAboutHero::EInfoLevel::INBATTLE);
 		info.initFromHero(owner.defendingHeroInstance, InfoAboutHero::EInfoLevel::INBATTLE);
-		Point position = (GH.screenDimensions().x >= 1000)
-				? Point(pos.x + pos.w + 15, pos.y + 60)
-				: Point(pos.x + pos.w -79, pos.y + 195);
-		defenderHeroWindow = std::make_shared<HeroInfoBasicPanel>(info, &position);
+		defenderHeroWindow = std::make_shared<HeroInfoBasicPanel>(info, nullptr);
 	}
 	}
 	if(owner.attackingHeroInstance)
 	if(owner.attackingHeroInstance)
 	{
 	{
 		InfoAboutHero info;
 		InfoAboutHero info;
 		info.initFromHero(owner.attackingHeroInstance, InfoAboutHero::EInfoLevel::INBATTLE);
 		info.initFromHero(owner.attackingHeroInstance, InfoAboutHero::EInfoLevel::INBATTLE);
-		Point position = (GH.screenDimensions().x >= 1000)
-				? Point(pos.x - 93, pos.y + 60)
-				: Point(pos.x + 1, pos.y + 195);
-		attackerHeroWindow = std::make_shared<HeroInfoBasicPanel>(info, &position);
+		attackerHeroWindow = std::make_shared<HeroInfoBasicPanel>(info, nullptr);
 	}
 	}
 
 
 	bool showInfoWindows = settings["battle"]["stickyHeroInfoWindows"].Bool();
 	bool showInfoWindows = settings["battle"]["stickyHeroInfoWindows"].Bool();
@@ -155,6 +158,8 @@ void BattleWindow::createStickyHeroInfoWindows()
 		if(defenderHeroWindow)
 		if(defenderHeroWindow)
 			defenderHeroWindow->disable();
 			defenderHeroWindow->disable();
 	}
 	}
+
+	setPositionInfoWindow();
 }
 }
 
 
 void BattleWindow::createTimerInfoWindows()
 void BattleWindow::createTimerInfoWindows()
@@ -222,6 +227,7 @@ void BattleWindow::hideQueue()
 		pos.h -= queue->pos.h;
 		pos.h -= queue->pos.h;
 		pos = center();
 		pos = center();
 	}
 	}
+	setPositionInfoWindow();
 	GH.windows().totalRedraw();
 	GH.windows().totalRedraw();
 }
 }
 
 
@@ -235,6 +241,7 @@ void BattleWindow::showQueue()
 
 
 	createQueue();
 	createQueue();
 	updateQueue();
 	updateQueue();
+	setPositionInfoWindow();
 	GH.windows().totalRedraw();
 	GH.windows().totalRedraw();
 }
 }
 
 
@@ -281,6 +288,38 @@ void BattleWindow::updateQueue()
 	queue->update();
 	queue->update();
 }
 }
 
 
+void BattleWindow::setPositionInfoWindow()
+{
+	if(defenderHeroWindow)
+	{
+		Point position = (GH.screenDimensions().x >= 1000)
+				? Point(pos.x + pos.w + 15, pos.y + 60)
+				: Point(pos.x + pos.w -79, pos.y + 195);
+		defenderHeroWindow->moveTo(position);
+	}
+	if(attackerHeroWindow)
+	{
+		Point position = (GH.screenDimensions().x >= 1000)
+				? Point(pos.x - 93, pos.y + 60)
+				: Point(pos.x + 1, pos.y + 195);
+		attackerHeroWindow->moveTo(position);
+	}
+	if(defenderStackWindow)
+	{
+		Point position = (GH.screenDimensions().x >= 1000)
+				? Point(pos.x + pos.w + 15, defenderHeroWindow ? defenderHeroWindow->pos.y + 210 : pos.y + 60)
+				: Point(pos.x + pos.w -79, defenderHeroWindow ? defenderHeroWindow->pos.y : pos.y + 195);
+		defenderStackWindow->moveTo(position);
+	}
+	if(attackerStackWindow)
+	{
+		Point position = (GH.screenDimensions().x >= 1000)
+				? Point(pos.x - 93, attackerHeroWindow ? attackerHeroWindow->pos.y + 210 : pos.y + 60)
+				: Point(pos.x + 1, attackerHeroWindow ? attackerHeroWindow->pos.y : pos.y + 195);
+		attackerStackWindow->moveTo(position);
+	}
+}
+
 void BattleWindow::updateHeroInfoWindow(uint8_t side, const InfoAboutHero & hero)
 void BattleWindow::updateHeroInfoWindow(uint8_t side, const InfoAboutHero & hero)
 {
 {
 	std::shared_ptr<HeroInfoBasicPanel> panelToUpdate = side == 0 ? attackerHeroWindow : defenderHeroWindow;
 	std::shared_ptr<HeroInfoBasicPanel> panelToUpdate = side == 0 ? attackerHeroWindow : defenderHeroWindow;
@@ -295,10 +334,7 @@ void BattleWindow::updateStackInfoWindow(const CStack * stack)
 
 
 	if(stack && stack->unitSide() == BattleSide::DEFENDER)
 	if(stack && stack->unitSide() == BattleSide::DEFENDER)
 	{
 	{
-		Point position = (GH.screenDimensions().x >= 1000)
-				? Point(pos.x + pos.w + 15, defenderHeroWindow ? defenderHeroWindow->pos.y + 210 : pos.y)
-				: Point(pos.x + pos.w -79, defenderHeroWindow ? defenderHeroWindow->pos.y : pos.y + 135);
-		defenderStackWindow = std::make_shared<StackInfoBasicPanel>(stack, &position);
+		defenderStackWindow = std::make_shared<StackInfoBasicPanel>(stack);
 		defenderStackWindow->setEnabled(showInfoWindows);
 		defenderStackWindow->setEnabled(showInfoWindows);
 	}
 	}
 	else
 	else
@@ -306,14 +342,13 @@ void BattleWindow::updateStackInfoWindow(const CStack * stack)
 	
 	
 	if(stack && stack->unitSide() == BattleSide::ATTACKER)
 	if(stack && stack->unitSide() == BattleSide::ATTACKER)
 	{
 	{
-		Point position = (GH.screenDimensions().x >= 1000)
-				? Point(pos.x - 93, attackerHeroWindow ? attackerHeroWindow->pos.y + 210 : pos.y)
-				: Point(pos.x + 1, attackerHeroWindow ? attackerHeroWindow->pos.y : pos.y + 135);
-		attackerStackWindow = std::make_shared<StackInfoBasicPanel>(stack, &position);
+		attackerStackWindow = std::make_shared<StackInfoBasicPanel>(stack);
 		attackerStackWindow->setEnabled(showInfoWindows);
 		attackerStackWindow->setEnabled(showInfoWindows);
 	}
 	}
 	else
 	else
 		attackerStackWindow = nullptr;
 		attackerStackWindow = nullptr;
+	
+	setPositionInfoWindow();
 }
 }
 
 
 void BattleWindow::heroManaPointsChanged(const CGHeroInstance * hero)
 void BattleWindow::heroManaPointsChanged(const CGHeroInstance * hero)
@@ -449,7 +484,7 @@ void BattleWindow::bFleef()
 
 
 	if ( owner.getBattle()->battleCanFlee() )
 	if ( owner.getBattle()->battleCanFlee() )
 	{
 	{
-		CFunctionList<void()> ony = std::bind(&BattleWindow::reallyFlee,this);
+		auto ony = std::bind(&BattleWindow::reallyFlee,this);
 		owner.curInt->showYesNoDialog(CGI->generaltexth->allTexts[28], ony, nullptr); //Are you sure you want to retreat?
 		owner.curInt->showYesNoDialog(CGI->generaltexth->allTexts[28], ony, nullptr); //Are you sure you want to retreat?
 	}
 	}
 	else
 	else
@@ -551,6 +586,12 @@ void BattleWindow::bAutofightf()
 	if (owner.actionsController->spellcastingModeActive())
 	if (owner.actionsController->spellcastingModeActive())
 		return;
 		return;
 
 
+	if(settings["battle"]["endWithAutocombat"].Bool() && onlyOnePlayerHuman)
+	{
+		endWithAutocombat();
+		return;
+	}
+
 	//Stop auto-fight mode
 	//Stop auto-fight mode
 	if(owner.curInt->isAutoFightOn)
 	if(owner.curInt->isAutoFightOn)
 	{
 	{
@@ -601,7 +642,7 @@ void BattleWindow::bSpellf()
 	{
 	{
 		//TODO: move to spell mechanics, add more information to spell cast problem
 		//TODO: move to spell mechanics, add more information to spell cast problem
 		//Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible
 		//Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible
-		auto blockingBonus = owner.currentHero()->getBonusLocalFirst(Selector::type()(BonusType::BLOCK_ALL_MAGIC));
+		auto blockingBonus = owner.currentHero()->getFirstBonus(Selector::type()(BonusType::BLOCK_ALL_MAGIC));
 		if (!blockingBonus)
 		if (!blockingBonus)
 			return;
 			return;
 
 
@@ -721,7 +762,8 @@ void BattleWindow::blockUI(bool on)
 	setShortcutBlocked(EShortcut::BATTLE_WAIT, on || owner.tacticsMode || !canWait);
 	setShortcutBlocked(EShortcut::BATTLE_WAIT, on || owner.tacticsMode || !canWait);
 	setShortcutBlocked(EShortcut::BATTLE_DEFEND, on || owner.tacticsMode);
 	setShortcutBlocked(EShortcut::BATTLE_DEFEND, on || owner.tacticsMode);
 	setShortcutBlocked(EShortcut::BATTLE_SELECT_ACTION, on || owner.tacticsMode);
 	setShortcutBlocked(EShortcut::BATTLE_SELECT_ACTION, on || owner.tacticsMode);
-	setShortcutBlocked(EShortcut::BATTLE_AUTOCOMBAT, owner.actionsController->spellcastingModeActive());
+	setShortcutBlocked(EShortcut::BATTLE_AUTOCOMBAT, (settings["battle"]["endWithAutocombat"].Bool() && onlyOnePlayerHuman) ? on || owner.tacticsMode || owner.actionsController->spellcastingModeActive() : owner.actionsController->spellcastingModeActive());
+	setShortcutBlocked(EShortcut::BATTLE_END_WITH_AUTOCOMBAT, on || owner.tacticsMode || !onlyOnePlayerHuman || owner.actionsController->spellcastingModeActive());
 	setShortcutBlocked(EShortcut::BATTLE_TACTICS_END, on && owner.tacticsMode);
 	setShortcutBlocked(EShortcut::BATTLE_TACTICS_END, on && owner.tacticsMode);
 	setShortcutBlocked(EShortcut::BATTLE_TACTICS_NEXT, on && owner.tacticsMode);
 	setShortcutBlocked(EShortcut::BATTLE_TACTICS_NEXT, on && owner.tacticsMode);
 	setShortcutBlocked(EShortcut::BATTLE_CONSOLE_DOWN, on && !owner.tacticsMode);
 	setShortcutBlocked(EShortcut::BATTLE_CONSOLE_DOWN, on && !owner.tacticsMode);
@@ -733,6 +775,39 @@ std::optional<uint32_t> BattleWindow::getQueueHoveredUnitId()
 	return queue->getHoveredUnitIdIfAny();
 	return queue->getHoveredUnitIdIfAny();
 }
 }
 
 
+void BattleWindow::endWithAutocombat() 
+{
+	if(!owner.makingTurn() || owner.tacticsMode)
+		return;
+
+	LOCPLINT->showYesNoDialog(
+		VLC->generaltexth->translate("vcmi.battleWindow.endWithAutocombat"),
+		[this]()
+		{
+			owner.curInt->isAutoFightEndBattle = true;
+
+			auto ai = CDynLibHandler::getNewBattleAI(settings["server"]["friendlyAI"].String());
+
+			AutocombatPreferences autocombatPreferences = AutocombatPreferences();
+			autocombatPreferences.enableSpellsUsage = settings["battle"]["enableAutocombatSpells"].Bool();
+
+			ai->initBattleInterface(owner.curInt->env, owner.curInt->cb, autocombatPreferences);
+			ai->battleStart(owner.getBattleID(), owner.army1, owner.army2, int3(0,0,0), owner.attackingHeroInstance, owner.defendingHeroInstance, owner.getBattle()->battleGetMySide(), false);
+
+			owner.curInt->isAutoFightOn = true;
+			owner.curInt->cb->registerBattleInterface(ai);
+			owner.curInt->autofightingAI = ai;
+
+			owner.requestAutofightingAIToTakeAction();
+
+			close();
+
+			owner.curInt->battleInt.reset();
+		},
+		nullptr
+	);
+}
+
 void BattleWindow::showAll(Canvas & to)
 void BattleWindow::showAll(Canvas & to)
 {
 {
 	CIntObject::showAll(to);
 	CIntObject::showAll(to);

+ 8 - 0
client/battle/BattleWindow.h

@@ -76,6 +76,8 @@ class BattleWindow : public InterfaceObjectConfigurable
 
 
 	std::shared_ptr<BattleConsole> buildBattleConsole(const JsonNode &) const;
 	std::shared_ptr<BattleConsole> buildBattleConsole(const JsonNode &) const;
 
 
+	bool onlyOnePlayerHuman;
+
 public:
 public:
 	BattleWindow(BattleInterface & owner );
 	BattleWindow(BattleInterface & owner );
 	~BattleWindow();
 	~BattleWindow();
@@ -100,6 +102,9 @@ public:
 	/// Refresh queue after turn order changes
 	/// Refresh queue after turn order changes
 	void updateQueue();
 	void updateQueue();
 
 
+	// Set positions for hero & stack info window
+	void setPositionInfoWindow();
+
 	/// Refresh sticky variant of hero info window after spellcast, side same as in BattleSpellCast::side
 	/// Refresh sticky variant of hero info window after spellcast, side same as in BattleSpellCast::side
 	void updateHeroInfoWindow(uint8_t side, const InfoAboutHero & hero);
 	void updateHeroInfoWindow(uint8_t side, const InfoAboutHero & hero);
 
 
@@ -125,5 +130,8 @@ public:
 
 
 	/// Set possible alternative options. If more than 1 - the last will be considered as default option
 	/// Set possible alternative options. If more than 1 - the last will be considered as default option
 	void setAlternativeActions(const std::list<PossiblePlayerBattleAction> &);
 	void setAlternativeActions(const std::list<PossiblePlayerBattleAction> &);
+
+	/// ends battle with autocombat
+	void endWithAutocombat();
 };
 };
 
 

+ 14 - 29
client/eventsSDL/InputSourceText.cpp

@@ -21,10 +21,6 @@
 
 
 #include <SDL_events.h>
 #include <SDL_events.h>
 
 
-#ifdef VCMI_APPLE
-#	include <dispatch/dispatch.h>
-#endif
-
 void InputSourceText::handleEventTextInput(const SDL_TextInputEvent & text)
 void InputSourceText::handleEventTextInput(const SDL_TextInputEvent & text)
 {
 {
 	GH.events().dispatchTextInput(text.text);
 	GH.events().dispatchTextInput(text.text);
@@ -37,38 +33,27 @@ void InputSourceText::handleEventTextEditing(const SDL_TextEditingEvent & text)
 
 
 void InputSourceText::startTextInput(const Rect & whereInput)
 void InputSourceText::startTextInput(const Rect & whereInput)
 {
 {
-
-#ifdef VCMI_APPLE
-	dispatch_async(dispatch_get_main_queue(), ^{
-#endif
-
-	Rect rectInScreenCoordinates = GH.screenHandler().convertLogicalPointsToWindow(whereInput);
-	SDL_Rect textInputRect = CSDL_Ext::toSDL(rectInScreenCoordinates);
-
-	SDL_SetTextInputRect(&textInputRect);
-
-	if (SDL_IsTextInputActive() == SDL_FALSE)
+	GH.dispatchMainThread([whereInput]()
 	{
 	{
-		SDL_StartTextInput();
-	}
+		Rect rectInScreenCoordinates = GH.screenHandler().convertLogicalPointsToWindow(whereInput);
+		SDL_Rect textInputRect = CSDL_Ext::toSDL(rectInScreenCoordinates);
+
+		SDL_SetTextInputRect(&textInputRect);
 
 
-#ifdef VCMI_APPLE
+		if (SDL_IsTextInputActive() == SDL_FALSE)
+		{
+			SDL_StartTextInput();
+		}
 	});
 	});
-#endif
 }
 }
 
 
 void InputSourceText::stopTextInput()
 void InputSourceText::stopTextInput()
 {
 {
-#ifdef VCMI_APPLE
-	dispatch_async(dispatch_get_main_queue(), ^{
-#endif
-
-	if (SDL_IsTextInputActive() == SDL_TRUE)
+	GH.dispatchMainThread([]()
 	{
 	{
-		SDL_StopTextInput();
-	}
-
-#ifdef VCMI_APPLE
+		if (SDL_IsTextInputActive() == SDL_TRUE)
+		{
+			SDL_StopTextInput();
+		}
 	});
 	});
-#endif
 }
 }

+ 1 - 1
client/gui/InterfaceObjectConfigurable.cpp

@@ -301,7 +301,7 @@ EShortcut InterfaceObjectConfigurable::readHotkey(const JsonNode & config) const
 	EShortcut result = GH.shortcuts().findShortcut(config.String());
 	EShortcut result = GH.shortcuts().findShortcut(config.String());
 	if (result == EShortcut::NONE)
 	if (result == EShortcut::NONE)
 		logGlobal->error("Invalid hotkey '%s' in interface configuration!", config.String());
 		logGlobal->error("Invalid hotkey '%s' in interface configuration!", config.String());
-	return result;;
+	return result;
 }
 }
 
 
 std::shared_ptr<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNode & config) const
 std::shared_ptr<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNode & config) const

+ 1 - 0
client/gui/Shortcut.h

@@ -125,6 +125,7 @@ enum class EShortcut
 	BATTLE_SURRENDER,
 	BATTLE_SURRENDER,
 	BATTLE_RETREAT,
 	BATTLE_RETREAT,
 	BATTLE_AUTOCOMBAT,
 	BATTLE_AUTOCOMBAT,
+	BATTLE_END_WITH_AUTOCOMBAT,
 	BATTLE_CAST_SPELL,
 	BATTLE_CAST_SPELL,
 	BATTLE_WAIT,
 	BATTLE_WAIT,
 	BATTLE_DEFEND,
 	BATTLE_DEFEND,

+ 2 - 0
client/gui/ShortcutHandler.cpp

@@ -124,6 +124,7 @@ std::vector<EShortcut> ShortcutHandler::translateKeycode(SDL_Keycode key) const
 		{SDLK_s,         EShortcut::BATTLE_SURRENDER          },
 		{SDLK_s,         EShortcut::BATTLE_SURRENDER          },
 		{SDLK_r,         EShortcut::BATTLE_RETREAT            },
 		{SDLK_r,         EShortcut::BATTLE_RETREAT            },
 		{SDLK_a,         EShortcut::BATTLE_AUTOCOMBAT         },
 		{SDLK_a,         EShortcut::BATTLE_AUTOCOMBAT         },
+		{SDLK_e,         EShortcut::BATTLE_END_WITH_AUTOCOMBAT},
 		{SDLK_c,         EShortcut::BATTLE_CAST_SPELL         },
 		{SDLK_c,         EShortcut::BATTLE_CAST_SPELL         },
 		{SDLK_w,         EShortcut::BATTLE_WAIT               },
 		{SDLK_w,         EShortcut::BATTLE_WAIT               },
 		{SDLK_d,         EShortcut::BATTLE_DEFEND             },
 		{SDLK_d,         EShortcut::BATTLE_DEFEND             },
@@ -265,6 +266,7 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
 		{"battleSurrender",          EShortcut::BATTLE_SURRENDER          },
 		{"battleSurrender",          EShortcut::BATTLE_SURRENDER          },
 		{"battleRetreat",            EShortcut::BATTLE_RETREAT            },
 		{"battleRetreat",            EShortcut::BATTLE_RETREAT            },
 		{"battleAutocombat",         EShortcut::BATTLE_AUTOCOMBAT         },
 		{"battleAutocombat",         EShortcut::BATTLE_AUTOCOMBAT         },
+		{"battleAutocombatEnd",      EShortcut::BATTLE_END_WITH_AUTOCOMBAT},
 		{"battleCastSpell",          EShortcut::BATTLE_CAST_SPELL         },
 		{"battleCastSpell",          EShortcut::BATTLE_CAST_SPELL         },
 		{"battleWait",               EShortcut::BATTLE_WAIT               },
 		{"battleWait",               EShortcut::BATTLE_WAIT               },
 		{"battleDefend",             EShortcut::BATTLE_DEFEND             },
 		{"battleDefend",             EShortcut::BATTLE_DEFEND             },

+ 4 - 0
client/lobby/CBonusSelection.cpp

@@ -72,6 +72,7 @@ CBonusSelection::CBonusSelection()
 
 
 	buttonStart = std::make_shared<CButton>(Point(475, 536), AnimationPath::builtin("CBBEGIB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::startMap, this), EShortcut::GLOBAL_ACCEPT);
 	buttonStart = std::make_shared<CButton>(Point(475, 536), AnimationPath::builtin("CBBEGIB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::startMap, this), EShortcut::GLOBAL_ACCEPT);
 	buttonRestart = std::make_shared<CButton>(Point(475, 536), AnimationPath::builtin("CBRESTB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::restartMap, this), EShortcut::GLOBAL_ACCEPT);
 	buttonRestart = std::make_shared<CButton>(Point(475, 536), AnimationPath::builtin("CBRESTB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::restartMap, this), EShortcut::GLOBAL_ACCEPT);
+	buttonVideo = std::make_shared<CButton>(Point(705, 214), AnimationPath::builtin("CBVIDEB.DEF"), CButton::tooltip(), [this](){ GH.windows().createAndPushWindow<CPrologEpilogVideo>(getCampaign()->scenario(CSH->campaignMap).prolog, [this](){ redraw(); }); });
 	buttonBack = std::make_shared<CButton>(Point(624, 536), AnimationPath::builtin("CBCANCB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::goBack, this), EShortcut::GLOBAL_CANCEL);
 	buttonBack = std::make_shared<CButton>(Point(624, 536), AnimationPath::builtin("CBCANCB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::goBack, this), EShortcut::GLOBAL_CANCEL);
 
 
 	campaignName = std::make_shared<CLabel>(481, 28, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->si->getCampaignName());
 	campaignName = std::make_shared<CLabel>(481, 28, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->si->getCampaignName());
@@ -309,6 +310,7 @@ void CBonusSelection::updateAfterStateChange()
 	if(CSH->state != EClientState::GAMEPLAY)
 	if(CSH->state != EClientState::GAMEPLAY)
 	{
 	{
 		buttonRestart->disable();
 		buttonRestart->disable();
+		buttonVideo->disable();
 		buttonStart->enable();
 		buttonStart->enable();
 		if(!getCampaign()->conqueredScenarios().empty())
 		if(!getCampaign()->conqueredScenarios().empty())
 			buttonBack->block(true);
 			buttonBack->block(true);
@@ -319,6 +321,7 @@ void CBonusSelection::updateAfterStateChange()
 	{
 	{
 		buttonStart->disable();
 		buttonStart->disable();
 		buttonRestart->enable();
 		buttonRestart->enable();
+		buttonVideo->enable();
 		buttonBack->block(false);
 		buttonBack->block(false);
 		if(buttonDifficultyLeft)
 		if(buttonDifficultyLeft)
 			buttonDifficultyLeft->disable();
 			buttonDifficultyLeft->disable();
@@ -401,6 +404,7 @@ void CBonusSelection::startMap()
 	//block buttons immediately
 	//block buttons immediately
 	buttonStart->block(true);
 	buttonStart->block(true);
 	buttonRestart->block(true);
 	buttonRestart->block(true);
+	buttonVideo->block(true);
 	buttonBack->block(true);
 	buttonBack->block(true);
 
 
 	if(LOCPLINT) // we're currently ingame, so ask for starting new map and end game
 	if(LOCPLINT) // we're currently ingame, so ask for starting new map and end game

+ 1 - 0
client/lobby/CBonusSelection.h

@@ -65,6 +65,7 @@ public:
 	std::shared_ptr<CButton> buttonStart;
 	std::shared_ptr<CButton> buttonStart;
 	std::shared_ptr<CButton> buttonRestart;
 	std::shared_ptr<CButton> buttonRestart;
 	std::shared_ptr<CButton> buttonBack;
 	std::shared_ptr<CButton> buttonBack;
+	std::shared_ptr<CButton> buttonVideo;
 	std::shared_ptr<CLabel> campaignName;
 	std::shared_ptr<CLabel> campaignName;
 	std::shared_ptr<CLabel> labelCampaignDescription;
 	std::shared_ptr<CLabel> labelCampaignDescription;
 	std::shared_ptr<CTextBox> campaignDescription;
 	std::shared_ptr<CTextBox> campaignDescription;

+ 33 - 4
client/lobby/CLobbyScreen.cpp

@@ -12,6 +12,7 @@
 
 
 #include "CBonusSelection.h"
 #include "CBonusSelection.h"
 #include "TurnOptionsTab.h"
 #include "TurnOptionsTab.h"
+#include "ExtraOptionsTab.h"
 #include "OptionsTab.h"
 #include "OptionsTab.h"
 #include "RandomMapTab.h"
 #include "RandomMapTab.h"
 #include "SelectionTab.h"
 #include "SelectionTab.h"
@@ -53,7 +54,10 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType)
 
 
 		buttonOptions = std::make_shared<CButton>(Point(411, 510), AnimationPath::builtin("GSPBUTT.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabOpt), EShortcut::LOBBY_ADDITIONAL_OPTIONS);
 		buttonOptions = std::make_shared<CButton>(Point(411, 510), AnimationPath::builtin("GSPBUTT.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabOpt), EShortcut::LOBBY_ADDITIONAL_OPTIONS);
 		if(settings["general"]["enableUiEnhancements"].Bool())
 		if(settings["general"]["enableUiEnhancements"].Bool())
-			buttonTurnOptions = std::make_shared<CButton>(Point(619, 510), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabTurnOptions), EShortcut::NONE);
+		{
+			buttonTurnOptions = std::make_shared<CButton>(Point(619, 105), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabTurnOptions), EShortcut::NONE);
+			buttonExtraOptions = std::make_shared<CButton>(Point(619, 510), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabExtraOptions), EShortcut::NONE);
+		}
 	};
 	};
 
 
 	buttonChat = std::make_shared<CButton>(Point(619, 80), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), EShortcut::LOBBY_HIDE_CHAT);
 	buttonChat = std::make_shared<CButton>(Point(619, 80), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), EShortcut::LOBBY_HIDE_CHAT);
@@ -65,6 +69,7 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType)
 	{
 	{
 		tabOpt = std::make_shared<OptionsTab>();
 		tabOpt = std::make_shared<OptionsTab>();
 		tabTurnOptions = std::make_shared<TurnOptionsTab>();
 		tabTurnOptions = std::make_shared<TurnOptionsTab>();
+		tabExtraOptions = std::make_shared<ExtraOptionsTab>();
 		tabRand = std::make_shared<RandomMapTab>();
 		tabRand = std::make_shared<RandomMapTab>();
 		tabRand->mapInfoChanged += std::bind(&IServerAPI::setMapInfo, CSH, _1, _2);
 		tabRand->mapInfoChanged += std::bind(&IServerAPI::setMapInfo, CSH, _1, _2);
 		buttonRMG = std::make_shared<CButton>(Point(411, 105), AnimationPath::builtin("GSPBUTT.DEF"), CGI->generaltexth->zelp[47], 0, EShortcut::LOBBY_RANDOM_MAP);
 		buttonRMG = std::make_shared<CButton>(Point(411, 105), AnimationPath::builtin("GSPBUTT.DEF"), CGI->generaltexth->zelp[47], 0, EShortcut::LOBBY_RANDOM_MAP);
@@ -84,6 +89,7 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType)
 	{
 	{
 		tabOpt = std::make_shared<OptionsTab>();
 		tabOpt = std::make_shared<OptionsTab>();
 		tabTurnOptions = std::make_shared<TurnOptionsTab>();
 		tabTurnOptions = std::make_shared<TurnOptionsTab>();
+		tabExtraOptions = std::make_shared<ExtraOptionsTab>();
 		buttonStart = std::make_shared<CButton>(Point(411, 535), AnimationPath::builtin("SCNRLOD.DEF"), CGI->generaltexth->zelp[103], std::bind(&CLobbyScreen::startScenario, this, false), EShortcut::LOBBY_LOAD_GAME);
 		buttonStart = std::make_shared<CButton>(Point(411, 535), AnimationPath::builtin("SCNRLOD.DEF"), CGI->generaltexth->zelp[103], std::bind(&CLobbyScreen::startScenario, this, false), EShortcut::LOBBY_LOAD_GAME);
 		initLobby();
 		initLobby();
 		break;
 		break;
@@ -122,16 +128,30 @@ void CLobbyScreen::toggleTab(std::shared_ptr<CIntObject> tab)
 		CSH->sendGuiAction(LobbyGuiAction::OPEN_RANDOM_MAP_OPTIONS);
 		CSH->sendGuiAction(LobbyGuiAction::OPEN_RANDOM_MAP_OPTIONS);
 	else if(tab == tabTurnOptions)
 	else if(tab == tabTurnOptions)
 		CSH->sendGuiAction(LobbyGuiAction::OPEN_TURN_OPTIONS);
 		CSH->sendGuiAction(LobbyGuiAction::OPEN_TURN_OPTIONS);
+	else if(tab == tabExtraOptions)
+		CSH->sendGuiAction(LobbyGuiAction::OPEN_EXTRA_OPTIONS);
 	CSelectionBase::toggleTab(tab);
 	CSelectionBase::toggleTab(tab);
 }
 }
 
 
 void CLobbyScreen::startCampaign()
 void CLobbyScreen::startCampaign()
 {
 {
-	if(CSH->mi)
-	{
+	if(!CSH->mi)
+		return;
+
+	try {
 		auto ourCampaign = CampaignHandler::getCampaign(CSH->mi->fileURI);
 		auto ourCampaign = CampaignHandler::getCampaign(CSH->mi->fileURI);
 		CSH->setCampaignState(ourCampaign);
 		CSH->setCampaignState(ourCampaign);
 	}
 	}
+	catch (const std::runtime_error &e)
+	{
+		// handle possible exception on map loading. For example campaign that contains map in unsupported format
+		// for example, wog campaigns or hota campaigns without hota map support mod
+		MetaString message;
+		message.appendTextID("vcmi.client.errors.invalidMap");
+		message.replaceRawString(e.what());
+
+		CInfoWindow::showInfoDialog(message.toString(), {});
+	}
 }
 }
 
 
 void CLobbyScreen::startScenario(bool allowOnlyAI)
 void CLobbyScreen::startScenario(bool allowOnlyAI)
@@ -157,6 +177,9 @@ void CLobbyScreen::toggleMode(bool host)
 	if (buttonTurnOptions)
 	if (buttonTurnOptions)
 		buttonTurnOptions->addTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.turnOptions.hover"), FONT_SMALL, buttonColor);
 		buttonTurnOptions->addTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.turnOptions.hover"), FONT_SMALL, buttonColor);
 
 
+	if (buttonExtraOptions)
+		buttonExtraOptions->addTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.extraOptions.hover"), FONT_SMALL, buttonColor);
+
 	if(buttonRMG)
 	if(buttonRMG)
 	{
 	{
 		buttonRMG->addTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, buttonColor);
 		buttonRMG->addTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, buttonColor);
@@ -168,10 +191,14 @@ void CLobbyScreen::toggleMode(bool host)
 	if (buttonTurnOptions)
 	if (buttonTurnOptions)
 		buttonTurnOptions->block(!host);
 		buttonTurnOptions->block(!host);
 
 
+	if (buttonExtraOptions)
+		buttonExtraOptions->block(!host);
+
 	if(CSH->mi)
 	if(CSH->mi)
 	{
 	{
 		tabOpt->recreate();
 		tabOpt->recreate();
 		tabTurnOptions->recreate();
 		tabTurnOptions->recreate();
+		tabExtraOptions->recreate();
 	}
 	}
 }
 }
 
 
@@ -191,7 +218,9 @@ void CLobbyScreen::updateAfterStateChange()
 		if (tabOpt)
 		if (tabOpt)
 			tabOpt->recreate();
 			tabOpt->recreate();
 		if (tabTurnOptions)
 		if (tabTurnOptions)
-		tabTurnOptions->recreate();
+			tabTurnOptions->recreate();
+		if (tabExtraOptions)
+			tabExtraOptions->recreate();
 	}
 	}
 
 
 	buttonStart->block(CSH->mi == nullptr || CSH->isGuest());
 	buttonStart->block(CSH->mi == nullptr || CSH->isGuest());

+ 1 - 1
client/lobby/CSelectionBase.cpp

@@ -22,7 +22,6 @@
 #include "../CPlayerInterface.h"
 #include "../CPlayerInterface.h"
 #include "../CMusicHandler.h"
 #include "../CMusicHandler.h"
 #include "../CVideoHandler.h"
 #include "../CVideoHandler.h"
-#include "../CPlayerInterface.h"
 #include "../CServerHandler.h"
 #include "../CServerHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/Shortcut.h"
 #include "../gui/Shortcut.h"
@@ -43,6 +42,7 @@
 
 
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/CHeroHandler.h"
+#include "../../lib/CRandomGenerator.h"
 #include "../../lib/CThreadHelper.h"
 #include "../../lib/CThreadHelper.h"
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/mapping/CMapInfo.h"
 #include "../../lib/mapping/CMapInfo.h"

+ 3 - 0
client/lobby/CSelectionBase.h

@@ -27,6 +27,7 @@ class CToggleGroup;
 class RandomMapTab;
 class RandomMapTab;
 class OptionsTab;
 class OptionsTab;
 class TurnOptionsTab;
 class TurnOptionsTab;
+class ExtraOptionsTab;
 class SelectionTab;
 class SelectionTab;
 class InfoCard;
 class InfoCard;
 class CChatBox;
 class CChatBox;
@@ -60,6 +61,7 @@ public:
 	std::shared_ptr<CButton> buttonRMG;
 	std::shared_ptr<CButton> buttonRMG;
 	std::shared_ptr<CButton> buttonOptions;
 	std::shared_ptr<CButton> buttonOptions;
 	std::shared_ptr<CButton> buttonTurnOptions;
 	std::shared_ptr<CButton> buttonTurnOptions;
+	std::shared_ptr<CButton> buttonExtraOptions;
 	std::shared_ptr<CButton> buttonStart;
 	std::shared_ptr<CButton> buttonStart;
 	std::shared_ptr<CButton> buttonBack;
 	std::shared_ptr<CButton> buttonBack;
 	std::shared_ptr<CButton> buttonSimturns;
 	std::shared_ptr<CButton> buttonSimturns;
@@ -67,6 +69,7 @@ public:
 	std::shared_ptr<SelectionTab> tabSel;
 	std::shared_ptr<SelectionTab> tabSel;
 	std::shared_ptr<OptionsTab> tabOpt;
 	std::shared_ptr<OptionsTab> tabOpt;
 	std::shared_ptr<TurnOptionsTab> tabTurnOptions;
 	std::shared_ptr<TurnOptionsTab> tabTurnOptions;
+	std::shared_ptr<ExtraOptionsTab> tabExtraOptions;
 	std::shared_ptr<RandomMapTab> tabRand;
 	std::shared_ptr<RandomMapTab> tabRand;
 	std::shared_ptr<CIntObject> curTab;
 	std::shared_ptr<CIntObject> curTab;
 
 

+ 18 - 0
client/lobby/ExtraOptionsTab.cpp

@@ -0,0 +1,18 @@
+/*
+ * ExtraOptionsTab.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 "ExtraOptionsTab.h"
+
+ExtraOptionsTab::ExtraOptionsTab()
+	: OptionsTabBase(JsonPath::builtin("config/widgets/extraOptionsTab.json"))
+{
+
+}

+ 18 - 0
client/lobby/ExtraOptionsTab.h

@@ -0,0 +1,18 @@
+/*
+ * ExtraOptionsTab.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 "OptionsTabBase.h"
+
+class ExtraOptionsTab : public OptionsTabBase
+{
+public:
+	ExtraOptionsTab();
+};

Some files were not shown because too many files changed in this diff