浏览代码

Merge pull request #5896 from IvanSavenko/code_reorganize

Split large source files into smaller files per 1 class
Ivan Savenko 5 月之前
父节点
当前提交
46072a1d60
共有 100 个文件被更改,包括 2405 次插入1974 次删除
  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. 1 0
      client/CPlayerInterface.cpp
  11. 1 0
      client/CServerHandler.cpp
  12. 2 1
      client/Client.cpp
  13. 1 1
      client/GameChatHandler.cpp
  14. 1 0
      client/HeroMovementController.cpp
  15. 0 1
      client/UIHelper.cpp
  16. 1 1
      client/adventureMap/AdventureMapInterface.cpp
  17. 1 1
      client/adventureMap/CInGameConsole.cpp
  18. 1 1
      client/adventureMap/CMinimap.cpp
  19. 0 1
      client/adventureMap/MapAudioPlayer.cpp
  20. 1 1
      client/mapView/MapRenderer.cpp
  21. 0 356
      lib/CCreatureSet.h
  22. 30 7
      lib/CMakeLists.txt
  23. 1 1
      lib/battle/BattleLayout.cpp
  24. 0 1
      lib/battle/BattleStateInfoForRetreat.cpp
  25. 1 0
      lib/battle/DamageCalculator.cpp
  26. 0 1
      lib/bonuses/Bonus.cpp
  27. 0 1
      lib/bonuses/Limiters.cpp
  28. 1 0
      lib/gameState/CGameState.cpp
  29. 2 0
      lib/gameState/EVictoryLossCheckResult.h
  30. 1 1
      lib/gameState/InfoAboutArmy.h
  31. 1 1
      lib/json/JsonRandom.cpp
  32. 1 1
      lib/mapObjectConstructors/CommonConstructors.cpp
  33. 1 0
      lib/mapObjectConstructors/DwellingInstanceConstructor.cpp
  34. 1 1
      lib/mapObjects/CGCreature.h
  35. 2 1
      lib/mapObjects/CGDwelling.h
  36. 3 1
      lib/mapObjects/CGHeroInstance.h
  37. 1 1
      lib/mapObjects/CGResource.h
  38. 1 0
      lib/mapObjects/CGTownInstance.cpp
  39. 1 1
      lib/mapObjects/CGTownInstance.h
  40. 2 1
      lib/mapObjects/CRewardableObject.h
  41. 1 0
      lib/mapObjects/IObjectInterface.cpp
  42. 0 1
      lib/mapObjects/MapObjects.h
  43. 2 1
      lib/mapObjects/MiscObjects.h
  44. 27 30
      lib/mapObjects/army/CArmedInstance.cpp
  45. 26 15
      lib/mapObjects/army/CArmedInstance.h
  46. 80 0
      lib/mapObjects/army/CCommanderInstance.cpp
  47. 55 0
      lib/mapObjects/army/CCommanderInstance.h
  48. 43 544
      lib/mapObjects/army/CCreatureSet.cpp
  49. 160 0
      lib/mapObjects/army/CCreatureSet.h
  50. 54 0
      lib/mapObjects/army/CSimpleArmy.h
  51. 91 0
      lib/mapObjects/army/CStackBasicDescriptor.cpp
  52. 65 0
      lib/mapObjects/army/CStackBasicDescriptor.h
  53. 358 0
      lib/mapObjects/army/CStackInstance.cpp
  54. 134 0
      lib/mapObjects/army/CStackInstance.h
  55. 37 0
      lib/mapping/CCastleEvent.h
  56. 1 0
      lib/mapping/CMap.cpp
  57. 2 1
      lib/mapping/CMap.h
  58. 59 0
      lib/mapping/CMapEvent.h
  59. 1 0
      lib/mapping/MapFormatH3M.cpp
  60. 5 70
      lib/mapping/TerrainTile.h
  61. 2 2
      lib/networkPacks/PacksForClient.h
  62. 1 0
      lib/networkPacks/PacksForClientBattle.h
  63. 1 1
      lib/pathfinder/CGPathNode.cpp
  64. 1 1
      lib/pathfinder/PathfinderUtil.h
  65. 1 1
      lib/pathfinder/PathfindingRules.cpp
  66. 3 2
      lib/rewardable/Info.cpp
  67. 1 1
      lib/rewardable/Interface.cpp
  68. 2 0
      lib/rewardable/Interface.h
  69. 1 0
      lib/rewardable/Limiter.h
  70. 0 1
      lib/rewardable/Reward.h
  71. 1 0
      lib/rmg/modificators/ObjectManager.cpp
  72. 1 0
      lib/serializer/SerializerReflection.cpp
  73. 0 782
      lib/spells/AdventureSpellMechanics.cpp
  74. 0 123
      lib/spells/AdventureSpellMechanics.h
  75. 7 1
      lib/spells/ISpellMechanics.cpp
  76. 144 0
      lib/spells/adventure/AdventureSpellMechanics.cpp
  77. 46 0
      lib/spells/adventure/AdventureSpellMechanics.h
  78. 166 0
      lib/spells/adventure/DimensionDoorMechanics.cpp
  79. 30 0
      lib/spells/adventure/DimensionDoorMechanics.h
  80. 78 0
      lib/spells/adventure/ScuttleBoatMechanics.cpp
  81. 28 0
      lib/spells/adventure/ScuttleBoatMechanics.h
  82. 111 0
      lib/spells/adventure/SummonBoatMechanics.cpp
  83. 28 0
      lib/spells/adventure/SummonBoatMechanics.h
  84. 299 0
      lib/spells/adventure/TownPortalMechanics.cpp
  85. 35 0
      lib/spells/adventure/TownPortalMechanics.h
  86. 90 0
      lib/spells/adventure/ViewWorldMechanics.cpp
  87. 48 0
      lib/spells/adventure/ViewWorldMechanics.h
  88. 1 1
      lib/texts/MetaString.cpp
  89. 1 0
      mapeditor/inspector/armywidget.cpp
  90. 1 1
      mapeditor/inspector/armywidget.h
  91. 1 0
      mapeditor/inspector/towneventswidget.cpp
  92. 0 1
      mapeditor/inspector/towneventswidget.h
  93. 0 1
      mapeditor/mapsettings/eventsettings.cpp
  94. 1 1
      scripting/lua/api/StackInstance.h
  95. 0 1
      server/CGameHandler.cpp
  96. 1 0
      server/CVCMIServer.cpp
  97. 1 0
      server/NetPacksLobbyServer.cpp
  98. 1 0
      server/battles/BattleResultProcessor.cpp
  99. 1 0
      server/processors/HeroPoolProcessor.cpp
  100. 1 0
      server/processors/NewTurnProcessor.cpp

+ 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"

+ 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"
 

+ 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

+ 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"

+ 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"

+ 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

+ 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"

+ 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;

+ 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"
 

+ 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
 {

+ 1 - 0
lib/rewardable/Limiter.h

@@ -12,6 +12,7 @@
 
 #include "../GameConstants.h"
 #include "../ResourceSet.h"
+#include "../mapObjects/army/CStackBasicDescriptor.h"
 #include "../serializer/Serializeable.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 0 - 1
lib/rewardable/Reward.h

@@ -12,7 +12,6 @@
 
 #include "../ResourceSet.h"
 #include "../bonuses/Bonus.h"
-#include "../CCreatureSet.h"
 #include "../networkPacks/Component.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 1 - 0
lib/rmg/modificators/ObjectManager.cpp

@@ -23,6 +23,7 @@
 #include "../../CCreatureHandler.h"
 #include "../../mapObjectConstructors/AObjectTypeHandler.h"
 #include "../../mapObjectConstructors/CObjectClassesHandler.h"
+#include "../../mapObjects/army/CStackInstance.h"
 #include "../../mapObjects/CGCreature.h"
 #include "../../mapping/CMap.h"
 #include "../../mapping/CMapEditManager.h"

+ 1 - 0
lib/serializer/SerializerReflection.cpp

@@ -22,6 +22,7 @@
 #include "../entities/hero/CHero.h"
 #include "../mapObjects/ObjectTemplate.h"
 #include "../mapping/CMapInfo.h"
+#include "../mapping/CCastleEvent.h"
 #include "../rmg/CMapGenOptions.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 0 - 782
lib/spells/AdventureSpellMechanics.cpp

@@ -1,782 +0,0 @@
-/*
- * AdventureSpellMechanics.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 "AdventureSpellMechanics.h"
-
-#include "CSpellHandler.h"
-#include "Problem.h"
-
-#include "../CPlayerState.h"
-#include "../IGameSettings.h"
-#include "../callback/IGameInfoCallback.h"
-#include "../mapObjects/CGHeroInstance.h"
-#include "../mapObjects/CGTownInstance.h"
-#include "../mapObjects/MiscObjects.h"
-#include "../mapping/CMap.h"
-#include "../networkPacks/PacksForClient.h"
-
-#include <vstd/RNG.h>
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-///AdventureSpellMechanics
-AdventureSpellMechanics::AdventureSpellMechanics(const CSpell * s):
-	IAdventureSpellMechanics(s)
-{
-}
-
-bool AdventureSpellMechanics::canBeCast(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const
-{
-	if(!owner->isAdventure())
-		return false;
-
-	const auto * heroCaster = dynamic_cast<const CGHeroInstance *>(caster);
-
-	if (heroCaster)
-	{
-		if(heroCaster->isGarrisoned())
-			return false;
-
-		const auto level = heroCaster->getSpellSchoolLevel(owner);
-		const auto cost = owner->getCost(level);
-
-		if(!heroCaster->canCastThisSpell(owner))
-			return false;
-
-		if(heroCaster->mana < cost)
-			return false;
-	}
-
-	return canBeCastImpl(problem, cb, caster);
-}
-
-bool AdventureSpellMechanics::canBeCastAt(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
-{
-	return canBeCast(problem, cb, caster) && canBeCastAtImpl(problem, cb, caster, pos);
-}
-
-bool AdventureSpellMechanics::canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const
-{
-	return true;
-}
-
-bool AdventureSpellMechanics::canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
-{
-	return true;
-}
-
-bool AdventureSpellMechanics::adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	spells::detail::ProblemImpl problem;
-
-	if (!canBeCastAt(problem, env->getCb(), parameters.caster, parameters.pos))
-		return false;
-
-	ESpellCastResult result = beginCast(env, parameters);
-
-	if(result == ESpellCastResult::OK)
-		performCast(env, parameters);
-
-	return result != ESpellCastResult::ERROR;
-}
-
-ESpellCastResult AdventureSpellMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	if(owner->hasEffects())
-	{
-		//todo: cumulative effects support
-		const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
-
-		std::vector<Bonus> bonuses;
-
-		owner->getEffects(bonuses, schoolLevel, false, parameters.caster->getEnchantPower(owner));
-
-		for(const Bonus & b : bonuses)
-		{
-			GiveBonus gb;
-			gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId());
-			gb.bonus = b;
-			env->apply(gb);
-		}
-
-		return ESpellCastResult::OK;
-	}
-	else
-	{
-		//There is no generic algorithm of adventure cast
-		env->complain("Unimplemented adventure spell");
-		return ESpellCastResult::ERROR;
-	}
-}
-
-ESpellCastResult AdventureSpellMechanics::beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	return ESpellCastResult::OK;
-}
-
-void AdventureSpellMechanics::endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	// no-op, only for implementation in derived classes
-}
-
-void AdventureSpellMechanics::performCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	const auto level = parameters.caster->getSpellSchoolLevel(owner);
-	const auto cost = owner->getCost(level);
-
-	AdvmapSpellCast asc;
-	asc.casterID = ObjectInstanceID(parameters.caster->getCasterUnitId());
-	asc.spellID = owner->id;
-	env->apply(asc);
-
-	ESpellCastResult result = applyAdventureEffects(env, parameters);
-
-	switch(result)
-	{
-	case ESpellCastResult::OK:
-		parameters.caster->spendMana(env, cost);
-		endCast(env, parameters);
-		break;
-	default:
-		break;
-	}
-}
-
-///SummonBoatMechanics
-SummonBoatMechanics::SummonBoatMechanics(const CSpell * s):
-	AdventureSpellMechanics(s)
-{
-}
-
-bool SummonBoatMechanics::canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const
-{
-	if(!caster->getHeroCaster())
-		return false;
-
-	if(caster->getHeroCaster()->inBoat())
-	{
-		MetaString message = MetaString::createFromTextID("core.genrltxt.333");
-		caster->getCasterName(message);
-		problem.add(std::move(message));
-		return false;
-	}
-
-	int3 summonPos = caster->getHeroCaster()->bestLocation();
-
-	if(summonPos.x < 0)
-	{
-		MetaString message = MetaString::createFromTextID("core.genrltxt.334");
-		caster->getCasterName(message);
-		problem.add(std::move(message));
-		return false;
-	}
-
-	return true;
-}
-
-ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
-
-	//check if spell works at all
-	if(env->getRNG()->nextInt(0, 99) >= owner->getLevelPower(schoolLevel)) //power is % chance of success
-	{
-		InfoWindow iw;
-		iw.player = parameters.caster->getCasterOwner();
-		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 336); //%s tried to summon a boat, but failed.
-		parameters.caster->getCasterName(iw.text);
-		env->apply(iw);
-		return ESpellCastResult::OK;
-	}
-
-	//try to find unoccupied boat to summon
-	const CGBoat * nearest = nullptr;
-	double dist = 0;
-	for(const auto & b : env->getMap()->getObjects<CGBoat>())
-	{
-		if(b->getBoardedHero() || b->layer != EPathfindingLayer::SAIL)
-			continue; //we're looking for unoccupied boat
-
-		double nDist = b->visitablePos().dist2d(parameters.caster->getHeroCaster()->visitablePos());
-		if(!nearest || nDist < dist) //it's first boat or closer than previous
-		{
-			nearest = b;
-			dist = nDist;
-		}
-	}
-
-	int3 summonPos = parameters.caster->getHeroCaster()->bestLocation();
-
-	if(nullptr != nearest) //we found boat to summon
-	{
-		ChangeObjPos cop;
-		cop.objid = nearest->id;
-		cop.nPos = summonPos;
-		cop.initiator = parameters.caster->getCasterOwner();
-		env->apply(cop);
-	}
-	else if(schoolLevel < 2) //none or basic level -> cannot create boat :(
-	{
-		InfoWindow iw;
-		iw.player = parameters.caster->getCasterOwner();
-		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 335); //There are no boats to summon.
-		env->apply(iw);
-		return ESpellCastResult::ERROR;
-	}
-	else //create boat
-	{
-		env->createBoat(summonPos, BoatId::NECROPOLIS, parameters.caster->getCasterOwner());
-	}
-	return ESpellCastResult::OK;
-}
-
-///ScuttleBoatMechanics
-ScuttleBoatMechanics::ScuttleBoatMechanics(const CSpell * s):
-	AdventureSpellMechanics(s)
-{
-}
-
-bool ScuttleBoatMechanics::canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
-{
-	if(!cb->isInTheMap(pos))
-		return false;
-
-	if (caster->getHeroCaster())
-	{
-		int3 casterPosition = caster->getHeroCaster()->getSightCenter();
-
-		if(!isInScreenRange(casterPosition, pos))
-			return false;
-	}
-
-	if(!cb->isVisibleFor(pos, caster->getCasterOwner()))
-		return false;
-
-	const TerrainTile * t = cb->getTile(pos);
-	if(!t || t->visitableObjects.empty())
-		return false;
-
-	const CGObjectInstance * topObject = cb->getObj(t->visitableObjects.back());
-	if (topObject->ID != Obj::BOAT)
-		return false;
-
-	return true;
-}
-
-ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
-	//check if spell works at all
-	if(env->getRNG()->nextInt(0, 99) >= owner->getLevelPower(schoolLevel)) //power is % chance of success
-	{
-		InfoWindow iw;
-		iw.player = parameters.caster->getCasterOwner();
-		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed
-		parameters.caster->getCasterName(iw.text);
-		env->apply(iw);
-		return ESpellCastResult::OK;
-	}
-
-	const TerrainTile & t = env->getMap()->getTile(parameters.pos);
-
-	RemoveObject ro;
-	ro.initiator = parameters.caster->getCasterOwner();
-	ro.objectID = t.visitableObjects.back();
-	env->apply(ro);
-	return ESpellCastResult::OK;
-}
-
-///DimensionDoorMechanics
-DimensionDoorMechanics::DimensionDoorMechanics(const CSpell * s):
-	AdventureSpellMechanics(s)
-{
-}
-
-bool DimensionDoorMechanics::canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const
-{
-	if(!caster->getHeroCaster())
-		return false;
-
-	if(caster->getHeroCaster()->movementPointsRemaining() <= 0) //unlike town portal non-zero MP is enough
-	{
-		problem.add(MetaString::createFromTextID("core.genrltxt.125"));
-		return false;
-	}
-
-	const auto schoolLevel = caster->getSpellSchoolLevel(owner);
-
-	std::stringstream cachingStr;
-	cachingStr << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << owner->id.num;
-
-	int castsAlreadyPerformedThisTurn = caster->getHeroCaster()->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(owner->id)), cachingStr.str())->size();
-	int castsLimit = owner->getLevelPower(schoolLevel);
-
-	bool isTournamentRulesLimitEnabled = cb->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT);
-	if(isTournamentRulesLimitEnabled)
-	{
-		int3 mapSize = cb->getMapSize();
-
-		bool meetsTournamentRulesTwoCastsRequirements =  mapSize.x * mapSize.y * mapSize.z >= GameConstants::TOURNAMENT_RULES_DD_MAP_TILES_THRESHOLD
-			&& schoolLevel == MasteryLevel::EXPERT;
-
-		castsLimit = meetsTournamentRulesTwoCastsRequirements ? 2 : 1;
-	}
-
-	if(castsAlreadyPerformedThisTurn >= castsLimit) //limit casts per turn
-	{
-		MetaString message = MetaString::createFromTextID("core.genrltxt.338");
-		caster->getCasterName(message);
-		problem.add(std::move(message));
-		return false;
-	}
-
-	return true;
-}
-
-bool DimensionDoorMechanics::canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
-{
-	if(!cb->isInTheMap(pos))
-		return false;
-
-	if(cb->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES))
-	{
-		if(!cb->isVisibleFor(pos, caster->getCasterOwner()))
-			return false;
-	}
-
-	int3 casterPosition = caster->getHeroCaster()->getSightCenter();
-
-	const TerrainTile * dest = cb->getTileUnchecked(pos);
-	const TerrainTile * curr = cb->getTileUnchecked(casterPosition);
-
-	if(!dest)
-		return false;
-
-	if(!curr)
-		return false;
-
-	if(!isInScreenRange(casterPosition, pos))
-		return false;
-
-	if(cb->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE))
-	{
-		if(!dest->isClear(curr))
-			return false;
-	}
-	else
-	{
-		if (dest->blocked())
-			return false;
-	}
-
-	return true;
-}
-
-ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
-	const int baseCost = env->getCb()->getSettings().getInteger(EGameSettings::HEROES_MOVEMENT_COST_BASE);
-	const int movementCost = baseCost * ((schoolLevel >= 3) ? 2 : 3);
-
-	int3 casterPosition = parameters.caster->getHeroCaster()->getSightCenter();
-	const TerrainTile * dest = env->getCb()->getTile(parameters.pos);
-	const TerrainTile * curr = env->getCb()->getTile(casterPosition);
-
-	if(!dest->isClear(curr))
-	{
-		InfoWindow iw;
-		iw.player = parameters.caster->getCasterOwner();
-
-		// tile is either blocked or not possible to move (e.g. water <-> land)
-		if(env->getCb()->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS))
-		{
-			// SOD: DD to such "wrong" terrain results in mana and move points spending, but fails to move hero
-			iw.text = MetaString::createFromTextID("core.genrltxt.70"); // Dimension Door failed!
-			env->apply(iw);
-			// no return - resources will be spent
-		}
-		else
-		{
-			// HotA: game will show error message without taking mana or move points, even when DD into terra incognita
-			iw.text = MetaString::createFromTextID("vcmi.dimensionDoor.seaToLandError");
-			env->apply(iw);
-			return ESpellCastResult::CANCEL;
-		}
-	}
-
-	GiveBonus gb;
-	gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId());
-	gb.bonus = Bonus(BonusDuration::ONE_DAY, BonusType::NONE, BonusSource::SPELL_EFFECT, 0, BonusSourceID(owner->id));
-	env->apply(gb);
-
-	SetMovePoints smp;
-	smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());
-	if(movementCost < static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()))
-		smp.val = parameters.caster->getHeroCaster()->movementPointsRemaining() - movementCost;
-	else
-		smp.val = 0;
-	env->apply(smp);
-
-	return ESpellCastResult::OK;
-}
-
-void DimensionDoorMechanics::endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	int3 casterPosition = parameters.caster->getHeroCaster()->getSightCenter();
-	const TerrainTile * dest = env->getCb()->getTile(parameters.pos);
-	const TerrainTile * curr = env->getCb()->getTile(casterPosition);
-
-	if(dest->isClear(curr))
-		env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(parameters.pos), EMovementMode::DIMENSION_DOOR);
-}
-
-///TownPortalMechanics
-TownPortalMechanics::TownPortalMechanics(const CSpell * s):
-	AdventureSpellMechanics(s)
-{
-}
-
-ESpellCastResult TownPortalMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	const CGTownInstance * destination = nullptr;
-	const int moveCost = movementCost(env, parameters);
-	
-	if(!parameters.caster->getHeroCaster())
-	{
-		env->complain("Not a hero caster!");
-		return ESpellCastResult::ERROR;
-	}
-
-	if(parameters.caster->getSpellSchoolLevel(owner) < 2)
-	{
-		std::vector <const CGTownInstance*> pool = getPossibleTowns(env, parameters);
-		destination = findNearestTown(env, parameters, pool);
-
-		if(nullptr == destination)
-			return ESpellCastResult::ERROR;
-
-		if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < moveCost)
-			return ESpellCastResult::ERROR;
-
-		if(destination->getVisitingHero())
-		{
-			InfoWindow iw;
-			iw.player = parameters.caster->getCasterOwner();
-			iw.text.appendLocalString(EMetaText::GENERAL_TXT, 123);
-			env->apply(iw);
-			return ESpellCastResult::CANCEL;
-		}
-	}
-	else if(env->getMap()->isInTheMap(parameters.pos))
-	{
-		const TerrainTile & tile = env->getMap()->getTile(parameters.pos);
-
-		ObjectInstanceID topObjID = tile.topVisitableObj(false);
-		const CGObjectInstance * topObj = env->getMap()->getObject(topObjID);
-
-		if(!topObj)
-		{
-			env->complain("Destination tile is not visitable" + parameters.pos.toString());
-			return ESpellCastResult::ERROR;
-		}
-		else if(topObj->ID == Obj::HERO)
-		{
-			env->complain("Can't teleport to occupied town at " + parameters.pos.toString());
-			return ESpellCastResult::ERROR;
-		}
-		else if(topObj->ID != Obj::TOWN)
-		{
-			env->complain("No town at destination tile " + parameters.pos.toString());
-			return ESpellCastResult::ERROR;
-		}
-
-		destination = dynamic_cast<const CGTownInstance*>(topObj);
-
-		if(nullptr == destination)
-		{
-			env->complain("[Internal error] invalid town object at " + parameters.pos.toString());
-			return ESpellCastResult::ERROR;
-		}
-
-		const auto relations = env->getCb()->getPlayerRelations(destination->tempOwner, parameters.caster->getCasterOwner());
-
-		if(relations == PlayerRelations::ENEMIES)
-		{
-			env->complain("Can't teleport to enemy!");
-			return ESpellCastResult::ERROR;
-		}
-
-		if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < moveCost)
-		{
-			env->complain("This hero has not enough movement points!");
-			return ESpellCastResult::ERROR;
-		}
-
-		if(destination->getVisitingHero())
-		{
-			env->complain("[Internal error] Can't teleport to occupied town");
-			return ESpellCastResult::ERROR;
-		}
-	}
-	else
-	{
-		env->complain("Invalid destination tile");
-		return ESpellCastResult::ERROR;
-	}
-
-	const TerrainTile & from = env->getMap()->getTile(parameters.caster->getHeroCaster()->visitablePos());
-	const TerrainTile & dest = env->getMap()->getTile(destination->visitablePos());
-
-	if(!dest.entrableTerrain(&from))
-	{
-		InfoWindow iw;
-		iw.player = parameters.caster->getCasterOwner();
-		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 135);
-		env->apply(iw);
-		return ESpellCastResult::ERROR;
-	}
-
-	return ESpellCastResult::OK;
-}
-
-void TownPortalMechanics::endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	const int moveCost = movementCost(env, parameters);
-	const CGTownInstance * destination = nullptr;
-
-	if(parameters.caster->getSpellSchoolLevel(owner) < 2)
-	{
-		std::vector <const CGTownInstance*> pool = getPossibleTowns(env, parameters);
-		destination = findNearestTown(env, parameters, pool);
-	}
-	else
-	{
-		const TerrainTile & tile = env->getMap()->getTile(parameters.pos);
-		ObjectInstanceID topObjID = tile.topVisitableObj(false);
-		const CGObjectInstance * topObj = env->getMap()->getObject(topObjID);
-
-		destination = dynamic_cast<const CGTownInstance*>(topObj);
-	}
-
-	if(env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(destination->visitablePos()), EMovementMode::TOWN_PORTAL))
-	{
-		SetMovePoints smp;
-		smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());
-		smp.val = std::max<ui32>(0, parameters.caster->getHeroCaster()->movementPointsRemaining() - moveCost);
-		env->apply(smp);
-	}
-}
-
-ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	std::vector<const CGTownInstance *>	towns = getPossibleTowns(env, parameters);
-	
-	if(!parameters.caster->getHeroCaster())
-	{
-		env->complain("Not a hero caster!");
-		return ESpellCastResult::ERROR;
-	}
-
-	if(towns.empty())
-	{
-		InfoWindow iw;
-		iw.player = parameters.caster->getCasterOwner();
-		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 124);
-		env->apply(iw);
-		return ESpellCastResult::CANCEL;
-	}
-
-	const int moveCost = movementCost(env, parameters);
-
-	if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < moveCost)
-	{
-		InfoWindow iw;
-		iw.player = parameters.caster->getCasterOwner();
-		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 125);
-		env->apply(iw);
-		return ESpellCastResult::CANCEL;
-	}
-
-	if(!parameters.pos.isValid() && parameters.caster->getSpellSchoolLevel(owner) >= 2)
-	{
-		auto queryCallback = [this, env, parameters](std::optional<int32_t> reply) -> void
-		{
-			if(reply.has_value())
-			{
-				ObjectInstanceID townId(*reply);
-
-				const CGObjectInstance * o = env->getCb()->getObj(townId, true);
-				if(o == nullptr)
-				{
-					env->complain("Invalid object instance selected");
-					return;
-				}
-
-				if(!dynamic_cast<const CGTownInstance *>(o))
-				{
-					env->complain("Object instance is not town");
-					return;
-				}
-
-				AdventureSpellCastParameters p;
-				p.caster = parameters.caster;
-				p.pos = o->visitablePos();
-				performCast(env, p);
-			}
-		};
-
-		MapObjectSelectDialog request;
-
-		for(const auto * t : towns)
-		{
-			if(t->getVisitingHero() == nullptr) //empty town
-				request.objects.push_back(t->id);
-		}
-
-		if(request.objects.empty())
-		{
-			InfoWindow iw;
-			iw.player = parameters.caster->getCasterOwner();
-			iw.text.appendLocalString(EMetaText::GENERAL_TXT, 124);
-			env->apply(iw);
-			return ESpellCastResult::CANCEL;
-		}
-
-		request.player = parameters.caster->getCasterOwner();
-		request.title.appendLocalString(EMetaText::JK_TXT, 40);
-		request.description.appendLocalString(EMetaText::JK_TXT, 41);
-		request.icon = Component(ComponentType::SPELL, owner->id);
-
-		env->genericQuery(&request, request.player, queryCallback);
-
-		return ESpellCastResult::PENDING;
-	}
-
-	return ESpellCastResult::OK;
-}
-
-const CGTownInstance * TownPortalMechanics::findNearestTown(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const std::vector <const CGTownInstance *> & pool) const
-{
-	if(pool.empty())
-		return nullptr;
-	
-	if(!parameters.caster->getHeroCaster())
-		return nullptr;
-
-	auto nearest = pool.cbegin(); //nearest town's iterator
-	si32 dist = (*nearest)->visitablePos().dist2dSQ(parameters.caster->getHeroCaster()->visitablePos());
-
-	for(auto i = nearest + 1; i != pool.cend(); ++i)
-	{
-		si32 curDist = (*i)->visitablePos().dist2dSQ(parameters.caster->getHeroCaster()->visitablePos());
-
-		if(curDist < dist)
-		{
-			nearest = i;
-			dist = curDist;
-		}
-	}
-	return *nearest;
-}
-
-std::vector <const CGTownInstance*> TownPortalMechanics::getPossibleTowns(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	std::vector <const CGTownInstance*> ret;
-
-	const TeamState * team = env->getCb()->getPlayerTeam(parameters.caster->getCasterOwner());
-
-	for(const auto & color : team->players)
-	{
-		for(auto currTown : env->getCb()->getPlayerState(color)->getTowns())
-		{
-			ret.push_back(currTown);
-		}
-	}
-	return ret;
-}
-
-int32_t TownPortalMechanics::movementCost(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	if(parameters.caster != parameters.caster->getHeroCaster()) //if caster is not hero
-		return 0;
-	
-	int baseMovementCost = env->getCb()->getSettings().getInteger(EGameSettings::HEROES_MOVEMENT_COST_BASE);
-	return baseMovementCost * ((parameters.caster->getSpellSchoolLevel(owner) >= 3) ? 2 : 3);
-}
-
-///ViewMechanics
-ViewMechanics::ViewMechanics(const CSpell * s):
-	AdventureSpellMechanics(s)
-{
-}
-
-ESpellCastResult ViewMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
-{
-	ShowWorldViewEx pack;
-
-	pack.player = parameters.caster->getCasterOwner();
-
-	const auto spellLevel = parameters.caster->getSpellSchoolLevel(owner);
-
-	const auto & fowMap = env->getCb()->getPlayerTeam(parameters.caster->getCasterOwner())->fogOfWarMap;
-
-	for(const auto & obj : env->getMap()->getObjects())
-	{
-		//deleted object remain as empty pointer
-		if(obj && filterObject(obj, spellLevel))
-		{
-			ObjectPosInfo posInfo(obj);
-
-			if(fowMap[posInfo.pos.z][posInfo.pos.x][posInfo.pos.y] == 0)
-				pack.objectPositions.push_back(posInfo);
-		}
-	}
-	pack.showTerrain = showTerrain(spellLevel);
-
-	env->apply(pack);
-
-	return ESpellCastResult::OK;
-}
-
-///ViewAirMechanics
-ViewAirMechanics::ViewAirMechanics(const CSpell * s):
-	ViewMechanics(s)
-{
-}
-
-bool ViewAirMechanics::filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const
-{
-	return (obj->ID == Obj::ARTIFACT) || (spellLevel > 1 && obj->ID == Obj::HERO) || (spellLevel > 2 && obj->ID == Obj::TOWN);
-}
-
-bool ViewAirMechanics::showTerrain(const int32_t spellLevel) const
-{
-	return false;
-}
-
-///ViewEarthMechanics
-ViewEarthMechanics::ViewEarthMechanics(const CSpell * s):
-	ViewMechanics(s)
-{
-}
-
-bool ViewEarthMechanics::filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const
-{
-	return (obj->ID == Obj::RESOURCE) || (spellLevel > 1 && obj->ID == Obj::MINE);
-}
-
-bool ViewEarthMechanics::showTerrain(const int32_t spellLevel) const
-{
-	return spellLevel > 2;
-}
-
-VCMI_LIB_NAMESPACE_END

+ 0 - 123
lib/spells/AdventureSpellMechanics.h

@@ -1,123 +0,0 @@
-/*
- * AdventureSpellMechanics.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 "ISpellMechanics.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-class CGTownInstance;
-
-enum class ESpellCastResult
-{
-	OK, // cast successful
-	CANCEL, // cast failed but it is not an error, no mana has been spent
-	PENDING,
-	ERROR// error occurred, for example invalid request from player
-};
-
-class AdventureSpellMechanics : public IAdventureSpellMechanics
-{
-public:
-	AdventureSpellMechanics(const CSpell * s);
-
-	bool canBeCast(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const final;
-	bool canBeCastAt(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const final;
-
-	bool adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override final;
-protected:
-	///actual adventure cast implementation
-	virtual ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
-	virtual ESpellCastResult beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
-	virtual void endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
-	virtual bool canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const;
-	virtual bool canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const;
-
-	void performCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
-};
-
-class SummonBoatMechanics final : public AdventureSpellMechanics
-{
-public:
-	SummonBoatMechanics(const CSpell * s);
-protected:
-	bool canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const override;
-
-	ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
-};
-
-class ScuttleBoatMechanics final : public AdventureSpellMechanics
-{
-public:
-	ScuttleBoatMechanics(const CSpell * s);
-protected:
-	bool canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const override;
-
-	ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
-};
-
-class DimensionDoorMechanics final : public AdventureSpellMechanics
-{
-public:
-	DimensionDoorMechanics(const CSpell * s);
-protected:
-	bool canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const override;
-	bool canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const override;
-
-	ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
-	void endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
-};
-
-class TownPortalMechanics final : public AdventureSpellMechanics
-{
-public:
-	TownPortalMechanics(const CSpell * s);
-protected:
-	ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
-	ESpellCastResult beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
-	void endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
-private:
-	const CGTownInstance * findNearestTown(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const std::vector <const CGTownInstance*> & pool) const;
-	int32_t movementCost(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
-	std::vector <const CGTownInstance*> getPossibleTowns(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
-};
-
-class ViewMechanics : public AdventureSpellMechanics
-{
-public:
-	ViewMechanics(const CSpell * s);
-protected:
-	ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
-	virtual bool filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const = 0;
-	virtual bool showTerrain(const int32_t spellLevel) const = 0;
-};
-
-class ViewAirMechanics final : public ViewMechanics
-{
-public:
-	ViewAirMechanics(const CSpell * s);
-protected:
-	bool filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const override;
-	bool showTerrain(const int32_t spellLevel) const override;
-};
-
-class ViewEarthMechanics final : public ViewMechanics
-{
-public:
-	ViewEarthMechanics(const CSpell * s);
-protected:
-	bool filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const override;
-	bool showTerrain(const int32_t spellLevel) const override;
-};
-
-
-
-VCMI_LIB_NAMESPACE_END

+ 7 - 1
lib/spells/ISpellMechanics.cpp

@@ -26,7 +26,13 @@
 #include "TargetCondition.h"
 #include "Problem.h"
 
-#include "AdventureSpellMechanics.h"
+#include "adventure/AdventureSpellMechanics.h"
+#include "adventure/DimensionDoorMechanics.h"
+#include "adventure/ScuttleBoatMechanics.h"
+#include "adventure/SummonBoatMechanics.h"
+#include "adventure/TownPortalMechanics.h"
+#include "adventure/ViewWorldMechanics.h"
+
 #include "BattleSpellMechanics.h"
 
 #include "effects/Effects.h"

+ 144 - 0
lib/spells/adventure/AdventureSpellMechanics.cpp

@@ -0,0 +1,144 @@
+/*
+ * AdventureSpellMechanics.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 "AdventureSpellMechanics.h"
+
+#include "../CSpellHandler.h"
+#include "../Problem.h"
+
+#include "../../mapObjects/CGHeroInstance.h"
+#include "../../networkPacks/PacksForClient.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+AdventureSpellMechanics::AdventureSpellMechanics(const CSpell * s)
+	: IAdventureSpellMechanics(s)
+{
+}
+
+bool AdventureSpellMechanics::canBeCast(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const
+{
+	if(!owner->isAdventure())
+		return false;
+
+	const auto * heroCaster = dynamic_cast<const CGHeroInstance *>(caster);
+
+	if(heroCaster)
+	{
+		if(heroCaster->isGarrisoned())
+			return false;
+
+		const auto level = heroCaster->getSpellSchoolLevel(owner);
+		const auto cost = owner->getCost(level);
+
+		if(!heroCaster->canCastThisSpell(owner))
+			return false;
+
+		if(heroCaster->mana < cost)
+			return false;
+	}
+
+	return canBeCastImpl(problem, cb, caster);
+}
+
+bool AdventureSpellMechanics::canBeCastAt(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
+{
+	return canBeCast(problem, cb, caster) && canBeCastAtImpl(problem, cb, caster, pos);
+}
+
+bool AdventureSpellMechanics::canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const
+{
+	return true;
+}
+
+bool AdventureSpellMechanics::canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
+{
+	return true;
+}
+
+bool AdventureSpellMechanics::adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	spells::detail::ProblemImpl problem;
+
+	if(!canBeCastAt(problem, env->getCb(), parameters.caster, parameters.pos))
+		return false;
+
+	ESpellCastResult result = beginCast(env, parameters);
+
+	if(result == ESpellCastResult::OK)
+		performCast(env, parameters);
+
+	return result != ESpellCastResult::ERROR;
+}
+
+ESpellCastResult AdventureSpellMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	if(owner->hasEffects())
+	{
+		//todo: cumulative effects support
+		const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
+
+		std::vector<Bonus> bonuses;
+
+		owner->getEffects(bonuses, schoolLevel, false, parameters.caster->getEnchantPower(owner));
+
+		for(const Bonus & b : bonuses)
+		{
+			GiveBonus gb;
+			gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId());
+			gb.bonus = b;
+			env->apply(gb);
+		}
+
+		return ESpellCastResult::OK;
+	}
+	else
+	{
+		//There is no generic algorithm of adventure cast
+		env->complain("Unimplemented adventure spell");
+		return ESpellCastResult::ERROR;
+	}
+}
+
+ESpellCastResult AdventureSpellMechanics::beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	return ESpellCastResult::OK;
+}
+
+void AdventureSpellMechanics::endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	// no-op, only for implementation in derived classes
+}
+
+void AdventureSpellMechanics::performCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	const auto level = parameters.caster->getSpellSchoolLevel(owner);
+	const auto cost = owner->getCost(level);
+
+	AdvmapSpellCast asc;
+	asc.casterID = ObjectInstanceID(parameters.caster->getCasterUnitId());
+	asc.spellID = owner->id;
+	env->apply(asc);
+
+	ESpellCastResult result = applyAdventureEffects(env, parameters);
+
+	switch(result)
+	{
+		case ESpellCastResult::OK:
+			parameters.caster->spendMana(env, cost);
+			endCast(env, parameters);
+			break;
+		default:
+			break;
+	}
+}
+
+VCMI_LIB_NAMESPACE_END

+ 46 - 0
lib/spells/adventure/AdventureSpellMechanics.h

@@ -0,0 +1,46 @@
+/*
+ * AdventureSpellMechanics.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 "../ISpellMechanics.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+enum class ESpellCastResult
+{
+	OK, // cast successful
+	CANCEL, // cast failed but it is not an error, no mana has been spent
+	PENDING,
+	ERROR // error occurred, for example invalid request from player
+};
+
+class AdventureSpellMechanics : public IAdventureSpellMechanics
+{
+public:
+	AdventureSpellMechanics(const CSpell * s);
+
+	bool canBeCast(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const final;
+	bool canBeCastAt(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const final;
+
+	bool adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override final;
+
+protected:
+	///actual adventure cast implementation
+	virtual ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
+	virtual ESpellCastResult beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
+	virtual void endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
+	virtual bool canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const;
+	virtual bool canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const;
+
+	void performCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 166 - 0
lib/spells/adventure/DimensionDoorMechanics.cpp

@@ -0,0 +1,166 @@
+/*
+ * DimensionDoorMechanics.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 "DimensionDoorMechanics.h"
+
+#include "../CSpellHandler.h"
+
+#include "../../IGameSettings.h"
+#include "../../callback/IGameInfoCallback.h"
+#include "../../mapObjects/CGHeroInstance.h"
+#include "../../mapping/TerrainTile.h"
+#include "../../networkPacks/PacksForClient.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+DimensionDoorMechanics::DimensionDoorMechanics(const CSpell * s)
+	: AdventureSpellMechanics(s)
+{
+}
+
+bool DimensionDoorMechanics::canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const
+{
+	if(!caster->getHeroCaster())
+		return false;
+
+	if(caster->getHeroCaster()->movementPointsRemaining() <= 0) //unlike town portal non-zero MP is enough
+	{
+		problem.add(MetaString::createFromTextID("core.genrltxt.125"));
+		return false;
+	}
+
+	const auto schoolLevel = caster->getSpellSchoolLevel(owner);
+
+	std::stringstream cachingStr;
+	cachingStr << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << owner->id.num;
+
+	int castsAlreadyPerformedThisTurn = caster->getHeroCaster()->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(owner->id)), cachingStr.str())->size();
+	int castsLimit = owner->getLevelPower(schoolLevel);
+
+	bool isTournamentRulesLimitEnabled = cb->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT);
+	if(isTournamentRulesLimitEnabled)
+	{
+		int3 mapSize = cb->getMapSize();
+
+		bool meetsTournamentRulesTwoCastsRequirements =  mapSize.x * mapSize.y * mapSize.z >= GameConstants::TOURNAMENT_RULES_DD_MAP_TILES_THRESHOLD && schoolLevel == MasteryLevel::EXPERT;
+
+		castsLimit = meetsTournamentRulesTwoCastsRequirements ? 2 : 1;
+	}
+
+	if(castsAlreadyPerformedThisTurn >= castsLimit) //limit casts per turn
+	{
+		MetaString message = MetaString::createFromTextID("core.genrltxt.338");
+		caster->getCasterName(message);
+		problem.add(std::move(message));
+		return false;
+	}
+
+	return true;
+}
+
+bool DimensionDoorMechanics::canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
+{
+	if(!cb->isInTheMap(pos))
+		return false;
+
+	if(cb->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES))
+	{
+		if(!cb->isVisibleFor(pos, caster->getCasterOwner()))
+			return false;
+	}
+
+	int3 casterPosition = caster->getHeroCaster()->getSightCenter();
+
+	const TerrainTile * dest = cb->getTileUnchecked(pos);
+	const TerrainTile * curr = cb->getTileUnchecked(casterPosition);
+
+	if(!dest)
+		return false;
+
+	if(!curr)
+		return false;
+
+	if(!isInScreenRange(casterPosition, pos))
+		return false;
+
+	if(cb->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE))
+	{
+		if(!dest->isClear(curr))
+			return false;
+	}
+	else
+	{
+		if(dest->blocked())
+			return false;
+	}
+
+	return true;
+}
+
+ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
+	const int baseCost = env->getCb()->getSettings().getInteger(EGameSettings::HEROES_MOVEMENT_COST_BASE);
+	const int movementCost = baseCost * ((schoolLevel >= 3) ? 2 : 3);
+
+	int3 casterPosition = parameters.caster->getHeroCaster()->getSightCenter();
+	const TerrainTile * dest = env->getCb()->getTile(parameters.pos);
+	const TerrainTile * curr = env->getCb()->getTile(casterPosition);
+
+	if(!dest->isClear(curr))
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->getCasterOwner();
+
+		// tile is either blocked or not possible to move (e.g. water <-> land)
+		if(env->getCb()->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS))
+		{
+			// SOD: DD to such "wrong" terrain results in mana and move points spending, but fails to move hero
+			iw.text = MetaString::createFromTextID("core.genrltxt.70"); // Dimension Door failed!
+			env->apply(iw);
+			// no return - resources will be spent
+		}
+		else
+		{
+			// HotA: game will show error message without taking mana or move points, even when DD into terra incognita
+			iw.text = MetaString::createFromTextID("vcmi.dimensionDoor.seaToLandError");
+			env->apply(iw);
+			return ESpellCastResult::CANCEL;
+		}
+	}
+
+	GiveBonus gb;
+	gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId());
+	gb.bonus = Bonus(BonusDuration::ONE_DAY, BonusType::NONE, BonusSource::SPELL_EFFECT, 0, BonusSourceID(owner->id));
+	env->apply(gb);
+
+	SetMovePoints smp;
+	smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());
+	if(movementCost < static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()))
+		smp.val = parameters.caster->getHeroCaster()->movementPointsRemaining() - movementCost;
+	else
+		smp.val = 0;
+	env->apply(smp);
+
+	return ESpellCastResult::OK;
+}
+
+void DimensionDoorMechanics::endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	int3 casterPosition = parameters.caster->getHeroCaster()->getSightCenter();
+	const TerrainTile * dest = env->getCb()->getTile(parameters.pos);
+	const TerrainTile * curr = env->getCb()->getTile(casterPosition);
+
+	if(dest->isClear(curr))
+		env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(parameters.pos), EMovementMode::DIMENSION_DOOR);
+}
+
+VCMI_LIB_NAMESPACE_END

+ 30 - 0
lib/spells/adventure/DimensionDoorMechanics.h

@@ -0,0 +1,30 @@
+/*
+ * DimensionDoorMechanics.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 "AdventureSpellMechanics.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class DimensionDoorMechanics final : public AdventureSpellMechanics
+{
+public:
+	DimensionDoorMechanics(const CSpell * s);
+
+protected:
+	bool canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const override;
+	bool canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const override;
+
+	ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
+	void endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 78 - 0
lib/spells/adventure/ScuttleBoatMechanics.cpp

@@ -0,0 +1,78 @@
+/*
+ * ScuttleBoatMechanics.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 "ScuttleBoatMechanics.h"
+
+#include "../CSpellHandler.h"
+
+#include "../../callback/IGameInfoCallback.h"
+#include "../../mapObjects/CGHeroInstance.h"
+#include "../../mapping/CMap.h"
+#include "../../networkPacks/PacksForClient.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+ScuttleBoatMechanics::ScuttleBoatMechanics(const CSpell * s)
+	: AdventureSpellMechanics(s)
+{
+}
+
+bool ScuttleBoatMechanics::canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
+{
+	if(!cb->isInTheMap(pos))
+		return false;
+
+	if(caster->getHeroCaster())
+	{
+		int3 casterPosition = caster->getHeroCaster()->getSightCenter();
+
+		if(!isInScreenRange(casterPosition, pos))
+			return false;
+	}
+
+	if(!cb->isVisibleFor(pos, caster->getCasterOwner()))
+		return false;
+
+	const TerrainTile * t = cb->getTile(pos);
+	if(!t || t->visitableObjects.empty())
+		return false;
+
+	const CGObjectInstance * topObject = cb->getObj(t->visitableObjects.back());
+	if(topObject->ID != Obj::BOAT)
+		return false;
+
+	return true;
+}
+
+ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
+	//check if spell works at all
+	if(env->getRNG()->nextInt(0, 99) >= owner->getLevelPower(schoolLevel)) //power is % chance of success
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->getCasterOwner();
+		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed
+		parameters.caster->getCasterName(iw.text);
+		env->apply(iw);
+		return ESpellCastResult::OK;
+	}
+
+	const TerrainTile & t = env->getMap()->getTile(parameters.pos);
+
+	RemoveObject ro;
+	ro.initiator = parameters.caster->getCasterOwner();
+	ro.objectID = t.visitableObjects.back();
+	env->apply(ro);
+	return ESpellCastResult::OK;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 28 - 0
lib/spells/adventure/ScuttleBoatMechanics.h

@@ -0,0 +1,28 @@
+/*
+ * ScuttleBoatMechanics.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 "AdventureSpellMechanics.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class ScuttleBoatMechanics final : public AdventureSpellMechanics
+{
+public:
+	ScuttleBoatMechanics(const CSpell * s);
+
+protected:
+	bool canBeCastAtImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const override;
+
+	ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 111 - 0
lib/spells/adventure/SummonBoatMechanics.cpp

@@ -0,0 +1,111 @@
+/*
+ * SummonBoatMechanics.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 "SummonBoatMechanics.h"
+
+#include "../CSpellHandler.h"
+
+#include "../../mapObjects/CGHeroInstance.h"
+#include "../../mapObjects/MiscObjects.h"
+#include "../../mapping/CMap.h"
+#include "../../networkPacks/PacksForClient.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+SummonBoatMechanics::SummonBoatMechanics(const CSpell * s)
+	: AdventureSpellMechanics(s)
+{
+}
+
+bool SummonBoatMechanics::canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const
+{
+	if(!caster->getHeroCaster())
+		return false;
+
+	if(caster->getHeroCaster()->inBoat())
+	{
+		MetaString message = MetaString::createFromTextID("core.genrltxt.333");
+		caster->getCasterName(message);
+		problem.add(std::move(message));
+		return false;
+	}
+
+	int3 summonPos = caster->getHeroCaster()->bestLocation();
+
+	if(summonPos.x < 0)
+	{
+		MetaString message = MetaString::createFromTextID("core.genrltxt.334");
+		caster->getCasterName(message);
+		problem.add(std::move(message));
+		return false;
+	}
+
+	return true;
+}
+
+ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
+
+	//check if spell works at all
+	if(env->getRNG()->nextInt(0, 99) >= owner->getLevelPower(schoolLevel)) //power is % chance of success
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->getCasterOwner();
+		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 336); //%s tried to summon a boat, but failed.
+		parameters.caster->getCasterName(iw.text);
+		env->apply(iw);
+		return ESpellCastResult::OK;
+	}
+
+	//try to find unoccupied boat to summon
+	const CGBoat * nearest = nullptr;
+	double dist = 0;
+	for(const auto & b : env->getMap()->getObjects<CGBoat>())
+	{
+		if(b->getBoardedHero() || b->layer != EPathfindingLayer::SAIL)
+			continue; //we're looking for unoccupied boat
+
+		double nDist = b->visitablePos().dist2d(parameters.caster->getHeroCaster()->visitablePos());
+		if(!nearest || nDist < dist) //it's first boat or closer than previous
+		{
+			nearest = b;
+			dist = nDist;
+		}
+	}
+
+	int3 summonPos = parameters.caster->getHeroCaster()->bestLocation();
+
+	if(nullptr != nearest) //we found boat to summon
+	{
+		ChangeObjPos cop;
+		cop.objid = nearest->id;
+		cop.nPos = summonPos;
+		cop.initiator = parameters.caster->getCasterOwner();
+		env->apply(cop);
+	}
+	else if(schoolLevel < 2) //none or basic level -> cannot create boat :(
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->getCasterOwner();
+		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 335); //There are no boats to summon.
+		env->apply(iw);
+		return ESpellCastResult::ERROR;
+	}
+	else //create boat
+	{
+		env->createBoat(summonPos, BoatId::NECROPOLIS, parameters.caster->getCasterOwner());
+	}
+	return ESpellCastResult::OK;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 28 - 0
lib/spells/adventure/SummonBoatMechanics.h

@@ -0,0 +1,28 @@
+/*
+ * SummonBoatMechanics.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 "AdventureSpellMechanics.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class SummonBoatMechanics final : public AdventureSpellMechanics
+{
+public:
+	SummonBoatMechanics(const CSpell * s);
+
+protected:
+	bool canBeCastImpl(spells::Problem & problem, const IGameInfoCallback * cb, const spells::Caster * caster) const override;
+
+	ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 299 - 0
lib/spells/adventure/TownPortalMechanics.cpp

@@ -0,0 +1,299 @@
+/*
+ * TownPortalMechanics.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 "TownPortalMechanics.h"
+
+#include "../CSpellHandler.h"
+
+#include "../../CPlayerState.h"
+#include "../../IGameSettings.h"
+#include "../../callback/IGameInfoCallback.h"
+#include "../../mapObjects/CGHeroInstance.h"
+#include "../../mapObjects/CGTownInstance.h"
+#include "../../mapping/CMap.h"
+#include "../../networkPacks/PacksForClient.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+TownPortalMechanics::TownPortalMechanics(const CSpell * s):
+	AdventureSpellMechanics(s)
+{
+}
+
+ESpellCastResult TownPortalMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	const CGTownInstance * destination = nullptr;
+	const int moveCost = movementCost(env, parameters);
+
+	if(!parameters.caster->getHeroCaster())
+	{
+		env->complain("Not a hero caster!");
+		return ESpellCastResult::ERROR;
+	}
+
+	if(parameters.caster->getSpellSchoolLevel(owner) < 2)
+	{
+		std::vector<const CGTownInstance *> pool = getPossibleTowns(env, parameters);
+		destination = findNearestTown(env, parameters, pool);
+
+		if(nullptr == destination)
+			return ESpellCastResult::ERROR;
+
+		if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < moveCost)
+			return ESpellCastResult::ERROR;
+
+		if(destination->getVisitingHero())
+		{
+			InfoWindow iw;
+			iw.player = parameters.caster->getCasterOwner();
+			iw.text.appendLocalString(EMetaText::GENERAL_TXT, 123);
+			env->apply(iw);
+			return ESpellCastResult::CANCEL;
+		}
+	}
+	else if(env->getMap()->isInTheMap(parameters.pos))
+	{
+		const TerrainTile & tile = env->getMap()->getTile(parameters.pos);
+
+		ObjectInstanceID topObjID = tile.topVisitableObj(false);
+		const CGObjectInstance * topObj = env->getMap()->getObject(topObjID);
+
+		if(!topObj)
+		{
+			env->complain("Destination tile is not visitable" + parameters.pos.toString());
+			return ESpellCastResult::ERROR;
+		}
+		else if(topObj->ID == Obj::HERO)
+		{
+			env->complain("Can't teleport to occupied town at " + parameters.pos.toString());
+			return ESpellCastResult::ERROR;
+		}
+		else if(topObj->ID != Obj::TOWN)
+		{
+			env->complain("No town at destination tile " + parameters.pos.toString());
+			return ESpellCastResult::ERROR;
+		}
+
+		destination = dynamic_cast<const CGTownInstance *>(topObj);
+
+		if(nullptr == destination)
+		{
+			env->complain("[Internal error] invalid town object at " + parameters.pos.toString());
+			return ESpellCastResult::ERROR;
+		}
+
+		const auto relations = env->getCb()->getPlayerRelations(destination->tempOwner, parameters.caster->getCasterOwner());
+
+		if(relations == PlayerRelations::ENEMIES)
+		{
+			env->complain("Can't teleport to enemy!");
+			return ESpellCastResult::ERROR;
+		}
+
+		if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < moveCost)
+		{
+			env->complain("This hero has not enough movement points!");
+			return ESpellCastResult::ERROR;
+		}
+
+		if(destination->getVisitingHero())
+		{
+			env->complain("[Internal error] Can't teleport to occupied town");
+			return ESpellCastResult::ERROR;
+		}
+	}
+	else
+	{
+		env->complain("Invalid destination tile");
+		return ESpellCastResult::ERROR;
+	}
+
+	const TerrainTile & from = env->getMap()->getTile(parameters.caster->getHeroCaster()->visitablePos());
+	const TerrainTile & dest = env->getMap()->getTile(destination->visitablePos());
+
+	if(!dest.entrableTerrain(&from))
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->getCasterOwner();
+		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 135);
+		env->apply(iw);
+		return ESpellCastResult::ERROR;
+	}
+
+	return ESpellCastResult::OK;
+}
+
+void TownPortalMechanics::endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	const int moveCost = movementCost(env, parameters);
+	const CGTownInstance * destination = nullptr;
+
+	if(parameters.caster->getSpellSchoolLevel(owner) < 2)
+	{
+		std::vector<const CGTownInstance *> pool = getPossibleTowns(env, parameters);
+		destination = findNearestTown(env, parameters, pool);
+	}
+	else
+	{
+		const TerrainTile & tile = env->getMap()->getTile(parameters.pos);
+		ObjectInstanceID topObjID = tile.topVisitableObj(false);
+		const CGObjectInstance * topObj = env->getMap()->getObject(topObjID);
+
+		destination = dynamic_cast<const CGTownInstance *>(topObj);
+	}
+
+	if(env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(destination->visitablePos()), EMovementMode::TOWN_PORTAL))
+	{
+		SetMovePoints smp;
+		smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());
+		smp.val = std::max<ui32>(0, parameters.caster->getHeroCaster()->movementPointsRemaining() - moveCost);
+		env->apply(smp);
+	}
+}
+
+ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	std::vector<const CGTownInstance *> towns = getPossibleTowns(env, parameters);
+
+	if(!parameters.caster->getHeroCaster())
+	{
+		env->complain("Not a hero caster!");
+		return ESpellCastResult::ERROR;
+	}
+
+	if(towns.empty())
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->getCasterOwner();
+		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 124);
+		env->apply(iw);
+		return ESpellCastResult::CANCEL;
+	}
+
+	const int moveCost = movementCost(env, parameters);
+
+	if(static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()) < moveCost)
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->getCasterOwner();
+		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 125);
+		env->apply(iw);
+		return ESpellCastResult::CANCEL;
+	}
+
+	if(!parameters.pos.isValid() && parameters.caster->getSpellSchoolLevel(owner) >= 2)
+	{
+		auto queryCallback = [this, env, parameters](std::optional<int32_t> reply) -> void
+		{
+			if(reply.has_value())
+			{
+				ObjectInstanceID townId(*reply);
+
+				const CGObjectInstance * o = env->getCb()->getObj(townId, true);
+				if(o == nullptr)
+				{
+					env->complain("Invalid object instance selected");
+					return;
+				}
+
+				if(!dynamic_cast<const CGTownInstance *>(o))
+				{
+					env->complain("Object instance is not town");
+					return;
+				}
+
+				AdventureSpellCastParameters p;
+				p.caster = parameters.caster;
+				p.pos = o->visitablePos();
+				performCast(env, p);
+			}
+		};
+
+		MapObjectSelectDialog request;
+
+		for(const auto * t : towns)
+		{
+			if(t->getVisitingHero() == nullptr) //empty town
+				request.objects.push_back(t->id);
+		}
+
+		if(request.objects.empty())
+		{
+			InfoWindow iw;
+			iw.player = parameters.caster->getCasterOwner();
+			iw.text.appendLocalString(EMetaText::GENERAL_TXT, 124);
+			env->apply(iw);
+			return ESpellCastResult::CANCEL;
+		}
+
+		request.player = parameters.caster->getCasterOwner();
+		request.title.appendLocalString(EMetaText::JK_TXT, 40);
+		request.description.appendLocalString(EMetaText::JK_TXT, 41);
+		request.icon = Component(ComponentType::SPELL, owner->id);
+
+		env->genericQuery(&request, request.player, queryCallback);
+
+		return ESpellCastResult::PENDING;
+	}
+
+	return ESpellCastResult::OK;
+}
+
+const CGTownInstance * TownPortalMechanics::findNearestTown(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const std::vector <const CGTownInstance *> & pool) const
+{
+	if(pool.empty())
+		return nullptr;
+
+	if(!parameters.caster->getHeroCaster())
+		return nullptr;
+
+	auto nearest = pool.cbegin(); //nearest town's iterator
+	si32 dist = (*nearest)->visitablePos().dist2dSQ(parameters.caster->getHeroCaster()->visitablePos());
+
+	for(auto i = nearest + 1; i != pool.cend(); ++i)
+	{
+		si32 curDist = (*i)->visitablePos().dist2dSQ(parameters.caster->getHeroCaster()->visitablePos());
+
+		if(curDist < dist)
+		{
+			nearest = i;
+			dist = curDist;
+		}
+	}
+	return *nearest;
+}
+
+std::vector<const CGTownInstance *> TownPortalMechanics::getPossibleTowns(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	std::vector<const CGTownInstance *> ret;
+
+	const TeamState * team = env->getCb()->getPlayerTeam(parameters.caster->getCasterOwner());
+
+	for(const auto & color : team->players)
+	{
+		for(auto currTown : env->getCb()->getPlayerState(color)->getTowns())
+		{
+			ret.push_back(currTown);
+		}
+	}
+	return ret;
+}
+
+int32_t TownPortalMechanics::movementCost(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	if(parameters.caster != parameters.caster->getHeroCaster()) //if caster is not hero
+		return 0;
+
+	int baseMovementCost = env->getCb()->getSettings().getInteger(EGameSettings::HEROES_MOVEMENT_COST_BASE);
+	return baseMovementCost * ((parameters.caster->getSpellSchoolLevel(owner) >= 3) ? 2 : 3);
+}
+
+VCMI_LIB_NAMESPACE_END

+ 35 - 0
lib/spells/adventure/TownPortalMechanics.h

@@ -0,0 +1,35 @@
+/*
+ * TownPortalMechanics.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 "AdventureSpellMechanics.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CGTownInstance;
+
+class TownPortalMechanics final : public AdventureSpellMechanics
+{
+public:
+	TownPortalMechanics(const CSpell * s);
+
+protected:
+	ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
+	ESpellCastResult beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
+	void endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
+
+private:
+	const CGTownInstance * findNearestTown(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const std::vector<const CGTownInstance *> & pool) const;
+	int32_t movementCost(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
+	std::vector<const CGTownInstance *> getPossibleTowns(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 90 - 0
lib/spells/adventure/ViewWorldMechanics.cpp

@@ -0,0 +1,90 @@
+/*
+ * ViewWorldMechanics.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 "ViewWorldMechanics.h"
+
+#include "../CSpellHandler.h"
+
+#include "../../CPlayerState.h"
+#include "../../callback/IGameInfoCallback.h"
+#include "../../mapObjects/CGHeroInstance.h"
+#include "../../mapping/CMap.h"
+#include "../../networkPacks/PacksForClient.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+ViewMechanics::ViewMechanics(const CSpell * s):
+	AdventureSpellMechanics(s)
+{
+}
+
+ESpellCastResult ViewMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	ShowWorldViewEx pack;
+
+	pack.player = parameters.caster->getCasterOwner();
+
+	const auto spellLevel = parameters.caster->getSpellSchoolLevel(owner);
+
+	const auto & fowMap = env->getCb()->getPlayerTeam(parameters.caster->getCasterOwner())->fogOfWarMap;
+
+	for(const auto & obj : env->getMap()->getObjects())
+	{
+		//deleted object remain as empty pointer
+		if(obj && filterObject(obj, spellLevel))
+		{
+			ObjectPosInfo posInfo(obj);
+
+			if(fowMap[posInfo.pos.z][posInfo.pos.x][posInfo.pos.y] == 0)
+				pack.objectPositions.push_back(posInfo);
+		}
+	}
+	pack.showTerrain = showTerrain(spellLevel);
+
+	env->apply(pack);
+
+	return ESpellCastResult::OK;
+}
+
+///ViewAirMechanics
+ViewAirMechanics::ViewAirMechanics(const CSpell * s)
+	: ViewMechanics(s)
+{
+}
+
+bool ViewAirMechanics::filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const
+{
+	return (obj->ID == Obj::ARTIFACT) || (spellLevel > 1 && obj->ID == Obj::HERO) || (spellLevel > 2 && obj->ID == Obj::TOWN);
+}
+
+bool ViewAirMechanics::showTerrain(const int32_t spellLevel) const
+{
+	return false;
+}
+
+///ViewEarthMechanics
+ViewEarthMechanics::ViewEarthMechanics(const CSpell * s)
+	: ViewMechanics(s)
+{
+}
+
+bool ViewEarthMechanics::filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const
+{
+	return (obj->ID == Obj::RESOURCE) || (spellLevel > 1 && obj->ID == Obj::MINE);
+}
+
+bool ViewEarthMechanics::showTerrain(const int32_t spellLevel) const
+{
+	return spellLevel > 2;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 48 - 0
lib/spells/adventure/ViewWorldMechanics.h

@@ -0,0 +1,48 @@
+/*
+ * ViewWorldMechanics.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 "AdventureSpellMechanics.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class ViewMechanics : public AdventureSpellMechanics
+{
+public:
+	ViewMechanics(const CSpell * s);
+
+protected:
+	ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
+	virtual bool filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const = 0;
+	virtual bool showTerrain(const int32_t spellLevel) const = 0;
+};
+
+class ViewAirMechanics final : public ViewMechanics
+{
+public:
+	ViewAirMechanics(const CSpell * s);
+
+protected:
+	bool filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const override;
+	bool showTerrain(const int32_t spellLevel) const override;
+};
+
+class ViewEarthMechanics final : public ViewMechanics
+{
+public:
+	ViewEarthMechanics(const CSpell * s);
+
+protected:
+	bool filterObject(const CGObjectInstance * obj, const int32_t spellLevel) const override;
+	bool showTerrain(const int32_t spellLevel) const override;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/texts/MetaString.cpp

@@ -11,7 +11,6 @@
 #include "MetaString.h"
 
 #include "CCreatureHandler.h"
-#include "CCreatureSet.h"
 #include "entities/artifact/CArtifact.h"
 #include "entities/faction/CFaction.h"
 #include "entities/hero/CHero.h"
@@ -19,6 +18,7 @@
 #include "CSkillHandler.h"
 #include "GameConstants.h"
 #include "GameLibrary.h"
+#include "mapObjects/army/CStackBasicDescriptor.h"
 #include "mapObjectConstructors/CObjectClassesHandler.h"
 #include "spells/CSpellHandler.h"
 #include "serializer/JsonSerializeFormat.h"

+ 1 - 0
mapeditor/inspector/armywidget.cpp

@@ -14,6 +14,7 @@
 #include "CCreatureHandler.h"
 
 #include "../../lib/GameLibrary.h"
+#include "../../lib/mapObjects/army/CStackInstance.h"
 
 ArmyWidget::ArmyWidget(CArmedInstance & a, QWidget *parent) :
 	QDialog(parent),

+ 1 - 1
mapeditor/inspector/armywidget.h

@@ -12,7 +12,7 @@
 #include "../StdInc.h"
 #include <QDialog>
 #include "baseinspectoritemdelegate.h"
-#include "../lib/mapObjects/CArmedInstance.h"
+#include "../lib/mapObjects/army/CArmedInstance.h"
 
 const int TOTAL_SLOTS = 7;
 

+ 1 - 0
mapeditor/inspector/towneventswidget.cpp

@@ -16,6 +16,7 @@
 #include "mapsettings/eventsettings.h"
 #include "../../lib/constants/NumericConstants.h"
 #include "../../lib/constants/StringConstants.h"
+#include "../../lib/mapping/CCastleEvent.h"
 
 TownEventsWidget::TownEventsWidget(CGTownInstance & town, QWidget * parent) :
 	QDialog(parent),

+ 0 - 1
mapeditor/inspector/towneventswidget.h

@@ -12,7 +12,6 @@
 #include "../StdInc.h"
 #include <QDialog>
 #include "baseinspectoritemdelegate.h"
-#include "../lib/mapping/CMapDefines.h"
 #include "../lib/mapObjects/CGTownInstance.h"
 #include "../mapcontroller.h"
 

+ 0 - 1
mapeditor/mapsettings/eventsettings.cpp

@@ -12,7 +12,6 @@
 #include "timedevent.h"
 #include "ui_eventsettings.h"
 #include "../mapcontroller.h"
-#include "../../lib/mapping/CMapDefines.h"
 #include "../../lib/constants/NumericConstants.h"
 #include "../../lib/constants/StringConstants.h"
 

+ 1 - 1
scripting/lua/api/StackInstance.h

@@ -14,7 +14,7 @@
 
 #include "../LuaWrapper.h"
 
-#include "../../../lib/CCreatureSet.h"
+#include "../../../lib/mapObjects/army/CStackInstance.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 0 - 1
server/CGameHandler.cpp

@@ -25,7 +25,6 @@
 
 #include "../lib/CConfigHandler.h"
 #include "../lib/CCreatureHandler.h"
-#include "../lib/CCreatureSet.h"
 #include "../lib/CPlayerState.h"
 #include "../lib/CSoundBase.h"
 #include "../lib/GameConstants.h"

+ 1 - 0
server/CVCMIServer.cpp

@@ -16,6 +16,7 @@
 #include "processors/PlayerMessageProcessor.h"
 
 #include "../lib/CThreadHelper.h"
+#include "../lib/GameLibrary.h"
 #include "../lib/campaign/CampaignState.h"
 #include "../lib/entities/hero/CHeroHandler.h"
 #include "../lib/entities/hero/CHeroClass.h"

+ 1 - 0
server/NetPacksLobbyServer.cpp

@@ -14,6 +14,7 @@
 #include "CGameHandler.h"
 
 #include "../lib/StartInfo.h"
+#include "../lib/GameLibrary.h"
 
 #include "../lib/CRandomGenerator.h"
 #include "../lib/campaign/CampaignState.h"

+ 1 - 0
server/battles/BattleResultProcessor.cpp

@@ -17,6 +17,7 @@
 #include "../queries/QueriesProcessor.h"
 #include "../queries/BattleQueries.h"
 
+#include "../../lib/GameLibrary.h"
 #include "../../lib/CStack.h"
 #include "../../lib/CPlayerState.h"
 #include "../../lib/IGameSettings.h"

+ 1 - 0
server/processors/HeroPoolProcessor.cpp

@@ -21,6 +21,7 @@
 #include "../../lib/entities/hero/CHero.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapping/TerrainTile.h"
 #include "../../lib/networkPacks/PacksForClient.h"
 #include "../../lib/gameState/CGameState.h"
 #include "../../lib/gameState/TavernHeroesPool.h"

+ 1 - 0
server/processors/NewTurnProcessor.cpp

@@ -27,6 +27,7 @@
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/mapObjects/IOwnableObject.h"
 #include "../../lib/mapping/CMap.h"
+#include "../../lib/mapping/CCastleEvent.h"
 #include "../../lib/networkPacks/PacksForClient.h"
 #include "../../lib/networkPacks/StackLocation.h"
 #include "../../lib/pathfinder/TurnInfo.h"

部分文件因为文件数量过多而无法显示