瀏覽代碼

Merge branch 'develop' into randomize_optional_roads

Tomasz Zieliński 2 月之前
父節點
當前提交
3bfd714d3f
共有 100 個文件被更改,包括 974 次插入629 次删除
  1. 190 0
      .clang-format
  2. 83 0
      .clang-tidy
  3. 20 0
      .editorconfig
  4. 50 0
      .github/workflows/aab-from-build.yml
  5. 1 1
      .github/workflows/github.yml
  6. 2 0
      AI/BattleAI/AttackPossibility.cpp
  7. 0 1
      AI/BattleAI/AttackPossibility.h
  8. 4 5
      AI/BattleAI/BattleAI.cpp
  9. 1 2
      AI/BattleAI/BattleAI.h
  10. 4 1
      AI/BattleAI/BattleEvaluator.cpp
  11. 2 1
      AI/BattleAI/BattleEvaluator.h
  12. 13 11
      AI/BattleAI/BattleExchangeVariant.cpp
  13. 0 1
      AI/BattleAI/BattleExchangeVariant.h
  14. 15 18
      AI/BattleAI/PotentialTargets.cpp
  15. 21 14
      AI/BattleAI/StackWithBonuses.cpp
  16. 2 3
      AI/BattleAI/StackWithBonuses.h
  17. 0 1
      AI/BattleAI/main.cpp
  18. 3 8
      AI/CMakeLists.txt
  19. 2 0
      AI/EmptyAI/CEmptyAI.cpp
  20. 1 2
      AI/EmptyAI/CEmptyAI.h
  21. 1 1
      AI/FuzzyLite
  22. 65 43
      AI/Nullkiller/AIGateway.cpp
  23. 10 8
      AI/Nullkiller/AIGateway.h
  24. 23 21
      AI/Nullkiller/AIUtility.cpp
  25. 1 1
      AI/Nullkiller/AIUtility.h
  26. 20 20
      AI/Nullkiller/Analyzers/ArmyManager.cpp
  27. 1 1
      AI/Nullkiller/Analyzers/BuildAnalyzer.cpp
  28. 1 1
      AI/Nullkiller/Analyzers/BuildAnalyzer.h
  29. 7 6
      AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp
  30. 15 10
      AI/Nullkiller/Analyzers/HeroManager.cpp
  31. 35 35
      AI/Nullkiller/Behaviors/DefenceBehavior.cpp
  32. 7 7
      AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp
  33. 2 2
      AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp
  34. 17 17
      AI/Nullkiller/Behaviors/StartupBehavior.cpp
  35. 1 1
      AI/Nullkiller/Behaviors/StayAtTownBehavior.cpp
  36. 3 4
      AI/Nullkiller/Engine/AIMemory.cpp
  37. 1 1
      AI/Nullkiller/Engine/FuzzyEngines.cpp
  38. 4 5
      AI/Nullkiller/Engine/FuzzyHelper.cpp
  39. 4 4
      AI/Nullkiller/Engine/Nullkiller.cpp
  40. 26 70
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  41. 2 2
      AI/Nullkiller/Engine/PriorityEvaluator.h
  42. 0 1
      AI/Nullkiller/Engine/Settings.cpp
  43. 2 1
      AI/Nullkiller/Goals/AbstractGoal.cpp
  44. 10 7
      AI/Nullkiller/Goals/AdventureSpellCast.cpp
  45. 1 1
      AI/Nullkiller/Goals/BuildThis.cpp
  46. 1 1
      AI/Nullkiller/Goals/BuildThis.h
  47. 3 3
      AI/Nullkiller/Goals/BuyArmy.cpp
  48. 1 1
      AI/Nullkiller/Goals/CGoal.h
  49. 32 52
      AI/Nullkiller/Goals/CompleteQuest.cpp
  50. 0 1
      AI/Nullkiller/Goals/CompleteQuest.h
  51. 20 20
      AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp
  52. 1 1
      AI/Nullkiller/Goals/ExecuteHeroChain.cpp
  53. 2 2
      AI/Nullkiller/Goals/RecruitHero.cpp
  54. 1 1
      AI/Nullkiller/Goals/StayAtTown.cpp
  55. 9 9
      AI/Nullkiller/Helpers/ArmyFormation.cpp
  56. 1 1
      AI/Nullkiller/Markers/UnlockCluster.h
  57. 57 51
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  58. 3 7
      AI/Nullkiller/Pathfinding/AINodeStorage.h
  59. 0 1
      AI/Nullkiller/Pathfinding/AIPathfinder.cpp
  60. 3 3
      AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp
  61. 1 1
      AI/Nullkiller/Pathfinding/AIPathfinderConfig.h
  62. 4 4
      AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.cpp
  63. 4 2
      AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h
  64. 2 4
      AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp
  65. 6 0
      AI/Nullkiller/Pathfinding/Actions/BoatActions.h
  66. 2 2
      AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.cpp
  67. 9 6
      AI/Nullkiller/Pathfinding/Actions/QuestAction.cpp
  68. 1 1
      AI/Nullkiller/Pathfinding/Actions/TownPortalAction.cpp
  69. 3 1
      AI/Nullkiller/Pathfinding/Actions/TownPortalAction.h
  70. 7 8
      AI/Nullkiller/Pathfinding/Actors.cpp
  71. 1 1
      AI/Nullkiller/Pathfinding/Actors.h
  72. 5 5
      AI/Nullkiller/Pathfinding/GraphPaths.cpp
  73. 0 1
      AI/Nullkiller/Pathfinding/ObjectGraph.cpp
  74. 9 10
      AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp
  75. 28 15
      AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp
  76. 0 1
      AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h
  77. 4 3
      AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp
  78. 0 1
      AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h
  79. 0 1
      AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h
  80. 0 1
      AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.h
  81. 0 1
      AI/Nullkiller/StdInc.h
  82. 2 6
      AI/StupidAI/StupidAI.cpp
  83. 1 2
      AI/StupidAI/StupidAI.h
  84. 0 1
      AI/StupidAI/main.cpp
  85. 10 4
      AI/VCAI/AIUtility.cpp
  86. 1 2
      AI/VCAI/AIUtility.h
  87. 1 2
      AI/VCAI/ArmyManager.cpp
  88. 0 1
      AI/VCAI/BuildingManager.cpp
  89. 1 1
      AI/VCAI/FuzzyEngines.cpp
  90. 1 1
      AI/VCAI/FuzzyHelper.cpp
  91. 2 1
      AI/VCAI/Goals/AbstractGoal.cpp
  92. 0 1
      AI/VCAI/Goals/AbstractGoal.h
  93. 10 4
      AI/VCAI/Goals/AdventureSpellCast.cpp
  94. 1 1
      AI/VCAI/Goals/Build.cpp
  95. 1 1
      AI/VCAI/Goals/BuildThis.cpp
  96. 2 2
      AI/VCAI/Goals/BuildThis.h
  97. 4 4
      AI/VCAI/Goals/CollectRes.cpp
  98. 42 30
      AI/VCAI/Goals/CompleteQuest.cpp
  99. 0 1
      AI/VCAI/Goals/CompleteQuest.h
  100. 2 2
      AI/VCAI/Goals/GatherArmy.cpp

+ 190 - 0
.clang-format

@@ -0,0 +1,190 @@
+---
+Language:        Cpp
+# BasedOnStyle:  Mozilla
+AccessModifierOffset: -4
+AlignAfterOpenBracket: BlockIndent
+AlignArrayOfStructures: Left
+AlignConsecutiveMacros: false 
+AlignConsecutiveAssignments: false 
+AlignConsecutiveBitFields: false 
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Left
+AlignOperands:   AlignAfterOperator
+AlignTrailingComments: false
+AllowAllArgumentsOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortEnumsOnASingleLine: false
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortLambdasOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: true
+AlwaysBreakTemplateDeclarations: Yes
+AttributeMacros:
+  - __capability
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+  AfterCaseLabel:  false
+  AfterClass:      true
+  AfterControlStatement: Never
+  AfterEnum:       true
+  AfterFunction:   true
+  AfterNamespace:  false
+  AfterObjCDeclaration: false
+  AfterStruct:     true
+  AfterUnion:      true
+  AfterExternBlock: true
+  BeforeCatch:     false
+  BeforeElse:      false
+  BeforeLambdaBody: false
+  BeforeWhile:     false
+  IndentBraces:    false
+  SplitEmptyFunction: false
+  SplitEmptyRecord: false
+  SplitEmptyNamespace: false
+BreakBeforeBinaryOperators: None
+BreakBeforeConceptDeclarations: true
+BreakBeforeBraces: Allman
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeComma
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit:     160
+CommentPragmas:  '^ IWYU pragma:'
+QualifierAlignment: Left
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DeriveLineEnding: true
+DerivePointerAlignment: false
+DisableFormat:   false
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: LogicalBlock
+ExperimentalAutoDetectBinPacking: false
+PackConstructorInitializers: BinPack
+BasedOnStyle:    ''
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+AllowAllConstructorInitializersOnNextLine: true
+FixNamespaceComments: false
+ForEachMacros:
+  - foreach
+  - Q_FOREACH
+  - BOOST_FOREACH
+IfMacros:
+  - KJ_IF_MAYBE
+IncludeBlocks: Preserve
+IncludeCategories:
+  # Precompiled header
+  - Regex:           '"StdInc\.h"'
+    Priority:        0
+    SortPriority:    -1
+IncludeIsMainRegex: '(Test)?$'
+IncludeIsMainSourceRegex: ''
+IndentAccessModifiers: false
+IndentCaseLabels: true
+IndentCaseBlocks: false
+IndentGotoLabels: true
+IndentPPDirectives: AfterHash
+IndentExternBlock: AfterExternBlock
+IndentRequires:  false
+IndentWidth:     4
+IndentWrappedFunctionNames: false
+InsertTrailingCommas: None
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+LambdaBodyIndentation: Signature
+MacroBlockBegin: ''
+MacroBlockEnd:   ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 4
+ObjCBreakBeforeNestedBlockParam: true
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: false
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakOpenParenthesis: 0
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 200
+PenaltyIndentedWhitespace: 0
+PointerAlignment: Middle
+PPIndentWidth:   -1
+ReferenceAlignment: Pointer
+ReflowComments:  false
+RemoveBracesLLVM: false
+SeparateDefinitionBlocks: Leave
+ShortNamespaceLines: 1
+SortIncludes:    CaseSensitive
+SortJavaStaticImport: Before
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: Never
+SpaceBeforeParensOptions:
+  AfterControlStatements: true
+  AfterForeachMacros: true
+  AfterFunctionDefinitionName: false
+  AfterFunctionDeclarationName: false
+  AfterIfMacros:   true
+  AfterOverloadedOperator: false
+  BeforeNonEmptyParentheses: false
+SpaceAroundPointerQualifiers: Default
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles:  Never
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInLineCommentPrefix:
+  Minimum:         0
+  Maximum:         -1
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpaceBeforeSquareBrackets: false
+BitFieldColonSpacing: Both
+Standard:        Latest
+StatementAttributeLikeMacros:
+  - FALLTHROUGH
+  - STRONG_INLINE
+  - DLL_EXPORT
+  - DLL_IMPORT
+  - ELF_VISIBILITY
+  - Q_EMIT
+StatementMacros:
+  - Q_UNUSED
+  - QT_REQUIRE_VERSION
+TabWidth:        4
+UseCRLF:         false
+UseTab:          ForContinuationAndIndentation 
+WhitespaceSensitiveMacros:
+  - STRINGIZE
+  - PP_STRINGIZE
+  - BOOST_PP_STRINGIZE
+  - NS_SWIFT_NAME
+  - CF_SWIFT_NAME
+...
+
+

+ 83 - 0
.clang-tidy

@@ -0,0 +1,83 @@
+# Enabled, but situational - a lot of triggers in old code:
+#  -bugprone-narrowing-conversions,                      # might be too noisy
+#  -cppcoreguidelines-narrowing-conversions,             # might be too noisy
+#  -cppcoreguidelines-pro-bounds-pointer-arithmetic,     # pointer arithmetic should be avoided, with possible exception for low-level reader classes
+#  -misc-private-member-variables-in-classes,            # good for classes, should be disabled/ignored for structs
+#  -readability-function-cognitive-complexity,           # can show candidates for refactoring
+#
+# Currently disabled options:
+#  -misc-unused-parameters,                              # a lot of intended cases, e.g. interfaces & their implementations
+#  -misc-include-cleaner,                                # a lot of false-positives, mostly due to StdInc.h
+#  -cppcoreguidelines-init-variables,                    # better to rely on "maybe initialized" compiler warnings
+#  -cppcoreguidelines-owning-memory,                     # requires 'gsl' library presence
+#  -cppcoreguidelines-avoid-magic-numbers,               # too much noise
+#  -readability-identifier-length,                       # too much noise
+#  -readability-magic-numbers,                           # too much noise
+#  -bugprone-easily-swappable-parameters,                # improvement doubtful
+#  -cppcoreguidelines-avoid-const-or-ref-data-members    # improvement doubtful
+#  -cppcoreguidelines-special-member-functions,          # improvement doubtful
+#  -cppcoreguidelines-pro-bounds-constant-array-index,   # improvement doubtful
+#  -readability-static-accessed-through-instance,        # improvement doubtful
+#  -readability-else-after-return,                       # improvement doubtful
+#  -modernize-pass-by-value,                             # improvement doubtful
+#  -google-readability-braces-around-statements,         # not in our code style
+#  -google-readability-namespace-comments,               # not in our code style
+#  -google-readability-todo,                             # not in our code style
+#  -modernize-use-trailing-return-type,                  # not in our code style
+#  -modernize-return-braced-init-list,                   # not in our code style
+#  -modernize-use-nodiscard,                             # not in our code style
+#  -readability-braces-around-statements,                # not in our code style
+#  -readability-implicit-bool-conversion,                # not in our code style for pointers, and int-only detection seems to be broken?
+#  -readability-uppercase-literal-suffix                 # not in our code style
+#  -readability-convert-member-functions-to-static,      # candidate for re-enabling, to show poorly designed class methods
+
+Checks: >
+  -*,
+  boost-*,
+  bugprone-*,
+  clang-*,
+  cppcoreguidelines-*,
+  google-*,
+  misc-*,
+  modernize-*,
+  performance-*,
+  readability-*,
+  -bugprone-easily-swappable-parameters,
+  -cppcoreguidelines-avoid-const-or-ref-data-members,
+  -cppcoreguidelines-avoid-magic-numbers,
+  -cppcoreguidelines-init-variables,
+  -cppcoreguidelines-owning-memory,
+  -cppcoreguidelines-special-member-functions,
+  -cppcoreguidelines-pro-bounds-constant-array-index,
+  -google-readability-braces-around-statements,
+  -google-readability-namespace-comments,
+  -google-readability-todo,
+  -misc-include-cleaner,
+  -misc-unused-parameters,
+  -modernize-use-trailing-return-type,
+  -modernize-return-braced-init-list,
+  -modernize-pass-by-value,
+  -modernize-use-nodiscard,
+  -readability-braces-around-statements,
+  -readability-convert-member-functions-to-static,
+  -readability-else-after-return,
+  -readability-identifier-length,
+  -readability-implicit-bool-conversion,
+  -readability-magic-numbers,
+  -readability-static-accessed-through-instance,
+  -readability-uppercase-literal-suffix,
+  -readability-use-anyofallof
+
+CheckOptions:
+  - key:             misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
+    value:           'true'
+  - key:             llvm-else-after-return.WarnOnConditionVariables
+    value:           'false'
+  - key:             misc-const-correctness.AnalyzeValues 
+    value:           'false'
+  - key:             misc-include-cleaner.IgnoreHeaders
+    value:           'StdInc.h'
+  - key:             readability-function-size.StatementThreshold
+    value:           '800'
+  - key:             misc-include-cleaner.IgnoreHeaders
+    value:           'StdInc.h'

+ 20 - 0
.editorconfig

@@ -0,0 +1,20 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = tab
+insert_final_newline = true
+spelling_language = en-US
+trim_trailing_whitespace = true
+
+[{*.py,CMakePresets.json}]
+indent_style = space
+indent_size = 4
+
+[*.{md,yml}]
+indent_style = space
+indent_size = 2
+
+[*.ui]
+indent_style = space
+indent_size = 1

+ 50 - 0
.github/workflows/aab-from-build.yml

@@ -0,0 +1,50 @@
+name: Create AAB from archived android build dir
+
+on:
+  workflow_dispatch:
+    inputs:
+      build_dir_xz_url:
+        description: 'URL to XZ-archived android build dir'
+        required: true
+        type: string
+
+jobs:
+  aab:
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+      with:
+        sparse-checkout: CI/android
+
+    - name: Install JDK
+      uses: actions/setup-java@v4
+      with:
+        distribution: 'temurin'
+        java-version: '11'
+
+    - name: Download & unpack archive
+      run: curl -L '${{ inputs.build_dir_xz_url }}' | tar -xf - --xz
+
+    - name: Build aab
+      run: |
+        set -x
+
+        repoPath="$(pwd)"
+        cd android-build
+
+        sed -iEe "s|qt5AndroidDir=.+|qt5AndroidDir=$(pwd)/qt5AndroidDir|" gradle.properties
+        echo "sdk.dir=$ANDROID_HOME" > local.properties
+        echo "signingRoot=$repoPath/CI/android" > vcmi-app/gradle.properties
+
+        ./gradlew bundleRelease
+        ANDROID_AAB_PATH="$(ls vcmi-app/build/outputs/bundle/release/*.aab)"
+        echo "ANDROID_AAB_PATH=$ANDROID_AAB_PATH" >> $GITHUB_ENV
+
+    - name: Artifact
+      uses: actions/upload-artifact@v4
+      with:
+        name: aab
+        compression-level: 0
+        path: |
+          ${{ env.ANDROID_AAB_PATH }}

+ 1 - 1
.github/workflows/github.yml

@@ -408,7 +408,7 @@ jobs:
             python3 CI/validate_json.py
 
         - name: Validate Markdown
-          uses: DavidAnson/markdownlint-cli2-action@v19
+          uses: DavidAnson/markdownlint-cli2-action@v20
           with:
             config: 'CI/example.markdownlint-cli2.jsonc'
             globs: '**/*.md'

+ 2 - 0
AI/BattleAI/AttackPossibility.cpp

@@ -17,6 +17,8 @@
 #include "../../lib/spells/ObstacleCasterProxy.h"
 #include "../../lib/battle/CObstacleInstance.h"
 
+#include "../../lib/GameLibrary.h"
+
 uint64_t averageDmg(const DamageRange & range)
 {
 	return (range.min + range.max) / 2;

+ 0 - 1
AI/BattleAI/AttackPossibility.h

@@ -9,7 +9,6 @@
  */
 #pragma once
 #include "../../lib/battle/CUnitState.h"
-#include "../../CCallback.h"
 #include "StackWithBonuses.h"
 
 #define BATTLE_TRACE_LEVEL 0

+ 4 - 5
AI/BattleAI/BattleAI.cpp

@@ -17,6 +17,9 @@
 #include "tbb/parallel_for.h"
 #include "../../lib/CStopWatch.h"
 #include "../../lib/CThreadHelper.h"
+#include "../../lib/battle/CPlayerBattleCallback.h"
+#include "../../lib/callback/CBattleCallback.h"
+#include "../../lib/callback/IGameInfoCallback.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/spells/ISpellMechanics.h"
@@ -34,8 +37,7 @@
 
 CBattleAI::CBattleAI()
 	: side(BattleSide::NONE),
-	wasWaitingForRealize(false),
-	wasUnlockingGs(false)
+	wasWaitingForRealize(false)
 {
 }
 
@@ -45,7 +47,6 @@ CBattleAI::~CBattleAI()
 	{
 		//Restore previous state of CB - it may be shared with the main AI (like VCAI)
 		cb->waitTillRealize = wasWaitingForRealize;
-		cb->unlockGsWhenWaiting = wasUnlockingGs;
 	}
 }
 
@@ -66,9 +67,7 @@ void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
 	cb = CB;
 	playerID = *CB->getPlayerID();
 	wasWaitingForRealize = CB->waitTillRealize;
-	wasUnlockingGs = CB->unlockGsWhenWaiting;
 	CB->waitTillRealize = false;
-	CB->unlockGsWhenWaiting = false;
 	movesSkippedByDefense = 0;
 
 	logHexNumbers();

+ 1 - 2
AI/BattleAI/BattleAI.h

@@ -8,8 +8,8 @@
  *
  */
 #pragma once
-#include "../../lib/AI_Base.h"
 #include "../../lib/battle/ReachabilityInfo.h"
+#include "../../lib/callback/CGameInterface.h"
 #include "PossibleSpellcast.h"
 #include "PotentialTargets.h"
 
@@ -60,7 +60,6 @@ class CBattleAI : public CBattleGameInterface
 
 	//Previous setting of cb
 	bool wasWaitingForRealize;
-	bool wasUnlockingGs;
 	int movesSkippedByDefense;
 
 public:

+ 4 - 1
AI/BattleAI/BattleEvaluator.cpp

@@ -16,6 +16,8 @@
 #include "tbb/parallel_for.h"
 #include "../../lib/CStopWatch.h"
 #include "../../lib/CThreadHelper.h"
+#include "../../lib/battle/CPlayerBattleCallback.h"
+#include "../../lib/callback/CBattleCallback.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/entities/building/TownFortifications.h"
 #include "../../lib/spells/CSpellHandler.h"
@@ -24,6 +26,7 @@
 #include "../../lib/battle/CObstacleInstance.h"
 #include "../../lib/battle/BattleAction.h"
 #include "../../lib/CRandomGenerator.h"
+#include "../../lib/GameLibrary.h"
 
 
 // TODO: remove
@@ -567,7 +570,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
 					ourTurnSpan++;
 				}
 
-				state->nextTurn(unit->unitId());
+				state->nextTurn(unit->unitId(), BattleUnitTurnReason::TURN_QUEUE);
 
 				PotentialTargets potentialTargets(unit, damageCache, state);
 

+ 2 - 1
AI/BattleAI/BattleEvaluator.h

@@ -8,7 +8,6 @@
  *
  */
 #pragma once
-#include "../../lib/AI_Base.h"
 #include "../../lib/battle/ReachabilityInfo.h"
 #include "PossibleSpellcast.h"
 #include "PotentialTargets.h"
@@ -17,6 +16,8 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 class CSpell;
+class CBattleCallback;
+class BattleAction;
 
 VCMI_LIB_NAMESPACE_END
 

+ 13 - 11
AI/BattleAI/BattleExchangeVariant.cpp

@@ -11,7 +11,9 @@
 #include "BattleExchangeVariant.h"
 #include "BattleEvaluator.h"
 #include "../../lib/CStack.h"
-#include "tbb/parallel_for.h"
+#include "../../lib/GameLibrary.h"
+
+#include <tbb/parallel_for.h>
 
 AttackerValue::AttackerValue()
 	: value(0),
@@ -383,7 +385,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 		}
 
 		auto turnsToReach = (distance - 1) / speed + 1;
-		const BattleHexArray & hexes = enemy->getSurroundingHexes();
+		const BattleHexArray & hexes = enemy->getAttackableHexes(activeStack);
 		auto enemySpeed = enemy->getMovementRange();
 		auto speedRatio = speed / static_cast<float>(enemySpeed);
 		auto multiplier = (speedRatio > 1 ? 1 : speedRatio) * penaltyMultiplier;
@@ -414,8 +416,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 				logAi->trace("New high score");
 #endif
 
-				for(BattleHex enemyHex : enemy->getAttackableHexes(activeStack))
-				{
+					BattleHex enemyHex = hex;
 					while(!flying && dists.distances[enemyHex.toInt()] > speed && dists.predecessors.at(enemyHex.toInt()).isValid())
 					{
 						enemyHex = dists.predecessors.at(enemyHex.toInt());
@@ -423,14 +424,17 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 						if(dists.accessibility[enemyHex.toInt()] == EAccessibility::ALIVE_STACK)
 						{
 							auto defenderToBypass = hb->battleGetUnitByPos(enemyHex);
-
-							if(defenderToBypass)
+							assert(defenderToBypass != nullptr);
+							auto attackHex = dists.predecessors[enemyHex.toInt()];
+							
+							if(defenderToBypass &&
+							   defenderToBypass != enemy &&
+							   vstd::contains(defenderToBypass->getAttackableHexes(activeStack), attackHex))
 							{
 #if BATTLE_TRACE_LEVEL >= 1
 								logAi->trace("Found target to bypass at %d", enemyHex.toInt());
 #endif
-
-								auto attackHex = dists.predecessors[enemyHex.toInt()];
+								
 								auto baiBypass = BattleAttackInfo(activeStack, defenderToBypass, 0, cb->battleCanShoot(activeStack));
 								auto attackBypass = AttackPossibility::evaluate(baiBypass, attackHex, damageCache, hb);
 
@@ -459,9 +463,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
 						}
 					}
 
-					result.positions.insert(enemyHex);
-				}
-
+				result.positions.insert(enemyHex);
 				result.cachedAttack = attack;
 				result.turnsToReach = turnsToReach;
 			}

+ 0 - 1
AI/BattleAI/BattleExchangeVariant.h

@@ -9,7 +9,6 @@
  */
 #pragma once
 
-#include "../../lib/AI_Base.h"
 #include "../../lib/battle/ReachabilityInfo.h"
 #include "PotentialTargets.h"
 #include "StackWithBonuses.h"

+ 15 - 18
AI/BattleAI/PotentialTargets.cpp

@@ -22,21 +22,11 @@ PotentialTargets::PotentialTargets(
 	auto avHexes = state->battleGetAvailableHexes(reachability, attackerInfo, false);
 
 	//FIXME: this should part of battleGetAvailableHexes
-	bool forceTarget = false;
-	const battle::Unit * forcedTarget = nullptr;
-	BattleHex forcedHex;
+	bool isBerserk = attackerInfo->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE);
+	ForcedAction forcedAction;
 
-	if(attackerInfo->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE))
-	{
-		forceTarget = true;
-		auto nearest = state->getNearestStack(attackerInfo);
-
-		if(nearest.first != nullptr)
-		{
-			forcedTarget = nearest.first;
-			forcedHex = nearest.second;
-		}
-	}
+	if(isBerserk)
+		forcedAction = state->getBerserkForcedAction(attackerInfo);
 
 	auto aliveUnits = state->battleGetUnitsIf([=](const battle::Unit * unit)
 	{
@@ -45,7 +35,7 @@ PotentialTargets::PotentialTargets(
 
 	for(auto defender : aliveUnits)
 	{
-		if(!forceTarget && !state->battleMatchOwner(attackerInfo, defender))
+		if(!isBerserk && !state->battleMatchOwner(attackerInfo, defender))
 			continue;
 
 		auto GenerateAttackInfo = [&](bool shooting, const BattleHex & hex) -> AttackPossibility
@@ -56,12 +46,19 @@ PotentialTargets::PotentialTargets(
 			return AttackPossibility::evaluate(bai, hex, damageCache, state);
 		};
 
-		if(forceTarget)
+		if(isBerserk)
 		{
-			if(forcedTarget && defender->unitId() == forcedTarget->unitId())
-				possibleAttacks.push_back(GenerateAttackInfo(false, forcedHex));
+			bool isActionAttack = forcedAction.type == EActionType::WALK_AND_ATTACK || forcedAction.type == EActionType::SHOOT;
+			if (isActionAttack && defender->unitId() == forcedAction.target->unitId())
+			{
+				bool rangeAttack = forcedAction.type == EActionType::SHOOT;
+				BattleHex hex = forcedAction.type == EActionType::WALK_AND_ATTACK ? forcedAction.position : BattleHex::INVALID;
+				possibleAttacks.push_back(GenerateAttackInfo(rangeAttack, hex));
+			}
 			else
+			{
 				unreachableEnemies.push_back(defender);
+			}
 		}
 		else if(state->battleCanShoot(attackerInfo, defender->getPosition()))
 		{

+ 21 - 14
AI/BattleAI/StackWithBonuses.cpp

@@ -15,6 +15,7 @@
 #include "../../lib/battle/BattleLayout.h"
 #include "../../lib/CStack.h"
 #include "../../lib/ScriptHandler.h"
+#include "../../lib/gameState/GameStatePackVisitor.h"
 #include "../../lib/networkPacks/PacksForClientBattle.h"
 #include "../../lib/networkPacks/SetStackEffect.h"
 
@@ -132,11 +133,10 @@ SlotID StackWithBonuses::unitSlot() const
 	return slot;
 }
 
-TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const CSelector & limit,
-	const std::string & cachingStr) const
+TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const std::string & cachingStr) const
 {
 	auto ret = std::make_shared<BonusList>();
-	TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, cachingStr);
+	TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, cachingStr);
 
 	vstd::copy_if(*originalList, std::back_inserter(*ret), [this](const std::shared_ptr<Bonus> & b)
 	{
@@ -146,7 +146,7 @@ TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, c
 
 	for(const Bonus & bonus : bonusesToUpdate)
 	{
-		if(selector(&bonus) && (!limit || limit(&bonus)))
+		if(selector(&bonus))
 		{
 			if(ret->getFirst(Selector::source(BonusSource::SPELL_EFFECT, bonus.sid).And(Selector::typeSubtype(bonus.type, bonus.subtype))))
 			{
@@ -163,7 +163,7 @@ TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, c
 	for(auto & bonus : bonusesToAdd)
 	{
 		auto b = std::make_shared<Bonus>(bonus);
-		if(selector(b.get()) && (!limit || !limit(b.get())))
+		if(selector(b.get()))
 			ret->push_back(b);
 	}
 	//TODO limiters?
@@ -342,14 +342,14 @@ void HypotheticBattle::nextRound()
 	}
 }
 
-void HypotheticBattle::nextTurn(uint32_t unitId)
+void HypotheticBattle::nextTurn(uint32_t unitId, BattleUnitTurnReason reason)
 {
 	activeUnitId = unitId;
 	auto unit = getForUpdate(unitId);
 
 	unit->removeUnitBonus(Bonus::UntilGetsTurn);
 
-	unit->afterGetsTurn();
+	unit->afterGetsTurn(reason);
 }
 
 void HypotheticBattle::addUnit(uint32_t id, const JsonNode & data)
@@ -538,37 +538,44 @@ void HypotheticBattle::HypotheticServerCallback::apply(CPackForClient & pack)
 
 void HypotheticBattle::HypotheticServerCallback::apply(BattleLogMessage & pack)
 {
-	pack.applyBattle(owner);
+	BattleStatePackVisitor visitor(*owner);
+	pack.visit(visitor);
 }
 
 void HypotheticBattle::HypotheticServerCallback::apply(BattleStackMoved & pack)
 {
-	pack.applyBattle(owner);
+	BattleStatePackVisitor visitor(*owner);
+	pack.visit(visitor);
 }
 
 void HypotheticBattle::HypotheticServerCallback::apply(BattleUnitsChanged & pack)
 {
-	pack.applyBattle(owner);
+	BattleStatePackVisitor visitor(*owner);
+	pack.visit(visitor);
 }
 
 void HypotheticBattle::HypotheticServerCallback::apply(SetStackEffect & pack)
 {
-	pack.applyBattle(owner);
+	BattleStatePackVisitor visitor(*owner);
+	pack.visit(visitor);
 }
 
 void HypotheticBattle::HypotheticServerCallback::apply(StacksInjured & pack)
 {
-	pack.applyBattle(owner);
+	BattleStatePackVisitor visitor(*owner);
+	pack.visit(visitor);
 }
 
 void HypotheticBattle::HypotheticServerCallback::apply(BattleObstaclesChanged & pack)
 {
-	pack.applyBattle(owner);
+	BattleStatePackVisitor visitor(*owner);
+	pack.visit(visitor);
 }
 
 void HypotheticBattle::HypotheticServerCallback::apply(CatapultAttack & pack)
 {
-	pack.applyBattle(owner);
+	BattleStatePackVisitor visitor(*owner);
+	pack.visit(visitor);
 }
 
 HypotheticBattle::HypotheticEnvironment::HypotheticEnvironment(HypotheticBattle * owner_, const Environment * upperEnvironment)

+ 2 - 3
AI/BattleAI/StackWithBonuses.h

@@ -90,8 +90,7 @@ public:
 	SlotID unitSlot() const override;
 
 	///IBonusBearer
-	TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit,
-		const std::string & cachingStr = "") const override;
+	TConstBonusListPtr getAllBonuses(const CSelector & selector, const std::string & cachingStr = "") const override;
 
 	int32_t getTreeVersion() const override;
 
@@ -137,7 +136,7 @@ public:
 	battle::Units getUnitsIf(const battle::UnitFilter & predicate) const override;
 
 	void nextRound() override;
-	void nextTurn(uint32_t unitId) override;
+	void nextTurn(uint32_t unitId, BattleUnitTurnReason reason) override;
 
 	void addUnit(uint32_t id, const JsonNode & data) override;
 	void setUnitState(uint32_t id, const JsonNode & data, int64_t healthDelta) override;

+ 0 - 1
AI/BattleAI/main.cpp

@@ -8,7 +8,6 @@
  *
  */
 #include "StdInc.h"
-#include "../../lib/AI_Base.h"
 #include "BattleAI.h"
 
 #ifdef __GNUC__

+ 3 - 8
AI/CMakeLists.txt

@@ -30,14 +30,9 @@ if(NOT fuzzylite_FOUND)
 	if(ANDROID)
 		set(FL_BACKTRACE OFF CACHE BOOL "" FORCE)
 	endif()
-	#It is for compiling FuzzyLite, it will not compile without it on GCC
-	if("x${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "xGNU" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU")
-		add_compile_options(-Wno-error=deprecated-declarations)
-	endif()
-	add_subdirectory(FuzzyLite/fuzzylite EXCLUDE_FROM_ALL)
-	set_property(TARGET fl-static PROPERTY CXX_STANDARD 14) # doesn't compile under 17 due to using removed symbol(s)
-	add_library(fuzzylite::fuzzylite ALIAS fl-static)
-	target_include_directories(fl-static PUBLIC ${CMAKE_HOME_DIRECTORY}/AI/FuzzyLite/fuzzylite)
+
+	add_subdirectory(FuzzyLite EXCLUDE_FROM_ALL)
+	add_library(fuzzylite::fuzzylite ALIAS staticTarget)
 endif()
 
 #######################################

+ 2 - 0
AI/EmptyAI/CEmptyAI.cpp

@@ -13,6 +13,8 @@
 #include "../../lib/CRandomGenerator.h"
 #include "../../lib/CStack.h"
 #include "../../lib/battle/BattleAction.h"
+#include "../../lib/battle/CPlayerBattleCallback.h"
+#include "../../lib/callback/CCallback.h"
 
 void CEmptyAI::initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB)
 {

+ 1 - 2
AI/EmptyAI/CEmptyAI.h

@@ -9,8 +9,7 @@
  */
 #pragma once
 
-#include "../../lib/AI_Base.h"
-#include "../../CCallback.h"
+#include "../../lib/callback/CGlobalAI.h"
 
 struct HeroMoveDetails;
 

+ 1 - 1
AI/FuzzyLite

@@ -1 +1 @@
-Subproject commit 7aee562d6ca17f3cf42588ffb5116e03017c3c50
+Subproject commit 13b3122f5c353c0389ed4e66041d548c44ec9df6

+ 65 - 43
AI/Nullkiller/AIGateway.cpp

@@ -9,13 +9,17 @@
  */
 #include "StdInc.h"
 
-#include "../../lib/ArtifactUtils.h"
+#include "../../lib/AsyncRunner.h"
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/StartInfo.h"
+#include "../../lib/battle/CPlayerBattleCallback.h"
+#include "../../lib/entities/artifact/ArtifactUtils.h"
+#include "../../lib/entities/artifact/CArtifact.h"
 #include "../../lib/entities/building/CBuilding.h"
 #include "../../lib/mapObjects/MapObjects.h"
 #include "../../lib/mapObjects/ObjectTemplate.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapping/TerrainTile.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/IGameSettings.h"
 #include "../../lib/gameState/CGameState.h"
@@ -32,8 +36,6 @@
 #include "AIGateway.h"
 #include "Goals/Goals.h"
 
-static tbb::task_arena executeActionAsyncArena;
-
 namespace NKAI
 {
 
@@ -68,12 +70,13 @@ struct SetGlobalState
 #define MAKING_TURN SET_GLOBAL_STATE(this)
 
 AIGateway::AIGateway()
+	:status(this)
 {
 	LOG_TRACE(logAi);
 	destinationTeleport = ObjectInstanceID();
 	destinationTeleportPos = int3(-1);
 	nullkiller.reset(new Nullkiller());
-	asyncTasks = std::make_unique<tbb::task_group>();
+	asyncTasks = std::make_unique<AsyncRunner>();
 }
 
 AIGateway::~AIGateway()
@@ -125,7 +128,7 @@ void AIGateway::heroMoved(const TryMoveHero & details, bool verbose)
 	else if(details.result == TryMoveHero::EMBARK && hero)
 	{
 		//make sure AI not attempt to visit used boat
-		validateObject(hero->boat);
+		validateObject(hero->getBoat());
 	}
 	else if(details.result == TryMoveHero::DISEMBARK && o1)
 	{
@@ -266,7 +269,7 @@ void AIGateway::heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance
 	NET_EVENT_HANDLER;
 }
 
-void AIGateway::tileHidden(const std::unordered_set<int3> & pos)
+void AIGateway::tileHidden(const FowTilesType & pos)
 {
 	LOG_TRACE(logAi);
 	NET_EVENT_HANDLER;
@@ -274,7 +277,7 @@ void AIGateway::tileHidden(const std::unordered_set<int3> & pos)
 	nullkiller->memory->removeInvisibleObjects(myCb.get());
 }
 
-void AIGateway::tileRevealed(const std::unordered_set<int3> & pos)
+void AIGateway::tileRevealed(const FowTilesType & pos)
 {
 	LOG_TRACE(logAi);
 	NET_EVENT_HANDLER;
@@ -324,9 +327,15 @@ void AIGateway::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID her
 	});
 }
 
+void AIGateway::heroExperienceChanged(const CGHeroInstance * hero, si64 val)
+{
+	LOG_TRACE_PARAMS(logAi, "val '%i'", val);
+	NET_EVENT_HANDLER;
+}
+
 void AIGateway::heroPrimarySkillChanged(const CGHeroInstance * hero, PrimarySkill which, si64 val)
 {
-	LOG_TRACE_PARAMS(logAi, "which '%i', val '%i'", static_cast<int>(which) % val);
+	LOG_TRACE_PARAMS(logAi, "which '%i', val '%i'", which.getNum() % val);
 	NET_EVENT_HANDLER;
 }
 
@@ -575,7 +584,6 @@ void AIGateway::initGameInterface(std::shared_ptr<Environment> env, std::shared_
 	NET_EVENT_HANDLER;
 	playerID = *myCb->getPlayerID();
 	myCb->waitTillRealize = true;
-	myCb->unlockGsWhenWaiting = true;
 
 	nullkiller->init(CB, this);
 	
@@ -593,11 +601,11 @@ void AIGateway::yourTurn(QueryID queryID)
 
 	nullkiller->makingTurnInterrupption.reset();
 
-	executeActionAsyncArena.enqueue(asyncTasks->defer([this]()
+	asyncTasks->run([this]()
 	{
 		ScopedThreadName guard("NKAI::makingTurn");
 		makeTurn();
-	}));
+	});
 }
 
 void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID)
@@ -629,7 +637,7 @@ void AIGateway::commanderGotLevel(const CCommanderInstance * commander, std::vec
 {
 	LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
 	NET_EVENT_HANDLER;
-	status.addQuery(queryID, boost::str(boost::format("Commander %s of %s got level %d") % commander->name % commander->armyObj->nodeName() % (int)commander->level));
+	status.addQuery(queryID, boost::str(boost::format("Commander %s of %s got level %d") % commander->name % commander->getArmy()->nodeName() % (int)commander->level));
 	executeActionAsync("commanderGotLevel", [this, queryID](){ answerQuery(queryID, 0); });
 }
 
@@ -774,7 +782,7 @@ void AIGateway::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstan
 	//you can't request action from action-response thread
 	executeActionAsync("showGarrisonDialog", [this, up, down, removableUnits, queryID]()
 	{
-		if(removableUnits && up->tempOwner == down->tempOwner && nullkiller->settings->isGarrisonTroopsUsageAllowed() && !cb->getStartInfo()->isRestorationOfErathiaCampaign())
+		if(removableUnits && up->tempOwner == down->tempOwner && nullkiller->settings->isGarrisonTroopsUsageAllowed() && !cb->getStartInfo()->restrictedGarrisonsForAI())
 		{
 			pickBestCreatures(down, up);
 		}
@@ -817,11 +825,11 @@ bool AIGateway::makePossibleUpgrades(const CArmedInstance * obj)
 					int oldValue = s->getCreature()->getAIValue();
 					int newValue = upgID.toCreature()->getAIValue();
 
-					if(newValue > oldValue && nullkiller->getFreeResources().canAfford(upgradeInfo.getUpgradeCostsFor(upgID) * s->count))
+					if(newValue > oldValue && nullkiller->getFreeResources().canAfford(upgradeInfo.getUpgradeCostsFor(upgID) * s->getCount()))
 					{
 						myCb->upgradeCreature(obj, SlotID(i), upgID);
 						upgraded = true;
-						logAi->debug("Upgraded %d %s to %s", s->count, upgradeInfo.oldID.toCreature()->getNamePluralTranslated(), 
+						logAi->debug("Upgraded %d %s to %s", s->getCount(), upgradeInfo.oldID.toCreature()->getNamePluralTranslated(),
 							upgradeInfo.getUpgrade().toCreature()->getNamePluralTranslated());
 					}
 					else
@@ -905,19 +913,19 @@ void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h
 	switch(obj->ID)
 	{
 	case Obj::TOWN:
-		if(h->visitedTown) //we are inside, not just attacking
+		if(h->getVisitedTown()) //we are inside, not just attacking
 		{
 			makePossibleUpgrades(h.get());
 
 			std::unique_lock lockGuard(nullkiller->aiStateMutex);
 
-			if(!h->visitedTown->garrisonHero || !nullkiller->isHeroLocked(h->visitedTown->garrisonHero))
-				moveCreaturesToHero(h->visitedTown);
+			if(!h->getVisitedTown()->getGarrisonHero() || !nullkiller->isHeroLocked(h->getVisitedTown()->getGarrisonHero()))
+				moveCreaturesToHero(h->getVisitedTown());
 
 			if(nullkiller->heroManager->getHeroRole(h) == HeroRole::MAIN && !h->hasSpellbook()
 				&& nullkiller->getFreeGold() >= GameConstants::SPELLBOOK_GOLD_COST)
 			{
-				if(h->visitedTown->hasBuilt(BuildingID::MAGES_GUILD_1))
+				if(h->getVisitedTown()->hasBuilt(BuildingID::MAGES_GUILD_1))
 					cb->buyArtifact(h.get(), ArtifactID::SPELLBOOK);
 			}
 		}
@@ -930,9 +938,9 @@ void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h
 
 void AIGateway::moveCreaturesToHero(const CGTownInstance * t)
 {
-	if(t->visitingHero && t->armedGarrison() && t->visitingHero->tempOwner == t->tempOwner)
+	if(t->getVisitingHero() && t->armedGarrison() && t->getVisitingHero()->tempOwner == t->tempOwner)
 	{
-		pickBestCreatures(t->visitingHero, t->getUpperArmy());
+		pickBestCreatures(t->getVisitingHero(), t->getUpperArmy());
 	}
 }
 
@@ -1040,6 +1048,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 	auto equipBest = [](const CGHeroInstance * h, const CGHeroInstance * otherh, bool giveStuffToFirstHero) -> void
 	{
 		bool changeMade = false;
+		std::set<std::pair<ArtifactInstanceID, ArtifactInstanceID> > swappedSet;
 		do
 		{
 			changeMade = false;
@@ -1050,22 +1059,22 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 			{
 				for(auto p : h->artifactsWorn)
 				{
-					if(p.second.artifact)
+					if(p.second.getArt())
 						allArtifacts.push_back(ArtifactLocation(h->id, p.first));
 				}
 			}
 			for(auto slot : h->artifactsInBackpack)
-				allArtifacts.push_back(ArtifactLocation(h->id, h->getArtPos(slot.artifact)));
+				allArtifacts.push_back(ArtifactLocation(h->id, h->getArtPos(slot.getArt())));
 
 			if(otherh)
 			{
 				for(auto p : otherh->artifactsWorn)
 				{
-					if(p.second.artifact)
+					if(p.second.getArt())
 						allArtifacts.push_back(ArtifactLocation(otherh->id, p.first));
 				}
 				for(auto slot : otherh->artifactsInBackpack)
-					allArtifacts.push_back(ArtifactLocation(otherh->id, otherh->getArtPos(slot.artifact)));
+					allArtifacts.push_back(ArtifactLocation(otherh->id, otherh->getArtPos(slot.getArt())));
 			}
 			//we give stuff to one hero or another, depending on giveStuffToFirstHero
 
@@ -1087,7 +1096,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 				auto s = artHolder->getSlot(location.slot);
 				if(!s || s->locked) //we can't move locks
 					continue;
-				auto artifact = s->artifact;
+				auto artifact = s->getArt();
 				if(!artifact)
 					continue;
 				//FIXME: why are the above possible to be null?
@@ -1111,21 +1120,30 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 					for(auto slot : artifact->getType()->getPossibleSlots().at(target->bearerType()))
 					{
 						auto otherSlot = target->getSlot(slot);
-						if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one
+						if(otherSlot && otherSlot->getArt()) //we need to exchange artifact for better one
 						{
-							int64_t otherArtifactScore = getArtifactScoreForHero(target, otherSlot->artifact);
-							logAi->trace( "Comparing artifacts of %s: %s vs %s. Score: %d vs %d", target->getHeroTypeName(), artifact->getType()->getJsonKey(), otherSlot->artifact->getType()->getJsonKey(), artifactScore, otherArtifactScore);
+							int64_t otherArtifactScore = getArtifactScoreForHero(target, otherSlot->getArt());
+							logAi->trace( "Comparing artifacts of %s: %s vs %s. Score: %d vs %d", target->getHeroTypeName(), artifact->getType()->getJsonKey(), otherSlot->getArt()->getType()->getJsonKey(), artifactScore, otherArtifactScore);
 
 							//if that artifact is better than what we have, pick it
 							//combined artifacts are not always allowed to move
 							if(artifactScore > otherArtifactScore && artifact->canBePutAt(target, slot, true))
 							{
+								std::pair swapPair = std::minmax<ArtifactInstanceID>({artifact->getId(), otherSlot->artifactID});
+								if (swappedSet.find(swapPair) != swappedSet.end())
+								{
+									logAi->warn(
+										"Artifacts % s < -> % s have already swapped before, ignored.",
+										artifact->getType()->getJsonKey(),
+										otherSlot->getArt()->getType()->getJsonKey());
+									continue;
+								}
 								logAi->trace(
 									"Exchange artifacts %s <-> %s",
 									artifact->getType()->getJsonKey(),
-									otherSlot->artifact->getType()->getJsonKey());
+									otherSlot->getArt()->getType()->getJsonKey());
 
-								if(!otherSlot->artifact->canBePutAt(artHolder, location.slot, true))
+								if(!otherSlot->getArt()->canBePutAt(artHolder, location.slot, true))
 								{
 									ArtifactLocation destLocation(target->id, slot);
 									ArtifactLocation backpack(artHolder->id, ArtifactPosition::BACKPACK_START);
@@ -1135,10 +1153,11 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 								}
 								else
 								{
-									cb->swapArtifacts(location, ArtifactLocation(target->id, target->getArtPos(otherSlot->artifact)));
+									cb->swapArtifacts(location, ArtifactLocation(target->id, target->getArtPos(otherSlot->getArt())));
 								}
 
 								changeMade = true;
+								swappedSet.insert(swapPair);
 								break;
 							}
 						}
@@ -1170,7 +1189,7 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re
 
 		if(!recruiter->getSlotFor(creID).validSlot())
 		{
-			for(auto stack : recruiter->Slots())
+			for(const auto & stack : recruiter->Slots())
 			{
 				if(!stack.second->getType())
 					continue;
@@ -1272,10 +1291,10 @@ void AIGateway::addVisitableObj(const CGObjectInstance * obj)
 
 bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
 {
-	if(h->inTownGarrison && h->visitedTown)
+	if(h->isGarrisoned() && h->getVisitedTown())
 	{
-		cb->swapGarrisonHero(h->visitedTown);
-		moveCreaturesToHero(h->visitedTown);
+		cb->swapGarrisonHero(h->getVisitedTown());
+		moveCreaturesToHero(h->getVisitedTown());
 	}
 
 	//TODO: consider if blockVisit objects change something in our checks: AIUtility::isBlockVisitObj()
@@ -1322,8 +1341,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
 		{
 			auto tile = cb->getTile(coord, false);
 			assert(tile);
-			return tile->topVisitableObj(ignoreHero);
-			//return cb->getTile(coord,false)->topVisitableObj(ignoreHero);
+			return cb->getObj(tile->topVisitableObj(ignoreHero), false);
 		};
 
 		auto isTeleportAction = [&](EPathNodeAction action) -> bool
@@ -1533,7 +1551,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
 
 				int toGive;
 				int toGet;
-				m->getOffer(res, g.resID, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
+				m->getOffer(res.getNum(), g.resID, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
 				toGive = static_cast<int>(toGive * (it->resVal / toGive)); //round down
 				//TODO trade only as much as needed
 				if (toGive) //don't try to sell 0 resources
@@ -1586,7 +1604,7 @@ void AIGateway::endTurn()
 
 void AIGateway::buildArmyIn(const CGTownInstance * t)
 {
-	makePossibleUpgrades(t->visitingHero);
+	makePossibleUpgrades(t->getVisitingHero());
 	makePossibleUpgrades(t);
 	recruitCreatures(t, t->getUpperArmy());
 	moveCreaturesToHero(t);
@@ -1608,13 +1626,13 @@ void AIGateway::executeActionAsync(const std::string & description, const std::f
 	if (!asyncTasks)
 		throw std::runtime_error("Attempt to execute task on shut down AI state!");
 
-	executeActionAsyncArena.enqueue(asyncTasks->defer([this, description, whatToDo]()
+	asyncTasks->run([this, description, whatToDo]()
 	{
 		ScopedThreadName guard("NKAI::" + description);
 		SET_GLOBAL_STATE(this);
 		std::shared_lock gsLock(CGameState::mutex);
 		whatToDo();
-	}));
+	});
 }
 
 void AIGateway::lostHero(HeroPtr h)
@@ -1667,7 +1685,8 @@ void AIGateway::validateObject(ObjectIdRef obj)
 	}
 }
 
-AIStatus::AIStatus()
+AIStatus::AIStatus(AIGateway * gateway)
+	: gateway(gateway)
 {
 	battle = NO_BATTLE;
 	havingTurn = false;
@@ -1749,7 +1768,10 @@ void AIStatus::waitTillFree()
 {
 	std::unique_lock<std::mutex> lock(mx);
 	while(battle != NO_BATTLE || !remainingQueries.empty() || !objectsBeingVisited.empty() || ongoingHeroMovement)
+	{
 		cv.wait_for(lock, std::chrono::milliseconds(10));
+		gateway->nullkiller->makingTurnInterrupption.interruptionPoint();
+	}
 }
 
 bool AIStatus::haveTurn()

+ 10 - 8
AI/Nullkiller/AIGateway.h

@@ -11,25 +11,26 @@
 
 #include "AIUtility.h"
 #include "Goals/AbstractGoal.h"
-#include "../../lib/AI_Base.h"
-#include "../../CCallback.h"
 #include "../../lib/CThreadHelper.h"
 #include "../../lib/GameConstants.h"
 #include "../../lib/GameLibrary.h"
 #include "../../lib/CCreatureHandler.h"
+#include "../../lib/callback/CAdventureAI.h"
 #include "../../lib/mapObjects/MiscObjects.h"
 #include "../../lib/spells/CSpellHandler.h"
 #include "Pathfinding/AIPathfinder.h"
 #include "Engine/Nullkiller.h"
 
-#include <tbb/task_group.h>
-#include <tbb/task_arena.h>
+VCMI_LIB_NAMESPACE_BEGIN
+class AsyncRunner;
+VCMI_LIB_NAMESPACE_END
 
 namespace NKAI
 {
 
 class AIStatus
 {
+	AIGateway * gateway;
 	std::mutex mx;
 	std::condition_variable cv;
 
@@ -43,7 +44,7 @@ class AIStatus
 	bool havingTurn;
 
 public:
-	AIStatus();
+	AIStatus(AIGateway * gateway);
 	~AIStatus();
 	void setBattle(BattleState BS);
 	void setMove(bool ongoing);
@@ -74,7 +75,7 @@ public:
 	AIStatus status;
 	std::string battlename;
 	std::shared_ptr<CCallback> myCb;
-	std::unique_ptr<tbb::task_group> asyncTasks;
+	std::unique_ptr<AsyncRunner> asyncTasks;
 
 public:
 	ObjectInstanceID selectedObject;
@@ -105,7 +106,7 @@ public:
 	void heroMoved(const TryMoveHero & details, bool verbose = true) override;
 	void heroInGarrisonChange(const CGTownInstance * town) override;
 	void centerView(int3 pos, int focusTime) override;
-	void tileHidden(const std::unordered_set<int3> & pos) override;
+	void tileHidden(const FowTilesType & pos) override;
 	void artifactMoved(const ArtifactLocation & src, const ArtifactLocation & dst) override;
 	void artifactAssembled(const ArtifactLocation & al) override;
 	void showTavernWindow(const CGObjectInstance * object, const CGHeroInstance * visitor, QueryID queryID) override;
@@ -120,8 +121,9 @@ public:
 	void heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start) override;
 	void availableArtifactsChanged(const CGBlackMarket * bm = nullptr) override;
 	void heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance * town) override;
-	void tileRevealed(const std::unordered_set<int3> & pos) override;
+	void tileRevealed(const FowTilesType & pos) override;
 	void heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query) override;
+	void heroExperienceChanged(const CGHeroInstance * hero, si64 val) override;
 	void heroPrimarySkillChanged(const CGHeroInstance * hero, PrimarySkill which, si64 val) override;
 	void showRecruitmentDialog(const CGDwelling * dwelling, const CArmedInstance * dst, int level, QueryID queryID) override;
 	void heroMovePointsChanged(const CGHeroInstance * hero) override;

+ 23 - 21
AI/Nullkiller/AIUtility.cpp

@@ -14,8 +14,10 @@
 
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/CConfigHandler.h"
+#include "../../lib/entities/artifact/CArtifact.h"
 #include "../../lib/mapObjects/MapObjects.h"
-#include "../../lib/mapping/CMapDefines.h"
+#include "../../lib/mapObjects/CQuest.h"
+#include "../../lib/mapping/TerrainTile.h"
 #include "../../lib/gameState/QuestInfo.h"
 #include "../../lib/IGameSettings.h"
 #include "../../lib/bonuses/Limiters.h"
@@ -201,7 +203,7 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
 	}
 	else if(!fromWater) // do not try to board when in water sector
 	{
-		if(t->visitableObjects.size() == 1 && t->topVisitableId() == Obj::BOAT)
+		if(t->visitableObjects.size() == 1 && cb->getObjInstance(t->topVisitableObj())->ID == Obj::BOAT)
 			return true;
 	}
 	return false;
@@ -269,7 +271,7 @@ bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2)
 
 double getArtifactBonusRelevance(const CGHeroInstance * hero, const std::shared_ptr<Bonus> & bonus)
 {
-	if (bonus->propagator && bonus->limiter && bonus->propagator->getPropagatorType() == CBonusSystemNode::BATTLE)
+	if (bonus->propagator && bonus->limiter && bonus->propagator->getPropagatorType() == BonusNodeType::BATTLE_WIDE)
 	{
 		// assume that this is battle wide / other side propagator+limiter
 		// consider it as fully relevant since we don't know about future combat when equipping artifacts
@@ -288,7 +290,7 @@ double getArtifactBonusRelevance(const CGHeroInstance * hero, const std::shared_
 
 		for (const auto & slot : hero->Slots())
 		{
-			const auto allBonuses = slot.second->getAllBonuses(Selector::all, Selector::all);
+			const auto allBonuses = slot.second->getAllBonuses(Selector::all);
 			BonusLimitationContext context = {*bonus, *slot.second, *allBonuses, stillUndecided};
 
 			uint64_t unitStrength = slot.second->getPower();
@@ -374,10 +376,10 @@ double getArtifactBonusRelevance(const CGHeroInstance * hero, const std::shared_
 	switch (bonus->type)
 	{
 		case BonusType::MOVEMENT:
-			if (hero->boat && bonus->subtype == BonusCustomSubtype::heroMovementSea)
+			if (hero->getBoat() && bonus->subtype == BonusCustomSubtype::heroMovementSea)
 				return veryRelevant;
 
-			if (!hero->boat && bonus->subtype == BonusCustomSubtype::heroMovementLand)
+			if (!hero->getBoat() && bonus->subtype == BonusCustomSubtype::heroMovementLand)
 				return relevant;
 			return notRelevant;
 		case BonusType::STACKS_SPEED:
@@ -394,9 +396,9 @@ double getArtifactBonusRelevance(const CGHeroInstance * hero, const std::shared_
 				return relevant; // spellpower / knowledge - always relevant
 		case BonusType::WATER_WALKING:
 		case BonusType::FLYING_MOVEMENT:
-			return hero->boat ? notRelevant : relevant; // boat can't fly
+			return hero->getBoat() ? notRelevant : relevant; // boat can't fly
 		case BonusType::WHIRLPOOL_PROTECTION:
-			return hero->boat ? relevant : notRelevant;
+			return hero->getBoat() ? relevant : notRelevant;
 		case BonusType::UNDEAD_RAISE_PERCENTAGE:
 			return hero->hasBonusOfType(BonusType::IMPROVED_NECROMANCY) ? veryRelevant : notRelevant;
 		case BonusType::SPELL_DAMAGE:
@@ -408,9 +410,9 @@ double getArtifactBonusRelevance(const CGHeroInstance * hero, const std::shared_
 			if (bonus->subtype == BonusCustomSubtype::damageTypeMelee)
 				return veryRelevant * (1 - getArmyPercentageWithBonus(BonusType::SHOOTER));
 			return 0;
-		case BonusType::FULL_MANA_REGENERATION:
+		case BonusType::MANA_PERCENTAGE_REGENERATION:
 		case BonusType::MANA_REGENERATION:
-			return hero->mana < hero->manaLimit() ? relevant : notRelevant;
+			return hero->hasSpellbook() ? relevant : notRelevant;
 		case BonusType::LEARN_BATTLE_SPELL_CHANCE:
 			return hero->hasBonusOfType(BonusType::LEARN_BATTLE_SPELL_LEVEL_LIMIT) ? relevant : notRelevant;
 		case BonusType::NO_DISTANCE_PENALTY:
@@ -481,8 +483,8 @@ int32_t getArtifactBonusScoreImpl(const std::shared_ptr<Bonus> & bonus)
 			return 0;
 		case BonusType::CREATURE_GROWTH:
 			return (1+bonus->subtype.getNum()) * bonus->val * 400;
-		case BonusType::FULL_MANA_REGENERATION:
-			return 15000;
+		case BonusType::MANA_PERCENTAGE_REGENERATION:
+			return bonus->val * 150;
 		case BonusType::MANA_REGENERATION:
 			return bonus->val * 500;
 		case BonusType::SPELLS_OF_SCHOOL:
@@ -524,7 +526,7 @@ int32_t getArtifactBonusScoreImpl(const std::shared_ptr<Bonus> & bonus)
 
 int32_t getArtifactBonusScore(const std::shared_ptr<Bonus> & bonus)
 {
-	if (bonus->propagator && bonus->propagator->getPropagatorType() == CBonusSystemNode::BATTLE)
+	if (bonus->propagator && bonus->propagator->getPropagatorType() == BonusNodeType::BATTLE_WIDE)
 	{
 		if (bonus->limiter)
 		{
@@ -632,7 +634,7 @@ int getDuplicatingSlots(const CArmedInstance * army)
 {
 	int duplicatingSlots = 0;
 
-	for(auto stack : army->Slots())
+	for(const auto & stack : army->Slots())
 	{
 		if(stack.second->getCreature() && army->getSlotFor(stack.second->getCreature()) != stack.first)
 			duplicatingSlots++;
@@ -656,7 +658,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
 	{
 		for(auto q : ai->cb->getMyQuests())
 		{
-			if(q.obj == obj)
+			if(q.obj == obj->id)
 			{
 				return false; // do not visit guards or gates when wandering
 			}
@@ -669,9 +671,9 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
 	{
 		for(auto q : ai->cb->getMyQuests())
 		{
-			if(q.obj == obj)
+			if(q.obj == obj->id)
 			{
-				if(q.quest->checkQuest(h))
+				if(q.getQuest(ai->cb.get())->checkQuest(h))
 					return true; //we completed the quest
 				else
 					return false; //we can't complete this quest
@@ -707,7 +709,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
 	}
 	case Obj::HILL_FORT:
 	{
-		for(auto slot : h->Slots())
+		for(const auto & slot : h->Slots())
 		{
 			if(slot.second->getType()->hasUpgrades())
 				return true; //TODO: check price?
@@ -767,7 +769,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
 bool townHasFreeTavern(const CGTownInstance * town)
 {
 	if(!town->hasBuilt(BuildingID::TAVERN)) return false;
-	if(!town->visitingHero) return true;
+	if(!town->getVisitingHero()) return true;
 
 	bool canMoveVisitingHeroToGarrison = !town->getUpperArmy()->stacksCount();
 
@@ -778,9 +780,9 @@ uint64_t getHeroArmyStrengthWithCommander(const CGHeroInstance * hero, const CCr
 {
 	auto armyStrength = heroArmy->getArmyStrength(fortLevel);
 
-	if(hero && hero->commander && hero->commander->alive)
+	if(hero && hero->getCommander() && hero->getCommander()->alive)
 	{
-		armyStrength += 100 * hero->commander->level;
+		armyStrength += 100 * hero->getCommander()->level;
 	}
 
 	return armyStrength;

+ 1 - 1
AI/Nullkiller/AIUtility.h

@@ -44,7 +44,7 @@
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/CStopWatch.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
-#include "../../CCallback.h"
+#include "../../lib/callback/CCallback.h"
 
 #include <chrono>
 

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

@@ -11,9 +11,8 @@
 #include "StdInc.h"
 #include "ArmyManager.h"
 #include "../Engine/Nullkiller.h"
-#include "../../../CCallback.h"
 #include "../../../lib/mapObjects/MapObjects.h"
-#include "../../../lib/mapping/CMapDefines.h"
+#include "../../../lib/mapping/TerrainTile.h"
 #include "../../../lib/IGameSettings.h"
 #include "../../../lib/GameConstants.h"
 #include "../../../lib/TerrainHandler.h"
@@ -98,7 +97,7 @@ std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, c
 
 			slotInfp.creature = cre;
 			slotInfp.power += i.second->getPower();
-			slotInfp.count += i.second->count;
+			slotInfp.count += i.second->getCount();
 		}
 	}
 
@@ -173,7 +172,7 @@ class TemporaryArmy : public CArmedInstance
 public:
 	void armyChanged() override {}
 	TemporaryArmy()
-		:CArmedInstance(nullptr, true)
+		:CArmedInstance(nullptr, BonusNodeType::UNKNOWN, true)
 	{
 	}
 };
@@ -232,18 +231,19 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
 			auto morale = slot.second->moraleVal();
 			auto multiplier = 1.0f;
 
-			const auto & badMoraleDice = cb->getSettings().getVector(EGameSettings::COMBAT_BAD_MORALE_DICE);
-			const auto & highMoraleDice = cb->getSettings().getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE);
+			const auto & badMoraleChance = cb->getSettings().getVector(EGameSettings::COMBAT_BAD_MORALE_CHANCE);
+			const auto & highMoraleChance = cb->getSettings().getVector(EGameSettings::COMBAT_GOOD_MORALE_CHANCE);
+			int moraleDiceSize = cb->getSettings().getInteger(EGameSettings::COMBAT_MORALE_DICE_SIZE);
 
-			if(morale < 0 && !badMoraleDice.empty())
+			if(morale < 0 && !badMoraleChance.empty())
 			{
-				size_t diceIndex = std::min<size_t>(badMoraleDice.size(), -morale) - 1;
-				multiplier -= 1.0 / badMoraleDice.at(diceIndex);
+				size_t chanceIndex = std::min<size_t>(badMoraleChance.size(), -morale) - 1;
+				multiplier -= 1.0 / moraleDiceSize * badMoraleChance.at(chanceIndex);
 			}
-			else if(morale > 0 && !highMoraleDice.empty())
+			else if(morale > 0 && !highMoraleChance.empty())
 			{
-				size_t diceIndex = std::min<size_t>(highMoraleDice.size(), morale) - 1;
-				multiplier += 1.0 / highMoraleDice.at(diceIndex);
+				size_t chanceIndex = std::min<size_t>(highMoraleChance.size(), morale) - 1;
+				multiplier += 1.0 / moraleDiceSize * highMoraleChance.at(chanceIndex);
 			}
 
 			newValue += multiplier * slot.second->getPower();
@@ -488,9 +488,9 @@ void ArmyManager::update()
 
 	for(auto army : total)
 	{
-		for(auto slot : army->Slots())
+		for(const auto & slot : army->Slots())
 		{
-			totalArmy[slot.second->getCreatureID()].count += slot.second->count;
+			totalArmy[slot.second->getCreatureID()].count += slot.second->getCount();
 		}
 	}
 
@@ -505,12 +505,12 @@ std::vector<SlotInfo> ArmyManager::convertToSlots(const CCreatureSet * army) con
 {
 	std::vector<SlotInfo> result;
 
-	for(auto slot : army->Slots())
+	for(const auto & slot : army->Slots())
 	{
 		SlotInfo slotInfo;
 
 		slotInfo.creature = slot.second->getCreatureID().toCreature();
-		slotInfo.count = slot.second->count;
+		slotInfo.count = slot.second->getCount();
 		slotInfo.power = evaluateStackPower(slotInfo.creature, slotInfo.count);
 
 		result.push_back(slotInfo);
@@ -523,7 +523,7 @@ std::vector<StackUpgradeInfo> ArmyManager::getHillFortUpgrades(const CCreatureSe
 {
 	std::vector<StackUpgradeInfo> upgrades;
 
-	for(auto creature : army->Slots())
+	for(const auto & creature : army->Slots())
 	{
 		CreatureID initial = creature.second->getCreatureID();
 		auto possibleUpgrades = initial.toCreature()->upgrades;
@@ -536,7 +536,7 @@ std::vector<StackUpgradeInfo> ArmyManager::getHillFortUpgrades(const CCreatureSe
 			return cre.toCreature()->getAIValue();
 		});
 
-		StackUpgradeInfo upgrade = StackUpgradeInfo(initial, strongestUpgrade, creature.second->count);
+		StackUpgradeInfo upgrade = StackUpgradeInfo(initial, strongestUpgrade, creature.second->getCount());
 
 		if(initial.toCreature()->getLevel() == 1)
 			upgrade.cost = TResources();
@@ -551,7 +551,7 @@ std::vector<StackUpgradeInfo> ArmyManager::getDwellingUpgrades(const CCreatureSe
 {
 	std::vector<StackUpgradeInfo> upgrades;
 
-	for(auto creature : army->Slots())
+	for(const auto & creature : army->Slots())
 	{
 		CreatureID initial = creature.second->getCreatureID();
 		auto possibleUpgrades = initial.toCreature()->upgrades;
@@ -575,7 +575,7 @@ std::vector<StackUpgradeInfo> ArmyManager::getDwellingUpgrades(const CCreatureSe
 			return cre.toCreature()->getAIValue();
 		});
 
-		StackUpgradeInfo upgrade = StackUpgradeInfo(initial, strongestUpgrade, creature.second->count);
+		StackUpgradeInfo upgrade = StackUpgradeInfo(initial, strongestUpgrade, creature.second->getCount());
 
 		upgrades.push_back(upgrade);
 	}

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

@@ -351,7 +351,7 @@ void BuildAnalyzer::updateDailyIncome()
 	}
 }
 
-bool BuildAnalyzer::hasAnyBuilding(int32_t alignment, BuildingID bid) const
+bool BuildAnalyzer::hasAnyBuilding(FactionID alignment, BuildingID bid) const
 {
 	for(auto tdi : developmentInfos)
 	{

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

@@ -97,7 +97,7 @@ public:
 	TResources getDailyIncome() const { return dailyIncome; }
 	float getGoldPressure() const { return goldPressure; }
 	bool isGoldPressureHigh() const;
-	bool hasAnyBuilding(int32_t alignment, BuildingID bid) const;
+	bool hasAnyBuilding(FactionID alignment, BuildingID bid) const;
 
 private:
 	BuildingInfo getBuildingOrPrerequisite(

+ 7 - 6
AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp

@@ -12,7 +12,7 @@
 
 #include "../Engine/Nullkiller.h"
 #include "../pforeach.h"
-#include "../../../lib/CRandomGenerator.h"
+#include "../../../lib/callback/GameRandomizer.h"
 #include "../../../lib/logging/VisualLogger.h"
 
 namespace NKAI
@@ -93,8 +93,8 @@ void DangerHitMapAnalyzer::updateHitMap()
 		{
 			auto town = dynamic_cast<const CGTownInstance *>(obj);
 
-			if(town->garrisonHero)
-				heroes[town->garrisonHero->tempOwner][town->garrisonHero] = HeroRole::MAIN;
+			if(town->getGarrisonHero())
+				heroes[town->getGarrisonHero()->tempOwner][town->getGarrisonHero()] = HeroRole::MAIN;
 		}
 	}
 
@@ -211,13 +211,14 @@ void DangerHitMapAnalyzer::calculateTileOwners()
 	auto addTownHero = [&](const CGTownInstance * town)
 	{
 			auto townHero = temporaryHeroes.emplace_back(std::make_unique<CGHeroInstance>(town->cb)).get();
-			CRandomGenerator rng;
+			GameRandomizer randomizer(*town->cb);
 			auto visitablePos = town->visitablePos();
 			
+			townHero->id = town->id;
 			townHero->setOwner(ai->playerID); // lets avoid having multiple colors
-			townHero->initHero(rng, static_cast<HeroTypeID>(0));
+			townHero->initHero(randomizer, static_cast<HeroTypeID>(0));
 			townHero->pos = townHero->convertFromVisitablePos(visitablePos);
-			townHero->initObj(rng);
+			townHero->initObj(randomizer);
 			
 			heroTownMap[townHero] = town;
 			townHeroes[townHero] = HeroRole::MAIN;

+ 15 - 10
AI/Nullkiller/Analyzers/HeroManager.cpp

@@ -12,6 +12,8 @@
 #include "../Engine/Nullkiller.h"
 #include "../../../lib/mapObjects/MapObjects.h"
 #include "../../../lib/IGameSettings.h"
+#include "../../../lib/spells/ISpellMechanics.h"
+#include "../../../lib/spells/adventure/TownPortalEffect.h"
 
 namespace NKAI
 {
@@ -210,32 +212,35 @@ float HeroManager::getFightingStrengthCached(const CGHeroInstance * hero) const
 
 float HeroManager::getMagicStrength(const CGHeroInstance * hero) const
 {
-	auto hasFly = hero->spellbookContainsSpell(SpellID::FLY);
-	auto hasTownPortal = hero->spellbookContainsSpell(SpellID::TOWN_PORTAL);
 	auto manaLimit = hero->manaLimit();
 	auto spellPower = hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
-	auto hasEarth = hero->getSpellSchoolLevel(SpellID(SpellID::TOWN_PORTAL).toSpell()) > 0;
 
 	auto score = 0.0f;
 
+	// FIXME: this will not cover spells give by scrolls / tomes. Intended?
 	for(auto spellId : hero->getSpellsInSpellbook())
 	{
 		auto spell = spellId.toSpell();
+
+		if (!spell->isAdventure())
+			continue;
+
 		auto schoolLevel = hero->getSpellSchoolLevel(spell);
+		auto townPortalEffect = spell->getAdventureMechanics().getEffectAs<TownPortalEffect>(hero);
 
 		score += (spell->getLevel() + 1) * (schoolLevel + 1) * 0.05f;
+
+		if (spell->getAdventureMechanics().givesBonus(hero, BonusType::FLYING_MOVEMENT))
+			score += 0.3;
+
+		if(townPortalEffect != nullptr && schoolLevel != 0)
+			score += 0.6f;
 	}
 
 	vstd::amin(score, 1);
 
 	score *= std::min(1.0f, spellPower / 10.0f);
 
-	if(hasFly)
-		score += 0.3f;
-
-	if(hasTownPortal && hasEarth)
-		score += 0.6f;
-
 	vstd::amin(score, 1);
 
 	score *= std::min(1.0f, manaLimit / 100.0f);
@@ -293,7 +298,7 @@ const CGHeroInstance * HeroManager::findWeakHeroToDismiss(uint64_t armyLimit, co
 			|| existingHero->getArmyStrength() >armyLimit
 			|| getHeroRole(existingHero) == HeroRole::MAIN
 			|| existingHero->movementPointsRemaining()
-			|| (townToSpare != nullptr && existingHero->visitedTown == townToSpare)
+			|| (townToSpare != nullptr && existingHero->getVisitedTown() == townToSpare)
 			|| existingHero->artifactsWorn.size() > (existingHero->hasSpellbook() ? 2 : 1))
 		{
 			continue;

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

@@ -109,30 +109,30 @@ void handleCounterAttack(
 
 bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoalVec & tasks, const Nullkiller * ai)
 {
-	if(ai->isHeroLocked(town->garrisonHero.get()))
+	if(ai->isHeroLocked(town->getGarrisonHero()))
 	{
 		logAi->trace(
 			"Hero %s in garrison of town %s is supposed to defend the town",
-			town->garrisonHero->getNameTranslated(),
+			town->getGarrisonHero()->getNameTranslated(),
 			town->getNameTranslated());
 
 		return true;
 	}
 
-	if(!town->visitingHero)
+	if(!town->getVisitingHero())
 	{
 		if(ai->cb->getHeroCount(ai->playerID, false) < GameConstants::MAX_HEROES_PER_PLAYER)
 		{
 			logAi->trace(
 				"Extracting hero %s from garrison of town %s",
-				town->garrisonHero->getNameTranslated(),
+				town->getGarrisonHero()->getNameTranslated(),
 				town->getNameTranslated());
 
 			tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, nullptr).setpriority(5)));
 
 			return false;
 		}
-		else if(ai->heroManager->getHeroRole(town->garrisonHero.get()) == HeroRole::MAIN)
+		else if(ai->heroManager->getHeroRole(town->getGarrisonHero()) == HeroRole::MAIN)
 		{
 			auto armyDismissLimit = 1000;
 			auto heroToDismiss = ai->heroManager->findWeakHeroToDismiss(armyDismissLimit);
@@ -160,7 +160,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 	
 	threats.push_back(threatNode.fastestDanger); // no guarantee that fastest danger will be there
 
-	if (town->garrisonHero && handleGarrisonHeroFromPreviousTurn(town, tasks, ai))
+	if (town->getGarrisonHero() && handleGarrisonHeroFromPreviousTurn(town, tasks, ai))
 	{
 		return;
 	}
@@ -233,16 +233,16 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 				path.toString());
 #endif
 
-			auto townDefenseStrength = town->garrisonHero
-				? town->garrisonHero->getTotalStrength()
-				: (town->visitingHero ? town->visitingHero->getTotalStrength() : town->getUpperArmy()->getArmyStrength());
+			auto townDefenseStrength = town->getGarrisonHero()
+				? town->getGarrisonHero()->getTotalStrength()
+				: (town->getVisitingHero() ? town->getVisitingHero()->getTotalStrength() : town->getUpperArmy()->getArmyStrength());
 
-			if(town->visitingHero && path.targetHero == town->visitingHero.get())
+			if(town->getVisitingHero() && path.targetHero == town->getVisitingHero())
 			{
 				if(path.getHeroStrength() < townDefenseStrength)
 					continue;
 			}
-			else if(town->garrisonHero && path.targetHero == town->garrisonHero.get())
+			else if(town->getGarrisonHero() && path.targetHero == town->getGarrisonHero())
 			{
 				if(path.getHeroStrength() < townDefenseStrength)
 					continue;
@@ -271,7 +271,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 				continue;
 			}
 
-			if(path.targetHero == town->visitingHero.get() && path.exchangeCount == 1)
+			if(path.targetHero == town->getVisitingHero() && path.exchangeCount == 1)
 			{
 #if NKAI_TRACE_LEVEL >= 1
 				logAi->trace("Put %s to garrison of town %s",
@@ -280,7 +280,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 #endif
 
 				// dismiss creatures we are not able to pick to be able to hide in garrison
-				if(town->garrisonHero
+				if(town->getGarrisonHero()
 					|| town->getUpperArmy()->stacksCount() == 0
 					|| path.targetHero->canBeMergedWith(*town)
 					|| (town->getUpperArmy()->getArmyStrength() < 500 && town->fortLevel() >= CGTownInstance::CITADEL))
@@ -288,25 +288,25 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 					tasks.push_back(
 						Goals::sptr(Composition()
 							.addNext(DefendTown(town, threat, path.targetHero))
-							.addNext(ExchangeSwapTownHeroes(town, town->visitingHero.get(), HeroLockedReason::DEFENCE))));
+							.addNext(ExchangeSwapTownHeroes(town, town->getVisitingHero(), HeroLockedReason::DEFENCE))));
 				}
 
 				continue;
 			}
 
 			// main without army and visiting scout with army, very specific case
-			if(town->visitingHero && town->getUpperArmy()->stacksCount() == 0
-				&& path.targetHero != town->visitingHero.get() && path.exchangeCount == 1 && path.turn() == 0
-				&& ai->heroManager->evaluateHero(path.targetHero) > ai->heroManager->evaluateHero(town->visitingHero.get())
-				&& 10 * path.targetHero->getTotalStrength() < town->visitingHero->getTotalStrength())
+			if(town->getVisitingHero() && town->getUpperArmy()->stacksCount() == 0
+				&& path.targetHero != town->getVisitingHero() && path.exchangeCount == 1 && path.turn() == 0
+				&& ai->heroManager->evaluateHero(path.targetHero) > ai->heroManager->evaluateHero(town->getVisitingHero())
+				&& 10 * path.targetHero->getTotalStrength() < town->getVisitingHero()->getTotalStrength())
 			{
-				path.heroArmy = town->visitingHero.get();
+				path.heroArmy = town->getVisitingHero();
 
 				tasks.push_back(
 					Goals::sptr(Composition()
 						.addNext(DefendTown(town, threat, path))
 						.addNextSequence({
-								sptr(ExchangeSwapTownHeroes(town, town->visitingHero.get())),
+								sptr(ExchangeSwapTownHeroes(town, town->getVisitingHero())),
 								sptr(ExecuteHeroChain(path, town)),
 								sptr(ExchangeSwapTownHeroes(town, path.targetHero, HeroLockedReason::DEFENCE))
 							})));
@@ -350,22 +350,22 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 			composition.addNext(DefendTown(town, threat, path));
 			TGoalVec sequence;
 
-			if(town->garrisonHero && path.targetHero == town->garrisonHero.get() && path.exchangeCount == 1)
+			if(town->getGarrisonHero() && path.targetHero == town->getGarrisonHero() && path.exchangeCount == 1)
 			{
-				composition.addNext(ExchangeSwapTownHeroes(town, town->garrisonHero.get(), HeroLockedReason::DEFENCE));
+				composition.addNext(ExchangeSwapTownHeroes(town, town->getGarrisonHero(), HeroLockedReason::DEFENCE));
 				tasks.push_back(Goals::sptr(composition));
 
 #if NKAI_TRACE_LEVEL >= 1
 				logAi->trace("Locking hero %s in garrison of %s",
-					town->garrisonHero.get()->getObjectName(),
+					town->getGarrisonHero()->getObjectName(),
 					town->getObjectName());
 #endif
 
 				continue;
 			}
-			else if(town->visitingHero && path.targetHero != town->visitingHero && !path.containsHero(town->visitingHero))
+			else if(town->getVisitingHero() && path.targetHero != town->getVisitingHero() && !path.containsHero(town->getVisitingHero()))
 			{
-				if(town->garrisonHero && town->garrisonHero != path.targetHero)
+				if(town->getGarrisonHero() && town->getGarrisonHero() != path.targetHero)
 				{
 #if NKAI_TRACE_LEVEL >= 1
 					logAi->trace("Cancel moving %s to defend town %s as the town has garrison hero",
@@ -376,7 +376,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 				}
 				else if(path.turn() == 0)
 				{
-					sequence.push_back(sptr(ExchangeSwapTownHeroes(town, town->visitingHero.get())));
+					sequence.push_back(sptr(ExchangeSwapTownHeroes(town, town->getVisitingHero())));
 				}
 			}
 
@@ -425,7 +425,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 
 void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitMapInfo & threat, const CGTownInstance * town, const Nullkiller * ai) const
 {
-	if (threat.turn > 0 || town->garrisonHero || town->visitingHero)
+	if (threat.turn > 0 || town->getGarrisonHero() || town->getVisitingHero())
 		return;
 	
 	if(town->hasBuilt(BuildingID::TAVERN)
@@ -461,25 +461,25 @@ void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitM
 			bool needSwap = false;
 			const CGHeroInstance * heroToDismiss = nullptr;
 
-			if(town->visitingHero)
+			if(town->getVisitingHero())
 			{
-				if(!town->garrisonHero)
+				if(!town->getGarrisonHero())
 					needSwap = true;
 				else
 				{
-					if(town->visitingHero->getArmyStrength() < town->garrisonHero->getArmyStrength())
+					if(town->getVisitingHero()->getArmyStrength() < town->getGarrisonHero()->getArmyStrength())
 					{
-						if(town->visitingHero->getArmyStrength() >= hero->getArmyStrength())
+						if(town->getVisitingHero()->getArmyStrength() >= hero->getArmyStrength())
 							continue;
 
-						heroToDismiss = town->visitingHero.get();
+						heroToDismiss = town->getVisitingHero();
 					}
-					else if(town->garrisonHero->getArmyStrength() >= hero->getArmyStrength())
+					else if(town->getGarrisonHero()->getArmyStrength() >= hero->getArmyStrength())
 						continue;
 					else
 					{
 						needSwap = true;
-						heroToDismiss = town->garrisonHero.get();
+						heroToDismiss = town->getGarrisonHero();
 					}
 				}
 
@@ -499,7 +499,7 @@ void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitM
 			Goals::Composition recruitHeroComposition;
 
 			if(needSwap)
-				sequence.push_back(sptr(ExchangeSwapTownHeroes(town, town->visitingHero.get())));
+				sequence.push_back(sptr(ExchangeSwapTownHeroes(town, town->getVisitingHero())));
 
 			if(heroToDismiss)
 				sequence.push_back(sptr(DismissHero(heroToDismiss)));

+ 7 - 7
AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp

@@ -167,21 +167,21 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const Nullkiller * ai, con
 
 			composition.addNext(heroExchange);
 
-			if(hero->inTownGarrison && path.turn() == 0)
+			if(hero->isGarrisoned() && path.turn() == 0)
 			{
 				auto lockReason = ai->getHeroLockedReason(hero);
 
-				if(path.targetHero->visitedTown == hero->visitedTown)
+				if(path.targetHero->getVisitedTown() == hero->getVisitedTown())
 				{
 					composition.addNextSequence({
-						sptr(ExchangeSwapTownHeroes(hero->visitedTown, hero, lockReason))});
+						sptr(ExchangeSwapTownHeroes(hero->getVisitedTown(), hero, lockReason))});
 				}
 				else
 				{
 					composition.addNextSequence({
-						sptr(ExchangeSwapTownHeroes(hero->visitedTown)),
+						sptr(ExchangeSwapTownHeroes(hero->getVisitedTown())),
 						sptr(exchangePath),
-						sptr(ExchangeSwapTownHeroes(hero->visitedTown, hero, lockReason))});
+						sptr(ExchangeSwapTownHeroes(hero->getVisitedTown(), hero, lockReason))});
 				}
 			}
 			else
@@ -262,7 +262,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
 			continue;
 		}
 
-		if(upgrader->visitingHero && (upgrader->visitingHero.get() != path.targetHero || path.exchangeCount == 1))
+		if(upgrader->getVisitingHero() && (upgrader->getVisitingHero() != path.targetHero || path.exchangeCount == 1))
 		{
 #if NKAI_TRACE_LEVEL >= 2
 			logAi->trace("Ignore path. Town has visiting hero.");
@@ -289,7 +289,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
 
 		auto upgrade = ai->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
 
-		if(!upgrader->garrisonHero
+		if(!upgrader->getGarrisonHero()
 			&& (
 				hasMainAround
 				|| ai->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN))

+ 2 - 2
AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp

@@ -68,7 +68,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const
 			closestThreat = std::min(closestThreat, threat.turn);
 		}
 		//Don't hire a hero where there already is one present
-		if (town->visitingHero && town->garrisonHero)
+		if (town->getVisitingHero() && town->getGarrisonHero())
 			continue;
 		float visitability = 0;
 		for (auto checkHero : ourHeroes)
@@ -98,7 +98,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const
 
 			for(auto hero : availableHeroes)
 			{
-				if ((town->visitingHero || town->garrisonHero) 
+				if ((town->getVisitingHero() || town->getGarrisonHero()) 
 					&& closestThreat < 1
 					&& hero->getArmyCost() < GameConstants::HERO_GOLD_COST / 3.0)
 					continue;

+ 17 - 17
AI/Nullkiller/Behaviors/StartupBehavior.cpp

@@ -32,7 +32,7 @@ const AIPath getShortestPath(const CGTownInstance * town, const std::vector<AIPa
 {
 	auto shortestPath = *vstd::minElementByFun(paths, [town](const AIPath & path) -> float
 	{
-		if(town->garrisonHero && path.targetHero == town->garrisonHero.get())
+		if(town->getGarrisonHero() && path.targetHero == town->getGarrisonHero())
 			return 1;
 
 		return path.movementCost();
@@ -53,7 +53,7 @@ const CGHeroInstance * getNearestHero(const Nullkiller * ai, const CGTownInstanc
 	if(shortestPath.nodes.size() > 1
 		|| shortestPath.turn() != 0
 		|| shortestPath.targetHero->visitablePos().dist2dSQ(town->visitablePos()) > 4
-		|| (town->garrisonHero && shortestPath.targetHero == town->garrisonHero.get()))
+		|| (town->getGarrisonHero() && shortestPath.targetHero == town->getGarrisonHero()))
 		return nullptr;
 
 	return shortestPath.targetHero;
@@ -64,7 +64,7 @@ bool needToRecruitHero(const Nullkiller * ai, const CGTownInstance * startupTown
 	if(!ai->heroManager->canRecruitHero(startupTown))
 		return false;
 
-	if(!startupTown->garrisonHero && !startupTown->visitingHero)
+	if(!startupTown->getGarrisonHero() && !startupTown->getVisitingHero())
 		return true;
 
 	int treasureSourcesCount = 0;
@@ -122,8 +122,8 @@ Goals::TGoalVec StartupBehavior::decompose(const Nullkiller * ai) const
 	{
 		startupTown = *vstd::maxElementByFun(towns, [ai](const CGTownInstance * town) -> float
 		{
-			if(town->garrisonHero)
-				return ai->heroManager->evaluateHero(town->garrisonHero.get());
+			if(town->getGarrisonHero())
+				return ai->heroManager->evaluateHero(town->getGarrisonHero());
 
 			auto closestHero = getNearestHero(ai, town);
 
@@ -147,7 +147,7 @@ Goals::TGoalVec StartupBehavior::decompose(const Nullkiller * ai) const
 
 	if(closestHero)
 	{
-		if(!startupTown->visitingHero)
+		if(!startupTown->getVisitingHero())
 		{
 			if(ai->armyManager->howManyReinforcementsCanGet(startupTown->getUpperArmy(), startupTown->getUpperArmy(), closestHero, TerrainId::NONE) > 200)
 			{
@@ -163,12 +163,12 @@ Goals::TGoalVec StartupBehavior::decompose(const Nullkiller * ai) const
 		}
 		else
 		{
-			auto visitingHero = startupTown->visitingHero.get();
+			auto visitingHero = startupTown->getVisitingHero();
 			auto visitingHeroScore = ai->heroManager->evaluateHero(visitingHero);
 				
-			if(startupTown->garrisonHero)
+			if(startupTown->getGarrisonHero())
 			{
-				auto garrisonHero = startupTown->garrisonHero.get();
+				auto garrisonHero = startupTown->getGarrisonHero();
 				auto garrisonHeroScore = ai->heroManager->evaluateHero(garrisonHero);
 
 				if(visitingHeroScore > garrisonHeroScore
@@ -187,7 +187,7 @@ Goals::TGoalVec StartupBehavior::decompose(const Nullkiller * ai) const
 			else if(canRecruitHero)
 			{
 				auto canPickTownArmy = startupTown->stacksCount() == 0
-					|| ai->armyManager->howManyReinforcementsCanGet(startupTown->visitingHero, startupTown) > 0;
+					|| ai->armyManager->howManyReinforcementsCanGet(startupTown->getVisitingHero(), startupTown) > 0;
 
 				if(canPickTownArmy)
 				{
@@ -197,16 +197,16 @@ Goals::TGoalVec StartupBehavior::decompose(const Nullkiller * ai) const
 		}
 	}
 
-	if(tasks.empty() && canRecruitHero && !startupTown->visitingHero)
+	if(tasks.empty() && canRecruitHero && !startupTown->getVisitingHero())
 	{
 		tasks.push_back(Goals::sptr(Goals::RecruitHero(startupTown)));
 	}
 
-	if(tasks.empty() && !startupTown->visitingHero)
+	if(tasks.empty() && !startupTown->getVisitingHero())
 	{
 		for(auto town : towns)
 		{
-			if(!town->visitingHero && needToRecruitHero(ai, town))
+			if(!town->getVisitingHero() && needToRecruitHero(ai, town))
 			{
 				tasks.push_back(Goals::sptr(Goals::RecruitHero(town)));
 
@@ -219,10 +219,10 @@ Goals::TGoalVec StartupBehavior::decompose(const Nullkiller * ai) const
 	{
 		for(const CGTownInstance * town : towns)
 		{
-			if(town->garrisonHero
-				&& town->garrisonHero->movementPointsRemaining()
-				&& !town->visitingHero
-				&& ai->getHeroLockedReason(town->garrisonHero) != HeroLockedReason::DEFENCE)
+			if(town->getGarrisonHero()
+				&& town->getGarrisonHero()->movementPointsRemaining()
+				&& !town->getVisitingHero()
+				&& ai->getHeroLockedReason(town->getGarrisonHero()) != HeroLockedReason::DEFENCE)
 			{
 				tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(town, nullptr).setpriority(MIN_PRIORITY)));
 			}

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

@@ -43,7 +43,7 @@ Goals::TGoalVec StayAtTownBehavior::decompose(const Nullkiller * ai) const
 
 		for(auto & path : paths)
 		{
-			if(town->visitingHero && town->visitingHero.get() != path.targetHero)
+			if(town->getVisitingHero() && town->getVisitingHero() != path.targetHero)
 				continue;
 
 			if(!path.getFirstBlockedAction() && path.exchangeCount <= 1)

+ 3 - 4
AI/Nullkiller/Engine/AIMemory.cpp

@@ -9,7 +9,6 @@
 */
 #include "../StdInc.h"
 #include "AIMemory.h"
-#include "../../../CCallback.h"
 
 namespace NKAI
 {
@@ -22,10 +21,10 @@ void AIMemory::removeFromMemory(const CGObjectInstance * obj)
 	//TODO: Find better way to handle hero boat removal
 	if(auto hero = dynamic_cast<const CGHeroInstance *>(obj))
 	{
-		if(hero->boat)
+		if(hero->inBoat())
 		{
-			vstd::erase_if_present(visitableObjs, hero->boat);
-			vstd::erase_if_present(alreadyVisited, hero->boat);
+			vstd::erase_if_present(visitableObjs, hero->getBoat());
+			vstd::erase_if_present(alreadyVisited, hero->getBoat());
 		}
 	}
 }

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

@@ -61,7 +61,7 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
 	static const CSelector selectorSTACKS_SPEED = Selector::type()(BonusType::STACKS_SPEED);
 	static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)BonusType::STACKS_SPEED);
 
-	for(auto s : army->Slots())
+	for(const auto & s : army->Slots())
 	{
 		bool walker = true;
 		auto bearer = s.second->getType()->getBonusBearer();

+ 4 - 5
AI/Nullkiller/Engine/FuzzyHelper.cpp

@@ -15,7 +15,6 @@
 
 #include "../../../lib/mapObjectConstructors/AObjectTypeHandler.h"
 #include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h"
-#include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
 
 namespace NKAI
 {
@@ -48,16 +47,16 @@ ui64 FuzzyHelper::evaluateDanger(const int3 & tile, const CGHeroInstance * visit
 		{
 			auto hero = dynamic_cast<const CGHeroInstance *>(dangerousObject);
 
-			if(hero->visitedTown && !hero->visitedTown->garrisonHero)
+			if(hero->getVisitedTown() && !hero->getVisitedTown()->getGarrisonHero())
 			{
-				objectDanger += evaluateDanger(hero->visitedTown.get());
+				objectDanger += evaluateDanger(hero->getVisitedTown());
 			}
 			objectDanger *= ai->heroManager->getFightingStrengthCached(hero);
 		}
 		if (objWithID<Obj::TOWN>(dangerousObject))
 		{
 			auto town = dynamic_cast<const CGTownInstance*>(dangerousObject);
-			auto hero = town->garrisonHero;
+			auto hero = town->getGarrisonHero();
 
 			if (hero)
 				objectDanger *= ai->heroManager->getFightingStrengthCached(hero);
@@ -122,7 +121,7 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
 		const CGTownInstance * town = dynamic_cast<const CGTownInstance *>(obj);
 		auto danger = town->getUpperArmy()->getArmyStrength();
 
-		if(danger || town->visitingHero)
+		if(danger || town->getVisitingHero())
 		{
 			auto fortLevel = town->fortLevel();
 

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

@@ -77,7 +77,7 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, AIGateway * gateway)
 
 	settings = std::make_unique<Settings>(cb->getStartInfo()->difficulty);
 
-	PathfinderOptions pathfinderOptions(cb.get());
+	PathfinderOptions pathfinderOptions(*cb);
 
 	pathfinderOptions.useTeleportTwoWay = true;
 	pathfinderOptions.useTeleportOneWay = settings->isOneWayMonolithUsageAllowed();
@@ -642,7 +642,7 @@ bool Nullkiller::handleTrading()
 {
 	bool haveTraded = false;
 	bool shouldTryToTrade = true;
-	int marketId = -1;
+	ObjectInstanceID marketId;
 	for (auto town : cb->getTownsInfo())
 	{
 		if (town->hasBuiltSomeTradeBuilding())
@@ -650,9 +650,9 @@ bool Nullkiller::handleTrading()
 			marketId = town->id;
 		}
 	}
-	if (marketId == -1)
+	if (!marketId.hasValue())
 		return false;
-	if (const CGObjectInstance* obj = cb->getObj(ObjectInstanceID(marketId), false))
+	if (const CGObjectInstance* obj = cb->getObj(marketId, false))
 	{
 		if (const auto* m = dynamic_cast<const IMarket*>(obj))
 		{

+ 26 - 70
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -11,16 +11,16 @@
 #include <limits>
 
 #include "Nullkiller.h"
+#include "../../../lib/entities/artifact/CArtifact.h"
 #include "../../../lib/mapObjectConstructors/AObjectTypeHandler.h"
 #include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h"
-#include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
 #include "../../../lib/mapObjects/CGResource.h"
-#include "../../../lib/mapping/CMapDefines.h"
+#include "../../../lib/mapping/TerrainTile.h"
 #include "../../../lib/RoadHandler.h"
 #include "../../../lib/CCreatureHandler.h"
 #include "../../../lib/GameLibrary.h"
 #include "../../../lib/StartInfo.h"
-#include "../../../CCallback.h"
+#include "../../../lib/GameSettings.h"
 #include "../../../lib/filesystem/Filesystem.h"
 #include "../Goals/ExecuteHeroChain.h"
 #include "../Goals/BuildThis.h"
@@ -145,46 +145,6 @@ int32_t getResourcesGoldReward(const TResources & res)
 	return result;
 }
 
-uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
-{
-	auto objectInfo = target->getObjectHandler()->getObjectInfo(target->appearance);
-	CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
-	auto creatures = bankInfo->getPossibleCreaturesReward(target->cb);
-	uint64_t result = 0;
-
-	const auto& slots = hero->Slots();
-	ui64 weakestStackPower = 0;
-	int duplicatingSlots = getDuplicatingSlots(hero);
-
-	if (slots.size() >= GameConstants::ARMY_SIZE)
-	{
-		//No free slot, we might discard our weakest stack
-		weakestStackPower = std::numeric_limits<ui64>().max();
-		for (const auto & stack : slots)
-		{
-			vstd::amin(weakestStackPower, stack.second->getPower());
-		}
-	}
-
-	for (auto c : creatures)
-	{
-		//Only if hero has slot for this creature in the army
-		auto ccre = dynamic_cast<const CCreature*>(c.data.getType());
-		if (hero->getSlotFor(ccre).validSlot() || duplicatingSlots > 0)
-		{
-			result += (c.data.getType()->getAIValue() * c.data.count) * c.chance;
-		}
-		/*else
-		{
-			//we will need to discard the weakest stack
-			result += (c.data.type->getAIValue() * c.data.count - weakestStackPower) * c.chance;
-		}*/
-	}
-	result /= 100; //divide by total chance
-
-	return result;
-}
-
 uint64_t getDwellingArmyValue(CCallback * cb, const CGObjectInstance * target, bool checkGold)
 {
 	auto dwelling = dynamic_cast<const CGDwelling *>(target);
@@ -246,6 +206,11 @@ int getDwellingArmyCost(const CGObjectInstance * target)
 	return cost;
 }
 
+static uint64_t evaluateSpellScrollArmyValue(const SpellID &)
+{
+	return 1500;
+}
+
 static uint64_t evaluateArtifactArmyValue(const CArtifact * art)
 {
 	if(art->getId() == ArtifactID::SPELL_SCROLL)
@@ -277,9 +242,9 @@ uint64_t RewardEvaluator::getArmyReward(
 	case Obj::CREATURE_GENERATOR4:
 		return getDwellingArmyValue(ai->cb.get(), target, checkGold);
 	case Obj::SPELL_SCROLL:
-		//FALL_THROUGH
+		return evaluateSpellScrollArmyValue(dynamic_cast<const CGArtifact *>(target)->getArtifactInstance()->getScrollSpellID());
 	case Obj::ARTIFACT:
-		return evaluateArtifactArmyValue(dynamic_cast<const CGArtifact *>(target)->storedArtifact->getType());
+		return evaluateArtifactArmyValue(dynamic_cast<const CGArtifact *>(target)->getArtifactInstance()->getType());
 	case Obj::HERO:
 		return  relations == PlayerRelations::ENEMIES
 			? enemyArmyEliminationRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->getArmyStrength()
@@ -305,25 +270,16 @@ uint64_t RewardEvaluator::getArmyReward(
 
 			auto rewardValue = 0;
 
-			if(!info.reward.artifacts.empty())
-			{
-				for(auto artID : info.reward.artifacts)
-				{
-					const auto * art = dynamic_cast<const CArtifact *>(LIBRARY->artifacts()->getById(artID));
+			for(auto artID : info.reward.grantedArtifacts)
+				rewardValue += evaluateArtifactArmyValue(artID.toArtifact());
 
-					rewardValue += evaluateArtifactArmyValue(art);
-				}
-			}
+			for(auto scroll : info.reward.grantedScrolls)
+				rewardValue += evaluateSpellScrollArmyValue(scroll);
 
-			if(!info.reward.creatures.empty())
-			{
-				for(const auto & stackInfo : info.reward.creatures)
-				{
-					rewardValue += stackInfo.getType()->getAIValue() * stackInfo.getCount();
-				}
-			}
+			for(const auto & stackInfo : info.reward.creatures)
+				rewardValue += stackInfo.getType()->getAIValue() * stackInfo.getCount();
 
-			totalValue += rewardValue > 0 ? rewardValue / (info.reward.artifacts.size() + info.reward.creatures.size()) : 0;
+			totalValue += rewardValue > 0 ? rewardValue / (info.reward.grantedArtifacts.size() + info.reward.creatures.size()) : 0;
 		}
 
 		return totalValue;
@@ -421,7 +377,7 @@ float RewardEvaluator::getEnemyHeroStrategicalValue(const CGHeroInstance * enemy
 	return std::min(1.5f, objectValue * 0.9f + (1.5f - (1.5f / (1 + enemy->level))));
 }
 
-float RewardEvaluator::getResourceRequirementStrength(int resType) const
+float RewardEvaluator::getResourceRequirementStrength(GameResID resType) const
 {
 	TResources requiredResources = ai->buildAnalyzer->getResourcesRequiredNow();
 	TResources dailyIncome = ai->buildAnalyzer->getDailyIncome();
@@ -437,7 +393,7 @@ float RewardEvaluator::getResourceRequirementStrength(int resType) const
 	return std::min(ratio, 1.0f);
 }
 
-float RewardEvaluator::getTotalResourceRequirementStrength(int resType) const
+float RewardEvaluator::getTotalResourceRequirementStrength(GameResID resType) const
 {
 	TResources requiredResources = ai->buildAnalyzer->getTotalResourcesRequired();
 	TResources dailyIncome = ai->buildAnalyzer->getDailyIncome();
@@ -626,7 +582,7 @@ float RewardEvaluator::evaluateWitchHutSkillScore(const CGObjectInstance * hut,
 		return role == HeroRole::SCOUT ? 2 : 0;
 
 	if(hero->getSecSkillLevel(skill) != MasteryLevel::NONE
-		|| hero->secSkills.size() >= GameConstants::SKILL_PER_HERO)
+		|| static_cast<int>(hero->secSkills.size()) >= cb->getSettings().getInteger(EGameSettings::HEROES_SKILL_PER_HERO))
 		return 0;
 
 	auto score = ai->heroManager->evaluateSecSkill(skill, hero);
@@ -737,9 +693,9 @@ int32_t getArmyCost(const CArmedInstance * army)
 {
 	int32_t value = 0;
 
-	for(auto stack : army->Slots())
+	for(const auto & stack : army->Slots())
 	{
-		value += stack.second->getCreatureID().toCreature()->getFullRecruitCost().marketValue() * stack.second->count;
+		value += stack.second->getCreatureID().toCreature()->getFullRecruitCost().marketValue() * stack.second->getCount();
 	}
 
 	return value;
@@ -1028,12 +984,12 @@ public:
 		float totalPower = 0;
 
 		// Map to store the aggregated power of creatures by CreatureID
-		std::map<int, float> totalPowerByCreatureID;
+		std::map<CreatureID, float> totalPowerByCreatureID;
 
 		// Calculate hero power and total power by CreatureID
-		for (auto slot : hero->Slots())
+		for (const auto & slot : hero->Slots())
 		{
-			int creatureID = slot.second->getCreatureID();
+			CreatureID creatureID = slot.second->getCreatureID();
 			float slotPower = slot.second->getPower();
 
 			// Add the power of this slot to the heroPower
@@ -1261,7 +1217,7 @@ public:
 		}
 		else if(bi.id >= BuildingID::MAGES_GUILD_1 && bi.id <= BuildingID::MAGES_GUILD_5)
 		{
-			evaluationContext.skillReward += 2 * (bi.id - BuildingID::MAGES_GUILD_1);
+			evaluationContext.skillReward += 2 * bi.id.getMagesGuildLevel();
 			if (!alreadyOwn && evaluationContext.evaluator.ai->cb->canBuildStructure(buildThis.town, highestMageGuildPossible) != EBuildingState::FORBIDDEN)
 			{
 				for (auto hero : evaluationContext.evaluator.ai->cb->getHeroesInfo())

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

@@ -38,11 +38,11 @@ public:
 	uint64_t getArmyGrowth(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army) const;
 	int getGoldCost(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army) const;
 	float getEnemyHeroStrategicalValue(const CGHeroInstance * enemy) const;
-	float getResourceRequirementStrength(int resType) const;
+	float getResourceRequirementStrength(GameResID resType) const;
 	float getResourceRequirementStrength(const TResources & res) const;
 	float getStrategicalValue(const CGObjectInstance * target, const CGHeroInstance * hero = nullptr) const;
 	float getConquestValue(const CGObjectInstance* target) const;
-	float getTotalResourceRequirementStrength(int resType) const;
+	float getTotalResourceRequirementStrength(GameResID resType) const;
 	float evaluateWitchHutSkillScore(const CGObjectInstance * hut, const CGHeroInstance * hero, HeroRole role) const;
 	float getSkillReward(const CGObjectInstance * target, const CGHeroInstance * hero, HeroRole role) const;
 	int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const;

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

@@ -15,7 +15,6 @@
 #include "../../../lib/constants/StringConstants.h"
 #include "../../../lib/mapObjectConstructors/AObjectTypeHandler.h"
 #include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h"
-#include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
 #include "../../../lib/mapObjects/MapObjects.h"
 #include "../../../lib/modding/CModHandler.h"
 #include "../../../lib/GameLibrary.h"

+ 2 - 1
AI/Nullkiller/Goals/AbstractGoal.cpp

@@ -11,6 +11,7 @@
 #include "AbstractGoal.h"
 #include "../AIGateway.h"
 #include "../../../lib/constants/StringConstants.h"
+#include "../../../lib/entities/artifact/CArtifact.h"
 
 namespace NKAI
 {
@@ -55,7 +56,7 @@ std::string AbstractGoal::toString() const
 		desc = "GATHER TROOPS";
 		break;
 	case GET_ART_TYPE:
-		desc = "GET ARTIFACT OF TYPE " + LIBRARY->artifacts()->getByIndex(aid)->getNameTranslated();
+		desc = "GET ARTIFACT OF TYPE " + ArtifactID(aid).toEntity(LIBRARY)->getNameTranslated();
 		break;
 	case DIG_AT_TILE:
 		desc = "DIG AT TILE " + tile.toString();

+ 10 - 7
AI/Nullkiller/Goals/AdventureSpellCast.cpp

@@ -10,6 +10,8 @@
 #include "StdInc.h"
 #include "AdventureSpellCast.h"
 #include "../AIGateway.h"
+#include "../../../lib/spells/ISpellMechanics.h"
+#include "../../../lib/spells/adventure/TownPortalEffect.h"
 
 namespace NKAI
 {
@@ -39,29 +41,30 @@ void AdventureSpellCast::accept(AIGateway * ai)
 	if(hero->mana < hero->getSpellCost(spell))
 		throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated());
 
+	auto townPortalEffect = spell->getAdventureMechanics().getEffectAs<TownPortalEffect>(hero);
 
-	if(town && spellID == SpellID::TOWN_PORTAL)
+	if(town && townPortalEffect)
 	{
 		ai->selectedObject = town->id;
 
-		if(town->visitingHero && town->tempOwner == ai->playerID && !town->getUpperArmy()->stacksCount())
+		if(town->getVisitingHero() && town->tempOwner == ai->playerID && !town->getUpperArmy()->stacksCount())
 		{
 			ai->myCb->swapGarrisonHero(town);
 		}
 
-		if(town->visitingHero)
-			throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated());
+		if(town->getVisitingHero())
+			throw cannotFulfillGoalException("The town is already occupied by " + town->getVisitingHero()->getNameTranslated());
 	}
 
-	if (hero->inTownGarrison)
-		ai->myCb->swapGarrisonHero(hero->visitedTown);
+	if (hero->isGarrisoned())
+		ai->myCb->swapGarrisonHero(hero->getVisitedTown());
 
 	auto wait = cb->waitTillRealize;
 
 	cb->waitTillRealize = true;
 	cb->castSpell(hero, spellID, tile);
 
-	if(town && spellID == SpellID::TOWN_PORTAL)
+	if(town && townPortalEffect)
 	{
 		// visit town
 		ai->moveHeroToTile(town->visitablePos(), hero);

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

@@ -29,7 +29,7 @@ BuildThis::BuildThis(BuildingID Bid, const CGTownInstance * tid)
 		tid,
 		nullptr);
 
-	bid = Bid;
+	bid = Bid.getNum();
 	town = tid;
 }
 

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

@@ -34,7 +34,7 @@ namespace Goals
 		BuildThis(const BuildingInfo & buildingInfo, const TownDevelopmentInfo & townInfo) //should be private, but unit test uses it
 			: ElementarGoal(Goals::BUILD_STRUCTURE), buildingInfo(buildingInfo), townInfo(townInfo)
 		{
-			bid = buildingInfo.id;
+			bid = buildingInfo.id.getNum();
 			town = townInfo.town;
 		}
 		BuildThis(BuildingID Bid, const CGTownInstance * tid);

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

@@ -62,7 +62,7 @@ void BuyArmy::accept(AIGateway * ai)
 			{
 				SlotID lowestValueSlot;
 				int lowestValue = std::numeric_limits<int>::max();
-				for (auto slot : town->getUpperArmy()->Slots())
+				for (const auto & slot : town->getUpperArmy()->Slots())
 				{
 					if (slot.second->getCreatureID() != CreatureID::NONE)
 					{
@@ -97,9 +97,9 @@ void BuyArmy::accept(AIGateway * ai)
 		throw cannotFulfillGoalException("No creatures to buy.");
 	}
 
-	if(town->visitingHero && !town->garrisonHero)
+	if(town->getVisitingHero() && !town->getGarrisonHero())
 	{
-		ai->moveHeroToTile(town->visitablePos(), town->visitingHero.get());
+		ai->moveHeroToTile(town->visitablePos(), town->getVisitingHero());
 	}
 }
 

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

@@ -94,7 +94,7 @@ namespace Goals
 		bool isObjectAffected(ObjectInstanceID id) const override
 		{
 			return (AbstractGoal::hero && AbstractGoal::hero->id == id)
-				|| AbstractGoal::objid == id
+				|| AbstractGoal::objid == id.getNum()
 				|| (AbstractGoal::town && AbstractGoal::town->id == id);
 		}
 

+ 32 - 52
AI/Nullkiller/Goals/CompleteQuest.cpp

@@ -12,6 +12,7 @@
 #include "../Behaviors/CaptureObjectsBehavior.h"
 #include "../AIGateway.h"
 #include "../../../lib/GameLibrary.h"
+#include "../../../lib/mapObjects/CQuest.h"
 #include "../../../lib/texts/CGeneralTextHandler.h"
 
 namespace NKAI
@@ -21,7 +22,8 @@ using namespace Goals;
 
 bool isKeyMaster(const QuestInfo & q)
 {
-	return q.obj && (q.obj->ID == Obj::BORDER_GATE || q.obj->ID == Obj::BORDERGUARD);
+	auto object = q.getObject(cb);
+	return object && (object->ID == Obj::BORDER_GATE || object->ID == Obj::BORDERGUARD);
 }
 
 std::string CompleteQuest::toString() const
@@ -37,27 +39,28 @@ TGoalVec CompleteQuest::decompose(const Nullkiller * ai) const
 	}
 
 	logAi->debug("Trying to realize quest: %s", questToString());
-	
-	if(!q.quest->mission.artifacts.empty())
+	auto quest = q.getQuest(cb);
+
+	if(!quest->mission.artifacts.empty())
 		return missionArt(ai);
 
-	if(!q.quest->mission.heroes.empty())
+	if(!quest->mission.heroes.empty())
 		return missionHero(ai);
 
-	if(!q.quest->mission.creatures.empty())
+	if(!quest->mission.creatures.empty())
 		return missionArmy(ai);
 
-	if(q.quest->mission.resources.nonZero())
+	if(quest->mission.resources.nonZero())
 		return missionResources(ai);
 
-	if(q.quest->killTarget != ObjectInstanceID::NONE)
+	if(quest->killTarget != ObjectInstanceID::NONE)
 		return missionDestroyObj(ai);
 
-	for(auto & s : q.quest->mission.primary)
+	for(auto & s : quest->mission.primary)
 		if(s)
 			return missionIncreasePrimaryStat(ai);
 
-	if(q.quest->mission.heroLevel > 0)
+	if(quest->mission.heroLevel > 0)
 		return missionLevel(ai);
 
 	return TGoalVec();
@@ -67,52 +70,52 @@ bool CompleteQuest::operator==(const CompleteQuest & other) const
 {
 	if(isKeyMaster(q))
 	{
-		return isKeyMaster(other.q) && q.obj->subID == other.q.obj->subID;
+		return isKeyMaster(other.q) && q.getObject(cb)->subID == other.q.getObject(cb)->subID;
 	}
 	else if(isKeyMaster(other.q))
 	{
 		return false;
 	}
 
-	return q.quest->qid == other.q.quest->qid;
+	return q.getQuest(cb) == other.q.getQuest(cb);
 }
 
 uint64_t CompleteQuest::getHash() const
 {
 	if(isKeyMaster(q))
 	{
-		return q.obj->subID;
+		return q.getObject(cb)->subID;
 	}
 
-	return q.quest->qid;
+	return q.getObject(cb)->id.getNum();
 }
 
 std::string CompleteQuest::questToString() const
 {
 	if(isKeyMaster(q))
 	{
-		return "find " + LIBRARY->generaltexth->tentColors[q.obj->subID] + " keymaster tent";
+		return "find " + LIBRARY->generaltexth->tentColors[q.getObject(cb)->subID] + " keymaster tent";
 	}
 
-	if(q.quest->questName == CQuest::missionName(EQuestMission::NONE))
+	if(q.getQuest(cb)->questName == CQuest::missionName(EQuestMission::NONE))
 		return "inactive quest";
 
 	MetaString ms;
-	q.quest->getRolloverText(q.obj->cb, ms, false);
+	q.getQuest(cb)->getRolloverText(cb, ms, false);
 
 	return ms.toString();
 }
 
 TGoalVec CompleteQuest::tryCompleteQuest(const Nullkiller * ai) const
 {
-	auto paths = ai->pathfinder->getPathInfo(q.obj->visitablePos());
+	auto paths = ai->pathfinder->getPathInfo(q.getObject(cb)->visitablePos());
 
 	vstd::erase_if(paths, [&](const AIPath & path) -> bool
 	{
-		return !q.quest->checkQuest(path.targetHero);
+		return !q.getQuest(cb)->checkQuest(path.targetHero);
 	});
 	
-	return CaptureObjectsBehavior::getVisitGoals(paths, ai, q.obj);
+	return CaptureObjectsBehavior::getVisitGoals(paths, ai, q.getObject(cb));
 }
 
 TGoalVec CompleteQuest::missionArt(const Nullkiller * ai) const
@@ -124,9 +127,9 @@ TGoalVec CompleteQuest::missionArt(const Nullkiller * ai) const
 
 	CaptureObjectsBehavior findArts;
 
-	for(auto art : q.quest->mission.artifacts)
+	for(auto art : q.getQuest(cb)->mission.artifacts)
 	{
-		solutions.push_back(sptr(CaptureObjectsBehavior().ofType(Obj::ARTIFACT, art)));
+		solutions.push_back(sptr(CaptureObjectsBehavior().ofType(Obj::ARTIFACT, art.getNum())));
 	}
 
 	return solutions;
@@ -147,14 +150,14 @@ TGoalVec CompleteQuest::missionHero(const Nullkiller * ai) const
 
 TGoalVec CompleteQuest::missionArmy(const Nullkiller * ai) const
 {
-	auto paths = ai->pathfinder->getPathInfo(q.obj->visitablePos());
+	auto paths = ai->pathfinder->getPathInfo(q.getObject(cb)->visitablePos());
 
 	vstd::erase_if(paths, [&](const AIPath & path) -> bool
 	{
-		return !CQuest::checkMissionArmy(q.quest, path.heroArmy);
+		return !CQuest::checkMissionArmy(q.getQuest(cb), path.heroArmy);
 	});
 
-	return CaptureObjectsBehavior::getVisitGoals(paths, ai, q.obj);
+	return CaptureObjectsBehavior::getVisitGoals(paths, ai, q.getObject(cb));
 }
 
 TGoalVec CompleteQuest::missionIncreasePrimaryStat(const Nullkiller * ai) const
@@ -169,51 +172,28 @@ TGoalVec CompleteQuest::missionLevel(const Nullkiller * ai) const
 
 TGoalVec CompleteQuest::missionKeymaster(const Nullkiller * ai) const
 {
-	if(isObjectPassable(ai, q.obj))
+	if(isObjectPassable(ai, q.getObject(cb)))
 	{
-		return CaptureObjectsBehavior(q.obj).decompose(ai);
+		return CaptureObjectsBehavior(q.getObject(cb)).decompose(ai);
 	}
 	else
 	{
-		return CaptureObjectsBehavior().ofType(Obj::KEYMASTER, q.obj->subID).decompose(ai);
+		return CaptureObjectsBehavior().ofType(Obj::KEYMASTER, q.getObject(cb)->subID).decompose(ai);
 	}
 }
 
 TGoalVec CompleteQuest::missionResources(const Nullkiller * ai) const
 {
 	TGoalVec solutions = tryCompleteQuest(ai);
-
-	/*auto heroes = cb->getHeroesInfo(); //TODO: choose best / free hero from among many possibilities?
-
-	if(heroes.size())
-	{
-		if(q.quest->checkQuest(heroes.front())) //it doesn't matter which hero it is
-		{
-			return solutions;// ai->ah->howToVisitObj(q.obj);
-		}
-		else
-		{
-			for(int i = 0; i < q.quest->m7resources.size(); ++i)
-			{
-				if(q.quest->m7resources[i])
-					solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.quest->m7resources[i])));
-			}
-		}
-	}
-	else
-	{
-		solutions.push_back(sptr(Goals::RecruitHero())); //FIXME: checkQuest requires any hero belonging to player :(
-	}*/
-
 	return solutions;
 }
 
 TGoalVec CompleteQuest::missionDestroyObj(const Nullkiller * ai) const
 {
-	auto obj = ai->cb->getObj(q.quest->killTarget);
+	auto obj = ai->cb->getObj(q.getQuest(cb)->killTarget);
 
 	if(!obj)
-		return CaptureObjectsBehavior(q.obj).decompose(ai);
+		return CaptureObjectsBehavior(q.getObject(cb)).decompose(ai);
 
 	auto relations = ai->cb->getPlayerRelations(ai->playerID, obj->tempOwner);
 

+ 0 - 1
AI/Nullkiller/Goals/CompleteQuest.h

@@ -10,7 +10,6 @@
 #pragma once
 
 #include "../AIUtility.h"
-#include "../../../CCallback.h"
 #include "../Goals/CGoal.h"
 #include "../../../lib/gameState/QuestInfo.h"
 

+ 20 - 20
AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp

@@ -19,7 +19,7 @@ namespace NKAI
 using namespace Goals;
 
 ExchangeSwapTownHeroes::ExchangeSwapTownHeroes(
-	const CGTownInstance * town, 
+	const CGTownInstance * town,
 	const CGHeroInstance * garrisonHero,
 	HeroLockedReason lockingReason)
 	:ElementarGoal(Goals::EXCHANGE_SWAP_TOWN_HEROES), town(town), garrisonHero(garrisonHero), lockingReason(lockingReason)
@@ -30,11 +30,11 @@ std::vector<ObjectInstanceID> ExchangeSwapTownHeroes::getAffectedObjects() const
 {
 	std::vector<ObjectInstanceID> affectedObjects = { town->id };
 
-	if(town->garrisonHero)
-		affectedObjects.push_back(town->garrisonHero->id);
+	if(town->getGarrisonHero())
+		affectedObjects.push_back(town->getGarrisonHero()->id);
 
-	if(town->visitingHero)
-		affectedObjects.push_back(town->visitingHero->id);
+	if(town->getVisitingHero())
+		affectedObjects.push_back(town->getVisitingHero()->id);
 
 	return affectedObjects;
 }
@@ -42,8 +42,8 @@ std::vector<ObjectInstanceID> ExchangeSwapTownHeroes::getAffectedObjects() const
 bool ExchangeSwapTownHeroes::isObjectAffected(ObjectInstanceID id) const
 {
 	return town->id == id
-		|| (town->visitingHero && town->visitingHero->id == id)
-		|| (town->garrisonHero && town->garrisonHero->id == id);
+		|| (town->getVisitingHero() && town->getVisitingHero()->id == id)
+		|| (town->getGarrisonHero() && town->getGarrisonHero()->id == id);
 }
 
 std::string ExchangeSwapTownHeroes::toString() const
@@ -58,39 +58,39 @@ bool ExchangeSwapTownHeroes::operator==(const ExchangeSwapTownHeroes & other) co
 
 void ExchangeSwapTownHeroes::accept(AIGateway * ai)
 {
-	if(!garrisonHero)
+	if(!getGarrisonHero())
 	{
-		auto currentGarrisonHero = town->garrisonHero;
+		auto currentGarrisonHero = town->getGarrisonHero();
 		
 		if(!currentGarrisonHero)
 			throw cannotFulfillGoalException("Invalid configuration. There is no hero in town garrison.");
 		
 		cb->swapGarrisonHero(town);
 
-		if(currentGarrisonHero.get() != town->visitingHero.get())
+		if(currentGarrisonHero != town->getVisitingHero())
 		{
 			logAi->error("VisitingHero is empty, expected %s", currentGarrisonHero->getNameTranslated());
 			return;
 		}
 
 		ai->buildArmyIn(town);
-		ai->nullkiller->unlockHero(currentGarrisonHero.get());
+		ai->nullkiller->unlockHero(currentGarrisonHero);
 		logAi->debug("Extracted hero %s from garrison of %s", currentGarrisonHero->getNameTranslated(), town->getNameTranslated());
 
 		return;
 	}
 
-	if(town->visitingHero && town->visitingHero.get() != garrisonHero)
+	if(town->getVisitingHero() && town->getVisitingHero() != getGarrisonHero())
 		cb->swapGarrisonHero(town);
 
 	ai->makePossibleUpgrades(town);
-	ai->moveHeroToTile(town->visitablePos(), garrisonHero);
+	ai->moveHeroToTile(town->visitablePos(), getGarrisonHero());
 
 	auto upperArmy = town->getUpperArmy();
 	
-	if(!town->garrisonHero)
+	if(!town->getGarrisonHero())
 	{
-		if (!garrisonHero->canBeMergedWith(*town))
+		if (!getGarrisonHero()->canBeMergedWith(*town))
 		{
 			while (upperArmy->stacksCount() != 0)
 			{
@@ -103,16 +103,16 @@ void ExchangeSwapTownHeroes::accept(AIGateway * ai)
 
 	if(lockingReason != HeroLockedReason::NOT_LOCKED)
 	{
-		ai->nullkiller->lockHero(garrisonHero, lockingReason);
+		ai->nullkiller->lockHero(getGarrisonHero(), lockingReason);
 	}
 
-	if(town->visitingHero && town->visitingHero != garrisonHero)
+	if(town->getVisitingHero() && town->getVisitingHero() != getGarrisonHero())
 	{
-		ai->nullkiller->unlockHero(town->visitingHero.get());
-		ai->makePossibleUpgrades(town->visitingHero);
+		ai->nullkiller->unlockHero(town->getVisitingHero());
+		ai->makePossibleUpgrades(town->getVisitingHero());
 	}
 
-	logAi->debug("Put hero %s to garrison of %s", garrisonHero->getNameTranslated(), town->getNameTranslated());
+	logAi->debug("Put hero %s to garrison of %s", getGarrisonHero()->getNameTranslated(), town->getNameTranslated());
 }
 
 }

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

@@ -68,7 +68,7 @@ std::vector<ObjectInstanceID> ExecuteHeroChain::getAffectedObjects() const
 
 bool ExecuteHeroChain::isObjectAffected(ObjectInstanceID id) const
 {
-	if(chainPath.targetHero->id == id || objid == id)
+	if(chainPath.targetHero->id == id || objid == id.getNum())
 		return true;
 
 	for(auto & node : chainPath.nodes)

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

@@ -59,12 +59,12 @@ void RecruitHero::accept(AIGateway * ai)
 	if(!heroToHire)
 		throw cannotFulfillGoalException("No hero to hire!");
 
-	if(t->visitingHero)
+	if(t->getVisitingHero())
 	{
 		cb->swapGarrisonHero(t);
 	}
 
-	if(t->visitingHero)
+	if(t->getVisitingHero())
 		throw cannotFulfillGoalException("Town " + t->nodeName() + " is occupied. Cannot recruit hero!");
 
 	cb->recruitHero(t, heroToHire);

+ 1 - 1
AI/Nullkiller/Goals/StayAtTown.cpp

@@ -23,7 +23,7 @@ StayAtTown::StayAtTown(const CGTownInstance * town, AIPath & path)
 {
 	sethero(path.targetHero);
 	settown(town);
-	movementWasted = static_cast<float>(hero->movementPointsRemaining()) / hero->movementPointsLimit(!hero->boat) - path.movementCost();
+	movementWasted = static_cast<float>(hero->movementPointsRemaining()) / hero->movementPointsLimit(!hero->inBoat()) - path.movementCost();
 	vstd::amax(movementWasted, 0);
 }
 

+ 9 - 9
AI/Nullkiller/Helpers/ArmyFormation.cpp

@@ -21,11 +21,11 @@ void ArmyFormation::rearrangeArmyForWhirlpool(const CGHeroInstance * hero)
 
 void ArmyFormation::addSingleCreatureStacks(const CGHeroInstance * hero)
 {
-	auto freeSlots = hero->getFreeSlotsQueue();
+	auto freeSlots = hero->getFreeSlots();
 
 	while(!freeSlots.empty())
 	{
-		auto weakestCreature = vstd::minElementByFun(hero->Slots(), [](const std::pair<SlotID, CStackInstance *> & slot) -> int
+		TSlots::const_iterator weakestCreature = vstd::minElementByFun(hero->Slots(), [](const auto & slot) -> int
 			{
 				return slot.second->getCount() == 1
 					? std::numeric_limits<int>::max()
@@ -37,8 +37,8 @@ void ArmyFormation::addSingleCreatureStacks(const CGHeroInstance * hero)
 			break;
 		}
 
-		cb->splitStack(hero, hero, weakestCreature->first, freeSlots.front(), 1);
-		freeSlots.pop();
+		cb->splitStack(hero, hero, weakestCreature->first, freeSlots.back(), 1);
+		freeSlots.pop_back();
 	}
 }
 
@@ -48,14 +48,14 @@ void ArmyFormation::rearrangeArmyForSiege(const CGTownInstance * town, const CGH
 
 	if(town->fortLevel() > CGTownInstance::FORT)
 	{
-		std::vector<CStackInstance *> stacks;
+		std::vector<const CStackInstance *> stacks;
 
-		for(auto slot : attacker->Slots())
-			stacks.push_back(slot.second);
+		for(const auto & slot : attacker->Slots())
+			stacks.push_back(slot.second.get());
 
 		boost::sort(
 			stacks,
-			[](CStackInstance * slot1, CStackInstance * slot2) -> bool
+			[](const CStackInstance * slot1, const CStackInstance * slot2) -> bool
 			{
 				auto cre1 = slot1->getCreatureID().toCreature();
 				auto cre2 = slot2->getCreatureID().toCreature();
@@ -67,7 +67,7 @@ void ArmyFormation::rearrangeArmyForSiege(const CGTownInstance * town, const CGH
 
 		for(int i = 0; i < stacks.size(); i++)
 		{
-			auto pos = vstd::findKey(attacker->Slots(), stacks[i]);
+			auto pos = stacks[i]->getArmy()->findStack(stacks[i]);
 
 			if(pos.getNum() != i)
 				cb->swapCreatures(attacker, attacker, static_cast<SlotID>(i), pos);

+ 1 - 1
AI/Nullkiller/Markers/UnlockCluster.h

@@ -34,7 +34,7 @@ namespace Goals
 		{
 			tile = cluster->blocker->visitablePos();
 			hero = pathToCenter.targetHero;
-			objid = cluster->blocker->id;
+			objid = cluster->blocker->id.getNum();
 		}
 
 		bool operator==(const UnlockCluster & other) const override;

+ 57 - 51
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -11,15 +11,14 @@
 #include "AINodeStorage.h"
 #include "Actions/TownPortalAction.h"
 #include "Actions/WhirlpoolAction.h"
-#include "../Goals/Goals.h"
-#include "../AIGateway.h"
 #include "../Engine/Nullkiller.h"
-#include "../../../CCallback.h"
+#include "../../../lib/callback/IGameInfoCallback.h"
 #include "../../../lib/mapping/CMap.h"
-#include "../../../lib/mapObjects/MapObjects.h"
 #include "../../../lib/pathfinder/CPathfinder.h"
 #include "../../../lib/pathfinder/PathfinderUtil.h"
 #include "../../../lib/pathfinder/PathfinderOptions.h"
+#include "../../../lib/spells/ISpellMechanics.h"
+#include "../../../lib/spells/adventure/TownPortalEffect.h"
 #include "../../../lib/IGameSettings.h"
 #include "../../../lib/CPlayerState.h"
 
@@ -112,7 +111,7 @@ AINodeStorage::AINodeStorage(const Nullkiller * ai, const int3 & Sizes)
 
 AINodeStorage::~AINodeStorage() = default;
 
-void AINodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs)
+void AINodeStorage::initialize(const PathfinderOptions & options, const IGameInfoCallback & gameInfo)
 {
 	if(heroChainPass != EHeroChainPass::INITIAL)
 		return;
@@ -121,8 +120,8 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
 
 	//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
 	const PlayerColor fowPlayer = ai->playerID;
-	const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap;
-	const int3 sizes = gs->getMapSize();
+	const auto & fow = gameInfo.getPlayerTeam(fowPlayer)->fogOfWarMap;
+	const int3 sizes = gameInfo.getMapSize();
 
 	//Each thread gets different x, but an array of y located next to each other in memory
 
@@ -140,23 +139,23 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
 			{
 				for(pos.y = 0; pos.y < sizes.y; ++pos.y)
 				{
-					const TerrainTile & tile = gs->getMap().getTile(pos);
-					if (!tile.getTerrain()->isPassable())
+					const TerrainTile * tile = gameInfo.getTile(pos);
+					if (!tile->getTerrain()->isPassable())
 						continue;
 
-					if (tile.isWater())
+					if (tile->isWater())
 					{
-						resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
+						resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, *tile, fow, player, gameInfo));
 						if (useFlying)
-							resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
+							resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, *tile, fow, player, gameInfo));
 						if (useWaterWalking)
-							resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility<ELayer::WATER>(pos, tile, fow, player, gs));
+							resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility<ELayer::WATER>(pos, *tile, fow, player, gameInfo));
 					}
 					else
 					{
-						resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility<ELayer::LAND>(pos, tile, fow, player, gs));
+						resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility<ELayer::LAND>(pos, *tile, fow, player, gameInfo));
 						if (useFlying)
-							resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
+							resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, *tile, fow, player, gameInfo));
 					}
 				}
 			}
@@ -180,7 +179,7 @@ std::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
 	const EPathfindingLayer layer, 
 	const ChainActor * actor)
 {
-	int bucketIndex = ((uintptr_t)actor + static_cast<uint32_t>(layer)) % ai->settings->getPathfinderBucketsCount();
+	int bucketIndex = ((uintptr_t)actor + layer.getNum()) % ai->settings->getPathfinderBucketsCount();
 	int bucketOffset = bucketIndex * ai->settings->getPathfinderBucketSize();
 	auto chains = nodes.get(pos);
 
@@ -284,7 +283,7 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
 					return;
 				}
 
-				auto weakest = vstd::minElementByFun(dstNode->actor->creatureSet->Slots(), [](std::pair<SlotID, const CStackInstance *> pair) -> int
+				const auto & weakest = vstd::minElementByFun(dstNode->actor->creatureSet->Slots(), [](const auto & pair) -> int
 					{
 						return pair.second->getCount() * pair.second->getCreatureID().toCreature()->getAIValue();
 					});
@@ -904,6 +903,7 @@ ExchangeCandidate HeroChainCalculationTask::calculateExchange(
 	candidate.setCost(carrierParentNode->getCost() + otherParentNode->getCost() / 1000.0);
 	candidate.moveRemains = carrierParentNode->moveRemains;
 	candidate.danger = carrierParentNode->danger;
+	candidate.version = carrierParentNode->version;
 
 	if(carrierParentNode->turns < otherParentNode->turns)
 	{
@@ -958,7 +958,7 @@ void AINodeStorage::setHeroes(std::map<const CGHeroInstance *, HeroRole> heroes)
 	{
 		// do not allow our own heroes in garrison to act on map
 		if(hero.first->getOwner() == ai->playerID
-			&& hero.first->inTownGarrison
+			&& hero.first->isGarrisoned()
 			&& (ai->isHeroLocked(hero.first) || ai->heroManager->heroCapReached(false)))
 		{
 			continue;
@@ -969,7 +969,7 @@ void AINodeStorage::setHeroes(std::map<const CGHeroInstance *, HeroRole> heroes)
 
 		if(actor->hero->tempOwner != ai->playerID)
 		{
-			bool onLand = !actor->hero->boat || actor->hero->boat->layer != EPathfindingLayer::SAIL;
+			bool onLand = !actor->hero->inBoat() || actor->hero->getBoat()->layer != EPathfindingLayer::SAIL;
 			actor->initialMovement = actor->hero->movementPointsLimit(onLand);
 		}
 
@@ -987,9 +987,9 @@ void AINodeStorage::setTownsAndDwellings(
 	{
 		uint64_t mask = FirstActorMask << actors.size();
 
-		// TODO: investigate logix of second condition || ai->nullkiller->getHeroLockedReason(town->garrisonHero) != HeroLockedReason::DEFENCE
+		// TODO: investigate logix of second condition || ai->nullkiller->getHeroLockedReason(town->getGarrisonHero()) != HeroLockedReason::DEFENCE
 		// check defence imrove
-		if(!town->garrisonHero)
+		if(!town->getGarrisonHero())
 		{
 			actors.push_back(std::make_shared<TownGarrisonActor>(town, mask));
 		}
@@ -1061,32 +1061,27 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
 struct TownPortalFinder
 {
 	const std::vector<CGPathNode *> & initialNodes;
-	MasteryLevel::Type townPortalSkillLevel;
-	uint64_t movementNeeded;
 	const ChainActor * actor;
 	const CGHeroInstance * hero;
 	std::vector<const CGTownInstance *> targetTowns;
 	AINodeStorage * nodeStorage;
-
-	SpellID spellID;
 	const CSpell * townPortal;
-
-	TownPortalFinder(
-		const ChainActor * actor,
-		const std::vector<CGPathNode *> & initialNodes,
-		std::vector<const CGTownInstance *> targetTowns,
-		AINodeStorage * nodeStorage)
-		:actor(actor), initialNodes(initialNodes), hero(actor->hero),
-		targetTowns(targetTowns), nodeStorage(nodeStorage)
+	uint64_t movementNeeded;
+	SpellID spellID;
+	bool townSelectionAllowed;
+
+	TownPortalFinder(const ChainActor * actor, const std::vector<CGPathNode *> & initialNodes, const std::vector<const CGTownInstance *> & targetTowns, AINodeStorage * nodeStorage, SpellID spellID)
+		: initialNodes(initialNodes)
+		, actor(actor)
+		, hero(actor->hero)
+		, targetTowns(targetTowns)
+		, nodeStorage(nodeStorage)
+		, townPortal(spellID.toSpell())
+		, spellID(spellID)
 	{
-		spellID = SpellID::TOWN_PORTAL;
-		townPortal = spellID.toSpell();
-
-		// TODO: Copy/Paste from TownPortalMechanics
-		townPortalSkillLevel = MasteryLevel::Type(hero->getSpellSchoolLevel(townPortal));
-
-		int baseCost = hero->cb->getSettings().getInteger(EGameSettings::HEROES_MOVEMENT_COST_BASE);
-		movementNeeded = baseCost * (townPortalSkillLevel >= MasteryLevel::EXPERT ? 2 : 3);
+		auto townPortalEffect = townPortal->getAdventureMechanics().getEffectAs<TownPortalEffect>(hero);
+		movementNeeded = townPortalEffect->getMovementPointsRequired();
+		townSelectionAllowed = townPortalEffect->townSelectionAllowed();
 	}
 
 	bool actorCanCastTownPortal()
@@ -1107,7 +1102,7 @@ struct TownPortalFinder
 				continue;
 			}
 
-			if(townPortalSkillLevel < MasteryLevel::ADVANCED)
+			if(!townSelectionAllowed)
 			{
 				const CGTownInstance * nearestTown = *vstd::minElementByFun(targetTowns, [&](const CGTownInstance * t) -> int
 				{
@@ -1153,7 +1148,7 @@ struct TownPortalFinder
 				DO_NOT_SAVE_TO_COMMITTED_TILES);
 
 			node->theNodeBefore = bestNode;
-			node->addSpecialAction(std::make_shared<AIPathfinding::TownPortalAction>(targetTown));
+			node->addSpecialAction(std::make_shared<AIPathfinding::TownPortalAction>(targetTown, spellID));
 		}
 
 		return nodeOptional;
@@ -1179,25 +1174,36 @@ void AINodeStorage::calculateTownPortal(
 		return; // no towns no need to run loop further
 	}
 
-	TownPortalFinder townPortalFinder(actor, initialNodes, towns, this);
-
-	if(townPortalFinder.actorCanCastTownPortal())
+	for (const auto & spell : LIBRARY->spellh->objects)
 	{
+		if (!spell || !spell->isAdventure())
+			continue;
+
+		auto townPortalEffect = spell->getAdventureMechanics().getEffectAs<TownPortalEffect>(actor->hero);
+
+		if (!townPortalEffect)
+			continue;
+
+		TownPortalFinder townPortalFinder(actor, initialNodes, towns, this, spell->id);
+
+		if(!townPortalFinder.actorCanCastTownPortal())
+			continue;
+
 		for(const CGTownInstance * targetTown : towns)
 		{
-			if(targetTown->visitingHero
+			if(targetTown->getVisitingHero()
 				&& targetTown->getUpperArmy()->stacksCount()
-				&& maskMap.find(targetTown->visitingHero.get()) != maskMap.end())
+				&& maskMap.find(targetTown->getVisitingHero()) != maskMap.end())
 			{
-				auto basicMask = maskMap.at(targetTown->visitingHero.get());
+				auto basicMask = maskMap.at(targetTown->getVisitingHero());
 				bool sameActorInTown = actor->chainMask == basicMask;
 
 				if(!sameActorInTown)
 					continue;
 			}
 
-			if (targetTown->visitingHero
-				&& (targetTown->visitingHero.get()->getFactionID() != actor->hero->getFactionID()
+			if (targetTown->getVisitingHero()
+				&& (targetTown->getVisitingHero()->getFactionID() != actor->hero->getFactionID()
 					|| targetTown->getUpperArmy()->stacksCount()))
 				continue;
 

+ 3 - 7
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -16,10 +16,6 @@ constexpr int NKAI_GRAPH_TRACE_LEVEL = 0; // To actually enable graph visualizat
 
 #include "../../../lib/pathfinder/CGPathNode.h"
 #include "../../../lib/pathfinder/INodeStorage.h"
-#include "../../../lib/mapObjects/CGHeroInstance.h"
-#include "../AIUtility.h"
-#include "../Engine/FuzzyHelper.h"
-#include "../Goals/AbstractGoal.h"
 #include "Actions/SpecialAction.h"
 #include "Actors.h"
 
@@ -186,7 +182,7 @@ public:
 	AINodeStorage(const Nullkiller * ai, const int3 & sizes);
 	~AINodeStorage();
 
-	void initialize(const PathfinderOptions & options, const CGameState * gs) override;
+	void initialize(const PathfinderOptions & options, const IGameInfoCallback & gameInfo) override;
 
 	bool increaseHeroChainTurnLimit();
 	bool selectFirstActor();
@@ -286,12 +282,12 @@ public:
 
 	inline EPathAccessibility getAccessibility(const int3 & tile, EPathfindingLayer layer) const
 	{
-		return (*this->accessibility)[tile.z][tile.x][tile.y][layer];
+		return (*this->accessibility)[tile.z][tile.x][tile.y][layer.getNum()];
 	}
 
 	inline void resetTile(const int3 & tile, EPathfindingLayer layer, EPathAccessibility tileAccessibility)
 	{
-		(*this->accessibility)[tile.z][tile.x][tile.y][layer] = tileAccessibility;
+		(*this->accessibility)[tile.z][tile.x][tile.y][layer.getNum()] = tileAccessibility;
 	}
 
 	inline int getBucket(const ChainActor * actor) const

+ 0 - 1
AI/Nullkiller/Pathfinding/AIPathfinder.cpp

@@ -10,7 +10,6 @@
 #include "StdInc.h"
 #include "AIPathfinder.h"
 #include "AIPathfinderConfig.h"
-#include "../../../CCallback.h"
 #include "../../../lib/mapping/CMap.h"
 #include "../Engine/Nullkiller.h"
 

+ 3 - 3
AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp

@@ -44,7 +44,7 @@ namespace AIPathfinding
 		Nullkiller * ai,
 		std::shared_ptr<AINodeStorage> nodeStorage,
 		bool allowBypassObjects)
-		:PathfinderConfig(nodeStorage, cb, makeRuleset(cb, ai, nodeStorage, allowBypassObjects)), aiNodeStorage(nodeStorage)
+		:PathfinderConfig(nodeStorage, *cb, makeRuleset(cb, ai, nodeStorage, allowBypassObjects)), aiNodeStorage(nodeStorage)
 	{
 		options.canUseCast = true;
 		options.allowLayerTransitioningAfterBattle = true;
@@ -56,14 +56,14 @@ namespace AIPathfinding
 
 	AIPathfinderConfig::~AIPathfinderConfig() = default;
 
-	CPathfinderHelper * AIPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs)
+	CPathfinderHelper * AIPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, const IGameInfoCallback & gameInfo)
 	{
 		auto hero = aiNodeStorage->getHero(source.node);
 		auto & helper = pathfindingHelpers[hero];
 
 		if(!helper)
 		{
-			helper.reset(new CPathfinderHelper(gs, hero, options));
+			helper.reset(new CPathfinderHelper(gameInfo, hero, options));
 		}
 
 		return helper.get();

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

@@ -35,7 +35,7 @@ namespace AIPathfinding
 
 		~AIPathfinderConfig();
 
-		CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
+		CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, const IGameInfoCallback & gameInfo) override;
 	};
 }
 

+ 4 - 4
AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.cpp

@@ -28,12 +28,12 @@ namespace AIPathfinding
 		manaCost = hero->getSpellCost(spellToCast.toSpell());
 	}
 
-	WaterWalkingAction::WaterWalkingAction(const CGHeroInstance * hero)
-		:AdventureCastAction(SpellID::WATER_WALK, hero, DayFlags::WATER_WALK_CAST)
+	WaterWalkingAction::WaterWalkingAction(const CGHeroInstance * hero, SpellID spellToCast)
+		:AdventureCastAction(spellToCast, hero, DayFlags::WATER_WALK_CAST)
 	{ }
 
-	AirWalkingAction::AirWalkingAction(const CGHeroInstance * hero)
-		: AdventureCastAction(SpellID::FLY, hero, DayFlags::FLY_CAST)
+	AirWalkingAction::AirWalkingAction(const CGHeroInstance * hero, SpellID spellToCast)
+		: AdventureCastAction(spellToCast, hero, DayFlags::FLY_CAST)
 	{
 	}
 

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

@@ -45,14 +45,16 @@ namespace AIPathfinding
 
 	class WaterWalkingAction : public AdventureCastAction
 	{
+		SpellID spellToCast;
 	public:
-		WaterWalkingAction(const CGHeroInstance * hero);
+		WaterWalkingAction(const CGHeroInstance * hero, SpellID spellToCast);
 	};
 
 	class AirWalkingAction : public AdventureCastAction
 	{
+		SpellID spellToCast;
 	public:
-		AirWalkingAction(const CGHeroInstance * hero);
+		AirWalkingAction(const CGHeroInstance * hero, SpellID spellToCast);
 	};
 }
 

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

@@ -92,7 +92,7 @@ namespace AIPathfinding
 
 	void SummonBoatAction::execute(AIGateway * ai, const CGHeroInstance * hero) const
 	{
-		Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT).accept(ai);
+		Goals::AdventureSpellCast(hero, usedSpell).accept(ai);
 	}
 
 	const ChainActor * SummonBoatAction::getActor(const ChainActor * sourceActor) const
@@ -139,10 +139,8 @@ namespace AIPathfinding
 
 	int32_t SummonBoatAction::getManaCost(const CGHeroInstance * hero) const
 	{
-		SpellID summonBoat = SpellID::SUMMON_BOAT;
-
 		// FIXME: this should be hero->getSpellCost, however currently queries to bonus system are too slow
-		return summonBoat.toSpell()->getCost(0);
+		return usedSpell.toSpell()->getCost(0);
 	}
 }
 

+ 6 - 0
AI/Nullkiller/Pathfinding/Actions/BoatActions.h

@@ -24,7 +24,13 @@ namespace AIPathfinding
 	
 	class SummonBoatAction : public VirtualBoatAction
 	{
+		SpellID usedSpell;
 	public:
+		SummonBoatAction(SpellID usedSpell)
+			: usedSpell(usedSpell)
+		{
+		}
+
 		void execute(AIGateway * ai, const CGHeroInstance * hero) const override;
 
 		virtual void applyOnDestination(

+ 2 - 2
AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.cpp

@@ -20,13 +20,13 @@ namespace AIPathfinding
 {
 	void BuyArmyAction::execute(AIGateway * ai, const CGHeroInstance * hero) const
 	{
-		if(!hero->visitedTown)
+		if(!hero->getVisitedTown())
 		{
 			throw cannotFulfillGoalException(
 				hero->getNameTranslated() + " being at " + hero->visitablePos().toString() + " has no town to recruit creatures.");
 		}
 
-		ai->recruitCreatures(hero->visitedTown, hero);
+		ai->recruitCreatures(hero->getVisitedTown(), hero);
 	}
 
 	std::string BuyArmyAction::toString() const

+ 9 - 6
AI/Nullkiller/Pathfinding/Actions/QuestAction.cpp

@@ -12,6 +12,7 @@
 #include "QuestAction.h"
 #include "../../AIGateway.h"
 #include "../../Goals/CompleteQuest.h"
+#include "../../../../lib/mapObjects/CQuest.h"
 
 namespace NKAI
 {
@@ -30,16 +31,18 @@ namespace AIPathfinding
 
 	bool QuestAction::canAct(const Nullkiller * ai, const CGHeroInstance * hero) const
 	{
-		if(questInfo.obj->ID == Obj::BORDER_GATE || questInfo.obj->ID == Obj::BORDERGUARD)
+		auto object = questInfo.getObject(ai->cb.get());
+		auto quest = questInfo.getQuest(ai->cb.get());
+		if(object->ID == Obj::BORDER_GATE || object->ID == Obj::BORDERGUARD)
 		{
-			return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(hero);
+			return dynamic_cast<const IQuestObject *>(object)->checkQuest(hero);
 		}
 
-		auto notActivated = !questInfo.obj->wasVisited(ai->playerID)
-			&& !questInfo.quest->activeForPlayers.count(hero->getOwner());
+		auto notActivated = !object->wasVisited(ai->playerID)
+			&& !quest->activeForPlayers.count(hero->getOwner());
 		
 		return notActivated
-			|| questInfo.quest->checkQuest(hero);
+			|| quest->checkQuest(hero);
 	}
 
 	Goals::TSubgoal QuestAction::decompose(const Nullkiller * ai, const CGHeroInstance * hero) const
@@ -49,7 +52,7 @@ namespace AIPathfinding
 
 	void QuestAction::execute(AIGateway * ai, const CGHeroInstance * hero) const
 	{
-		ai->moveHeroToTile(questInfo.obj->visitablePos(), hero);
+		ai->moveHeroToTile(questInfo.getObject(ai->myCb.get())->visitablePos(), hero);
 	}
 
 	std::string QuestAction::toString() const

+ 1 - 1
AI/Nullkiller/Pathfinding/Actions/TownPortalAction.cpp

@@ -20,7 +20,7 @@ using namespace AIPathfinding;
 
 void TownPortalAction::execute(AIGateway * ai, const CGHeroInstance * hero) const
 {
-	auto goal = Goals::AdventureSpellCast(hero, SpellID::TOWN_PORTAL);
+	auto goal = Goals::AdventureSpellCast(hero, usedSpell);
 	
 	goal.town = target;
 	goal.tile = target->visitablePos();

+ 3 - 1
AI/Nullkiller/Pathfinding/Actions/TownPortalAction.h

@@ -22,10 +22,12 @@ namespace AIPathfinding
 	{
 	private:
 		const CGTownInstance * target;
+		SpellID usedSpell;
 
 	public:
-		TownPortalAction(const CGTownInstance * target)
+		TownPortalAction(const CGTownInstance * target, SpellID usedSpell)
 			:target(target)
+			,usedSpell(usedSpell)
 		{
 		}
 

+ 7 - 8
AI/Nullkiller/Pathfinding/Actors.cpp

@@ -11,9 +11,8 @@
 #include "Actors.h"
 #include "../AIGateway.h"
 #include "../Engine/Nullkiller.h"
-#include "../../../CCallback.h"
 #include "../../../lib/mapObjects/MapObjects.h"
-#include "../../../lib/mapping/CMapDefines.h"
+#include "../../../lib/mapping/TerrainTile.h"
 #include "../../../lib/pathfinder/TurnInfo.h"
 #include "Actions/BuyArmyAction.h"
 
@@ -43,7 +42,7 @@ ChainActor::ChainActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t
 	baseActor(this), carrierParent(nullptr), otherParent(nullptr), actorExchangeCount(1), armyCost(), actorAction()
 {
 	initialPosition = hero->visitablePos();
-	layer = hero->boat ? hero->boat->layer : EPathfindingLayer::LAND;
+	layer = hero->inBoat() ? hero->getBoat()->layer : EPathfindingLayer::LAND;
 	initialMovement = hero->movementPointsRemaining();
 	initialTurn = 0;
 	armyValue = getHeroArmyStrengthWithCommander(hero, hero);
@@ -75,7 +74,7 @@ int ChainActor::maxMovePoints(CGPathNode::ELayer layer)
 		throw std::logic_error("Asking movement points for static actor");
 #endif
 
-	return hero->movementPointsLimit(layer);
+	return hero->movementPointsLimit(layer != EPathfindingLayer::SAIL);
 }
 
 std::string ChainActor::toString() const
@@ -358,11 +357,11 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
 	}
 	else
 	{
-		for(auto slot : army->Slots())
+		for(const auto & slot : army->Slots())
 		{
-			auto targetSlot = target->getSlotFor(slot.second->getCreatureID());
+			const auto & targetSlot = target->getSlotFor(slot.second->getCreatureID());
 
-			target->addToSlot(targetSlot, slot.second->getCreatureID(), slot.second->count);
+			target->addToSlot(targetSlot, slot.second->getCreatureID(), slot.second->getCount());
 		}
 	}
 
@@ -422,7 +421,7 @@ DwellingActor::DwellingActor(const CGDwelling * dwelling, uint64_t chainMask, bo
 {
 	for(auto & slot : creatureSet->Slots())
 	{
-		armyCost += slot.second->getCreatureID().toCreature()->getFullRecruitCost() * slot.second->count;
+		armyCost += slot.second->getCreatureID().toCreature()->getFullRecruitCost() * slot.second->getCount();
 	}
 }
 

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

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

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

@@ -11,7 +11,7 @@
 #include "GraphPaths.h"
 #include "AIPathfinderConfig.h"
 #include "../../../lib/CRandomGenerator.h"
-#include "../../../CCallback.h"
+#include "../../../lib/mapObjects/CQuest.h"
 #include "../../../lib/mapping/CMap.h"
 #include "../Engine/Nullkiller.h"
 #include "../../../lib/logging/VisualLogger.h"
@@ -58,7 +58,7 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil
 	graph.copyFrom(*ai->baseGraph);
 	graph.connectHeroes(ai);
 
-	visualKey = std::to_string(ai->playerID) + ":" + targetHero->getNameTranslated();
+	visualKey = std::to_string(ai->playerID.getNum()) + ":" + targetHero->getNameTranslated();
 	pathNodes.clear();
 
 	GraphNodeComparer cmp(pathNodes);
@@ -82,11 +82,11 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil
 				|| node.obj->ID == Obj::BORDER_GATE)
 			{
 				auto questObj = dynamic_cast<const IQuestObject *>(node.obj);
-				auto questInfo = QuestInfo(questObj->quest, node.obj, pos.coord);
+				auto questInfo = QuestInfo(node.obj->id);
 
 				if(node.obj->ID == Obj::QUEST_GUARD
-					&& questObj->quest->mission == Rewardable::Limiter{}
-					&& questObj->quest->killTarget == ObjectInstanceID::NONE)
+					&& questObj->getQuest().mission == Rewardable::Limiter{}
+					&& questObj->getQuest().killTarget == ObjectInstanceID::NONE)
 				{
 					continue;
 				}

+ 0 - 1
AI/Nullkiller/Pathfinding/ObjectGraph.cpp

@@ -12,7 +12,6 @@
 #include "ObjectGraphCalculator.h"
 #include "AIPathfinderConfig.h"
 #include "../../../lib/CRandomGenerator.h"
-#include "../../../CCallback.h"
 #include "../../../lib/mapping/CMap.h"
 #include "../Engine/Nullkiller.h"
 #include "../../../lib/logging/VisualLogger.h"

+ 9 - 10
AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp

@@ -10,8 +10,7 @@
 #include "StdInc.h"
 #include "ObjectGraphCalculator.h"
 #include "AIPathfinderConfig.h"
-#include "../../../lib/CRandomGenerator.h"
-#include "../../../CCallback.h"
+#include "../../../lib/callback/GameRandomizer.h"
 #include "../../../lib/mapping/CMap.h"
 #include "../Engine/Nullkiller.h"
 #include "../../../lib/logging/VisualLogger.h"
@@ -288,17 +287,17 @@ void ObjectGraphCalculator::addObjectActor(const CGObjectInstance * obj)
 {
 	auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(obj->cb)).get();
 
-	CRandomGenerator rng;
+	GameRandomizer randomizer(*obj->cb);
 	auto visitablePos = obj->visitablePos();
 
 	objectActor->setOwner(ai->playerID); // lets avoid having multiple colors
-	objectActor->initHero(rng, static_cast<HeroTypeID>(0));
+	objectActor->initHero(randomizer, static_cast<HeroTypeID>(0));
 	objectActor->pos = objectActor->convertFromVisitablePos(visitablePos);
-	objectActor->initObj(rng);
+	objectActor->initObj(randomizer);
 
 	if(cb->getTile(visitablePos)->isWater())
 	{
-		objectActor->boat = temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get();
+		objectActor->setBoat(temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get());
 	}
 
 	assert(objectActor->visitablePos() == visitablePos);
@@ -326,16 +325,16 @@ void ObjectGraphCalculator::addJunctionActor(const int3 & visitablePos, bool isV
 	auto internalCb = temporaryActorHeroes.front()->cb;
 	auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(internalCb)).get();
 
-	CRandomGenerator rng;
+	GameRandomizer randomizer(*internalCb);
 
 	objectActor->setOwner(ai->playerID); // lets avoid having multiple colors
-	objectActor->initHero(rng, static_cast<HeroTypeID>(0));
+	objectActor->initHero(randomizer, static_cast<HeroTypeID>(0));
 	objectActor->pos = objectActor->convertFromVisitablePos(visitablePos);
-	objectActor->initObj(rng);
+	objectActor->initObj(randomizer);
 
 	if(isVirtualBoat || ai->cb->getTile(visitablePos)->isWater())
 	{
-		objectActor->boat = temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get();
+		objectActor->setBoat(temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get());
 	}
 
 	assert(objectActor->visitablePos() == visitablePos);

+ 28 - 15
AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp

@@ -12,6 +12,8 @@
 #include "../../Engine/Nullkiller.h"
 #include "../../../../lib/pathfinder/CPathfinder.h"
 #include "../../../../lib/pathfinder/TurnInfo.h"
+#include "../../../../lib/spells/ISpellMechanics.h"
+#include "../../../../lib/spells/adventure/SummonBoatEffect.h"
 
 namespace NKAI
 {
@@ -109,19 +111,22 @@ namespace AIPathfinding
 
 	void AILayerTransitionRule::setup()
 	{
-		SpellID waterWalk = SpellID::WATER_WALK;
-		SpellID airWalk = SpellID::FLY;
-
 		for(const CGHeroInstance * hero : nodeStorage->getAllHeroes())
 		{
-			if(hero->canCastThisSpell(waterWalk.toSpell()) && hero->mana >= hero->getSpellCost(waterWalk.toSpell()))
+			for (const auto & spell : LIBRARY->spellh->objects)
 			{
-				waterWalkingActions[hero] = std::make_shared<WaterWalkingAction>(hero);
-			}
+				if (!spell || !spell->isAdventure())
+					continue;
 
-			if(hero->canCastThisSpell(airWalk.toSpell()) && hero->mana >= hero->getSpellCost(airWalk.toSpell()))
-			{
-				airWalkingActions[hero] = std::make_shared<AirWalkingAction>(hero);
+				if(spell->getAdventureMechanics().givesBonus(hero, BonusType::WATER_WALKING) && hero->canCastThisSpell(spell.get()) && hero->mana >= hero->getSpellCost(spell.get()))
+				{
+					waterWalkingActions[hero] = std::make_shared<WaterWalkingAction>(hero, spell->id);
+				}
+
+				if(spell->getAdventureMechanics().givesBonus(hero, BonusType::FLYING_MOVEMENT) && hero->canCastThisSpell(spell.get()) && hero->mana >= hero->getSpellCost(spell.get()))
+				{
+					airWalkingActions[hero] = std::make_shared<AirWalkingAction>(hero, spell->id);
+				}
 			}
 		}
 
@@ -159,13 +164,21 @@ namespace AIPathfinding
 
 		for(const CGHeroInstance * hero : nodeStorage->getAllHeroes())
 		{
-			auto summonBoatSpell = SpellID(SpellID::SUMMON_BOAT).toSpell();
-
-			if(hero->canCastThisSpell(summonBoatSpell)
-				&& hero->getSpellSchoolLevel(summonBoatSpell) >= MasteryLevel::ADVANCED)
+			for (const auto & spell : LIBRARY->spellh->objects)
 			{
-				// TODO: For lower school level we might need to check the existence of some boat
-				summonableVirtualBoats[hero] = std::make_shared<SummonBoatAction>();
+				if (!spell || !spell->isAdventure())
+					continue;
+
+				auto effect = spell->getAdventureMechanics().getEffectAs<SummonBoatEffect>(hero);
+
+				if (!effect || !hero->canCastThisSpell(spell.get()))
+					continue;
+
+				if (effect->canCreateNewBoat() && effect->getSuccessChance(hero) == 100)
+				{
+					// TODO: For lower school level we might need to check the existence of some boat
+					summonableVirtualBoats[hero] = std::make_shared<SummonBoatAction>(spell->id);
+				}
 			}
 		}
 	}

+ 0 - 1
AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h

@@ -14,7 +14,6 @@
 #include "../../AIGateway.h"
 #include "../Actions/BoatActions.h"
 #include "../Actions/AdventureSpellCastMovementActions.h"
-#include "../../../../CCallback.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
 #include "../../../../lib/pathfinder/PathfindingRules.h"
 

+ 4 - 3
AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp

@@ -14,6 +14,7 @@
 #include "../Actions/WhirlpoolAction.h"
 #include "../../Goals/Invalid.h"
 #include "AIPreviousNodeRule.h"
+#include "../../../../lib/mapObjects/CQuest.h"
 #include "../../../../lib/pathfinder/PathfinderOptions.h"
 #include "../../../../lib/pathfinder/CPathfinder.h"
 
@@ -165,12 +166,12 @@ namespace AIPathfinding
 	{
 		const AIPathNode * destinationNode = nodeStorage->getAINode(destination.node);
 		auto questObj = dynamic_cast<const IQuestObject *>(destination.nodeObject);
-		auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord);
+		auto questInfo = QuestInfo(destination.nodeObject->id);
 		QuestAction questAction(questInfo);
 
 		if(destination.nodeObject->ID == Obj::QUEST_GUARD
-		   && questObj->quest->mission == Rewardable::Limiter{}
-		   && questObj->quest->killTarget == ObjectInstanceID::NONE)
+		   && questObj->getQuest().mission == Rewardable::Limiter{}
+		   && questObj->getQuest().killTarget == ObjectInstanceID::NONE)
 		{
 			return false;
 		}

+ 0 - 1
AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h

@@ -12,7 +12,6 @@
 
 #include "../AINodeStorage.h"
 #include "../../AIGateway.h"
-#include "../../../../CCallback.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
 #include "../../../../lib/pathfinder/PathfindingRules.h"
 

+ 0 - 1
AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h

@@ -12,7 +12,6 @@
 
 #include "../AINodeStorage.h"
 #include "../../AIGateway.h"
-#include "../../../../CCallback.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
 #include "../../../../lib/pathfinder/PathfindingRules.h"
 

+ 0 - 1
AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.h

@@ -12,7 +12,6 @@
 
 #include "../AINodeStorage.h"
 #include "../../AIGateway.h"
-#include "../../../../CCallback.h"
 #include "../../../../lib/mapObjects/MapObjects.h"
 #include "../../../../lib/pathfinder/PathfindingRules.h"
 

+ 0 - 1
AI/Nullkiller/StdInc.h

@@ -10,4 +10,3 @@
 #pragma  once
 #include "../../Global.h"
 VCMI_LIB_USING_NAMESPACE
-#include "../../CCallback.h"

+ 2 - 6
AI/StupidAI/StupidAI.cpp

@@ -8,19 +8,18 @@
  *
  */
 #include "StdInc.h"
-#include "../../lib/AI_Base.h"
 #include "StupidAI.h"
 #include "../../lib/CStack.h"
-#include "../../CCallback.h"
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/battle/BattleAction.h"
 #include "../../lib/battle/BattleInfo.h"
+#include "../../lib/battle/CPlayerBattleCallback.h"
+#include "../../lib/callback/CBattleCallback.h"
 #include "../../lib/CRandomGenerator.h"
 
 CStupidAI::CStupidAI()
 	: side(BattleSide::NONE)
 	, wasWaitingForRealize(false)
-	, wasUnlockingGs(false)
 {
 	print("created");
 }
@@ -32,7 +31,6 @@ CStupidAI::~CStupidAI()
 	{
 		//Restore previous state of CB - it may be shared with the main AI (like VCAI)
 		cb->waitTillRealize = wasWaitingForRealize;
-		cb->unlockGsWhenWaiting = wasUnlockingGs;
 	}
 }
 
@@ -43,9 +41,7 @@ void CStupidAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
 	cb = CB;
 
 	wasWaitingForRealize = CB->waitTillRealize;
-	wasUnlockingGs = CB->unlockGsWhenWaiting;
 	CB->waitTillRealize = false;
-	CB->unlockGsWhenWaiting = false;
 }
 
 void CStupidAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB, AutocombatPreferences autocombatPreferences)

+ 1 - 2
AI/StupidAI/StupidAI.h

@@ -11,7 +11,7 @@
 
 #include "../../lib/battle/BattleHex.h"
 #include "../../lib/battle/ReachabilityInfo.h"
-#include "../../lib/CGameInterface.h"
+#include "../../lib/callback/CBattleGameInterface.h"
 
 class EnemyInfo;
 
@@ -22,7 +22,6 @@ class CStupidAI : public CBattleGameInterface
 	std::shared_ptr<Environment> env;
 
 	bool wasWaitingForRealize;
-	bool wasUnlockingGs;
 
 	void print(const std::string &text) const;
 public:

+ 0 - 1
AI/StupidAI/main.cpp

@@ -9,7 +9,6 @@
  */
 #include "StdInc.h"
 
-#include "../../lib/AI_Base.h"
 #include "StupidAI.h"
 
 #ifdef __GNUC__

+ 10 - 4
AI/VCAI/AIUtility.cpp

@@ -15,9 +15,10 @@
 
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/CConfigHandler.h"
+#include "../../lib/entities/artifact/CArtifact.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/mapObjects/CQuest.h"
-#include "../../lib/mapping/CMapDefines.h"
+#include "../../lib/mapping/TerrainTile.h"
 
 extern FuzzyHelper * fh;
 
@@ -192,7 +193,7 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
 	}
 	else if(!fromWater) // do not try to board when in water sector
 	{
-		if(t->visitableObjects.size() == 1 && t->topVisitableId() == Obj::BOAT)
+		if(t->visitableObjects.size() == 1 && cb->getObjInstance(t->topVisitableObj())->ID == Obj::BOAT)
 			return true;
 	}
 	return false;
@@ -200,9 +201,14 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
 
 bool isBlockedBorderGate(int3 tileToHit) //TODO: is that function needed? should be handled by pathfinder
 {
-	if(cb->getTile(tileToHit)->topVisitableId() != Obj::BORDER_GATE)
+	const auto * object = cb->getTopObj(tileToHit);
+	if(!object)
 		return false;
-	auto gate = dynamic_cast<const CGKeys *>(cb->getTile(tileToHit)->topVisitableObj());
+
+	if(object->ID != Obj::BORDER_GATE)
+		return false;
+
+	auto gate = dynamic_cast<const CGKeys *>(object);
 	return !gate->passableFor(ai->playerID);
 }
 

+ 1 - 2
AI/VCAI/AIUtility.h

@@ -14,10 +14,9 @@
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/CStopWatch.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
-#include "../../CCallback.h"
+#include "../../lib/callback/CCallback.h"
 
 class VCAI;
-class CCallback;
 struct creInfo;
 
 using crint3 = const int3 &;

+ 1 - 2
AI/VCAI/ArmyManager.cpp

@@ -11,7 +11,6 @@
 #include "StdInc.h"
 #include "ArmyManager.h"
 
-#include "../../CCallback.h"
 #include "../../lib/mapObjects/MapObjects.h"
 
 void ArmyManager::init(CPlayerSpecificInfoCallback * CB)
@@ -41,7 +40,7 @@ std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, c
 
 			slotInfp.creature = cre;
 			slotInfp.power += i.second->getPower();
-			slotInfp.count += i.second->count;
+			slotInfp.count += i.second->getCount();
 		}
 	}
 

+ 0 - 1
AI/VCAI/BuildingManager.cpp

@@ -11,7 +11,6 @@
 #include "StdInc.h"
 #include "BuildingManager.h"
 
-#include "../../CCallback.h"
 #include "../../lib/mapObjects/MapObjects.h"
 #include "../../lib/entities/building/CBuilding.h"
 

+ 1 - 1
AI/VCAI/FuzzyEngines.cpp

@@ -60,7 +60,7 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
 	static const CSelector selectorSTACKS_SPEED = Selector::type()(BonusType::STACKS_SPEED);
 	static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)BonusType::STACKS_SPEED);
 
-	for(auto s : army->Slots())
+	for(const auto & s : army->Slots())
 	{
 		bool walker = true;
 		auto bearer = s.second->getType()->getBonusBearer();

+ 1 - 1
AI/VCAI/FuzzyHelper.cpp

@@ -11,11 +11,11 @@
 #include "FuzzyHelper.h"
 
 #include "Goals/Goals.h"
+#include "Goals/CompleteQuest.h"
 #include "VCAI.h"
 
 #include "../../lib/mapObjectConstructors/AObjectTypeHandler.h"
 #include "../../lib/mapObjectConstructors/CObjectClassesHandler.h"
-#include "../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
 #include "../../lib/mapObjects/CGCreature.h"
 #include "../../lib/mapObjects/CGDwelling.h"
 #include "../../lib/gameState/InfoAboutArmy.h"

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

@@ -15,6 +15,7 @@
 #include "../ResourceManager.h"
 #include "../BuildingManager.h"
 #include "../../../lib/constants/StringConstants.h"
+#include "../../../lib/entities/artifact/CArtifact.h"
 
 using namespace Goals;
 
@@ -85,7 +86,7 @@ std::string AbstractGoal::name() const //TODO: virtualize
 	}
 	break;
 	case GET_ART_TYPE:
-		desc = "GET ARTIFACT OF TYPE " + LIBRARY->artifacts()->getByIndex(aid)->getNameTranslated();
+		desc = "GET ARTIFACT OF TYPE " + ArtifactID(aid).toEntity(LIBRARY)->getNameTranslated();
 		break;
 	case VISIT_TILE:
 		desc = "VISIT TILE " + tile.toString();

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

@@ -16,7 +16,6 @@
 struct HeroPtr;
 class VCAI;
 class FuzzyHelper;
-class CCallback;
 
 namespace Goals
 {

+ 10 - 4
AI/VCAI/Goals/AdventureSpellCast.cpp

@@ -13,6 +13,8 @@
 #include "../FuzzyHelper.h"
 #include "../AIhelper.h"
 #include "../../../lib/mapObjects/CGTownInstance.h"
+#include "../../../lib/spells/ISpellMechanics.h"
+#include "../../../lib/spells/adventure/TownPortalEffect.h"
 
 using namespace Goals;
 
@@ -39,15 +41,19 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve()
 	if(hero->mana < hero->getSpellCost(spell))
 		throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated());
 
-	if(spellID == SpellID::TOWN_PORTAL && town && town->visitingHero)
-		throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated());
+	auto townPortalEffect = spell->getAdventureMechanics().getEffectAs<TownPortalEffect>(hero.h);
+
+	if(townPortalEffect && town && town->getVisitingHero())
+		throw cannotFulfillGoalException("The town is already occupied by " + town->getVisitingHero()->getNameTranslated());
 
 	return iAmElementar();
 }
 
 void AdventureSpellCast::accept(VCAI * ai)
 {
-	if(town && spellID == SpellID::TOWN_PORTAL)
+	auto townPortalEffect = spellID.toSpell()->getAdventureMechanics().getEffectAs<TownPortalEffect>(hero.h);
+
+	if(town && townPortalEffect)
 	{
 		ai->selectedObject = town->id;
 	}
@@ -57,7 +63,7 @@ void AdventureSpellCast::accept(VCAI * ai)
 	cb->waitTillRealize = true;
 	cb->castSpell(hero.h, spellID, tile);
 
-	if(town && spellID == SpellID::TOWN_PORTAL)
+	if(town && townPortalEffect)
 	{
 		// visit town
 		ai->moveHeroToTile(town->visitablePos(), hero);

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

@@ -36,7 +36,7 @@ TGoalVec Build::getAllPossibleSubgoals()
 		if(!t->hasBuilt(ai->ah->getMaxPossibleGoldBuilding(t)) && expensiveBuilding.has_value())
 		{
 			auto potentialBuilding = expensiveBuilding.value();
-			switch(expensiveBuilding.value().bid)
+			switch(expensiveBuilding.value().bid.toEnum())
 			{
 			case BuildingID::TOWN_HALL:
 			case BuildingID::CITY_HALL:

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

@@ -32,7 +32,7 @@ TSubgoal BuildThis::whatToDoToAchieve()
 
 	// find town if not set
 	if(!town && hero)
-		town = hero->visitedTown;
+		town = hero->getVisitedTown();
 
 	if(!town)
 	{

+ 2 - 2
AI/VCAI/Goals/BuildThis.h

@@ -27,14 +27,14 @@ namespace Goals
 		BuildThis(BuildingID Bid, const CGTownInstance * tid)
 			: CGoal(Goals::BUILD_STRUCTURE)
 		{
-			bid = Bid;
+			bid = Bid.getNum();
 			town = tid;
 			priority = 1;
 		}
 		BuildThis(BuildingID Bid)
 			: CGoal(Goals::BUILD_STRUCTURE)
 		{
-			bid = Bid;
+			bid = Bid.getNum();
 			priority = 1;
 		}
 		TGoalVec getAllPossibleSubgoals() override

+ 4 - 4
AI/VCAI/Goals/CollectRes.cpp

@@ -41,7 +41,7 @@ TGoalVec CollectRes::getAllPossibleSubgoals()
 		switch (obj->ID.num)
 		{
 		case Obj::TREASURE_CHEST:
-			return resID == GameResID(EGameResID::GOLD);
+			return resID == GameResID(EGameResID::GOLD).getNum();
 			break;
 		case Obj::RESOURCE:
 			return dynamic_cast<const CGResource*>(obj)->resourceID() == GameResID(resID);
@@ -62,13 +62,13 @@ TGoalVec CollectRes::getAllPossibleSubgoals()
 			}
 			break;
 		case Obj::MYSTICAL_GARDEN:
-			if ((resID != GameResID(EGameResID::GOLD)) && (resID != GameResID(EGameResID::GEMS)))
+			if (resID != GameResID(EGameResID::GOLD).getNum() && resID != GameResID(EGameResID::GEMS).getNum())
 				return false;
 			break;
 		case Obj::WATER_WHEEL:
 		case Obj::LEAN_TO:
 		case Obj::WAGON:
-			if (resID != GameResID(EGameResID::GOLD))
+			if (resID != GameResID(EGameResID::GOLD).getNum())
 				return false;
 			break;
 		default:
@@ -177,7 +177,7 @@ TSubgoal CollectRes::whatToDoToTrade()
 				continue;
 			int toGive = -1;
 			int toReceive = -1;
-			m->getOffer(i, resID, toGive, toReceive, EMarketMode::RESOURCE_RESOURCE);
+			m->getOffer(i.getNum(), resID, toGive, toReceive, EMarketMode::RESOURCE_RESOURCE);
 			assert(toGive > 0 && toReceive > 0);
 			howManyCanWeBuy += toReceive * (ai->ah->freeResources()[i] / toGive);
 		}

+ 42 - 30
AI/VCAI/Goals/CompleteQuest.cpp

@@ -8,7 +8,15 @@
 *
 */
 #include "StdInc.h"
-#include "Goals.h"
+#include "CompleteQuest.h"
+
+#include "CollectRes.h"
+#include "FindObj.h"
+#include "GatherArmy.h"
+#include "GatherTroops.h"
+#include "GetArtOfType.h"
+#include "RecruitHero.h"
+
 #include "../VCAI.h"
 #include "../FuzzyHelper.h"
 #include "../AIhelper.h"
@@ -18,45 +26,47 @@ using namespace Goals;
 
 bool CompleteQuest::operator==(const CompleteQuest & other) const
 {
-	return q.quest->qid == other.q.quest->qid;
+	return q.getQuest(cb) == other.q.getQuest(cb);
 }
 
 bool isKeyMaster(const QuestInfo & q)
 {
-	return q.obj && (q.obj->ID == Obj::BORDER_GATE || q.obj->ID == Obj::BORDERGUARD);
+	auto object = q.getObject(cb);
+	return object && (object->ID == Obj::BORDER_GATE || object->ID == Obj::BORDERGUARD);
 }
 
 TGoalVec CompleteQuest::getAllPossibleSubgoals()
 {
 	TGoalVec solutions;
+	auto quest = q.getQuest(cb);
 
-	if(!q.quest->isCompleted)
+	if(!quest->isCompleted)
 	{
 		logAi->debug("Trying to realize quest: %s", questToString());
 		
 		if(isKeyMaster(q))
 			return missionKeymaster();
 
-		if(!q.quest->mission.artifacts.empty())
+		if(!quest->mission.artifacts.empty())
 			return missionArt();
 
-		if(!q.quest->mission.heroes.empty())
+		if(!quest->mission.heroes.empty())
 			return missionHero();
 
-		if(!q.quest->mission.creatures.empty())
+		if(!quest->mission.creatures.empty())
 			return missionArmy();
 
-		if(q.quest->mission.resources.nonZero())
+		if(quest->mission.resources.nonZero())
 			return missionResources();
 
-		if(q.quest->killTarget != ObjectInstanceID::NONE)
+		if(quest->killTarget != ObjectInstanceID::NONE)
 			return missionDestroyObj();
 
-		for(auto & s : q.quest->mission.primary)
+		for(auto & s : quest->mission.primary)
 			if(s)
 				return missionIncreasePrimaryStat();
 
-		if(q.quest->mission.heroLevel > 0)
+		if(quest->mission.heroLevel > 0)
 			return missionLevel();
 	}
 
@@ -65,7 +75,7 @@ TGoalVec CompleteQuest::getAllPossibleSubgoals()
 
 TSubgoal CompleteQuest::whatToDoToAchieve()
 {
-	if(q.quest->mission == Rewardable::Limiter{})
+	if(q.getQuest(cb)->mission == Rewardable::Limiter{})
 	{
 		throw cannotFulfillGoalException("Can not complete inactive quest");
 	}
@@ -99,11 +109,13 @@ std::string CompleteQuest::completeMessage() const
 
 std::string CompleteQuest::questToString() const
 {
-	if(q.quest->questName == CQuest::missionName(EQuestMission::NONE))
+	auto quest = q.getQuest(cb);
+
+	if(quest->questName == CQuest::missionName(EQuestMission::NONE))
 		return "inactive quest";
 
 	MetaString ms;
-	q.quest->getRolloverText(q.obj->cb, ms, false);
+	quest->getRolloverText(cb, ms, false);
 
 	return ms.toString();
 }
@@ -116,9 +128,9 @@ TGoalVec CompleteQuest::tryCompleteQuest() const
 
 	for(auto hero : heroes)
 	{
-		if(q.quest->checkQuest(hero))
+		if(q.getQuest(cb)->checkQuest(hero))
 		{
-			vstd::concatenate(solutions, ai->ah->howToVisitObj(hero, ObjectIdRef(q.obj->id)));
+			vstd::concatenate(solutions, ai->ah->howToVisitObj(hero, ObjectIdRef(q.getObject(cb)->id)));
 		}
 	}
 
@@ -132,9 +144,9 @@ TGoalVec CompleteQuest::missionArt() const
 	if(!solutions.empty())
 		return solutions;
 
-	for(auto art : q.quest->mission.artifacts)
+	for(auto art : q.getQuest(cb)->mission.artifacts)
 	{
-		solutions.push_back(sptr(GetArtOfType(art))); //TODO: transport?
+		solutions.push_back(sptr(GetArtOfType(art.getNum()))); //TODO: transport?
 	}
 
 	return solutions;
@@ -160,9 +172,9 @@ TGoalVec CompleteQuest::missionArmy() const
 	if(!solutions.empty())
 		return solutions;
 
-	for(auto creature : q.quest->mission.creatures)
+	for(auto creature : q.getQuest(cb)->mission.creatures)
 	{
-		solutions.push_back(sptr(GatherTroops(creature.getId(), creature.count)));
+		solutions.push_back(sptr(GatherTroops(creature.getId().getNum(), creature.getCount())));
 	}
 
 	return solutions;
@@ -174,7 +186,7 @@ TGoalVec CompleteQuest::missionIncreasePrimaryStat() const
 
 	if(solutions.empty())
 	{
-		for(int i = 0; i < q.quest->mission.primary.size(); ++i)
+		for(int i = 0; i < q.getQuest(cb)->mission.primary.size(); ++i)
 		{
 			// TODO: library, school and other boost objects
 			logAi->debug("Don't know how to increase primary stat %d", i);
@@ -190,7 +202,7 @@ TGoalVec CompleteQuest::missionLevel() const
 
 	if(solutions.empty())
 	{
-		logAi->debug("Don't know how to reach hero level %d", q.quest->mission.heroLevel);
+		logAi->debug("Don't know how to reach hero level %d", q.getQuest(cb)->mission.heroLevel);
 	}
 
 	return solutions;
@@ -202,7 +214,7 @@ TGoalVec CompleteQuest::missionKeymaster() const
 
 	if(solutions.empty())
 	{
-		solutions.push_back(sptr(Goals::FindObj(Obj::KEYMASTER, q.obj->subID)));
+		solutions.push_back(sptr(Goals::FindObj(Obj::KEYMASTER, q.getObject(cb)->subID)));
 	}
 
 	return solutions;
@@ -216,16 +228,16 @@ TGoalVec CompleteQuest::missionResources() const
 
 	if(heroes.size())
 	{
-		if(q.quest->checkQuest(heroes.front())) //it doesn't matter which hero it is
+		if(q.getQuest(cb)->checkQuest(heroes.front())) //it doesn't matter which hero it is
 		{
-			return ai->ah->howToVisitObj(q.obj);
+			return ai->ah->howToVisitObj(q.getObject(cb));
 		}
 		else
 		{
-			for(int i = 0; i < q.quest->mission.resources.size(); ++i)
+			for(int i = 0; i < q.getQuest(cb)->mission.resources.size(); ++i)
 			{
-				if(q.quest->mission.resources[i])
-					solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.quest->mission.resources[i])));
+				if(q.getQuest(cb)->mission.resources[i])
+					solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.getQuest(cb)->mission.resources[i])));
 			}
 		}
 	}
@@ -241,10 +253,10 @@ TGoalVec CompleteQuest::missionDestroyObj() const
 {
 	TGoalVec solutions;
 
-	auto obj = cb->getObj(q.quest->killTarget);
+	auto obj = cb->getObj(q.getQuest(cb)->killTarget);
 
 	if(!obj)
-		return ai->ah->howToVisitObj(q.obj);
+		return ai->ah->howToVisitObj(q.getObject(cb));
 
 	if(obj->ID == Obj::HERO)
 	{

+ 0 - 1
AI/VCAI/Goals/CompleteQuest.h

@@ -10,7 +10,6 @@
 #pragma once
 
 #include "CGoal.h"
-#include "../../../lib/GameLibrary.h"
 #include "../../../lib/gameState/QuestInfo.h"
 
 namespace Goals

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

@@ -56,14 +56,14 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
 		if(waysToVisit.size())
 		{
 			//grab army from town
-			if(!t->visitingHero && ai->ah->howManyReinforcementsCanGet(hero.get(), t))
+			if(!t->getVisitingHero() && ai->ah->howManyReinforcementsCanGet(hero.get(), t))
 			{
 				if(!vstd::contains(ai->townVisitsThisWeek[hero], t))
 					vstd::concatenate(ret, waysToVisit);
 			}
 
 			//buy army in town
-			if (!t->visitingHero || t->visitingHero == hero.get(true))
+			if (!t->getVisitingHero() || t->getVisitingHero() == hero.get(true))
 			{
 				std::vector<int> values = {
 					value,

部分文件因文件數量過多而無法顯示