Browse Source

Split CCreatureSet file on .h/.cpp per class basis

Ivan Savenko 3 months ago
parent
commit
8721bdd728
47 changed files with 899 additions and 753 deletions
  1. 1 1
      client/Client.cpp
  2. 1 1
      client/GameChatHandler.cpp
  3. 0 1
      client/UIHelper.cpp
  4. 1 1
      client/adventureMap/CInGameConsole.cpp
  5. 0 1
      client/adventureMap/MapAudioPlayer.cpp
  6. 13 4
      lib/CMakeLists.txt
  7. 1 1
      lib/battle/BattleLayout.cpp
  8. 0 1
      lib/battle/BattleStateInfoForRetreat.cpp
  9. 1 0
      lib/battle/DamageCalculator.cpp
  10. 0 1
      lib/bonuses/Bonus.cpp
  11. 0 1
      lib/bonuses/Limiters.cpp
  12. 2 0
      lib/gameState/EVictoryLossCheckResult.h
  13. 1 1
      lib/gameState/InfoAboutArmy.h
  14. 1 1
      lib/json/JsonRandom.cpp
  15. 1 0
      lib/mapObjectConstructors/DwellingInstanceConstructor.cpp
  16. 1 1
      lib/mapObjects/CGCreature.h
  17. 2 1
      lib/mapObjects/CGDwelling.h
  18. 3 1
      lib/mapObjects/CGHeroInstance.h
  19. 1 1
      lib/mapObjects/CGResource.h
  20. 1 1
      lib/mapObjects/CGTownInstance.h
  21. 2 1
      lib/mapObjects/CRewardableObject.h
  22. 0 1
      lib/mapObjects/MapObjects.h
  23. 2 1
      lib/mapObjects/MiscObjects.h
  24. 8 10
      lib/mapObjects/army/CArmedInstance.cpp
  25. 8 4
      lib/mapObjects/army/CArmedInstance.h
  26. 79 0
      lib/mapObjects/army/CCommanderInstance.cpp
  27. 51 0
      lib/mapObjects/army/CCommanderInstance.h
  28. 5 495
      lib/mapObjects/army/CCreatureSet.cpp
  29. 4 213
      lib/mapObjects/army/CCreatureSet.h
  30. 54 0
      lib/mapObjects/army/CSimpleArmy.h
  31. 90 0
      lib/mapObjects/army/CStackBasicDescriptor.cpp
  32. 64 0
      lib/mapObjects/army/CStackBasicDescriptor.h
  33. 356 0
      lib/mapObjects/army/CStackInstance.cpp
  34. 130 0
      lib/mapObjects/army/CStackInstance.h
  35. 2 1
      lib/networkPacks/PacksForClient.h
  36. 1 0
      lib/networkPacks/PacksForClientBattle.h
  37. 3 2
      lib/rewardable/Info.cpp
  38. 2 0
      lib/rewardable/Interface.h
  39. 1 0
      lib/rewardable/Limiter.h
  40. 0 1
      lib/rewardable/Reward.h
  41. 1 0
      lib/rmg/modificators/ObjectManager.cpp
  42. 1 1
      lib/texts/MetaString.cpp
  43. 1 0
      mapeditor/inspector/armywidget.cpp
  44. 1 1
      mapeditor/inspector/armywidget.h
  45. 1 1
      scripting/lua/api/StackInstance.h
  46. 0 1
      server/CGameHandler.cpp
  47. 1 0
      server/queries/MapQueries.h

+ 1 - 1
client/Client.cpp

@@ -36,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"

+ 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/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()

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

+ 13 - 4
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
@@ -299,7 +304,6 @@ set(lib_MAIN_SRCS
 	CAndroidVMHelper.cpp
 	CBonusTypeHandler.cpp
 	CCreatureHandler.cpp
-	CCreatureSet.cpp
 	CPlayerState.cpp
 	CRandomGenerator.cpp
 	CScriptingModule.cpp
@@ -564,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
@@ -587,6 +590,13 @@ 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/CCastleEvent.h
 	mapping/CMapEditManager.h
@@ -758,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"

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

+ 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

+ 8 - 10
lib/mapObjects/CArmedInstance.cpp → lib/mapObjects/army/CArmedInstance.cpp

@@ -11,16 +11,14 @@
 #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 "../mapping/TerrainTile.h"
-#include "../GameLibrary.h"
-#include "../gameState/CGameState.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
 

+ 8 - 4
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/CBonusSystemNode.h"
+#include "../../bonuses/BonusCache.h"
+
+#include <vcmi/Entity.h>
 
 VCMI_LIB_NAMESPACE_BEGIN
 

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

@@ -0,0 +1,79 @@
+/*
+ * 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

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

@@ -0,0 +1,51 @@
+/*
+ * 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

+ 5 - 495
lib/CCreatureSet.cpp → lib/mapObjects/army/CCreatureSet.cpp

@@ -10,22 +10,11 @@
 #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"
+
+#include "../../CConfigHandler.h"
+#include "../../texts/CGeneralTextHandler.h"
+#include "../../serializer/JsonSerializeFormat.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -707,483 +696,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

+ 4 - 213
lib/CCreatureSet.h → lib/mapObjects/army/CCreatureSet.h

@@ -9,205 +9,18 @@
  */
 #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 "CSimpleArmy.h"
 
-#include <vcmi/Entity.h>
+#include "serializer/Serializeable.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class JsonNode;
-class CCreature;
-class CGHeroInstance;
+class CStackInstance;
 class CArmedInstance;
-class CCreatureArtifactSet;
+class CStackBasicDescriptor;
 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>;
@@ -219,28 +32,6 @@ struct DLL_LINKAGE CreatureSlotComparer
 
 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" };

+ 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

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

@@ -0,0 +1,90 @@
+/*
+ * 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

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

@@ -0,0 +1,64 @@
+/*
+ * 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

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

@@ -0,0 +1,356 @@
+/*
+ * 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

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

@@ -0,0 +1,130 @@
+/*
+ * 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 "mapObjects/CGObjectInstance.h"
+#include "entities/artifact/CArtifactSet.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

+ 2 - 1
lib/networkPacks/PacksForClient.h

@@ -17,15 +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 "../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;

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

+ 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 - 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 - 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/queries/MapQueries.h

@@ -16,6 +16,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 class CGHeroInstance;
 class CGObjectInstance;
 class IObjectInterface;
+class CArmedInstance;
 VCMI_LIB_NAMESPACE_END
 
 //Created when player starts turn or when player puts game on [ause