瀏覽代碼

Merge branch 'develop' into max_range_spell

Laserlicht 3 月之前
父節點
當前提交
ca8cfaf0ad
共有 100 個文件被更改,包括 2152 次插入1570 次删除
  1. 1 0
      AI/Nullkiller/AIGateway.cpp
  2. 1 1
      AI/Nullkiller/AIUtility.cpp
  3. 1 1
      AI/Nullkiller/Analyzers/ArmyManager.cpp
  4. 1 1
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  5. 1 1
      AI/Nullkiller/Pathfinding/Actors.cpp
  6. 1 1
      AI/VCAI/AIUtility.cpp
  7. 2 1
      AI/VCAI/Pathfinding/AIPathfinder.cpp
  8. 0 1
      AI/VCAI/Pathfinding/PathfindingManager.cpp
  9. 1 0
      AI/VCAI/VCAI.cpp
  10. 41 1
      ChangeLog.md
  11. 1 0
      client/CPlayerInterface.cpp
  12. 1 0
      client/CServerHandler.cpp
  13. 2 1
      client/Client.cpp
  14. 1 1
      client/GameChatHandler.cpp
  15. 1 0
      client/HeroMovementController.cpp
  16. 0 1
      client/UIHelper.cpp
  17. 1 1
      client/adventureMap/AdventureMapInterface.cpp
  18. 1 1
      client/adventureMap/CInGameConsole.cpp
  19. 1 1
      client/adventureMap/CMinimap.cpp
  20. 0 1
      client/adventureMap/MapAudioPlayer.cpp
  21. 17 16
      client/adventureMap/TurnTimerWidget.cpp
  22. 50 24
      client/battle/BattleWindow.cpp
  23. 4 0
      client/battle/BattleWindow.h
  24. 1 1
      client/battle/HeroInfoWindow.cpp
  25. 2 2
      client/battle/StackInfoBasicPanel.cpp
  26. 5 2
      client/lobby/CLobbyScreen.cpp
  27. 40 6
      client/mainmenu/CHighScoreScreen.cpp
  28. 3 0
      client/mainmenu/CHighScoreScreen.h
  29. 7 2
      client/mainmenu/CMainMenu.cpp
  30. 1 1
      client/mapView/MapRenderer.cpp
  31. 23 22
      client/render/AssetGenerator.cpp
  32. 34 20
      client/widgets/CExchangeController.cpp
  33. 69 18
      config/commanders.json
  34. 1 1
      config/creatures/neutral.json
  35. 1 0
      config/factions/tower.json
  36. 5 5
      config/mainmenu.json
  37. 11 1
      config/schemas/settings.json
  38. 6 0
      config/widgets/battleWindow2.json
  39. 10 15
      docs/images/Bonus_System_Nodes.gv
  40. 348 342
      docs/images/Bonus_System_Nodes.svg
  41. 6 0
      docs/modders/Bonus/Bonus_Types.md
  42. 25 10
      lib/CCreatureHandler.cpp
  43. 0 356
      lib/CCreatureSet.h
  44. 30 7
      lib/CMakeLists.txt
  45. 1 1
      lib/battle/BattleLayout.cpp
  46. 0 1
      lib/battle/BattleStateInfoForRetreat.cpp
  47. 1 0
      lib/battle/DamageCalculator.cpp
  48. 4 0
      lib/battle/SideInBattle.cpp
  49. 4 0
      lib/battle/SideInBattle.h
  50. 0 1
      lib/bonuses/Bonus.cpp
  51. 1 0
      lib/bonuses/BonusEnum.h
  52. 0 1
      lib/bonuses/Limiters.cpp
  53. 1 0
      lib/gameState/CGameState.cpp
  54. 2 0
      lib/gameState/EVictoryLossCheckResult.h
  55. 39 10
      lib/gameState/GameStatePackVisitor.cpp
  56. 75 0
      lib/gameState/GameStatistics.cpp
  57. 6 0
      lib/gameState/GameStatistics.h
  58. 1 1
      lib/gameState/InfoAboutArmy.h
  59. 1 1
      lib/json/JsonRandom.cpp
  60. 13 1
      lib/mapObjectConstructors/CObjectClassesHandler.cpp
  61. 1 1
      lib/mapObjectConstructors/CommonConstructors.cpp
  62. 1 0
      lib/mapObjectConstructors/DwellingInstanceConstructor.cpp
  63. 1 1
      lib/mapObjects/CGCreature.h
  64. 2 1
      lib/mapObjects/CGDwelling.h
  65. 1 4
      lib/mapObjects/CGHeroInstance.cpp
  66. 3 1
      lib/mapObjects/CGHeroInstance.h
  67. 8 1
      lib/mapObjects/CGPandoraBox.cpp
  68. 10 5
      lib/mapObjects/CGPandoraBox.h
  69. 1 1
      lib/mapObjects/CGResource.h
  70. 1 0
      lib/mapObjects/CGTownInstance.cpp
  71. 1 1
      lib/mapObjects/CGTownInstance.h
  72. 2 1
      lib/mapObjects/CRewardableObject.h
  73. 1 0
      lib/mapObjects/IObjectInterface.cpp
  74. 0 1
      lib/mapObjects/MapObjects.h
  75. 2 1
      lib/mapObjects/MiscObjects.h
  76. 27 30
      lib/mapObjects/army/CArmedInstance.cpp
  77. 26 15
      lib/mapObjects/army/CArmedInstance.h
  78. 80 0
      lib/mapObjects/army/CCommanderInstance.cpp
  79. 55 0
      lib/mapObjects/army/CCommanderInstance.h
  80. 43 544
      lib/mapObjects/army/CCreatureSet.cpp
  81. 160 0
      lib/mapObjects/army/CCreatureSet.h
  82. 54 0
      lib/mapObjects/army/CSimpleArmy.h
  83. 91 0
      lib/mapObjects/army/CStackBasicDescriptor.cpp
  84. 65 0
      lib/mapObjects/army/CStackBasicDescriptor.h
  85. 358 0
      lib/mapObjects/army/CStackInstance.cpp
  86. 134 0
      lib/mapObjects/army/CStackInstance.h
  87. 37 0
      lib/mapping/CCastleEvent.h
  88. 1 0
      lib/mapping/CMap.cpp
  89. 2 1
      lib/mapping/CMap.h
  90. 59 0
      lib/mapping/CMapEvent.h
  91. 1 0
      lib/mapping/MapFormatH3M.cpp
  92. 5 70
      lib/mapping/TerrainTile.h
  93. 2 2
      lib/networkPacks/PacksForClient.h
  94. 1 0
      lib/networkPacks/PacksForClientBattle.h
  95. 1 1
      lib/pathfinder/CGPathNode.cpp
  96. 1 1
      lib/pathfinder/PathfinderUtil.h
  97. 1 1
      lib/pathfinder/PathfindingRules.cpp
  98. 3 2
      lib/rewardable/Info.cpp
  99. 1 1
      lib/rewardable/Interface.cpp
  100. 2 0
      lib/rewardable/Interface.h

+ 1 - 0
AI/Nullkiller/AIGateway.cpp

@@ -19,6 +19,7 @@
 #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"

+ 1 - 1
AI/Nullkiller/AIUtility.cpp

@@ -17,7 +17,7 @@
 #include "../../lib/entities/artifact/CArtifact.h"
 #include "../../lib/mapObjects/MapObjects.h"
 #include "../../lib/mapObjects/CQuest.h"
-#include "../../lib/mapping/CMapDefines.h"
+#include "../../lib/mapping/TerrainTile.h"
 #include "../../lib/gameState/QuestInfo.h"
 #include "../../lib/IGameSettings.h"
 #include "../../lib/bonuses/Limiters.h"

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

@@ -12,7 +12,7 @@
 #include "ArmyManager.h"
 #include "../Engine/Nullkiller.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"

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

@@ -15,7 +15,7 @@
 #include "../../../lib/mapObjectConstructors/AObjectTypeHandler.h"
 #include "../../../lib/mapObjectConstructors/CObjectClassesHandler.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"

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

@@ -12,7 +12,7 @@
 #include "../AIGateway.h"
 #include "../Engine/Nullkiller.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"
 

+ 1 - 1
AI/VCAI/AIUtility.cpp

@@ -18,7 +18,7 @@
 #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;
 

+ 2 - 1
AI/VCAI/Pathfinding/AIPathfinder.cpp

@@ -10,7 +10,8 @@
 #include "StdInc.h"
 #include "AIPathfinder.h"
 #include "AIPathfinderConfig.h"
-#include "../../../lib/mapping/CMapDefines.h"
+
+#include "../../../lib/mapping/TerrainTile.h"
 
 #include <tbb/task_group.h>
 

+ 0 - 1
AI/VCAI/Pathfinding/PathfindingManager.cpp

@@ -14,7 +14,6 @@
 #include "../Goals/Goals.h"
 #include "../Goals/CompleteQuest.h"
 #include "../../../lib/gameState/QuestInfo.h"
-#include "../../../lib/mapping/CMapDefines.h"
 #include "../../../lib/mapObjects/CQuest.h"
 
 PathfindingManager::PathfindingManager(CPlayerSpecificInfoCallback * CB, VCAI * AI)

+ 1 - 0
AI/VCAI/VCAI.cpp

@@ -33,6 +33,7 @@
 #include "../../lib/entities/artifact/CArtifact.h"
 #include "../../lib/entities/building/CBuilding.h"
 #include "../../lib/mapObjects/CQuest.h"
+#include "../../lib/mapping/TerrainTile.h"
 #include "../../lib/networkPacks/PacksForClient.h"
 #include "../../lib/networkPacks/PacksForClientBattle.h"
 #include "../../lib/networkPacks/PacksForServer.h"

+ 41 - 1
ChangeLog.md

@@ -4,7 +4,7 @@
 
 ### General
 
-* Added support for loading h3m maps from HotA 1.7 (also needs support from HotA mod)
+* Added support for loading h3m maps and campaigns from HotA 1.7 (also needs support from HotA mod)
 * It is now possible to change default directory paths used by VCMI on Windows
 * Added option for VCMI to honor mute switch on iOS
 * Added alternative versions of cheats from RoE and AB editions
@@ -22,6 +22,8 @@
 
 ### Interface
 
+* Implemented unit action panel in combat for easy selection of unit's alternative actions
+* It is now possible to permanently open creature window of any unit using "show info" button from unit action panel
 * Fixed not functioning keybindings when non-lating keyboard layout is in use
 * Fixed bonuses from terrain (such as Holy Grounds) not showing up in unit window
 * Right-click tooltip on list of spells affecting unit in unit window will now show full spell description
@@ -39,6 +41,13 @@
 * Change scroll direction for horizontal slider when using mouse wheel to match HD mod
 * Game will now show correct visitation text for map dwellings from mods with 2 or 3 available creatures
 * Right-click tooltip on Refugee Camp will now show preview of available creatures if current player have visited it this week
+* Heroes on minimaps are now shown as icon, to help with readability on large maps or on small displays
+* Fixed movement of software cursor when touch input is in use, for example on mobile systems
+* Fixed positioning of GUI elements when improved hero exchange from vcmi extras mod is in use
+* Engaging into a combat with wandering monster will cause monster to show attack sprite on adventure map
+* Equipping or unequipping artifacts that affect unit growth will now instantly update town interface with new growth values
+* Implemented "Show Path" setting option from H3
+* Fixed hero path not updating instantly after using infinite movement cheat
 
 ### Mechanics
 
@@ -54,6 +63,9 @@
 * Attack skill provided by equipped artifact will now correctly modify damage range of Ballista
 * Lord Haart is now available instead of Sir Mullich on random maps, in line with H3
 * Hypnotized units with multi-target attacks such as Cerberi or Hydra will now attack all units that they view as enemy
+* Heroes with neutral units are now always subject to terrain penalties (unless hero has sufficiently advanced Pathfinding skill)
+* Commanders can now hold up to 9 artifacts
+* Familiar's Magic Channel ability will no longer work after Familiars are dead
 * Fixed functionality of Adela specialty
 * Fixed inability to use ranged attack if enemy clone that was blocking unit was killed in this round before
 * Fixed inability of creatures to cast spells when controlled by defending player in hotseat mode
@@ -73,6 +85,10 @@
 * Removed default limit of 16 total heroes per player
 * It is now possible to flee the combat while having shackles of war when other side got no hero
 * If hero has Necromancy and no available slot, game will now also consider upgrades of upgrades as potentially rised creature
+* It is no longer possible to summon boat on top of another boat with hero in it
+* Multi-creature dwellings like Elemental Conflux now correctly provide town growth bonus for every available creature
+* Changed formula of town arrow towers to be more in line with H3
+* During simultaneous turns, it is no longer possible to interact (for example, start battle) with player that is already engaged into a different combat
 
 ## AI
 
@@ -94,11 +110,19 @@
 
 * Added support for custom description and icons for creature abilities and artifacts
 * Added support for charged artifacts that can be used a limited number of times
+* Added basic support for addition of new bonuses via mods, for use in limiters
+* Added partial support for custom magic schools
 * It is now possible to have growing artifacts for heroes, not only for commanders
 * It is now possible to replace or append individual entries in json lists (`[ 1, 2, 3 ]`) without replacing entire list
 * It is now possible to specify both nominator and denominator (roll difficulty & dice size) for luck and morale probabilities
+* It is now possible to define hero secondary skill specialties in form `"secondary" : "archery"`
+* It is now possible to use limiters in unit stack experience bonuses
+* It is now possible to configure power of H3-like skill and creature specialties (5% / level) via game config
 * Added support for configuring icons for bonus icons in creature window per bonus subtype or per bonus value
 * Added BASE_TILE_MOVEMENT_COST bonus that allows configuring minimal cost for moving between tiles for heroes
+* TIMES_HERO_LEVEL updater now accepts stepSize parameter
+* Added TIMES_STACK_SIZE and TIMES_ARMY_SIZE updaters that multiplies bonus value by size of unit stack
+* GENERATE_RESOURCE bonus is now also checked in town, mine, and garrison scope
 * Added support for instance bonuses for artifacts that stack if multiple copies of the same artifacts are equipped on hero
 * Owner updater now correctly works with opposite side limiter when composite limiters `noneOf` or `anyOf` are used
 * Bonuses with terrain limiter will now correctly update on hero stepping onto different terrain
@@ -112,13 +136,28 @@
 * It is now possible to add additional campaign sets without causing mod conflicts
 * Game will now automatically generate campaign screen backgrounds, depending on number of campaigns in set
 * Creature type limiter will now correctly handle upgrades of upgrades
+* It is now possible to configure skills available in Magic University in Conflux or similar building from mods
 * ENCHANTER bonus will no longer cast mass spells by default. Spell would still be massive it is massive on specified school master level
+* CREATURE_UPGRADE bonus is now checked from unit scope and will only allow upgrades of this unit (unless propagated to hero)
 * Mod validation will now report map dwellings with invalid dimensions that were found in mods
+* Removed `height` property from town buildings. Towns now use SIGHT_RADIUS bonus to determine terrain reveal radius similar to heroes
 * Added "Campaigns" mod type
 * Added bonus MULTIHEX_UNIT_ATTACK - configurable version of Dragon Breath
 * Added bonus MULTIHEX_ENEMY_ATTACK - configurable version of Cerberi multi-headed attack that only hits enemies
 * Added bonus MULTIHEX_ANIMATION - optional bonus that does not affects gameplay, but allows to define in which cases game should use alternative attack animation
+* Added bonus STACK_EXPERIENCE_GAIN_PERCENT that affects amount of stack experience received by units after combat
 * Added HERO_SPELL_CASTS_PER_COMBAT_TURN bonus
+* Added MANA_PERCENTAGE_REGENERATION bonus that replaces less configurable FULL_MANA_REGENERATION bonus
+* Added VULNERABLE_FROM_BACK bonus that increases damage if unit with this bonus is attacked from behind (e.g. needs to reverse)
+* Added FEARFUL bonus that replaces less configurable FEAR and FEARLESS bonuses
+* Added FULL_MAP_DARKNESS bonus that covers with fow everything outside of sight radius of enemies
+* Added FULL_MAP_SCOUTING bonus for Skyship. Skyship now provides bonus instead of being weird case of lookout tower.
+* Added LIVING bonus that is automatically given to all creatures that don't have other creature nature bonus
+* Added SKELETON_TRANSFORMER_TARGET that indicates which creature this unit should be transformed into when placed into Skeleton Transformer
+* Added TRANSMUTATION_IMMUNITY bonus that prevents affected unit from TRANSMUTATION effects
+* Reworked DARKNESS bonus. Bonus now can be used on both towns and heroes, and only applies on start of turn
+* DRAGON_NATURE bonus will no longer affect result of Skeleton Transformer use
+* Removed SYNERGY_TARGET bonus
 * Added `playerGlobal` visit mode to configurable map objects. After visit of such object, all map objects of the same type are considered as visited by player
 * Added `forceCombat` property to configurable map objects. If such object is guarded, visiting it would immediately force combat without asking a player
 * Added `commanderBonuses` property to configurable map objects rewards that gives bonuses to hero commander (if exists)
@@ -134,6 +173,7 @@
 * Added `scrolls` property to configurable map objects limiter that requires hero to have scroll with specific spell
 * Added `availableSlots` property to configurable map objects limiter that requires hero to have specific artifact slots empty
 * It is now possible to give units to visiting hero using configurable town building, but only if hero can accept these units into his army
+* Added support for configuring positions of spells in mage guild window per town
 
 ### Map Editor
 

+ 1 - 0
client/CPlayerInterface.cpp

@@ -68,6 +68,7 @@
 
 #include "../lib/callback/CDynLibHandler.h"
 #include "../lib/CConfigHandler.h"
+#include "../lib/GameLibrary.h"
 #include "../lib/texts/CGeneralTextHandler.h"
 #include "../lib/CPlayerState.h"
 #include "../lib/CRandomGenerator.h"

+ 1 - 0
client/CServerHandler.cpp

@@ -32,6 +32,7 @@
 #include "mainmenu/CHighScoreScreen.h"
 
 #include "../lib/CConfigHandler.h"
+#include "../lib/GameLibrary.h"
 #include "../lib/texts/CGeneralTextHandler.h"
 #include "../lib/ConditionalWait.h"
 #include "../lib/CThreadHelper.h"

+ 2 - 1
client/Client.cpp

@@ -23,6 +23,7 @@
 #include "mapView/mapHandler.h"
 
 #include "../lib/CConfigHandler.h"
+#include "../lib/GameLibrary.h"
 #include "../lib/battle/BattleInfo.h"
 #include "../lib/battle/CPlayerBattleCallback.h"
 #include "../lib/callback/CCallback.h"
@@ -35,8 +36,8 @@
 #include "../lib/VCMIDirs.h"
 #include "../lib/UnlockGuard.h"
 #include "../lib/serializer/Connection.h"
+#include "../lib/mapObjects/army/CArmedInstance.h"
 #include "../lib/mapping/CMapService.h"
-#include "../lib/mapObjects/CArmedInstance.h"
 #include "../lib/pathfinder/CGPathNode.h"
 #include "../lib/filesystem/Filesystem.h"
 

+ 1 - 1
client/GameChatHandler.cpp

@@ -21,7 +21,7 @@
 
 #include "../lib/callback/CCallback.h"
 #include "../lib/networkPacks/PacksForLobby.h"
-#include "../lib/mapObjects/CArmedInstance.h"
+#include "../lib/mapObjects/army/CArmedInstance.h"
 #include "../lib/CConfigHandler.h"
 #include "../lib/GameLibrary.h"
 #include "../lib/texts/CGeneralTextHandler.h"

+ 1 - 0
client/HeroMovementController.cpp

@@ -26,6 +26,7 @@
 #include "../lib/callback/CCallback.h"
 #include "../lib/pathfinder/CGPathNode.h"
 #include "../lib/mapObjects/CGHeroInstance.h"
+#include "../lib/mapping/TerrainTile.h"
 #include "../lib/networkPacks/PacksForClient.h"
 #include "../lib/RoadHandler.h"
 #include "../lib/TerrainHandler.h"

+ 0 - 1
client/UIHelper.cpp

@@ -16,7 +16,6 @@
 #include "../lib/mapObjects/CGHeroInstance.h"
 #include "../lib/networkPacks/ArtifactLocation.h"
 #include "../lib/CRandomGenerator.h"
-#include "../lib/CCreatureSet.h"
 
 std::vector<Component> UIHelper::getArtifactsComponents(const CArtifactSet & artSet, const std::vector<MoveArtifactInfo> & movedPack)
 {

+ 1 - 1
client/adventureMap/AdventureMapInterface.cpp

@@ -37,6 +37,7 @@
 #include "../PlayerLocalState.h"
 #include "../CPlayerInterface.h"
 
+#include "../../lib/GameLibrary.h"
 #include "../../lib/IGameSettings.h"
 #include "../../lib/StartInfo.h"
 #include "../../lib/callback/CCallback.h"
@@ -44,7 +45,6 @@
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
-#include "../../lib/mapping/CMapDefines.h"
 #include "../../lib/pathfinder/CGPathNode.h"
 #include "../../lib/pathfinder/TurnInfo.h"
 #include "../../lib/spells/ISpellMechanics.h"

+ 1 - 1
client/adventureMap/CInGameConsole.cpp

@@ -29,7 +29,7 @@
 
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CThreadHelper.h"
-#include "../../lib/mapObjects/CArmedInstance.h"
+#include "../../lib/texts/MetaString.h"
 #include "../../lib/texts/TextOperations.h"
 
 CInGameConsole::CInGameConsole()

+ 1 - 1
client/adventureMap/CMinimap.cpp

@@ -30,7 +30,7 @@
 #include "../../lib/TerrainHandler.h"
 #include "../../lib/callback/CCallback.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
-#include "../../lib/mapping/CMapDefines.h"
+#include "../../lib/mapping/TerrainTile.h"
 #include "../../lib/texts/CGeneralTextHandler.h"
 
 ColorRGBA CMinimapInstance::getTileColor(const int3 & pos) const

+ 0 - 1
client/adventureMap/MapAudioPlayer.cpp

@@ -20,7 +20,6 @@
 #include "../../lib/CRandomGenerator.h"
 #include "../../lib/TerrainHandler.h"
 #include "../../lib/callback/CCallback.h"
-#include "../../lib/mapObjects/CArmedInstance.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapping/CMap.h"
 

+ 17 - 16
client/adventureMap/TurnTimerWidget.cpp

@@ -17,6 +17,8 @@
 #include "../GameInstance.h"
 #include "../media/ISoundPlayer.h"
 #include "../render/Graphics.h"
+#include "../render/IFont.h"
+#include "../render/IRenderHandler.h"
 #include "../widgets/Images.h"
 #include "../widgets/GraphicalPrimitiveCanvas.h"
 #include "../widgets/TextControls.h"
@@ -45,29 +47,28 @@ TurnTimerWidget::TurnTimerWidget(const Point & position, PlayerColor player)
 	const auto & timers = GAME->interface()->cb->getStartInfo()->turnTimerInfo;
 
 	backgroundTexture = std::make_shared<CFilledTexture>(ImagePath::builtin("DiBoxBck"), pos); // 1 px smaller on all sides
+	backgroundBorder = std::make_shared<TransparentFilledRectangle>(pos, ColorRGBA(0, 0, 0, 128), Colors::BRIGHT_YELLOW);
 
-	if (isBattleMode)
-		backgroundBorder = std::make_shared<TransparentFilledRectangle>(pos, ColorRGBA(0, 0, 0, 128), Colors::BRIGHT_YELLOW);
-	else
-		backgroundBorder = std::make_shared<TransparentFilledRectangle>(pos, ColorRGBA(0, 0, 0, 128), Colors::BLACK);
+	int bigFontHeight = ENGINE->renderHandler().loadFont(FONT_BIG)->getLineHeight();
 
+	pos.h += 6;
 	if (isBattleMode)
 	{
-		pos.w = 76;
+		pos.w = 77;
 
-		pos.h += 20;
-		playerLabelsMain[player] = std::make_shared<CLabel>(pos.w / 2, pos.h - 10, FONT_BIG, ETextAlignment::CENTER, graphics->playerColors[player.getNum()], "");
+		pos.h += bigFontHeight - 4;
+		playerLabelsMain[player] = std::make_shared<CLabel>(pos.w / 2, pos.h - 2, FONT_BIG, ETextAlignment::BOTTOMCENTER, graphics->playerColors[player.getNum()], "");
 
 		if (timers.battleTimer != 0)
 		{
-			pos.h += 20;
-			playerLabelsBattle[player] = std::make_shared<CLabel>(pos.w / 2, pos.h - 10, FONT_BIG, ETextAlignment::CENTER, graphics->playerColors[player.getNum()], "");
+			pos.h += bigFontHeight;
+			playerLabelsBattle[player] = std::make_shared<CLabel>(pos.w / 2, pos.h - 2, FONT_BIG, ETextAlignment::BOTTOMCENTER, graphics->playerColors[player.getNum()], "");
 		}
 
 		if (!timers.accumulatingUnitTimer && timers.unitTimer != 0)
 		{
-			pos.h += 20;
-			playerLabelsUnit[player] = std::make_shared<CLabel>(pos.w / 2, pos.h - 10, FONT_BIG, ETextAlignment::CENTER, graphics->playerColors[player.getNum()], "");
+			pos.h += bigFontHeight;
+			playerLabelsUnit[player] = std::make_shared<CLabel>(pos.w / 2, pos.h - 2, FONT_BIG, ETextAlignment::BOTTOMCENTER, graphics->playerColors[player.getNum()], "");
 		}
 
 		updateTextLabel(player, GAME->interface()->cb->getPlayerTurnTime(player));
@@ -75,10 +76,10 @@ TurnTimerWidget::TurnTimerWidget(const Point & position, PlayerColor player)
 	else
 	{
 		if (!timers.accumulatingTurnTimer && timers.baseTimer != 0)
-			pos.w = 120;
+			pos.w = 130;
 		else
-			pos.w = 60;
-
+			pos.w = 70;
+		
 		for(PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player)
 		{
 			if (GAME->interface()->cb->getStartInfo()->playerInfos.count(player) == 0)
@@ -87,8 +88,8 @@ TurnTimerWidget::TurnTimerWidget(const Point & position, PlayerColor player)
 			if (!GAME->interface()->cb->getStartInfo()->playerInfos.at(player).isControlledByHuman())
 				continue;
 
-			pos.h += 20;
-			playerLabelsMain[player] = std::make_shared<CLabel>(pos.w / 2, pos.h - 10, FONT_BIG, ETextAlignment::CENTER, graphics->playerColors[player.getNum()], "");
+			pos.h += bigFontHeight - 4;
+			playerLabelsMain[player] = std::make_shared<CLabel>(pos.w / 2, pos.h - 2, FONT_BIG, ETextAlignment::BOTTOMCENTER, graphics->playerColors[player.getNum()], "");
 
 			updateTextLabel(player, GAME->interface()->cb->getPlayerTurnTime(player));
 		}

+ 50 - 24
client/battle/BattleWindow.cpp

@@ -191,10 +191,10 @@ void BattleWindow::createQuickSpellWindow()
 	OBJECT_CONSTRUCTION;
 
 	quickSpellWindow = std::make_shared<QuickSpellPanel>(owner);
-	quickSpellWindow->moveTo(Point(pos.x - 67, pos.y));
+	quickSpellWindow->moveTo(Point(pos.x - 52, pos.y));
 
 	unitActionWindow = std::make_shared<UnitActionPanel>(owner);
-	unitActionWindow->moveTo(Point(pos.x + pos.w + 15, pos.y));
+	unitActionWindow->moveTo(Point(pos.x + pos.w, pos.y));
 
 	if(settings["battle"]["enableQuickSpellPanel"].Bool())
 		showStickyQuickSpellWindow();
@@ -218,8 +218,8 @@ void BattleWindow::hideStickyQuickSpellWindow()
 	quickSpellWindow->disable();
 	unitActionWindow->disable();
 
-	setPositionInfoWindow();
 	createTimerInfoWindows();
+	setPositionInfoWindow();
 	ENGINE->windows().totalRedraw();
 }
 
@@ -230,14 +230,14 @@ void BattleWindow::showStickyQuickSpellWindow()
 
 	auto hero = owner.getBattle()->battleGetMyHero();
 
-	bool quickSpellWindowVisible = ENGINE->screenDimensions().x >= 1050 && hero != nullptr && hero->hasSpellbook();
-	bool unitActionWindowVisible = ENGINE->screenDimensions().x >= 1050;
+	bool quickSpellWindowVisible = hasSpaceForQuickActions() && hero != nullptr && hero->hasSpellbook();
+	bool unitActionWindowVisible = hasSpaceForQuickActions();
 
 	quickSpellWindow->setEnabled(quickSpellWindowVisible);
 	unitActionWindow->setEnabled(unitActionWindowVisible);
 
-	setPositionInfoWindow();
 	createTimerInfoWindows();
+	setPositionInfoWindow();
 	ENGINE->windows().totalRedraw();
 }
 
@@ -245,8 +245,8 @@ void BattleWindow::createTimerInfoWindows()
 {
 	OBJECT_CONSTRUCTION;
 
-	int xOffsetAttacker = quickSpellWindow->isDisabled() ? 0 : -53;
-	int xOffsetDefender = unitActionWindow->isDisabled() ? 0 : 53;
+	int xOffsetAttacker = quickSpellWindow->isDisabled() ? 0 : -51;
+	int xOffsetDefender = unitActionWindow->isDisabled() ? 0 : 51;
 
 	if(GAME->interface()->cb->getStartInfo()->turnTimerInfo.battleTimer != 0 || GAME->interface()->cb->getStartInfo()->turnTimerInfo.unitTimer != 0)
 	{
@@ -255,16 +255,16 @@ void BattleWindow::createTimerInfoWindows()
 
 		if (attacker.isValidPlayer())
 		{
-			if (ENGINE->screenDimensions().x >= 1000)
-				attackerTimerWidget = std::make_shared<TurnTimerWidget>(Point(-92 + xOffsetAttacker, 1), attacker);
+			if (placeInfoWindowsOutside())
+				attackerTimerWidget = std::make_shared<TurnTimerWidget>(Point(-76 + xOffsetAttacker, 0), attacker);
 			else
 				attackerTimerWidget = std::make_shared<TurnTimerWidget>(Point(1, 135), attacker);
 		}
 
 		if (defender.isValidPlayer())
 		{
-			if (ENGINE->screenDimensions().x >= 1000)
-				defenderTimerWidget = std::make_shared<TurnTimerWidget>(Point(pos.w + 16 + xOffsetDefender, 1), defender);
+			if (placeInfoWindowsOutside())
+				defenderTimerWidget = std::make_shared<TurnTimerWidget>(Point(pos.w + xOffsetDefender, 0), defender);
 			else
 				defenderTimerWidget = std::make_shared<TurnTimerWidget>(Point(pos.w - 78, 135), defender);
 		}
@@ -383,34 +383,37 @@ void BattleWindow::updateQueue()
 
 void BattleWindow::setPositionInfoWindow()
 {
-	int xOffsetAttacker = quickSpellWindow->isDisabled() ? 0 : -53;
-	int xOffsetDefender = unitActionWindow->isDisabled() ? 0 : 53;
+	int xOffsetAttacker = quickSpellWindow->isDisabled() ? 0 : -51;
+	int xOffsetDefender = unitActionWindow->isDisabled() ? 0 : 51;
+
+	int yOffsetAttacker = attackerTimerWidget ? attackerTimerWidget->pos.h + 9 : 0;
+	int yOffsetDefender = defenderTimerWidget ? defenderTimerWidget->pos.h + 9 : 0;
 
 	if(defenderHeroWindow)
 	{
-		Point position = (ENGINE->screenDimensions().x >= 1000)
-				? Point(pos.x + pos.w + 15 + xOffsetDefender, pos.y + 60)
+		Point position = placeInfoWindowsOutside()
+				? Point(pos.x + pos.w - 1 + xOffsetDefender, pos.y - 1 + yOffsetDefender)
 				: Point(pos.x + pos.w -79, pos.y + 195);
 		defenderHeroWindow->moveTo(position);
 	}
 	if(attackerHeroWindow)
 	{
-		Point position = (ENGINE->screenDimensions().x >= 1000)
-				? Point(pos.x - 93 + xOffsetAttacker, pos.y + 60)
+		Point position = placeInfoWindowsOutside()
+				? Point(pos.x - 77 + xOffsetAttacker, pos.y - 1 + yOffsetAttacker)
 				: Point(pos.x + 1, pos.y + 195);
 		attackerHeroWindow->moveTo(position);
 	}
 	if(defenderStackWindow)
 	{
-		Point position = (ENGINE->screenDimensions().x >= 1000)
-				? Point(pos.x + pos.w + 15 + xOffsetDefender, defenderHeroWindow ? defenderHeroWindow->pos.y + 210 : pos.y + 60)
+		Point position = placeInfoWindowsOutside()
+				? Point(pos.x + pos.w - 1 + xOffsetDefender, defenderHeroWindow ? defenderHeroWindow->pos.y + 210 : pos.y - 1 + yOffsetDefender)
 				: Point(pos.x + pos.w -79, defenderHeroWindow ? defenderHeroWindow->pos.y : pos.y + 195);
 		defenderStackWindow->moveTo(position);
 	}
 	if(attackerStackWindow)
 	{
-		Point position = (ENGINE->screenDimensions().x >= 1000)
-				? Point(pos.x - 93 + xOffsetAttacker, attackerHeroWindow ? attackerHeroWindow->pos.y + 210 : pos.y + 60)
+		Point position = placeInfoWindowsOutside()
+				? Point(pos.x - 77 + xOffsetAttacker, attackerHeroWindow ? attackerHeroWindow->pos.y + 210 : pos.y - 1 + yOffsetAttacker)
 				: Point(pos.x + 1, attackerHeroWindow ? attackerHeroWindow->pos.y : pos.y + 195);
 		attackerStackWindow->moveTo(position);
 	}
@@ -444,8 +447,8 @@ void BattleWindow::updateStackInfoWindow(const CStack * stack)
 	else
 		attackerStackWindow = nullptr;
 	
-	setPositionInfoWindow();
 	createTimerInfoWindows();
+	setPositionInfoWindow();
 }
 
 void BattleWindow::heroManaPointsChanged(const CGHeroInstance * hero)
@@ -847,7 +850,7 @@ void BattleWindow::showAll(Canvas & to)
 	CIntObject::showAll(to);
 
 	if (ENGINE->screenDimensions().x != 800 || ENGINE->screenDimensions().y !=600)
-		CMessage::drawBorder(owner.curInt->playerID, to, pos.w+28, pos.h+29, pos.x-14, pos.y-15);
+		to.drawBorder(Rect(pos.x-1, pos.y, pos.w+2, pos.h+1), Colors::BRIGHT_YELLOW);
 }
 
 void BattleWindow::show(Canvas & to)
@@ -862,3 +865,26 @@ void BattleWindow::close()
 		logGlobal->error("Only top interface must be closed");
 	ENGINE->windows().popWindows(1);
 }
+
+bool BattleWindow::hasSpaceForQuickActions() const
+{
+	constexpr int widthWithQuickActions = 800 + 50*2;
+
+	return ENGINE->screenDimensions().x >= widthWithQuickActions;
+}
+
+bool BattleWindow::placeInfoWindowsOutside() const
+{
+	constexpr int widthWithQuickActions = 800 + 50*2 + 75*2;
+	constexpr int widthBaseWindow = 800 + 75*2;
+
+	if (quickActionsPanelActive())
+		return ENGINE->screenDimensions().x >= widthWithQuickActions;
+	else
+		return ENGINE->screenDimensions().x >= widthBaseWindow;
+}
+
+bool BattleWindow::quickActionsPanelActive() const
+{
+	return unitActionWindow->isActive();
+}

+ 4 - 0
client/battle/BattleWindow.h

@@ -83,6 +83,10 @@ class BattleWindow : public InterfaceObjectConfigurable
 
 	bool onlyOnePlayerHuman;
 
+	bool hasSpaceForQuickActions() const;
+	bool quickActionsPanelActive() const;
+	bool placeInfoWindowsOutside() const;
+
 public:
 	BattleWindow(BattleInterface & owner );
 

+ 1 - 1
client/battle/HeroInfoWindow.cpp

@@ -26,7 +26,7 @@ HeroInfoBasicPanel::HeroInfoBasicPanel(const InfoAboutHero & hero, const Point *
 
 	if(initializeBackground)
 	{
-		background = std::make_shared<CPicture>(ImagePath::builtin("CHRPOP"));
+		background = std::make_shared<CPicture>(ImagePath::builtin("CHRPOP"), Rect(1, 1, 76, 200), 1, 1);
 		background->setPlayerColor(hero.owner);
 	}
 

+ 2 - 2
client/battle/StackInfoBasicPanel.cpp

@@ -27,10 +27,10 @@ StackInfoBasicPanel::StackInfoBasicPanel(const CStack * stack, bool initializeBa
 
 	if(initializeBackground)
 	{
-		background = std::make_shared<CPicture>(ImagePath::builtin("CCRPOP"));
+		background = std::make_shared<CPicture>(ImagePath::builtin("CCRPOP"), Rect(1, 1, 76, 286), 1, 1);
 		background->pos.y += 37;
 		background->setPlayerColor(stack->getOwner());
-		background2 = std::make_shared<CPicture>(ImagePath::builtin("CHRPOP"));
+		background2 = std::make_shared<CPicture>(ImagePath::builtin("CHRPOP"), Rect(1, 1, 76, 200), 1, 1);
 		background2->setPlayerColor(stack->getOwner());
 	}
 

+ 5 - 2
client/lobby/CLobbyScreen.cpp

@@ -62,8 +62,11 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType, bool hideScreen)
 		}
 	};
 
-	buttonChat = std::make_shared<CButton>(Point(619, 80), AnimationPath::builtin("GSPBUT2.DEF"), LIBRARY->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), EShortcut::LOBBY_TOGGLE_CHAT);
-	buttonChat->setTextOverlay(LIBRARY->generaltexth->allTexts[532], FONT_SMALL, Colors::WHITE);
+	if(screenType != ESelectionScreen::campaignList)
+	{
+		buttonChat = std::make_shared<CButton>(Point(619, 80), AnimationPath::builtin("GSPBUT2.DEF"), LIBRARY->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), EShortcut::LOBBY_TOGGLE_CHAT);
+		buttonChat->setTextOverlay(LIBRARY->generaltexth->allTexts[532], FONT_SMALL, Colors::WHITE);
+	}
 
 	switch(screenType)
 	{

+ 40 - 6
client/mainmenu/CHighScoreScreen.cpp

@@ -37,10 +37,13 @@
 #include "../../lib/gameState/HighScore.h"
 #include "../../lib/gameState/GameStatistics.h"
 #include "../../lib/GameLibrary.h"
+#include "../../lib/serializer/JsonSerializer.h"
+#include "../../lib/serializer/JsonDeserializer.h"
 
 CHighScoreScreen::CHighScoreScreen(HighScorePage highscorepage, int highlighted)
 	: CWindowObject(BORDERED), highscorepage(highscorepage), highlighted(highlighted)
 {
+	addUsedEvents(LCLICK);
 	addUsedEvents(SHOW_POPUP);
 
 	OBJECT_CONSTRUCTION;
@@ -50,7 +53,7 @@ CHighScoreScreen::CHighScoreScreen(HighScorePage highscorepage, int highlighted)
 	addButtons();
 }
 
-void CHighScoreScreen::showPopupWindow(const Point & cursorPosition)
+void CHighScoreScreen::rowEvent(std::function<void(int row, bool currentGameNotInListEntry)> func, const Point & cursorPosition)
 {
 	for (int i = 0; i < screenRows; i++)
 	{
@@ -58,14 +61,33 @@ void CHighScoreScreen::showPopupWindow(const Point & cursorPosition)
 
 		Rect r = Rect(80, 40 + i * 50, 635, 50);
 		if(r.isInside(cursorPosition - pos))
-		{
-			std::string tmp = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"][currentGameNotInListEntry ? highlighted : i]["datetime"].String();
-			if(!tmp.empty())
-				CRClickPopup::createAndPush(tmp);
-		}
+			func(i, currentGameNotInListEntry);
 	}
 }
 
+void CHighScoreScreen::clickPressed(const Point & cursorPosition)
+{
+	rowEvent([this](int row, bool currentGameNotInListEntry){
+		auto node = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"][currentGameNotInListEntry ? highlighted : row];
+		if(node["statistic"].isNull())
+			return;
+		
+		JsonDeserializer ser(nullptr, node);
+		StatisticDataSet stat;
+		ser.serializeStruct("statistic", stat);
+		ENGINE->windows().createAndPushWindow<CStatisticScreen>(stat);
+	}, cursorPosition);
+}
+
+void CHighScoreScreen::showPopupWindow(const Point & cursorPosition)
+{
+	rowEvent([this](int row, bool currentGameNotInListEntry){
+		std::string tmp = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"][currentGameNotInListEntry ? highlighted : row]["datetime"].String();
+		if(!tmp.empty())
+			CRClickPopup::createAndPush("{" + LIBRARY->generaltexth->translate("core.help.316.hover") + "}\n\n" + tmp);
+	}, cursorPosition);
+}
+
 void CHighScoreScreen::addButtons()
 {
 	OBJECT_CONSTRUCTION;
@@ -251,6 +273,15 @@ int CHighScoreInputScreen::addEntry(std::string text) {
 	newNode["points"].Integer() = calc.calculate().cheater ? 0 : calc.calculate().total;
 	newNode["datetime"].String() = TextOperations::getFormattedDateTimeLocal(std::time(nullptr));
 	newNode["posFlag"].Bool() = true;
+	if(!calc.isCampaign)
+	{
+		JsonSerializer ser(nullptr, newNode);
+		ser.serializeStruct("statistic", stat);
+	}
+
+	int highscoreEntriesCap = settings["general"]["highscoreEntriesCap"].Integer();
+	if (baseNode.size() > highscoreEntriesCap - 1)
+		baseNode.resize(highscoreEntriesCap - 1);
 
 	baseNode.push_back(newNode);
 	boost::range::sort(baseNode, sortFunctor);
@@ -258,6 +289,9 @@ int CHighScoreInputScreen::addEntry(std::string text) {
 	int pos = -1;
 	for (int i = 0; i < baseNode.size(); i++)
 	{
+		if(!baseNode[i]["statistic"].isNull() && i >= settings["general"]["highscoreStatisticEntriesCap"].Integer() && baseNode[i]["posFlag"].isNull())
+			baseNode[i]["statistic"].clear();
+
 		if(!baseNode[i]["posFlag"].isNull())
 		{
 			baseNode[i]["posFlag"].clear();

+ 3 - 0
client/mainmenu/CHighScoreScreen.h

@@ -38,6 +38,9 @@ private:
 	void buttonResetClick();
 	void buttonExitClick();
 
+	void rowEvent(std::function<void(int row, bool currentGameNotInListEntry)> func, const Point & cursorPosition);
+
+	void clickPressed(const Point & cursorPosition) override;
 	void showPopupWindow(const Point & cursorPosition) override;
 	void showAll(Canvas & to) override;
 

+ 7 - 2
client/mainmenu/CMainMenu.cpp

@@ -221,8 +221,13 @@ std::shared_ptr<CButton> CMenuEntry::createButton(CMenuScreen * parent, const Js
 	std::function<void()> command = genCommand(parent, parent->menuNameToEntry, button["command"].String());
 
 	std::pair<std::string, std::string> help;
-	if(!button["help"].isNull() && button["help"].Float() > 0)
-		help = LIBRARY->generaltexth->zelp[(size_t)button["help"].Float()];
+	if(!button["help"].isNull())
+	{
+		if(button["help"].isNumber() && button["help"].Float() > 0)
+			help = LIBRARY->generaltexth->zelp[(size_t)button["help"].Float()];
+		if(button["help"].isString() && !button["help"].String().empty())
+			help = {"", LIBRARY->generaltexth->translate(button["help"].String())};
+	}	
 
 	int posx = static_cast<int>(button["x"].Float());
 	if(posx < 0)

+ 1 - 1
client/mapView/MapRenderer.cpp

@@ -29,7 +29,7 @@
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/MiscObjects.h"
 #include "../../lib/mapObjects/ObjectTemplate.h"
-#include "../../lib/mapping/CMapDefines.h"
+#include "../../lib/mapping/TerrainTile.h"
 #include "../../lib/pathfinder/CGPathNode.h"
 
 struct NeighborTilesInfo

+ 23 - 22
client/render/AssetGenerator.cpp

@@ -221,69 +221,69 @@ AssetGenerator::CanvasPtr AssetGenerator::createCampaignBackground(int selection
 	Rect bigBlockRegion(292, 74, 248, 114);
 	Canvas croppedBigBlock = bigBlock->getCanvas();
 	croppedBigBlock.draw(img, Point(0, 0), bigBlockRegion);
-	bigBlock->scaleTo(Point(200, 114), EScalingAlgorithm::NEAREST);
+	Point bigBlockSize(200, 114);
 
 	// SmallBlock section
 	auto smallBlock = ENGINE->renderHandler().createImage(Point(248, 114), CanvasScalingPolicy::IGNORE);
 	Canvas croppedSmallBlock = smallBlock->getCanvas();
 	croppedSmallBlock.draw(img, Point(0, 0), bigBlockRegion);
-	smallBlock->scaleTo(Point(134, 114), EScalingAlgorithm::NEAREST);
+	Point smallBlockSize(134, 114);
 
 	// Tripple block section
 	auto trippleBlock = ENGINE->renderHandler().createImage(Point(72, 116), CanvasScalingPolicy::IGNORE);
 	Rect trippleBlockSection(512, 246, 72, 116);
 	Canvas croppedTrippleBlock = trippleBlock->getCanvas();
 	croppedTrippleBlock.draw(img, Point(0, 0), trippleBlockSection);
-	trippleBlock->scaleTo(Point(70, 114), EScalingAlgorithm::NEAREST);
+	Point trippleBlockSize(70, 114);
 
 
 	// First campaigns line
 	if (selection > 7)
 	{
 		// Rebuild 1. campaigns line from 2 to 3 fields
-		canvas.draw(bigBlock, Point(40, 72));
-		canvas.draw(trippleBlock, Point(240, 73));
-		canvas.draw(bigBlock, Point(310, 72));
-		canvas.draw(trippleBlock, Point(510, 72));
-		canvas.draw(bigBlock, Point(580, 72));
-		canvas.draw(trippleBlock, Point(780, 72));
+		canvas.drawScaled(bigBlock->getCanvas(), Point(40, 72), bigBlockSize);
+		canvas.drawScaled(trippleBlock->getCanvas(), Point(240, 73), trippleBlockSize);
+		canvas.drawScaled(bigBlock->getCanvas(), Point(310, 72), bigBlockSize);
+		canvas.drawScaled(trippleBlock->getCanvas(), Point(510, 72), trippleBlockSize);
+		canvas.drawScaled(bigBlock->getCanvas(), Point(580, 72), bigBlockSize);
+		canvas.drawScaled(trippleBlock->getCanvas(), Point(780, 72), trippleBlockSize);
 	} 
 	else
 	{
 		// Empty 1 + 2. field
-		canvas.draw(bigBlock, Point(90, 72));
-		canvas.draw(bigBlock, Point(540, 72));
+		canvas.drawScaled(bigBlock->getCanvas(), Point(90, 72), bigBlockSize);
+		canvas.drawScaled(bigBlock->getCanvas(), Point(540, 72), bigBlockSize);
 	}
 
 	// Second campaigns line
 	// 3. Field
-	canvas.draw(bigBlock, Point(43, 245));
+	canvas.drawScaled(bigBlock->getCanvas(), Point(43, 245), bigBlockSize);
 
 	if (selection == 4)
 	{
 		// Disabled 4. field
-		canvas.draw(trippleBlock, Point(310, 245));
-		canvas.draw(smallBlock, Point(380, 245));
+		canvas.drawScaled(trippleBlock->getCanvas(), Point(310, 245), trippleBlockSize);
+		canvas.drawScaled(smallBlock->getCanvas(), Point(380, 245), smallBlockSize);
 	}
 	else
 	{
 		// Empty 4. field
-		canvas.draw(bigBlock, Point(314, 244));
+		canvas.drawScaled(bigBlock->getCanvas(), Point(314, 244), bigBlockSize);
 	}
 	
 	// 5. Field
-	canvas.draw(bigBlock, Point(586, 246));
+	canvas.drawScaled(bigBlock->getCanvas(), Point(586, 246), bigBlockSize);
 
 	// Third campaigns line
 	// 6. Field
 	if (selection >= 6)
 	{
-		canvas.draw(bigBlock, Point(32, 417));
+		canvas.drawScaled(bigBlock->getCanvas(), Point(32, 417), bigBlockSize);
 	}
 	else
 	{
-		canvas.draw(trippleBlock, Point(30, 417));
-		canvas.draw(smallBlock, Point(100, 417));
+		canvas.drawScaled(trippleBlock->getCanvas(), Point(30, 417), trippleBlockSize);
+		canvas.drawScaled(smallBlock->getCanvas(), Point(100, 417), smallBlockSize);
 	}
 
 	auto locatorSkull = ImageLocator(ImagePath::builtin("CAMPNOSC"), EImageBlitMode::OPAQUE);
@@ -292,14 +292,15 @@ AssetGenerator::CanvasPtr AssetGenerator::createCampaignBackground(int selection
 	if (selection >= 7)
 	{
 		// Only skull part
-		canvas.draw(bigBlock, Point(404, 417));
+		canvas.drawScaled(bigBlock->getCanvas(), Point(404, 417), bigBlockSize);
 		canvas.draw(imgSkull, Point(563, 512), Rect(178, 108, 43, 19));
 	}
 	else
 	{
 		// Original disabled field with skull and stone for 8. field
-		imgSkull->scaleTo(Point(238, 150), EScalingAlgorithm::NEAREST);
-		canvas.draw(imgSkull, Point(385, 400));
+		Canvas canvasSkull = Canvas(Point(imgSkull->width(), imgSkull->height()), CanvasScalingPolicy::IGNORE);
+		canvasSkull.draw(imgSkull, Point(0, 0), Rect(0, 0, imgSkull->width(), imgSkull->height()));
+		canvas.drawScaled(canvasSkull, Point(385, 400), Point(238, 150));
 	}
 
 

+ 34 - 20
client/widgets/CExchangeController.cpp

@@ -29,34 +29,48 @@ void CExchangeController::swapArmy()
 	const auto & leftSlots = left->Slots();
 	const auto & rightSlots = right->Slots();
 
-	auto i = leftSlots.begin();
-	auto j = rightSlots.begin();
+	auto leftIt = leftSlots.begin();
+	auto rightIt = rightSlots.begin();
 
-	for(; i != leftSlots.end() && j != rightSlots.end(); i++, j++)
+	// Swap slots that are full in both armies
+	// [A] [B] => [B] [A]
+	for (SlotID slotID(0); slotID < GameConstants::ARMY_SIZE; ++slotID)
 	{
-		GAME->interface()->cb->swapCreatures(left, right, i->first, j->first);
+		if (left->hasStackAtSlot(slotID) && right->hasStackAtSlot(slotID))
+			GAME->interface()->cb->swapCreatures(left, right, slotID, slotID);
 	}
 
-	if(i != leftSlots.end())
+	// Swap pairs of stacks in different slots and correct their positions
+	// [A] [ ]    [B] [ ]    [ ] [A]
+	//         =>         =>
+	// [ ] [B]    [ ] [A]    [B] [ ]
+	for (;;)
 	{
-		auto freeSlots = right->getFreeSlots();
-		auto slot = freeSlots.begin();
+		while (leftIt != leftSlots.end() && right->hasStackAtSlot(leftIt->first))
+			leftIt++;
 
-		for(; i != leftSlots.end() && slot != freeSlots.end(); i++, slot++)
-		{
-			GAME->interface()->cb->swapCreatures(left, right, i->first, *slot);
-		}
-	}
-	else if(j != rightSlots.end())
-	{
-		auto freeSlots = left->getFreeSlots();
-		auto slot = freeSlots.begin();
+		while (rightIt != rightSlots.end() && left->hasStackAtSlot(rightIt->first))
+			rightIt++;
 
-		for(; j != rightSlots.end() && slot != freeSlots.end(); j++, slot++)
-		{
-			GAME->interface()->cb->swapCreatures(left, right, *slot, j->first);
-		}
+		if (leftIt == leftSlots.end() || rightIt == rightSlots.end())
+			break;
+
+		GAME->interface()->cb->swapCreatures(left, right, leftIt->first, rightIt->first);
+
+		GAME->interface()->cb->swapCreatures(left, left, leftIt->first, rightIt->first);
+		GAME->interface()->cb->swapCreatures(right, right, rightIt->first, leftIt->first);
+
+		leftIt++;
+		rightIt++;
 	}
+
+	// Move remaining unpaired stacks (if armies size is different)
+	// [A] [ ] => [ ] [A]
+	for(; leftIt != leftSlots.end(); leftIt++)
+		GAME->interface()->cb->swapCreatures(left, right, leftIt->first, leftIt->first);
+
+	for(; rightIt != rightSlots.end(); rightIt++)
+		GAME->interface()->cb->swapCreatures(left, right, rightIt->first, rightIt->first);
 }
 
 void CExchangeController::moveArmy(bool leftToRight, std::optional<SlotID> heldSlot)

+ 69 - 18
config/commanders.json

@@ -3,9 +3,9 @@
 	//Commander receives these bonuses on level-up
 	"bonusPerLevel":
 	[
-		["CREATURE_DAMAGE", 2, "creatureDamageMin", 0 ], //+2 minimum damage
-		["CREATURE_DAMAGE", 4, "creatureDamageMax", 0 ], //+4 maximum damage
-		["STACK_HEALTH", 20, null, 0 ] //+20 hp
+		{ "type" : "CREATURE_DAMAGE", "val" : 2, "subtype" : "creatureDamageMin" }, //+2 minimum damage
+		{ "type" : "CREATURE_DAMAGE", "val" : 4, "subtype" : "creatureDamageMax" }, //+4 maximum damage
+		{ "type" : "STACK_HEALTH", "val" : 20 } //+20 hp
 	],
 	//Value of bonuses given by each skill level
 	"skillLevels":
@@ -22,20 +22,71 @@
 	"abilityRequirements":
 	//Two secondary skills needed for each special ability
 	[
-		{"ability": ["ENEMY_DEFENCE_REDUCTION", 50, null, 0 ], "skills": [0, 1]},
-		{"ability": ["FEAR", 0, null, 0 ], "skills": [0, 2]},
-		{"ability": ["ALWAYS_MAXIMUM_DAMAGE", 0, null, 0 ], "skills": [0, 3]},
-		{"ability": [["SHOOTER", 0, null, 0 ], ["NO_MELEE_PENALTY", 0, null, 0 ]], "skills": [0, 4]},
-		{"ability": ["BLOCKS_RETALIATION", 0, null, 0 ], "skills": [0,5]},
-		{"ability": ["UNLIMITED_RETALIATIONS", 0, null, 0 ], "skills": [1, 2]},
-		{"ability": ["ATTACKS_ALL_ADJACENT", 0, null, 0 ], "skills": [1, 3]},
-		{"ability": ["NONE", 30, null, 0 ], "skills": [1, 4]}, // TODO: Implement bonus that gives chance to completely block one enemy attack per turn
-		{"ability": ["FIRE_SHIELD", 1, null, 0 ], "skills": [1, 5]},
-		{"ability": ["ADDITIONAL_ATTACK", 1, null, 0 ], "skills": [2, 3]},
-		{"ability": ["HP_REGENERATION", 50, null, 0 ], "skills": [2, 4]},
-		{"ability": ["SPELL_AFTER_ATTACK", 30, "spell.paralyze", 0 ], "skills": [2, 5]},
-		{"ability": ["JOUSTING", 5, null, 0 ], "skills": [3, 4]},
-		{"ability": ["DEATH_STARE", 1, "deathStareCommander", 0 ], "skills": [3,5]},
-		{"ability": ["FLYING", 0, "movementFlying", 0 ], "skills": [4,5]}
+		{
+			"ability": [{ "type" : "ENEMY_DEFENCE_REDUCTION", "val": 50 }],
+			"skills": [0, 1]
+		},
+		{
+			"ability": [{ "type" : "FEAR", "val": 0 }],
+			"skills": [0, 2]
+		},
+		{
+			"ability": [{ "type" : "ALWAYS_MAXIMUM_DAMAGE", "val": 0 }],
+			"skills": [0, 3]
+		},
+		{
+			"ability": [
+				{ "type" : "SHOOTER", "val": 0 },
+				{ "type" : "NO_MELEE_PENALTY", "val": 0 }
+			],
+			"skills": [0, 4]
+		},
+		{
+			"ability": [{ "type" : "BLOCKS_RETALIATION", "val": 0 }],
+			"skills": [0,5]
+		},
+		{
+			"ability": [{ "type" : "UNLIMITED_RETALIATIONS", "val": 0 }],
+			"skills": [1, 2]
+		},
+		{
+			"ability": [{ "type" : "ATTACKS_ALL_ADJACENT", "val": 0 }],
+			"skills": [1, 3]
+		},
+		{
+			// TODO: Implement bonus that gives chance to completely block one enemy attack per turn
+			"ability": [{ "type" : "NONE", "val": 30 }],
+			"skills": [1, 4]
+		}, 
+		{
+			"ability": [{ "type" : "FIRE_SHIELD", "val": 1 }],
+			"skills": [1, 5]
+		},
+		{
+			"ability": [{ "type" : "ADDITIONAL_ATTACK", "val": 1 }],
+			"skills": [2, 3]
+		},
+		{
+			"ability": [{ "type" : "HP_REGENERATION", "val": 50 }],
+			"skills": [2, 4]
+		},
+		{
+			"ability": [{ "type" : "SPELL_AFTER_ATTACK", "val": 30, "subtype" : "spell.paralyze" }],
+			"skills": [2, 5]
+		},
+		{
+			"ability": [{ "type" : "JOUSTING", "val": 5 }],
+			"skills": [3, 4]
+		},
+		{
+			"ability": [{ "type" : "DEATH_STARE", "val": 1, "subtype" : "deathStareCommander" }],
+			"skills": [3,5]
+		},
+		{
+			"ability": [{ "type" : "FLYING", "val": 0, "subtype" : "movementFlying" }],
+			"skills": [4,5]
+		}
 	]
+	
+	
 }

+ 1 - 1
config/creatures/neutral.json

@@ -110,7 +110,7 @@
 			"fearless" :
 			{
 				"type" : "FEARFUL",
-				"valueType" : "INDEPENDENT_MAX",
+				"valueType" : "INDEPENDENT_MIN",
 				"description" : "PLACEHOLDER",
 				"val" : 0
 				

+ 1 - 0
config/factions/tower.json

@@ -202,6 +202,7 @@
 					"produce" : { "gold": 5000 },
 					"bonuses": [
 						{ "type": "PRIMARY_SKILL", "subtype": "primarySkill.knowledge", "val": 15 },
+						{ "type": "COMBAT_MANA_BONUS", "val": 150 },
 						{ "type": "FULL_MAP_SCOUTING" }
 					]
 				},

+ 5 - 5
config/mainmenu.json

@@ -95,11 +95,11 @@
 				"name" : "campaign",
 				"buttons":
 				[
-					{"x": 634, "y":  67, "center" : true, "name":"CSSSOD",  "shortcut" : "mainMenuCampaignSod", "command": "campaigns sod"},
-					{"x": 637, "y": 181, "center" : true, "name":"CSSROE",  "shortcut" : "mainMenuCampaignRoe", "command": "campaigns roe"},
-					{"x": 638, "y": 301, "center" : true, "name":"CSSARM",  "shortcut" : "mainMenuCampaignAb", "command": "campaigns ab"},
-					{"x": 638, "y": 413, "center" : true, "name":"CSSCUS",  "shortcut" : "mainMenuCampaign", "command": "start campaign"},
-					{"x": 639, "y": 518, "center" : true, "name":"CSSEXIT", "shortcut" : "mainMenuBack",  "command": "to new"}
+					{"x": 634, "y":  67, "center" : true, "name":"CSSSOD",  "shortcut" : "mainMenuCampaignSod", "help": "core.genrltxt.734", "command": "campaigns sod"},
+					{"x": 637, "y": 181, "center" : true, "name":"CSSROE",  "shortcut" : "mainMenuCampaignRoe", "help": "core.genrltxt.725", "command": "campaigns roe"},
+					{"x": 638, "y": 301, "center" : true, "name":"CSSARM",  "shortcut" : "mainMenuCampaignAb", "help": "core.genrltxt.724", "command": "campaigns ab"},
+					{"x": 638, "y": 413, "center" : true, "name":"CSSCUS",  "shortcut" : "mainMenuCampaign", "help": "core.genrltxt.726", "command": "start campaign"},
+					{"x": 639, "y": 518, "center" : true, "name":"CSSEXIT", "shortcut" : "mainMenuBack", "help": "core.genrltxt.727", "command": "to new"}
 				],
 			}
 		]

+ 11 - 1
config/schemas/settings.json

@@ -46,7 +46,9 @@
 				"enableOverlay",
 				"lastKindomInterface",
 				"enableSubtitle",
-				"ignoreMuteSwitch"
+				"ignoreMuteSwitch",
+				"highscoreEntriesCap",
+				"highscoreStatisticEntriesCap"
 			],
 			"properties" : {
 				"playerName" : {
@@ -166,6 +168,14 @@
 				"ignoreMuteSwitch" : {
 					"type": "boolean",
 					"default": true
+				},
+				"highscoreEntriesCap" : {
+					"type" : "number",
+					"default" : 100
+				},
+				"highscoreStatisticEntriesCap" : {
+					"type" : "number",
+					"default" : 15
 				}
 			}
 		},

+ 6 - 0
config/widgets/battleWindow2.json

@@ -8,6 +8,12 @@
 			"position": {"x": 0, "y": 556},
 			"image": "COPLACBR.bmp"
 		},
+		{
+			// fix transparent gap between content and border
+			"type": "transparentFilledRectangle",
+			"rect": {"x": 0, "y": 555, "w": 801, "h": 46},
+			"color": [0, 0, 0, 255]
+		},
 		{
 			"type": "picture",
 			"name": "menuBattle",

+ 10 - 15
docs/images/Bonus_System_Nodes.gv

@@ -23,7 +23,7 @@ digraph mygraph {
 		"Team" [
 			label =<<table>
 					<tr><td><b>Team</b></td></tr>
-					<tr><td>Propagator: <font face="monospace"><b>TEAM_PROPAGATOR</b></font></td></tr>
+					<tr><td>Propagator: <font face="monospace"><b>TEAM</b></font></td></tr>
 					<tr><td>C++ Class: <font face="monospace"><b>TeamState</b></font></td></tr>
 					<tr><td>Per-team node.<br/>Game will put players without team<br/>into a team with a single player</td></tr>
 				</table>>
@@ -31,7 +31,7 @@ digraph mygraph {
 		"Player" [
 			label =<<table>
 					<tr><td><b>Player</b></td></tr>
-					<tr><td>Propagator: <font face="monospace"><b>PLAYER_PROPAGATOR</b></font></td></tr>
+					<tr><td>Propagator: <font face="monospace"><b>PLAYER</b></font></td></tr>
 					<tr><td>C++ Class: <font face="monospace"><b>CPlayerState</b></font></td></tr>
 					<tr><td>Per-player team.<br/>All objects owned by a player<br/>belong to such node</td></tr>
 				</table>>
@@ -50,13 +50,6 @@ digraph mygraph {
 					<tr><td>Contains per-hero global bonuses, specialty bonuses, <br/>primary and secondary skill bonuses, campaign primary skill bonus</td></tr>
 				</table>>
 		]
-		"Combat" [
-			label =<<table>
-					<tr><td><b>Combat</b></td></tr>
-					<tr><td>Propagator: <font face="monospace"><b>BATTLE_WIDE</b></font></td></tr>
-					<tr><td>Node that contains both sides of a combat<br/>Anything propagated to this node will affect both sides in combat</td></tr>
-				</table>>
-		]
 	};
 
 	subgraph rankedHeroes {
@@ -68,11 +61,11 @@ digraph mygraph {
 					<tr><td>Hero that is currently<br/>visiting owned or allied town</td></tr>
 				</table>>
 		]
-		"Garrisoned Hero" [
+		"Hero defending town" [
 			fillcolor="#80808080"
 			label =<<table>
-					<tr><td><b>Garrisoned Hero</b></td></tr>
-					<tr><td>Hero that is currently<br/>placed in a garrison of owned town</td></tr>
+					<tr><td><b>Hero defending town</b></td></tr>
+					<tr><td>Hero that is currently<br/>fighting on a defending side in a siege</td></tr>
 				</table>>
 		]
 		"Wandering Hero" [
@@ -111,6 +104,7 @@ digraph mygraph {
 			fillcolor="#80808080"
 			label =<<table>
 					<tr><td><b>Town</b></td></tr>
+					<tr><td>Propagator: <font face="monospace"><b>TOWN</b></font></td></tr>
 					<tr><td>C++ Class: <font face="monospace"><b>CGTownInstance</b></font></td></tr>
 					<tr><td>Represents a town on map.</td></tr>
 					<tr><td>Contains town building bonuses</td></tr>
@@ -140,7 +134,7 @@ digraph mygraph {
 		"Town and visiting hero" [
 			label =<<table>
 					<tr><td><b>Town and Visiting Hero</b></td></tr>
-					<tr><td>Propagator: <font face="monospace"><b>VISITED_TOWN_AND_VISITOR</b></font></td></tr>
+					<tr><td>Propagator: <font face="monospace"><b>TOWN_AND_VISITOR</b></font></td></tr>
 					<tr><td>C++ Class: <font face="monospace"><b>CTownAndVisitingHero</b></font></td></tr>
 					<tr><td>Helper node that exists solely<br/>to propagate bonuses to both town and visiting hero</td></tr>
 					<tr><td>Note: Neutral towns are attached to global node instead</td></tr>
@@ -190,6 +184,7 @@ digraph mygraph {
 		"Army" [
 			label =<<table>
 					<tr><td><b>Army</b></td></tr>
+					<tr><td>Propagator: <font face="monospace"><b>ARMY</b></font></td></tr>
 					<tr><td>C++ Class: <font face="monospace"><b>CArmedInstance</b></font></td></tr>
 					<tr><td>Represents any object that can hold army,<br/>such as town, hero, mines, garrisons, wandering monsters</td></tr>
 					<tr><td>Contain anti-magic garrison bonus, faction mixing morale bonus</td></tr>
@@ -253,13 +248,13 @@ digraph mygraph {
 	"Creature Type" -> "Summon in Combat"
 	"Creature Type" -> "Unit in Army"
 
-	"Town" -> "Garrisoned Hero"
+	"Town" -> "Hero defending town"
 	"Town" -> "Army"
 	"Neutral Army" -> "Army"
 	"Owned Army" -> "Army"
 
 	"Visiting Hero" -> "Hero"
-	"Garrisoned Hero" -> "Hero"
+	"Hero defending town" -> "Hero"
 	"Wandering Hero" -> "Hero"
 	"Hero" -> "Army"
 }

+ 348 - 342
docs/images/Bonus_System_Nodes.svg

@@ -4,536 +4,542 @@
 <!-- Generated by graphviz version 2.43.0 (0)
  -->
 <!-- Title: mygraph Pages: 1 -->
-<svg width="1970pt" height="1540pt"
- viewBox="0.00 0.00 1970.00 1540.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1536)">
+<svg width="1847pt" height="1571pt"
+ viewBox="0.00 0.00 1847.00 1571.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1567)">
 <title>mygraph</title>
-<polygon fill="white" stroke="transparent" points="-4,4 -4,-1536 1966,-1536 1966,4 -4,4"/>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-1567 1843,-1567 1843,4 -4,4"/>
 <!-- Global -->
 <g id="node1" class="node">
 <title>Global</title>
-<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="1495,-1532 1005,-1532 1005,-1360 1495,-1360 1495,-1532"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1008,-1508 1008,-1529 1492,-1529 1492,-1508 1008,-1508"/>
-<text text-anchor="start" x="1226.5" y="-1515.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Global</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1008,-1485 1008,-1506 1492,-1506 1492,-1485 1008,-1485"/>
-<text text-anchor="start" x="1154" y="-1492.8" font-family="Noto Serif" font-size="14.00">Propagator: </text>
-<text text-anchor="start" x="1238" y="-1492.8" font-family="monospace" font-weight="bold" font-size="14.00">GLOBAL_EFFECT</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1008,-1462 1008,-1483 1492,-1483 1492,-1462 1008,-1462"/>
-<text text-anchor="start" x="1174.5" y="-1469.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
-<text text-anchor="start" x="1242.5" y="-1469.8" font-family="monospace" font-weight="bold" font-size="14.00">CGameState</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1008,-1424 1008,-1460 1492,-1460 1492,-1424 1008,-1424"/>
-<text text-anchor="start" x="1178.5" y="-1445.8" font-family="Noto Serif" font-size="14.00">Global node to which</text>
-<text text-anchor="start" x="1148.5" y="-1430.8" font-family="Noto Serif" font-size="14.00">all map entities are connected</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1008,-1386 1008,-1422 1492,-1422 1492,-1386 1008,-1386"/>
-<text text-anchor="start" x="1097" y="-1407.8" font-family="Noto Serif" font-size="14.00">Note: Not recruited heroes (such as in tavern)</text>
-<text text-anchor="start" x="1152.5" y="-1392.8" font-family="Noto Serif" font-size="14.00">are not attached to any node</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1008,-1363 1008,-1384 1492,-1384 1492,-1363 1008,-1363"/>
-<text text-anchor="start" x="1011" y="-1369.8" font-family="Noto Serif" font-size="14.00">Contains global bonuses, global stack experience and difficulty bonuses</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1005,-1360 1005,-1532 1495,-1532 1495,-1360 1005,-1360"/>
+<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="1382,-1563 892,-1563 892,-1391 1382,-1391 1382,-1563"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="895,-1539 895,-1560 1379,-1560 1379,-1539 895,-1539"/>
+<text text-anchor="start" x="1113.5" y="-1546.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Global</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="895,-1516 895,-1537 1379,-1537 1379,-1516 895,-1516"/>
+<text text-anchor="start" x="1041" y="-1523.8" font-family="Noto Serif" font-size="14.00">Propagator: </text>
+<text text-anchor="start" x="1125" y="-1523.8" font-family="monospace" font-weight="bold" font-size="14.00">GLOBAL_EFFECT</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="895,-1493 895,-1514 1379,-1514 1379,-1493 895,-1493"/>
+<text text-anchor="start" x="1061.5" y="-1500.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
+<text text-anchor="start" x="1129.5" y="-1500.8" font-family="monospace" font-weight="bold" font-size="14.00">CGameState</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="895,-1455 895,-1491 1379,-1491 1379,-1455 895,-1455"/>
+<text text-anchor="start" x="1065.5" y="-1476.8" font-family="Noto Serif" font-size="14.00">Global node to which</text>
+<text text-anchor="start" x="1035.5" y="-1461.8" font-family="Noto Serif" font-size="14.00">all map entities are connected</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="895,-1417 895,-1453 1379,-1453 1379,-1417 895,-1417"/>
+<text text-anchor="start" x="984" y="-1438.8" font-family="Noto Serif" font-size="14.00">Note: Not recruited heroes (such as in tavern)</text>
+<text text-anchor="start" x="1039.5" y="-1423.8" font-family="Noto Serif" font-size="14.00">are not attached to any node</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="895,-1394 895,-1415 1379,-1415 1379,-1394 895,-1394"/>
+<text text-anchor="start" x="898" y="-1400.8" font-family="Noto Serif" font-size="14.00">Contains global bonuses, global stack experience and difficulty bonuses</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="892,-1391 892,-1563 1382,-1563 1382,-1391 892,-1391"/>
 </g>
 <!-- Team -->
 <g id="node2" class="node">
 <title>Team</title>
-<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="1299.5,-1324 1044.5,-1324 1044.5,-1198 1299.5,-1198 1299.5,-1324"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1048,-1300 1048,-1321 1297,-1321 1297,-1300 1048,-1300"/>
-<text text-anchor="start" x="1152.5" y="-1307.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Team</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1048,-1277 1048,-1298 1297,-1298 1297,-1277 1048,-1277"/>
-<text text-anchor="start" x="1068.5" y="-1284.8" font-family="Noto Serif" font-size="14.00">Propagator: </text>
-<text text-anchor="start" x="1152.5" y="-1284.8" font-family="monospace" font-weight="bold" font-size="14.00">TEAM_PROPAGATOR</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1048,-1254 1048,-1275 1297,-1275 1297,-1254 1048,-1254"/>
-<text text-anchor="start" x="1101" y="-1261.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
-<text text-anchor="start" x="1169" y="-1261.8" font-family="monospace" font-weight="bold" font-size="14.00">TeamState</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1048,-1201 1048,-1252 1297,-1252 1297,-1201 1048,-1201"/>
-<text text-anchor="start" x="1121" y="-1237.8" font-family="Noto Serif" font-size="14.00">Per&#45;team node.</text>
-<text text-anchor="start" x="1051" y="-1222.8" font-family="Noto Serif" font-size="14.00">Game will put players without team</text>
-<text text-anchor="start" x="1065.5" y="-1207.8" font-family="Noto Serif" font-size="14.00">into a team with a single player</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1044.5,-1198 1044.5,-1324 1299.5,-1324 1299.5,-1198 1044.5,-1198"/>
+<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="1186.5,-1355 931.5,-1355 931.5,-1229 1186.5,-1229 1186.5,-1355"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="935,-1331 935,-1352 1184,-1352 1184,-1331 935,-1331"/>
+<text text-anchor="start" x="1039.5" y="-1338.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Team</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="935,-1308 935,-1329 1184,-1329 1184,-1308 935,-1308"/>
+<text text-anchor="start" x="1000.5" y="-1315.8" font-family="Noto Serif" font-size="14.00">Propagator: </text>
+<text text-anchor="start" x="1084.5" y="-1315.8" font-family="monospace" font-weight="bold" font-size="14.00">TEAM</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="935,-1285 935,-1306 1184,-1306 1184,-1285 935,-1285"/>
+<text text-anchor="start" x="988" y="-1292.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
+<text text-anchor="start" x="1056" y="-1292.8" font-family="monospace" font-weight="bold" font-size="14.00">TeamState</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="935,-1232 935,-1283 1184,-1283 1184,-1232 935,-1232"/>
+<text text-anchor="start" x="1008" y="-1268.8" font-family="Noto Serif" font-size="14.00">Per&#45;team node.</text>
+<text text-anchor="start" x="938" y="-1253.8" font-family="Noto Serif" font-size="14.00">Game will put players without team</text>
+<text text-anchor="start" x="952.5" y="-1238.8" font-family="Noto Serif" font-size="14.00">into a team with a single player</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="931.5,-1229 931.5,-1355 1186.5,-1355 1186.5,-1229 931.5,-1229"/>
 </g>
 <!-- Global&#45;&gt;Team -->
 <g id="edge1" class="edge">
 <title>Global&#45;&gt;Team</title>
-<path fill="none" stroke="black" d="M1213.74,-1359.93C1209.97,-1351.09 1206.17,-1342.16 1202.47,-1333.49"/>
-<polygon fill="black" stroke="black" points="1205.65,-1332.03 1198.51,-1324.2 1199.21,-1334.77 1205.65,-1332.03"/>
+<path fill="none" stroke="black" d="M1100.74,-1390.93C1096.97,-1382.09 1093.17,-1373.16 1089.47,-1364.49"/>
+<polygon fill="black" stroke="black" points="1092.65,-1363.03 1085.51,-1355.2 1086.21,-1365.77 1092.65,-1363.03"/>
 </g>
 <!-- Neutral Army -->
-<g id="node9" class="node">
+<g id="node8" class="node">
 <title>Neutral Army</title>
-<polygon fill="#808080" fill-opacity="0.501961" stroke="transparent" points="1962,-679 1682,-679 1682,-614 1962,-614 1962,-679"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1685,-654.5 1685,-675.5 1959,-675.5 1959,-654.5 1685,-654.5"/>
-<text text-anchor="start" x="1772.5" y="-662.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Neutral Army</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1685,-616.5 1685,-652.5 1959,-652.5 1959,-616.5 1685,-616.5"/>
-<text text-anchor="start" x="1688.5" y="-638.3" font-family="Noto Serif" font-size="14.00">Any army that is not owned by a player</text>
-<text text-anchor="start" x="1688" y="-623.3" font-family="Noto Serif" font-size="14.00">Wandering monsters, Banks, Events, etc</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1682,-614 1682,-679 1962,-679 1962,-614 1682,-614"/>
+<polygon fill="#808080" fill-opacity="0.501961" stroke="transparent" points="1839,-702 1559,-702 1559,-637 1839,-637 1839,-702"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1562,-677.5 1562,-698.5 1836,-698.5 1836,-677.5 1562,-677.5"/>
+<text text-anchor="start" x="1649.5" y="-685.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Neutral Army</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1562,-639.5 1562,-675.5 1836,-675.5 1836,-639.5 1562,-639.5"/>
+<text text-anchor="start" x="1565.5" y="-661.3" font-family="Noto Serif" font-size="14.00">Any army that is not owned by a player</text>
+<text text-anchor="start" x="1565" y="-646.3" font-family="Noto Serif" font-size="14.00">Wandering monsters, Banks, Events, etc</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1559,-637 1559,-702 1839,-702 1839,-637 1559,-637"/>
 </g>
 <!-- Global&#45;&gt;Neutral Army -->
 <g id="edge2" class="edge">
 <title>Global&#45;&gt;Neutral Army</title>
-<path fill="none" stroke="black" d="M1495.07,-1396.94C1650.32,-1350.63 1822,-1262.65 1822,-1100 1822,-1100 1822,-1100 1822,-932 1822,-845.69 1822,-744.66 1822,-689.21"/>
-<polygon fill="black" stroke="black" points="1825.5,-689.07 1822,-679.07 1818.5,-689.07 1825.5,-689.07"/>
+<path fill="none" stroke="black" d="M1382.07,-1427.94C1537.32,-1381.63 1709,-1293.65 1709,-1131 1709,-1131 1709,-1131 1709,-963 1709,-873.55 1704.27,-768.8 1701.32,-712.1"/>
+<polygon fill="black" stroke="black" points="1704.81,-711.89 1700.79,-702.09 1697.82,-712.26 1704.81,-711.89"/>
 </g>
 <!-- Player -->
 <g id="node3" class="node">
 <title>Player</title>
-<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="796.5,-1162 559.5,-1162 559.5,-1036 796.5,-1036 796.5,-1162"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="563,-1138 563,-1159 794,-1159 794,-1138 563,-1138"/>
-<text text-anchor="start" x="655" y="-1145.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Player</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="563,-1115 563,-1136 794,-1136 794,-1115 563,-1115"/>
-<text text-anchor="start" x="566" y="-1122.8" font-family="Noto Serif" font-size="14.00">Propagator: </text>
-<text text-anchor="start" x="650" y="-1122.8" font-family="monospace" font-weight="bold" font-size="14.00">PLAYER_PROPAGATOR</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="563,-1092 563,-1113 794,-1113 794,-1092 563,-1092"/>
-<text text-anchor="start" x="594.5" y="-1099.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
-<text text-anchor="start" x="662.5" y="-1099.8" font-family="monospace" font-weight="bold" font-size="14.00">CPlayerState</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="563,-1039 563,-1090 794,-1090 794,-1039 563,-1039"/>
-<text text-anchor="start" x="622.5" y="-1075.8" font-family="Noto Serif" font-size="14.00">Per&#45;player team.</text>
-<text text-anchor="start" x="580" y="-1060.8" font-family="Noto Serif" font-size="14.00">All objects owned by a player</text>
-<text text-anchor="start" x="611.5" y="-1045.8" font-family="Noto Serif" font-size="14.00">belong to such node</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="559.5,-1036 559.5,-1162 796.5,-1162 796.5,-1036 559.5,-1036"/>
+<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="669.5,-1193 460.5,-1193 460.5,-1067 669.5,-1067 669.5,-1193"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="464,-1169 464,-1190 667,-1190 667,-1169 464,-1169"/>
+<text text-anchor="start" x="542" y="-1176.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Player</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="464,-1146 464,-1167 667,-1167 667,-1146 464,-1146"/>
+<text text-anchor="start" x="498.5" y="-1153.8" font-family="Noto Serif" font-size="14.00">Propagator: </text>
+<text text-anchor="start" x="582.5" y="-1153.8" font-family="monospace" font-weight="bold" font-size="14.00">PLAYER</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="464,-1123 464,-1144 667,-1144 667,-1123 464,-1123"/>
+<text text-anchor="start" x="481.5" y="-1130.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
+<text text-anchor="start" x="549.5" y="-1130.8" font-family="monospace" font-weight="bold" font-size="14.00">CPlayerState</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="464,-1070 464,-1121 667,-1121 667,-1070 464,-1070"/>
+<text text-anchor="start" x="509.5" y="-1106.8" font-family="Noto Serif" font-size="14.00">Per&#45;player team.</text>
+<text text-anchor="start" x="467" y="-1091.8" font-family="Noto Serif" font-size="14.00">All objects owned by a player</text>
+<text text-anchor="start" x="498.5" y="-1076.8" font-family="Noto Serif" font-size="14.00">belong to such node</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="460.5,-1067 460.5,-1193 669.5,-1193 669.5,-1067 460.5,-1067"/>
 </g>
 <!-- Team&#45;&gt;Player -->
 <g id="edge3" class="edge">
 <title>Team&#45;&gt;Player</title>
-<path fill="none" stroke="black" d="M1044.46,-1218.69C971.28,-1194.99 879.66,-1165.31 806.34,-1141.57"/>
-<polygon fill="black" stroke="black" points="807.17,-1138.16 796.58,-1138.4 805.01,-1144.82 807.17,-1138.16"/>
+<path fill="none" stroke="black" d="M931.46,-1249.69C853.47,-1224.43 754.53,-1192.39 679.13,-1167.96"/>
+<polygon fill="black" stroke="black" points="680.18,-1164.63 669.59,-1164.87 678.02,-1171.28 680.18,-1164.63"/>
 </g>
 <!-- Wandering Hero -->
-<g id="node8" class="node">
+<g id="node7" class="node">
 <title>Wandering Hero</title>
-<polygon fill="#808080" fill-opacity="0.501961" stroke="transparent" points="1664,-679 1428,-679 1428,-614 1664,-614 1664,-679"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1431,-654.5 1431,-675.5 1661,-675.5 1661,-654.5 1431,-654.5"/>
-<text text-anchor="start" x="1487" y="-662.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Wandering Hero</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1431,-616.5 1431,-652.5 1661,-652.5 1661,-616.5 1431,-616.5"/>
-<text text-anchor="start" x="1472" y="-638.3" font-family="Noto Serif" font-size="14.00">Hero that is currently</text>
-<text text-anchor="start" x="1434" y="-623.3" font-family="Noto Serif" font-size="14.00">moving on map, outside of towns</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1428,-614 1428,-679 1664,-679 1664,-614 1428,-614"/>
+<polygon fill="#808080" fill-opacity="0.501961" stroke="transparent" points="1522,-702 1286,-702 1286,-637 1522,-637 1522,-702"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1289,-677.5 1289,-698.5 1519,-698.5 1519,-677.5 1289,-677.5"/>
+<text text-anchor="start" x="1345" y="-685.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Wandering Hero</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1289,-639.5 1289,-675.5 1519,-675.5 1519,-639.5 1289,-639.5"/>
+<text text-anchor="start" x="1330" y="-661.3" font-family="Noto Serif" font-size="14.00">Hero that is currently</text>
+<text text-anchor="start" x="1292" y="-646.3" font-family="Noto Serif" font-size="14.00">moving on map, outside of towns</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1286,-637 1286,-702 1522,-702 1522,-637 1286,-637"/>
 </g>
 <!-- Player&#45;&gt;Wandering Hero -->
 <g id="edge5" class="edge">
 <title>Player&#45;&gt;Wandering Hero</title>
-<path fill="none" stroke="black" d="M796.56,-1096.14C1064.93,-1090.69 1700.65,-1070.42 1765,-1000 1851.75,-905.07 1786.66,-810.2 1696,-719 1682.06,-704.97 1664.83,-693.33 1647.07,-683.81"/>
-<polygon fill="black" stroke="black" points="1648.24,-680.47 1637.75,-679.01 1645.04,-686.7 1648.24,-680.47"/>
+<path fill="none" stroke="black" d="M669.91,-1127.41C929.66,-1122.38 1586.53,-1102.64 1652,-1031 1749.85,-923.93 1563.78,-776.04 1462.88,-707.78"/>
+<polygon fill="black" stroke="black" points="1464.54,-704.67 1454.28,-702.01 1460.64,-710.49 1464.54,-704.67"/>
 </g>
 <!-- Owned Army -->
-<g id="node10" class="node">
+<g id="node9" class="node">
 <title>Owned Army</title>
-<polygon fill="#808080" fill-opacity="0.501961" stroke="transparent" points="555.5,-679 356.5,-679 356.5,-614 555.5,-614 555.5,-679"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="360,-654.5 360,-675.5 553,-675.5 553,-654.5 360,-654.5"/>
-<text text-anchor="start" x="409.5" y="-662.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Owned Army</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="360,-616.5 360,-652.5 553,-652.5 553,-616.5 360,-616.5"/>
-<text text-anchor="start" x="373" y="-638.3" font-family="Noto Serif" font-size="14.00">Army owned by a player.</text>
-<text text-anchor="start" x="363" y="-623.3" font-family="Noto Serif" font-size="14.00">Mines, Garrisons, Dwellings</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="356.5,-614 356.5,-679 555.5,-679 555.5,-614 356.5,-614"/>
+<polygon fill="#808080" fill-opacity="0.501961" stroke="transparent" points="555.5,-702 356.5,-702 356.5,-637 555.5,-637 555.5,-702"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="360,-677.5 360,-698.5 553,-698.5 553,-677.5 360,-677.5"/>
+<text text-anchor="start" x="409.5" y="-685.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Owned Army</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="360,-639.5 360,-675.5 553,-675.5 553,-639.5 360,-639.5"/>
+<text text-anchor="start" x="373" y="-661.3" font-family="Noto Serif" font-size="14.00">Army owned by a player.</text>
+<text text-anchor="start" x="363" y="-646.3" font-family="Noto Serif" font-size="14.00">Mines, Garrisons, Dwellings</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="356.5,-637 356.5,-702 555.5,-702 555.5,-637 356.5,-637"/>
 </g>
 <!-- Player&#45;&gt;Owned Army -->
 <g id="edge6" class="edge">
 <title>Player&#45;&gt;Owned Army</title>
-<path fill="none" stroke="black" d="M559.38,-1065.77C526.78,-1051.05 494.95,-1029.97 475,-1000 411.22,-904.22 430.78,-759.31 445.74,-688.86"/>
-<polygon fill="black" stroke="black" points="449.17,-689.54 447.9,-679.03 442.33,-688.04 449.17,-689.54"/>
+<path fill="none" stroke="black" d="M460.3,-1103.68C422.65,-1088.98 384.05,-1066.14 362,-1031 297.12,-927.64 378.94,-780.81 426.46,-710.73"/>
+<polygon fill="black" stroke="black" points="429.56,-712.39 432.35,-702.17 423.8,-708.42 429.56,-712.39"/>
 </g>
 <!-- Owned Object -->
-<g id="node11" class="node">
+<g id="node10" class="node">
 <title>Owned Object</title>
-<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="338,-683 0,-683 0,-610 338,-610 338,-683"/>
+<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="338,-706 0,-706 0,-633 338,-633 338,-706"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="3,-681.5 3,-702.5 335,-702.5 335,-681.5 3,-681.5"/>
+<text text-anchor="start" x="119" y="-689.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Owned Object</text>
 <polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="3,-658.5 3,-679.5 335,-679.5 335,-658.5 3,-658.5"/>
-<text text-anchor="start" x="119" y="-666.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Owned Object</text>
+<text text-anchor="start" x="6" y="-665.3" font-family="Noto Serif" font-size="14.00">Other objects owned by a player, like Lighthouse</text>
 <polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="3,-635.5 3,-656.5 335,-656.5 335,-635.5 3,-635.5"/>
-<text text-anchor="start" x="6" y="-642.3" font-family="Noto Serif" font-size="14.00">Other objects owned by a player, like Lighthouse</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="3,-612.5 3,-633.5 335,-633.5 335,-612.5 3,-612.5"/>
-<text text-anchor="start" x="50" y="-619.3" font-family="Noto Serif" font-size="14.00">Contains Flaggable Objects bonuses</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="0,-610 0,-683 338,-683 338,-610 0,-610"/>
+<text text-anchor="start" x="50" y="-642.3" font-family="Noto Serif" font-size="14.00">Contains Flaggable Objects bonuses</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="0,-633 0,-706 338,-706 338,-633 0,-633"/>
 </g>
 <!-- Player&#45;&gt;Owned Object -->
 <g id="edge7" class="edge">
 <title>Player&#45;&gt;Owned Object</title>
-<path fill="none" stroke="black" d="M559.41,-1066.51C515.59,-1051.13 467.31,-1029.44 429,-1000 316.53,-913.58 229.92,-765.96 191.2,-692.2"/>
-<polygon fill="black" stroke="black" points="194.22,-690.41 186.5,-683.15 188.01,-693.64 194.22,-690.41"/>
+<path fill="none" stroke="black" d="M460.28,-1102.84C416.92,-1087.72 368.75,-1064.79 334,-1031 242.09,-941.63 196.58,-791.5 178.61,-716.31"/>
+<polygon fill="black" stroke="black" points="181.93,-715.16 176.25,-706.21 175.11,-716.75 181.93,-715.16"/>
 </g>
 <!-- Town and visiting hero -->
-<g id="node15" class="node">
+<g id="node14" class="node">
 <title>Town and visiting hero</title>
-<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="871.5,-1000 484.5,-1000 484.5,-866 871.5,-866 871.5,-1000"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="488,-976 488,-997 869,-997 869,-976 488,-976"/>
-<text text-anchor="start" x="595.5" y="-983.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Town and Visiting Hero</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="488,-953 488,-974 869,-974 869,-953 488,-953"/>
-<text text-anchor="start" x="537" y="-960.8" font-family="Noto Serif" font-size="14.00">Propagator: </text>
-<text text-anchor="start" x="621" y="-960.8" font-family="monospace" font-weight="bold" font-size="14.00">VISITED_TOWN_AND_VISITOR</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="488,-930 488,-951 869,-951 869,-930 488,-930"/>
-<text text-anchor="start" x="561.5" y="-937.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
-<text text-anchor="start" x="629.5" y="-937.8" font-family="monospace" font-weight="bold" font-size="14.00">CTownAndVisitingHero</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="488,-892 488,-928 869,-928 869,-892 488,-892"/>
-<text text-anchor="start" x="579" y="-913.8" font-family="Noto Serif" font-size="14.00">Helper node that exists solely</text>
-<text text-anchor="start" x="502.5" y="-898.8" font-family="Noto Serif" font-size="14.00">to propagate bonuses to both town and visiting hero</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="488,-869 488,-890 869,-890 869,-869 488,-869"/>
-<text text-anchor="start" x="491" y="-875.8" font-family="Noto Serif" font-size="14.00">Note: Neutral towns are attached to global node instead</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="484.5,-866 484.5,-1000 871.5,-1000 871.5,-866 484.5,-866"/>
+<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="758.5,-1031 371.5,-1031 371.5,-897 758.5,-897 758.5,-1031"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="375,-1007 375,-1028 756,-1028 756,-1007 375,-1007"/>
+<text text-anchor="start" x="482.5" y="-1014.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Town and Visiting Hero</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="375,-984 375,-1005 756,-1005 756,-984 375,-984"/>
+<text text-anchor="start" x="457" y="-991.8" font-family="Noto Serif" font-size="14.00">Propagator: </text>
+<text text-anchor="start" x="541" y="-991.8" font-family="monospace" font-weight="bold" font-size="14.00">TOWN_AND_VISITOR</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="375,-961 375,-982 756,-982 756,-961 375,-961"/>
+<text text-anchor="start" x="448.5" y="-968.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
+<text text-anchor="start" x="516.5" y="-968.8" font-family="monospace" font-weight="bold" font-size="14.00">CTownAndVisitingHero</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="375,-923 375,-959 756,-959 756,-923 375,-923"/>
+<text text-anchor="start" x="466" y="-944.8" font-family="Noto Serif" font-size="14.00">Helper node that exists solely</text>
+<text text-anchor="start" x="389.5" y="-929.8" font-family="Noto Serif" font-size="14.00">to propagate bonuses to both town and visiting hero</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="375,-900 375,-921 756,-921 756,-900 375,-900"/>
+<text text-anchor="start" x="378" y="-906.8" font-family="Noto Serif" font-size="14.00">Note: Neutral towns are attached to global node instead</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="371.5,-897 371.5,-1031 758.5,-1031 758.5,-897 371.5,-897"/>
 </g>
 <!-- Player&#45;&gt;Town and visiting hero -->
 <g id="edge4" class="edge">
 <title>Player&#45;&gt;Town and visiting hero</title>
-<path fill="none" stroke="black" d="M678,-1036C678,-1027.59 678,-1018.88 678,-1010.26"/>
-<polygon fill="black" stroke="black" points="681.5,-1010.19 678,-1000.19 674.5,-1010.19 681.5,-1010.19"/>
+<path fill="none" stroke="black" d="M565,-1067C565,-1058.59 565,-1049.88 565,-1041.26"/>
+<polygon fill="black" stroke="black" points="568.5,-1041.19 565,-1031.19 561.5,-1041.19 568.5,-1041.19"/>
 </g>
 <!-- Hero -->
 <g id="node4" class="node">
 <title>Hero</title>
-<polygon fill="#808080" fill-opacity="0.501961" stroke="transparent" points="1435.5,-574 966.5,-574 966.5,-410 1435.5,-410 1435.5,-574"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="970,-550 970,-571 1433,-571 1433,-550 970,-550"/>
-<text text-anchor="start" x="1183.5" y="-557.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Hero</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="970,-527 970,-548 1433,-548 1433,-527 970,-527"/>
-<text text-anchor="start" x="1142.5" y="-534.8" font-family="Noto Serif" font-size="14.00">Propagator: </text>
-<text text-anchor="start" x="1226.5" y="-534.8" font-family="monospace" font-weight="bold" font-size="14.00">HERO</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="970,-504 970,-525 1433,-525 1433,-504 970,-504"/>
-<text text-anchor="start" x="1109.5" y="-511.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
-<text text-anchor="start" x="1177.5" y="-511.8" font-family="monospace" font-weight="bold" font-size="14.00">CGHeroInstance</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="970,-451 970,-502 1433,-502 1433,-451 970,-451"/>
-<text text-anchor="start" x="1016.5" y="-487.8" font-family="Noto Serif" font-size="14.00">Represents a hero, either owned by player or in prison.</text>
-<text text-anchor="start" x="1054" y="-472.8" font-family="Noto Serif" font-size="14.00">Bonuses from specialty and secondary skills</text>
-<text text-anchor="start" x="1090" y="-457.8" font-family="Noto Serif" font-size="14.00">are attached directly to this node</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="970,-413 970,-449 1433,-449 1433,-413 970,-413"/>
-<text text-anchor="start" x="1023.5" y="-434.8" font-family="Noto Serif" font-size="14.00">Contains per&#45;hero global bonuses, specialty bonuses, </text>
-<text text-anchor="start" x="973" y="-419.8" font-family="Noto Serif" font-size="14.00">primary and secondary skill bonuses, campaign primary skill bonus</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="966.5,-410 966.5,-574 1435.5,-574 1435.5,-410 966.5,-410"/>
+<polygon fill="#808080" fill-opacity="0.501961" stroke="transparent" points="1209.5,-597 740.5,-597 740.5,-433 1209.5,-433 1209.5,-597"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="744,-573 744,-594 1207,-594 1207,-573 744,-573"/>
+<text text-anchor="start" x="957.5" y="-580.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Hero</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="744,-550 744,-571 1207,-571 1207,-550 744,-550"/>
+<text text-anchor="start" x="916.5" y="-557.8" font-family="Noto Serif" font-size="14.00">Propagator: </text>
+<text text-anchor="start" x="1000.5" y="-557.8" font-family="monospace" font-weight="bold" font-size="14.00">HERO</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="744,-527 744,-548 1207,-548 1207,-527 744,-527"/>
+<text text-anchor="start" x="883.5" y="-534.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
+<text text-anchor="start" x="951.5" y="-534.8" font-family="monospace" font-weight="bold" font-size="14.00">CGHeroInstance</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="744,-474 744,-525 1207,-525 1207,-474 744,-474"/>
+<text text-anchor="start" x="790.5" y="-510.8" font-family="Noto Serif" font-size="14.00">Represents a hero, either owned by player or in prison.</text>
+<text text-anchor="start" x="828" y="-495.8" font-family="Noto Serif" font-size="14.00">Bonuses from specialty and secondary skills</text>
+<text text-anchor="start" x="864" y="-480.8" font-family="Noto Serif" font-size="14.00">are attached directly to this node</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="744,-436 744,-472 1207,-472 1207,-436 744,-436"/>
+<text text-anchor="start" x="797.5" y="-457.8" font-family="Noto Serif" font-size="14.00">Contains per&#45;hero global bonuses, specialty bonuses, </text>
+<text text-anchor="start" x="747" y="-442.8" font-family="Noto Serif" font-size="14.00">primary and secondary skill bonuses, campaign primary skill bonus</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="740.5,-433 740.5,-597 1209.5,-597 1209.5,-433 740.5,-433"/>
 </g>
 <!-- Army -->
 <g id="node19" class="node">
 <title>Army</title>
-<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="1052,-374 604,-374 604,-263 1052,-263 1052,-374"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="607,-349.5 607,-370.5 1049,-370.5 1049,-349.5 607,-349.5"/>
-<text text-anchor="start" x="808" y="-357.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Army</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="607,-326.5 607,-347.5 1049,-347.5 1049,-326.5 607,-326.5"/>
-<text text-anchor="start" x="736" y="-334.3" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
-<text text-anchor="start" x="804" y="-334.3" font-family="monospace" font-weight="bold" font-size="14.00">CArmedInstance</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="607,-288.5 607,-324.5 1049,-324.5 1049,-288.5 607,-288.5"/>
-<text text-anchor="start" x="687" y="-310.3" font-family="Noto Serif" font-size="14.00">Represents any object that can hold army,</text>
-<text text-anchor="start" x="632.5" y="-295.3" font-family="Noto Serif" font-size="14.00">such as town, hero, mines, garrisons, wandering monsters</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="607,-265.5 607,-286.5 1049,-286.5 1049,-265.5 607,-265.5"/>
-<text text-anchor="start" x="610" y="-272.3" font-family="Noto Serif" font-size="14.00">Contain anti&#45;magic garrison bonus, faction mixing morale bonus</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="604,-263 604,-374 1052,-374 1052,-263 604,-263"/>
+<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="1199,-397 751,-397 751,-263 1199,-263 1199,-397"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="754,-373 754,-394 1196,-394 1196,-373 754,-373"/>
+<text text-anchor="start" x="955" y="-380.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Army</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="754,-350 754,-371 1196,-371 1196,-350 754,-350"/>
+<text text-anchor="start" x="916" y="-357.8" font-family="Noto Serif" font-size="14.00">Propagator: </text>
+<text text-anchor="start" x="1000" y="-357.8" font-family="monospace" font-weight="bold" font-size="14.00">ARMY</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="754,-327 754,-348 1196,-348 1196,-327 754,-327"/>
+<text text-anchor="start" x="883" y="-334.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
+<text text-anchor="start" x="951" y="-334.8" font-family="monospace" font-weight="bold" font-size="14.00">CArmedInstance</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="754,-289 754,-325 1196,-325 1196,-289 754,-289"/>
+<text text-anchor="start" x="834" y="-310.8" font-family="Noto Serif" font-size="14.00">Represents any object that can hold army,</text>
+<text text-anchor="start" x="779.5" y="-295.8" font-family="Noto Serif" font-size="14.00">such as town, hero, mines, garrisons, wandering monsters</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="754,-266 754,-287 1196,-287 1196,-266 754,-266"/>
+<text text-anchor="start" x="757" y="-272.8" font-family="Noto Serif" font-size="14.00">Contain anti&#45;magic garrison bonus, faction mixing morale bonus</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="751,-263 751,-397 1199,-397 1199,-263 751,-263"/>
 </g>
 <!-- Hero&#45;&gt;Army -->
 <g id="edge30" class="edge">
 <title>Hero&#45;&gt;Army</title>
-<path fill="none" stroke="black" d="M1024.6,-409.9C1001.41,-399.23 978.02,-388.47 955.85,-378.28"/>
-<polygon fill="black" stroke="black" points="957.31,-375.1 946.76,-374.1 954.38,-381.46 957.31,-375.1"/>
-</g>
-<!-- Combat -->
-<g id="node5" class="node">
-<title>Combat</title>
-<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="800,-559 350,-559 350,-425 800,-425 800,-559"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="353,-535 353,-556 797,-556 797,-535 353,-535"/>
-<text text-anchor="start" x="547.5" y="-542.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Combat</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="353,-512 353,-533 797,-533 797,-512 353,-512"/>
-<text text-anchor="start" x="487.5" y="-519.8" font-family="Noto Serif" font-size="14.00">Propagator: </text>
-<text text-anchor="start" x="571.5" y="-519.8" font-family="monospace" font-weight="bold" font-size="14.00">BATTLE_WIDE</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="353,-489 353,-510 797,-510 797,-489 353,-489"/>
-<text text-anchor="start" x="499.5" y="-496.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
-<text text-anchor="start" x="567.5" y="-496.8" font-family="monospace" font-weight="bold" font-size="14.00">BattleInfo</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="353,-451 353,-487 797,-487 797,-451 353,-451"/>
-<text text-anchor="start" x="434.5" y="-472.8" font-family="Noto Serif" font-size="14.00">Node that contains both sides of a combat</text>
-<text text-anchor="start" x="356" y="-457.8" font-family="Noto Serif" font-size="14.00">Anything propagated to this node will affect both sides in combat</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="353,-428 353,-449 797,-449 797,-428 353,-428"/>
-<text text-anchor="start" x="417" y="-434.8" font-family="Noto Serif" font-size="14.00">Contains battlefield and native terrain bonuses</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="350,-425 350,-559 800,-559 800,-425 350,-425"/>
-</g>
-<!-- Combat&#45;&gt;Army -->
-<g id="edge11" class="edge">
-<title>Combat&#45;&gt;Army</title>
-<path fill="none" stroke="black" d="M672.45,-424.94C694.33,-410.11 717.43,-394.45 738.94,-379.87"/>
-<polygon fill="black" stroke="black" points="741.1,-382.63 747.42,-374.13 737.17,-376.84 741.1,-382.63"/>
+<path fill="none" stroke="black" d="M975,-432.93C975,-424.44 975,-415.82 975,-407.38"/>
+<polygon fill="black" stroke="black" points="978.5,-407.19 975,-397.19 971.5,-407.19 978.5,-407.19"/>
 </g>
 <!-- Visiting Hero -->
-<g id="node6" class="node">
+<g id="node5" class="node">
 <title>Visiting Hero</title>
-<polygon fill="#808080" fill-opacity="0.501961" stroke="transparent" points="1371.5,-679 1162.5,-679 1162.5,-614 1371.5,-614 1371.5,-679"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1166,-654.5 1166,-675.5 1369,-675.5 1369,-654.5 1166,-654.5"/>
-<text text-anchor="start" x="1220.5" y="-662.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Visiting Hero</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1166,-616.5 1166,-652.5 1369,-652.5 1369,-616.5 1166,-616.5"/>
-<text text-anchor="start" x="1193.5" y="-638.3" font-family="Noto Serif" font-size="14.00">Hero that is currently</text>
-<text text-anchor="start" x="1169" y="-623.3" font-family="Noto Serif" font-size="14.00">visiting owned or allied town</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1162.5,-614 1162.5,-679 1371.5,-679 1371.5,-614 1162.5,-614"/>
+<polygon fill="#808080" fill-opacity="0.501961" stroke="transparent" points="1150.5,-702 941.5,-702 941.5,-637 1150.5,-637 1150.5,-702"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="945,-677.5 945,-698.5 1148,-698.5 1148,-677.5 945,-677.5"/>
+<text text-anchor="start" x="999.5" y="-685.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Visiting Hero</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="945,-639.5 945,-675.5 1148,-675.5 1148,-639.5 945,-639.5"/>
+<text text-anchor="start" x="972.5" y="-661.3" font-family="Noto Serif" font-size="14.00">Hero that is currently</text>
+<text text-anchor="start" x="948" y="-646.3" font-family="Noto Serif" font-size="14.00">visiting owned or allied town</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="941.5,-637 941.5,-702 1150.5,-702 1150.5,-637 941.5,-637"/>
 </g>
 <!-- Visiting Hero&#45;&gt;Hero -->
 <g id="edge27" class="edge">
 <title>Visiting Hero&#45;&gt;Hero</title>
-<path fill="none" stroke="black" d="M1253.32,-613.9C1249.37,-604.77 1244.85,-594.32 1240.15,-583.46"/>
-<polygon fill="black" stroke="black" points="1243.36,-582.06 1236.18,-574.28 1236.94,-584.84 1243.36,-582.06"/>
+<path fill="none" stroke="black" d="M1031.29,-636.9C1027.04,-627.77 1022.17,-617.32 1017.12,-606.46"/>
+<polygon fill="black" stroke="black" points="1020.23,-604.86 1012.84,-597.28 1013.89,-607.82 1020.23,-604.86"/>
 </g>
-<!-- Garrisoned Hero -->
-<g id="node7" class="node">
-<title>Garrisoned Hero</title>
-<polygon fill="#808080" fill-opacity="0.501961" stroke="transparent" points="1106,-679 856,-679 856,-614 1106,-614 1106,-679"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="859,-654.5 859,-675.5 1103,-675.5 1103,-654.5 859,-654.5"/>
-<text text-anchor="start" x="921" y="-662.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Garrisoned Hero</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="859,-616.5 859,-652.5 1103,-652.5 1103,-616.5 859,-616.5"/>
-<text text-anchor="start" x="907" y="-638.3" font-family="Noto Serif" font-size="14.00">Hero that is currently</text>
-<text text-anchor="start" x="862" y="-623.3" font-family="Noto Serif" font-size="14.00">placed in a garrison of owned town</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="856,-614 856,-679 1106,-679 1106,-614 856,-614"/>
-</g>
-<!-- Garrisoned Hero&#45;&gt;Hero -->
+<!-- Hero defending town -->
+<g id="node6" class="node">
+<title>Hero defending town</title>
+<polygon fill="#808080" fill-opacity="0.501961" stroke="transparent" points="876,-702 612,-702 612,-637 876,-637 876,-702"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="615,-677.5 615,-698.5 873,-698.5 873,-677.5 615,-677.5"/>
+<text text-anchor="start" x="669" y="-685.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Hero defending town</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="615,-639.5 615,-675.5 873,-675.5 873,-639.5 615,-639.5"/>
+<text text-anchor="start" x="670" y="-661.3" font-family="Noto Serif" font-size="14.00">Hero that is currently</text>
+<text text-anchor="start" x="618" y="-646.3" font-family="Noto Serif" font-size="14.00">fighting on a defending side in a siege</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="612,-637 612,-702 876,-702 876,-637 612,-637"/>
+</g>
+<!-- Hero defending town&#45;&gt;Hero -->
 <g id="edge28" class="edge">
-<title>Garrisoned Hero&#45;&gt;Hero</title>
-<path fill="none" stroke="black" d="M1026.58,-613.9C1041.08,-603.86 1057.87,-592.21 1075.21,-580.2"/>
-<polygon fill="black" stroke="black" points="1077.52,-582.85 1083.75,-574.28 1073.54,-577.1 1077.52,-582.85"/>
+<title>Hero defending town&#45;&gt;Hero</title>
+<path fill="none" stroke="black" d="M791.86,-636.9C807.22,-626.76 825.04,-615 843.41,-602.87"/>
+<polygon fill="black" stroke="black" points="845.47,-605.71 851.89,-597.28 841.61,-599.87 845.47,-605.71"/>
 </g>
 <!-- Wandering Hero&#45;&gt;Hero -->
 <g id="edge29" class="edge">
 <title>Wandering Hero&#45;&gt;Hero</title>
-<path fill="none" stroke="black" d="M1474.52,-613.9C1450.56,-603.31 1422.6,-590.95 1393.87,-578.25"/>
-<polygon fill="black" stroke="black" points="1395.08,-574.96 1384.51,-574.12 1392.25,-581.36 1395.08,-574.96"/>
+<path fill="none" stroke="black" d="M1315.37,-636.99C1284.97,-626.19 1249.34,-613.52 1212.8,-600.53"/>
+<polygon fill="black" stroke="black" points="1213.58,-597.09 1202.98,-597.04 1211.23,-603.69 1213.58,-597.09"/>
 </g>
 <!-- Neutral Army&#45;&gt;Army -->
 <g id="edge25" class="edge">
 <title>Neutral Army&#45;&gt;Army</title>
-<path fill="none" stroke="black" d="M1784.91,-613.89C1720.6,-561.13 1582.08,-456.33 1445,-410 1285.7,-356.16 1232.22,-399.85 1062.07,-374.08"/>
-<polygon fill="black" stroke="black" points="1062.45,-370.6 1052.03,-372.5 1061.36,-377.51 1062.45,-370.6"/>
+<path fill="none" stroke="black" d="M1558.9,-638.05C1549.48,-636.26 1540.11,-634.56 1531,-633 1470.47,-622.64 1302.48,-638.27 1257,-597 1201.59,-546.72 1267.27,-490.16 1219,-433 1209.61,-421.88 1198.81,-411.96 1187.09,-403.1"/>
+<polygon fill="black" stroke="black" points="1188.99,-400.15 1178.83,-397.13 1184.89,-405.82 1188.99,-400.15"/>
 </g>
 <!-- Owned Army&#45;&gt;Army -->
 <g id="edge26" class="edge">
 <title>Owned Army&#45;&gt;Army</title>
-<path fill="none" stroke="black" d="M380.63,-613.89C364.79,-603.59 350.14,-590.44 341,-574 305.59,-510.29 294.41,-466.05 341,-410 374.42,-369.8 485.94,-347.37 593.84,-334.9"/>
-<polygon fill="black" stroke="black" points="594.38,-338.36 603.93,-333.76 593.6,-331.41 594.38,-338.36"/>
+<path fill="none" stroke="black" d="M462.31,-636.86C474.15,-585.95 504.66,-486.2 570,-433 618.57,-393.45 680.47,-368.98 741.18,-353.92"/>
+<polygon fill="black" stroke="black" points="742,-357.32 750.91,-351.58 740.37,-350.51 742,-357.32"/>
 </g>
 <!-- Town -->
-<g id="node12" class="node">
+<g id="node11" class="node">
 <title>Town</title>
-<polygon fill="#808080" fill-opacity="0.501961" stroke="transparent" points="925,-822.5 699,-822.5 699,-726.5 925,-726.5 925,-822.5"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="702,-798.5 702,-819.5 922,-819.5 922,-798.5 702,-798.5"/>
-<text text-anchor="start" x="792.5" y="-806.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Town</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="702,-775.5 702,-796.5 922,-796.5 922,-775.5 702,-775.5"/>
-<text text-anchor="start" x="720" y="-783.3" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
-<text text-anchor="start" x="788" y="-783.3" font-family="monospace" font-weight="bold" font-size="14.00">CGTownInstance</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="702,-752.5 702,-773.5 922,-773.5 922,-752.5 702,-752.5"/>
-<text text-anchor="start" x="721" y="-759.3" font-family="Noto Serif" font-size="14.00">Represents a town on map.</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="702,-729.5 702,-750.5 922,-750.5 922,-729.5 702,-729.5"/>
-<text text-anchor="start" x="705" y="-736.3" font-family="Noto Serif" font-size="14.00">Contains town building bonuses</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="699,-726.5 699,-822.5 925,-822.5 925,-726.5 699,-726.5"/>
-</g>
-<!-- Town&#45;&gt;Garrisoned Hero -->
+<polygon fill="#808080" fill-opacity="0.501961" stroke="transparent" points="692,-861 466,-861 466,-742 692,-742 692,-861"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="469,-836.5 469,-857.5 689,-857.5 689,-836.5 469,-836.5"/>
+<text text-anchor="start" x="559.5" y="-844.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Town</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="469,-813.5 469,-834.5 689,-834.5 689,-813.5 469,-813.5"/>
+<text text-anchor="start" x="520" y="-821.3" font-family="Noto Serif" font-size="14.00">Propagator: </text>
+<text text-anchor="start" x="604" y="-821.3" font-family="monospace" font-weight="bold" font-size="14.00">TOWN</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="469,-790.5 469,-811.5 689,-811.5 689,-790.5 469,-790.5"/>
+<text text-anchor="start" x="487" y="-798.3" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
+<text text-anchor="start" x="555" y="-798.3" font-family="monospace" font-weight="bold" font-size="14.00">CGTownInstance</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="469,-767.5 469,-788.5 689,-788.5 689,-767.5 469,-767.5"/>
+<text text-anchor="start" x="488" y="-774.3" font-family="Noto Serif" font-size="14.00">Represents a town on map.</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="469,-744.5 469,-765.5 689,-765.5 689,-744.5 469,-744.5"/>
+<text text-anchor="start" x="472" y="-751.3" font-family="Noto Serif" font-size="14.00">Contains town building bonuses</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="466,-742 466,-861 692,-861 692,-742 466,-742"/>
+</g>
+<!-- Town&#45;&gt;Hero defending town -->
 <g id="edge23" class="edge">
-<title>Town&#45;&gt;Garrisoned Hero</title>
-<path fill="none" stroke="black" d="M875.41,-726.22C893.52,-712.72 913.01,-698.19 930.26,-685.33"/>
-<polygon fill="black" stroke="black" points="932.6,-687.95 938.53,-679.17 928.42,-682.34 932.6,-687.95"/>
+<title>Town&#45;&gt;Hero defending town</title>
+<path fill="none" stroke="black" d="M653.29,-741.97C667.74,-730.58 682.53,-718.93 695.87,-708.42"/>
+<polygon fill="black" stroke="black" points="698.27,-710.98 703.96,-702.04 693.94,-705.48 698.27,-710.98"/>
 </g>
 <!-- Town&#45;&gt;Army -->
 <g id="edge24" class="edge">
 <title>Town&#45;&gt;Army</title>
-<path fill="none" stroke="black" d="M813.67,-726.2C816.55,-644.44 822.46,-476.58 825.72,-384.13"/>
-<polygon fill="black" stroke="black" points="829.22,-384.18 826.08,-374.06 822.23,-383.93 829.22,-384.18"/>
+<path fill="none" stroke="black" d="M581.26,-741.61C584.03,-708.79 590.04,-667.67 603,-633 640.02,-533.92 650.94,-500.95 732,-433 745.61,-421.59 760.68,-411.3 776.45,-402.04"/>
+<polygon fill="black" stroke="black" points="778.32,-405 785.28,-397.01 774.86,-398.92 778.32,-405"/>
 </g>
 <!-- Artifact Instance -->
-<g id="node13" class="node">
+<g id="node12" class="node">
 <title>Artifact Instance</title>
-<polygon fill="#00ffff" fill-opacity="0.501961" stroke="transparent" points="1687,-830 1311,-830 1311,-719 1687,-719 1687,-830"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1314,-805.5 1314,-826.5 1684,-826.5 1684,-805.5 1314,-805.5"/>
-<text text-anchor="start" x="1438.5" y="-813.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Artifact Instance</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1314,-782.5 1314,-803.5 1684,-803.5 1684,-782.5 1314,-782.5"/>
-<text text-anchor="start" x="1394.5" y="-790.3" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
-<text text-anchor="start" x="1462.5" y="-790.3" font-family="monospace" font-weight="bold" font-size="14.00">CArtifactInstance</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1314,-744.5 1314,-780.5 1684,-780.5 1684,-744.5 1314,-744.5"/>
-<text text-anchor="start" x="1344" y="-766.3" font-family="Noto Serif" font-size="14.00">Represents a particular instance of an artifact</text>
-<text text-anchor="start" x="1402" y="-751.3" font-family="Noto Serif" font-size="14.00"> that hero can equip or trade</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1314,-721.5 1314,-742.5 1684,-742.5 1684,-721.5 1314,-721.5"/>
-<text text-anchor="start" x="1317" y="-728.3" font-family="Noto Serif" font-size="14.00">Contains bonuses of spell scrolls and growing artifacts</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1311,-719 1311,-830 1687,-830 1687,-719 1311,-719"/>
+<polygon fill="#00ffff" fill-opacity="0.501961" stroke="transparent" points="1454,-857 1078,-857 1078,-746 1454,-746 1454,-857"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1081,-832.5 1081,-853.5 1451,-853.5 1451,-832.5 1081,-832.5"/>
+<text text-anchor="start" x="1205.5" y="-840.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Artifact Instance</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1081,-809.5 1081,-830.5 1451,-830.5 1451,-809.5 1081,-809.5"/>
+<text text-anchor="start" x="1161.5" y="-817.3" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
+<text text-anchor="start" x="1229.5" y="-817.3" font-family="monospace" font-weight="bold" font-size="14.00">CArtifactInstance</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1081,-771.5 1081,-807.5 1451,-807.5 1451,-771.5 1081,-771.5"/>
+<text text-anchor="start" x="1111" y="-793.3" font-family="Noto Serif" font-size="14.00">Represents a particular instance of an artifact</text>
+<text text-anchor="start" x="1169" y="-778.3" font-family="Noto Serif" font-size="14.00"> that hero can equip or trade</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1081,-748.5 1081,-769.5 1451,-769.5 1451,-748.5 1081,-748.5"/>
+<text text-anchor="start" x="1084" y="-755.3" font-family="Noto Serif" font-size="14.00">Contains bonuses of spell scrolls and growing artifacts</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1078,-746 1078,-857 1454,-857 1454,-746 1078,-746"/>
 </g>
 <!-- Artifact Instance&#45;&gt;Hero -->
 <g id="edge19" class="edge">
 <title>Artifact Instance&#45;&gt;Hero</title>
-<path fill="none" stroke="black" d="M1446.84,-718.76C1437.06,-707.35 1427.31,-695.1 1419,-683 1398.29,-652.85 1405.59,-637.08 1381,-610 1371.53,-599.57 1360.94,-589.68 1349.76,-580.38"/>
-<polygon fill="black" stroke="black" points="1351.91,-577.62 1341.93,-574.04 1347.5,-583.06 1351.91,-577.62"/>
+<path fill="none" stroke="black" d="M1239.36,-745.63C1220.54,-710.63 1192.86,-665.85 1160,-633 1149.53,-622.53 1137.96,-612.54 1125.87,-603.12"/>
+<polygon fill="black" stroke="black" points="1127.93,-600.29 1117.85,-597.01 1123.69,-605.86 1127.93,-600.29"/>
 </g>
 <!-- Boat -->
-<g id="node14" class="node">
+<g id="node13" class="node">
 <title>Boat</title>
-<polygon fill="#00ffff" fill-opacity="0.501961" stroke="transparent" points="1254.5,-822.5 943.5,-822.5 943.5,-726.5 1254.5,-726.5 1254.5,-822.5"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="947,-798.5 947,-819.5 1252,-819.5 1252,-798.5 947,-798.5"/>
-<text text-anchor="start" x="1083" y="-806.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Boat</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="947,-775.5 947,-796.5 1252,-796.5 1252,-775.5 947,-775.5"/>
-<text text-anchor="start" x="1040.5" y="-783.3" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
-<text text-anchor="start" x="1108.5" y="-783.3" font-family="monospace" font-weight="bold" font-size="14.00">CGBoat</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="947,-752.5 947,-773.5 1252,-773.5 1252,-752.5 947,-752.5"/>
-<text text-anchor="start" x="950" y="-759.3" font-family="Noto Serif" font-size="14.00">Represents a boat or other type of transport.</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="947,-729.5 947,-750.5 1252,-750.5 1252,-729.5 947,-729.5"/>
-<text text-anchor="start" x="953.5" y="-736.3" font-family="Noto Serif" font-size="14.00">Contains bonuses provided to boarded hero</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="943.5,-726.5 943.5,-822.5 1254.5,-822.5 1254.5,-726.5 943.5,-726.5"/>
+<polygon fill="#00ffff" fill-opacity="0.501961" stroke="transparent" points="1021.5,-849.5 710.5,-849.5 710.5,-753.5 1021.5,-753.5 1021.5,-849.5"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="714,-825.5 714,-846.5 1019,-846.5 1019,-825.5 714,-825.5"/>
+<text text-anchor="start" x="850" y="-833.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Boat</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="714,-802.5 714,-823.5 1019,-823.5 1019,-802.5 714,-802.5"/>
+<text text-anchor="start" x="807.5" y="-810.3" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
+<text text-anchor="start" x="875.5" y="-810.3" font-family="monospace" font-weight="bold" font-size="14.00">CGBoat</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="714,-779.5 714,-800.5 1019,-800.5 1019,-779.5 714,-779.5"/>
+<text text-anchor="start" x="717" y="-786.3" font-family="Noto Serif" font-size="14.00">Represents a boat or other type of transport.</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="714,-756.5 714,-777.5 1019,-777.5 1019,-756.5 714,-756.5"/>
+<text text-anchor="start" x="720.5" y="-763.3" font-family="Noto Serif" font-size="14.00">Contains bonuses provided to boarded hero</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="710.5,-753.5 710.5,-849.5 1021.5,-849.5 1021.5,-753.5 710.5,-753.5"/>
 </g>
 <!-- Boat&#45;&gt;Hero -->
 <g id="edge10" class="edge">
 <title>Boat&#45;&gt;Hero</title>
-<path fill="none" stroke="black" d="M1114.06,-726.38C1124.73,-693.58 1139.65,-648.92 1154,-610 1157.14,-601.48 1160.5,-592.65 1163.92,-583.83"/>
-<polygon fill="black" stroke="black" points="1167.27,-584.86 1167.65,-574.28 1160.75,-582.32 1167.27,-584.86"/>
+<path fill="none" stroke="black" d="M884.12,-753.2C899.36,-713.43 921.58,-655.43 940.24,-606.74"/>
+<polygon fill="black" stroke="black" points="943.61,-607.72 943.92,-597.13 937.07,-605.22 943.61,-607.72"/>
 </g>
 <!-- Town and visiting hero&#45;&gt;Visiting Hero -->
 <g id="edge9" class="edge">
 <title>Town and visiting hero&#45;&gt;Visiting Hero</title>
-<path fill="none" stroke="black" d="M871.69,-868C874.81,-867.31 877.92,-866.64 881,-866 964.71,-848.68 1205.93,-892.74 1264,-830 1298.6,-792.62 1290.93,-730.23 1280.43,-688.97"/>
-<polygon fill="black" stroke="black" points="1283.79,-687.96 1277.8,-679.22 1277.03,-689.79 1283.79,-687.96"/>
+<path fill="none" stroke="black" d="M758.73,-899.19C761.84,-898.43 764.93,-897.7 768,-897 825.5,-883.83 990.5,-903.89 1031,-861 1068.11,-821.7 1064.06,-755.25 1056.21,-712.14"/>
+<polygon fill="black" stroke="black" points="1059.63,-711.38 1054.26,-702.25 1052.76,-712.73 1059.63,-711.38"/>
 </g>
 <!-- Town and visiting hero&#45;&gt;Town -->
 <g id="edge8" class="edge">
 <title>Town and visiting hero&#45;&gt;Town</title>
-<path fill="none" stroke="black" d="M734.83,-865.63C744.91,-853.85 755.31,-841.71 765.08,-830.29"/>
-<polygon fill="black" stroke="black" points="767.89,-832.4 771.74,-822.52 762.57,-827.84 767.89,-832.4"/>
+<path fill="none" stroke="black" d="M570.78,-896.79C571.51,-888.38 572.26,-879.76 573,-871.31"/>
+<polygon fill="black" stroke="black" points="576.51,-871.38 573.89,-861.11 569.53,-870.77 576.51,-871.38"/>
+</g>
+<!-- Combat -->
+<g id="node15" class="node">
+<title>Combat</title>
+<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="1716,-582 1266,-582 1266,-448 1716,-448 1716,-582"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1269,-558 1269,-579 1713,-579 1713,-558 1269,-558"/>
+<text text-anchor="start" x="1463.5" y="-565.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Combat</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1269,-535 1269,-556 1713,-556 1713,-535 1269,-535"/>
+<text text-anchor="start" x="1403.5" y="-542.8" font-family="Noto Serif" font-size="14.00">Propagator: </text>
+<text text-anchor="start" x="1487.5" y="-542.8" font-family="monospace" font-weight="bold" font-size="14.00">BATTLE_WIDE</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1269,-512 1269,-533 1713,-533 1713,-512 1269,-512"/>
+<text text-anchor="start" x="1415.5" y="-519.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
+<text text-anchor="start" x="1483.5" y="-519.8" font-family="monospace" font-weight="bold" font-size="14.00">BattleInfo</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1269,-474 1269,-510 1713,-510 1713,-474 1269,-474"/>
+<text text-anchor="start" x="1350.5" y="-495.8" font-family="Noto Serif" font-size="14.00">Node that contains both sides of a combat</text>
+<text text-anchor="start" x="1272" y="-480.8" font-family="Noto Serif" font-size="14.00">Anything propagated to this node will affect both sides in combat</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1269,-451 1269,-472 1713,-472 1713,-451 1269,-451"/>
+<text text-anchor="start" x="1333" y="-457.8" font-family="Noto Serif" font-size="14.00">Contains battlefield and native terrain bonuses</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1266,-448 1266,-582 1716,-582 1716,-448 1266,-448"/>
+</g>
+<!-- Combat&#45;&gt;Army -->
+<g id="edge11" class="edge">
+<title>Combat&#45;&gt;Army</title>
+<path fill="none" stroke="black" d="M1304.71,-447.93C1261.28,-432.53 1214.9,-416.08 1171.06,-400.53"/>
+<polygon fill="black" stroke="black" points="1172.04,-397.17 1161.45,-397.12 1169.7,-403.77 1172.04,-397.17"/>
 </g>
 <!-- Creature Type -->
 <g id="node16" class="node">
 <title>Creature Type</title>
-<polygon fill="#00ffff" fill-opacity="0.501961" stroke="transparent" points="1494,-366.5 1070,-366.5 1070,-270.5 1494,-270.5 1494,-366.5"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1073,-342.5 1073,-363.5 1491,-363.5 1491,-342.5 1073,-342.5"/>
-<text text-anchor="start" x="1230.5" y="-350.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Creature Type</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1073,-319.5 1073,-340.5 1491,-340.5 1491,-319.5 1073,-319.5"/>
-<text text-anchor="start" x="1210.5" y="-327.3" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
-<text text-anchor="start" x="1278.5" y="-327.3" font-family="monospace" font-weight="bold" font-size="14.00">CCreature</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1073,-296.5 1073,-317.5 1491,-317.5 1491,-296.5 1073,-296.5"/>
-<text text-anchor="start" x="1098" y="-303.3" font-family="Noto Serif" font-size="14.00">Represents a creature type, such as Pikeman or Archer</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1073,-273.5 1073,-294.5 1491,-294.5 1491,-273.5 1073,-273.5"/>
-<text text-anchor="start" x="1076" y="-280.3" font-family="Noto Serif" font-size="14.00">Contains creature abilities bonuses, stack experience bonuses</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1070,-270.5 1070,-366.5 1494,-366.5 1494,-270.5 1070,-270.5"/>
+<polygon fill="#00ffff" fill-opacity="0.501961" stroke="transparent" points="1641,-378 1217,-378 1217,-282 1641,-282 1641,-378"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1220,-354 1220,-375 1638,-375 1638,-354 1220,-354"/>
+<text text-anchor="start" x="1377.5" y="-361.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Creature Type</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1220,-331 1220,-352 1638,-352 1638,-331 1220,-331"/>
+<text text-anchor="start" x="1357.5" y="-338.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
+<text text-anchor="start" x="1425.5" y="-338.8" font-family="monospace" font-weight="bold" font-size="14.00">CCreature</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1220,-308 1220,-329 1638,-329 1638,-308 1220,-308"/>
+<text text-anchor="start" x="1245" y="-314.8" font-family="Noto Serif" font-size="14.00">Represents a creature type, such as Pikeman or Archer</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1220,-285 1220,-306 1638,-306 1638,-285 1220,-285"/>
+<text text-anchor="start" x="1223" y="-291.8" font-family="Noto Serif" font-size="14.00">Contains creature abilities bonuses, stack experience bonuses</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1217,-282 1217,-378 1641,-378 1641,-282 1217,-282"/>
 </g>
 <!-- Unit in Army -->
 <g id="node20" class="node">
 <title>Unit in Army</title>
-<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="1198,-227 922,-227 922,-124 1198,-124 1198,-227"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="925,-202.5 925,-223.5 1195,-223.5 1195,-202.5 925,-202.5"/>
-<text text-anchor="start" x="1013.5" y="-210.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Unit in Army</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="925,-179.5 925,-200.5 1195,-200.5 1195,-179.5 925,-179.5"/>
-<text text-anchor="start" x="968" y="-187.3" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
-<text text-anchor="start" x="1036" y="-187.3" font-family="monospace" font-weight="bold" font-size="14.00">CStackInstance</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="925,-126.5 925,-177.5 1195,-177.5 1195,-126.5 925,-126.5"/>
-<text text-anchor="start" x="928" y="-163.3" font-family="Noto Serif" font-size="14.00">Represents a unit that is part of a army</text>
-<text text-anchor="start" x="945.5" y="-148.3" font-family="Noto Serif" font-size="14.00">A unit always has a creature type,</text>
-<text text-anchor="start" x="933.5" y="-133.3" font-family="Noto Serif" font-size="14.00">belongs to an army and has stack size</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="922,-124 922,-227 1198,-227 1198,-124 922,-124"/>
+<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="1345,-227 1069,-227 1069,-124 1345,-124 1345,-227"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1072,-202.5 1072,-223.5 1342,-223.5 1342,-202.5 1072,-202.5"/>
+<text text-anchor="start" x="1160.5" y="-210.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Unit in Army</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1072,-179.5 1072,-200.5 1342,-200.5 1342,-179.5 1072,-179.5"/>
+<text text-anchor="start" x="1115" y="-187.3" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
+<text text-anchor="start" x="1183" y="-187.3" font-family="monospace" font-weight="bold" font-size="14.00">CStackInstance</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1072,-126.5 1072,-177.5 1342,-177.5 1342,-126.5 1072,-126.5"/>
+<text text-anchor="start" x="1075" y="-163.3" font-family="Noto Serif" font-size="14.00">Represents a unit that is part of a army</text>
+<text text-anchor="start" x="1092.5" y="-148.3" font-family="Noto Serif" font-size="14.00">A unit always has a creature type,</text>
+<text text-anchor="start" x="1080.5" y="-133.3" font-family="Noto Serif" font-size="14.00">belongs to an army and has stack size</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1069,-124 1069,-227 1345,-227 1345,-124 1069,-124"/>
 </g>
 <!-- Creature Type&#45;&gt;Unit in Army -->
 <g id="edge22" class="edge">
 <title>Creature Type&#45;&gt;Unit in Army</title>
-<path fill="none" stroke="black" d="M1207.77,-270.35C1188.77,-258.29 1168.16,-245.2 1148.52,-232.72"/>
-<polygon fill="black" stroke="black" points="1150.2,-229.64 1139.88,-227.23 1146.44,-235.55 1150.2,-229.64"/>
+<path fill="none" stroke="black" d="M1360.28,-281.79C1337.82,-266.37 1312.65,-249.08 1289.3,-233.04"/>
+<polygon fill="black" stroke="black" points="1291.01,-229.97 1280.79,-227.19 1287.05,-235.74 1291.01,-229.97"/>
 </g>
 <!-- Commander -->
 <g id="node21" class="node">
 <title>Commander</title>
-<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="1524,-212 1216,-212 1216,-139 1524,-139 1524,-212"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1219,-187.5 1219,-208.5 1521,-208.5 1521,-187.5 1219,-187.5"/>
-<text text-anchor="start" x="1326.5" y="-195.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Commander</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1219,-164.5 1219,-185.5 1521,-185.5 1521,-164.5 1219,-164.5"/>
-<text text-anchor="start" x="1261.5" y="-172.3" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
-<text text-anchor="start" x="1329.5" y="-172.3" font-family="monospace" font-weight="bold" font-size="14.00">CCommanderInstance</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1219,-141.5 1219,-162.5 1521,-162.5 1521,-141.5 1219,-141.5"/>
-<text text-anchor="start" x="1222" y="-148.3" font-family="Noto Serif" font-size="14.00">Represents a hero commander, WoG feature</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1216,-139 1216,-212 1524,-212 1524,-139 1216,-139"/>
+<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="1671,-212 1363,-212 1363,-139 1671,-139 1671,-212"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1366,-187.5 1366,-208.5 1668,-208.5 1668,-187.5 1366,-187.5"/>
+<text text-anchor="start" x="1473.5" y="-195.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Commander</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1366,-164.5 1366,-185.5 1668,-185.5 1668,-164.5 1366,-164.5"/>
+<text text-anchor="start" x="1408.5" y="-172.3" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
+<text text-anchor="start" x="1476.5" y="-172.3" font-family="monospace" font-weight="bold" font-size="14.00">CCommanderInstance</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1366,-141.5 1366,-162.5 1668,-162.5 1668,-141.5 1366,-141.5"/>
+<text text-anchor="start" x="1369" y="-148.3" font-family="Noto Serif" font-size="14.00">Represents a hero commander, WoG feature</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1363,-139 1363,-212 1671,-212 1671,-139 1363,-139"/>
 </g>
 <!-- Creature Type&#45;&gt;Commander -->
 <g id="edge20" class="edge">
 <title>Creature Type&#45;&gt;Commander</title>
-<path fill="none" stroke="black" d="M1311.55,-270.16C1321.49,-254.23 1332.54,-236.52 1342.33,-220.83"/>
-<polygon fill="black" stroke="black" points="1345.3,-222.68 1347.63,-212.35 1339.36,-218.98 1345.3,-222.68"/>
+<path fill="none" stroke="black" d="M1456.36,-281.58C1467.51,-262.26 1480.33,-240.04 1491.31,-221.02"/>
+<polygon fill="black" stroke="black" points="1494.4,-222.67 1496.37,-212.25 1488.34,-219.17 1494.4,-222.67"/>
 </g>
 <!-- Summon in Combat -->
 <g id="node23" class="node">
 <title>Summon in Combat</title>
-<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="903.5,-219.5 576.5,-219.5 576.5,-131.5 903.5,-131.5 903.5,-219.5"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="580,-195.5 580,-216.5 901,-216.5 901,-195.5 580,-195.5"/>
-<text text-anchor="start" x="671.5" y="-203.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Summon in Combat</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="580,-172.5 580,-193.5 901,-193.5 901,-172.5 580,-172.5"/>
-<text text-anchor="start" x="681.5" y="-180.3" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
-<text text-anchor="start" x="749.5" y="-180.3" font-family="monospace" font-weight="bold" font-size="14.00">CStack</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="580,-134.5 580,-170.5 901,-170.5 901,-134.5 580,-134.5"/>
-<text text-anchor="start" x="583" y="-156.3" font-family="Noto Serif" font-size="14.00">Represents any unit that was added in combat,</text>
-<text text-anchor="start" x="626" y="-141.3" font-family="Noto Serif" font-size="14.00">and may not remain after combat</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="576.5,-131.5 576.5,-219.5 903.5,-219.5 903.5,-131.5 576.5,-131.5"/>
+<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="1050.5,-219.5 723.5,-219.5 723.5,-131.5 1050.5,-131.5 1050.5,-219.5"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="727,-195.5 727,-216.5 1048,-216.5 1048,-195.5 727,-195.5"/>
+<text text-anchor="start" x="818.5" y="-203.3" font-family="Noto Serif" font-weight="bold" font-size="14.00">Summon in Combat</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="727,-172.5 727,-193.5 1048,-193.5 1048,-172.5 727,-172.5"/>
+<text text-anchor="start" x="828.5" y="-180.3" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
+<text text-anchor="start" x="896.5" y="-180.3" font-family="monospace" font-weight="bold" font-size="14.00">CStack</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="727,-134.5 727,-170.5 1048,-170.5 1048,-134.5 727,-134.5"/>
+<text text-anchor="start" x="730" y="-156.3" font-family="Noto Serif" font-size="14.00">Represents any unit that was added in combat,</text>
+<text text-anchor="start" x="773" y="-141.3" font-family="Noto Serif" font-size="14.00">and may not remain after combat</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="723.5,-131.5 723.5,-219.5 1050.5,-219.5 1050.5,-131.5 723.5,-131.5"/>
 </g>
 <!-- Creature Type&#45;&gt;Summon in Combat -->
 <g id="edge21" class="edge">
 <title>Creature Type&#45;&gt;Summon in Combat</title>
-<path fill="none" stroke="black" d="M1091.54,-270.49C1081.21,-267.95 1070.98,-265.44 1061,-263 995.25,-246.9 978.4,-244.49 913,-227 907.29,-225.47 901.48,-223.9 895.61,-222.28"/>
-<polygon fill="black" stroke="black" points="896.27,-218.83 885.7,-219.54 894.4,-225.58 896.27,-218.83"/>
+<path fill="none" stroke="black" d="M1275.33,-282C1252.8,-275.43 1229.84,-268.91 1208,-263 1142.66,-245.31 1125.4,-244.49 1060,-227 1054.29,-225.47 1048.48,-223.9 1042.61,-222.28"/>
+<polygon fill="black" stroke="black" points="1043.27,-218.83 1032.7,-219.54 1041.4,-225.58 1043.27,-218.83"/>
 </g>
 <!-- Artifact Type -->
 <g id="node17" class="node">
 <title>Artifact Type</title>
-<polygon fill="#00ffff" fill-opacity="0.501961" stroke="transparent" points="1254,-981 890,-981 890,-885 1254,-885 1254,-981"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="893,-957 893,-978 1251,-978 1251,-957 893,-957"/>
-<text text-anchor="start" x="1024" y="-964.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Artifact Type</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="893,-934 893,-955 1251,-955 1251,-934 893,-934"/>
-<text text-anchor="start" x="1000.5" y="-941.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
-<text text-anchor="start" x="1068.5" y="-941.8" font-family="monospace" font-weight="bold" font-size="14.00">CArtifact</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="893,-911 893,-932 1251,-932 1251,-911 893,-911"/>
-<text text-anchor="start" x="896" y="-917.8" font-family="Noto Serif" font-size="14.00">Represents an artifact type, for example Ring of Life</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="893,-888 893,-909 1251,-909 1251,-888 893,-888"/>
-<text text-anchor="start" x="956.5" y="-894.8" font-family="Noto Serif" font-size="14.00">Contains fixed bonuses of artifacts</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="890,-885 890,-981 1254,-981 1254,-885 890,-885"/>
+<polygon fill="#00ffff" fill-opacity="0.501961" stroke="transparent" points="1141,-1012 777,-1012 777,-916 1141,-916 1141,-1012"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="780,-988 780,-1009 1138,-1009 1138,-988 780,-988"/>
+<text text-anchor="start" x="911" y="-995.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Artifact Type</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="780,-965 780,-986 1138,-986 1138,-965 780,-965"/>
+<text text-anchor="start" x="887.5" y="-972.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
+<text text-anchor="start" x="955.5" y="-972.8" font-family="monospace" font-weight="bold" font-size="14.00">CArtifact</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="780,-942 780,-963 1138,-963 1138,-942 780,-942"/>
+<text text-anchor="start" x="783" y="-948.8" font-family="Noto Serif" font-size="14.00">Represents an artifact type, for example Ring of Life</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="780,-919 780,-940 1138,-940 1138,-919 780,-919"/>
+<text text-anchor="start" x="843.5" y="-925.8" font-family="Noto Serif" font-size="14.00">Contains fixed bonuses of artifacts</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="777,-916 777,-1012 1141,-1012 1141,-916 777,-916"/>
 </g>
 <!-- Artifact Type&#45;&gt;Artifact Instance -->
 <g id="edge17" class="edge">
 <title>Artifact Type&#45;&gt;Artifact Instance</title>
-<path fill="none" stroke="black" d="M1200.4,-884.94C1244.52,-868.77 1294.43,-850.48 1340.65,-833.54"/>
-<polygon fill="black" stroke="black" points="1341.96,-836.79 1350.14,-830.06 1339.55,-830.21 1341.96,-836.79"/>
+<path fill="none" stroke="black" d="M1049.04,-915.93C1081.44,-898.99 1118.42,-879.66 1152.56,-861.81"/>
+<polygon fill="black" stroke="black" points="1154.39,-864.8 1161.64,-857.06 1151.15,-858.59 1154.39,-864.8"/>
 </g>
 <!-- Artifact Component -->
 <g id="node18" class="node">
 <title>Artifact Component</title>
-<polygon fill="#808080" fill-opacity="0.501961" stroke="transparent" points="1756,-977 1272,-977 1272,-889 1756,-889 1756,-977"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1275,-953 1275,-974 1753,-974 1753,-953 1275,-953"/>
-<text text-anchor="start" x="1443" y="-960.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Artifact Component</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1275,-930 1275,-951 1753,-951 1753,-930 1275,-930"/>
-<text text-anchor="start" x="1409.5" y="-937.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
-<text text-anchor="start" x="1477.5" y="-937.8" font-family="monospace" font-weight="bold" font-size="14.00">CArtifactInstance</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1275,-892 1275,-928 1753,-928 1753,-892 1275,-892"/>
-<text text-anchor="start" x="1398.5" y="-913.8" font-family="Noto Serif" font-size="14.00">For combined, non&#45;fused artifacts,</text>
-<text text-anchor="start" x="1278" y="-898.8" font-family="Noto Serif" font-size="14.00">instances of components are attached to instance of combined artifact</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1272,-889 1272,-977 1756,-977 1756,-889 1272,-889"/>
+<polygon fill="#808080" fill-opacity="0.501961" stroke="transparent" points="1643,-1008 1159,-1008 1159,-920 1643,-920 1643,-1008"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1162,-984 1162,-1005 1640,-1005 1640,-984 1162,-984"/>
+<text text-anchor="start" x="1330" y="-991.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Artifact Component</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1162,-961 1162,-982 1640,-982 1640,-961 1162,-961"/>
+<text text-anchor="start" x="1296.5" y="-968.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
+<text text-anchor="start" x="1364.5" y="-968.8" font-family="monospace" font-weight="bold" font-size="14.00">CArtifactInstance</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1162,-923 1162,-959 1640,-959 1640,-923 1162,-923"/>
+<text text-anchor="start" x="1285.5" y="-944.8" font-family="Noto Serif" font-size="14.00">For combined, non&#45;fused artifacts,</text>
+<text text-anchor="start" x="1165" y="-929.8" font-family="Noto Serif" font-size="14.00">instances of components are attached to instance of combined artifact</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1159,-920 1159,-1008 1643,-1008 1643,-920 1159,-920"/>
 </g>
 <!-- Artifact Component&#45;&gt;Artifact Instance -->
 <g id="edge18" class="edge">
 <title>Artifact Component&#45;&gt;Artifact Instance</title>
-<path fill="none" stroke="black" d="M1509.86,-888.82C1508.42,-873.76 1506.77,-856.52 1505.19,-840.1"/>
-<polygon fill="black" stroke="black" points="1508.66,-839.63 1504.22,-830.01 1501.69,-840.3 1508.66,-839.63"/>
+<path fill="none" stroke="black" d="M1364.82,-919.99C1350.61,-903.09 1334.01,-883.36 1318.52,-864.94"/>
+<polygon fill="black" stroke="black" points="1321.08,-862.55 1311.96,-857.15 1315.72,-867.05 1321.08,-862.55"/>
 </g>
 <!-- Army&#45;&gt;Unit in Army -->
 <g id="edge13" class="edge">
 <title>Army&#45;&gt;Unit in Army</title>
-<path fill="none" stroke="black" d="M917.7,-262.99C934.2,-252.95 951.46,-242.47 968.03,-232.4"/>
-<polygon fill="black" stroke="black" points="969.89,-235.36 976.62,-227.17 966.26,-229.38 969.89,-235.36"/>
+<path fill="none" stroke="black" d="M1075.41,-263C1090.66,-252.97 1106.28,-242.7 1121.19,-232.91"/>
+<polygon fill="black" stroke="black" points="1123.44,-235.62 1129.87,-227.2 1119.59,-229.77 1123.44,-235.62"/>
 </g>
 <!-- Army&#45;&gt;Commander -->
 <g id="edge12" class="edge">
 <title>Army&#45;&gt;Commander</title>
-<path fill="none" stroke="black" d="M1052.23,-264.59C1124.31,-247.46 1190.04,-231.65 1207,-227 1220.58,-223.28 1234.74,-219.2 1248.79,-215.02"/>
-<polygon fill="black" stroke="black" points="1249.91,-218.34 1258.49,-212.12 1247.91,-211.63 1249.91,-218.34"/>
+<path fill="none" stroke="black" d="M1199.1,-265.37C1202.09,-264.57 1205.05,-263.78 1208,-263 1272.61,-245.89 1289.61,-244.9 1354,-227 1367.57,-223.23 1381.72,-219.12 1395.76,-214.92"/>
+<polygon fill="black" stroke="black" points="1396.89,-218.24 1405.46,-212.01 1394.88,-211.53 1396.89,-218.24"/>
 </g>
 <!-- Army&#45;&gt;Summon in Combat -->
 <g id="edge14" class="edge">
 <title>Army&#45;&gt;Summon in Combat</title>
-<path fill="none" stroke="black" d="M793.98,-262.99C786.81,-251.5 779.27,-239.42 772.17,-228.04"/>
-<polygon fill="black" stroke="black" points="775.1,-226.14 766.84,-219.51 769.17,-229.84 775.1,-226.14"/>
+<path fill="none" stroke="black" d="M936.91,-263C930.24,-251.44 923.39,-239.56 916.98,-228.45"/>
+<polygon fill="black" stroke="black" points="919.97,-226.63 911.94,-219.72 913.91,-230.13 919.97,-226.63"/>
 </g>
 <!-- Unit in Combat -->
 <g id="node22" class="node">
 <title>Unit in Combat</title>
-<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="1386.5,-88 1043.5,-88 1043.5,0 1386.5,0 1386.5,-88"/>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1047,-64 1047,-85 1384,-85 1384,-64 1047,-64"/>
-<text text-anchor="start" x="1162" y="-71.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Unit in Combat</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1047,-41 1047,-62 1384,-62 1384,-41 1047,-41"/>
-<text text-anchor="start" x="1156.5" y="-48.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
-<text text-anchor="start" x="1224.5" y="-48.8" font-family="monospace" font-weight="bold" font-size="14.00">CStack</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1047,-3 1047,-39 1384,-39 1384,-3 1047,-3"/>
-<text text-anchor="start" x="1050" y="-24.8" font-family="Noto Serif" font-size="14.00">Represents current state of a unit during combat,</text>
-<text text-anchor="start" x="1070.5" y="-9.8" font-family="Noto Serif" font-size="14.00">can be affected by spells or receive damage</text>
-<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1043.5,0 1043.5,-88 1386.5,-88 1386.5,0 1043.5,0"/>
+<polygon fill="#602000" fill-opacity="0.501961" stroke="transparent" points="1533.5,-88 1190.5,-88 1190.5,0 1533.5,0 1533.5,-88"/>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1194,-64 1194,-85 1531,-85 1531,-64 1194,-64"/>
+<text text-anchor="start" x="1309" y="-71.8" font-family="Noto Serif" font-weight="bold" font-size="14.00">Unit in Combat</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1194,-41 1194,-62 1531,-62 1531,-41 1194,-41"/>
+<text text-anchor="start" x="1303.5" y="-48.8" font-family="Noto Serif" font-size="14.00">C++ Class: </text>
+<text text-anchor="start" x="1371.5" y="-48.8" font-family="monospace" font-weight="bold" font-size="14.00">CStack</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1194,-3 1194,-39 1531,-39 1531,-3 1194,-3"/>
+<text text-anchor="start" x="1197" y="-24.8" font-family="Noto Serif" font-size="14.00">Represents current state of a unit during combat,</text>
+<text text-anchor="start" x="1217.5" y="-9.8" font-family="Noto Serif" font-size="14.00">can be affected by spells or receive damage</text>
+<polygon fill="none" stroke="#000000" stroke-opacity="0.501961" points="1190.5,0 1190.5,-88 1533.5,-88 1533.5,0 1190.5,0"/>
 </g>
 <!-- Unit in Army&#45;&gt;Unit in Combat -->
 <g id="edge15" class="edge">
 <title>Unit in Army&#45;&gt;Unit in Combat</title>
-<path fill="none" stroke="black" d="M1120.82,-123.69C1132.25,-114.13 1144.2,-104.15 1155.6,-94.63"/>
-<polygon fill="black" stroke="black" points="1157.96,-97.22 1163.39,-88.12 1153.47,-91.85 1157.96,-97.22"/>
+<path fill="none" stroke="black" d="M1267.82,-123.69C1279.25,-114.13 1291.2,-104.15 1302.6,-94.63"/>
+<polygon fill="black" stroke="black" points="1304.96,-97.22 1310.39,-88.12 1300.47,-91.85 1304.96,-97.22"/>
 </g>
 <!-- Commander&#45;&gt;Unit in Combat -->
 <g id="edge16" class="edge">
 <title>Commander&#45;&gt;Unit in Combat</title>
-<path fill="none" stroke="black" d="M1327.24,-138.77C1310.99,-125.2 1292.19,-109.49 1274.67,-94.85"/>
-<polygon fill="black" stroke="black" points="1276.64,-91.94 1266.72,-88.21 1272.15,-97.31 1276.64,-91.94"/>
+<path fill="none" stroke="black" d="M1474.24,-138.77C1457.99,-125.2 1439.19,-109.49 1421.67,-94.85"/>
+<polygon fill="black" stroke="black" points="1423.64,-91.94 1413.72,-88.21 1419.15,-97.31 1423.64,-91.94"/>
 </g>
 </g>
 </svg>

+ 6 - 0
docs/modders/Bonus/Bonus_Types.md

@@ -264,6 +264,12 @@ Defines maximum level of spells than hero can learn from any source (Wisdom)
 
 - val: maximal level to learn
 
+### COMBAT_MANA_BONUS
+
+Grants affected hero additional mana for the duration of combat. Bonus may give total mana above mana limit. Any additional mana not spent during combat will be lost.
+
+- val: amount of additional mana
+
 ## Hero specialties
 
 ### SPECIAL_SPELL_LEV

+ 25 - 10
lib/CCreatureHandler.cpp

@@ -470,6 +470,16 @@ CCreatureHandler::CCreatureHandler()
 
 void CCreatureHandler::loadCommanders()
 {
+	const auto & parseBonusWithCompatibility = [](const JsonNode & node)
+	{
+		// MOD COMPATIBILITY: 1.6 mods use old, vector format.
+		// NOTE: please also remove parseBonus itself - commanders is the last place that uses it
+		if (node.isVector())
+			return JsonUtils::parseBonus(node.Vector());
+		else
+			return JsonUtils::parseBonus(node);
+	};
+
 	auto configResource = JsonPath::builtin("config/commanders.json");
 
 	std::string modSource = LIBRARY->modh->findResourceOrigin(configResource);
@@ -480,7 +490,7 @@ void CCreatureHandler::loadCommanders()
 
 	for (auto bonus : config["bonusPerLevel"].Vector())
 	{
-		commanderLevelPremy.push_back(JsonUtils::parseBonus(bonus.Vector()));
+		commanderLevelPremy.push_back(parseBonusWithCompatibility(bonus));
 	}
 
 	int level = 0;
@@ -497,15 +507,20 @@ void CCreatureHandler::loadCommanders()
 	for (auto ability : config["abilityRequirements"].Vector())
 	{
 		std::pair <std::vector<std::shared_ptr<Bonus> >, std::pair <ui8, ui8> > a;
-		JsonVector & abilities = ability["ability"].Vector();
-		a.first = std::vector<std::shared_ptr<Bonus> >();
-		if (abilities[0].isVector()) 
-			for (int i = 0; i < abilities.size(); i++) 
-				a.first.push_back(JsonUtils::parseBonus(abilities[i].Vector()));
-		else 
-			a.first.push_back(JsonUtils::parseBonus(ability["ability"].Vector()));
-		a.second.first =  static_cast<ui8>(ability["skills"].Vector()[0].Float());
-		a.second.second = static_cast<ui8>(ability["skills"].Vector()[1].Float());
+		JsonNode & abilities = ability["ability"];
+
+		if (abilities[0].isString()) // old format with single bonus
+		{
+			a.first.push_back(parseBonusWithCompatibility(abilities));
+		}
+		else
+		{
+			for (const auto & ability : abilities.Vector())
+				a.first.push_back(parseBonusWithCompatibility(ability));
+		}
+
+		a.second.first =  static_cast<ui8>(ability["skills"][0].Float());
+		a.second.second = static_cast<ui8>(ability["skills"][1].Float());
 		skillRequirements.push_back (a);
 	}
 }

+ 0 - 356
lib/CCreatureSet.h

@@ -1,356 +0,0 @@
-/*
- * CCreatureSet.h, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-#pragma once
-
-#include "CCreatureHandler.h"
-#include "GameConstants.h"
-#include "bonuses/Bonus.h"
-#include "bonuses/BonusCache.h"
-#include "bonuses/CBonusSystemNode.h"
-#include "callback/GameCallbackHolder.h"
-#include "serializer/Serializeable.h"
-#include "mapObjects/CGObjectInstance.h"
-#include "entities/artifact/CArtifactSet.h"
-
-#include <vcmi/Entity.h>
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-class JsonNode;
-class CCreature;
-class CGHeroInstance;
-class CArmedInstance;
-class CCreatureArtifactSet;
-class JsonSerializeFormat;
-
-class DLL_LINKAGE CStackBasicDescriptor
-{
-	CreatureID typeID;
-	TQuantity count = -1; //exact quantity or quantity ID from CCreature::getQuantityID when getting info about enemy army
-
-public:
-	CStackBasicDescriptor();
-	CStackBasicDescriptor(const CreatureID & id, TQuantity Count);
-	CStackBasicDescriptor(const CCreature *c, TQuantity Count);
-	virtual ~CStackBasicDescriptor() = default;
-
-	const Creature * getType() const;
-	const CCreature * getCreature() const;
-	CreatureID getId() const;
-	TQuantity getCount() const;
-
-	virtual void setType(const CCreature * c);
-	virtual void setCount(TQuantity amount);
-
-	friend bool operator== (const CStackBasicDescriptor & l, const CStackBasicDescriptor & r);
-
-	template <typename Handler> void serialize(Handler &h)
-	{
-		if(h.saving)
-		{
-			h & typeID;
-		}
-		else
-		{
-			CreatureID creatureID;
-			h & creatureID;
-			if(creatureID != CreatureID::NONE)
-				setType(creatureID.toCreature());
-		}
-
-		h & count;
-	}
-
-	void serializeJson(JsonSerializeFormat & handler);
-};
-
-class DLL_LINKAGE CStackInstance : public CBonusSystemNode, public CStackBasicDescriptor, public CArtifactSet, public ACreature, public GameCallbackHolder
-{
-	BonusValueCache nativeTerrain;
-	BonusValueCache initiative;
-
-	CArmedInstance * armyInstance = nullptr; //stack must be part of some army, army must be part of some object
-
-	IGameInfoCallback * getCallback() const final { return cb; }
-
-	TExpType totalExperience;//commander needs same amount of exp as hero
-public:
-	struct RandomStackInfo
-	{
-		uint8_t level;
-		uint8_t upgrade;
-	};
-	// helper variable used during loading map, when object (hero or town) have creatures that must have same alignment.
-	std::optional<RandomStackInfo> randomStack;
-
-	CArmedInstance * getArmy();
-	const CArmedInstance * getArmy() const; //stack must be part of some army, army must be part of some object
-	void setArmy(CArmedInstance *ArmyObj);
-
-	TExpType getTotalExperience() const;
-	TExpType getAverageExperience() const;
-	virtual bool canGainExperience() const;
-
-	template <typename Handler> void serialize(Handler &h)
-	{
-		h & static_cast<CBonusSystemNode&>(*this);
-		h & static_cast<CStackBasicDescriptor&>(*this);
-		h & static_cast<CArtifactSet&>(*this);
-
-		if (h.hasFeature(Handler::Version::STACK_INSTANCE_ARMY_FIX))
-		{
-			// no-op
-		}
-		if (h.hasFeature(Handler::Version::NO_RAW_POINTERS_IN_SERIALIZER))
-		{
-			ObjectInstanceID dummyID;
-			h & dummyID;
-		}
-		else
-		{
-			std::shared_ptr<CGObjectInstance> army;
-			h & army;
-		}
-
-		h & totalExperience;
-		if (!h.hasFeature(Handler::Version::STACK_INSTANCE_EXPERIENCE_FIX))
-		{
-			totalExperience *= getCount();
-		}
-	}
-
-	void serializeJson(JsonSerializeFormat & handler);
-
-	//overrides CBonusSystemNode
-	std::string bonusToString(const std::shared_ptr<Bonus>& bonus) const override; // how would bonus description look for this particular type of node
-	ImagePath bonusToGraphics(const std::shared_ptr<Bonus> & bonus) const; //file name of graphics from StackSkills , in future possibly others
-
-	//IConstBonusProvider
-	const IBonusBearer* getBonusBearer() const override;
-	//INativeTerrainProvider
-	FactionID getFactionID() const override;
-
-	virtual ui64 getPower() const;
-	/// Returns total market value of resources needed to recruit this unit
-	virtual ui64 getMarketValue() const;
-	CCreature::CreatureQuantityId getQuantityID() const;
-	std::string getQuantityTXT(bool capitalized = true) const;
-	virtual int getExpRank() const;
-	virtual int getLevel() const; //different for regular stack and commander
-	CreatureID getCreatureID() const; //-1 if not available
-	std::string getName() const; //plural or singular
-	CStackInstance(IGameInfoCallback *cb);
-	CStackInstance(IGameInfoCallback *cb, BonusNodeType nodeType, bool isHypothetic = false);
-	CStackInstance(IGameInfoCallback *cb, const CreatureID & id, TQuantity count, bool isHypothetic = false);
-	virtual ~CStackInstance() = default;
-
-	void setType(const CreatureID & creID);
-	void setType(const CCreature * c) final;
-	void setCount(TQuantity amount) final;
-
-	/// Gives specified amount of stack experience that will not be scaled by unit size
-	void giveAverageStackExperience(TExpType exp);
-	void giveTotalStackExperience(TExpType exp);
-
-	bool valid(bool allowUnrandomized) const;
-	ArtPlacementMap putArtifact(const ArtifactPosition & pos, const CArtifactInstance * art) override;//from CArtifactSet
-	void removeArtifact(const ArtifactPosition & pos) override;
-	ArtBearer bearerType() const override; //from CArtifactSet
-	std::string nodeName() const override; //from CBonusSystemnode
-	PlayerColor getOwner() const override;
-
-	int32_t getInitiative(int turn = 0) const final;
-	TerrainId getNativeTerrain() const final;
-	TerrainId getCurrentTerrain() const;
-};
-
-class DLL_LINKAGE CCommanderInstance : public CStackInstance
-{
-public:
-	//TODO: what if Commander is not a part of creature set?
-
-	//commander class is determined by its base creature
-	ui8 alive; //maybe change to bool when breaking save compatibility?
-	ui8 level; //required only to count callbacks
-	std::string name; // each Commander has different name
-	std::vector <ui8> secondarySkills; //ID -> level
-	std::set <ui8> specialSkills;
-	//std::vector <CArtifactInstance *> arts;
-	CCommanderInstance(IGameInfoCallback *cb);
-	CCommanderInstance(IGameInfoCallback *cb, const CreatureID & id);
-	void setAlive (bool alive);
-	void levelUp ();
-
-	bool canGainExperience() const override;
-	bool gainsLevel() const; //true if commander has lower level than should upon his experience
-	ui64 getPower() const override {return 0;};
-	int getExpRank() const override;
-	int getLevel() const override;
-	ArtBearer bearerType() const override; //from CArtifactSet
-
-	template <typename Handler> void serialize(Handler &h)
-	{
-		h & static_cast<CStackInstance&>(*this);
-		h & alive;
-		h & level;
-		h & name;
-		h & secondarySkills;
-		h & specialSkills;
-	}
-};
-
-using TSlots = std::map<SlotID, std::unique_ptr<CStackInstance>>;
-using TSimpleSlots = std::map<SlotID, std::pair<CreatureID, TQuantity>>;
-
-using TPairCreatureSlot = std::pair<const CCreature *, SlotID>;
-using TMapCreatureSlot = std::map<const CCreature *, SlotID>;
-
-struct DLL_LINKAGE CreatureSlotComparer
-{
-	bool operator()(const TPairCreatureSlot & lhs, const TPairCreatureSlot & rhs);
-};
-
-using TCreatureQueue = std::priority_queue<TPairCreatureSlot, std::vector<TPairCreatureSlot>, CreatureSlotComparer>;
-
-class IArmyDescriptor
-{
-public:
-	virtual void clearSlots() = 0;
-	virtual bool setCreature(SlotID slot, CreatureID cre, TQuantity count) = 0;
-};
-
-//simplified version of CCreatureSet
-class DLL_LINKAGE CSimpleArmy : public IArmyDescriptor
-{
-public:
-	TSimpleSlots army;
-	void clearSlots() override;
-	bool setCreature(SlotID slot, CreatureID cre, TQuantity count) override;
-	operator bool() const;
-
-	template <typename Handler> void serialize(Handler &h)
-	{
-		h & army;
-	}
-};
-
-namespace NArmyFormation
-{
-	static const std::vector<std::string> names{ "wide", "tight" };
-}
-
-class DLL_LINKAGE CCreatureSet : public IArmyDescriptor, public virtual Serializeable //seven combined creatures
-{
-	CCreatureSet(const CCreatureSet &) = delete;
-	CCreatureSet &operator=(const CCreatureSet&);
-
-public:
-	TSlots stacks; //slots[slot_id]->> pair(creature_id,creature_quantity)
-	EArmyFormation formation = EArmyFormation::LOOSE; //0 - wide, 1 - tight
-
-	CCreatureSet() = default; //Should be here to avoid compile errors
-	virtual ~CCreatureSet();
-	virtual void armyChanged();
-
-	const CStackInstance & operator[](const SlotID & slot) const;
-
-	const TSlots &Slots() const {return stacks;}
-
-	void addToSlot(const SlotID & slot, const CreatureID & cre, TQuantity count, bool allowMerging = true); //Adds stack to slot. Slot must be empty or with same type creature
-	void addToSlot(const SlotID & slot, std::unique_ptr<CStackInstance> stack, bool allowMerging = true); //Adds stack to slot. Slot must be empty or with same type creature
-	void clearSlots() override;
-	void setFormation(EArmyFormation tight);
-	virtual CArmedInstance * getArmy() { return nullptr; }
-	virtual const CArmedInstance * getArmy() const { return nullptr; }
-
-	//basic operations
-	void putStack(const SlotID & slot, std::unique_ptr<CStackInstance> stack); //adds new stack to the army, slot must be empty
-	void setStackCount(const SlotID & slot, TQuantity count); //stack must exist!
-	std::unique_ptr<CStackInstance> detachStack(const SlotID & slot); //removes stack from army but doesn't destroy it (so it can be moved somewhere else or safely deleted)
-	void setStackType(const SlotID & slot, const CreatureID & type);
-
-	/// Give specified amount of experience to all units in army
-	/// Amount of granted experience is scaled by unit stack size
-	void giveAverageStackExperience(TExpType exp);
-
-	/// Give specified amount of experience to unit in specified slot
-	/// Amount of granted experience is not scaled by unit stack size
-	void giveTotalStackExperience(const SlotID & slot, TExpType exp);
-
-	/// Erased stack from specified slot. Slot must be non-empty
-	void eraseStack(const SlotID & slot);
-
-	/// Joins stack into stack that occupies targeted slot.
-	/// Slot must be non-empty and contain same creature type
-	void joinStack(const SlotID & slot, std::unique_ptr<CStackInstance> stack); //adds new stack to the existing stack of the same type
-
-	/// Splits off some units of specified stack and returns newly created stack
-	/// Slot must be non-empty and contain more units that split quantity
-	std::unique_ptr<CStackInstance> splitStack(const SlotID & slot, TQuantity toSplit);
-
-	void changeStackCount(const SlotID & slot, TQuantity toAdd); //stack must exist!
-	bool setCreature (SlotID slot, CreatureID type, TQuantity quantity) override; //replaces creature in stack; slots 0 to 6, if quantity=0 erases stack
-	void setToArmy(CSimpleArmy &src); //erases all our army and moves stacks from src to us; src MUST NOT be an armed object! WARNING: use it wisely. Or better do not use at all.
-
-	const CStackInstance & getStack(const SlotID & slot) const; //stack must exist
-	CStackInstance * getStackPtr(const SlotID & slot) const; //if stack doesn't exist, returns nullptr
-	const CCreature * getCreature(const SlotID & slot) const; //workaround of map issue;
-	int getStackCount(const SlotID & slot) const;
-	TExpType getStackTotalExperience(const SlotID & slot) const;
-	TExpType getStackAverageExperience(const SlotID & slot) const;
-	SlotID findStack(const CStackInstance *stack) const; //-1 if none
-	SlotID getSlotFor(const CreatureID & creature, ui32 slotsAmount = GameConstants::ARMY_SIZE) const; //returns -1 if no slot available
-	SlotID getSlotFor(const CCreature *c, ui32 slotsAmount = GameConstants::ARMY_SIZE) const; //returns -1 if no slot available
-	bool hasCreatureSlots(const CCreature * c, const SlotID & exclude) const;
-	std::vector<SlotID> getCreatureSlots(const CCreature * c, const SlotID & exclude, TQuantity ignoreAmount = -1) const;
-	bool isCreatureBalanced(const CCreature* c, TQuantity ignoreAmount = 1) const; // Check if the creature is evenly distributed across slots
-
-	SlotID getFreeSlot(ui32 slotsAmount = GameConstants::ARMY_SIZE) const; //returns first free slot
-	std::vector<SlotID> getFreeSlots(ui32 slotsAmount = GameConstants::ARMY_SIZE) const;
-	std::queue<SlotID> getFreeSlotsQueue(ui32 slotsAmount = GameConstants::ARMY_SIZE) const;
-
-	TMapCreatureSlot getCreatureMap() const;
-	TCreatureQueue getCreatureQueue(const SlotID & exclude) const;
-
-	bool mergeableStacks(std::pair<SlotID, SlotID> & out, const SlotID & preferable = SlotID()) const; //looks for two same stacks, returns slot positions;
-	bool validTypes(bool allowUnrandomized = false) const; //checks if all types of creatures are set properly
-	bool slotEmpty(const SlotID & slot) const;
-	int stacksCount() const;
-	virtual bool needsLastStack() const; //true if last stack cannot be taken
-	ui64 getArmyStrength(int fortLevel = 0) const; //sum of AI values of creatures
-	ui64 getArmyCost() const; //sum of cost of creatures
-	ui64 getPower(const SlotID & slot) const; //value of specific stack
-	std::string getRoughAmount(const SlotID & slot, int mode = 0) const; //rough size of specific stack
-	std::string getArmyDescription() const;
-	bool hasStackAtSlot(const SlotID & slot) const;
-
-	bool contains(const CStackInstance *stack) const;
-	bool canBeMergedWith(const CCreatureSet &cs, bool allowMergingStacks = true) const;
-
-	/// Returns true if this creature set contains all listed units
-	/// If requireLastStack is true, then this function will also
-	/// require presence of any unit other than requested (or more units than requested)
-	bool hasUnits(const std::vector<CStackBasicDescriptor> & units, bool requireLastStack = true) const;
-
-	template <typename Handler> void serialize(Handler &h)
-	{
-		h & stacks;
-		h & formation;
-	}
-
-	void serializeJson(JsonSerializeFormat & handler, const std::string & armyFieldName, const std::optional<int> fixedSize = std::nullopt);
-
-	operator bool() const
-	{
-		return !stacks.empty();
-	}
-};
-
-VCMI_LIB_NAMESPACE_END

+ 30 - 7
lib/CMakeLists.txt

@@ -142,7 +142,6 @@ set(lib_MAIN_SRCS
 	mapObjectConstructors/HillFortInstanceConstructor.cpp
 	mapObjectConstructors/ShipyardInstanceConstructor.cpp
 
-	mapObjects/CArmedInstance.cpp
 	mapObjects/CGCreature.cpp
 	mapObjects/CGDwelling.cpp
 	mapObjects/CGHeroInstance.cpp
@@ -162,6 +161,12 @@ set(lib_MAIN_SRCS
 	mapObjects/ObjectTemplate.cpp
 	mapObjects/ObstacleSetHandler.cpp
 
+	mapObjects/army/CArmedInstance.cpp
+	mapObjects/army/CCommanderInstance.cpp
+	mapObjects/army/CCreatureSet.cpp
+	mapObjects/army/CStackBasicDescriptor.cpp
+	mapObjects/army/CStackInstance.cpp
+
 	mapping/CDrawRoadsOperation.cpp
 	mapping/CMap.cpp
 	mapping/CMapHeader.cpp
@@ -251,7 +256,6 @@ set(lib_MAIN_SRCS
 	serializer/SerializerReflection.cpp
 
 	spells/AbilityCaster.cpp
-	spells/AdventureSpellMechanics.cpp
 	spells/BattleSpellMechanics.cpp
 	spells/BonusCaster.cpp
 	spells/CSpellHandler.cpp
@@ -264,6 +268,13 @@ set(lib_MAIN_SRCS
 	spells/TargetCondition.cpp
 	spells/ViewSpellInt.cpp
 
+	spells/adventure/AdventureSpellMechanics.cpp
+	spells/adventure/DimensionDoorMechanics.cpp
+	spells/adventure/ScuttleBoatMechanics.cpp
+	spells/adventure/SummonBoatMechanics.cpp
+	spells/adventure/TownPortalMechanics.cpp
+	spells/adventure/ViewWorldMechanics.cpp
+
 	spells/effects/Catapult.cpp
 	spells/effects/Clone.cpp
 	spells/effects/Damage.cpp
@@ -293,7 +304,6 @@ set(lib_MAIN_SRCS
 	CAndroidVMHelper.cpp
 	CBonusTypeHandler.cpp
 	CCreatureHandler.cpp
-	CCreatureSet.cpp
 	CPlayerState.cpp
 	CRandomGenerator.cpp
 	CScriptingModule.cpp
@@ -558,7 +568,6 @@ set(lib_MAIN_HEADERS
 	mapObjectConstructors/ShipyardInstanceConstructor.h
 	mapObjectConstructors/SObjectSounds.h
 
-	mapObjects/CArmedInstance.h
 	mapObjects/CGCreature.h
 	mapObjects/CGDwelling.h
 	mapObjects/CGHeroInstance.h
@@ -581,9 +590,17 @@ set(lib_MAIN_HEADERS
 	mapObjects/ObjectTemplate.h
 	mapObjects/ObstacleSetHandler.h
 
+	mapObjects/army/CArmedInstance.h
+	mapObjects/army/CCommanderInstance.h
+	mapObjects/army/CCreatureSet.h
+	mapObjects/army/CSimpleArmy.h
+	mapObjects/army/CStackBasicDescriptor.h
+	mapObjects/army/CStackInstance.h
+
 	mapping/CDrawRoadsOperation.h
-	mapping/CMapDefines.h
+	mapping/CCastleEvent.h
 	mapping/CMapEditManager.h
+	mapping/CMapEvent.h
 	mapping/CMapHeader.h
 	mapping/CMap.h
 	mapping/CMapInfo.h
@@ -598,6 +615,7 @@ set(lib_MAIN_HEADERS
 	mapping/MapReaderH3M.h
 	mapping/MapFormatJson.h
 	mapping/ObstacleProxy.h
+	mapping/TerrainTile.h
 
 	modding/ActiveModsInSaveList.h
 	modding/CModHandler.h
@@ -700,7 +718,6 @@ set(lib_MAIN_HEADERS
 	serializer/SerializerReflection.h
 
 	spells/AbilityCaster.h
-	spells/AdventureSpellMechanics.h
 	spells/BattleSpellMechanics.h
 	spells/BonusCaster.h
 	spells/CSpellHandler.h
@@ -713,6 +730,13 @@ set(lib_MAIN_HEADERS
 	spells/TargetCondition.h
 	spells/ViewSpellInt.h
 
+	spells/adventure/AdventureSpellMechanics.h
+	spells/adventure/DimensionDoorMechanics.h
+	spells/adventure/ScuttleBoatMechanics.h
+	spells/adventure/SummonBoatMechanics.h
+	spells/adventure/TownPortalMechanics.h
+	spells/adventure/ViewWorldMechanics.h
+
 	spells/effects/Catapult.h
 	spells/effects/Clone.h
 	spells/effects/Damage.h
@@ -744,7 +768,6 @@ set(lib_MAIN_HEADERS
 	CAndroidVMHelper.h
 	CBonusTypeHandler.h
 	CCreatureHandler.h
-	CCreatureSet.h
 	ConditionalWait.h
 	Color.h
 	CPlayerState.h

+ 1 - 1
lib/battle/BattleLayout.cpp

@@ -13,8 +13,8 @@
 #include "../GameSettings.h"
 #include "../GameLibrary.h"
 #include "../callback/IGameInfoCallback.h"
+#include "../mapObjects/army/CArmedInstance.h"
 #include "../json/JsonNode.h"
-#include "../mapObjects/CArmedInstance.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 0 - 1
lib/battle/BattleStateInfoForRetreat.cpp

@@ -12,7 +12,6 @@
 #include "BattleStateInfoForRetreat.h"
 #include "Unit.h"
 #include "CBattleInfoCallback.h"
-#include "../CCreatureSet.h"
 #include "../mapObjects/CGHeroInstance.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 1 - 0
lib/battle/DamageCalculator.cpp

@@ -15,6 +15,7 @@
 #include "Unit.h"
 
 #include "../bonuses/Bonus.h"
+#include "../CCreatureHandler.h"
 #include "../mapObjects/CGTownInstance.h"
 #include "../spells/CSpellHandler.h"
 #include "../IGameSettings.h"

+ 4 - 0
lib/battle/SideInBattle.cpp

@@ -19,7 +19,11 @@ void SideInBattle::init(const CGHeroInstance * Hero, const CArmedInstance * Army
 {
 	armyObjectID = Army->id;
 	if (Hero)
+	{
 		heroID = Hero->id;
+		initialMana = Hero->mana;
+		additionalMana = Hero->valOfBonuses(BonusType::COMBAT_MANA_BONUS);
+	}
 
 	switch(Army->ID.toEnum())
 	{

+ 4 - 0
lib/battle/SideInBattle.h

@@ -28,6 +28,8 @@ struct DLL_LINKAGE SideInBattle : public GameCallbackHolder
 	uint32_t castSpellsCount = 0; //how many spells each side has been cast this turn
 	std::vector<SpellID> usedSpellsHistory; //every time hero casts spell, it's inserted here -> eagle eye skill
 	int32_t enchanterCounter = 0; //tends to pass through 0, so sign is needed
+	int32_t initialMana = 0;
+	int32_t additionalMana = 0;
 
 	void init(const CGHeroInstance * Hero, const CArmedInstance * Army);
 	const CArmedInstance * getArmy() const;
@@ -41,6 +43,8 @@ struct DLL_LINKAGE SideInBattle : public GameCallbackHolder
 		h & castSpellsCount;
 		h & usedSpellsHistory;
 		h & enchanterCounter;
+		h & initialMana;
+		h & additionalMana;
 	}
 };
 

+ 0 - 1
lib/bonuses/Bonus.cpp

@@ -16,7 +16,6 @@
 
 #include "../CBonusTypeHandler.h"
 #include "../CCreatureHandler.h"
-#include "../CCreatureSet.h"
 #include "../CSkillHandler.h"
 #include "../TerrainHandler.h"
 #include "../GameLibrary.h"

+ 1 - 0
lib/bonuses/BonusEnum.h

@@ -192,6 +192,7 @@ class JsonNode;
 	BONUS_NAME(FULL_MAP_SCOUTING) /*Skyship*/\
 	BONUS_NAME(FULL_MAP_DARKNESS) /*opposite to Skyship*/\
 	BONUS_NAME(TRANSMUTATION_IMMUNITY) /*blocks TRANSMUTATION bonus*/\
+	BONUS_NAME(COMBAT_MANA_BONUS) /* Additional mana per combat */ \
 	BONUS_NAME(SPECIFIC_SPELL_RANGE) /* value used for allowed spell range, subtype - spell id */\
 	/* end of list */
 

+ 0 - 1
lib/bonuses/Limiters.cpp

@@ -16,7 +16,6 @@
 #include "../GameLibrary.h"
 #include "../entities/faction/CTownHandler.h"
 #include "../CCreatureHandler.h"
-#include "../CCreatureSet.h"
 #include "../CStack.h"
 #include "../TerrainHandler.h"
 #include "../constants/StringConstants.h"

+ 1 - 0
lib/gameState/CGameState.cpp

@@ -50,6 +50,7 @@
 #include "../mapObjects/CGTownInstance.h"
 #include "../mapObjects/CQuest.h"
 #include "../mapObjects/MiscObjects.h"
+#include "../mapping/CCastleEvent.h"
 #include "../mapping/CMap.h"
 #include "../mapping/CMapEditManager.h"
 #include "../mapping/CMapService.h"

+ 2 - 0
lib/gameState/EVictoryLossCheckResult.h

@@ -9,6 +9,8 @@
  */
 #pragma once
 
+#include "../texts/MetaString.h"
+
 VCMI_LIB_NAMESPACE_BEGIN
 
 class DLL_LINKAGE EVictoryLossCheckResult

+ 39 - 10
lib/gameState/GameStatePackVisitor.cpp

@@ -1176,18 +1176,27 @@ void GameStatePackVisitor::visitBattleStart(BattleStart & pack)
 	pack.info->battleID = gs.nextBattleID;
 	pack.info->localInit();
 
-	if (pack.info->getDefendedTown() && pack.info->getSideHero(BattleSide::DEFENDER))
+	if (pack.info->getDefendedTown() && pack.info->getSide(BattleSide::DEFENDER).heroID.hasValue())
 	{
 		CGTownInstance * town = gs.getTown(pack.info->townID);
 		CGHeroInstance * hero = gs.getHero(pack.info->getSideHero(BattleSide::DEFENDER)->id);
 
-		if (town->getVisitingHero() == hero)
+		if (hero)
 		{
 			hero->detachFrom(town->townAndVis);
 			hero->attachTo(*town);
 		}
 	}
 
+	for(auto i : {BattleSide::ATTACKER, BattleSide::DEFENDER})
+	{
+		if (pack.info->getSide(i).heroID.hasValue())
+		{
+			CGHeroInstance * hero = gs.getHero(pack.info->getSideHero(i)->id);
+			hero->mana = pack.info->getSide(i).initialMana + pack.info->getSide(i).additionalMana;
+		}
+	}
+
 	gs.currentBattles.push_back(std::move(pack.info));
 	gs.nextBattleID = BattleID(gs.nextBattleID.getNum() + 1);
 }
@@ -1366,7 +1375,7 @@ void GameStatePackVisitor::restorePreBattleState(BattleID battleID)
 		CGTownInstance * town = gs.getTown(currentBattle.townID);
 		CGHeroInstance * hero = gs.getHero(currentBattle.getSideHero(BattleSide::DEFENDER)->id);
 
-		if (town->getVisitingHero() == hero)
+		if (hero)
 		{
 			hero->detachFrom(*town);
 			hero->attachTo(town->townAndVis);
@@ -1383,6 +1392,17 @@ void GameStatePackVisitor::visitBattleCancelled(BattleCancelled & pack)
 		return battle->battleID == pack.battleID;
 	});
 
+	const auto & currentBattle = **battleIter;
+
+	for(auto i : {BattleSide::ATTACKER, BattleSide::DEFENDER})
+	{
+		if (currentBattle.getSide(i).heroID.hasValue())
+		{
+			CGHeroInstance * hero = gs.getHero(currentBattle.getSideHero(i)->id);
+			hero->mana = currentBattle.getSide(i).initialMana;
+		}
+	}
+
 	assert(battleIter != gs.currentBattles.end());
 	gs.currentBattles.erase(battleIter);
 }
@@ -1401,14 +1421,23 @@ void GameStatePackVisitor::visitBattleResultsApplied(BattleResultsApplied & pack
 	for(auto & movingPack : pack.movingArtifacts)
 		movingPack.visit(*this);
 
-	const auto currentBattle = std::find_if(gs.currentBattles.begin(), gs.currentBattles.end(),
-											[&](const auto & battle)
-											{
-												return battle->battleID == pack.battleID;
-											});
+	auto battleIter = boost::range::find_if(gs.currentBattles, [&](const auto & battle)
+	{
+		return battle->battleID == pack.battleID;
+	});
+	const auto & currentBattle = **battleIter;
+
+	for(auto i : {BattleSide::ATTACKER, BattleSide::DEFENDER})
+	{
+		if (currentBattle.getSide(i).heroID.hasValue())
+		{
+			CGHeroInstance * hero = gs.getHero(currentBattle.getSideHero(i)->id);
+			hero->mana = std::min(hero->mana, currentBattle.getSide(i).initialMana);
+		}
+	}
 
-	assert(currentBattle != gs.currentBattles.end());
-	gs.currentBattles.erase(currentBattle);
+	assert(battleIter != gs.currentBattles.end());
+	gs.currentBattles.erase(battleIter);
 }
 
 void GameStatePackVisitor::visitBattleObstaclesChanged(BattleObstaclesChanged & pack)

+ 75 - 0
lib/gameState/GameStatistics.cpp

@@ -22,6 +22,8 @@
 #include "../mapObjects/MiscObjects.h"
 #include "../mapping/CMap.h"
 #include "../entities/building/CBuilding.h"
+#include "../serializer/JsonDeserializer.h"
+#include "../serializer/JsonUpdater.h"
 
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -79,6 +81,79 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons
 	return data;
 }
 
+void StatisticDataSetEntry::serializeJson(JsonSerializeFormat & handler)
+{
+	handler.serializeString("map", map);
+	handler.serializeInt("timestamp", timestamp);
+	handler.serializeInt("day", day);
+	handler.serializeId("player", player, PlayerColor::CANNOT_DETERMINE);
+	handler.serializeString("playerName", playerName);
+	handler.serializeInt("team", team);
+	handler.serializeBool("isHuman", isHuman);
+	handler.serializeEnum("status", status, {"ingame", "loser", "winner"});
+	resources.serializeJson(handler, "resources");
+	handler.serializeInt("numberHeroes", numberHeroes);
+	handler.serializeInt("numberTowns", numberTowns);
+	handler.serializeInt("numberArtifacts", numberArtifacts);
+	handler.serializeInt("numberDwellings", numberDwellings);
+	handler.serializeInt("armyStrength", armyStrength);
+	handler.serializeInt("totalExperience", totalExperience);
+	handler.serializeInt("income", income);
+	handler.serializeFloat("mapExploredRatio", mapExploredRatio);
+	handler.serializeFloat("obeliskVisitedRatio", obeliskVisitedRatio);
+	handler.serializeFloat("townBuiltRatio", townBuiltRatio);
+	handler.serializeBool("hasGrail", hasGrail);
+	{
+		auto zonesData = handler.enterStruct("numMines");
+		for(TResource idx = 0; idx < (GameConstants::RESOURCE_QUANTITY - 1); idx++)
+			handler.serializeInt(GameConstants::RESOURCE_NAMES[idx], numMines[idx], 0);
+	}
+	handler.serializeInt("score", score);
+	handler.serializeInt("maxHeroLevel", maxHeroLevel);
+	handler.serializeInt("numBattlesNeutral", numBattlesNeutral);
+	handler.serializeInt("numBattlesPlayer", numBattlesPlayer);
+	handler.serializeInt("numWinBattlesNeutral", numWinBattlesNeutral);
+	handler.serializeInt("numWinBattlesPlayer", numWinBattlesPlayer);
+	handler.serializeInt("numHeroSurrendered", numHeroSurrendered);
+	handler.serializeInt("numHeroEscaped", numHeroEscaped);
+	spentResourcesForArmy.serializeJson(handler, "spentResourcesForArmy");
+	spentResourcesForBuildings.serializeJson(handler, "spentResourcesForBuildings");
+	tradeVolume.serializeJson(handler, "tradeVolume");
+	handler.serializeBool("eventCapturedTown", eventCapturedTown);
+	handler.serializeBool("eventDefeatedStrongestHero", eventDefeatedStrongestHero);
+	handler.serializeInt("movementPointsUsed", movementPointsUsed);
+}
+
+void StatisticDataSet::PlayerAccumulatedValueStorage::serializeJson(JsonSerializeFormat & handler)
+{
+	handler.serializeInt("numBattlesNeutral", numBattlesNeutral);
+	handler.serializeInt("numBattlesPlayer", numBattlesPlayer);
+	handler.serializeInt("numWinBattlesNeutral", numWinBattlesNeutral);
+	handler.serializeInt("numWinBattlesPlayer", numWinBattlesPlayer);
+	handler.serializeInt("numHeroSurrendered", numHeroSurrendered);
+	handler.serializeInt("numHeroEscaped", numHeroEscaped);
+	spentResourcesForArmy.serializeJson(handler, "spentResourcesForArmy");
+	spentResourcesForBuildings.serializeJson(handler, "spentResourcesForBuildings");
+	tradeVolume.serializeJson(handler, "tradeVolume");
+	handler.serializeInt("movementPointsUsed", movementPointsUsed);
+	handler.serializeInt("lastCapturedTownDay", lastCapturedTownDay);
+	handler.serializeInt("lastDefeatedStrongestHeroDay", lastDefeatedStrongestHeroDay);
+}
+
+void StatisticDataSet::serializeJson(JsonSerializeFormat & handler)
+{
+	{
+		auto eventsHandler = handler.enterArray("data");
+		eventsHandler.syncSize(data, JsonNode::JsonType::DATA_VECTOR);
+		eventsHandler.serializeStruct(data);
+	}
+	{
+		auto eventsHandler = handler.enterStruct("accumulatedValues");
+		for(auto & val : accumulatedValues)
+			eventsHandler->serializeStruct(GameConstants::PLAYER_COLOR_NAMES[val.first], val.second);
+	}
+}
+
 std::string StatisticDataSet::toCsv(std::string sep) const
 {
 	std::stringstream ss;

+ 6 - 0
lib/gameState/GameStatistics.h

@@ -57,6 +57,8 @@ struct DLL_LINKAGE StatisticDataSetEntry
 	bool eventDefeatedStrongestHero;
 	si64 movementPointsUsed;
 
+	void serializeJson(JsonSerializeFormat & handler);
+
 	template <typename Handler> void serialize(Handler &h)
 	{
 		h & map;
@@ -105,6 +107,8 @@ public:
 	std::string toCsv(std::string sep) const;
 	std::string writeCsv() const;
 
+	void serializeJson(JsonSerializeFormat & handler);
+
 	struct PlayerAccumulatedValueStorage // holds some actual values needed for stats
 	{
 		int numBattlesNeutral;
@@ -120,6 +124,8 @@ public:
 		int lastCapturedTownDay;
 		int lastDefeatedStrongestHeroDay;
 
+		void serializeJson(JsonSerializeFormat & handler);
+
 		template <typename Handler> void serialize(Handler &h)
 		{
 			h & numBattlesNeutral;

+ 1 - 1
lib/gameState/InfoAboutArmy.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "CCreatureSet.h"
+#include "../mapObjects/army/CStackBasicDescriptor.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/json/JsonRandom.cpp

@@ -23,13 +23,13 @@
 #include "../constants/StringConstants.h"
 #include "../GameLibrary.h"
 #include "../CCreatureHandler.h"
-#include "../CCreatureSet.h"
 #include "../spells/CSpellHandler.h"
 #include "../CSkillHandler.h"
 #include "../entities/artifact/CArtHandler.h"
 #include "../entities/hero/CHero.h"
 #include "../entities/hero/CHeroClass.h"
 #include "../gameState/CGameState.h"
+#include "../mapObjects/army/CStackBasicDescriptor.h"
 #include "../mapObjects/IObjectInterface.h"
 #include "../modding/IdentifierStorage.h"
 #include "../modding/ModScope.h"

+ 13 - 1
lib/mapObjectConstructors/CObjectClassesHandler.cpp

@@ -8,6 +8,7 @@
  *
  */
 #include "StdInc.h"
+#include "CConfigHandler.h"
 #include "CObjectClassesHandler.h"
 
 #include "../filesystem/Filesystem.h"
@@ -205,9 +206,20 @@ void CObjectClassesHandler::loadSubObject(const std::string & scope, const std::
 
 TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * baseObject, size_t index)
 {
-	assert(identifier.find(':') == std::string::npos);
 	assert(!scope.empty());
 
+	if (settings["mods"]["validation"].String() != "off")
+	{
+		size_t separator = identifier.find(':');
+
+		if (separator != std::string::npos)
+		{
+			std::string modName = identifier.substr(0, separator);
+			std::string objectName = identifier.substr(separator + 1);
+			logMod->warn("Mod %s: Map object type with format '%s' will add new map object, not modify it! Please use '%s' form and add dependency on mod '%s' instead!", scope, identifier, modName, identifier );
+		}
+	}
+
 	std::string handler = baseObject->handlerName;
 	if(!handlerConstructors.count(handler))
 	{

+ 1 - 1
lib/mapObjectConstructors/CommonConstructors.cpp

@@ -26,8 +26,8 @@
 #include "../mapObjects/CGTownInstance.h"
 #include "../mapObjects/MiscObjects.h"
 #include "../mapObjects/ObjectTemplate.h"
+#include "../mapping/TerrainTile.h"
 #include "../modding/IdentifierStorage.h"
-#include "../mapping/CMapDefines.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 0
lib/mapObjectConstructors/DwellingInstanceConstructor.cpp

@@ -14,6 +14,7 @@
 #include "../texts/CGeneralTextHandler.h"
 #include "../json/JsonRandom.h"
 #include "../GameLibrary.h"
+#include "../mapObjects/army/CStackInstance.h"
 #include "../mapObjects/CGDwelling.h"
 #include "../mapObjects/ObjectTemplate.h"
 #include "../modding/IdentifierStorage.h"

+ 1 - 1
lib/mapObjects/CGCreature.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "CArmedInstance.h"
+#include "army/CArmedInstance.h"
 #include "../ResourceSet.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 2 - 1
lib/mapObjects/CGDwelling.h

@@ -10,9 +10,10 @@
 
 #pragma once
 
-#include "CArmedInstance.h"
 #include "IOwnableObject.h"
 
+#include "army/CArmedInstance.h"
+
 VCMI_LIB_NAMESPACE_BEGIN
 
 class CGDwelling;

+ 1 - 4
lib/mapObjects/CGHeroInstance.cpp

@@ -1360,10 +1360,7 @@ CBonusSystemNode & CGHeroInstance::whereShouldBeAttached(CGameState & gs)
 	if(visitedTown.hasValue())
 	{
 		auto town = gs.getTown(visitedTown);
-		if(isGarrisoned())
-			return *town;
-		else
-			return town->townAndVis;
+		return town->townAndVis;
 	}
 	else
 		return CArmedInstance::whereShouldBeAttached(gs);

+ 3 - 1
lib/mapObjects/CGHeroInstance.h

@@ -11,9 +11,11 @@
 
 #include <vcmi/spells/Caster.h>
 
-#include "CArmedInstance.h"
 #include "IOwnableObject.h"
 
+#include "army/CArmedInstance.h"
+#include "army/CCommanderInstance.h"
+
 #include "../bonuses/BonusCache.h"
 #include "../entities/hero/EHeroGender.h"
 

+ 8 - 1
lib/mapObjects/CGPandoraBox.cpp

@@ -275,6 +275,12 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler)
 	}
 }
 
+CGEvent::CGEvent(IGameInfoCallback * cb)
+	: CGPandoraBox(cb)
+	, availableFor(PlayerColor::ALL_PLAYERS().begin(), PlayerColor::ALL_PLAYERS().end())
+{
+}
+
 void CGEvent::init()
 {
 	blockVisit = false;
@@ -333,7 +339,8 @@ void CGEvent::serializeJsonOptions(JsonSerializeFormat & handler)
 	handler.serializeBool("aIActivable", computerActivate, false);
 	handler.serializeBool("humanActivable", humanActivate, true);
 	handler.serializeBool("removeAfterVisit", removeAfterVisit, false);
-	handler.serializeIdArray("availableFor", availableFor);
+	if (handler.saving || !handler.getCurrent()["availableFor"].isNull())
+		handler.serializeIdArray("availableFor", availableFor); // else - keep default value
 }
 
 VCMI_LIB_NAMESPACE_END

+ 10 - 5
lib/mapObjects/CGPandoraBox.h

@@ -43,12 +43,17 @@ protected:
 class DLL_LINKAGE CGEvent : public CGPandoraBox  //event objects
 {
 public:
-	using CGPandoraBox::CGPandoraBox;
+	CGEvent(IGameInfoCallback *cb);
 
-	bool removeAfterVisit = false; //true if event is removed after occurring
-	std::set<PlayerColor> availableFor; //players whom this event is available for
-	bool computerActivate = false; //true if computer player can activate this event
-	bool humanActivate = false; //true if human player can activate this event
+	//players whom this event is available for
+	std::set<PlayerColor> availableFor;
+
+	//true if event is removed after occurring
+	bool removeAfterVisit = false;
+	//true if computer player can activate this event
+	bool computerActivate = false;
+	//true if human player can activate this event
+	bool humanActivate = false;
 
 	template <typename Handler> void serialize(Handler &h)
 	{

+ 1 - 1
lib/mapObjects/CGResource.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "CArmedInstance.h"
+#include "army/CArmedInstance.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 0
lib/mapObjects/CGTownInstance.cpp

@@ -22,6 +22,7 @@
 #include "../texts/CGeneralTextHandler.h"
 #include "../gameState/CGameState.h"
 #include "../gameState/UpgradeInfo.h"
+#include "../mapping/CCastleEvent.h"
 #include "../mapping/CMap.h"
 #include "../CPlayerState.h"
 #include "../StartInfo.h"

+ 1 - 1
lib/mapObjects/CGTownInstance.h

@@ -167,7 +167,7 @@ public:
 	void removeAllBuildings();
 	std::set<BuildingID> getBuildings() const;
 
-	TResources getBuildingCost(const BuildingID & buildingID) const;
+	ResourceSet getBuildingCost(const BuildingID & buildingID) const;
 	ResourceSet dailyIncome() const override;
 	std::vector<CreatureID> providedCreatures() const override;
 

+ 2 - 1
lib/mapObjects/CRewardableObject.h

@@ -9,7 +9,8 @@
  */
 #pragma once
 
-#include "CArmedInstance.h"
+#include "army/CArmedInstance.h"
+
 #include "../rewardable/Interface.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 1 - 0
lib/mapObjects/IObjectInterface.cpp

@@ -18,6 +18,7 @@
 #include "../callback/IGameInfoCallback.h"
 #include "../callback/IGameEventCallback.h"
 #include "../mapObjects/CGHeroInstance.h"
+#include "../mapping/TerrainTile.h"
 #include "../networkPacks/PacksForClient.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 0 - 1
lib/mapObjects/MapObjects.h

@@ -13,7 +13,6 @@
 // Possible TODO - remove this header after CObjectHandler.cpp will be fully split into smaller files
 #include "CObjectHandler.h"
 
-#include "CArmedInstance.h"
 #include "CGDwelling.h"
 #include "CGHeroInstance.h"
 #include "CGMarket.h"

+ 2 - 1
lib/mapObjects/MiscObjects.h

@@ -9,8 +9,9 @@
  */
 #pragma once
 
-#include "CArmedInstance.h"
 #include "IOwnableObject.h"
+#include "army/CArmedInstance.h"
+#include "../entities/artifact/CArtifactInstance.h"
 #include "../texts/MetaString.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 27 - 30
lib/mapObjects/CArmedInstance.cpp → lib/mapObjects/army/CArmedInstance.cpp

@@ -11,22 +11,20 @@
 #include "StdInc.h"
 #include "CArmedInstance.h"
 
-#include "../CCreatureHandler.h"
-#include "../CPlayerState.h"
-#include "../callback/IGameInfoCallback.h"
-#include "../entities/faction/CFaction.h"
-#include "../entities/faction/CTown.h"
-#include "../entities/faction/CTownHandler.h"
-#include "../GameLibrary.h"
-#include "../gameState/CGameState.h"
-#include "../mapping/CMapDefines.h"
-#include "../texts/CGeneralTextHandler.h"
+#include "CStackInstance.h"
+
+#include "../../CPlayerState.h"
+#include "../../entities/faction/CTown.h"
+#include "../../entities/faction/CTownHandler.h"
+#include "../../mapping/TerrainTile.h"
+#include "../../GameLibrary.h"
+#include "../../gameState/CGameState.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
 void CArmedInstance::randomizeArmy(FactionID type)
 {
-	for (auto & elem : stacks)
+	for(auto & elem : stacks)
 	{
 		if(elem.second->randomStack)
 		{
@@ -41,16 +39,16 @@ void CArmedInstance::randomizeArmy(FactionID type)
 	}
 }
 
-CArmedInstance::CArmedInstance(IGameInfoCallback *cb)
-	:CArmedInstance(cb, BonusNodeType::ARMY, false)
+CArmedInstance::CArmedInstance(IGameInfoCallback * cb)
+	: CArmedInstance(cb, BonusNodeType::ARMY, false)
 {
 }
 
-CArmedInstance::CArmedInstance(IGameInfoCallback *cb, BonusNodeType nodeType, bool isHypothetic):
-	CGObjectInstance(cb),
-	CBonusSystemNode(nodeType, isHypothetic),
-	nonEvilAlignmentMix(this, Selector::type()(BonusType::NONEVIL_ALIGNMENT_MIX)), // Take Angelic Alliance troop-mixing freedom of non-evil units into account.
-	battle(nullptr)
+CArmedInstance::CArmedInstance(IGameInfoCallback * cb, BonusNodeType nodeType, bool isHypothetic)
+	: CGObjectInstance(cb)
+	, CBonusSystemNode(nodeType, isHypothetic)
+	, nonEvilAlignmentMix(this, Selector::type()(BonusType::NONEVIL_ALIGNMENT_MIX)) // Take Angelic Alliance troop-mixing freedom of non-evil units into account.
+	, battle(nullptr)
 {
 }
 
@@ -60,7 +58,7 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 		return;
 
 	auto b = getExportedBonusList().getFirst(Selector::sourceType()(BonusSource::ARMY).And(Selector::type()(BonusType::MORALE)));
- 	if(!b)
+	if(!b)
 	{
 		b = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::MORALE, BonusSource::ARMY, 0, BonusSourceID());
 		addNewBonus(b);
@@ -72,11 +70,11 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 
 	for(const auto & slot : Slots())
 	{
-		const auto * creature  = slot.second->getCreatureID().toEntity(LIBRARY);
+		const auto * creature = slot.second->getCreatureID().toEntity(LIBRARY);
 
 		factions.insert(creature->getFactionID());
 		// Check for undead flag instead of faction (undead mummies are neutral)
-		if (!hasUndead)
+		if(!hasUndead)
 		{
 			//this is costly check, let's skip it at first undead
 			hasUndead |= slot.second->hasBonusOfType(BonusType::UNDEAD);
@@ -85,16 +83,16 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 
 	size_t factionsInArmy = factions.size(); //town garrison seems to take both sets into account
 
-	if (nonEvilAlignmentMix.hasBonus())
+	if(nonEvilAlignmentMix.hasBonus())
 	{
 		size_t mixableFactions = 0;
 
 		for(auto f : factions)
 		{
-			if (LIBRARY->factions()->getById(f)->getAlignment() != EAlignment::EVIL)
+			if(LIBRARY->factions()->getById(f)->getAlignment() != EAlignment::EVIL)
 				mixableFactions++;
 		}
-		if (mixableFactions > 0)
+		if(mixableFactions > 0)
 			factionsInArmy -= mixableFactions - 1;
 	}
 
@@ -105,20 +103,20 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 		b->val = +1;
 		bonusDescription.appendTextID("core.arraytxt.115"); //All troops of one alignment +1
 	}
-	else if (!factions.empty()) // no bonus from empty garrison
+	else if(!factions.empty()) // no bonus from empty garrison
 	{
 		b->val = 2 - static_cast<si32>(factionsInArmy);
 		bonusDescription.appendTextID("core.arraytxt.114"); //Troops of %d alignments %d
 		bonusDescription.replaceNumber(factionsInArmy);
 	}
-	
+
 	b->description = bonusDescription;
 
 	nodeHasChanged();
 
 	//-1 modifier for any Undead unit in army
 	auto undeadModifier = getExportedBonusList().getFirst(Selector::source(BonusSource::ARMY, BonusCustomSource::undeadMoraleDebuff));
- 	if(hasUndead)
+	if(hasUndead)
 	{
 		if(!undeadModifier)
 		{
@@ -129,7 +127,6 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 	}
 	else if(undeadModifier)
 		removeBonus(undeadModifier);
-
 }
 
 void CArmedInstance::armyChanged()
@@ -176,7 +173,7 @@ void CArmedInstance::attachUnitsToArmy()
 		elem.second->setArmy(getArmy());
 }
 
-const IBonusBearer* CArmedInstance::getBonusBearer() const
+const IBonusBearer * CArmedInstance::getBonusBearer() const
 {
 	return this;
 }
@@ -189,7 +186,7 @@ void CArmedInstance::serializeJsonOptions(JsonSerializeFormat & handler)
 
 TerrainId CArmedInstance::getCurrentTerrain() const
 {
-	if (anchorPos().isValid())
+	if(anchorPos().isValid())
 		return cb->getTile(visitablePos())->getTerrainID();
 	else
 		return TerrainId::NONE;

+ 26 - 15
lib/mapObjects/CArmedInstance.h → lib/mapObjects/army/CArmedInstance.h

@@ -9,10 +9,14 @@
  */
 #pragma once
 
-#include "CGObjectInstance.h"
-#include "../CCreatureSet.h"
-#include "../bonuses/CBonusSystemNode.h"
-#include "../bonuses/BonusCache.h"
+#include "CCreatureSet.h"
+
+#include "../CGObjectInstance.h"
+
+#include "../../bonuses/BonusCache.h"
+#include "../../bonuses/CBonusSystemNode.h"
+
+#include <vcmi/Entity.h>
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -32,26 +36,32 @@ protected:
 	virtual CBonusSystemNode & whatShouldBeAttached();
 
 public:
-	BattleInfo *battle; //set to the current battle, if engaged
+	BattleInfo * battle; //set to the current battle, if engaged
 
 	void randomizeArmy(FactionID type);
 	virtual void updateMoraleBonusFromArmy();
 
 	void armyChanged() override;
-	CArmedInstance * getArmy() final { return this; }
-	const CArmedInstance * getArmy() const final { return this; }
+	CArmedInstance * getArmy() final
+	{
+		return this;
+	}
+	const CArmedInstance * getArmy() const final
+	{
+		return this;
+	}
 
 	//////////////////////////////////////////////////////////////////////////
 	//IConstBonusProvider
-	const IBonusBearer* getBonusBearer() const override;
+	const IBonusBearer * getBonusBearer() const override;
 
 	void attachToBonusSystem(CGameState & gs) override;
 	void detachFromBonusSystem(CGameState & gs) override;
 	void restoreBonusSystem(CGameState & gs) override;
 	//////////////////////////////////////////////////////////////////////////
 
-	CArmedInstance(IGameInfoCallback *cb);
-	CArmedInstance(IGameInfoCallback *cb, BonusNodeType nodeType, bool isHypothetic);
+	CArmedInstance(IGameInfoCallback * cb);
+	CArmedInstance(IGameInfoCallback * cb, BonusNodeType nodeType, bool isHypothetic);
 
 	PlayerColor getOwner() const override
 	{
@@ -59,14 +69,15 @@ public:
 	}
 
 	TerrainId getCurrentTerrain() const;
-	
+
 	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 
-	template <typename Handler> void serialize(Handler &h)
+	template<typename Handler>
+	void serialize(Handler & h)
 	{
-		h & static_cast<CGObjectInstance&>(*this);
-		h & static_cast<CBonusSystemNode&>(*this);
-		h & static_cast<CCreatureSet&>(*this);
+		h & static_cast<CGObjectInstance &>(*this);
+		h & static_cast<CBonusSystemNode &>(*this);
+		h & static_cast<CCreatureSet &>(*this);
 
 		if(!h.saving && h.loadingGamestate)
 			attachUnitsToArmy();

+ 80 - 0
lib/mapObjects/army/CCommanderInstance.cpp

@@ -0,0 +1,80 @@
+/*
+ * CCommanderInstance.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CCommanderInstance.h"
+
+#include "../../GameLibrary.h"
+#include "../../entities/hero/CHeroHandler.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+CCommanderInstance::CCommanderInstance(IGameInfoCallback * cb)
+	: CStackInstance(cb)
+{
+}
+
+CCommanderInstance::CCommanderInstance(IGameInfoCallback * cb, const CreatureID & id)
+	: CStackInstance(cb, BonusNodeType::COMMANDER, false)
+	, name("Commando")
+{
+	alive = true;
+	level = 1;
+	setCount(1);
+	setType(nullptr);
+	secondarySkills.resize(ECommander::SPELL_POWER + 1);
+	setType(id);
+	//TODO - parse them
+}
+
+void CCommanderInstance::setAlive(bool Alive)
+{
+	//TODO: helm of immortality
+	alive = Alive;
+	if(!alive)
+	{
+		removeBonusesRecursive(Bonus::UntilCommanderKilled);
+	}
+}
+
+bool CCommanderInstance::canGainExperience() const
+{
+	return alive;
+}
+
+int CCommanderInstance::getExpRank() const
+{
+	return LIBRARY->heroh->level(getTotalExperience());
+}
+
+int CCommanderInstance::getLevel() const
+{
+	return std::max(1, getExpRank());
+}
+
+void CCommanderInstance::levelUp()
+{
+	level++;
+	for(const auto & bonus : LIBRARY->creh->commanderLevelPremy)
+	{ //grant all regular level-up bonuses
+		accumulateBonus(bonus);
+	}
+}
+
+ArtBearer CCommanderInstance::bearerType() const
+{
+	return ArtBearer::COMMANDER;
+}
+
+bool CCommanderInstance::gainsLevel() const
+{
+	return getTotalExperience() >= LIBRARY->heroh->reqExp(level + 1);
+}
+
+VCMI_LIB_NAMESPACE_END

+ 55 - 0
lib/mapObjects/army/CCommanderInstance.h

@@ -0,0 +1,55 @@
+/*
+ * CCommanderInstance.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "CStackInstance.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class DLL_LINKAGE CCommanderInstance : public CStackInstance
+{
+public:
+	//TODO: what if Commander is not a part of creature set?
+
+	//commander class is determined by its base creature
+	ui8 alive; //maybe change to bool when breaking save compatibility?
+	ui8 level; //required only to count callbacks
+	std::string name; // each Commander has different name
+	std::vector<ui8> secondarySkills; //ID -> level
+	std::set<ui8> specialSkills;
+	//std::vector <CArtifactInstance *> arts;
+	CCommanderInstance(IGameInfoCallback * cb);
+	CCommanderInstance(IGameInfoCallback * cb, const CreatureID & id);
+	void setAlive(bool alive);
+	void levelUp();
+
+	bool canGainExperience() const override;
+	bool gainsLevel() const; //true if commander has lower level than should upon his experience
+	ui64 getPower() const override
+	{
+		return 0;
+	};
+	int getExpRank() const override;
+	int getLevel() const override;
+	ArtBearer bearerType() const override; //from CArtifactSet
+
+	template<typename Handler>
+	void serialize(Handler & h)
+	{
+		h & static_cast<CStackInstance &>(*this);
+		h & alive;
+		h & level;
+		h & name;
+		h & secondarySkills;
+		h & specialSkills;
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 43 - 544
lib/CCreatureSet.cpp → lib/mapObjects/army/CCreatureSet.cpp

@@ -10,27 +10,15 @@
 #include "StdInc.h"
 #include "CCreatureSet.h"
 
-#include "CConfigHandler.h"
-#include "CCreatureHandler.h"
-#include "GameLibrary.h"
-#include "IGameSettings.h"
-#include "callback/IGameInfoCallback.h"
-#include "entities/hero/CHeroHandler.h"
-#include "mapObjects/CGHeroInstance.h"
-#include "modding/ModScope.h"
-#include "texts/CGeneralTextHandler.h"
-#include "spells/CSpellHandler.h"
-#include "IBonusTypeHandler.h"
-#include "serializer/JsonSerializeFormat.h"
-#include "gameState/CGameState.h"
-
-#include <vcmi/FactionService.h>
-#include <vcmi/Faction.h>
+#include "../CGHeroInstance.h"
 
-VCMI_LIB_NAMESPACE_BEGIN
+#include "../../CConfigHandler.h"
+#include "../../texts/CGeneralTextHandler.h"
+#include "../../serializer/JsonSerializeFormat.h"
 
+VCMI_LIB_NAMESPACE_BEGIN
 
-bool CreatureSlotComparer::operator()(const TPairCreatureSlot & lhs, const TPairCreatureSlot & rhs)
+	bool CreatureSlotComparer::operator()(const TPairCreatureSlot & lhs, const TPairCreatureSlot & rhs)
 {
 	return lhs.first->getAIValue() < rhs.first->getAIValue(); // Descendant order sorting
 }
@@ -38,7 +26,7 @@ bool CreatureSlotComparer::operator()(const TPairCreatureSlot & lhs, const TPair
 const CStackInstance & CCreatureSet::operator[](const SlotID & slot) const
 {
 	auto i = stacks.find(slot);
-	if (i != stacks.end())
+	if(i != stacks.end())
 		return *i->second;
 	else
 		throw std::runtime_error("That slot is empty!");
@@ -47,7 +35,7 @@ const CStackInstance & CCreatureSet::operator[](const SlotID & slot) const
 const CCreature * CCreatureSet::getCreature(const SlotID & slot) const
 {
 	auto i = stacks.find(slot);
-	if (i != stacks.end())
+	if(i != stacks.end())
 		return i->second->getCreature();
 	else
 		return nullptr;
@@ -82,7 +70,7 @@ SlotID CCreatureSet::getSlotFor(const CreatureID & creature, ui32 slotsAmount) c
 	return getSlotFor(creature.toCreature(), slotsAmount);
 }
 
-SlotID CCreatureSet::getSlotFor(const CCreature *c, ui32 slotsAmount) const
+SlotID CCreatureSet::getSlotFor(const CCreature * c, ui32 slotsAmount) const
 {
 	assert(c);
 	for(const auto & elem : stacks)
@@ -149,7 +137,6 @@ bool CCreatureSet::isCreatureBalanced(const CCreature * c, TQuantity ignoreAmoun
 		if(count == ignoreAmount || count < 1)
 			continue;
 
-
 		if(count > max)
 			max = count;
 		if(count < min)
@@ -162,7 +149,7 @@ bool CCreatureSet::isCreatureBalanced(const CCreature * c, TQuantity ignoreAmoun
 
 SlotID CCreatureSet::getFreeSlot(ui32 slotsAmount) const
 {
-	for(ui32 i=0; i<slotsAmount; i++)
+	for(ui32 i = 0; i < slotsAmount; i++)
 	{
 		if(!vstd::contains(stacks, SlotID(i)))
 		{
@@ -190,7 +177,7 @@ std::queue<SlotID> CCreatureSet::getFreeSlotsQueue(ui32 slotsAmount) const
 {
 	std::queue<SlotID> freeSlots;
 
-	for (ui32 i = 0; i < slotsAmount; i++)
+	for(ui32 i = 0; i < slotsAmount; i++)
 	{
 		auto slot = SlotID(i);
 
@@ -236,7 +223,7 @@ TCreatureQueue CCreatureSet::getCreatureQueue(const SlotID & exclude) const
 
 TQuantity CCreatureSet::getStackCount(const SlotID & slot) const
 {
-	if (!hasStackAtSlot(slot))
+	if(!hasStackAtSlot(slot))
 		return 0;
 	return stacks.at(slot)->getCount();
 }
@@ -254,9 +241,9 @@ TExpType CCreatureSet::getStackAverageExperience(const SlotID & slot) const
 bool CCreatureSet::mergeableStacks(std::pair<SlotID, SlotID> & out, const SlotID & preferable) const /*looks for two same stacks, returns slot positions */
 {
 	//try to match creature to our preferred stack
-	if(preferable.validSlot() &&  vstd::contains(stacks, preferable))
+	if(preferable.validSlot() && vstd::contains(stacks, preferable))
 	{
-		const CCreature *cr = stacks.find(preferable)->second->getCreature();
+		const CCreature * cr = stacks.find(preferable)->second->getCreature();
 		for(const auto & elem : stacks)
 		{
 			if(cr == elem.second->getType() && elem.first != preferable)
@@ -285,7 +272,7 @@ bool CCreatureSet::mergeableStacks(std::pair<SlotID, SlotID> & out, const SlotID
 
 void CCreatureSet::addToSlot(const SlotID & slot, const CreatureID & cre, TQuantity count, bool allowMerging)
 {
-	const CCreature *c = cre.toCreature();
+	const CCreature * c = cre.toCreature();
 
 	if(!hasStackAtSlot(slot))
 	{
@@ -342,18 +329,18 @@ bool CCreatureSet::needsLastStack() const
 ui64 CCreatureSet::getArmyStrength(int fortLevel) const
 {
 	ui64 ret = 0;
-	for (const auto& elem : stacks)
+	for(const auto & elem : stacks)
 	{
 		ui64 powerToAdd = elem.second->getPower();
-		if (fortLevel > 0)
+		if(fortLevel > 0)
 		{
-			if (!elem.second->hasBonusOfType(BonusType::FLYING))
+			if(!elem.second->hasBonusOfType(BonusType::FLYING))
 			{
 				powerToAdd /= fortLevel;
-				if (!elem.second->hasBonusOfType(BonusType::SHOOTER))
+				if(!elem.second->hasBonusOfType(BonusType::SHOOTER))
 					powerToAdd /= fortLevel;
 			}
-		} 
+		}
 		ret += powerToAdd;
 	}
 	return ret;
@@ -362,7 +349,7 @@ ui64 CCreatureSet::getArmyStrength(int fortLevel) const
 ui64 CCreatureSet::getArmyCost() const
 {
 	ui64 ret = 0;
-	for (const auto& elem : stacks)
+	for(const auto & elem : stacks)
 		ret += elem.second->getMarketValue();
 	return ret;
 }
@@ -383,7 +370,7 @@ std::string CCreatureSet::getRoughAmount(const SlotID & slot, int mode) const
 		if(settings["gameTweaks"]["numericCreaturesQuantities"].Bool())
 			return CCreature::getQuantityRangeStringForId(quantity);
 
-		return LIBRARY->generaltexth->arraytxt[(174 + mode) + 3*(int)quantity];
+		return LIBRARY->generaltexth->arraytxt[(174 + mode) + 3 * (int)quantity];
 	}
 	return "";
 }
@@ -461,7 +448,8 @@ CStackInstance * CCreatureSet::getStackPtr(const SlotID & slot) const
 {
 	if(hasStackAtSlot(slot))
 		return stacks.find(slot)->second.get();
-	else return nullptr;
+	else
+		return nullptr;
 }
 
 void CCreatureSet::eraseStack(const SlotID & slot)
@@ -470,7 +458,7 @@ void CCreatureSet::eraseStack(const SlotID & slot)
 	detachStack(slot);
 }
 
-bool CCreatureSet::contains(const CStackInstance *stack) const
+bool CCreatureSet::contains(const CStackInstance * stack) const
 {
 	if(!stack)
 		return false;
@@ -482,10 +470,10 @@ bool CCreatureSet::contains(const CStackInstance *stack) const
 	return false;
 }
 
-SlotID CCreatureSet::findStack(const CStackInstance *stack) const
+SlotID CCreatureSet::findStack(const CStackInstance * stack) const
 {
 	const auto * h = dynamic_cast<const CGHeroInstance *>(this);
-	if (h && h->getCommander() == stack)
+	if(h && h->getCommander() == stack)
 		return SlotID::COMMANDER_SLOT_PLACEHOLDER;
 
 	if(!stack)
@@ -510,7 +498,7 @@ void CCreatureSet::putStack(const SlotID & slot, std::unique_ptr<CStackInstance>
 
 void CCreatureSet::joinStack(const SlotID & slot, std::unique_ptr<CStackInstance> stack)
 {
-	[[maybe_unused]] const CCreature *c = getCreature(slot);
+	[[maybe_unused]] const CCreature * c = getCreature(slot);
 	assert(c == stack->getType());
 	assert(c);
 
@@ -541,7 +529,7 @@ void CCreatureSet::changeStackCount(const SlotID & slot, TQuantity toAdd)
 
 CCreatureSet::~CCreatureSet() = default;
 
-void CCreatureSet::setToArmy(CSimpleArmy &src)
+void CCreatureSet::setToArmy(CSimpleArmy & src)
 {
 	clearSlots();
 	while(src)
@@ -577,12 +565,12 @@ void CCreatureSet::setStackType(const SlotID & slot, const CreatureID & type)
 	armyChanged();
 }
 
-bool CCreatureSet::canBeMergedWith(const CCreatureSet &cs, bool allowMergingStacks) const
+bool CCreatureSet::canBeMergedWith(const CCreatureSet & cs, bool allowMergingStacks) const
 {
 	if(!allowMergingStacks)
 	{
 		int freeSlots = stacksCount() - GameConstants::ARMY_SIZE;
-		std::set<const CCreature*> cresToAdd;
+		std::set<const CCreature *> cresToAdd;
 		for(const auto & elem : cs.stacks)
 		{
 			SlotID dest = getSlotFor(elem.second->getCreature());
@@ -598,13 +586,13 @@ bool CCreatureSet::canBeMergedWith(const CCreatureSet &cs, bool allowMergingStac
 
 		//get types of creatures that need their own slot
 		for(const auto & elem : cs.stacks)
-			if ((j = cres.getSlotFor(elem.second->getCreature())).validSlot())
-				cres.addToSlot(j, elem.second->getId(), 1, true);  //merge if possible
-			//cres.addToSlot(elem.first, elem.second->type->getId(), 1, true);
+			if((j = cres.getSlotFor(elem.second->getCreature())).validSlot())
+				cres.addToSlot(j, elem.second->getId(), 1, true); //merge if possible
+		//cres.addToSlot(elem.first, elem.second->type->getId(), 1, true);
 		for(const auto & elem : stacks)
 		{
-			if ((j = cres.getSlotFor(elem.second->getCreature())).validSlot())
-				cres.addToSlot(j, elem.second->getId(), 1, true);  //merge if possible
+			if((j = cres.getSlotFor(elem.second->getCreature())).validSlot())
+				cres.addToSlot(j, elem.second->getId(), 1, true); //merge if possible
 			else
 				return false; //no place found
 		}
@@ -622,22 +610,22 @@ bool CCreatureSet::hasUnits(const std::vector<CStackBasicDescriptor> & units, bo
 		for(const auto & slot : Slots())
 		{
 			const auto & heroStack = slot.second;
-			if (heroStack->getType() == reqStack.getType())
+			if(heroStack->getType() == reqStack.getType())
 			{
 				count += heroStack->getCount();
 				testedSlots += 1;
 			}
 		}
-		if (count > reqStack.getCount())
+		if(count > reqStack.getCount())
 			foundExtraCreatures = true;
 
-		if (count < reqStack.getCount()) //not enough creatures of this kind
+		if(count < reqStack.getCount()) //not enough creatures of this kind
 			return false;
 	}
 
-	if (requireLastStack)
+	if(requireLastStack)
 	{
-		if (!foundExtraCreatures && testedSlots >= Slots().size())
+		if(!foundExtraCreatures && testedSlots >= Slots().size())
 			return false;
 	}
 
@@ -649,16 +637,7 @@ bool CCreatureSet::hasStackAtSlot(const SlotID & slot) const
 	return vstd::contains(stacks, slot);
 }
 
-CCreatureSet & CCreatureSet::operator=(const CCreatureSet&cs)
-{
-	assert(0);
-	return *this;
-}
-
-void CCreatureSet::armyChanged()
-{
-
-}
+void CCreatureSet::armyChanged() {}
 
 void CCreatureSet::serializeJson(JsonSerializeFormat & handler, const std::string & armyFieldName, const std::optional<int> fixedSize)
 {
@@ -668,13 +647,12 @@ void CCreatureSet::serializeJson(JsonSerializeFormat & handler, const std::strin
 	handler.serializeEnum("formation", formation, NArmyFormation::names);
 	auto a = handler.enterArray(armyFieldName);
 
-
 	if(handler.saving)
 	{
 		size_t sz = 0;
 
 		for(const auto & p : stacks)
-			vstd::amax(sz, p.first.getNum()+1);
+			vstd::amax(sz, p.first.getNum() + 1);
 
 		if(fixedSize)
 			vstd::amax(sz, fixedSize.value());
@@ -707,483 +685,4 @@ void CCreatureSet::serializeJson(JsonSerializeFormat & handler, const std::strin
 	}
 }
 
-CStackInstance::CStackInstance(IGameInfoCallback *cb)
-	: CStackInstance(cb, BonusNodeType::STACK_INSTANCE, false)
-{}
-
-CStackInstance::CStackInstance(IGameInfoCallback *cb, BonusNodeType nodeType, bool isHypothetic)
-	: CBonusSystemNode(nodeType, isHypothetic)
-	, CStackBasicDescriptor(nullptr, 0)
-	, CArtifactSet(cb)
-	, GameCallbackHolder(cb)
-	, nativeTerrain(this, Selector::type()(BonusType::TERRAIN_NATIVE))
-	, initiative(this, Selector::type()(BonusType::STACKS_SPEED))
-	, totalExperience(0)
-{}
-
-CStackInstance::CStackInstance(IGameInfoCallback *cb, const CreatureID & id, TQuantity Count, bool isHypothetic)
-	: CStackInstance(cb, BonusNodeType::STACK_INSTANCE, false)
-{
-	setType(id);
-	setCount(Count);
-}
-
-CCreature::CreatureQuantityId CStackInstance::getQuantityID() const
-{
-	return CCreature::getQuantityID(getCount());
-}
-
-int CStackInstance::getExpRank() const
-{
-	if (!LIBRARY->engineSettings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
-		return 0;
-	int tier = getType()->getLevel();
-	if (vstd::iswithin(tier, 1, 7))
-	{
-		for(int i = static_cast<int>(LIBRARY->creh->expRanks[tier].size()) - 2; i > -1; --i) //sic!
-		{ //exp values vary from 1st level to max exp at 11th level
-			if (getAverageExperience() >= LIBRARY->creh->expRanks[tier][i])
-				return ++i; //faster, but confusing - 0 index mean 1st level of experience
-		}
-		return 0;
-	}
-	else //higher tier
-	{
-		for(int i = static_cast<int>(LIBRARY->creh->expRanks[0].size()) - 2; i > -1; --i)
-		{
-			if (getAverageExperience() >= LIBRARY->creh->expRanks[0][i])
-				return ++i;
-		}
-		return 0;
-	}
-}
-
-int CStackInstance::getLevel() const
-{
-	return std::max(1, getType()->getLevel());
-}
-
-void CStackInstance::giveAverageStackExperience(TExpType desiredAmountPerUnit)
-{
-	if (!canGainExperience())
-		return;
-
-	int level = std::clamp(getLevel(), 1, 7);
-	TExpType maxAmountPerUnit = LIBRARY->creh->expRanks[level].back();
-	TExpType actualAmountPerUnit = std::min(desiredAmountPerUnit, maxAmountPerUnit * LIBRARY->creh->maxExpPerBattle[level]/100);
-	TExpType maxExperience = maxAmountPerUnit * getCount();
-	TExpType maxExperienceToGain = maxExperience - totalExperience;
-	TExpType actualGainedExperience = std::min(maxExperienceToGain, actualAmountPerUnit * getCount());
-
-	totalExperience	+= actualGainedExperience;
-}
-
-void CStackInstance::giveTotalStackExperience(TExpType experienceToGive)
-{
-	if (!canGainExperience())
-		return;
-
-	totalExperience	+= experienceToGive;
-}
-
-TExpType CStackInstance::getTotalExperience() const
-{
-	return totalExperience;
-}
-
-TExpType CStackInstance::getAverageExperience() const
-{
-	return totalExperience / getCount();
-}
-
-bool CStackInstance::canGainExperience() const
-{
-	return cb->getSettings().getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE);
-}
-
-void CStackInstance::setType(const CreatureID & creID)
-{
-	if (creID == CreatureID::NONE)
-		setType(nullptr);//FIXME: unused branch?
-	else
-		setType(creID.toCreature());
-}
-
-void CStackInstance::setType(const CCreature *c)
-{
-	if(getCreature())
-	{
-		detachFromSource(*getCreature());
-		if (LIBRARY->engineSettings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
-			totalExperience = totalExperience * LIBRARY->creh->expAfterUpgrade / 100;
-	}
-
-	CStackBasicDescriptor::setType(c);
-
-	if(getCreature())
-		attachToSource(*getCreature());
-}
-
-void CStackInstance::setCount(TQuantity newCount)
-{
-	assert(newCount >= 0);
-
-	if (newCount < getCount())
-	{
-		TExpType averageExperience = totalExperience / getCount();
-		totalExperience = averageExperience * newCount;
-	}
-
-	CStackBasicDescriptor::setCount(newCount);
-	nodeHasChanged();
-}
-
-std::string CStackInstance::bonusToString(const std::shared_ptr<Bonus>& bonus) const
-{
-	if (!bonus->description.empty())
-		return bonus->description.toString();
-	else
-		return LIBRARY->getBth()->bonusToString(bonus, this);
-}
-
-ImagePath CStackInstance::bonusToGraphics(const std::shared_ptr<Bonus> & bonus) const
-{
-	if (!bonus->customIconPath.empty())
-		return bonus->customIconPath;
-	return LIBRARY->getBth()->bonusToGraphics(bonus);
-}
-
-CArmedInstance * CStackInstance::getArmy()
-{
-	return armyInstance;
-}
-
-const CArmedInstance * CStackInstance::getArmy() const
-{
-	return armyInstance;
-}
-
-void CStackInstance::setArmy(CArmedInstance * ArmyObj)
-{
-	auto oldArmy = getArmy();
-
-	if(oldArmy)
-	{
-		detachFrom(*oldArmy);
-		armyInstance = nullptr;
-	}
-
-	if(ArmyObj)
-	{
-		attachTo(const_cast<CArmedInstance&>(*ArmyObj));
-		armyInstance = ArmyObj;
-	}
-}
-
-std::string CStackInstance::getQuantityTXT(bool capitalized) const
-{
-	CCreature::CreatureQuantityId quantity = getQuantityID();
-
-	if ((int)quantity)
-	{
-		if(settings["gameTweaks"]["numericCreaturesQuantities"].Bool())
-			return CCreature::getQuantityRangeStringForId(quantity);
-
-		return LIBRARY->generaltexth->arraytxt[174 + (int)quantity*3 - 1 - capitalized];
-	}
-	else
-		return "";
-}
-
-bool CStackInstance::valid(bool allowUnrandomized) const
-{
-	if(!randomStack)
-	{
-		return (getType() && getType() == getId().toEntity(LIBRARY));
-	}
-	else
-		return allowUnrandomized;
-}
-
-std::string CStackInstance::nodeName() const
-{
-	std::ostringstream oss;
-	oss << "Stack of " << getCount() << " of ";
-	if(getType())
-		oss << getType()->getNamePluralTextID();
-	else
-		oss << "[UNDEFINED TYPE]";
-
-	return oss.str();
-}
-
-PlayerColor CStackInstance::getOwner() const
-{
-	auto army = getArmy();
-	return army ? army->getOwner() : PlayerColor::NEUTRAL;
-}
-
-int32_t CStackInstance::getInitiative(int turn) const
-{
-	if (turn == 0)
-		return initiative.getValue();
-
-	return ACreature::getInitiative(turn);
-}
-
-TerrainId CStackInstance::getNativeTerrain() const
-{
-	if (nativeTerrain.hasBonus())
-		return TerrainId::ANY_TERRAIN;
-
-	return getFactionID().toEntity(LIBRARY)->getNativeTerrain();
-}
-
-TerrainId CStackInstance::getCurrentTerrain() const
-{
-	assert(getArmy() != nullptr);
-	return getArmy()->getCurrentTerrain();
-}
-
-CreatureID CStackInstance::getCreatureID() const
-{
-	if(getType())
-		return getType()->getId();
-	else
-		return CreatureID::NONE;
-}
-
-std::string CStackInstance::getName() const
-{
-	return (getCount() > 1) ? getType()->getNamePluralTranslated() : getType()->getNameSingularTranslated();
-}
-
-ui64 CStackInstance::getPower() const
-{
-	assert(getType());
-	return static_cast<ui64>(getType()->getAIValue()) * getCount();
-}
-
-ui64 CStackInstance::getMarketValue() const
-{
-	assert(getType());
-	return getType()->getFullRecruitCost().marketValue() * getCount();
-}
-
-ArtBearer CStackInstance::bearerType() const
-{
-	return ArtBearer::CREATURE;
-}
-
-CStackInstance::ArtPlacementMap CStackInstance::putArtifact(const ArtifactPosition & pos, const CArtifactInstance * art)
-{
-	assert(!getArt(pos));
-	assert(art->canBePutAt(this, pos));
-
-	attachToSource(*art);
-	return CArtifactSet::putArtifact(pos, art);
-}
-
-void CStackInstance::removeArtifact(const ArtifactPosition & pos)
-{
-	assert(getArt(pos));
-
-	detachFromSource(*getArt(pos));
-	CArtifactSet::removeArtifact(pos);
-}
-
-void CStackInstance::serializeJson(JsonSerializeFormat & handler)
-{
-	//todo: artifacts
-	CStackBasicDescriptor::serializeJson(handler);//must be first
-
-	if(handler.saving)
-	{
-		if(randomStack)
-		{
-			int level = randomStack->level;
-			int upgrade = randomStack->upgrade;
-
-			handler.serializeInt("level", level, 0);
-			handler.serializeInt("upgraded", upgrade, 0);
-		}
-	}
-	else
-	{
-		//type set by CStackBasicDescriptor::serializeJson
-		if(getType() == nullptr)
-		{
-			uint8_t level = 0;
-			uint8_t upgrade = 0;
-
-			handler.serializeInt("level", level, 0);
-			handler.serializeInt("upgrade", upgrade, 0);
-
-			randomStack = RandomStackInfo{ level, upgrade };
-		}
-	}
-}
-
-FactionID CStackInstance::getFactionID() const
-{
-	if(getType())
-		return getType()->getFactionID();
-		
-	return FactionID::NEUTRAL;
-}
-
-const IBonusBearer* CStackInstance::getBonusBearer() const
-{
-	return this;
-}
-
-CCommanderInstance::CCommanderInstance(IGameInfoCallback *cb)
-	:CStackInstance(cb)
-{}
-
-CCommanderInstance::CCommanderInstance(IGameInfoCallback *cb, const CreatureID & id)
-	: CStackInstance(cb, BonusNodeType::COMMANDER, false)
-	, name("Commando")
-{
-	alive = true;
-	level = 1;
-	setCount(1);
-	setType(nullptr);
-	secondarySkills.resize (ECommander::SPELL_POWER + 1);
-	setType(id);
-	//TODO - parse them
-}
-
-void CCommanderInstance::setAlive (bool Alive)
-{
-	//TODO: helm of immortality
-	alive = Alive;
-	if (!alive)
-	{
-		removeBonusesRecursive(Bonus::UntilCommanderKilled);
-	}
-}
-
-bool CCommanderInstance::canGainExperience() const
-{
-	return alive;
-}
-
-int CCommanderInstance::getExpRank() const
-{
-	return LIBRARY->heroh->level (getTotalExperience());
-}
-
-int CCommanderInstance::getLevel() const
-{
-	return std::max (1, getExpRank());
-}
-
-void CCommanderInstance::levelUp ()
-{
-	level++;
-	for(const auto & bonus : LIBRARY->creh->commanderLevelPremy)
-	{ //grant all regular level-up bonuses
-		accumulateBonus(bonus);
-	}
-}
-
-ArtBearer CCommanderInstance::bearerType() const
-{
-	return ArtBearer::COMMANDER;
-}
-
-bool CCommanderInstance::gainsLevel() const
-{
-	return getTotalExperience() >= LIBRARY->heroh->reqExp(level + 1);
-}
-
-//This constructor should be placed here to avoid side effects
-CStackBasicDescriptor::CStackBasicDescriptor() = default;
-
-CStackBasicDescriptor::CStackBasicDescriptor(const CreatureID & id, TQuantity Count):
-	typeID(id),
-	count(Count)
-{
-}
-
-CStackBasicDescriptor::CStackBasicDescriptor(const CCreature *c, TQuantity Count)
-	: typeID(c ? c->getId() : CreatureID()), count(Count)
-{
-}
-
-const CCreature * CStackBasicDescriptor::getCreature() const
-{
-	return typeID.hasValue() ? typeID.toCreature() : nullptr;
-}
-
-const Creature * CStackBasicDescriptor::getType() const
-{
-	return typeID.hasValue() ? typeID.toEntity(LIBRARY) : nullptr;
-}
-
-CreatureID CStackBasicDescriptor::getId() const
-{
-	return typeID;
-}
-
-TQuantity CStackBasicDescriptor::getCount() const
-{
-	return count;
-}
-
-void CStackBasicDescriptor::setType(const CCreature * c)
-{
-	typeID = c ? c->getId() : CreatureID();
-}
-
-void CStackBasicDescriptor::setCount(TQuantity newCount)
-{
-	assert(newCount >= 0);
-	count = newCount;
-}
-
-bool operator== (const CStackBasicDescriptor & l, const CStackBasicDescriptor & r)
-{
-	return l.typeID == r.typeID && l.count == r.count;
-}
-
-void CStackBasicDescriptor::serializeJson(JsonSerializeFormat & handler)
-{
-	handler.serializeInt("amount", count);
-
-	if(handler.saving)
-	{
-		if(typeID.hasValue())
-		{
-			std::string typeName = typeID.toEntity(LIBRARY)->getJsonKey();
-			handler.serializeString("type", typeName);
-		}
-	}
-	else
-	{
-		std::string typeName;
-		handler.serializeString("type", typeName);
-		if(!typeName.empty())
-			setType(CreatureID(CreatureID::decode(typeName)).toCreature());
-	}
-}
-
-void CSimpleArmy::clearSlots()
-{
-	army.clear();
-}
-
-CSimpleArmy::operator bool() const
-{
-	return !army.empty();
-}
-
-bool CSimpleArmy::setCreature(SlotID slot, CreatureID cre, TQuantity count)
-{
-	assert(!vstd::contains(army, slot));
-	army[slot] = std::make_pair(cre, count);
-	return true;
-}
-
 VCMI_LIB_NAMESPACE_END

+ 160 - 0
lib/mapObjects/army/CCreatureSet.h

@@ -0,0 +1,160 @@
+/*
+ * CCreatureSet.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "CSimpleArmy.h"
+#include "CStackInstance.h"
+
+#include "serializer/Serializeable.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CStackInstance;
+class CArmedInstance;
+class CStackBasicDescriptor;
+class JsonSerializeFormat;
+
+using TSlots = std::map<SlotID, std::unique_ptr<CStackInstance>>;
+
+using TPairCreatureSlot = std::pair<const CCreature *, SlotID>;
+using TMapCreatureSlot = std::map<const CCreature *, SlotID>;
+
+struct DLL_LINKAGE CreatureSlotComparer
+{
+	bool operator()(const TPairCreatureSlot & lhs, const TPairCreatureSlot & rhs);
+};
+
+using TCreatureQueue = std::priority_queue<TPairCreatureSlot, std::vector<TPairCreatureSlot>, CreatureSlotComparer>;
+
+namespace NArmyFormation
+{
+static const std::vector<std::string> names{"wide", "tight"};
+}
+
+class DLL_LINKAGE CCreatureSet : public IArmyDescriptor, public virtual Serializeable, boost::noncopyable //seven combined creatures
+{
+	CCreatureSet(const CCreatureSet &) = delete;
+	CCreatureSet & operator=(const CCreatureSet &) = delete;
+
+public:
+	TSlots stacks; //slots[slot_id]->> pair(creature_id,creature_quantity)
+	EArmyFormation formation = EArmyFormation::LOOSE; //0 - wide, 1 - tight
+
+	CCreatureSet() = default; //Should be here to avoid compile errors
+	virtual ~CCreatureSet();
+	virtual void armyChanged();
+
+	const CStackInstance & operator[](const SlotID & slot) const;
+
+	const TSlots &Slots() const {return stacks;}
+
+	//Adds stack to slot. Slot must be empty or with same type creature
+	void addToSlot(const SlotID & slot, const CreatureID & cre, TQuantity count, bool allowMerging = true);
+	//Adds stack to slot. Slot must be empty or with same type creature
+	void addToSlot(const SlotID & slot, std::unique_ptr<CStackInstance> stack, bool allowMerging = true);
+
+	void clearSlots() override;
+	void setFormation(EArmyFormation tight);
+	virtual CArmedInstance * getArmy() { return nullptr; }
+	virtual const CArmedInstance * getArmy() const { return nullptr; }
+
+	//adds new stack to the army, slot must be empty
+	void putStack(const SlotID & slot, std::unique_ptr<CStackInstance> stack);
+	//stack must exist!
+	void setStackCount(const SlotID & slot, TQuantity count);
+	//removes stack from army but doesn't destroy it (so it can be moved somewhere else or safely deleted)
+	std::unique_ptr<CStackInstance> detachStack(const SlotID & slot);
+
+	void setStackType(const SlotID & slot, const CreatureID & type);
+
+	/// Give specified amount of experience to all units in army
+	/// Amount of granted experience is scaled by unit stack size
+	void giveAverageStackExperience(TExpType exp);
+
+	/// Give specified amount of experience to unit in specified slot
+	/// Amount of granted experience is not scaled by unit stack size
+	void giveTotalStackExperience(const SlotID & slot, TExpType exp);
+
+	/// Erased stack from specified slot. Slot must be non-empty
+	void eraseStack(const SlotID & slot);
+
+	/// Joins stack into stack that occupies targeted slot.
+	/// Slot must be non-empty and contain same creature type
+	void joinStack(const SlotID & slot, std::unique_ptr<CStackInstance> stack); //adds new stack to the existing stack of the same type
+
+	/// Splits off some units of specified stack and returns newly created stack
+	/// Slot must be non-empty and contain more units that split quantity
+	std::unique_ptr<CStackInstance> splitStack(const SlotID & slot, TQuantity toSplit);
+
+	//stack must exist!
+	void changeStackCount(const SlotID & slot, TQuantity toAdd);
+
+	//replaces creature in stack; slots 0 to 6, if quantity=0 erases stack
+	bool setCreature(SlotID slot, CreatureID type, TQuantity quantity) override;
+
+	//erases all our army and moves stacks from src to us; src MUST NOT be an armed object! WARNING: use it wisely. Or better do not use at all.
+	void setToArmy(CSimpleArmy & src);
+
+	const CStackInstance & getStack(const SlotID & slot) const; //stack must exist
+	CStackInstance * getStackPtr(const SlotID & slot) const; //if stack doesn't exist, returns nullptr
+	const CCreature * getCreature(const SlotID & slot) const; //workaround of map issue;
+	int getStackCount(const SlotID & slot) const;
+	TExpType getStackTotalExperience(const SlotID & slot) const;
+	TExpType getStackAverageExperience(const SlotID & slot) const;
+	SlotID findStack(const CStackInstance * stack) const; //-1 if none
+	SlotID getSlotFor(const CreatureID & creature, ui32 slotsAmount = GameConstants::ARMY_SIZE) const; //returns -1 if no slot available
+	SlotID getSlotFor(const CCreature * c, ui32 slotsAmount = GameConstants::ARMY_SIZE) const; //returns -1 if no slot available
+	bool hasCreatureSlots(const CCreature * c, const SlotID & exclude) const;
+	std::vector<SlotID> getCreatureSlots(const CCreature * c, const SlotID & exclude, TQuantity ignoreAmount = -1) const;
+	bool isCreatureBalanced(const CCreature * c, TQuantity ignoreAmount = 1) const; // Check if the creature is evenly distributed across slots
+
+	SlotID getFreeSlot(ui32 slotsAmount = GameConstants::ARMY_SIZE) const; //returns first free slot
+	std::vector<SlotID> getFreeSlots(ui32 slotsAmount = GameConstants::ARMY_SIZE) const;
+	std::queue<SlotID> getFreeSlotsQueue(ui32 slotsAmount = GameConstants::ARMY_SIZE) const;
+
+	TMapCreatureSlot getCreatureMap() const;
+	TCreatureQueue getCreatureQueue(const SlotID & exclude) const;
+
+	bool mergeableStacks(std::pair<SlotID, SlotID> & out, const SlotID & preferable = SlotID()) const; //looks for two same stacks, returns slot positions;
+	bool validTypes(bool allowUnrandomized = false) const; //checks if all types of creatures are set properly
+	bool slotEmpty(const SlotID & slot) const;
+	int stacksCount() const;
+	virtual bool needsLastStack() const; //true if last stack cannot be taken
+	ui64 getArmyStrength(int fortLevel = 0) const; //sum of AI values of creatures
+	ui64 getArmyCost() const; //sum of cost of creatures
+	ui64 getPower(const SlotID & slot) const; //value of specific stack
+	std::string getRoughAmount(const SlotID & slot, int mode = 0) const; //rough size of specific stack
+	std::string getArmyDescription() const;
+	bool hasStackAtSlot(const SlotID & slot) const;
+
+	bool contains(const CStackInstance * stack) const;
+	bool canBeMergedWith(const CCreatureSet & cs, bool allowMergingStacks = true) const;
+
+	/// Returns true if this creature set contains all listed units
+	/// If requireLastStack is true, then this function will also
+	/// require presence of any unit other than requested (or more units than requested)
+	bool hasUnits(const std::vector<CStackBasicDescriptor> & units, bool requireLastStack = true) const;
+
+	template<typename Handler>
+	void serialize(Handler & h)
+	{
+		h & stacks;
+		h & formation;
+	}
+
+	void serializeJson(JsonSerializeFormat & handler, const std::string & armyFieldName, const std::optional<int> fixedSize = std::nullopt);
+
+	operator bool() const
+	{
+		return !stacks.empty();
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 54 - 0
lib/mapObjects/army/CSimpleArmy.h

@@ -0,0 +1,54 @@
+/*
+ * CSimpleArmy.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "GameConstants.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class IArmyDescriptor
+{
+public:
+	virtual void clearSlots() = 0;
+	virtual bool setCreature(SlotID slot, CreatureID cre, TQuantity count) = 0;
+};
+
+using TSimpleSlots = std::map<SlotID, std::pair<CreatureID, TQuantity>>;
+
+//simplified version of CCreatureSet
+class DLL_LINKAGE CSimpleArmy : public IArmyDescriptor
+{
+public:
+	TSimpleSlots army;
+	void clearSlots() override
+	{
+		army.clear();
+	}
+
+	bool setCreature(SlotID slot, CreatureID cre, TQuantity count) override
+	{
+		assert(!vstd::contains(army, slot));
+		army[slot] = std::make_pair(cre, count);
+		return true;
+	}
+
+	operator bool() const
+	{
+		return !army.empty();
+	}
+
+	template<typename Handler>
+	void serialize(Handler & h)
+	{
+		h & army;
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 91 - 0
lib/mapObjects/army/CStackBasicDescriptor.cpp

@@ -0,0 +1,91 @@
+/*
+ * CStackBasicDescriptor.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CStackBasicDescriptor.h"
+
+#include "../../CCreatureHandler.h"
+#include "../../GameLibrary.h"
+#include "../../serializer/JsonSerializeFormat.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+//This constructor should be placed here to avoid side effects
+CStackBasicDescriptor::CStackBasicDescriptor() = default;
+
+CStackBasicDescriptor::CStackBasicDescriptor(const CreatureID & id, TQuantity Count)
+	: typeID(id)
+	, count(Count)
+{
+}
+
+CStackBasicDescriptor::CStackBasicDescriptor(const CCreature * c, TQuantity Count)
+	: typeID(c ? c->getId() : CreatureID())
+	, count(Count)
+{
+}
+
+const CCreature * CStackBasicDescriptor::getCreature() const
+{
+	return typeID.hasValue() ? typeID.toCreature() : nullptr;
+}
+
+const Creature * CStackBasicDescriptor::getType() const
+{
+	return typeID.hasValue() ? typeID.toEntity(LIBRARY) : nullptr;
+}
+
+CreatureID CStackBasicDescriptor::getId() const
+{
+	return typeID;
+}
+
+TQuantity CStackBasicDescriptor::getCount() const
+{
+	return count;
+}
+
+void CStackBasicDescriptor::setType(const CCreature * c)
+{
+	typeID = c ? c->getId() : CreatureID();
+}
+
+void CStackBasicDescriptor::setCount(TQuantity newCount)
+{
+	assert(newCount >= 0);
+	count = newCount;
+}
+
+bool operator==(const CStackBasicDescriptor & l, const CStackBasicDescriptor & r)
+{
+	return l.typeID == r.typeID && l.count == r.count;
+}
+
+void CStackBasicDescriptor::serializeJson(JsonSerializeFormat & handler)
+{
+	handler.serializeInt("amount", count);
+
+	if(handler.saving)
+	{
+		if(typeID.hasValue())
+		{
+			std::string typeName = typeID.toEntity(LIBRARY)->getJsonKey();
+			handler.serializeString("type", typeName);
+		}
+	}
+	else
+	{
+		std::string typeName;
+		handler.serializeString("type", typeName);
+		if(!typeName.empty())
+			setType(CreatureID(CreatureID::decode(typeName)).toCreature());
+	}
+}
+
+VCMI_LIB_NAMESPACE_END

+ 65 - 0
lib/mapObjects/army/CStackBasicDescriptor.h

@@ -0,0 +1,65 @@
+/*
+ * CStackBasicDescriptor.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "GameConstants.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class JsonNode;
+class CCreature;
+class CGHeroInstance;
+class CArmedInstance;
+class CCreatureArtifactSet;
+class JsonSerializeFormat;
+
+class DLL_LINKAGE CStackBasicDescriptor
+{
+	CreatureID typeID;
+	TQuantity count = -1; //exact quantity or quantity ID from CCreature::getQuantityID when getting info about enemy army
+
+public:
+	CStackBasicDescriptor();
+	CStackBasicDescriptor(const CreatureID & id, TQuantity Count);
+	CStackBasicDescriptor(const CCreature * c, TQuantity Count);
+	virtual ~CStackBasicDescriptor() = default;
+
+	const Creature * getType() const;
+	const CCreature * getCreature() const;
+	CreatureID getId() const;
+	TQuantity getCount() const;
+
+	virtual void setType(const CCreature * c);
+	virtual void setCount(TQuantity amount);
+
+	friend bool operator==(const CStackBasicDescriptor & l, const CStackBasicDescriptor & r);
+
+	template<typename Handler>
+	void serialize(Handler & h)
+	{
+		if(h.saving)
+		{
+			h & typeID;
+		}
+		else
+		{
+			CreatureID creatureID;
+			h & creatureID;
+			if(creatureID != CreatureID::NONE)
+				setType(creatureID.toCreature());
+		}
+
+		h & count;
+	}
+
+	void serializeJson(JsonSerializeFormat & handler);
+};
+
+VCMI_LIB_NAMESPACE_END

+ 358 - 0
lib/mapObjects/army/CStackInstance.cpp

@@ -0,0 +1,358 @@
+/*
+ * CStackInstance.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CStackInstance.h"
+
+#include "CArmedInstance.h"
+
+#include "../../CConfigHandler.h"
+#include "../../GameLibrary.h"
+#include "../../IGameSettings.h"
+#include "../../callback/IGameInfoCallback.h"
+#include "../../entities/faction/CFaction.h"
+#include "../../texts/CGeneralTextHandler.h"
+#include "../../IBonusTypeHandler.h"
+#include "../../serializer/JsonSerializeFormat.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+CStackInstance::CStackInstance(IGameInfoCallback * cb)
+	: CStackInstance(cb, BonusNodeType::STACK_INSTANCE, false)
+{
+}
+
+CStackInstance::CStackInstance(IGameInfoCallback * cb, BonusNodeType nodeType, bool isHypothetic)
+	: CBonusSystemNode(nodeType, isHypothetic)
+	, CStackBasicDescriptor(nullptr, 0)
+	, CArtifactSet(cb)
+	, GameCallbackHolder(cb)
+	, nativeTerrain(this, Selector::type()(BonusType::TERRAIN_NATIVE))
+	, initiative(this, Selector::type()(BonusType::STACKS_SPEED))
+	, totalExperience(0)
+{
+}
+
+CStackInstance::CStackInstance(IGameInfoCallback * cb, const CreatureID & id, TQuantity Count, bool isHypothetic)
+	: CStackInstance(cb, BonusNodeType::STACK_INSTANCE, false)
+{
+	setType(id);
+	setCount(Count);
+}
+
+CCreature::CreatureQuantityId CStackInstance::getQuantityID() const
+{
+	return CCreature::getQuantityID(getCount());
+}
+
+int CStackInstance::getExpRank() const
+{
+	if(!LIBRARY->engineSettings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
+		return 0;
+	int tier = getType()->getLevel();
+	if(vstd::iswithin(tier, 1, 7))
+	{
+		for(int i = static_cast<int>(LIBRARY->creh->expRanks[tier].size()) - 2; i > -1; --i) //sic!
+		{ //exp values vary from 1st level to max exp at 11th level
+			if(getAverageExperience() >= LIBRARY->creh->expRanks[tier][i])
+				return ++i; //faster, but confusing - 0 index mean 1st level of experience
+		}
+		return 0;
+	}
+	else //higher tier
+	{
+		for(int i = static_cast<int>(LIBRARY->creh->expRanks[0].size()) - 2; i > -1; --i)
+		{
+			if(getAverageExperience() >= LIBRARY->creh->expRanks[0][i])
+				return ++i;
+		}
+		return 0;
+	}
+}
+
+int CStackInstance::getLevel() const
+{
+	return std::max(1, getType()->getLevel());
+}
+
+void CStackInstance::giveAverageStackExperience(TExpType desiredAmountPerUnit)
+{
+	if(!canGainExperience())
+		return;
+
+	int level = std::clamp(getLevel(), 1, 7);
+	TExpType maxAmountPerUnit = LIBRARY->creh->expRanks[level].back();
+	TExpType actualAmountPerUnit = std::min(desiredAmountPerUnit, maxAmountPerUnit * LIBRARY->creh->maxExpPerBattle[level] / 100);
+	TExpType maxExperience = maxAmountPerUnit * getCount();
+	TExpType maxExperienceToGain = maxExperience - totalExperience;
+	TExpType actualGainedExperience = std::min(maxExperienceToGain, actualAmountPerUnit * getCount());
+
+	totalExperience += actualGainedExperience;
+}
+
+void CStackInstance::giveTotalStackExperience(TExpType experienceToGive)
+{
+	if(!canGainExperience())
+		return;
+
+	totalExperience += experienceToGive;
+}
+
+TExpType CStackInstance::getTotalExperience() const
+{
+	return totalExperience;
+}
+
+TExpType CStackInstance::getAverageExperience() const
+{
+	return totalExperience / getCount();
+}
+
+bool CStackInstance::canGainExperience() const
+{
+	return cb->getSettings().getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE);
+}
+
+void CStackInstance::setType(const CreatureID & creID)
+{
+	if(creID == CreatureID::NONE)
+		setType(nullptr); //FIXME: unused branch?
+	else
+		setType(creID.toCreature());
+}
+
+void CStackInstance::setType(const CCreature * c)
+{
+	if(getCreature())
+	{
+		detachFromSource(*getCreature());
+		if(LIBRARY->engineSettings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
+			totalExperience = totalExperience * LIBRARY->creh->expAfterUpgrade / 100;
+	}
+
+	CStackBasicDescriptor::setType(c);
+
+	if(getCreature())
+		attachToSource(*getCreature());
+}
+
+void CStackInstance::setCount(TQuantity newCount)
+{
+	assert(newCount >= 0);
+
+	if(newCount < getCount())
+	{
+		TExpType averageExperience = totalExperience / getCount();
+		totalExperience = averageExperience * newCount;
+	}
+
+	CStackBasicDescriptor::setCount(newCount);
+	nodeHasChanged();
+}
+
+std::string CStackInstance::bonusToString(const std::shared_ptr<Bonus> & bonus) const
+{
+	if(!bonus->description.empty())
+		return bonus->description.toString();
+	else
+		return LIBRARY->getBth()->bonusToString(bonus, this);
+}
+
+ImagePath CStackInstance::bonusToGraphics(const std::shared_ptr<Bonus> & bonus) const
+{
+	if(!bonus->customIconPath.empty())
+		return bonus->customIconPath;
+	return LIBRARY->getBth()->bonusToGraphics(bonus);
+}
+
+CArmedInstance * CStackInstance::getArmy()
+{
+	return armyInstance;
+}
+
+const CArmedInstance * CStackInstance::getArmy() const
+{
+	return armyInstance;
+}
+
+void CStackInstance::setArmy(CArmedInstance * ArmyObj)
+{
+	auto oldArmy = getArmy();
+
+	if(oldArmy)
+	{
+		detachFrom(*oldArmy);
+		armyInstance = nullptr;
+	}
+
+	if(ArmyObj)
+	{
+		attachTo(const_cast<CArmedInstance &>(*ArmyObj));
+		armyInstance = ArmyObj;
+	}
+}
+
+std::string CStackInstance::getQuantityTXT(bool capitalized) const
+{
+	CCreature::CreatureQuantityId quantity = getQuantityID();
+
+	if((int)quantity)
+	{
+		if(settings["gameTweaks"]["numericCreaturesQuantities"].Bool())
+			return CCreature::getQuantityRangeStringForId(quantity);
+
+		return LIBRARY->generaltexth->arraytxt[174 + (int)quantity * 3 - 1 - capitalized];
+	}
+	else
+		return "";
+}
+
+bool CStackInstance::valid(bool allowUnrandomized) const
+{
+	if(!randomStack)
+	{
+		return (getType() && getType() == getId().toEntity(LIBRARY));
+	}
+	else
+		return allowUnrandomized;
+}
+
+std::string CStackInstance::nodeName() const
+{
+	std::ostringstream oss;
+	oss << "Stack of " << getCount() << " of ";
+	if(getType())
+		oss << getType()->getNamePluralTextID();
+	else
+		oss << "[UNDEFINED TYPE]";
+
+	return oss.str();
+}
+
+PlayerColor CStackInstance::getOwner() const
+{
+	auto army = getArmy();
+	return army ? army->getOwner() : PlayerColor::NEUTRAL;
+}
+
+int32_t CStackInstance::getInitiative(int turn) const
+{
+	if(turn == 0)
+		return initiative.getValue();
+
+	return ACreature::getInitiative(turn);
+}
+
+TerrainId CStackInstance::getNativeTerrain() const
+{
+	if(nativeTerrain.hasBonus())
+		return TerrainId::ANY_TERRAIN;
+
+	return getFactionID().toEntity(LIBRARY)->getNativeTerrain();
+}
+
+TerrainId CStackInstance::getCurrentTerrain() const
+{
+	assert(getArmy() != nullptr);
+	return getArmy()->getCurrentTerrain();
+}
+
+CreatureID CStackInstance::getCreatureID() const
+{
+	if(getType())
+		return getType()->getId();
+	else
+		return CreatureID::NONE;
+}
+
+std::string CStackInstance::getName() const
+{
+	return (getCount() > 1) ? getType()->getNamePluralTranslated() : getType()->getNameSingularTranslated();
+}
+
+ui64 CStackInstance::getPower() const
+{
+	assert(getType());
+	return static_cast<ui64>(getType()->getAIValue()) * getCount();
+}
+
+ui64 CStackInstance::getMarketValue() const
+{
+	assert(getType());
+	return getType()->getFullRecruitCost().marketValue() * getCount();
+}
+
+ArtBearer CStackInstance::bearerType() const
+{
+	return ArtBearer::CREATURE;
+}
+
+CStackInstance::ArtPlacementMap CStackInstance::putArtifact(const ArtifactPosition & pos, const CArtifactInstance * art)
+{
+	assert(!getArt(pos));
+	assert(art->canBePutAt(this, pos));
+
+	attachToSource(*art);
+	return CArtifactSet::putArtifact(pos, art);
+}
+
+void CStackInstance::removeArtifact(const ArtifactPosition & pos)
+{
+	assert(getArt(pos));
+
+	detachFromSource(*getArt(pos));
+	CArtifactSet::removeArtifact(pos);
+}
+
+void CStackInstance::serializeJson(JsonSerializeFormat & handler)
+{
+	//todo: artifacts
+	CStackBasicDescriptor::serializeJson(handler); //must be first
+
+	if(handler.saving)
+	{
+		if(randomStack)
+		{
+			int level = randomStack->level;
+			int upgrade = randomStack->upgrade;
+
+			handler.serializeInt("level", level, 0);
+			handler.serializeInt("upgraded", upgrade, 0);
+		}
+	}
+	else
+	{
+		//type set by CStackBasicDescriptor::serializeJson
+		if(getType() == nullptr)
+		{
+			uint8_t level = 0;
+			uint8_t upgrade = 0;
+
+			handler.serializeInt("level", level, 0);
+			handler.serializeInt("upgrade", upgrade, 0);
+
+			randomStack = RandomStackInfo{level, upgrade};
+		}
+	}
+}
+
+FactionID CStackInstance::getFactionID() const
+{
+	if(getType())
+		return getType()->getFactionID();
+
+	return FactionID::NEUTRAL;
+}
+
+const IBonusBearer * CStackInstance::getBonusBearer() const
+{
+	return this;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 134 - 0
lib/mapObjects/army/CStackInstance.h

@@ -0,0 +1,134 @@
+/*
+ * CStackInstance.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "CStackBasicDescriptor.h"
+
+#include "CCreatureHandler.h"
+#include "bonuses/BonusCache.h"
+#include "bonuses/CBonusSystemNode.h"
+#include "callback/GameCallbackHolder.h"
+#include "entities/artifact/CArtifactSet.h"
+#include "mapObjects/CGObjectInstance.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class JsonNode;
+class CCreature;
+class CGHeroInstance;
+class CArmedInstance;
+class CCreatureArtifactSet;
+class JsonSerializeFormat;
+
+class DLL_LINKAGE CStackInstance : public CBonusSystemNode, public CStackBasicDescriptor, public CArtifactSet, public ACreature, public GameCallbackHolder
+{
+	BonusValueCache nativeTerrain;
+	BonusValueCache initiative;
+
+	CArmedInstance * armyInstance = nullptr; //stack must be part of some army, army must be part of some object
+
+	IGameInfoCallback * getCallback() const final
+	{
+		return cb;
+	}
+
+	TExpType totalExperience; //commander needs same amount of exp as hero
+public:
+	struct RandomStackInfo
+	{
+		uint8_t level;
+		uint8_t upgrade;
+	};
+	// helper variable used during loading map, when object (hero or town) have creatures that must have same alignment.
+	std::optional<RandomStackInfo> randomStack;
+
+	CArmedInstance * getArmy();
+	const CArmedInstance * getArmy() const; //stack must be part of some army, army must be part of some object
+	void setArmy(CArmedInstance * ArmyObj);
+
+	TExpType getTotalExperience() const;
+	TExpType getAverageExperience() const;
+	virtual bool canGainExperience() const;
+
+	template<typename Handler>
+	void serialize(Handler & h)
+	{
+		h & static_cast<CBonusSystemNode &>(*this);
+		h & static_cast<CStackBasicDescriptor &>(*this);
+		h & static_cast<CArtifactSet &>(*this);
+
+		if(h.hasFeature(Handler::Version::STACK_INSTANCE_ARMY_FIX))
+		{
+			// no-op
+		}
+		if(h.hasFeature(Handler::Version::NO_RAW_POINTERS_IN_SERIALIZER))
+		{
+			ObjectInstanceID dummyID;
+			h & dummyID;
+		}
+		else
+		{
+			std::shared_ptr<CGObjectInstance> army;
+			h & army;
+		}
+
+		h & totalExperience;
+		if(!h.hasFeature(Handler::Version::STACK_INSTANCE_EXPERIENCE_FIX))
+		{
+			totalExperience *= getCount();
+		}
+	}
+
+	void serializeJson(JsonSerializeFormat & handler);
+
+	//overrides CBonusSystemNode
+	std::string bonusToString(const std::shared_ptr<Bonus> & bonus) const override; // how would bonus description look for this particular type of node
+	ImagePath bonusToGraphics(const std::shared_ptr<Bonus> & bonus) const; //file name of graphics from StackSkills , in future possibly others
+
+	//IConstBonusProvider
+	const IBonusBearer * getBonusBearer() const override;
+	//INativeTerrainProvider
+	FactionID getFactionID() const override;
+
+	virtual ui64 getPower() const;
+	/// Returns total market value of resources needed to recruit this unit
+	virtual ui64 getMarketValue() const;
+	CCreature::CreatureQuantityId getQuantityID() const;
+	std::string getQuantityTXT(bool capitalized = true) const;
+	virtual int getExpRank() const;
+	virtual int getLevel() const; //different for regular stack and commander
+	CreatureID getCreatureID() const; //-1 if not available
+	std::string getName() const; //plural or singular
+	CStackInstance(IGameInfoCallback * cb);
+	CStackInstance(IGameInfoCallback * cb, BonusNodeType nodeType, bool isHypothetic = false);
+	CStackInstance(IGameInfoCallback * cb, const CreatureID & id, TQuantity count, bool isHypothetic = false);
+	virtual ~CStackInstance() = default;
+
+	void setType(const CreatureID & creID);
+	void setType(const CCreature * c) final;
+	void setCount(TQuantity amount) final;
+
+	/// Gives specified amount of stack experience that will not be scaled by unit size
+	void giveAverageStackExperience(TExpType exp);
+	void giveTotalStackExperience(TExpType exp);
+
+	bool valid(bool allowUnrandomized) const;
+	ArtPlacementMap putArtifact(const ArtifactPosition & pos, const CArtifactInstance * art) override; //from CArtifactSet
+	void removeArtifact(const ArtifactPosition & pos) override;
+	ArtBearer bearerType() const override; //from CArtifactSet
+	std::string nodeName() const override; //from CBonusSystemnode
+	PlayerColor getOwner() const override;
+
+	int32_t getInitiative(int turn = 0) const final;
+	TerrainId getNativeTerrain() const final;
+	TerrainId getCurrentTerrain() const;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 37 - 0
lib/mapping/CCastleEvent.h

@@ -0,0 +1,37 @@
+/*
+ * CCastleEvent.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "CMapEvent.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+/// The castle event builds/adds buildings/creatures for a specific town.
+class DLL_LINKAGE CCastleEvent : public CMapEvent
+{
+public:
+	CCastleEvent() = default;
+
+	std::set<BuildingID> buildings;
+	std::vector<si32> creatures;
+
+	template<typename Handler>
+	void serialize(Handler & h)
+	{
+		h & static_cast<CMapEvent &>(*this);
+		h & buildings;
+		h & creatures;
+	}
+
+	void serializeJson(JsonSerializeFormat & handler) override;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 1 - 0
lib/mapping/CMap.cpp

@@ -12,6 +12,7 @@
 
 #include "CMapEditManager.h"
 #include "CMapOperation.h"
+#include "CCastleEvent.h"
 
 #include "../CCreatureHandler.h"
 #include "../CSkillHandler.h"

+ 2 - 1
lib/mapping/CMap.h

@@ -10,8 +10,9 @@
 
 #pragma once
 
-#include "CMapDefines.h"
+#include "CMapEvent.h"
 #include "CMapHeader.h"
+#include "TerrainTile.h"
 
 #include "../mapObjects/CGObjectInstance.h"
 #include "../callback/GameCallbackHolder.h"

+ 59 - 0
lib/mapping/CMapEvent.h

@@ -0,0 +1,59 @@
+/*
+ * CMapEvent.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "../ResourceSet.h"
+#include "../texts/MetaString.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class JsonSerializeFormat;
+
+/// The map event is an event which e.g. gives or takes resources of a specific
+/// amount to/from players and can appear regularly or once a time.
+class DLL_LINKAGE CMapEvent
+{
+public:
+	CMapEvent();
+	virtual ~CMapEvent() = default;
+
+	bool occursToday(int currentDay) const;
+	bool affectsPlayer(PlayerColor player, bool isHuman) const;
+
+	std::string name;
+	MetaString message;
+	TResources resources;
+	std::set<PlayerColor> players;
+	bool humanAffected;
+	bool computerAffected;
+	ui32 firstOccurrence;
+	ui32 nextOccurrence; /// specifies after how many days the event will occur the next time; 0 if event occurs only one time
+
+	std::vector<ObjectInstanceID> deletedObjectsInstances;
+
+	template<typename Handler>
+	void serialize(Handler & h)
+	{
+		h & name;
+		h & message;
+		h & resources;
+		h & players;
+		h & humanAffected;
+		h & computerAffected;
+		h & firstOccurrence;
+		h & nextOccurrence;
+		h & deletedObjectsInstances;
+	}
+
+	virtual void serializeJson(JsonSerializeFormat & handler);
+};
+
+VCMI_LIB_NAMESPACE_END

+ 1 - 0
lib/mapping/MapFormatH3M.cpp

@@ -11,6 +11,7 @@
 #include "StdInc.h"
 #include "MapFormatH3M.h"
 
+#include "CCastleEvent.h"
 #include "CMap.h"
 #include "MapReaderH3M.h"
 #include "MapFormatSettings.h"

+ 5 - 70
lib/mapping/CMapDefines.h → lib/mapping/TerrainTile.h

@@ -1,5 +1,5 @@
 /*
- * CMapDefines.h, part of VCMI engine
+ * TerrainTile.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -10,8 +10,6 @@
 
 #pragma once
 
-#include "../ResourceSet.h"
-#include "../texts/MetaString.h"
 #include "../GameLibrary.h"
 #include "../TerrainHandler.h"
 #include "../mapObjects/CGObjectInstance.h"
@@ -22,67 +20,6 @@ class TerrainType;
 class RiverType;
 class RoadType;
 class CGObjectInstance;
-class CGTownInstance;
-class JsonSerializeFormat;
-
-/// The map event is an event which e.g. gives or takes resources of a specific
-/// amount to/from players and can appear regularly or once a time.
-class DLL_LINKAGE CMapEvent
-{
-public:
-	CMapEvent();
-	virtual ~CMapEvent() = default;
-
-	bool occursToday(int currentDay) const;
-	bool affectsPlayer(PlayerColor player, bool isHuman) const;
-
-	std::string name;
-	MetaString message;
-	TResources resources;
-	std::set<PlayerColor> players;
-	bool humanAffected;
-	bool computerAffected;
-	ui32 firstOccurrence;
-	ui32 nextOccurrence; /// specifies after how many days the event will occur the next time; 0 if event occurs only one time
-
-	std::vector<ObjectInstanceID> deletedObjectsInstances;
-
-	template <typename Handler>
-	void serialize(Handler & h)
-	{
-		h & name;
-		h & message;
-		h & resources;
-		h & players;
-		h & humanAffected;
-		h & computerAffected;
-		h & firstOccurrence;
-		h & nextOccurrence;
-		h & deletedObjectsInstances;
-	}
-	
-	virtual void serializeJson(JsonSerializeFormat & handler);
-};
-
-/// The castle event builds/adds buildings/creatures for a specific town.
-class DLL_LINKAGE CCastleEvent: public CMapEvent
-{
-public:
-	CCastleEvent() = default;
-
-	std::set<BuildingID> buildings;
-	std::vector<si32> creatures;
-
-	template <typename Handler>
-	void serialize(Handler & h)
-	{
-		h & static_cast<CMapEvent &>(*this);
-		h & buildings;
-		h & creatures;
-	}
-	
-	void serializeJson(JsonSerializeFormat & handler) override;
-};
 
 /// The terrain tile describes the terrain type and the visual representation of the terrain.
 /// Furthermore the struct defines whether the tile is visitable or/and blocked and which objects reside in it.
@@ -130,7 +67,7 @@ struct DLL_LINKAGE TerrainTile
 	std::vector<ObjectInstanceID> visitableObjects;
 	std::vector<ObjectInstanceID> blockingObjects;
 
-	template <typename Handler>
+	template<typename Handler>
 	void serialize(Handler & h)
 	{
 		h & terrainType;
@@ -141,7 +78,7 @@ struct DLL_LINKAGE TerrainTile
 		h & roadDir;
 		h & extTileFlags;
 
-		if (h.hasFeature(Handler::Version::NO_RAW_POINTERS_IN_SERIALIZER))
+		if(h.hasFeature(Handler::Version::NO_RAW_POINTERS_IN_SERIALIZER))
 		{
 			h & visitableObjects;
 			h & blockingObjects;
@@ -150,14 +87,12 @@ struct DLL_LINKAGE TerrainTile
 		{
 			std::vector<std::shared_ptr<CGObjectInstance>> objectPtrs;
 			h & objectPtrs;
-			for (const auto & ptr : objectPtrs)
+			for(const auto & ptr : objectPtrs)
 				visitableObjects.push_back(ptr->id);
 			h & objectPtrs;
-			for (const auto & ptr : objectPtrs)
+			for(const auto & ptr : objectPtrs)
 				blockingObjects.push_back(ptr->id);
 		}
-
-
 	}
 };
 

+ 2 - 2
lib/networkPacks/PacksForClient.h

@@ -17,16 +17,16 @@
 #include "NetPacksBase.h"
 #include "ObjProperty.h"
 
-#include "../CCreatureSet.h"
 #include "../ResourceSet.h"
 #include "../TurnTimerInfo.h"
+#include "../bonuses/Bonus.h"
 #include "../gameState/EVictoryLossCheckResult.h"
 #include "../gameState/RumorState.h"
 #include "../gameState/QuestInfo.h"
 #include "../gameState/TavernSlot.h"
 #include "../gameState/GameStatistics.h"
 #include "../int3.h"
-#include "../mapping/CMapDefines.h"
+#include "../mapObjects/army/CSimpleArmy.h"
 #include "../spells/ViewSpellInt.h"
 
 class CClient;

+ 1 - 0
lib/networkPacks/PacksForClientBattle.h

@@ -16,6 +16,7 @@
 #include "../battle/BattleInfo.h"
 #include "../battle/BattleHexArray.h"
 #include "../battle/BattleUnitTurnReason.h"
+#include "../mapObjects/army/CStackBasicDescriptor.h"
 #include "../texts/MetaString.h"
 
 class CClient;

+ 1 - 1
lib/pathfinder/CGPathNode.cpp

@@ -14,7 +14,7 @@
 
 #include "../callback/IGameInfoCallback.h"
 #include "../mapObjects/CGHeroInstance.h"
-#include "../mapping/CMapDefines.h"
+#include "../mapping/TerrainTile.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/pathfinder/PathfinderUtil.h

@@ -10,7 +10,7 @@
 #pragma once
 
 #include "../mapObjects/CGObjectInstance.h"
-#include "../mapping/CMapDefines.h"
+#include "../mapping/TerrainTile.h"
 #include "../callback/IGameInfoCallback.h"
 #include "CGPathNode.h"
 

+ 1 - 1
lib/pathfinder/PathfindingRules.cpp

@@ -17,7 +17,7 @@
 
 #include "../mapObjects/CGHeroInstance.h"
 #include "../mapObjects/MiscObjects.h"
-#include "../mapping/CMapDefines.h"
+#include "../mapping/TerrainTile.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 3 - 2
lib/rewardable/Info.cpp

@@ -15,12 +15,13 @@
 #include "Limiter.h"
 #include "Reward.h"
 
+#include "../CCreatureHandler.h"
+#include "../GameLibrary.h"
 #include "../callback/IGameRandomizer.h"
-#include "../texts/CGeneralTextHandler.h"
 #include "../json/JsonRandom.h"
-#include "../GameLibrary.h"
 #include "../mapObjects/IObjectInterface.h"
 #include "../modding/IdentifierStorage.h"
+#include "../texts/CGeneralTextHandler.h"
 
 #include <vstd/RNG.h>
 

+ 1 - 1
lib/rewardable/Interface.cpp

@@ -22,7 +22,7 @@
 #include "../spells/ISpellMechanics.h"
 #include "../mapObjects/CGHeroInstance.h"
 #include "../mapObjects/MiscObjects.h"
-#include "../mapping/CMapDefines.h"
+#include "../mapping/TerrainTile.h"
 #include "../networkPacks/StackLocation.h"
 #include "../networkPacks/PacksForClient.h"
 

+ 2 - 0
lib/rewardable/Interface.h

@@ -16,6 +16,8 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 class IObjectInterface;
+class IGameEventCallback;
+class CArmedInstance;
 
 namespace Rewardable
 {

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