Selaa lähdekoodia

Converted Component class to use VariantIdentifier instead of int

Ivan Savenko 2 vuotta sitten
vanhempi
sitoutus
10e50548e7

+ 1 - 1
AI/Nullkiller/AIGateway.cpp

@@ -664,7 +664,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
 		// TODO: Find better way to understand it is Chest of Treasures
 		if(hero.validAndSet()
 			&& components.size() == 2
-			&& components.front().id == Component::EComponentType::RESOURCE
+			&& components.front().type == ComponentType::RESOURCE
 			&& (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN
 			|| nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE))
 		{

+ 4 - 4
client/CPlayerInterface.cpp

@@ -293,7 +293,7 @@ void CPlayerInterface::yourTurn(QueryID queryID)
 			std::string msg = CGI->generaltexth->allTexts[13];
 			boost::replace_first(msg, "%s", cb->getStartInfo()->playerInfos.find(playerID)->second.name);
 			std::vector<std::shared_ptr<CComponent>> cmp;
-			cmp.push_back(std::make_shared<CComponent>(CComponent::flag, playerID.getNum(), 0));
+			cmp.push_back(std::make_shared<CComponent>(ComponentType::FLAG, playerID));
 			showInfoDialog(msg, cmp);
 		}
 		else
@@ -326,7 +326,7 @@ void CPlayerInterface::acceptTurn(QueryID queryID)
 		auto playerColor = *cb->getPlayerID();
 
 		std::vector<Component> components;
-		components.emplace_back(Component::EComponentType::FLAG, playerColor.getNum(), 0, 0);
+		components.emplace_back(ComponentType::FLAG, playerColor);
 		MetaString text;
 
 		const auto & optDaysWithoutCastle = cb->getPlayerState(playerColor)->daysWithoutCastle;
@@ -1228,7 +1228,7 @@ void CPlayerInterface::showArtifactAssemblyDialog(const Artifact * artifact, con
 		text += boost::str(boost::format(CGI->generaltexth->allTexts[732]) % assembledArtifact->getNameTranslated());
 
 		// Picture of assembled artifact at bottom.
-		auto sc = std::make_shared<CComponent>(CComponent::artifact, assembledArtifact->getIndex(), 0);
+		auto sc = std::make_shared<CComponent>(ComponentType::ARTIFACT, assembledArtifact->getId());
 		scs.push_back(sc);
 	}
 	else
@@ -1441,7 +1441,7 @@ void CPlayerInterface::playerBlocked(int reason, bool start)
 			std::string msg = CGI->generaltexth->translate("vcmi.adventureMap.playerAttacked");
 			boost::replace_first(msg, "%s", cb->getStartInfo()->playerInfos.find(playerID)->second.name);
 			std::vector<std::shared_ptr<CComponent>> cmp;
-			cmp.push_back(std::make_shared<CComponent>(CComponent::flag, playerID.getNum(), 0));
+			cmp.push_back(std::make_shared<CComponent>(ComponentType::FLAG, playerID));
 			makingTurn = true; //workaround for stiff showInfoDialog implementation
 			showInfoDialog(msg, cmp);
 			makingTurn = false;

+ 17 - 13
client/adventureMap/CInfoBar.cpp

@@ -376,47 +376,51 @@ void CInfoBar::pushComponents(const std::vector<Component> & components, std::st
 		std::array<std::pair<std::vector<Component>, int>, 10> reward_map;
 		for(const auto & c : components)
 		{
-			switch(c.id)
+			switch(c.type)
 			{
-				case Component::EComponentType::PRIM_SKILL:
-				case Component::EComponentType::EXPERIENCE: 
+				case ComponentType::PRIM_SKILL:
+				case ComponentType::EXPERIENCE:
+				case ComponentType::LEVEL:
+				case ComponentType::MANA:
 					reward_map.at(0).first.push_back(c);
 					reward_map.at(0).second = 8; //At most 8, cannot be more
 					break;
-				case Component::EComponentType::SEC_SKILL:
+				case ComponentType::SEC_SKILL:
 					reward_map.at(1).first.push_back(c);
 					reward_map.at(1).second = 4; //At most 4
 					break;
-				case Component::EComponentType::SPELL: 
+				case ComponentType::SPELL:
 					reward_map.at(2).first.push_back(c);
 					reward_map.at(2).second = 4; //At most 4
 					break;
-				case Component::EComponentType::ARTIFACT:
+				case ComponentType::ARTIFACT:
+				case ComponentType::SPELL_SCROLL:
 					reward_map.at(3).first.push_back(c);
 					reward_map.at(3).second = 4; //At most 4, too long names
 					break;
-				case Component::EComponentType::CREATURE:
+				case ComponentType::CREATURE:
 					reward_map.at(4).first.push_back(c);
 					reward_map.at(4).second = 4; //At most 4, too long names
 					break;
-				case Component::EComponentType::RESOURCE:
+				case ComponentType::RESOURCE:
+				case ComponentType::RESOURCE_PER_DAY:
 					reward_map.at(5).first.push_back(c);
 					reward_map.at(5).second = 7; //At most 7
 					break;
-				case Component::EComponentType::MORALE: 
-				case Component::EComponentType::LUCK:
+				case ComponentType::MORALE:
+				case ComponentType::LUCK:
 					reward_map.at(6).first.push_back(c);
 					reward_map.at(6).second = 2; //At most 2 - 1 for morale + 1 for luck
 					break;
-				case Component::EComponentType::BUILDING:
+				case ComponentType::BUILDING:
 					reward_map.at(7).first.push_back(c);
 					reward_map.at(7).second = 1; //At most 1 - only large icons available AFAIK
 					break;
-				case Component::EComponentType::HERO_PORTRAIT:
+				case ComponentType::HERO_PORTRAIT:
 					reward_map.at(8).first.push_back(c);
 					reward_map.at(8).second = 1; //I do not think than we even can get more than 1 hero
 					break;
-				case Component::EComponentType::FLAG:
+				case ComponentType::FLAG:
 					reward_map.at(9).first.push_back(c);
 					reward_map.at(9).second = 1; //I do not think than we even can get more than 1 player in notification
 					break;

+ 1 - 1
client/lobby/OptionsTab.cpp

@@ -571,7 +571,7 @@ void OptionsTab::CPlayerOptionTooltipBox::genTownWindow()
 	for(auto & elem : town->creatures)
 	{
 		if(!elem.empty())
-			components.push_back(std::make_shared<CComponent>(CComponent::creature, elem.front(), 0, CComponent::tiny));
+			components.push_back(std::make_shared<CComponent>(ComponentType::CREATURE, elem.front(), 0, CComponent::tiny));
 	}
 	boxAssociatedCreatures = std::make_shared<CComponentBox>(components, Rect(10, 140, pos.w - 20, 140));
 }

+ 6 - 7
client/widgets/CArtifactHolder.cpp

@@ -49,7 +49,7 @@ void CArtPlace::setInternals(const CArtifactInstance * artInst)
 		if(settings["general"]["enableUiEnhancements"].Bool())
 		{
 			imageIndex = spellID.num;
-			if(baseType != CComponent::spell)
+			if(component.type != ComponentType::SPELL_SCROLL)
 			{
 				image->setScale(Point(pos.w, 34));
 				image->setAnimationPath(AnimationPath::builtin("spellscr"), imageIndex);
@@ -57,21 +57,20 @@ void CArtPlace::setInternals(const CArtifactInstance * artInst)
 			}
 		}
 		// Add spell component info (used to provide a pic in r-click popup)
-		baseType = CComponent::spell;
-		type = spellID;
+		component.type = ComponentType::SPELL_SCROLL;
+		component.subType = spellID;
 	}
 	else
 	{
-		if(settings["general"]["enableUiEnhancements"].Bool() && baseType != CComponent::artifact)
+		if(settings["general"]["enableUiEnhancements"].Bool() && component.type != ComponentType::ARTIFACT)
 		{
 			image->setScale(Point());
 			image->setAnimationPath(AnimationPath::builtin("artifact"), imageIndex);
 			image->moveTo(Point(pos.x, pos.y));
 		}
-		baseType = CComponent::artifact;
-		type = artInst->getTypeId();
+		component.type = ComponentType::ARTIFACT;
+		component.subType = artInst->getTypeId();
 	}
-	bonusValue = 0;
 	image->enable();
 	text = artInst->getDescription();
 }

+ 179 - 129
client/widgets/CComponent.cpp

@@ -39,41 +39,35 @@
 #include "../../lib/CArtHandler.h"
 #include "../../lib/CArtifactInstance.h"
 
-CComponent::CComponent(Etype Type, int Subtype, int Val, ESize imageSize, EFonts font):
-	perDay(false)
+CComponent::CComponent(ComponentType Type, ComponentSubType Subtype, std::optional<int32_t> Val, ESize imageSize, EFonts font)
 {
 	init(Type, Subtype, Val, imageSize, font, "");
 }
 
-CComponent::CComponent(Etype Type, int Subtype, std::string Val, ESize imageSize, EFonts font):
-	perDay(false)
+CComponent::CComponent(ComponentType Type, ComponentSubType Subtype, std::string Val, ESize imageSize, EFonts font)
 {
-	init(Type, Subtype, 0, imageSize, font, Val);
+	init(Type, Subtype, std::nullopt, imageSize, font, Val);
 }
 
 CComponent::CComponent(const Component & c, ESize imageSize, EFonts font)
-	: perDay(false)
 {
-	if(c.id == Component::EComponentType::RESOURCE && c.when==-1)
-		perDay = true;
-
-	init((Etype)c.id, c.subtype, c.val, imageSize, font);
+	init(c.type, c.subType, c.value, imageSize, font);
 }
 
-void CComponent::init(Etype Type, int Subtype, int Val, ESize imageSize, EFonts fnt, std::string ValText)
+void CComponent::init(ComponentType Type, ComponentSubType Subtype, std::optional<int32_t> Val, ESize imageSize, EFonts fnt, std::string ValText)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
 	addUsedEvents(SHOW_POPUP);
 
-	compType = Type;
-	subtype = Subtype;
-	val = Val;
-	valText = ValText;
+	data.type = Type;
+	data.subType = Subtype;
+	data.value = Val;
+
+	customSubtitle = ValText;
 	size = imageSize;
 	font = fnt;
 
-	assert(compType < typeInvalid);
 	assert(size < sizeInvalid);
 
 	setSurface(getFileName()[size], (int)getIndex());
@@ -94,7 +88,7 @@ void CComponent::init(Etype Type, int Subtype, int Val, ESize imageSize, EFonts
 	if (size < small)
 		max = 30;
 
-	if(Type == Etype::resource && !valText.empty())
+	if(Type == ComponentType::RESOURCE && !ValText.empty())
 		max = 80;
 
 	std::vector<std::string> textLines = CMessage::breakText(getSubtitle(), std::max<int>(max, pos.w), font);
@@ -115,151 +109,207 @@ void CComponent::init(Etype Type, int Subtype, int Val, ESize imageSize, EFonts
 
 std::vector<AnimationPath> CComponent::getFileName()
 {
-	static const std::string  primSkillsArr [] = {"PSKIL32",        "PSKIL32",        "PSKIL42",        "PSKILL"};
-	static const std::string  secSkillsArr [] =  {"SECSK32",        "SECSK32",        "SECSKILL",       "SECSK82"};
-	static const std::string  resourceArr [] =   {"SMALRES",        "RESOURCE",       "RESOURCE",       "RESOUR82"};
-	static const std::string  creatureArr [] =   {"CPRSMALL",       "CPRSMALL",       "CPRSMALL",       "TWCRPORT"};
-	static const std::string  artifactArr[]  =   {"Artifact",       "Artifact",       "Artifact",       "Artifact"};
-	static const std::string  spellsArr [] =     {"SpellInt",       "SpellInt",       "SpellInt",       "SPELLSCR"};
-	static const std::string  moraleArr [] =     {"IMRL22",         "IMRL30",         "IMRL42",         "imrl82"};
-	static const std::string  luckArr [] =       {"ILCK22",         "ILCK30",         "ILCK42",         "ilck82"};
-	static const std::string  heroArr [] =       {"PortraitsSmall", "PortraitsSmall", "PortraitsSmall", "PortraitsLarge"};
-	static const std::string  flagArr [] =       {"CREST58",        "CREST58",        "CREST58",        "CREST58"};
-
-	auto gen = [](const std::string * arr) -> std::vector<AnimationPath>
+	static const std::array<std::string, 4>  primSkillsArr = {"PSKIL32",        "PSKIL32",        "PSKIL42",        "PSKILL"};
+	static const std::array<std::string, 4>  secSkillsArr =  {"SECSK32",        "SECSK32",        "SECSKILL",       "SECSK82"};
+	static const std::array<std::string, 4>  resourceArr =   {"SMALRES",        "RESOURCE",       "RESOURCE",       "RESOUR82"};
+	static const std::array<std::string, 4>  creatureArr =   {"CPRSMALL",       "CPRSMALL",       "CPRSMALL",       "TWCRPORT"};
+	static const std::array<std::string, 4>  artifactArr =   {"Artifact",       "Artifact",       "Artifact",       "Artifact"};
+	static const std::array<std::string, 4>  spellsArr =     {"SpellInt",       "SpellInt",       "SpellInt",       "SPELLSCR"};
+	static const std::array<std::string, 4>  moraleArr =     {"IMRL22",         "IMRL30",         "IMRL42",         "imrl82"};
+	static const std::array<std::string, 4>  luckArr =       {"ILCK22",         "ILCK30",         "ILCK42",         "ilck82"};
+	static const std::array<std::string, 4>  heroArr =       {"PortraitsSmall", "PortraitsSmall", "PortraitsSmall", "PortraitsLarge"};
+	static const std::array<std::string, 4>  flagArr =       {"CREST58",        "CREST58",        "CREST58",        "CREST58"};
+
+	auto gen = [](const std::array<std::string, 4> & arr) -> std::vector<AnimationPath>
 	{
 		return { AnimationPath::builtin(arr[0]), AnimationPath::builtin(arr[1]), AnimationPath::builtin(arr[2]), AnimationPath::builtin(arr[3]) };
 	};
 
-	switch(compType)
+	switch(data.type)
 	{
-	case primskill:  return gen(primSkillsArr);
-	case secskill:   return gen(secSkillsArr);
-	case resource:   return gen(resourceArr);
-	case creature:   return gen(creatureArr);
-	case artifact:   return gen(artifactArr);
-	case experience: return gen(primSkillsArr);
-	case spell:      return gen(spellsArr);
-	case morale:     return gen(moraleArr);
-	case luck:       return gen(luckArr);
-	case building:   return std::vector<AnimationPath>(4, (*CGI->townh)[subtype]->town->clientInfo.buildingsIcons);
-	case hero:       return gen(heroArr);
-	case flag:       return gen(flagArr);
+		case ComponentType::PRIM_SKILL:
+		case ComponentType::EXPERIENCE:
+		case ComponentType::MANA:
+		case ComponentType::LEVEL:
+			return gen(primSkillsArr);
+		case ComponentType::SEC_SKILL:
+			return gen(secSkillsArr);
+		case ComponentType::RESOURCE:
+		case ComponentType::RESOURCE_PER_DAY:
+			return gen(resourceArr);
+		case ComponentType::CREATURE:
+			return gen(creatureArr);
+		case ComponentType::ARTIFACT:
+			return gen(artifactArr);
+		case ComponentType::SPELL_SCROLL:
+		case ComponentType::SPELL:
+			return gen(spellsArr);
+		case ComponentType::MORALE:
+			return gen(moraleArr);
+		case ComponentType::LUCK:
+			return gen(luckArr);
+		case ComponentType::BUILDING:
+			return std::vector<AnimationPath>(4, (*CGI->townh)[data.subType.as<BuildingTypeUniqueID>().getFaction()]->town->clientInfo.buildingsIcons);
+		case ComponentType::HERO_PORTRAIT:
+			return gen(heroArr);
+		case ComponentType::FLAG:
+			return gen(flagArr);
+		default:
+			assert(0);
+			return {};
 	}
-	assert(0);
-	return {};
 }
 
 size_t CComponent::getIndex()
 {
-	switch(compType)
+	switch(data.type)
 	{
-	case primskill:  return subtype;
-	case secskill:   return subtype*3 + 3 + val - 1;
-	case resource:   return subtype;
-	case creature:   return CGI->creatures()->getByIndex(subtype)->getIconIndex();
-	case artifact:   return CGI->artifacts()->getByIndex(subtype)->getIconIndex();
-	case experience: return 4;
-	case spell:      return (size < large) ? subtype + 1 : subtype;
-	case morale:     return val+3;
-	case luck:       return val+3;
-	case building:   return val;
-	case hero:       return CGI->heroTypes()->getByIndex(subtype)->getIconIndex();
-	case flag:       return subtype;
+		case ComponentType::PRIM_SKILL:
+			return data.subType.getNum();
+		case ComponentType::EXPERIENCE:
+		case ComponentType::LEVEL:
+			return 4; // for whatever reason, in H3 experience icon is located in primary skills icons
+		case ComponentType::MANA:
+			return 5; // for whatever reason, in H3 mana points icon is located in primary skills icons
+		case ComponentType::SEC_SKILL:
+			return data.subType.getNum() * 3 + 3 + data.value.value_or(0) - 1;
+		case ComponentType::RESOURCE:
+		case ComponentType::RESOURCE_PER_DAY:
+			return data.subType.getNum();
+		case ComponentType::CREATURE:
+			return CGI->creatures()->getById(data.subType.as<CreatureID>())->getIconIndex();
+		case ComponentType::ARTIFACT:
+			return CGI->artifacts()->getById(data.subType.as<ArtifactID>())->getIconIndex();
+		case ComponentType::SPELL_SCROLL:
+		case ComponentType::SPELL:
+			return (size < large) ? data.subType.getNum() + 1 : data.subType.getNum();
+		case ComponentType::MORALE:
+			return data.value.value_or(0) + 3;
+		case ComponentType::LUCK:
+			return data.value.value_or(0) + 3;
+		case ComponentType::BUILDING:
+			return data.subType.as<BuildingTypeUniqueID>().getBuilding();
+		case ComponentType::HERO_PORTRAIT:
+			return CGI->heroTypes()->getById(data.subType.as<HeroTypeID>())->getIconIndex();
+		case ComponentType::FLAG:
+			return data.subType.getNum();
+		default:
+			assert(0);
+			return 0;
 	}
-	assert(0);
-	return 0;
 }
 
 std::string CComponent::getDescription()
 {
-	switch(compType)
-	{
-	case primskill:  return (subtype < 4)? CGI->generaltexth->arraytxt[2+subtype] //Primary skill
-										 : CGI->generaltexth->allTexts[149]; //mana
-	case secskill:   return CGI->skillh->getByIndex(subtype)->getDescriptionTranslated(val);
-	case resource:   return CGI->generaltexth->allTexts[242];
-	case creature:   return "";
-	case artifact:
+	switch(data.type)
 	{
-		auto artID = ArtifactID(subtype);
-		auto description = VLC->arth->objects[artID]->getDescriptionTranslated();
-		if(artID == ArtifactID::SPELL_SCROLL)
+		case ComponentType::PRIM_SKILL:
+			return CGI->generaltexth->arraytxt[2+data.subType.getNum()];
+		case ComponentType::EXPERIENCE:
+		case ComponentType::LEVEL:
+			return CGI->generaltexth->allTexts[241];
+		case ComponentType::MANA:
+			return CGI->generaltexth->allTexts[149];
+		case ComponentType::SEC_SKILL:
+			return CGI->skillh->getByIndex(data.subType.getNum())->getDescriptionTranslated(data.value.value_or(0));
+		case ComponentType::RESOURCE:
+		case ComponentType::RESOURCE_PER_DAY:
+			return CGI->generaltexth->allTexts[242];
+		case ComponentType::CREATURE:
+			return "";
+		case ComponentType::ARTIFACT:
+			return VLC->artifacts()->getById(data.subType.as<ArtifactID>())->getDescriptionTranslated();
+		case ComponentType::SPELL_SCROLL:
 		{
-			ArtifactUtils::insertScrrollSpellName(description, SpellID(val));
+			auto description = VLC->arth->objects[ArtifactID::SPELL_SCROLL]->getDescriptionTranslated();
+			ArtifactUtils::insertScrrollSpellName(description, data.subType.as<SpellID>());
+			return description;
 		}
-		return description;
-	}
-	case experience: return CGI->generaltexth->allTexts[241];
-	case spell:      return (*CGI->spellh)[subtype]->getDescriptionTranslated(val);
-	case morale:     return CGI->generaltexth->heroscrn[ 4 - (val>0) + (val<0)];
-	case luck:       return CGI->generaltexth->heroscrn[ 7 - (val>0) + (val<0)];
-	case building:   return (*CGI->townh)[subtype]->town->buildings[BuildingID(val)]->getDescriptionTranslated();
-	case hero:       return "";
-	case flag:       return "";
+		case ComponentType::SPELL:
+			return VLC->spells()->getById(data.subType.as<SpellID>())->getDescriptionTranslated(data.value.value_or(0));
+		case ComponentType::MORALE:
+			return CGI->generaltexth->heroscrn[ 4 - (data.value.value_or(0)>0) + (data.value.value_or(0)<0)];
+		case ComponentType::LUCK:
+			return CGI->generaltexth->heroscrn[ 7 - (data.value.value_or(0)>0) + (data.value.value_or(0)<0)];
+		case ComponentType::BUILDING:
+		{
+			auto index = data.subType.as<BuildingTypeUniqueID>();
+			return (*CGI->townh)[index.getFaction()]->town->buildings[index.getBuilding()]->getDescriptionTranslated();
+		}
+		case ComponentType::HERO_PORTRAIT:
+			return "";
+		case ComponentType::FLAG:
+			return "";
+		default:
+			assert(0);
+			return 0;
 	}
-	assert(0);
-	return "";
 }
 
 std::string CComponent::getSubtitle()
 {
-	if(!perDay)
-		return getSubtitleInternal();
-
-	std::string ret = CGI->generaltexth->allTexts[3];
-	boost::replace_first(ret, "%d", getSubtitleInternal());
-	return ret;
-}
+	if (!customSubtitle.empty())
+		return customSubtitle;
 
-std::string CComponent::getSubtitleInternal()
-{
-	//FIXME: some of these are horrible (e.g creature)
-	switch(compType)
+	switch(data.type)
 	{
-	case primskill:  return boost::str(boost::format("%+d %s") % val % (subtype < 4 ? CGI->generaltexth->primarySkillNames[subtype] : CGI->generaltexth->allTexts[387]));
-	case secskill:   return CGI->generaltexth->levels[val-1] + "\n" + CGI->skillh->getByIndex(subtype)->getNameTranslated();
-	case resource:   return valText.empty() ? std::to_string(val) : valText;
-	case creature:
-		{
-			auto creature = CGI->creh->getByIndex(subtype);
-			if ( val )
-				return std::to_string(val) + " " + (val > 1 ? creature->getNamePluralTranslated() : creature->getNameSingularTranslated());
+		case ComponentType::PRIM_SKILL:
+			if (data.value)
+				return boost::str(boost::format("%+d %s") % data.value.value_or(0) % CGI->generaltexth->primarySkillNames[data.subType.getNum()]);
 			else
-				return val > 1 ? creature->getNamePluralTranslated() : creature->getNameSingularTranslated();
+				return CGI->generaltexth->primarySkillNames[data.subType.getNum()];
+		case ComponentType::EXPERIENCE:
+			return std::to_string(data.value.value_or(0));
+		case ComponentType::LEVEL:
+		{
+			std::string level = CGI->generaltexth->allTexts[442];
+			boost::replace_first(level, "1", std::to_string(data.value.value_or(0)));
+			return level;
 		}
-	case artifact:   return CGI->artifacts()->getByIndex(subtype)->getNameTranslated();
-	case experience:
+		case ComponentType::MANA:
+			return boost::str(boost::format("%+d %s") % data.value.value_or(0) % CGI->generaltexth->allTexts[387]);
+		case ComponentType::SEC_SKILL:
+			return CGI->generaltexth->levels[data.value.value_or(0)-1] + "\n" + CGI->skillh->getById(data.subType.as<SecondarySkill>())->getNameTranslated();
+		case ComponentType::RESOURCE:
+			return std::to_string(data.value.value_or(0));
+		case ComponentType::RESOURCE_PER_DAY:
+			return boost::str(boost::format(CGI->generaltexth->allTexts[387]) % data.value.value_or(0));
+		case ComponentType::CREATURE:
 		{
-			if(subtype == 1) //+1 level - tree of knowledge
-			{
-				std::string level = CGI->generaltexth->allTexts[442];
-				boost::replace_first(level, "1", std::to_string(val));
-				return level;
-			}
+			auto creature = CGI->creh->getById(data.subType.as<CreatureID>());
+			if ( data.value.value_or(0) )
+				return std::to_string(data.value.value_or(0)) + " " + (data.value.value_or(0) > 1 ? creature->getNamePluralTranslated() : creature->getNameSingularTranslated());
 			else
-			{
-				return std::to_string(val); //amount of experience OR level required for seer hut;
-			}
+				return data.value.value_or(0) > 1 ? creature->getNamePluralTranslated() : creature->getNameSingularTranslated();
 		}
-	case spell:      return CGI->spells()->getByIndex(subtype)->getNameTranslated();
-	case morale:     return "";
-	case luck:       return "";
-	case building:
-		{
-			auto building = (*CGI->townh)[subtype]->town->buildings[BuildingID(val)];
-			if(!building)
+		case ComponentType::ARTIFACT:
+			return CGI->artifacts()->getById(data.subType.as<ArtifactID>())->getNameTranslated();
+		case ComponentType::SPELL_SCROLL:
+		case ComponentType::SPELL:
+			return CGI->spells()->getById(data.subType.as<SpellID>())->getNameTranslated();
+		case ComponentType::MORALE:
+			return "";
+		case ComponentType::LUCK:
+			return "";
+		case ComponentType::BUILDING:
 			{
-				logGlobal->error("Town of faction %s has no building #%d", (*CGI->townh)[subtype]->town->faction->getNameTranslated(), val);
-				return (boost::format("Missing building #%d") % val).str();
+				auto index = data.subType.as<BuildingTypeUniqueID>();
+				auto building = (*CGI->townh)[index.getFaction()]->town->buildings[index.getBuilding()];
+				if(!building)
+				{
+					logGlobal->error("Town of faction %s has no building #%d", (*CGI->townh)[index.getFaction()]->town->faction->getNameTranslated(), index.getBuilding().getNum());
+					return (boost::format("Missing building #%d") % index.getBuilding().getNum()).str();
+				}
+				return building->getNameTranslated();
 			}
-			return building->getNameTranslated();
-		}
-	case hero:       return "";
-	case flag:       return CGI->generaltexth->capColors[subtype];
+		case ComponentType::HERO_PORTRAIT:
+			return "";
+		case ComponentType::FLAG:
+			return CGI->generaltexth->capColors[data.subType.as<PlayerColor>().getNum()];
+		default:
+			assert(0);
+			return "";
 	}
-	logGlobal->error("Invalid CComponent type: %d", (int)compType);
-	return "";
 }
 
 void CComponent::setSurface(const AnimationPath & defName, int imgPos)
@@ -299,7 +349,7 @@ CSelectableComponent::CSelectableComponent(const Component &c, std::function<voi
 	init();
 }
 
-CSelectableComponent::CSelectableComponent(Etype Type, int Sub, int Val, ESize imageSize, std::function<void()> OnSelect):
+CSelectableComponent::CSelectableComponent(ComponentType Type, ComponentSubType Sub, int Val, ESize imageSize, std::function<void()> OnSelect):
 	CComponent(Type,Sub,Val, imageSize),onSelect(OnSelect)
 {
 	setRedrawParent(true);

+ 7 - 16
client/widgets/CComponent.h

@@ -12,6 +12,7 @@
 #include "../gui/CIntObject.h"
 #include "../render/EFont.h"
 #include "../../lib/filesystem/ResourcePath.h"
+#include "../../lib/networkPacks/Component.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -26,11 +27,6 @@ class CLabel;
 class CComponent : public virtual CIntObject
 {
 public:
-	enum Etype
-	{
-		primskill, secskill, resource, creature, artifact, experience, spell, morale, luck, building, hero, flag, typeInvalid
-	};
-
 	//NOTE: not all types have exact these sizes or have less than 4 of them. In such cases closest one will be used
 	enum ESize
 	{
@@ -47,26 +43,21 @@ private:
 	size_t getIndex();
 	std::vector<AnimationPath> getFileName();
 	void setSurface(const AnimationPath & defName, int imgPos);
-	std::string getSubtitleInternal();
 
-	void init(Etype Type, int Subtype, int Val, ESize imageSize, EFonts font = FONT_SMALL, std::string ValText="");
+	void init(ComponentType Type, ComponentSubType Subtype, std::optional<int32_t> Val, ESize imageSize, EFonts font = FONT_SMALL, std::string ValText="");
 
 public:
 	std::shared_ptr<CAnimImage> image;
-
-	Etype compType; //component type
+	Component data;
+	std::string customSubtitle;
 	ESize size; //component size.
 	EFonts font; //Font size of label
-	int subtype; //type-dependant subtype. See getSomething methods for details
-	int val; // value \ strength \ amount of component. See getSomething methods for details
-	std::string valText; // value instead of amount; currently only for resource
-	bool perDay; // add "per day" text to subtitle
 
 	std::string getDescription();
 	std::string getSubtitle();
 
-	CComponent(Etype Type, int Subtype, int Val = 0, ESize imageSize=large, EFonts font = FONT_SMALL);
-	CComponent(Etype Type, int Subtype, std::string Val, ESize imageSize=large, EFonts font = FONT_SMALL);
+	CComponent(ComponentType Type, ComponentSubType Subtype, std::optional<int32_t> Val = std::nullopt, ESize imageSize=large, EFonts font = FONT_SMALL);
+	CComponent(ComponentType Type, ComponentSubType Subtype, std::string Val, ESize imageSize=large, EFonts font = FONT_SMALL);
 	CComponent(const Component &c, ESize imageSize=large, EFonts font = FONT_SMALL);
 
 	void showPopupWindow(const Point & cursorPosition) override; //call-in
@@ -86,7 +77,7 @@ public:
 
 	void clickPressed(const Point & cursorPosition) override; //call-in
 	void clickDouble(const Point & cursorPosition) override; //call-in
-	CSelectableComponent(Etype Type, int Sub, int Val, ESize imageSize=large, std::function<void()> OnSelect = nullptr);
+	CSelectableComponent(ComponentType Type, ComponentSubType Sub, int Val, ESize imageSize=large, std::function<void()> OnSelect = nullptr);
 	CSelectableComponent(const Component & c, std::function<void()> OnSelect = nullptr);
 };
 

+ 1 - 1
client/widgets/CWindowWithArtifacts.cpp

@@ -95,7 +95,7 @@ void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst
 		if(artPlace.getArt()->getTypeId() == ArtifactID::CATAPULT)
 		{
 			// The Catapult must be equipped
-			std::vector<std::shared_ptr<CComponent>> catapult(1, std::make_shared<CComponent>(CComponent::artifact, 3, 0));
+			std::vector<std::shared_ptr<CComponent>> catapult(1, std::make_shared<CComponent>(ComponentType::ARTIFACT, ArtifactID(ArtifactID::CATAPULT)));
 			LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[312], catapult);
 			return false;
 		}

+ 19 - 25
client/widgets/MiscWidgets.cpp

@@ -95,16 +95,16 @@ void LRClickableAreaWTextComp::clickPressed(const Point & cursorPosition)
 	LOCPLINT->showInfoDialog(text, comp);
 }
 
-LRClickableAreaWTextComp::LRClickableAreaWTextComp(const Rect &Pos, int BaseType)
-	: LRClickableAreaWText(Pos), baseType(BaseType), bonusValue(-1)
+LRClickableAreaWTextComp::LRClickableAreaWTextComp(const Rect &Pos, ComponentType BaseType)
+	: LRClickableAreaWText(Pos)
 {
-	type = -1;
+	component.type = BaseType;
 }
 
 std::shared_ptr<CComponent> LRClickableAreaWTextComp::createComponent() const
 {
-	if(baseType >= 0)
-		return std::make_shared<CComponent>(CComponent::Etype(baseType), type, bonusValue);
+	if(component.type != ComponentType::NONE)
+		return std::make_shared<CComponent>(component);
 	else
 		return std::shared_ptr<CComponent>();
 }
@@ -164,17 +164,11 @@ void CHeroArea::hover(bool on)
 void LRClickableAreaOpenTown::clickPressed(const Point & cursorPosition)
 {
 	if(town)
-	{
 		LOCPLINT->openTownWindow(town);
-		if ( type == 2 )
-			LOCPLINT->castleInt->builds->buildingClicked(BuildingID::VILLAGE_HALL);
-		else if ( type == 3 && town->fortLevel() )
-			LOCPLINT->castleInt->builds->buildingClicked(BuildingID::FORT);
-	}
 }
 
 LRClickableAreaOpenTown::LRClickableAreaOpenTown(const Rect & Pos, const CGTownInstance * Town)
-	: LRClickableAreaWTextComp(Pos, -1), town(Town)
+	: LRClickableAreaWTextComp(Pos), town(Town)
 {
 }
 
@@ -542,20 +536,21 @@ void MoraleLuckBox::set(const AFactionMember * node)
 {
 	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
 
-	const int textId[] = {62, 88}; //eg %s \n\n\n {Current Luck Modifiers:}
+	const std::array textId = {62, 88}; //eg %s \n\n\n {Current Luck Modifiers:}
 	const int noneTxtId = 108; //Russian version uses same text for neutral morale\luck
-	const int neutralDescr[] = {60, 86}; //eg {Neutral Morale} \n\n Neutral morale means your armies will neither be blessed with extra attacks or freeze in combat.
-	const int componentType[] = {CComponent::luck, CComponent::morale};
-	const int hoverTextBase[] = {7, 4};
+	const std::array neutralDescr = {60, 86}; //eg {Neutral Morale} \n\n Neutral morale means your armies will neither be blessed with extra attacks or freeze in combat.
+	const std::array componentType = {ComponentType::LUCK, ComponentType::MORALE};
+	const std::array hoverTextBase = {7, 4};
 	TConstBonusListPtr modifierList = std::make_shared<const BonusList>();
-	bonusValue = 0;
+
+	component.value = 0;
 
 	if(node)
-		bonusValue = morale ? node->moraleValAndBonusList(modifierList) : node->luckValAndBonusList(modifierList);
+		component.value = morale ? node->moraleValAndBonusList(modifierList) : node->luckValAndBonusList(modifierList);
 
-	int mrlt = (bonusValue>0)-(bonusValue<0); //signum: -1 - bad luck / morale, 0 - neutral, 1 - good
+	int mrlt = (component.value>0)-(component.value<0); //signum: -1 - bad luck / morale, 0 - neutral, 1 - good
 	hoverText = CGI->generaltexth->heroscrn[hoverTextBase[morale] - mrlt];
-	baseType = componentType[morale];
+	component.type = componentType[morale];
 	text = CGI->generaltexth->arraytxt[textId[morale]];
 	boost::algorithm::replace_first(text,"%s",CGI->generaltexth->arraytxt[neutralDescr[morale]-mrlt]);
 
@@ -563,19 +558,19 @@ void MoraleLuckBox::set(const AFactionMember * node)
 			|| node->getBonusBearer()->hasBonusOfType(BonusType::NON_LIVING)))
 	{
 		text += CGI->generaltexth->arraytxt[113]; //unaffected by morale
-		bonusValue = 0;
+		component.value = 0;
 	}
 	else if(morale && node && node->getBonusBearer()->hasBonusOfType(BonusType::NO_MORALE))
 	{
 		auto noMorale = node->getBonusBearer()->getBonus(Selector::type()(BonusType::NO_MORALE));
 		text += "\n" + noMorale->Description();
-		bonusValue = 0;
+		component.value = 0;
 	}
 	else if (!morale && node && node->getBonusBearer()->hasBonusOfType(BonusType::NO_LUCK))
 	{
 		auto noLuck = node->getBonusBearer()->getBonus(Selector::type()(BonusType::NO_LUCK));
 		text += "\n" + noLuck->Description();
-		bonusValue = 0;
+		component.value = 0;
 	}
 	else
 	{
@@ -595,7 +590,7 @@ void MoraleLuckBox::set(const AFactionMember * node)
 	else
 		imageName = morale ? "IMRL42" : "ILCK42";
 
-	image = std::make_shared<CAnimImage>(AnimationPath::builtin(imageName), bonusValue + 3);
+	image = std::make_shared<CAnimImage>(AnimationPath::builtin(imageName), *component.value + 3);
 	image->moveBy(Point(pos.w/2 - image->pos.w/2, pos.h/2 - image->pos.h/2));//center icon
 }
 
@@ -603,7 +598,6 @@ MoraleLuckBox::MoraleLuckBox(bool Morale, const Rect &r, bool Small)
 	: morale(Morale),
 	small(Small)
 {
-	bonusValue = 0;
 	pos = r + pos.topLeft();
 	defActions = 255-DISPOSE;
 }

+ 5 - 5
client/widgets/MiscWidgets.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../gui/CIntObject.h"
+#include "../../lib/networkPacks/Component.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -202,13 +203,12 @@ private:
 class LRClickableAreaWTextComp: public LRClickableAreaWText
 {
 public:
-	int type;
-	int baseType;
-	int bonusValue;
+	Component component;
+
 	void clickPressed(const Point & cursorPosition) override;
 	void showPopupWindow(const Point & cursorPosition) override;
 
-	LRClickableAreaWTextComp(const Rect &Pos = Rect(0,0,0,0), int BaseType = -1);
+	LRClickableAreaWTextComp(const Rect &Pos = Rect(0,0,0,0), ComponentType baseType = ComponentType::NONE);
 	std::shared_ptr<CComponent> createComponent() const;
 };
 
@@ -263,4 +263,4 @@ class SimpleLine : public CIntObject
 public:
     SimpleLine(Point pos1, Point pos2, ColorRGBA color);
     void showAll(Canvas & to) override;
-};
+};

+ 21 - 14
client/windows/CCastleInterface.cpp

@@ -157,7 +157,7 @@ void CBuildingRect::showPopupWindow(const Point & cursorPosition)
 	if (bid < BuildingID::DWELL_FIRST)
 	{
 		CRClickPopup::createAndPush(CInfoWindow::genText(bld->getNameTranslated(), bld->getDescriptionTranslated()),
-									std::make_shared<CComponent>(CComponent::building, bld->town->faction->getIndex(), bld->bid));
+									std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(bld->town->faction->getId(), bld->bid)));
 	}
 	else
 	{
@@ -860,7 +860,7 @@ void CCastleBuildings::enterBlacksmith(ArtifactID artifactID)
 
 void CCastleBuildings::enterBuilding(BuildingID building)
 {
-	std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(CComponent::building, town->subID, building));
+	std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), building)));
 	LOCPLINT->showInfoDialog( town->town->buildings.find(building)->second->getDescriptionTranslated(), comps);
 }
 
@@ -920,7 +920,7 @@ void CCastleBuildings::enterToTheQuickRecruitmentWindow()
 
 void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID::EBuildingSubID subID, BuildingID upgrades)
 {
-	std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(CComponent::building,town->getFaction(),building));
+	std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), building)));
 	std::string descr = town->town->buildings.find(building)->second->getDescriptionTranslated();
 	std::string hasNotProduced;
 	std::string hasProduced;
@@ -986,7 +986,7 @@ void CCastleBuildings::enterMagesGuild()
 			CFunctionList<void()> onYes = [this](){ openMagesGuild(); };
 			CFunctionList<void()> onNo = onYes;
 			onYes += [hero](){ LOCPLINT->cb->buyArtifact(hero, ArtifactID::SPELLBOOK); };
-			std::vector<std::shared_ptr<CComponent>> components(1, std::make_shared<CComponent>(CComponent::artifact,ArtifactID::SPELLBOOK,0));
+			std::vector<std::shared_ptr<CComponent>> components(1, std::make_shared<CComponent>(ComponentType::ARTIFACT, ArtifactID(ArtifactID::SPELLBOOK)));
 
 			LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[214], onYes, onNo, components);
 		}
@@ -1129,7 +1129,7 @@ void CCreaInfo::showPopupWindow(const Point & cursorPosition)
 	if (showAvailable)
 		GH.windows().createAndPushWindow<CDwellingInfoBox>(GH.screenDimensions().x / 2, GH.screenDimensions().y / 2, town, level);
 	else
-		CRClickPopup::createAndPush(genGrowthText(), std::make_shared<CComponent>(CComponent::creature, creature->getId()));
+		CRClickPopup::createAndPush(genGrowthText(), std::make_shared<CComponent>(ComponentType::CREATURE, creature->getId()));
 }
 
 bool CCreaInfo::getShowAvailable()
@@ -1180,7 +1180,7 @@ void CTownInfo::showPopupWindow(const Point & cursorPosition)
 {
 	if(building)
 	{
-		auto c =  std::make_shared<CComponent>(CComponent::building, building->town->faction->getIndex(), building->bid);
+		auto c =  std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(building->town->faction->getId(), building->bid));
 		CRClickPopup::createAndPush(CInfoWindow::genText(building->getNameTranslated(), building->getDescriptionTranslated()), c);
 	}
 }
@@ -1526,15 +1526,22 @@ CBuildWindow::CBuildWindow(const CGTownInstance *Town, const CBuilding * Buildin
 	//Create components for all required resources
 	std::vector<std::shared_ptr<CComponent>> components;
 
-	for(int i = 0; i<GameConstants::RESOURCE_QUANTITY; i++)
+	for(GameResID i : GameResID::ALL_RESOURCES())
 	{
 		if(building->resources[i])
 		{
-			std::string text = std::to_string(building->resources[i]);
-			int resAfterBuy = LOCPLINT->cb->getResourceAmount(GameResID(i)) - building->resources[i];
-			if(resAfterBuy < 0 && state != EBuildingState::ALREADY_PRESENT && settings["general"]["enableUiEnhancements"].Bool())
-				text = "{H3Red|" + std::to_string(LOCPLINT->cb->getResourceAmount(GameResID(i))) + "}" + " / " + text;
-			components.push_back(std::make_shared<CComponent>(CComponent::resource, i, text, CComponent::small));
+			MetaString message;
+			int resourceAmount = LOCPLINT->cb->getResourceAmount(i);
+			bool canAfford = resourceAmount >= building->resources[i];
+
+			if(!canAfford && state != EBuildingState::ALREADY_PRESENT && settings["general"]["enableUiEnhancements"].Bool())
+				message.appendRawString("{H3Red|%d}/%d");
+			else
+				message.appendRawString("%d");
+
+			message.replaceNumber(resourceAmount);
+			message.replaceNumber(building->resources[i]);
+			components.push_back(std::make_shared<CComponent>(ComponentType::RESOURCE, i, message.toString(), CComponent::small));
 		}
 	}
 
@@ -1889,12 +1896,12 @@ CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell)
 
 void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition)
 {
-	LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared<CComponent>(CComponent::spell, spell->id));
+	LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared<CComponent>(ComponentType::SPELL, spell->id));
 }
 
 void CMageGuildScreen::Scroll::showPopupWindow(const Point & cursorPosition)
 {
-	CRClickPopup::createAndPush(spell->getDescriptionTranslated(0), std::make_shared<CComponent>(CComponent::spell, spell->id));
+	CRClickPopup::createAndPush(spell->getDescriptionTranslated(0), std::make_shared<CComponent>(ComponentType::SPELL, spell->id));
 }
 
 void CMageGuildScreen::Scroll::hover(bool on)

+ 5 - 5
client/windows/CCreatureWindow.cpp

@@ -325,7 +325,7 @@ CStackWindow::ButtonsSection::ButtonsSection(CStackWindow * owner, int yOffset)
 				std::vector<std::shared_ptr<CComponent>> resComps;
 				for(TResources::nziterator i(totalCost); i.valid(); i++)
 				{
-					resComps.push_back(std::make_shared<CComponent>(CComponent::resource, i->resType, (int)i->resVal));
+					resComps.push_back(std::make_shared<CComponent>(ComponentType::RESOURCE, i->resType, i->resVal));
 				}
 
 				if(LOCPLINT->cb->getResourceAmount().canAfford(totalCost))
@@ -582,10 +582,10 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
 			const CCommanderInstance * commander = parent->info->commander;
 			expRankIcon = std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL42"), 4, 0, pos.x, pos.y);
 
-			auto area = std::make_shared<LRClickableAreaWTextComp>(Rect(pos.x, pos.y, 44, 44), CComponent::experience);
+			auto area = std::make_shared<LRClickableAreaWTextComp>(Rect(pos.x, pos.y, 44, 44), ComponentType::EXPERIENCE);
 			expArea = area;
 			area->text = CGI->generaltexth->allTexts[2];
-			area->bonusValue =	commander->getExpRank();
+			area->component.value = commander->getExpRank();
 			boost::replace_first(area->text, "%d", std::to_string(commander->getExpRank()));
 			boost::replace_first(area->text, "%d", std::to_string(CGI->heroh->reqExp(commander->getExpRank() + 1)));
 			boost::replace_first(area->text, "%d", std::to_string(commander->experience));
@@ -611,8 +611,8 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
 		if(art)
 		{
 			parent->stackArtifactIcon = std::make_shared<CAnimImage>(AnimationPath::builtin("ARTIFACT"), art->artType->getIconIndex(), 0, pos.x, pos.y);
-			parent->stackArtifactHelp = std::make_shared<LRClickableAreaWTextComp>(Rect(pos, Point(44, 44)), CComponent::artifact);
-			parent->stackArtifactHelp->type = art->artType->getId();
+			parent->stackArtifactHelp = std::make_shared<LRClickableAreaWTextComp>(Rect(pos, Point(44, 44)), ComponentType::ARTIFACT);
+			parent->stackArtifactHelp->component.subType = art->artType->getId();
 
 			if(parent->info->owner)
 			{

+ 10 - 9
client/windows/CHeroWindow.cpp

@@ -119,9 +119,9 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
 
 	for(int v = 0; v < GameConstants::PRIMARY_SKILLS; ++v)
 	{
-		auto area = std::make_shared<LRClickableAreaWTextComp>(Rect(30 + 70 * v, 109, 42, 64), CComponent::primskill);
+		auto area = std::make_shared<LRClickableAreaWTextComp>(Rect(30 + 70 * v, 109, 42, 64), ComponentType::PRIM_SKILL);
 		area->text = CGI->generaltexth->arraytxt[2+v];
-		area->type = v;
+		area->component.subType = PrimarySkill(v);
 		area->hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[1]) % CGI->generaltexth->primarySkillNames[v]);
 		primSkillAreas.push_back(area);
 
@@ -154,7 +154,7 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
 	for(int i = 0; i < std::min<size_t>(hero->secSkills.size(), 8u); ++i)
 	{
 		Rect r = Rect(i%2 == 0  ?  18  :  162,  276 + 48 * (i/2),  136,  42);
-		secSkillAreas.push_back(std::make_shared<LRClickableAreaWTextComp>(r, CComponent::secskill));
+		secSkillAreas.push_back(std::make_shared<LRClickableAreaWTextComp>(r, ComponentType::SEC_SKILL));
 		secSkillImages.push_back(std::make_shared<CAnimImage>(secSkills, 0, 0, r.x, r.y));
 
 		int x = (i % 2) ? 212 : 68;
@@ -232,20 +232,21 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded)
 	//primary skills support
 	for(size_t g=0; g<primSkillAreas.size(); ++g)
 	{
-		primSkillAreas[g]->bonusValue = curHero->getPrimSkillLevel(static_cast<PrimarySkill>(g));
-		primSkillValues[g]->setText(std::to_string(primSkillAreas[g]->bonusValue));
+		int value = curHero->getPrimSkillLevel(static_cast<PrimarySkill>(g));
+		primSkillAreas[g]->component.value = value;
+		primSkillValues[g]->setText(std::to_string(value));
 	}
 
 	//secondary skills support
 	for(size_t g=0; g< secSkillAreas.size(); ++g)
 	{
-		int skill = curHero->secSkills[g].first;
-		int	level = curHero->getSecSkillLevel(SecondarySkill(curHero->secSkills[g].first));
+		SecondarySkill skill = curHero->secSkills[g].first;
+		int	level = curHero->getSecSkillLevel(skill);
 		std::string skillName = CGI->skillh->getByIndex(skill)->getNameTranslated();
 		std::string skillValue = CGI->generaltexth->levels[level-1];
 
-		secSkillAreas[g]->type = skill;
-		secSkillAreas[g]->bonusValue = level;
+		secSkillAreas[g]->component.subType = skill;
+		secSkillAreas[g]->component.value = level;
 		secSkillAreas[g]->text = CGI->skillh->getByIndex(skill)->getDescriptionTranslated(level);
 		secSkillAreas[g]->hoverText = boost::str(boost::format(heroscrn[21]) % skillValue % skillName);
 		secSkillImages[g]->setFrame(skill*3 + level + 2);

+ 2 - 2
client/windows/CKingdomInterface.cpp

@@ -256,7 +256,7 @@ void InfoBoxAbstractHeroData::prepareMessage(std::string & text, std::shared_ptr
 		break;
 	case HERO_PRIMARY_SKILL:
 		text = CGI->generaltexth->arraytxt[2+getSubID()];
-		comp = std::make_shared<CComponent>(CComponent::primskill, getSubID(), (int)getValue());
+		comp = std::make_shared<CComponent>(ComponentType::PRIM_SKILL, PrimarySkill(getSubID()), getValue());
 		break;
 	case HERO_MANA:
 		text = CGI->generaltexth->allTexts[149];
@@ -271,7 +271,7 @@ void InfoBoxAbstractHeroData::prepareMessage(std::string & text, std::shared_ptr
 			if(value)
 			{
 				text = CGI->skillh->getByIndex(subID)->getDescriptionTranslated((int)value);
-				comp = std::make_shared<CComponent>(CComponent::secskill, subID, (int)value);
+				comp = std::make_shared<CComponent>(ComponentType::SEC_SKILL, SecondarySkill(subID), (int)value);
 			}
 			break;
 		}

+ 2 - 2
client/windows/CSpellWindow.cpp

@@ -558,7 +558,7 @@ void CSpellWindow::SpellArea::clickPressed(const Point & cursorPosition)
 		//battle spell on adv map or adventure map spell during combat => display infowindow, not cast
 		if((combatSpell ^ inCombat) || inCastle)
 		{
-			std::vector<std::shared_ptr<CComponent>> hlp(1, std::make_shared<CComponent>(CComponent::spell, mySpell->id, 0));
+			std::vector<std::shared_ptr<CComponent>> hlp(1, std::make_shared<CComponent>(ComponentType::SPELL, mySpell->id));
 			LOCPLINT->showInfoDialog(mySpell->getDescriptionTranslated(schoolLevel), hlp);
 		}
 		else if(combatSpell)
@@ -614,7 +614,7 @@ void CSpellWindow::SpellArea::showPopupWindow(const Point & cursorPosition)
 			boost::algorithm::replace_first(dmgInfo, "%d", std::to_string(causedDmg));
 		}
 
-		CRClickPopup::createAndPush(mySpell->getDescriptionTranslated(schoolLevel) + dmgInfo, std::make_shared<CComponent>(CComponent::spell, mySpell->id));
+		CRClickPopup::createAndPush(mySpell->getDescriptionTranslated(schoolLevel) + dmgInfo, std::make_shared<CComponent>(ComponentType::SPELL, mySpell->id));
 	}
 }
 

+ 6 - 11
client/windows/GUIClasses.cpp

@@ -401,7 +401,7 @@ CLevelWindow::CLevelWindow(const CGHeroInstance * hero, PrimarySkill pskill, std
 		std::vector<std::shared_ptr<CSelectableComponent>> comps;
 		for(auto & skill : skills)
 		{
-			auto comp = std::make_shared<CSelectableComponent>(CComponent::secskill, skill, hero->getSecSkillLevel(SecondarySkill(skill))+1, CComponent::medium);
+			auto comp = std::make_shared<CSelectableComponent>(ComponentType::SEC_SKILL, skill, hero->getSecSkillLevel(SecondarySkill(skill))+1, CComponent::medium);
 			comp->onChoose = std::bind(&CLevelWindow::close, this);
 			comps.push_back(comp);
 		}
@@ -693,9 +693,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 		else
 			primSkillAreas[g]->pos = Rect(Point(pos.x + 329, pos.y + 19 + 36 * g), Point(140, 32));
 		primSkillAreas[g]->text = CGI->generaltexth->arraytxt[2+g];
-		primSkillAreas[g]->type = g;
-		primSkillAreas[g]->bonusValue = 0;
-		primSkillAreas[g]->baseType = 0;
+		primSkillAreas[g]->component = Component( ComponentType::PRIM_SKILL, PrimarySkill(g));
 		primSkillAreas[g]->hoverText = CGI->generaltexth->heroscrn[1];
 		boost::replace_first(primSkillAreas[g]->hoverText, "%s", CGI->generaltexth->primarySkillNames[g]);
 	}
@@ -708,14 +706,11 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 		//secondary skill's clickable areas
 		for(int g=0; g<hero->secSkills.size(); ++g)
 		{
-			int skill = hero->secSkills[g].first,
-				level = hero->secSkills[g].second; // <1, 3>
+			SecondarySkill skill = hero->secSkills[g].first;
+			int level = hero->secSkills[g].second; // <1, 3>
 			secSkillAreas[b].push_back(std::make_shared<LRClickableAreaWTextComp>());
 			secSkillAreas[b][g]->pos = Rect(Point(pos.x + 32 + g * 36 + b * 454 , pos.y + (qeLayout ? 83 : 88)), Point(32, 32) );
-			secSkillAreas[b][g]->baseType = 1;
-
-			secSkillAreas[b][g]->type = skill;
-			secSkillAreas[b][g]->bonusValue = level;
+			secSkillAreas[b][g]->component = Component(ComponentType::SEC_SKILL, skill, level);
 			secSkillAreas[b][g]->text = CGI->skillh->getByIndex(skill)->getDescriptionTranslated(level);
 
 			secSkillAreas[b][g]->hoverText = CGI->generaltexth->heroscrn[21];
@@ -1082,7 +1077,7 @@ void CUniversityWindow::CItem::clickPressed(const Point & cursorPosition)
 
 void CUniversityWindow::CItem::showPopupWindow(const Point & cursorPosition)
 {
-	CRClickPopup::createAndPush(CGI->skillh->getByIndex(ID)->getDescriptionTranslated(1), std::make_shared<CComponent>(CComponent::secskill, ID, 1));
+	CRClickPopup::createAndPush(CGI->skillh->getByIndex(ID)->getDescriptionTranslated(1), std::make_shared<CComponent>(ComponentType::SEC_SKILL, ID, 1));
 }
 
 void CUniversityWindow::CItem::hover(bool on)

+ 1 - 1
client/windows/GUIClasses.h

@@ -375,7 +375,7 @@ class CUniversityWindow : public CStatusbarWindow
 		std::shared_ptr<CLabel> name;
 		std::shared_ptr<CLabel> level;
 	public:
-		int ID;//id of selected skill
+		SecondarySkill ID;//id of selected skill
 		CUniversityWindow * parent;
 
 		void showAll(Canvas & to) override;

+ 5 - 0
lib/CCreatureSet.cpp

@@ -1017,6 +1017,11 @@ const Creature * CStackBasicDescriptor::getType() const
 	return type;
 }
 
+CreatureID CStackBasicDescriptor::getId() const
+{
+	return type->getId();
+}
+
 TQuantity CStackBasicDescriptor::getCount() const
 {
 	return count;

+ 1 - 0
lib/CCreatureSet.h

@@ -39,6 +39,7 @@ public:
 	virtual ~CStackBasicDescriptor() = default;
 
 	const Creature * getType() const;
+	CreatureID getId() const;
 	TQuantity getCount() const;
 
 	virtual void setType(const CCreature * c);

+ 15 - 0
lib/constants/EntityIdentifiers.cpp

@@ -458,6 +458,21 @@ std::string GameResID::entityType()
 	return "resource";
 }
 
+const std::array<GameResID, 7> & GameResID::ALL_RESOURCES()
+{
+	static const std::array allResources = {
+		GameResID(WOOD),
+		GameResID(MERCURY),
+		GameResID(ORE),
+		GameResID(SULFUR),
+		GameResID(CRYSTAL),
+		GameResID(GEMS),
+		GameResID(GOLD)
+	};
+
+	return allResources;
+}
+
 std::string SecondarySkill::entityType()
 {
 	return "secondarySkill";

+ 3 - 1
lib/constants/EntityIdentifiers.h

@@ -960,9 +960,11 @@ public:
 	static si32 decode(const std::string & identifier);
 	static std::string encode(const si32 index);
 	static std::string entityType();
+
+	static const std::array<GameResID, 7> & ALL_RESOURCES();
 };
 
-class BuildingTypeUniqueID : public Identifier<BuildingTypeUniqueID>
+class DLL_LINKAGE BuildingTypeUniqueID : public Identifier<BuildingTypeUniqueID>
 {
 public:
 	BuildingTypeUniqueID(FactionID faction, BuildingID building );

+ 10 - 14
lib/mapObjects/CBank.cpp

@@ -79,11 +79,7 @@ std::vector<Component> CBank::getPopupComponents(PlayerColor player) const
 
 	for (auto const & guard : guardsAmounts)
 	{
-		Component comp;
-		comp.id = Component::EComponentType::CREATURE;
-		comp.subtype = guard.first.getNum();
-		comp.val = guard.second;
-
+		Component comp(ComponentType::CREATURE, guard.first, guard.second);
 		result.push_back(comp);
 	}
 	return result;
@@ -236,7 +232,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 				break;
 			}
 			cb->giveHeroBonus(&gbonus);
-			iw.components.emplace_back(Component::EComponentType::MORALE, 0, -1, 0);
+			iw.components.emplace_back(ComponentType::MORALE, -1);
 			iw.soundID = soundBase::GRAVEYARD;
 			break;
 		}
@@ -247,7 +243,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 			gb.id = hero->id.getNum();
 			cb->giveHeroBonus(&gb);
 			textID = 107;
-			iw.components.emplace_back(Component::EComponentType::LUCK, 0, -2, 0);
+			iw.components.emplace_back(ComponentType::LUCK, -2);
 			break;
 		}
 		case Obj::CREATURE_BANK:
@@ -267,21 +263,21 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 	//grant resources
 	if (bc)
 	{
-		for (int it = 0; it < bc->resources.size(); it++)
+		for (GameResID it : GameResID::ALL_RESOURCES())
 		{
 			if (bc->resources[it] != 0)
 			{
-				iw.components.emplace_back(Component::EComponentType::RESOURCE, it, bc->resources[it], 0);
+				iw.components.emplace_back(ComponentType::RESOURCE, it, bc->resources[it]);
 				loot.appendRawString("%d %s");
-				loot.replaceNumber(iw.components.back().val);
-				loot.replaceLocalString(EMetaText::RES_NAMES, iw.components.back().subtype);
+				loot.replaceNumber(bc->resources[it]);
+				loot.replaceLocalString(EMetaText::RES_NAMES, it);
 				cb->giveResource(hero->getOwner(), static_cast<EGameResID>(it), bc->resources[it]);
 			}
 		}
 		//grant artifacts
 		for (auto & elem : bc->artifacts)
 		{
-			iw.components.emplace_back(Component::EComponentType::ARTIFACT, elem, 0, 0);
+			iw.components.emplace_back(ComponentType::ARTIFACT, elem);
 			loot.appendRawString("%s");
 			loot.replaceLocalString(EMetaText::ART_NAMES, elem);
 			cb->giveHeroNewArtifact(hero, VLC->arth->objects[elem], ArtifactPosition::FIRST_AVAILABLE);
@@ -325,7 +321,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 					if(hero->canLearnSpell(spell))
 					{
 						spells.insert(spellId);
-						iw.components.emplace_back(Component::EComponentType::SPELL, spellId, 0, 0);
+						iw.components.emplace_back(ComponentType::SPELL, spellId);
 					}
 				}
 				else
@@ -356,7 +352,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 
 		for(const auto & elem : ourArmy.Slots())
 		{
-			iw.components.emplace_back(*elem.second);
+			iw.components.emplace_back(ComponentType::CREATURE, elem.second->getId(), elem.second->getCount());
 			loot.appendRawString("%s");
 			loot.replaceCreatureName(*elem.second);
 		}

+ 4 - 4
lib/mapObjects/CGCreature.cpp

@@ -563,17 +563,17 @@ void CGCreature::giveReward(const CGHeroInstance * h) const
 	if(!resources.empty())
 	{
 		cb->giveResources(h->tempOwner, resources);
-		for(int i = 0; i < resources.size(); i++)
+		for(auto const & res : GameResID::ALL_RESOURCES())
 		{
-			if(resources[i] > 0)
-				iw.components.emplace_back(Component::EComponentType::RESOURCE, i, resources[i], 0);
+			if(resources[res] > 0)
+				iw.components.emplace_back(ComponentType::RESOURCE, res, resources[res]);
 		}
 	}
 
 	if(gainedArtifact != ArtifactID::NONE)
 	{
 		cb->giveHeroNewArtifact(h, VLC->arth->objects[gainedArtifact], ArtifactPosition::FIRST_AVAILABLE);
-		iw.components.emplace_back(Component::EComponentType::ARTIFACT, gainedArtifact, 0, 0);
+		iw.components.emplace_back(ComponentType::ARTIFACT, gainedArtifact);
 	}
 
 	if(!iw.components.empty())

+ 1 - 1
lib/mapObjects/CGHeroInstance.cpp

@@ -944,7 +944,7 @@ void CGHeroInstance::showNecromancyDialog(const CStackBasicDescriptor &raisedSta
 	iw.type = EInfoWindowMode::AUTO;
 	iw.soundID = soundBase::pickup01 + rand.nextInt(6);
 	iw.player = tempOwner;
-	iw.components.emplace_back(raisedStack);
+	iw.components.emplace_back(ComponentType::CREATURE, raisedStack.getId(), raisedStack.count);
 
 	if (raisedStack.count > 1) // Practicing the dark arts of necromancy, ... (plural)
 	{

+ 5 - 5
lib/mapObjects/CGTownBuilding.cpp

@@ -207,31 +207,31 @@ void CTownBonus::onHeroVisit (const CGHeroInstance * h) const
 		case BuildingSubID::KNOWLEDGE_VISITING_BONUS: //wall of knowledge
 			what = PrimarySkill::KNOWLEDGE;
 			val = 1;
-			iw.components.emplace_back(Component::EComponentType::PRIM_SKILL, 3, 1, 0);
+			iw.components.emplace_back(ComponentType::PRIM_SKILL, PrimarySkill::KNOWLEDGE, 1);
 			break;
 
 		case BuildingSubID::SPELL_POWER_VISITING_BONUS: //order of fire
 			what = PrimarySkill::SPELL_POWER;
 			val = 1;
-			iw.components.emplace_back(Component::EComponentType::PRIM_SKILL, 2, 1, 0);
+			iw.components.emplace_back(ComponentType::PRIM_SKILL, PrimarySkill::SPELL_POWER, 1);
 			break;
 
 		case BuildingSubID::ATTACK_VISITING_BONUS: //hall of Valhalla
 			what = PrimarySkill::ATTACK;
 			val = 1;
-			iw.components.emplace_back(Component::EComponentType::PRIM_SKILL, 0, 1, 0);
+			iw.components.emplace_back(ComponentType::PRIM_SKILL, PrimarySkill::ATTACK, 1);
 			break;
 
 		case BuildingSubID::EXPERIENCE_VISITING_BONUS: //academy of battle scholars
 			what = PrimarySkill::EXPERIENCE;
 			val = static_cast<int>(h->calculateXp(1000));
-			iw.components.emplace_back(Component::EComponentType::EXPERIENCE, 0, val, 0);
+			iw.components.emplace_back(ComponentType::EXPERIENCE, val);
 			break;
 
 		case BuildingSubID::DEFENSE_VISITING_BONUS: //cage of warlords
 			what = PrimarySkill::DEFENSE;
 			val = 1;
-			iw.components.emplace_back(Component::EComponentType::PRIM_SKILL, 1, 1, 0);
+			iw.components.emplace_back(ComponentType::PRIM_SKILL, PrimarySkill::DEFENSE, 1);
 			break;
 
 		case BuildingSubID::CUSTOM_VISITING_BONUS:

+ 1 - 1
lib/mapObjects/CGTownInstance.cpp

@@ -325,7 +325,7 @@ void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const
 			InfoWindow iw;
 			iw.player = h->tempOwner;
 			iw.text.appendRawString(h->commander->getName());
-			iw.components.emplace_back(*h->commander);
+			iw.components.emplace_back(ComponentType::CREATURE, h->commander->getId(), h->commander->getCount());
 			cb->showInfoDialog(&iw);
 		}
 	}

+ 2 - 2
lib/mapObjects/CQuest.cpp

@@ -204,13 +204,13 @@ void CQuest::addTextReplacements(MetaString & text, std::vector<Component> & com
 	
 	if(killTarget != ObjectInstanceID::NONE && !heroName.empty())
 	{
-		components.emplace_back(Component::EComponentType::HERO_PORTRAIT, heroPortrait, 0, 0);
+		components.emplace_back(ComponentType::HERO_PORTRAIT, heroPortrait);
 		addKillTargetReplacements(text);
 	}
 	
 	if(killTarget != ObjectInstanceID::NONE && stackToKill.type)
 	{
-		components.emplace_back(stackToKill);
+		components.emplace_back(ComponentType::CREATURE, stackToKill.getId(), stackToKill.getCount());
 		addKillTargetReplacements(text);
 	}
 	

+ 5 - 5
lib/mapObjects/MiscObjects.cpp

@@ -169,7 +169,7 @@ void CGMine::flagMine(const PlayerColor & player) const
 	iw.soundID = soundBase::FLAGMINE;
 	iw.text.appendLocalString(EMetaText::MINE_EVNTS, producedResource); //not use subID, abandoned mines uses default mine texts
 	iw.player = player;
-	iw.components.emplace_back(Component::EComponentType::RESOURCE, producedResource, producedQuantity, -1);
+	iw.components.emplace_back(ComponentType::RESOURCE_PER_DAY, producedResource, producedQuantity);
 	cb->showInfoDialog(&iw);
 }
 
@@ -324,7 +324,7 @@ void CGResource::collectRes(const PlayerColor & player) const
 		sii.text.appendLocalString(EMetaText::ADVOB_TXT,113);
 		sii.text.replaceLocalString(EMetaText::RES_NAMES, resourceID());
 	}
-	sii.components.emplace_back(Component::EComponentType::RESOURCE, resourceID(), amount, 0);
+	sii.components.emplace_back(ComponentType::RESOURCE, resourceID(), amount);
 	sii.soundID = soundBase::pickup01 + CRandomGenerator::getDefault().nextInt(6);
 	cb->showInfoDialog(&sii);
 	cb->removeObject(this, player);
@@ -666,7 +666,7 @@ void CGWhirlpool::onHeroVisit( const CGHeroInstance * h ) const
 		iw.type = EInfoWindowMode::AUTO;
 		iw.player = h->tempOwner;
 		iw.text.appendLocalString(EMetaText::ADVOB_TXT, 168);
-		iw.components.emplace_back(CStackBasicDescriptor(h->getCreature(targetstack), -countToTake));
+		iw.components.emplace_back(ComponentType::CREATURE, h->getCreature(targetstack)->getId(), -countToTake);
 		cb->showInfoDialog(&iw);
 		cb->changeStackCount(StackLocation(h, targetstack), -countToTake);
 	}
@@ -790,7 +790,7 @@ void CGArtifact::onHeroVisit(const CGHeroInstance * h) const
 			{
 			case Obj::ARTIFACT:
 			{
-				iw.components.emplace_back(Component::EComponentType::ARTIFACT, getArtifact(), 0, 0);
+				iw.components.emplace_back(ComponentType::ARTIFACT, getArtifact());
 				if(!message.empty())
 					iw.text = message;
 				else
@@ -800,7 +800,7 @@ void CGArtifact::onHeroVisit(const CGHeroInstance * h) const
 			case Obj::SPELL_SCROLL:
 			{
 				int spellID = storedArtifact->getScrollSpellID();
-				iw.components.emplace_back(Component::EComponentType::SPELL, spellID, 0, 0);
+				iw.components.emplace_back(ComponentType::SPELL, spellID);
 				if(!message.empty())
 					iw.text = message;
 				else

+ 51 - 28
lib/networkPacks/Component.h

@@ -9,44 +9,67 @@
  */
 #pragma once
 
+#include "../constants/VariantIdentifier.h"
+#include "../constants/EntityIdentifiers.h"
+
 VCMI_LIB_NAMESPACE_BEGIN
 
-class CStackBasicDescriptor;
+enum class ComponentType : int8_t
+{
+	NONE = -1,
+	PRIM_SKILL,
+	SEC_SKILL,
+	RESOURCE,
+	RESOURCE_PER_DAY,
+	CREATURE,
+	ARTIFACT,
+	SPELL_SCROLL,
+	MANA,
+	EXPERIENCE,
+	LEVEL,
+	SPELL,
+	MORALE,
+	LUCK,
+	BUILDING,
+	HERO_PORTRAIT,
+	FLAG
+};
+
+using ComponentSubType = VariantIdentifier<PrimarySkill, SecondarySkill, GameResID, CreatureID, ArtifactID, SpellID, BuildingTypeUniqueID, HeroTypeID, PlayerColor>;
 
 struct Component
 {
-	enum class EComponentType : uint8_t
-	{
-		PRIM_SKILL,
-		SEC_SKILL,
-		RESOURCE,
-		CREATURE,
-		ARTIFACT,
-		EXPERIENCE,
-		SPELL,
-		MORALE,
-		LUCK,
-		BUILDING,
-		HERO_PORTRAIT,
-		FLAG,
-		INVALID //should be last
-	};
-	EComponentType id = EComponentType::INVALID;
-	ui16 subtype = 0; //id==EXPPERIENCE subtype==0 means exp points and subtype==1 levels
-	si32 val = 0; // + give; - take
-	si16 when = 0; // 0 - now; +x - within x days; -x - per x days
+	ComponentType type = ComponentType::NONE;
+	ComponentSubType subType;
+	std::optional<int32_t> value; // + give; - take
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & id;
-		h & subtype;
-		h & val;
-		h & when;
+		h & type;
+		h & subType;
+		h & value;
 	}
+
 	Component() = default;
-	DLL_LINKAGE explicit Component(const CStackBasicDescriptor &stack);
-	Component(Component::EComponentType Type, ui16 Subtype, si32 Val, si16 When)
-		:id(Type),subtype(Subtype),val(Val),when(When)
+
+	template<typename Numeric, std::enable_if_t<std::is_arithmetic_v<Numeric>, bool> = true>
+	Component(ComponentType type, Numeric value)
+		: type(type)
+		, value(value)
+	{
+	}
+
+	template<typename IdentifierType, std::enable_if_t<std::is_base_of_v<IdentifierBase, IdentifierType>, bool> = true>
+	Component(ComponentType type, IdentifierType subType)
+		: type(type)
+		, subType(subType)
+	{
+	}
+
+	Component(ComponentType type, ComponentSubType subType, int32_t value)
+		: type(type)
+		, subType(subType)
+		, value(value)
 	{
 	}
 };

+ 0 - 7
lib/networkPacks/NetPacksLib.cpp

@@ -2522,13 +2522,6 @@ void TurnTimeUpdate::applyGs(CGameState *gs) const
 	playerState.turnTimer = turnTimer;
 }
 
-Component::Component(const CStackBasicDescriptor & stack)
-	: id(EComponentType::CREATURE)
-	, subtype(stack.type->getId())
-	, val(stack.count)
-{
-}
-
 void EntitiesChanged::applyGs(CGameState * gs)
 {
 	for(const auto & change : changes)

+ 1 - 1
lib/rewardable/Info.cpp

@@ -201,7 +201,7 @@ void Rewardable::Info::configureReward(Rewardable::Configuration & object, CRand
 		CreatureID from(VLC->identifiers()->getIdentifier(node.second.meta, "creature", node.first).value());
 		CreatureID dest(VLC->identifiers()->getIdentifier(node.second.meta, "creature", node.second.String()).value());
 
-		reward.extraComponents.emplace_back(Component::EComponentType::CREATURE, dest.getNum(), 0, 0);
+		reward.extraComponents.emplace_back(ComponentType::CREATURE, dest.getNum());
 
 		reward.creaturesChange[from] = dest;
 	}

+ 11 - 15
lib/rewardable/Limiter.cpp

@@ -187,49 +187,45 @@ void Rewardable::Limiter::loadComponents(std::vector<Component> & comps,
 								 const CGHeroInstance * h) const
 {
 	if (heroExperience)
-		comps.emplace_back(Component::EComponentType::EXPERIENCE, 0, static_cast<si32>(h->calculateXp(heroExperience)), 0);
+		comps.emplace_back(ComponentType::EXPERIENCE, static_cast<si32>(h->calculateXp(heroExperience)));
 
 	if (heroLevel > 0)
-		comps.emplace_back(Component::EComponentType::EXPERIENCE, 1, heroLevel, 0);
+		comps.emplace_back(ComponentType::EXPERIENCE, heroLevel);
 
 	if (manaPoints || manaPercentage > 0)
 	{
 		int absoluteMana = h->manaLimit() ? (manaPercentage * h->mana / h->manaLimit() / 100) : 0;
-		comps.emplace_back(Component::EComponentType::PRIM_SKILL, 5, absoluteMana + manaPoints, 0);
+		comps.emplace_back(ComponentType::MANA, absoluteMana + manaPoints);
 	}
 
 	for (size_t i=0; i<primary.size(); i++)
 	{
 		if (primary[i] != 0)
-			comps.emplace_back(Component::EComponentType::PRIM_SKILL, static_cast<ui16>(i), primary[i], 0);
+			comps.emplace_back(ComponentType::PRIM_SKILL, PrimarySkill(i), primary[i]);
 	}
 
 	for(const auto & entry : secondary)
-		comps.emplace_back(Component::EComponentType::SEC_SKILL, entry.first, entry.second, 0);
+		comps.emplace_back(ComponentType::SEC_SKILL, entry.first, entry.second);
 
 	for(const auto & entry : artifacts)
-		comps.emplace_back(Component::EComponentType::ARTIFACT, entry, 1, 0);
+		comps.emplace_back(ComponentType::ARTIFACT, entry);
 
 	for(const auto & entry : spells)
-		comps.emplace_back(Component::EComponentType::SPELL, entry, 1, 0);
+		comps.emplace_back(ComponentType::SPELL, entry);
 
 	for(const auto & entry : creatures)
-		comps.emplace_back(Component::EComponentType::CREATURE, entry.type->getId(), entry.count, 0);
+		comps.emplace_back(ComponentType::CREATURE, entry.type->getId(), entry.count);
 	
 	for(const auto & entry : players)
-		comps.emplace_back(Component::EComponentType::FLAG, entry, 0, 0);
+		comps.emplace_back(ComponentType::FLAG, entry);
 	
-	//FIXME: portrait may not match hero, if custom portrait was set in map editor
 	for(const auto & entry : heroes)
-		comps.emplace_back(Component::EComponentType::HERO_PORTRAIT, VLC->heroTypes()->getById(entry)->getIconIndex(), 0, 0);
+		comps.emplace_back(ComponentType::HERO_PORTRAIT, entry);
 	
-	for(const auto & entry : heroClasses)
-		comps.emplace_back(Component::EComponentType::HERO_PORTRAIT, VLC->heroClasses()->getById(entry)->getIconIndex(), 0, 0);
-
 	for (size_t i=0; i<resources.size(); i++)
 	{
 		if (resources[i] !=0)
-			comps.emplace_back(Component::EComponentType::RESOURCE, static_cast<ui16>(i), resources[i], 0);
+			comps.emplace_back(ComponentType::RESOURCE, GameResID(i), resources[i]);
 	}
 }
 

+ 11 - 11
lib/rewardable/Reward.cpp

@@ -74,42 +74,42 @@ void Rewardable::Reward::loadComponents(std::vector<Component> & comps, const CG
 	for (auto & bonus : bonuses)
 	{
 		if (bonus.type == BonusType::MORALE)
-			comps.emplace_back(Component::EComponentType::MORALE, 0, bonus.val, 0);
+			comps.emplace_back(ComponentType::MORALE, bonus.val);
 		if (bonus.type == BonusType::LUCK)
-			comps.emplace_back(Component::EComponentType::LUCK, 0, bonus.val, 0);
+			comps.emplace_back(ComponentType::LUCK, bonus.val);
 	}
 	
 	if (heroExperience)
-		comps.emplace_back(Component::EComponentType::EXPERIENCE, 0, static_cast<si32>(h ? h->calculateXp(heroExperience) : heroExperience), 0);
+		comps.emplace_back(ComponentType::EXPERIENCE, static_cast<si32>(h ? h->calculateXp(heroExperience) : heroExperience));
 
 	if (heroLevel)
-		comps.emplace_back(Component::EComponentType::EXPERIENCE, 1, heroLevel, 0);
+		comps.emplace_back(ComponentType::LEVEL, heroLevel);
 
 	if (manaDiff || manaPercentage >= 0)
-		comps.emplace_back(Component::EComponentType::PRIM_SKILL, 5, h ? (calculateManaPoints(h) - h->mana) : manaDiff, 0);
+		comps.emplace_back(ComponentType::MANA, h ? (calculateManaPoints(h) - h->mana) : manaDiff);
 
 	for (size_t i=0; i<primary.size(); i++)
 	{
 		if (primary[i] != 0)
-			comps.emplace_back(Component::EComponentType::PRIM_SKILL, static_cast<ui16>(i), primary[i], 0);
+			comps.emplace_back(ComponentType::PRIM_SKILL, PrimarySkill(i), primary[i]);
 	}
 
 	for(const auto & entry : secondary)
-		comps.emplace_back(Component::EComponentType::SEC_SKILL, entry.first, entry.second, 0);
+		comps.emplace_back(ComponentType::SEC_SKILL, entry.first, entry.second);
 
 	for(const auto & entry : artifacts)
-		comps.emplace_back(Component::EComponentType::ARTIFACT, entry, 1, 0);
+		comps.emplace_back(ComponentType::ARTIFACT, entry);
 
 	for(const auto & entry : spells)
-		comps.emplace_back(Component::EComponentType::SPELL, entry, 1, 0);
+		comps.emplace_back(ComponentType::SPELL, entry);
 
 	for(const auto & entry : creatures)
-		comps.emplace_back(Component::EComponentType::CREATURE, entry.type->getId(), entry.count, 0);
+		comps.emplace_back(ComponentType::CREATURE, entry.type->getId(), entry.count);
 
 	for (size_t i=0; i<resources.size(); i++)
 	{
 		if (resources[i] !=0)
-			comps.emplace_back(Component::EComponentType::RESOURCE, static_cast<ui16>(i), resources[i], 0);
+			comps.emplace_back(ComponentType::RESOURCE, GameResID(i), resources[i]);
 	}
 }
 

+ 1 - 2
lib/spells/AdventureSpellMechanics.cpp

@@ -528,8 +528,7 @@ ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, cons
 		request.player = parameters.caster->getCasterOwner();
 		request.title.appendLocalString(EMetaText::JK_TXT, 40);
 		request.description.appendLocalString(EMetaText::JK_TXT, 41);
-		request.icon.id = Component::EComponentType::SPELL;
-		request.icon.subtype = owner->id.toEnum();
+		request.icon = Component(ComponentType::SPELL, owner->id);
 
 		env->genericQuery(&request, request.player, queryCallback);
 

+ 15 - 16
server/CGameHandler.cpp

@@ -1571,11 +1571,12 @@ void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID t
 
 	if (!cs1.spells.empty() || !cs2.spells.empty())//create a message
 	{
-		int ScholarSkillLevel = std::max(h1->getSecSkillLevel(SecondarySkill::SCHOLAR),
-		                                 h2->getSecSkillLevel(SecondarySkill::SCHOLAR));
+		SecondarySkill scholarSkill = SecondarySkill::SCHOLAR;
+
+		int scholarSkillLevel = std::max(h1->getSecSkillLevel(scholarSkill), h2->getSecSkillLevel(scholarSkill));
 		InfoWindow iw;
 		iw.player = h1->tempOwner;
-		iw.components.emplace_back(Component::EComponentType::SEC_SKILL, 18, ScholarSkillLevel, 0);
+		iw.components.emplace_back(ComponentType::SEC_SKILL, scholarSkill, scholarSkillLevel);
 
 		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 139);//"%s, who has studied magic extensively,
 		iw.text.replaceRawString(h1->getNameTranslated());
@@ -1586,7 +1587,7 @@ void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID t
 			int size = static_cast<int>(cs2.spells.size());
 			for (auto it : cs2.spells)
 			{
-				iw.components.emplace_back(Component::EComponentType::SPELL, it, 1, 0);
+				iw.components.emplace_back(ComponentType::SPELL, it);
 				iw.text.appendLocalString(EMetaText::SPELL_NAME, it.toEnum());
 				switch (size--)
 				{
@@ -1614,7 +1615,7 @@ void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID t
 			int size = static_cast<int>(cs1.spells.size());
 			for (auto it : cs1.spells)
 			{
-				iw.components.emplace_back(Component::EComponentType::SPELL, it, 1, 0);
+				iw.components.emplace_back(ComponentType::SPELL, it);
 				iw.text.appendLocalString(EMetaText::SPELL_NAME, it.toEnum());
 				switch (size--)
 				{
@@ -3216,10 +3217,10 @@ void CGameHandler::handleTimeEvents()
 				iw.player = color;
 				iw.text = ev.message;
 
-				for (int i=0; i<ev.resources.size(); i++)
+				for (GameResID i : GameResID::ALL_RESOURCES())
 				{
 					if (ev.resources[i]) //if resource is changed, we add it to the dialog
-						iw.components.emplace_back(Component::EComponentType::RESOURCE,i,ev.resources[i],0);
+						iw.components.emplace_back(ComponentType::RESOURCE, i, ev.resources[i]);
 				}
 
 				sendAndApply(&iw); //show dialog
@@ -3273,10 +3274,9 @@ void CGameHandler::handleTownEvents(CGTownInstance * town, NewTurn &n)
 				n.res[player] += ev.resources;
 				n.res[player].amax(0);
 
-				for (int i=0; i<ev.resources.size(); i++)
+				for (GameResID i : GameResID::ALL_RESOURCES())
 					if (ev.resources[i] && pinfo->resources[i] != n.res.at(player)[i]) //if resource had changed, we add it to the dialog
-						iw.components.emplace_back(Component::EComponentType::RESOURCE,i,n.res.at(player)[i]-was[i],0);
-
+						iw.components.emplace_back(ComponentType::RESOURCE, i, n.res.at(player)[i] - was[i]);
 			}
 
 			for (auto & i : ev.buildings)
@@ -3284,7 +3284,7 @@ void CGameHandler::handleTownEvents(CGTownInstance * town, NewTurn &n)
 				if (!town->hasBuilt(i))
 				{
 					buildStructure(town->id, i, true);
-					iw.components.emplace_back(Component::EComponentType::BUILDING, town->getFaction(), i, 0);
+					iw.components.emplace_back(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), i));
 				}
 			}
 
@@ -3300,8 +3300,7 @@ void CGameHandler::handleTownEvents(CGTownInstance * town, NewTurn &n)
 				if (!town->creatures.at(i).second.empty() && ev.creatures.at(i) > 0)//there is dwelling
 				{
 					sac.creatures[i].first += ev.creatures.at(i);
-					iw.components.emplace_back(Component::EComponentType::CREATURE,
-							town->creatures.at(i).second.back(), ev.creatures.at(i), 0);
+					iw.components.emplace_back(ComponentType::CREATURE, town->creatures.at(i).second.back(), ev.creatures.at(i));
 				}
 			}
 			sendAndApply(&iw); //show dialog
@@ -3641,7 +3640,7 @@ void CGameHandler::getVictoryLossMessage(PlayerColor player, const EVictoryLossC
 	out.player = player;
 	out.text = victoryLossCheckResult.messageToSelf;
 	out.text.replaceLocalString(EMetaText::COLOR, player.getNum());
-	out.components.emplace_back(Component::EComponentType::FLAG, player.getNum(), 0, 0);
+	out.components.emplace_back(ComponentType::FLAG, player);
 }
 
 bool CGameHandler::dig(const CGHeroInstance *h)
@@ -3669,7 +3668,7 @@ bool CGameHandler::dig(const CGHeroInstance *h)
 		sendAndApply(&iw);
 
 		iw.soundID = soundBase::invalid;
-		iw.components.emplace_back(Component::EComponentType::ARTIFACT, ArtifactID::GRAIL, 0, 0);
+		iw.components.emplace_back(ComponentType::ARTIFACT, ArtifactID(ArtifactID::GRAIL));
 		iw.text.clear();
 		iw.text.appendLocalString(EMetaText::ART_DESCR, ArtifactID::GRAIL);
 		sendAndApply(&iw);
@@ -4144,7 +4143,7 @@ const CGHeroInstance * CGameHandler::getVisitingHero(const CGObjectInstance *obj
 {
 	assert(obj);
 
-	for (auto const & query : queries->allQueries())
+	for(const auto & query : queries->allQueries())
 	{
 		auto visit = std::dynamic_pointer_cast<const CObjectVisitQuery>(query);
 		if (visit && visit->visitedObject == obj)

+ 6 - 4
server/battles/BattleResultProcessor.cpp

@@ -419,9 +419,11 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle)
 
 		for (auto art : arts) //TODO; separate function to display loot for various ojects?
 		{
-			iw.components.emplace_back(
-				Component::EComponentType::ARTIFACT, art->artType->getId(),
-				art->artType->getId() == ArtifactID::SPELL_SCROLL? art->getScrollSpellID() : SpellID(0), 0);
+			if (art->artType->getId() == ArtifactID::SPELL_SCROLL)
+				iw.components.emplace_back(ComponentType::SPELL_SCROLL, art->getScrollSpellID());
+			else
+				iw.components.emplace_back(ComponentType::ARTIFACT, art->artType->getId());
+
 			if (iw.components.size() >= 14)
 			{
 				gameHandler->sendAndApply(&iw);
@@ -463,7 +465,7 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle)
 			iw.text.replaceLocalString(EMetaText::SPELL_NAME, it->toEnum());
 			if (i == cs.spells.size() - 2) //we just added pre-last name
 				iw.text.replaceLocalString(EMetaText::GENERAL_TXT, 141); // " and "
-			iw.components.emplace_back(Component::EComponentType::SPELL, *it, 0, 0);
+			iw.components.emplace_back(ComponentType::SPELL, *it);
 		}
 		gameHandler->sendAndApply(&iw);
 		gameHandler->sendAndApply(&cs);