Browse Source

Merge pull request #4812 from SoundSSGood/secSkillPlace

[Hota] SeafaringAcademy. Initial PR
Ivan Savenko 11 months ago
parent
commit
f12119f86c
39 changed files with 415 additions and 368 deletions
  1. 2 2
      client/CMakeLists.txt
  2. 2 0
      client/widgets/CArtifactsOfHeroAltar.cpp
  3. 5 5
      client/widgets/CArtifactsOfHeroBackpack.cpp
  4. 45 38
      client/widgets/CArtifactsOfHeroBase.cpp
  5. 6 4
      client/widgets/CArtifactsOfHeroBase.h
  6. 4 4
      client/widgets/CArtifactsOfHeroKingdom.cpp
  7. 2 0
      client/widgets/CArtifactsOfHeroMain.cpp
  8. 8 5
      client/widgets/CArtifactsOfHeroMarket.cpp
  9. 1 1
      client/widgets/CArtifactsOfHeroMarket.h
  10. 161 101
      client/widgets/CComponentHolder.cpp
  11. 45 20
      client/widgets/CComponentHolder.h
  12. 1 1
      client/widgets/markets/CAltarArtifacts.cpp
  13. 29 21
      client/widgets/markets/CAltarCreatures.cpp
  14. 1 0
      client/widgets/markets/CAltarCreatures.h
  15. 10 10
      client/widgets/markets/CArtifactsBuying.cpp
  16. 4 4
      client/widgets/markets/CArtifactsSelling.cpp
  17. 8 8
      client/widgets/markets/CFreelancerGuild.cpp
  18. 3 3
      client/widgets/markets/CMarketBase.cpp
  19. 11 11
      client/widgets/markets/CMarketResources.cpp
  20. 7 7
      client/widgets/markets/CTransferResources.cpp
  21. 15 23
      client/widgets/markets/TradePanels.cpp
  22. 6 6
      client/widgets/markets/TradePanels.h
  23. 10 9
      client/windows/CCreatureWindow.cpp
  24. 2 2
      client/windows/CCreatureWindow.h
  25. 3 17
      client/windows/CExchangeWindow.cpp
  26. 1 2
      client/windows/CExchangeWindow.h
  27. 0 4
      client/windows/CHeroBackpackWindow.cpp
  28. 1 1
      client/windows/CHeroOverview.cpp
  29. 3 8
      client/windows/CHeroWindow.cpp
  30. 1 2
      client/windows/CHeroWindow.h
  31. 0 1
      client/windows/CMarketWindow.cpp
  32. 0 6
      client/windows/CWindowWithArtifacts.cpp
  33. 0 1
      client/windows/CWindowWithArtifacts.h
  34. 8 26
      client/windows/GUIClasses.cpp
  35. 2 4
      client/windows/GUIClasses.h
  36. 0 8
      lib/CArtifactInstance.cpp
  37. 0 1
      lib/CArtifactInstance.h
  38. 6 1
      lib/CSkillHandler.cpp
  39. 2 1
      lib/CSkillHandler.h

+ 2 - 2
client/CMakeLists.txt

@@ -116,8 +116,8 @@ set(vcmiclientcommon_SRCS
 	globalLobby/GlobalLobbyWindow.cpp
 
 	widgets/Buttons.cpp
-	widgets/CArtPlace.cpp
 	widgets/CComponent.cpp
+	widgets/CComponentHolder.cpp
 	widgets/CExchangeController.cpp
 	widgets/CGarrisonInt.cpp
 	widgets/CreatureCostBox.cpp
@@ -327,8 +327,8 @@ set(vcmiclientcommon_HEADERS
 	globalLobby/GlobalLobbyWindow.h
 
 	widgets/Buttons.h
-	widgets/CArtPlace.h
 	widgets/CComponent.h
+	widgets/CComponentHolder.h
 	widgets/CExchangeController.h
 	widgets/CGarrisonInt.h
 	widgets/CreatureCostBox.h

+ 2 - 0
client/widgets/CArtifactsOfHeroAltar.cpp

@@ -22,6 +22,8 @@
 CArtifactsOfHeroAltar::CArtifactsOfHeroAltar(const Point & position)
 {
 	init(position, std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1));
+	setClickPressedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2));
+	setShowPopupArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
 	enableGesture();
 	// The backpack is in the altar window above and to the right
 	for(auto & slot : backpack)

+ 5 - 5
client/widgets/CArtifactsOfHeroBackpack.cpp

@@ -40,6 +40,8 @@ CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack()
 		visibleCapacityMax = visibleCapacityMax > backpackCap ? backpackCap : visibleCapacityMax;
 
 	initAOHbackpack(visibleCapacityMax, backpackCap < 0 || visibleCapacityMax < backpackCap);
+	setClickPressedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2));
+	setShowPopupArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
 }
 
 void CArtifactsOfHeroBackpack::onSliderMoved(int newVal)
@@ -83,9 +85,7 @@ void CArtifactsOfHeroBackpack::initAOHbackpack(size_t slots, bool slider)
 			slotSizeWithMargin * (artPlaceIdx / slotsColumnsMax));
 		backpackSlotsBackgrounds.emplace_back(std::make_shared<CPicture>(ImagePath::builtin("heroWindow/artifactSlotEmpty"), pos));
 		artPlace = std::make_shared<CArtPlace>(pos);
-		artPlace->setArtifact(nullptr);
-		artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
-		artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
+		artPlace->setArtifact(ArtifactID(ArtifactID::NONE));
 		artPlaceIdx++;
 	}
 
@@ -126,12 +126,11 @@ size_t CArtifactsOfHeroBackpack::calcRows(size_t slots)
 CArtifactsOfHeroQuickBackpack::CArtifactsOfHeroQuickBackpack(const ArtifactPosition filterBySlot)
 	: CArtifactsOfHeroBackpack(0, 0)
 {
-	assert(ArtifactUtils::checkIfSlotValid(*getHero(), filterBySlot));
-
 	if(!ArtifactUtils::isSlotEquipment(filterBySlot))
 		return;
 
 	this->filterBySlot = filterBySlot;
+	setShowPopupArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
 }
 
 void CArtifactsOfHeroQuickBackpack::setHero(const CGHeroInstance * hero)
@@ -174,6 +173,7 @@ void CArtifactsOfHeroQuickBackpack::setHero(const CGHeroInstance * hero)
 		slotsColumnsMax = ceilf(sqrtf(requiredSlots));
 		slotsRowsMax = calcRows(requiredSlots);
 		initAOHbackpack(requiredSlots, false);
+		setClickPressedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2));
 		auto artPlace = backpack.begin();
 		for(auto & art : filteredArts)
 			setSlotData(*artPlace++, curHero->getArtPos(art.second));

+ 45 - 38
client/widgets/CArtifactsOfHeroBase.cpp

@@ -63,18 +63,14 @@ void CArtifactsOfHeroBase::init(
 		auto artPlace = std::make_shared<CArtPlace>(Point(403 + 46 * s, 365));
 		backpack.push_back(artPlace);
 	}
-	for(auto artPlace : artWorn)
+	for(auto & artPlace : artWorn)
 	{
 		artPlace.second->slot = artPlace.first;
-		artPlace.second->setArtifact(nullptr);
-		artPlace.second->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
-		artPlace.second->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
+		artPlace.second->setArtifact(ArtifactID(ArtifactID::NONE));
 	}
-	for(auto artPlace : backpack)
+	for(const auto & artPlace : backpack)
 	{
-		artPlace->setArtifact(nullptr);
-		artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
-		artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
+		artPlace->setArtifact(ArtifactID(ArtifactID::NONE));
 	}
 	leftBackpackRoll = std::make_shared<CButton>(Point(379, 364), AnimationPath::builtin("hsbtns3.def"), CButton::tooltip(),
 		[scrollCallback](){scrollCallback(true);}, EShortcut::MOVE_LEFT);
@@ -89,31 +85,56 @@ void CArtifactsOfHeroBase::init(
 	setRedrawParent(true);
 }
 
-void CArtifactsOfHeroBase::clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition)
+void CArtifactsOfHeroBase::setClickPressedArtPlacesCallback(const CArtPlace::ClickFunctor & callback) const
+{
+	for(const auto & [slot, artPlace] : artWorn)
+		artPlace->setClickPressedCallback(callback);
+	for(const auto & artPlace : backpack)
+		artPlace->setClickPressedCallback(callback);
+}
+
+void CArtifactsOfHeroBase::setShowPopupArtPlacesCallback(const CArtPlace::ClickFunctor & callback) const
 {
-	if(artPlace.isLocked())
+	for(const auto & [slot, artPlace] : artWorn)
+		artPlace->setShowPopupCallback(callback);
+	for(const auto & artPlace : backpack)
+		artPlace->setShowPopupCallback(callback);
+}
+
+void CArtifactsOfHeroBase::clickPressedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition)
+{
+	auto ownedPlace = getArtPlace(cursorPosition);
+	assert(ownedPlace != nullptr);
+
+	if(ownedPlace->isLocked())
 		return;
 
 	if(clickPressedCallback)
-		clickPressedCallback(artPlace, cursorPosition);
+		clickPressedCallback(*ownedPlace, cursorPosition);
 }
 
-void CArtifactsOfHeroBase::showPopupArtPlace(CArtPlace & artPlace, const Point & cursorPosition)
+void CArtifactsOfHeroBase::showPopupArtPlace(CComponentHolder & artPlace, const Point & cursorPosition)
 {
-	if(artPlace.isLocked())
+	auto ownedPlace = getArtPlace(cursorPosition);
+	assert(ownedPlace != nullptr);
+
+	if(ownedPlace->isLocked())
 		return;
 
 	if(showPopupCallback)
-		showPopupCallback(artPlace, cursorPosition);
+		showPopupCallback(*ownedPlace, cursorPosition);
 }
 
-void CArtifactsOfHeroBase::gestureArtPlace(CArtPlace & artPlace, const Point & cursorPosition)
+void CArtifactsOfHeroBase::gestureArtPlace(CComponentHolder & artPlace, const Point & cursorPosition)
 {
-	if(artPlace.isLocked())
+	auto ownedPlace = getArtPlace(cursorPosition);
+	assert(ownedPlace != nullptr);
+
+	if(ownedPlace->isLocked())
 		return;
 
 	if(gestureCallback)
-		gestureCallback(artPlace, cursorPosition);
+		gestureCallback(*ownedPlace, cursorPosition);
 }
 
 void CArtifactsOfHeroBase::setHero(const CGHeroInstance * hero)
@@ -154,26 +175,12 @@ void CArtifactsOfHeroBase::unmarkSlots()
 
 CArtifactsOfHeroBase::ArtPlacePtr CArtifactsOfHeroBase::getArtPlace(const ArtifactPosition & slot)
 {
-	if(ArtifactUtils::isSlotEquipment(slot))
-	{
-		if(artWorn.find(slot) == artWorn.end())
-		{
-			logGlobal->error("CArtifactsOfHero::getArtPlace: invalid slot %d", slot);
-			return nullptr;
-		}
+	if(ArtifactUtils::isSlotEquipment(slot) && artWorn.find(slot) != artWorn.end())
 		return artWorn[slot];
-	}
-	if(ArtifactUtils::isSlotBackpack(slot))
-	{
-		for(ArtPlacePtr artPlace : backpack)
-			if(artPlace->slot == slot)
-				return artPlace;
-		return nullptr;
-	}
-	else
-	{
-		return nullptr;
-	}
+	if(ArtifactUtils::isSlotBackpack(slot) && slot - ArtifactPosition::BACKPACK_START < backpack.size())
+		return(backpack[slot - ArtifactPosition::BACKPACK_START]);
+	logGlobal->error("CArtifactsOfHero::getArtPlace: invalid slot %d", slot);
+	return nullptr;
 }
 
 CArtifactsOfHeroBase::ArtPlacePtr CArtifactsOfHeroBase::getArtPlace(const Point & cursorPosition)
@@ -260,7 +267,7 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit
 	if(auto slotInfo = curHero->getSlot(slot))
 	{
 		artPlace->lockSlot(slotInfo->locked);
-		artPlace->setArtifact(slotInfo->artifact);
+		artPlace->setArtifact(slotInfo->artifact->getTypeId(), slotInfo->artifact->getScrollSpellID());
 		if(slotInfo->locked || slotInfo->artifact->isCombined())
 			return;
 
@@ -285,7 +292,7 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit
 	}
 	else
 	{
-		artPlace->setArtifact(nullptr);
+		artPlace->setArtifact(ArtifactID(ArtifactID::NONE));
 	}
 }
 

+ 6 - 4
client/widgets/CArtifactsOfHeroBase.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "CArtPlace.h"
+#include "CComponentHolder.h"
 #include "Scrollable.h"
 
 #include "../gui/Shortcut.h"
@@ -33,9 +33,9 @@ public:
 	
 	CArtifactsOfHeroBase();
 	virtual void putBackPickedArtifact();
-	virtual void clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition);
-	virtual void showPopupArtPlace(CArtPlace & artPlace, const Point & cursorPosition);
-	virtual void gestureArtPlace(CArtPlace & artPlace, const Point & cursorPosition);
+	virtual void clickPressedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition);
+	virtual void showPopupArtPlace(CComponentHolder & artPlace, const Point & cursorPosition);
+	virtual void gestureArtPlace(CComponentHolder & artPlace, const Point & cursorPosition);
 	virtual void setHero(const CGHeroInstance * hero);
 	virtual const CGHeroInstance * getHero() const;
 	virtual void scrollBackpack(bool left);
@@ -50,6 +50,8 @@ public:
 	void enableGesture();
 	const CArtifactInstance * getArt(const ArtifactPosition & slot) const;
 	void enableKeyboardShortcuts();
+	void setClickPressedArtPlacesCallback(const CArtPlace::ClickFunctor & callback) const;
+	void setShowPopupArtPlacesCallback(const CArtPlace::ClickFunctor & callback) const;
 
 	const CGHeroInstance * curHero;
 	ArtPlaceMap artWorn;

+ 4 - 4
client/widgets/CArtifactsOfHeroKingdom.cpp

@@ -29,15 +29,15 @@ CArtifactsOfHeroKingdom::CArtifactsOfHeroKingdom(ArtPlaceMap ArtWorn, std::vecto
 	for(auto artPlace : artWorn)
 	{
 		artPlace.second->slot = artPlace.first;
-		artPlace.second->setArtifact(nullptr);
-		artPlace.second->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
+		artPlace.second->setArtifact(ArtifactID(ArtifactID::NONE));
+		artPlace.second->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2));
 		artPlace.second->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
 	}
 	enableGesture();
 	for(auto artPlace : backpack)
 	{
-		artPlace->setArtifact(nullptr);
-		artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
+		artPlace->setArtifact(ArtifactID(ArtifactID::NONE));
+		artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2));
 		artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
 	}
 	leftBackpackRoll->addCallback(std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, -1));

+ 2 - 0
client/widgets/CArtifactsOfHeroMain.cpp

@@ -21,6 +21,8 @@
 CArtifactsOfHeroMain::CArtifactsOfHeroMain(const Point & position)
 {
 	init(position, std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1));
+	setClickPressedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2));
+	setShowPopupArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
 	enableGesture();
 }
 

+ 8 - 5
client/widgets/CArtifactsOfHeroMarket.cpp

@@ -15,25 +15,28 @@
 CArtifactsOfHeroMarket::CArtifactsOfHeroMarket(const Point & position, const int selectionWidth)
 {
 	init(position, std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1));
-
+	setClickPressedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2));
 	for(const auto & [slot, artPlace] : artWorn)
 		artPlace->setSelectionWidth(selectionWidth);
 	for(auto artPlace : backpack)
 		artPlace->setSelectionWidth(selectionWidth);
 };
 
-void CArtifactsOfHeroMarket::clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition)
+void CArtifactsOfHeroMarket::clickPressedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition)
 {
-	if(artPlace.isLocked())
+	auto ownedPlace = getArtPlace(cursorPosition);
+	assert(ownedPlace != nullptr);
+
+	if(ownedPlace->isLocked())
 		return;
 
-	if(const auto art = getArt(artPlace.slot))
+	if(const auto art = getArt(ownedPlace->slot))
 	{
 		if(onSelectArtCallback && art->artType->isTradable())
 		{
 			unmarkSlots();
 			artPlace.selectSlot(true);
-			onSelectArtCallback(&artPlace);
+			onSelectArtCallback(ownedPlace.get());
 		}
 		else
 		{

+ 1 - 1
client/widgets/CArtifactsOfHeroMarket.h

@@ -18,5 +18,5 @@ public:
 	std::function<void()> onClickNotTradableCallback;
 
 	CArtifactsOfHeroMarket(const Point & position, const int selectionWidth);
-	void clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition) override;
+	void clickPressedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) override;
 };

+ 161 - 101
client/widgets/CArtPlace.cpp → client/widgets/CComponentHolder.cpp

@@ -1,5 +1,5 @@
 /*
- * CArtPlace.cpp, part of VCMI engine
+ * CComponentHolder.cpp, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -8,14 +8,14 @@
  *
  */
 #include "StdInc.h"
-#include "CArtPlace.h"
+#include "CComponentHolder.h"
 
 #include "../gui/CGuiHandler.h"
 #include "../gui/Shortcut.h"
 
 #include "CComponent.h"
+#include "Images.h"
 
-#include "../windows/GUIClasses.h"
 #include "../render/Canvas.h"
 #include "../render/Colors.h"
 #include "../render/IRenderHandler.h"
@@ -28,27 +28,97 @@
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/networkPacks/ArtifactLocation.h"
 #include "../../lib/CConfigHandler.h"
+#include "../../lib/CSkillHandler.h"
 
-void CArtPlace::setInternals(const CArtifactInstance * artInst)
+CComponentHolder::CComponentHolder(const Rect & area, const Point & selectionOversize)
+	: SelectableSlot(area, selectionOversize)
 {
-	ourArt = artInst;
-	if(!artInst)
+	setClickPressedCallback([this](const CComponentHolder &, const Point & cursorPosition)
+		{
+			if(text.size())
+				LRClickableAreaWTextComp::clickPressed(cursorPosition);
+		});
+	setShowPopupCallback([this](const CComponentHolder &, const Point & cursorPosition)
+		{
+			if(text.size())
+				LRClickableAreaWTextComp::showPopupWindow(cursorPosition);
+		});
+}
+
+void CComponentHolder::setClickPressedCallback(const ClickFunctor & callback)
+{
+	clickPressedCallback = callback;
+}
+
+void CComponentHolder::setShowPopupCallback(const ClickFunctor & callback)
+{
+	showPopupCallback = callback;
+}
+
+void CComponentHolder::setGestureCallback(const ClickFunctor & callback)
+{
+	gestureCallback = callback;
+}
+
+void CComponentHolder::clickPressed(const Point & cursorPosition)
+{
+	if(clickPressedCallback)
+		clickPressedCallback(*this, cursorPosition);
+}
+
+void CComponentHolder::showPopupWindow(const Point & cursorPosition)
+{
+	if(showPopupCallback)
+		showPopupCallback(*this, cursorPosition);
+}
+
+void CComponentHolder::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
+{
+	if(!on)
+		return;
+
+	if(gestureCallback)
+		gestureCallback(*this, initialPosition);
+}
+
+CArtPlace::CArtPlace(Point position, const ArtifactID & artId, const SpellID & spellId)
+	: CComponentHolder(Rect(position, Point(44, 44)), Point(1, 1))
+	, locked(false)
+	, imageIndex(0)
+{
+	OBJECT_CONSTRUCTION;
+
+	image = std::make_shared<CAnimImage>(AnimationPath::builtin("artifact"), 0);
+	setArtifact(artId, spellId);
+	moveSelectionForeground();
+}
+
+void CArtPlace::setArtifact(const SpellID & newSpellId)
+{
+	setArtifact(ArtifactID::SPELL_SCROLL, newSpellId);
+}
+
+void CArtPlace::setArtifact(const ArtifactID & newArtId, const SpellID & newSpellId)
+{
+	artId = newArtId;
+	if(artId == ArtifactID::NONE)
 	{
 		image->disable();
 		text.clear();
-		hoverText = CGI->generaltexth->allTexts[507];
+		lockSlot(false);
 		return;
 	}
 
-	imageIndex = artInst->artType->getIconIndex();
-	if(artInst->getTypeId() == ArtifactID::SPELL_SCROLL)
+	const auto artType = artId.toArtifact();
+	imageIndex = artType->getIconIndex();
+	if(artId == ArtifactID::SPELL_SCROLL)
 	{
-		auto spellID = artInst->getScrollSpellID();
-		assert(spellID.num >= 0);
+		spellId = newSpellId;
+		assert(spellId.num > 0);
 
 		if(settings["general"]["enableUiEnhancements"].Bool())
 		{
-			imageIndex = spellID.num;
+			imageIndex = spellId.num;
 			if(component.type != ComponentType::SPELL_SCROLL)
 			{
 				image->setScale(Point(pos.w, 34));
@@ -58,7 +128,7 @@ void CArtPlace::setInternals(const CArtifactInstance * artInst)
 		}
 		// Add spell component info (used to provide a pic in r-click popup)
 		component.type = ComponentType::SPELL_SCROLL;
-		component.subType = spellID;
+		component.subType = spellId;
 	}
 	else
 	{
@@ -69,47 +139,33 @@ void CArtPlace::setInternals(const CArtifactInstance * artInst)
 			image->moveTo(Point(pos.x, pos.y));
 		}
 		component.type = ComponentType::ARTIFACT;
-		component.subType = artInst->getTypeId();
+		component.subType = artId;
 	}
 	image->enable();
-	text = artInst->getDescription();
-}
-
-CArtPlace::CArtPlace(Point position, const CArtifactInstance * art)
-	: SelectableSlot(Rect(position, Point(44, 44)), Point(1, 1))
-	, ourArt(art)
-	, locked(false)
-{
-	OBJECT_CONSTRUCTION;
+	lockSlot(locked);
 
-	imageIndex = 0;
-	if(locked)
-		imageIndex = ArtifactID::ART_LOCK;
-	else if(ourArt)
-		imageIndex = ourArt->artType->getIconIndex();
-
-	image = std::make_shared<CAnimImage>(AnimationPath::builtin("artifact"), imageIndex);
-	image->disable();
-	moveSelectionForeground();
+	text = artType->getDescriptionTranslated();
+	if(artType->isScroll())
+		ArtifactUtils::insertScrrollSpellName(text, spellId);
 }
 
-const CArtifactInstance * CArtPlace::getArt() const
+ArtifactID CArtPlace::getArtifactId() const
 {
-	return ourArt;
+	return artId;
 }
 
-CCommanderArtPlace::CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot, const CArtifactInstance * art)
-	: CArtPlace(position, art),
+CCommanderArtPlace::CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot,
+	const ArtifactID & artId, const SpellID & spellId)
+	: CArtPlace(position, artId, spellId),
 	commanderOwner(commanderOwner),
 	commanderSlotID(artSlot.num)
 {
-	setArtifact(art);
 }
 
 void CCommanderArtPlace::returnArtToHeroCallback()
 {
 	ArtifactPosition artifactPos = commanderSlotID;
-	ArtifactPosition freeSlot = ArtifactUtils::getArtBackpackPosition(commanderOwner, getArt()->getTypeId());
+	ArtifactPosition freeSlot = ArtifactUtils::getArtBackpackPosition(commanderOwner, getArtifactId());
 	if(freeSlot == ArtifactPosition::PRE_FIRST)
 	{
 		LOCPLINT->showInfoDialog(CGI->generaltexth->translate("core.genrltxt.152"));
@@ -120,10 +176,10 @@ void CCommanderArtPlace::returnArtToHeroCallback()
 		src.creature = SlotID::COMMANDER_SLOT_PLACEHOLDER;
 		ArtifactLocation dst(commanderOwner->id, freeSlot);
 
-		if(getArt()->canBePutAt(commanderOwner, freeSlot, true))
+		if(getArtifactId().toArtifact()->canBePutAt(commanderOwner, freeSlot, true))
 		{
 			LOCPLINT->cb->swapArtifacts(src, dst);
-			setArtifact(nullptr);
+			setArtifact(ArtifactID(ArtifactID::NONE));
 			parent->redraw();
 		}
 	}
@@ -131,88 +187,40 @@ void CCommanderArtPlace::returnArtToHeroCallback()
 
 void CCommanderArtPlace::clickPressed(const Point & cursorPosition)
 {
-	if(getArt() && text.size())
+	if(getArtifactId() != ArtifactID::NONE && text.size())
 		LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.commanderWindow.artifactMessage"), [this]() { returnArtToHeroCallback(); }, []() {});
 }
 
 void CCommanderArtPlace::showPopupWindow(const Point & cursorPosition)
 {
-	if(getArt() && text.size())
+	if(getArtifactId() != ArtifactID::NONE && text.size())
 		CArtPlace::showPopupWindow(cursorPosition);
 }
 
 void CArtPlace::lockSlot(bool on)
 {
-	if(locked == on)
-		return;
-
 	locked = on;
-
 	if(on)
+	{
 		image->setFrame(ArtifactID::ART_LOCK);
-	else if(ourArt)
-		image->setFrame(imageIndex);
-	else
-		image->setFrame(0);
-}
-
-bool CArtPlace::isLocked() const
-{
-	return locked;
-}
-
-void CArtPlace::clickPressed(const Point & cursorPosition)
-{
-	if(clickPressedCallback)
-		clickPressedCallback(*this, cursorPosition);
-}
-
-void CArtPlace::showPopupWindow(const Point & cursorPosition)
-{
-	if(showPopupCallback)
-		showPopupCallback(*this, cursorPosition);
-}
-
-void CArtPlace::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
-{
-	if(!on)
-		return;
-
-	if(gestureCallback)
-		gestureCallback(*this, initialPosition);
-}
-
-void CArtPlace::setArtifact(const CArtifactInstance * art)
-{
-	setInternals(art);
-	if(art)
+		hoverText = CGI->generaltexth->allTexts[507];
+	}
+	else if(artId != ArtifactID::NONE)
 	{
-		image->setFrame(locked ? static_cast<int>(ArtifactID::ART_LOCK) : imageIndex);
-
-		if(locked) // Locks should appear as empty.
-			hoverText = CGI->generaltexth->allTexts[507];
-		else
-			hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[1]) % ourArt->artType->getNameTranslated());
+		image->setFrame(imageIndex);
+		auto hoverText = MetaString::createFromRawString(CGI->generaltexth->heroscrn[1]);
+		hoverText.replaceName(artId);
+		this->hoverText = hoverText.toString();
 	}
 	else
 	{
-		lockSlot(false);
+		hoverText = CGI->generaltexth->allTexts[507];
 	}
 }
 
-void CArtPlace::setClickPressedCallback(const ClickFunctor & callback)
-{
-	clickPressedCallback = callback;
-}
-
-void CArtPlace::setShowPopupCallback(const ClickFunctor & callback)
-{
-	showPopupCallback = callback;
-}
-
-void CArtPlace::setGestureCallback(const ClickFunctor & callback)
+bool CArtPlace::isLocked() const
 {
-	gestureCallback = callback;
+	return locked;
 }
 
 void CArtPlace::addCombinedArtInfo(const std::map<const ArtifactID, std::vector<ArtifactID>> & arts)
@@ -252,3 +260,55 @@ void CArtPlace::addCombinedArtInfo(const std::map<const ArtifactID, std::vector<
 		text += info.toString();
 	}
 }
+
+CSecSkillPlace::CSecSkillPlace(const Point & position, const ImageSize & imageSize, const SecondarySkill & newSkillId, const uint8_t level)
+	: CComponentHolder(Rect(position, Point()), Point())
+{
+	OBJECT_CONSTRUCTION;
+
+	auto imagePath = AnimationPath::builtin("SECSKILL");
+	if(imageSize == ImageSize::MEDIUM)
+		imagePath = AnimationPath::builtin("SECSK32");
+	if(imageSize == ImageSize::SMALL)
+		imagePath = AnimationPath::builtin("SECSK82");
+
+	image = std::make_shared<CAnimImage>(imagePath, 0);
+	component.type = ComponentType::SEC_SKILL;
+	pos.w = image->pos.w;
+	pos.h = image->pos.h;
+	setSkill(newSkillId, level);
+}
+
+void CSecSkillPlace::setSkill(const SecondarySkill & newSkillId, const uint8_t level)
+{
+	skillId = newSkillId;
+	component.subType = newSkillId;
+	setLevel(level);
+}
+
+void CSecSkillPlace::setLevel(const uint8_t level)
+{
+	// 0 - none
+	// 1 - base
+	// 2 - advanced
+	// 3 - expert
+	assert(level <= 3);
+	if(skillId != SecondarySkill::NONE && level > 0)
+	{
+		const auto secSkill = skillId.toSkill();
+		image->setFrame(secSkill->getIconIndex(level - 1));
+		image->enable();
+		auto hoverText = MetaString::createFromRawString(CGI->generaltexth->heroscrn[21]);
+		hoverText.replaceRawString(CGI->generaltexth->levels[level - 1]);
+		hoverText.replaceTextID(secSkill->getNameTextID());
+		this->hoverText = hoverText.toString();
+		component.value = level;
+		text = secSkill->getDescriptionTranslated(level);
+	}
+	else
+	{
+		image->disable();
+		hoverText.clear();
+		text.clear();
+	}
+}

+ 45 - 20
client/widgets/CArtPlace.h → client/widgets/CComponentHolder.h

@@ -1,5 +1,5 @@
 /*
- * CArtPlace.h, part of VCMI engine
+ * CComponentHolder.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -13,37 +13,43 @@
 
 class CAnimImage;
 
-class CArtPlace : public SelectableSlot
+class CComponentHolder : public SelectableSlot
 {
 public:
-	using ClickFunctor = std::function<void(CArtPlace&, const Point&)>;
+	using ClickFunctor = std::function<void(CComponentHolder&, const Point&)>;
 
-	ArtifactPosition slot;
-	
-	CArtPlace(Point position, const CArtifactInstance * art = nullptr);
-	const CArtifactInstance * getArt() const;
-	void lockSlot(bool on);
-	bool isLocked() const;
-	void setArtifact(const CArtifactInstance * art);
+	ClickFunctor clickPressedCallback;
+	ClickFunctor showPopupCallback;
+	ClickFunctor gestureCallback;
+	std::shared_ptr<CAnimImage> image;
+
+	CComponentHolder(const Rect & area, const Point & selectionOversize);
 	void setClickPressedCallback(const ClickFunctor & callback);
 	void setShowPopupCallback(const ClickFunctor & callback);
 	void setGestureCallback(const ClickFunctor & callback);
 	void clickPressed(const Point & cursorPosition) override;
 	void showPopupWindow(const Point & cursorPosition) override;
 	void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
+};
+
+class CArtPlace : public CComponentHolder
+{
+public:
+	ArtifactPosition slot;
+	
+	CArtPlace(Point position, const ArtifactID & newArtId = ArtifactID::NONE, const SpellID & newSpellId = SpellID::NONE);
+	void setArtifact(const SpellID & newSpellId);
+	void setArtifact(const ArtifactID & newArtId, const SpellID & newSpellId = SpellID::NONE);
+	ArtifactID getArtifactId() const;
+	void lockSlot(bool on);
+	bool isLocked() const;
 	void addCombinedArtInfo(const std::map<const ArtifactID, std::vector<ArtifactID>> & arts);
 
 private:
-	const CArtifactInstance * ourArt;
+	ArtifactID artId;
+	SpellID spellId;
 	bool locked;
-	int imageIndex;
-	std::shared_ptr<CAnimImage> image;
-	ClickFunctor clickPressedCallback;
-	ClickFunctor showPopupCallback;
-	ClickFunctor gestureCallback;
-
-protected:
-	void setInternals(const CArtifactInstance * artInst);
+	int32_t imageIndex;
 };
 
 class CCommanderArtPlace : public CArtPlace
@@ -55,7 +61,26 @@ private:
 	void returnArtToHeroCallback();
 
 public:
-	CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot, const CArtifactInstance * art = nullptr);
+	CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot,
+		const ArtifactID & artId = ArtifactID::NONE, const SpellID & spellId = SpellID::NONE);
 	void clickPressed(const Point & cursorPosition) override;
 	void showPopupWindow(const Point & cursorPosition) override;
 };
+
+class CSecSkillPlace : public CComponentHolder
+{
+public:
+	enum class ImageSize
+	{
+		LARGE,
+		MEDIUM,
+		SMALL
+	};
+
+	CSecSkillPlace(const Point & position, const ImageSize & imageSize, const SecondarySkill & skillId = SecondarySkill::NONE, const uint8_t level = 0);
+	void setSkill(const SecondarySkill & newSkillId, const uint8_t level = 0);
+	void setLevel(const uint8_t level);
+
+private:
+	SecondarySkill skillId;
+};

+ 1 - 1
client/widgets/markets/CAltarArtifacts.cpp

@@ -58,7 +58,7 @@ CAltarArtifacts::CAltarArtifacts(const IMarket * market, const CGHeroInstance *
 			CAltarArtifacts::onSlotClickPressed(altarSlot, offerTradePanel);
 		});
 	offerTradePanel->updateSlotsCallback = std::bind(&CAltarArtifacts::updateAltarSlots, this);
-	offerTradePanel->moveTo(pos.topLeft() + Point(315, 52));
+	offerTradePanel->moveTo(pos.topLeft() + Point(315, 53));
 
 	CMarketBase::updateShowcases();
 	CAltarArtifacts::deselect();

+ 29 - 21
client/widgets/markets/CAltarCreatures.cpp

@@ -162,9 +162,9 @@ void CAltarCreatures::makeDeal()
 	for(int & units : unitsOnAltar)
 		units = 0;
 
-	for(auto heroSlot : offerTradePanel->slots)
+	for(const auto & heroSlot : offerTradePanel->slots)
 	{
-		heroSlot->setType(EType::CREATURE_PLACEHOLDER);
+		heroSlot->setID(CreatureID::NONE);
 		heroSlot->subtitle->clear();
 	}
 	deselect();
@@ -175,16 +175,16 @@ CMarketBase::MarketShowcasesParams CAltarCreatures::getShowcasesParams() const
 	std::optional<ShowcaseParams> bidSelected = std::nullopt;
 	std::optional<ShowcaseParams> offerSelected = std::nullopt;
 	if(bidTradePanel->isHighlighted())
-		bidSelected = ShowcaseParams {std::to_string(offerSlider->getValue()), CGI->creatures()->getByIndex(bidTradePanel->getSelectedItemId())->getIconIndex()};
+		bidSelected = ShowcaseParams {std::to_string(offerSlider->getValue()), CGI->creatures()->getByIndex(bidTradePanel->getHighlightedItemId())->getIconIndex()};
 	if(offerTradePanel->isHighlighted() && offerSlider->getValue() > 0)
-		offerSelected = ShowcaseParams {offerTradePanel->highlightedSlot->subtitle->getText(), CGI->creatures()->getByIndex(offerTradePanel->getSelectedItemId())->getIconIndex()};
+		offerSelected = ShowcaseParams {offerTradePanel->highlightedSlot->subtitle->getText(), CGI->creatures()->getByIndex(offerTradePanel->getHighlightedItemId())->getIconIndex()};
 	return MarketShowcasesParams {bidSelected, offerSelected};
 }
 
 void CAltarCreatures::sacrificeAll()
 {
 	std::optional<SlotID> lastSlot;
-	for(auto heroSlot : bidTradePanel->slots)
+	for(const auto & heroSlot : bidTradePanel->slots)
 	{
 		auto stackCount = hero->getStackCount(SlotID(heroSlot->serial));
 		if(stackCount > unitsOnAltar[heroSlot->serial])
@@ -211,7 +211,8 @@ void CAltarCreatures::sacrificeAll()
 void CAltarCreatures::updateAltarSlot(const std::shared_ptr<CTradeableItem> & slot)
 {
 	auto units = unitsOnAltar[slot->serial];
-	slot->setType(units > 0 ? EType::CREATURE : EType::CREATURE_PLACEHOLDER);
+	const auto [oppositeSlot, oppositePanel] = getOpposite(slot);
+	slot->setID(units > 0 ? oppositeSlot->id : CreatureID::NONE);
 	slot->subtitle->setText(units > 0 ?
 		boost::str(boost::format(CGI->generaltexth->allTexts[122]) % std::to_string(hero->calculateXp(units * expPerUnit[slot->serial]))) : "");
 }
@@ -234,21 +235,9 @@ void CAltarCreatures::onSlotClickPressed(const std::shared_ptr<CTradeableItem> &
 	if(newSlot == curPanel->highlightedSlot)
 		return;
 
-	auto oppositePanel = bidTradePanel;
 	curPanel->onSlotClickPressed(newSlot);
-	if(curPanel->highlightedSlot == bidTradePanel->highlightedSlot)
-	{
-		oppositePanel = offerTradePanel;
-	}
-	std::shared_ptr<CTradeableItem> oppositeNewSlot;
-	for(const auto & slot : oppositePanel->slots)
-		if(slot->serial == newSlot->serial)
-		{
-			oppositeNewSlot = slot;
-			break;
-		}
-	assert(oppositeNewSlot);
-	oppositePanel->onSlotClickPressed(oppositeNewSlot);
+	auto [oppositeSlot, oppositePanel] = getOpposite(newSlot);
+	oppositePanel->onSlotClickPressed(oppositeSlot);
 	highlightingChanged();
 	redraw();
 }
@@ -258,7 +247,7 @@ std::string CAltarCreatures::getTraderText()
 	if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
 	{
 		MetaString message = MetaString::createFromTextID("core.genrltxt.484");
-		message.replaceNamePlural(CreatureID(bidTradePanel->getSelectedItemId()));
+		message.replaceNamePlural(CreatureID(bidTradePanel->getHighlightedItemId()));
 		return message.toString();
 	}
 	else
@@ -266,3 +255,22 @@ std::string CAltarCreatures::getTraderText()
 		return "";
 	}
 }
+
+std::tuple<const std::shared_ptr<CTradeableItem>, std::shared_ptr<TradePanelBase>> CAltarCreatures::getOpposite(
+	const std::shared_ptr<CTradeableItem> & curSlot)
+{
+	assert(curSlot);
+
+	auto oppositePanel = bidTradePanel;
+	if(vstd::contains(bidTradePanel->slots, curSlot))
+		oppositePanel = offerTradePanel;
+
+	std::shared_ptr<CTradeableItem> oppositeSlot;
+	for(const auto & slot : oppositePanel->slots)
+		if (slot->serial == curSlot->serial)
+		{
+			oppositeSlot = slot;
+			break;
+		}
+	return std::make_tuple(oppositeSlot, oppositePanel);
+}

+ 1 - 0
client/widgets/markets/CAltarCreatures.h

@@ -33,4 +33,5 @@ private:
 	void onOfferSliderMoved(int newVal) override;
 	void onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<TradePanelBase> & curPanel) override;
 	std::string getTraderText() override;
+	std::tuple<const std::shared_ptr<CTradeableItem>, std::shared_ptr<TradePanelBase>> getOpposite(const std::shared_ptr<CTradeableItem> & curSlot);
 };

+ 10 - 10
client/widgets/markets/CArtifactsBuying.cpp

@@ -46,7 +46,7 @@ CArtifactsBuying::CArtifactsBuying(const IMarket * market, const CGHeroInstance
 			CArtifactsBuying::onSlotClickPressed(newSlot, offerTradePanel);
 		}, [this]()
 		{
-			CMarketBase::updateSubtitlesForBid(EMarketMode::RESOURCE_ARTIFACT, bidTradePanel->getSelectedItemId());
+			CMarketBase::updateSubtitlesForBid(EMarketMode::RESOURCE_ARTIFACT, bidTradePanel->getHighlightedItemId());
 		}, market->availableItemsIds(EMarketMode::RESOURCE_ARTIFACT));
 	offerTradePanel->deleteSlotsCheck = [this](const std::shared_ptr<CTradeableItem> & slot)
 	{
@@ -66,10 +66,10 @@ void CArtifactsBuying::deselect()
 
 void CArtifactsBuying::makeDeal()
 {
-	if(ArtifactID(offerTradePanel->getSelectedItemId()).toArtifact()->canBePutAt(hero))
+	if(ArtifactID(offerTradePanel->getHighlightedItemId()).toArtifact()->canBePutAt(hero))
 	{
-		LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_ARTIFACT, GameResID(bidTradePanel->getSelectedItemId()),
-			ArtifactID(offerTradePanel->getSelectedItemId()), offerQty, hero);
+		LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_ARTIFACT, GameResID(bidTradePanel->getHighlightedItemId()),
+			ArtifactID(offerTradePanel->getHighlightedItemId()), offerQty, hero);
 		CMarketTraderText::makeDeal();
 		deselect();
 	}
@@ -84,8 +84,8 @@ CMarketBase::MarketShowcasesParams CArtifactsBuying::getShowcasesParams() const
 	if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
 		return MarketShowcasesParams
 		{
-			ShowcaseParams {std::to_string(deal->isBlocked() ? 0 : bidQty), bidTradePanel->getSelectedItemId()},
-			ShowcaseParams {std::to_string(deal->isBlocked() ? 0 : offerQty), CGI->artifacts()->getByIndex(offerTradePanel->getSelectedItemId())->getIconIndex()}
+			ShowcaseParams {std::to_string(deal->isBlocked() ? 0 : bidQty), bidTradePanel->getHighlightedItemId()},
+			ShowcaseParams {std::to_string(deal->isBlocked() ? 0 : offerQty), CGI->artifacts()->getByIndex(offerTradePanel->getHighlightedItemId())->getIconIndex()}
 		};
 	else
 		return MarketShowcasesParams {std::nullopt, std::nullopt};
@@ -95,8 +95,8 @@ void CArtifactsBuying::highlightingChanged()
 {
 	if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
 	{
-		market->getOffer(bidTradePanel->getSelectedItemId(), offerTradePanel->getSelectedItemId(), bidQty, offerQty, EMarketMode::RESOURCE_ARTIFACT);
-		deal->block(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getSelectedItemId())) < bidQty || !LOCPLINT->makingTurn);
+		market->getOffer(bidTradePanel->getHighlightedItemId(), offerTradePanel->getHighlightedItemId(), bidQty, offerQty, EMarketMode::RESOURCE_ARTIFACT);
+		deal->block(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getHighlightedItemId())) < bidQty || !LOCPLINT->makingTurn);
 	}
 	CMarketBase::highlightingChanged();
 	CMarketTraderText::highlightingChanged();
@@ -107,10 +107,10 @@ std::string CArtifactsBuying::getTraderText()
 	if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
 	{
 		MetaString message = MetaString::createFromTextID("core.genrltxt.267");
-		message.replaceName(ArtifactID(offerTradePanel->getSelectedItemId()));
+		message.replaceName(ArtifactID(offerTradePanel->getHighlightedItemId()));
 		message.replaceNumber(bidQty);
 		message.replaceTextID(bidQty == 1 ? "core.genrltxt.161" : "core.genrltxt.160");
-		message.replaceName(GameResID(bidTradePanel->getSelectedItemId()));
+		message.replaceName(GameResID(bidTradePanel->getHighlightedItemId()));
 		return message.toString();
 	}
 	else

+ 4 - 4
client/widgets/markets/CArtifactsSelling.cpp

@@ -79,7 +79,7 @@ void CArtifactsSelling::makeDeal()
 	const auto art = hero->getArt(selectedHeroSlot);
 	assert(art);
 	LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::ARTIFACT_RESOURCE, art->getId(),
-		GameResID(offerTradePanel->getSelectedItemId()), offerQty, hero);
+		GameResID(offerTradePanel->getHighlightedItemId()), offerQty, hero);
 	CMarketTraderText::makeDeal();
 }
 
@@ -129,7 +129,7 @@ CMarketBase::MarketShowcasesParams CArtifactsSelling::getShowcasesParams() const
 		return MarketShowcasesParams
 		{
 			std::nullopt,
-			ShowcaseParams {std::to_string(offerQty), offerTradePanel->getSelectedItemId()}
+			ShowcaseParams {std::to_string(offerQty), offerTradePanel->getHighlightedItemId()}
 		};
 	else
 		return MarketShowcasesParams {std::nullopt, std::nullopt};
@@ -147,7 +147,7 @@ void CArtifactsSelling::highlightingChanged()
 	const auto art = hero->getArt(selectedHeroSlot);
 	if(art && offerTradePanel->isHighlighted())
 	{
-		market->getOffer(art->getTypeId(), offerTradePanel->getSelectedItemId(), bidQty, offerQty, EMarketMode::ARTIFACT_RESOURCE);
+		market->getOffer(art->getTypeId(), offerTradePanel->getHighlightedItemId(), bidQty, offerQty, EMarketMode::ARTIFACT_RESOURCE);
 		deal->block(!LOCPLINT->makingTurn);
 	}
 	CMarketBase::highlightingChanged();
@@ -162,7 +162,7 @@ std::string CArtifactsSelling::getTraderText()
 		MetaString message = MetaString::createFromTextID("core.genrltxt.268");
 		message.replaceNumber(offerQty);
 		message.replaceRawString(offerQty == 1 ? CGI->generaltexth->allTexts[161] : CGI->generaltexth->allTexts[160]);
-		message.replaceName(GameResID(offerTradePanel->getSelectedItemId()));
+		message.replaceName(GameResID(offerTradePanel->getHighlightedItemId()));
 		message.replaceName(art->getTypeId());
 		return message.toString();
 	}

+ 8 - 8
client/widgets/markets/CFreelancerGuild.cpp

@@ -29,7 +29,7 @@ CFreelancerGuild::CFreelancerGuild(const IMarket * market, const CGHeroInstance
 	: CMarketBase(market, hero)
 	, CResourcesBuying(
 		[this](const std::shared_ptr<CTradeableItem> & heroSlot){CFreelancerGuild::onSlotClickPressed(heroSlot, offerTradePanel);},
-		[this](){CMarketBase::updateSubtitlesForBid(EMarketMode::CREATURE_RESOURCE, bidTradePanel->getSelectedItemId());})
+		[this](){CMarketBase::updateSubtitlesForBid(EMarketMode::CREATURE_RESOURCE, bidTradePanel->getHighlightedItemId());})
 	, CMarketSlider([this](int newVal){CMarketSlider::onOfferSliderMoved(newVal);})
 {
 	OBJECT_CONSTRUCTION;
@@ -69,7 +69,7 @@ void CFreelancerGuild::makeDeal()
 {
 	if(auto toTrade = offerSlider->getValue(); toTrade != 0)
 	{
-		LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::CREATURE_RESOURCE, SlotID(bidTradePanel->highlightedSlot->serial), GameResID(offerTradePanel->getSelectedItemId()), bidQty * toTrade, hero);
+		LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::CREATURE_RESOURCE, SlotID(bidTradePanel->highlightedSlot->serial), GameResID(offerTradePanel->getHighlightedItemId()), bidQty * toTrade, hero);
 		CMarketTraderText::makeDeal();
 		deselect();
 	}
@@ -80,8 +80,8 @@ CMarketBase::MarketShowcasesParams CFreelancerGuild::getShowcasesParams() const
 	if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
 		return MarketShowcasesParams
 		{
-			ShowcaseParams {std::to_string(bidQty * offerSlider->getValue()), CGI->creatures()->getByIndex(bidTradePanel->getSelectedItemId())->getIconIndex()},
-			ShowcaseParams {std::to_string(offerQty * offerSlider->getValue()), offerTradePanel->getSelectedItemId()}
+			ShowcaseParams {std::to_string(bidQty * offerSlider->getValue()), CGI->creatures()->getByIndex(bidTradePanel->getHighlightedItemId())->getIconIndex()},
+			ShowcaseParams {std::to_string(offerQty * offerSlider->getValue()), offerTradePanel->getHighlightedItemId()}
 		};
 	else
 		return MarketShowcasesParams {std::nullopt, std::nullopt};
@@ -91,7 +91,7 @@ void CFreelancerGuild::highlightingChanged()
 {
 	if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
 	{
-		market->getOffer(bidTradePanel->getSelectedItemId(), offerTradePanel->getSelectedItemId(), bidQty, offerQty, EMarketMode::CREATURE_RESOURCE);
+		market->getOffer(bidTradePanel->getHighlightedItemId(), offerTradePanel->getHighlightedItemId(), bidQty, offerQty, EMarketMode::CREATURE_RESOURCE);
 		offerSlider->setAmount((hero->getStackCount(SlotID(bidTradePanel->highlightedSlot->serial)) - (hero->stacksCount() == 1 && hero->needsLastStack() ? 1 : 0)) / bidQty);
 		offerSlider->scrollTo(0);
 		offerSlider->block(false);
@@ -109,12 +109,12 @@ std::string CFreelancerGuild::getTraderText()
 		MetaString message = MetaString::createFromTextID("core.genrltxt.269");
 		message.replaceNumber(offerQty);
 		message.replaceRawString(offerQty == 1 ? CGI->generaltexth->allTexts[161] : CGI->generaltexth->allTexts[160]);
-		message.replaceName(GameResID(offerTradePanel->getSelectedItemId()));
+		message.replaceName(GameResID(offerTradePanel->getHighlightedItemId()));
 		message.replaceNumber(bidQty);
 		if(bidQty == 1)
-			message.replaceNameSingular(bidTradePanel->getSelectedItemId());
+			message.replaceNameSingular(bidTradePanel->getHighlightedItemId());
 		else
-			message.replaceNamePlural(bidTradePanel->getSelectedItemId());
+			message.replaceNamePlural(bidTradePanel->getHighlightedItemId());
 		return message.toString();
 	}
 	else

+ 3 - 3
client/widgets/markets/CMarketBase.cpp

@@ -93,7 +93,7 @@ void CMarketBase::updateSubtitlesForBid(EMarketMode marketMode, int bidId)
 
 void CMarketBase::updateShowcases()
 {
-	const auto updateSelectedBody = [](const std::shared_ptr<TradePanelBase> & tradePanel, const std::optional<const ShowcaseParams> & params)
+	const auto updateShowcase = [](const std::shared_ptr<TradePanelBase> & tradePanel, const std::optional<const ShowcaseParams> & params)
 	{
 		if(params.has_value())
 		{
@@ -109,9 +109,9 @@ void CMarketBase::updateShowcases()
 
 	const auto params = getShowcasesParams();
 	if(bidTradePanel)
-		updateSelectedBody(bidTradePanel, params.bidParams);
+		updateShowcase(bidTradePanel, params.bidParams);
 	if(offerTradePanel)
-		updateSelectedBody(offerTradePanel, params.offerParams);
+		updateShowcase(offerTradePanel, params.offerParams);
 }
 
 void CMarketBase::highlightingChanged()

+ 11 - 11
client/widgets/markets/CMarketResources.cpp

@@ -60,7 +60,7 @@ void CMarketResources::makeDeal()
 {
 	if(auto toTrade = offerSlider->getValue(); toTrade != 0)
 	{
-		LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(bidTradePanel->getSelectedItemId()),
+		LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(bidTradePanel->getHighlightedItemId()),
 			GameResID(offerTradePanel->highlightedSlot->id), bidQty * toTrade, hero);
 		CMarketTraderText::makeDeal();
 		deselect();
@@ -69,11 +69,11 @@ void CMarketResources::makeDeal()
 
 CMarketBase::MarketShowcasesParams CMarketResources::getShowcasesParams() const
 {
-	if(bidTradePanel->highlightedSlot && offerTradePanel->highlightedSlot && bidTradePanel->getSelectedItemId() != offerTradePanel->getSelectedItemId())
+	if(bidTradePanel->highlightedSlot && offerTradePanel->highlightedSlot && bidTradePanel->getHighlightedItemId() != offerTradePanel->getHighlightedItemId())
 		return MarketShowcasesParams
 		{
-			ShowcaseParams {std::to_string(bidQty * offerSlider->getValue()), bidTradePanel->getSelectedItemId()},
-			ShowcaseParams {std::to_string(offerQty * offerSlider->getValue()), offerTradePanel->getSelectedItemId()}
+			ShowcaseParams {std::to_string(bidQty * offerSlider->getValue()), bidTradePanel->getHighlightedItemId()},
+			ShowcaseParams {std::to_string(offerQty * offerSlider->getValue()), offerTradePanel->getHighlightedItemId()}
 		};
 	else
 		return MarketShowcasesParams {std::nullopt, std::nullopt};
@@ -83,10 +83,10 @@ void CMarketResources::highlightingChanged()
 {
 	if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
 	{
-		market->getOffer(bidTradePanel->getSelectedItemId(), offerTradePanel->getSelectedItemId(), bidQty, offerQty, EMarketMode::RESOURCE_RESOURCE);
-		offerSlider->setAmount(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getSelectedItemId())) / bidQty);
+		market->getOffer(bidTradePanel->getHighlightedItemId(), offerTradePanel->getHighlightedItemId(), bidQty, offerQty, EMarketMode::RESOURCE_RESOURCE);
+		offerSlider->setAmount(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getHighlightedItemId())) / bidQty);
 		offerSlider->scrollTo(0);
-		const bool isControlsBlocked = bidTradePanel->getSelectedItemId() != offerTradePanel->getSelectedItemId() ? false : true;
+		const bool isControlsBlocked = bidTradePanel->getHighlightedItemId() != offerTradePanel->getHighlightedItemId() ? false : true;
 		offerSlider->block(isControlsBlocked);
 		maxAmount->block(isControlsBlocked);
 		deal->block(isControlsBlocked || !LOCPLINT->makingTurn);
@@ -97,7 +97,7 @@ void CMarketResources::highlightingChanged()
 
 void CMarketResources::updateSubtitles()
 {
-	CMarketBase::updateSubtitlesForBid(EMarketMode::RESOURCE_RESOURCE, bidTradePanel->getSelectedItemId());
+	CMarketBase::updateSubtitlesForBid(EMarketMode::RESOURCE_RESOURCE, bidTradePanel->getHighlightedItemId());
 	if(bidTradePanel->highlightedSlot)
 		offerTradePanel->slots[bidTradePanel->highlightedSlot->serial]->subtitle->setText(CGI->generaltexth->allTexts[164]); // n/a
 }
@@ -105,15 +105,15 @@ void CMarketResources::updateSubtitles()
 std::string CMarketResources::getTraderText()
 {
 	if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted() &&
-		bidTradePanel->getSelectedItemId() != offerTradePanel->getSelectedItemId())
+		bidTradePanel->getHighlightedItemId() != offerTradePanel->getHighlightedItemId())
 	{
 		MetaString message = MetaString::createFromTextID("core.genrltxt.157");
 		message.replaceNumber(offerQty);
 		message.replaceRawString(offerQty == 1 ? CGI->generaltexth->allTexts[161] : CGI->generaltexth->allTexts[160]);
-		message.replaceName(GameResID(bidTradePanel->getSelectedItemId()));
+		message.replaceName(GameResID(bidTradePanel->getHighlightedItemId()));
 		message.replaceNumber(bidQty);
 		message.replaceRawString(bidQty == 1 ? CGI->generaltexth->allTexts[161] : CGI->generaltexth->allTexts[160]);
-		message.replaceName(GameResID(offerTradePanel->getSelectedItemId()));
+		message.replaceName(GameResID(offerTradePanel->getHighlightedItemId()));
 		return message.toString();
 	}
 	else

+ 7 - 7
client/widgets/markets/CTransferResources.cpp

@@ -64,8 +64,8 @@ void CTransferResources::makeDeal()
 {
 	if(auto toTrade = offerSlider->getValue(); toTrade != 0)
 	{
-		LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_PLAYER, GameResID(bidTradePanel->getSelectedItemId()),
-			PlayerColor(offerTradePanel->getSelectedItemId()), toTrade, hero);
+		LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_PLAYER, GameResID(bidTradePanel->getHighlightedItemId()),
+			PlayerColor(offerTradePanel->getHighlightedItemId()), toTrade, hero);
 		CMarketTraderText::makeDeal();
 		deselect();
 	}
@@ -76,8 +76,8 @@ CMarketBase::MarketShowcasesParams CTransferResources::getShowcasesParams() cons
 	if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
 		return MarketShowcasesParams
 		{
-			ShowcaseParams {std::to_string(offerSlider->getValue()), bidTradePanel->getSelectedItemId()},
-			ShowcaseParams {CGI->generaltexth->capColors[offerTradePanel->getSelectedItemId()], offerTradePanel->getSelectedItemId()}
+			ShowcaseParams {std::to_string(offerSlider->getValue()), bidTradePanel->getHighlightedItemId()},
+			ShowcaseParams {CGI->generaltexth->capColors[offerTradePanel->getHighlightedItemId()], offerTradePanel->getHighlightedItemId()}
 		};
 	else
 		return MarketShowcasesParams {std::nullopt, std::nullopt};
@@ -87,7 +87,7 @@ void CTransferResources::highlightingChanged()
 {
 	if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
 	{
-		offerSlider->setAmount(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getSelectedItemId())));
+		offerSlider->setAmount(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getHighlightedItemId())));
 		offerSlider->scrollTo(0);
 		offerSlider->block(false);
 		maxAmount->block(false);
@@ -102,8 +102,8 @@ std::string CTransferResources::getTraderText()
 	if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
 	{
 		MetaString message = MetaString::createFromTextID("core.genrltxt.165");
-		message.replaceName(GameResID(bidTradePanel->getSelectedItemId()));
-		message.replaceName(PlayerColor(offerTradePanel->getSelectedItemId()));
+		message.replaceName(GameResID(bidTradePanel->getHighlightedItemId()));
+		message.replaceName(PlayerColor(offerTradePanel->getHighlightedItemId()));
 		return message.toString();
 	}
 	else

+ 15 - 23
client/widgets/markets/TradePanels.cpp

@@ -23,11 +23,11 @@
 #include "../../../lib/texts/CGeneralTextHandler.h"
 #include "../../../lib/mapObjects/CGHeroInstance.h"
 
-CTradeableItem::CTradeableItem(const Rect & area, EType Type, int ID, int Serial)
+CTradeableItem::CTradeableItem(const Rect & area, EType Type, int32_t ID, int32_t serial)
 	: SelectableSlot(area, Point(1, 1))
 	, type(EType(-1)) // set to invalid, will be corrected in setType
 	, id(ID)
-	, serial(Serial)
+	, serial(serial)
 {
 	OBJECT_CONSTRUCTION;
 
@@ -65,17 +65,14 @@ void CTradeableItem::setType(EType newType)
 			subtitle->moveTo(pos.topLeft() + Point(35, 55));
 			image->moveTo(pos.topLeft() + Point(19, 8));
 			break;
-		case EType::CREATURE_PLACEHOLDER:
 		case EType::CREATURE:
 			subtitle->moveTo(pos.topLeft() + Point(30, 77));
 			break;
 		case EType::PLAYER:
 			subtitle->moveTo(pos.topLeft() + Point(31, 76));
 			break;
-		case EType::ARTIFACT_PLACEHOLDER:
-		case EType::ARTIFACT_INSTANCE:
-			image->moveTo(pos.topLeft() + Point(0, 1));
-			subtitle->moveTo(pos.topLeft() + Point(21, 56));
+		case EType::ARTIFACT:
+			subtitle->moveTo(pos.topLeft() + Point(21, 55));
 			break;
 		case EType::ARTIFACT_TYPE:
 			subtitle->moveTo(pos.topLeft() + Point(35, 57));
@@ -85,14 +82,14 @@ void CTradeableItem::setType(EType newType)
 	}
 }
 
-void CTradeableItem::setID(int newID)
+void CTradeableItem::setID(int32_t newID)
 {
 	if(id != newID)
 	{
 		id = newID;
 		if(image)
 		{
-			int index = getIndex();
+			const auto index = getIndex();
 			if(index < 0)
 				image->disable();
 			else
@@ -121,8 +118,7 @@ AnimationPath CTradeableItem::getFilename()
 	case EType::PLAYER:
 		return AnimationPath::builtin("CREST58");
 	case EType::ARTIFACT_TYPE:
-	case EType::ARTIFACT_PLACEHOLDER:
-	case EType::ARTIFACT_INSTANCE:
+	case EType::ARTIFACT:
 		return AnimationPath::builtin("artifact");
 	case EType::CREATURE:
 		return AnimationPath::builtin("TWCRPORT");
@@ -142,8 +138,7 @@ int CTradeableItem::getIndex()
 	case EType::PLAYER:
 		return id;
 	case EType::ARTIFACT_TYPE:
-	case EType::ARTIFACT_INSTANCE:
-	case EType::ARTIFACT_PLACEHOLDER:
+	case EType::ARTIFACT:
 		return CGI->artifacts()->getByIndex(id)->getIconIndex();
 	case EType::CREATURE:
 		return CGI->creatures()->getByIndex(id)->getIconIndex();
@@ -169,11 +164,10 @@ void CTradeableItem::hover(bool on)
 	switch(type)
 	{
 	case EType::CREATURE:
-	case EType::CREATURE_PLACEHOLDER:
 		GH.statusbar()->write(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->objects[id]->getNamePluralTranslated()));
 		break;
 	case EType::ARTIFACT_TYPE:
-	case EType::ARTIFACT_PLACEHOLDER:
+	case EType::ARTIFACT:
 		if(id < 0)
 			GH.statusbar()->write(CGI->generaltexth->zelp[582].first);
 		else
@@ -193,11 +187,9 @@ void CTradeableItem::showPopupWindow(const Point & cursorPosition)
 	switch(type)
 	{
 	case EType::CREATURE:
-	case EType::CREATURE_PLACEHOLDER:
 		break;
 	case EType::ARTIFACT_TYPE:
-	case EType::ARTIFACT_PLACEHOLDER:
-		//TODO: it's would be better for market to contain actual CArtifactInstance and not just ids of certain artifact type so we can use getEffectiveDescription.
+	case EType::ARTIFACT:
 		if (id >= 0)
 			CRClickPopup::createAndPush(CGI->artifacts()->getByIndex(id)->getDescriptionTranslated());
 		break;
@@ -241,7 +233,7 @@ void TradePanelBase::setShowcaseSubtitle(const std::string & text)
 	showcaseSlot->subtitle->setText(text);
 }
 
-int TradePanelBase::getSelectedItemId() const
+int32_t TradePanelBase::getHighlightedItemId() const
 {
 	if(highlightedSlot)
 		return highlightedSlot->id;
@@ -263,7 +255,7 @@ void TradePanelBase::onSlotClickPressed(const std::shared_ptr<CTradeableItem> &
 
 bool TradePanelBase::isHighlighted() const
 {
-	return getSelectedItemId() != -1;
+	return highlightedSlot != nullptr;
 }
 
 ResourcesPanel::ResourcesPanel(const CTradeableItem::ClickPressedFunctor & clickPressedCallback,
@@ -339,7 +331,7 @@ CreaturesPanel::CreaturesPanel(const CTradeableItem::ClickPressedFunctor & click
 	for(const auto & [creatureId, slotId, creaturesNum] : initialSlots)
 	{
 		auto slot = slots.emplace_back(std::make_shared<CTradeableItem>(Rect(slotsPos[slotId.num], slotDimension),
-			creaturesNum == 0 ? EType::CREATURE_PLACEHOLDER : EType::CREATURE, creatureId.num, slotId));
+			EType::CREATURE, creaturesNum == 0 ? -1 : creatureId.num, slotId));
 		slot->clickPressedCallback = clickPressedCallback;
 		if(creaturesNum != 0)
 			slot->subtitle->setText(std::to_string(creaturesNum));
@@ -357,7 +349,7 @@ CreaturesPanel::CreaturesPanel(const CTradeableItem::ClickPressedFunctor & click
 	for(const auto & srcSlot : srcSlots)
 	{
 		auto slot = slots.emplace_back(std::make_shared<CTradeableItem>(Rect(slotsPos[srcSlot->serial], srcSlot->pos.dimensions()),
-			emptySlots ? EType::CREATURE_PLACEHOLDER : EType::CREATURE, srcSlot->id, srcSlot->serial));
+			EType::CREATURE, emptySlots ? -1 : srcSlot->id, srcSlot->serial));
 		slot->clickPressedCallback = clickPressedCallback;
 		slot->subtitle->setText(emptySlots ? "" : srcSlot->subtitle->getText());
 		slot->setSelectionWidth(selectionWidth);
@@ -372,7 +364,7 @@ ArtifactsAltarPanel::ArtifactsAltarPanel(const CTradeableItem::ClickPressedFunct
 	int slotNum = 0;
 	for(auto & altarSlotPos : slotsPos)
 	{
-		auto slot = slots.emplace_back(std::make_shared<CTradeableItem>(Rect(altarSlotPos, Point(44, 44)), EType::ARTIFACT_PLACEHOLDER, -1, slotNum));
+		auto slot = slots.emplace_back(std::make_shared<CTradeableItem>(Rect(altarSlotPos, Point(44, 44)), EType::ARTIFACT, -1, slotNum));
 		slot->clickPressedCallback = clickPressedCallback;
 		slot->subtitle->clear();
 		slot->subtitle->moveBy(Point(0, -1));

+ 6 - 6
client/widgets/markets/TradePanels.h

@@ -16,7 +16,7 @@
 
 enum class EType
 {
-	RESOURCE, PLAYER, ARTIFACT_TYPE, CREATURE, CREATURE_PLACEHOLDER, ARTIFACT_PLACEHOLDER, ARTIFACT_INSTANCE
+	RESOURCE, PLAYER, ARTIFACT_TYPE, CREATURE, ARTIFACT
 };
 
 class CTradeableItem : public SelectableSlot, public std::enable_shared_from_this<CTradeableItem>
@@ -28,19 +28,19 @@ public:
 	using ClickPressedFunctor = std::function<void(const std::shared_ptr<CTradeableItem>&)>;
 
 	EType type;
-	int id;
-	const int serial;
+	int32_t id;
+	const int32_t serial;
 	std::shared_ptr<CLabel> subtitle;
 	ClickPressedFunctor clickPressedCallback;
 
 	void setType(EType newType);
-	void setID(int newID);
+	void setID(int32_t newID);
 	void clear();
 
 	void showPopupWindow(const Point & cursorPosition) override;
 	void hover(bool on) override;
 	void clickPressed(const Point & cursorPosition) override;
-	CTradeableItem(const Rect & area, EType Type, int ID, int Serial);
+	CTradeableItem(const Rect & area, EType Type, int32_t ID, int32_t serial);
 };
 
 class TradePanelBase : public CIntObject
@@ -61,7 +61,7 @@ public:
 	virtual void clearSubtitles();
 	void updateOffer(CTradeableItem & slot, int, int);
 	void setShowcaseSubtitle(const std::string & text);
-	int getSelectedItemId() const;
+	int32_t getHighlightedItemId() const;
 	void onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot);
 	bool isHighlighted() const;
 };

+ 10 - 9
client/windows/CCreatureWindow.cpp

@@ -17,8 +17,8 @@
 #include "../CPlayerInterface.h"
 #include "../render/Canvas.h"
 #include "../widgets/Buttons.h"
-#include "../widgets/CArtPlace.h"
 #include "../widgets/CComponent.h"
+#include "../widgets/CComponentHolder.h"
 #include "../widgets/Images.h"
 #include "../widgets/TextControls.h"
 #include "../widgets/ObjectLists.h"
@@ -432,7 +432,9 @@ CStackWindow::CommanderMainSection::CommanderMainSection(CStackWindow * owner, i
 	for(auto equippedArtifact : parent->info->commander->artifactsWorn)
 	{
 		Point artPos = getArtifactPos(equippedArtifact.first);
-		auto artPlace = std::make_shared<CCommanderArtPlace>(artPos, parent->info->owner, equippedArtifact.first, equippedArtifact.second.artifact);
+		const auto commanderArt = equippedArtifact.second.artifact;
+		assert(commanderArt);
+		auto artPlace = std::make_shared<CCommanderArtPlace>(artPos, parent->info->owner, equippedArtifact.first, commanderArt->getTypeId());
 		artifacts.push_back(artPlace);
 	}
 
@@ -616,11 +618,11 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
 		auto art = parent->info->stackNode->getArt(ArtifactPosition::CREATURE_SLOT);
 		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)), ComponentType::ARTIFACT);
-			parent->stackArtifactHelp->component.subType = art->artType->getId();
-			parent->stackArtifactHelp->text = art->getDescription();
-
+			parent->stackArtifact = std::make_shared<CArtPlace>(pos, art->getTypeId());
+			parent->stackArtifact->setShowPopupCallback([](CComponentHolder & artPlace, const Point & cursorPosition)
+				{
+					artPlace.LRClickableAreaWTextComp::showPopupWindow(cursorPosition);
+				});
 			if(parent->info->owner)
 			{
 				parent->stackArtifactButton = std::make_shared<CButton>(
@@ -1003,8 +1005,7 @@ void CStackWindow::removeStackArtifact(ArtifactPosition pos)
 		artLoc.creature = info->stackNode->armyObj->findStack(info->stackNode);
 		LOCPLINT->cb->swapArtifacts(artLoc, ArtifactLocation(info->owner->id, slot));
 		stackArtifactButton.reset();
-		stackArtifactHelp.reset();
-		stackArtifactIcon.reset();
+		stackArtifact.reset();
 		redraw();
 	}
 }

+ 2 - 2
client/windows/CCreatureWindow.h

@@ -28,6 +28,7 @@ class CTabbedInt;
 class CButton;
 class CMultiLineLabel;
 class CListBox;
+class CArtPlace;
 class CCommanderArtPlace;
 class LRClickableArea;
 
@@ -156,8 +157,7 @@ class CStackWindow : public CWindowObject
 		MainSection(CStackWindow * owner, int yOffset, bool showExp, bool showArt);
 	};
 
-	std::shared_ptr<CAnimImage> stackArtifactIcon;
-	std::shared_ptr<LRClickableAreaWTextComp> stackArtifactHelp;
+	std::shared_ptr<CArtPlace> stackArtifact;
 	std::shared_ptr<CButton> stackArtifactButton;
 
 

+ 3 - 17
client/windows/CExchangeWindow.cpp

@@ -82,7 +82,8 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 
 
 		for(int m=0; m < hero->secSkills.size(); ++m)
-			secSkillIcons[leftRight].push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SECSK32"), 0, 0, 32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88));
+			secSkills[leftRight].push_back(std::make_shared<CSecSkillPlace>(Point(32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88), CSecSkillPlace::ImageSize::MEDIUM,
+				hero->secSkills[m].first, hero->secSkills[m].second));
 
 		specImages[leftRight] = std::make_shared<CAnimImage>(AnimationPath::builtin("UN32"), hero->getHeroType()->imageIndex, 0, 67 + 490 * leftRight, qeLayout ? 41 : 45);
 
@@ -126,21 +127,6 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 	{
 		const CGHeroInstance * hero = heroInst.at(b);
 
-		//secondary skill's clickable areas
-		for(int g=0; g<hero->secSkills.size(); ++g)
-		{
-			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]->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];
-			boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->generaltexth->levels[level - 1]);
-			boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->skillh->getByIndex(skill)->getNameTranslated());
-		}
-
 		heroAreas[b] = std::make_shared<CHeroArea>(257 + 228 * b, 13, hero);
 		heroAreas[b]->addClickCallback([this, hero]() -> void
 									   {
@@ -396,7 +382,7 @@ void CExchangeWindow::update()
 			int id = hero->secSkills[m].first;
 			int level = hero->secSkills[m].second;
 
-			secSkillIcons[leftRight][m]->setFrame(2 + id * 3 + level);
+			secSkills[leftRight][m]->setSkill(id, level);
 		}
 
 		expValues[leftRight]->setText(TextOperations::formatMetric(hero->exp, 3));

+ 1 - 2
client/windows/CExchangeWindow.h

@@ -19,7 +19,6 @@ class CExchangeWindow : public CStatusbarWindow, public IGarrisonHolder, public
 	std::array<std::shared_ptr<CLabel>, 2> titles;
 	std::vector<std::shared_ptr<CAnimImage>> primSkillImages;//shared for both heroes
 	std::array<std::vector<std::shared_ptr<CLabel>>, 2> primSkillValues;
-	std::array<std::vector<std::shared_ptr<CAnimImage>>, 2> secSkillIcons;
 	std::array<std::shared_ptr<CAnimImage>, 2> specImages;
 	std::array<std::shared_ptr<CAnimImage>, 2> expImages;
 	std::array<std::shared_ptr<CLabel>, 2> expValues;
@@ -27,7 +26,7 @@ class CExchangeWindow : public CStatusbarWindow, public IGarrisonHolder, public
 	std::array<std::shared_ptr<CLabel>, 2> manaValues;
 
 	std::vector<std::shared_ptr<LRClickableAreaWTextComp>> primSkillAreas;
-	std::array<std::vector<std::shared_ptr<LRClickableAreaWTextComp>>, 2> secSkillAreas;
+	std::array<std::vector<std::shared_ptr<CSecSkillPlace>>, 2> secSkills;
 
 	std::array<std::shared_ptr<CHeroArea>, 2> heroAreas;
 	std::array<std::shared_ptr<LRClickableAreaWText>, 2> specialtyAreas;

+ 0 - 4
client/windows/CHeroBackpackWindow.cpp

@@ -93,10 +93,6 @@ CHeroQuickBackpackWindow::CHeroQuickBackpackWindow(const CGHeroInstance * hero,
 		if(const auto curHero = arts->getHero())
 			swapArtifactAndClose(*arts, artPlace.slot, ArtifactLocation(curHero->id, arts->getFilterSlot()));
 	};
-	arts->showPopupCallback = [this](CArtPlace & artPlace, const Point & cursorPosition)
-	{
-		showArifactInfo(*arts, artPlace, cursorPosition);
-	};
 	addSet(arts);
 	arts->setHero(hero);
 	addUsedEvents(GESTURE);

+ 1 - 1
client/windows/CHeroOverview.cpp

@@ -206,7 +206,7 @@ void CHeroOverview::genControls()
     i = 0;
     for(auto & skill : (*CGI->heroh)[heroIdx]->secSkillsInit)
     {
-        imageSecSkills.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SECSK32"), (*CGI->skillh)[skill.first]->getIconIndex() * 3 + skill.second + 2, 0, 302, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset)));
+        imageSecSkills.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SECSK32"), (*CGI->skillh)[skill.first]->getIconIndex(skill.second + 2), 0, 302, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset)));
         labelSecSkillsNames.push_back(std::make_shared<CLabel>(334 + 2 * borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) - 5, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->levels[skill.second - 1]));
         labelSecSkillsNames.push_back(std::make_shared<CLabel>(334 + 2 * borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) + 10, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, (*CGI->skillh)[skill.first]->getNameTranslated()));
         i++;

+ 3 - 8
client/windows/CHeroWindow.cpp

@@ -152,8 +152,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, ComponentType::SEC_SKILL));
-		secSkillImages.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SECSKILL"), 0, 0, r.x, r.y));
+		secSkills.emplace_back(std::make_shared<CSecSkillPlace>(r.topLeft(), CSecSkillPlace::ImageSize::LARGE));
 
 		int x = (i % 2) ? 212 : 68;
 		int y = 280 + 48 * (i/2);
@@ -234,20 +233,16 @@ void CHeroWindow::update()
 	}
 
 	//secondary skills support
-	for(size_t g=0; g< secSkillAreas.size(); ++g)
+	for(size_t g=0; g< secSkills.size(); ++g)
 	{
 		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]->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);
 		secSkillNames[g]->setText(skillName);
 		secSkillValues[g]->setText(skillValue);
+		secSkills[g]->setSkill(skill, level);
 	}
 
 	std::ostringstream expstr;

+ 1 - 2
client/windows/CHeroWindow.h

@@ -74,8 +74,7 @@ class CHeroWindow : public CStatusbarWindow, public IGarrisonHolder, public CWin
 	std::shared_ptr<CLabel> specName;
 	std::shared_ptr<MoraleLuckBox> morale;
 	std::shared_ptr<MoraleLuckBox> luck;
-	std::vector<std::shared_ptr<LRClickableAreaWTextComp>> secSkillAreas;
-	std::vector<std::shared_ptr<CAnimImage>> secSkillImages;
+	std::vector< std::shared_ptr<CSecSkillPlace>> secSkills;
 	std::vector<std::shared_ptr<CLabel>> secSkillNames;
 	std::vector<std::shared_ptr<CLabel>> secSkillValues;
 

+ 0 - 1
client/windows/CMarketWindow.cpp

@@ -219,7 +219,6 @@ void CMarketWindow::createArtifactsSelling(const IMarket * market, const CGHeroI
 	auto artsSellingMarket = std::make_shared<CArtifactsSelling>(market, hero, getMarketTitle(market->getObjInstanceID(), EMarketMode::ARTIFACT_RESOURCE));
 	artSets.clear();
 	const auto heroArts = artsSellingMarket->getAOHset();
-	heroArts->showPopupCallback = [this, heroArts](CArtPlace & artPlace, const Point & cursorPosition){showArifactInfo(*heroArts, artPlace, cursorPosition);};
 	addSet(heroArts);
 	marketWidget = artsSellingMarket;
 	initWidgetInternals(EMarketMode::ARTIFACT_RESOURCE, CGI->generaltexth->zelp[600]);

+ 0 - 6
client/windows/CWindowWithArtifacts.cpp

@@ -128,12 +128,6 @@ void CWindowWithArtifacts::showArtifactAssembling(const CArtifactsOfHeroBase & a
 	}
 }
 
-void CWindowWithArtifacts::showArifactInfo(const CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition) const
-{
-	if(artsInst.getArt(artPlace.slot) && artPlace.text.size())
-		artPlace.LRClickableAreaWTextComp::showPopupWindow(cursorPosition);
-}
-
 void CWindowWithArtifacts::showQuickBackpackWindow(const CGHeroInstance * hero, const ArtifactPosition & slot,
 	const Point & cursorPosition) const
 {

+ 0 - 1
client/windows/CWindowWithArtifacts.h

@@ -31,7 +31,6 @@ public:
 		bool allowExchange, bool altarTrading, bool closeWindow);
 	void swapArtifactAndClose(const CArtifactsOfHeroBase & artsInst, const ArtifactPosition & slot, const ArtifactLocation & dstLoc);
 	void showArtifactAssembling(const CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition) const;
-	void showArifactInfo(const CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition) const;
 	void showQuickBackpackWindow(const CGHeroInstance * hero, const ArtifactPosition & slot, const Point & cursorPosition) const;
 	void activate() override;
 	void deactivate() override;

+ 8 - 26
client/windows/GUIClasses.cpp

@@ -896,28 +896,18 @@ CUniversityWindow::CItem::CItem(CUniversityWindow * _parent, int _ID, int X, int
 	pos.x += X;
 	pos.y += Y;
 
-	icon = std::make_shared<CAnimImage>(AnimationPath::builtin("SECSKILL"), _ID * 3 + 3, 0);
-
-	pos.h = icon->pos.h;
-	pos.w = icon->pos.w;
+	skill = std::make_shared<CSecSkillPlace>(Point(), CSecSkillPlace::ImageSize::LARGE, _ID, 1);
+	skill->setClickPressedCallback([this](const CComponentHolder&, const Point& cursorPosition)
+		{
+			bool skillKnown = parent->hero->getSecSkillLevel(ID);
+			bool canLearn = parent->hero->canLearnSkill(ID);
 
+			if(!skillKnown && canLearn)
+				GH.windows().createAndPushWindow<CUnivConfirmWindow>(parent, ID, LOCPLINT->cb->getResourceAmount(EGameResID::GOLD) >= 2000);
+		});
 	update();
 }
 
-void CUniversityWindow::CItem::clickPressed(const Point & cursorPosition)
-{
-	bool skillKnown = parent->hero->getSecSkillLevel(ID);
-	bool canLearn =	parent->hero->canLearnSkill(ID);
-
-	if (!skillKnown && canLearn)
-		GH.windows().createAndPushWindow<CUnivConfirmWindow>(parent, ID, LOCPLINT->cb->getResourceAmount(EGameResID::GOLD) >= 2000);
-}
-
-void CUniversityWindow::CItem::showPopupWindow(const Point & cursorPosition)
-{
-	CRClickPopup::createAndPush(CGI->skillh->getByIndex(ID)->getDescriptionTranslated(1), std::make_shared<CComponent>(ComponentType::SEC_SKILL, ID, 1));
-}
-
 void CUniversityWindow::CItem::update()
 {
 	bool skillKnown = parent->hero->getSecSkillLevel(ID);
@@ -941,14 +931,6 @@ void CUniversityWindow::CItem::update()
 	level = std::make_shared<CLabel>(22, 57, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->levels[0]);
 }
 
-void CUniversityWindow::CItem::hover(bool on)
-{
-	if(on)
-		GH.statusbar()->write(ID.toEntity(VLC)->getNameTranslated());
-	else
-		GH.statusbar()->clear();
-}
-
 CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, BuildingID building, const IMarket * _market, const std::function<void()> & onWindowClosed)
 	: CWindowObject(PLAYER_COLORED, ImagePath::builtin("UNIVERS1")),
 	hero(_hero),

+ 2 - 4
client/windows/GUIClasses.h

@@ -46,6 +46,7 @@ class VideoWidget;
 class VideoWidgetOnce;
 class GraphicalPrimitiveCanvas;
 class TransparentFilledRectangle;
+class CSecSkillPlace;
 
 enum class EUserEvent;
 
@@ -370,7 +371,7 @@ class CUniversityWindow final : public CStatusbarWindow, public IMarketHolder
 {
 	class CItem final : public CIntObject
 	{
-		std::shared_ptr<CAnimImage> icon;
+		std::shared_ptr<CSecSkillPlace> skill;
 		std::shared_ptr<CPicture> topBar;
 		std::shared_ptr<CPicture> bottomBar;
 		std::shared_ptr<CLabel> name;
@@ -379,9 +380,6 @@ class CUniversityWindow final : public CStatusbarWindow, public IMarketHolder
 		SecondarySkill ID;//id of selected skill
 		CUniversityWindow * parent;
 
-		void clickPressed(const Point & cursorPosition) override;
-		void showPopupWindow(const Point & cursorPosition) override;
-		void hover(bool on) override;
 		void update();
 		CItem(CUniversityWindow * _parent, int _ID, int X, int Y);
 	};

+ 0 - 8
lib/CArtifactInstance.cpp

@@ -134,14 +134,6 @@ std::string CArtifactInstance::nodeName() const
 	return "Artifact instance of " + (artType ? artType->getJsonKey() : std::string("uninitialized")) + " type";
 }
 
-std::string CArtifactInstance::getDescription() const
-{
-	std::string text = artType->getDescriptionTranslated();
-	if(artType->isScroll())
-		ArtifactUtils::insertScrrollSpellName(text, getScrollSpellID());
-	return text;
-}
-
 ArtifactID CArtifactInstance::getTypeId() const
 {
 	return artType->getId();

+ 0 - 1
lib/CArtifactInstance.h

@@ -80,7 +80,6 @@ public:
 	CArtifactInstance();
 	void setType(const CArtifact * art);
 	std::string nodeName() const override;
-	std::string getDescription() const;
 	ArtifactID getTypeId() const;
 	ArtifactInstanceID getId() const;
 	void setId(ArtifactInstanceID id);

+ 6 - 1
lib/CSkillHandler.cpp

@@ -45,7 +45,12 @@ int32_t CSkill::getIndex() const
 
 int32_t CSkill::getIconIndex() const
 {
-	return getIndex(); //TODO: actual value with skill level
+	return getIndex() * 3 + 3; // Base master level
+}
+
+int32_t CSkill::getIconIndex(uint8_t skillMasterLevel) const
+{
+	return getIconIndex() + skillMasterLevel;
 }
 
 std::string CSkill::getNameTextID() const

+ 2 - 1
lib/CSkillHandler.h

@@ -34,6 +34,7 @@ public:
 private:
 	std::vector<LevelInfo> levels; // bonuses provided by basic, advanced and expert level
 	void addNewBonus(const std::shared_ptr<Bonus> & b, int level);
+	int32_t getIconIndex() const override;
 
 	SecondarySkill id;
 	std::string modScope;
@@ -50,7 +51,7 @@ public:
 	};
 
 	int32_t getIndex() const override;
-	int32_t getIconIndex() const override;
+	int32_t getIconIndex(uint8_t skillMasterLevel) const;
 	std::string getJsonKey() const override;
 	std::string getModScope() const override;
 	void registerIcons(const IconRegistar & cb) const override;