Просмотр исходного кода

Merge remote-tracking branch 'upstream/develop' into rewardable-expand

nordsoft 2 лет назад
Родитель
Сommit
97a1758e8e
50 измененных файлов с 1720 добавлено и 1417 удалено
  1. 1 1
      AI/Nullkiller/AIGateway.cpp
  2. 6 2
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  3. 14 9
      AI/VCAI/Goals/CollectRes.cpp
  4. 1 1
      AI/VCAI/VCAI.cpp
  5. 3 3
      CCallback.cpp
  6. 5 4
      CCallback.h
  7. 12 0
      client/CMakeLists.txt
  8. 9 12
      client/CPlayerInterface.cpp
  9. 167 923
      client/widgets/CArtifactHolder.cpp
  10. 30 123
      client/widgets/CArtifactHolder.h
  11. 109 0
      client/widgets/CArtifactsOfHeroAltar.cpp
  12. 32 0
      client/widgets/CArtifactsOfHeroAltar.h
  13. 277 0
      client/widgets/CArtifactsOfHeroBase.cpp
  14. 67 0
      client/widgets/CArtifactsOfHeroBase.h
  15. 54 0
      client/widgets/CArtifactsOfHeroKingdom.cpp
  16. 27 0
      client/widgets/CArtifactsOfHeroKingdom.h
  17. 35 0
      client/widgets/CArtifactsOfHeroMain.cpp
  18. 26 0
      client/widgets/CArtifactsOfHeroMain.h
  19. 41 0
      client/widgets/CArtifactsOfHeroMarket.cpp
  20. 21 0
      client/widgets/CArtifactsOfHeroMarket.h
  21. 5 8
      client/widgets/CGarrisonInt.cpp
  22. 363 0
      client/widgets/CWindowWithArtifacts.cpp
  23. 44 0
      client/widgets/CWindowWithArtifacts.h
  24. 1 1
      client/windows/CCastleInterface.cpp
  25. 13 14
      client/windows/CHeroWindow.cpp
  26. 3 3
      client/windows/CHeroWindow.h
  27. 4 4
      client/windows/CKingdomInterface.cpp
  28. 2 2
      client/windows/CKingdomInterface.h
  29. 75 98
      client/windows/CTradeWindow.cpp
  30. 3 3
      client/windows/CTradeWindow.h
  31. 31 29
      client/windows/GUIClasses.cpp
  32. 4 4
      client/windows/GUIClasses.h
  33. 34 14
      config/objects/generic.json
  34. 19 0
      lib/CArtHandler.cpp
  35. 1 0
      lib/CArtHandler.h
  36. 29 16
      lib/GameConstants.h
  37. 1 1
      lib/HeroBonus.cpp
  38. 12 8
      lib/JsonRandom.cpp
  39. 20 71
      lib/mapObjects/CGMarket.cpp
  40. 16 11
      lib/mapObjects/CGMarket.h
  41. 1 23
      lib/mapObjects/CGTownInstance.cpp
  42. 0 4
      lib/mapObjects/CGTownInstance.h
  43. 1 3
      lib/mapObjects/CObjectClassesHandler.cpp
  44. 62 0
      lib/mapObjects/CommonConstructors.cpp
  45. 23 0
      lib/mapObjects/CommonConstructors.h
  46. 1 1
      lib/mapObjects/MiscObjects.cpp
  47. 3 3
      lib/registerTypes/RegisterTypes.h
  48. 1 10
      mapeditor/mainwindow.cpp
  49. 4 4
      server/CGameHandler.cpp
  50. 7 4
      server/CVCMIServer.cpp

+ 1 - 1
AI/Nullkiller/AIGateway.cpp

@@ -1385,7 +1385,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
 				//TODO trade only as much as needed
 				if (toGive) //don't try to sell 0 resources
 				{
-					cb->trade(obj, EMarketMode::RESOURCE_RESOURCE, res, g.resID, toGive);
+					cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, g.resID, toGive);
 					accquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
 					logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, accquiredResources, g.resID, obj->getObjectName());
 				}

+ 6 - 2
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -297,6 +297,12 @@ int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroIn
 {
 	if(!target)
 		return 0;
+	
+	if(auto * m = dynamic_cast<const IMarket *>(target))
+	{
+		if(m->allowsTrade(EMarketMode::RESOURCE_SKILL))
+			return 2000;
+	}
 
 	switch(target->ID)
 	{
@@ -305,8 +311,6 @@ int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroIn
 	case Obj::SCHOOL_OF_MAGIC:
 	case Obj::SCHOOL_OF_WAR:
 		return 1000;
-	case Obj::UNIVERSITY:
-		return 2000;
 	case Obj::CREATURE_GENERATOR1:
 	case Obj::CREATURE_GENERATOR2:
 	case Obj::CREATURE_GENERATOR3:

+ 14 - 9
AI/VCAI/Goals/CollectRes.cpp

@@ -131,13 +131,16 @@ TSubgoal CollectRes::whatToDoToTrade()
 
 	std::vector<const CGObjectInstance *> visObjs;
 	ai->retrieveVisitableObjs(visObjs, true);
-	for (const CGObjectInstance * obj : visObjs)
+	for(const CGObjectInstance * obj : visObjs)
 	{
-		if (const IMarket * m = IMarket::castFrom(obj, false))
+		if(const IMarket * m = IMarket::castFrom(obj, false); m->allowsTrade(EMarketMode::RESOURCE_RESOURCE))
 		{
-			if (obj->ID == Obj::TOWN && obj->tempOwner == ai->playerID && m->allowsTrade(EMarketMode::RESOURCE_RESOURCE))
-				markets.push_back(m);
-			else if (obj->ID == Obj::TRADING_POST)
+			if(obj->ID == Obj::TOWN)
+			{
+				if(obj->tempOwner == ai->playerID)
+					markets.push_back(m);
+			}
+			else
 				markets.push_back(m);
 		}
 	}
@@ -149,9 +152,10 @@ TSubgoal CollectRes::whatToDoToTrade()
 
 	markets.erase(boost::remove_if(markets, [](const IMarket * market) -> bool
 	{
-		if (!(market->o->ID == Obj::TOWN && market->o->tempOwner == ai->playerID))
+		auto * o = dynamic_cast<const CGObjectInstance *>(market);
+		if(o && !(o->ID == Obj::TOWN && o->tempOwner == ai->playerID))
 		{
-			if (!ai->isAccessible(market->o->visitablePos()))
+			if(!ai->isAccessible(o->visitablePos()))
 				return true;
 		}
 		return false;
@@ -182,9 +186,10 @@ TSubgoal CollectRes::whatToDoToTrade()
 
 		if (howManyCanWeBuy >= value)
 		{
-			auto backObj = cb->getTopObj(m->o->visitablePos()); //it'll be a hero if we have one there; otherwise marketplace
+			auto * o = dynamic_cast<const CGObjectInstance *>(m);
+			auto backObj = cb->getTopObj(o->visitablePos()); //it'll be a hero if we have one there; otherwise marketplace
 			assert(backObj);
-			auto objid = m->o->id.getNum();
+			auto objid = o->id.getNum();
 			if (backObj->tempOwner != ai->playerID) //top object not owned
 			{
 				return sptr(VisitObj(objid)); //just go there

+ 1 - 1
AI/VCAI/VCAI.cpp

@@ -2130,7 +2130,7 @@ void VCAI::tryRealize(Goals::Trade & g) //trade
 				//TODO trade only as much as needed
 				if (toGive) //don't try to sell 0 resources
 				{
-					cb->trade(obj, EMarketMode::RESOURCE_RESOURCE, res, g.resID, toGive);
+					cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, g.resID, toGive);
 					accquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
 					logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, accquiredResources, g.resID, obj->getObjectName());
 				}

+ 3 - 3
CCallback.cpp

@@ -244,15 +244,15 @@ void CCallback::buyArtifact(const CGHeroInstance *hero, ArtifactID aid)
 	sendRequest(&pack);
 }
 
-void CCallback::trade(const CGObjectInstance * market, EMarketMode::EMarketMode mode, ui32 id1, ui32 id2, ui32 val1, const CGHeroInstance * hero)
+void CCallback::trade(const IMarket * market, EMarketMode::EMarketMode mode, ui32 id1, ui32 id2, ui32 val1, const CGHeroInstance * hero)
 {
 	trade(market, mode, std::vector<ui32>(1, id1), std::vector<ui32>(1, id2), std::vector<ui32>(1, val1), hero);
 }
 
-void CCallback::trade(const CGObjectInstance * market, EMarketMode::EMarketMode mode, const std::vector<ui32> & id1, const std::vector<ui32> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero)
+void CCallback::trade(const IMarket * market, EMarketMode::EMarketMode mode, const std::vector<ui32> & id1, const std::vector<ui32> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero)
 {
 	TradeOnMarketplace pack;
-	pack.marketId = market->id;
+	pack.marketId = dynamic_cast<const CGObjectInstance *>(market)->id;
 	pack.heroId = hero ? hero->id : ObjectInstanceID();
 	pack.mode = mode;
 	pack.r1 = id1;

+ 5 - 4
CCallback.h

@@ -33,6 +33,7 @@ class IBattleEventsReceiver;
 class IGameEventsReceiver;
 struct ArtifactLocation;
 class BattleStateInfoForRetreat;
+class IMarket;
 
 VCMI_LIB_NAMESPACE_END
 
@@ -73,8 +74,8 @@ public:
 	virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made
 	virtual void swapGarrisonHero(const CGTownInstance *town)=0;
 
-	virtual void trade(const CGObjectInstance * market, EMarketMode::EMarketMode mode, ui32 id1, ui32 id2, ui32 val1, const CGHeroInstance * hero = nullptr)=0; //mode==0: sell val1 units of id1 resource for id2 resiurce
-	virtual void trade(const CGObjectInstance * market, EMarketMode::EMarketMode mode, const std::vector<ui32> & id1, const std::vector<ui32> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr)=0;
+	virtual void trade(const IMarket * market, EMarketMode::EMarketMode mode, ui32 id1, ui32 id2, ui32 val1, const CGHeroInstance * hero = nullptr)=0; //mode==0: sell val1 units of id1 resource for id2 resiurce
+	virtual void trade(const IMarket * market, EMarketMode::EMarketMode mode, const std::vector<ui32> & id1, const std::vector<ui32> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr)=0;
 
 	virtual int selectionMade(int selection, QueryID queryID) =0;
 	virtual int sendQueryReply(const JsonNode & reply, QueryID queryID) =0;
@@ -168,8 +169,8 @@ public:
 	void endTurn() override;
 	void swapGarrisonHero(const CGTownInstance *town) override;
 	void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override;
-	void trade(const CGObjectInstance * market, EMarketMode::EMarketMode mode, ui32 id1, ui32 id2, ui32 val1, const CGHeroInstance * hero = nullptr) override;
-	void trade(const CGObjectInstance * market, EMarketMode::EMarketMode mode, const std::vector<ui32> & id1, const std::vector<ui32> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr) override;
+	void trade(const IMarket * market, EMarketMode::EMarketMode mode, ui32 id1, ui32 id2, ui32 val1, const CGHeroInstance * hero = nullptr) override;
+	void trade(const IMarket * market, EMarketMode::EMarketMode mode, const std::vector<ui32> & id1, const std::vector<ui32> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr) override;
 	void setFormation(const CGHeroInstance * hero, bool tight) override;
 	void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero) override;
 	void save(const std::string &fname) override;

+ 12 - 0
client/CMakeLists.txt

@@ -85,6 +85,12 @@ set(client_SRCS
 	widgets/MiscWidgets.cpp
 	widgets/ObjectLists.cpp
 	widgets/TextControls.cpp
+	widgets/CArtifactsOfHeroBase.cpp
+	widgets/CArtifactsOfHeroMain.cpp
+	widgets/CArtifactsOfHeroKingdom.cpp
+	widgets/CArtifactsOfHeroAltar.cpp
+	widgets/CArtifactsOfHeroMarket.cpp
+	widgets/CWindowWithArtifacts.cpp
 
 	windows/CCastleInterface.cpp
 	windows/CCreatureWindow.cpp
@@ -214,6 +220,12 @@ set(client_HEADERS
 	widgets/MiscWidgets.h
 	widgets/ObjectLists.h
 	widgets/TextControls.h
+	widgets/CArtifactsOfHeroBase.h
+	widgets/CArtifactsOfHeroMain.h
+	widgets/CArtifactsOfHeroKingdom.h
+	widgets/CArtifactsOfHeroAltar.h
+	widgets/CArtifactsOfHeroMarket.h
+	widgets/CWindowWithArtifacts.h
 
 	windows/CCastleInterface.h
 	windows/CCreatureWindow.h

+ 9 - 12
client/CPlayerInterface.cpp

@@ -1709,18 +1709,15 @@ void CPlayerInterface::stopMovement()
 void CPlayerInterface::showMarketWindow(const IMarket *market, const CGHeroInstance *visitor)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	if (market->o->ID == Obj::ALTAR_OF_SACRIFICE)
-	{
-		//EEMarketMode mode = market->availableModes().front();
-		if (market->allowsTrade(EMarketMode::ARTIFACT_EXP) && visitor->getAlignment() != EAlignment::EVIL)
-			GH.pushIntT<CAltarWindow>(market, visitor, EMarketMode::ARTIFACT_EXP);
-		else if (market->allowsTrade(EMarketMode::CREATURE_EXP) && visitor->getAlignment() != EAlignment::GOOD)
-			GH.pushIntT<CAltarWindow>(market, visitor, EMarketMode::CREATURE_EXP);
-	}
-	else
-	{
+
+	if(market->allowsTrade(EMarketMode::ARTIFACT_EXP) && visitor->getAlignment() != EAlignment::EVIL)
+		GH.pushIntT<CAltarWindow>(market, visitor, EMarketMode::ARTIFACT_EXP);
+	else if(market->allowsTrade(EMarketMode::CREATURE_EXP) && visitor->getAlignment() != EAlignment::GOOD)
+		GH.pushIntT<CAltarWindow>(market, visitor, EMarketMode::CREATURE_EXP);
+	else if(market->allowsTrade(EMarketMode::CREATURE_UNDEAD))
+		GH.pushIntT<CTransformerWindow>(market, visitor);
+	else if(!market->availableModes().empty())
 		GH.pushIntT<CMarketplaceWindow>(market, visitor, market->availableModes().front());
-	}
 }
 
 void CPlayerInterface::showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor)
@@ -1792,7 +1789,7 @@ void CPlayerInterface::askToAssembleArtifact(const ArtifactLocation &al)
 							 al.slot.num);
 			return;
 		}
-		CHeroArtPlace::askToAssemble(hero, al.slot);
+		ArtifactUtilsClient::askToAssemble(hero, al.slot);
 	}
 }
 

+ 167 - 923
client/widgets/CArtifactHolder.cpp

@@ -11,13 +11,9 @@
 #include "CArtifactHolder.h"
 
 #include "../gui/CGuiHandler.h"
-#include "../gui/CursorHandler.h"
 
-#include "Buttons.h"
 #include "CComponent.h"
 
-#include "../windows/CHeroWindow.h"
-#include "../windows/CSpellWindow.h"
 #include "../windows/GUIClasses.h"
 #include "../renderSDL/SDL_Extensions.h"
 #include "../CPlayerInterface.h"
@@ -25,369 +21,29 @@
 
 #include "../../CCallback.h"
 
-#include "../../lib/CArtHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
 
 #include "../../lib/mapObjects/CGHeroInstance.h"
 
-CHeroArtPlace::CHeroArtPlace(Point position, const CArtifactInstance * Art)
-	: CArtPlace(position, Art),
-	locked(false),
-	picked(false),
-	marked(false),
-	ourOwner(nullptr)
-{
-	createImage();
-}
-
-void CHeroArtPlace::createImage()
-{
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-
-	si32 imageIndex = 0;
-
-	if(locked)
-		imageIndex = ArtifactID::ART_LOCK;
-	else if(ourArt)
-		imageIndex = ourArt->artType->getIconIndex();
-
-	image = std::make_shared<CAnimImage>("artifact", imageIndex);
-	if(!ourArt)
-		image->disable();
-
-	selection = std::make_shared<CAnimImage>("artifact", ArtifactID::ART_SELECTION);
-	selection->disable();
-}
-
-void CHeroArtPlace::lockSlot(bool on)
-{
-	if (locked == on)
-		return;
-
-	locked = on;
-
-	if (on)
-		image->setFrame(ArtifactID::ART_LOCK);
-	else if (ourArt)
-		image->setFrame(ourArt->artType->getIconIndex());
-	else
-		image->setFrame(0);
-}
-
-void CHeroArtPlace::pickSlot(bool on)
-{
-	if (picked == on)
-		return;
-
-	picked = on;
-	if (on)
-		image->disable();
-	else
-		image->enable();
-}
-
-void CHeroArtPlace::selectSlot(bool on)
-{
-	if (marked == on)
-		return;
-
-	marked = on;
-	if (on)
-		selection->enable();
-	else
-		selection->disable();
-}
-
-void CHeroArtPlace::clickLeft(tribool down, bool previousState)
-{
-	//LRClickableAreaWTextComp::clickLeft(down);
-	bool inBackpack = ArtifactUtils::isSlotBackpack(slotID);
-	bool srcInBackpack = ArtifactUtils::isSlotBackpack(ourOwner->commonInfo->src.slotID);
-	bool srcInSameHero = ourOwner->commonInfo->src.AOH == ourOwner;
-
-	if(ourOwner->highlightModeCallback && ourArt)
-	{
-		if(down)
-		{
-			if(!ourArt->artType->isTradable()) //War Machine or Spellbook
-			{
-				LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[21]); //This item can't be traded.
-			}
-			else
-			{
-				ourOwner->unmarkSlots(false);
-				selectSlot(true);
-				ourOwner->highlightModeCallback(this);
-			}
-		}
-		return;
-	}
-
-	// If clicked on spellbook, open it only if no artifact is held at the moment.
-	if(ourArt && !down && previousState && !ourOwner->commonInfo->src.AOH)
-	{
-		if(ourArt->artType->getId() == ArtifactID::SPELLBOOK)
-				GH.pushIntT<CSpellWindow>(ourOwner->curHero, LOCPLINT, LOCPLINT->battleInt.get());
-	}
-
-	if (!down && previousState)
-	{
-		if(ourArt && ourArt->artType->getId() == ArtifactID::SPELLBOOK)
-			return; //this is handled separately
-
-		if(!ourOwner->commonInfo->src.AOH) //nothing has been clicked
-		{
-			if(ourArt  //to prevent selecting empty slots (bugfix to what GrayFace reported)
-				&&  ourOwner->curHero->tempOwner == LOCPLINT->playerID)//can't take art from another player
-			{
-				if(ourArt->artType->getId() == ArtifactID::CATAPULT) //catapult cannot be highlighted
-				{
-					std::vector<std::shared_ptr<CComponent>> catapult(1, std::make_shared<CComponent>(CComponent::artifact, 3, 0));
-					LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[312], catapult); //The Catapult must be equipped.
-					return;
-				}
-				select();
-			}
-		}
-		// Perform artifact transition
-		else if(ourArt != ourOwner->commonInfo->src.art)
-		{
-			if(inBackpack) // Backpack destination.
-			{
-				if(!srcInBackpack || slotID != ourOwner->commonInfo->src.slotID + 1)
-				{
-					const CArtifact * const cur = ourOwner->commonInfo->src.art->artType;
-
-					if(cur->getId() == ArtifactID::CATAPULT)
-					{
-						//should not happen, catapult cannot be selected
-						logGlobal->error("Attempt to move Catapult");
-					}
-					else if(cur->isBig())
-					{
-						//war machines cannot go to backpack
-						LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[153]) % cur->getNameTranslated()));
-					}
-					else
-					{
-						setMeAsDest();
-						vstd::amin(ourOwner->commonInfo->dst.slotID, ourOwner->curHero->artifactsInBackpack.size() + GameConstants::BACKPACK_START);
-						if(ArtifactUtils::isBackpackFreeSlots(ourOwner->curHero))
-						{
-							if(!srcInSameHero || ourOwner->commonInfo->dst.slotID != ourOwner->commonInfo->src.slotID)
-								ourOwner->realizeCurrentTransaction();
-						}
-						else
-						{
-							LOCPLINT->showInfoDialog(CGI->generaltexth->translate("core.genrltxt.152"));
-						}
-					}
-				}
-			}
-			//check if swap is possible
-			else if (fitsHere(ourOwner->commonInfo->src.art) &&
-				(!ourArt || ourOwner->curHero->tempOwner == LOCPLINT->playerID))
-			{
-				setMeAsDest();
-				ourOwner->realizeCurrentTransaction();
-			}
-		}
-	}
-}
-
-bool CHeroArtPlace::askToAssemble(const CGHeroInstance * hero, ArtifactPosition slot)
+void CArtPlace::setInternals(const CArtifactInstance * artInst)
 {
-	assert(hero);
-	const auto art = hero->getArt(slot);
-	assert(art);
-	auto assemblyPossibilities = ArtifactUtils::assemblyPossibilities(hero, art->getTypeId(), ArtifactUtils::isSlotEquipment(slot));
-
-	// If the artifact can be assembled, display dialog.
-	for(const auto * combination : assemblyPossibilities)
-	{
-		LOCPLINT->showArtifactAssemblyDialog(
-			art->artType,
-			combination,
-			std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), hero, slot, true, combination->getId()));
-
-		if(assemblyPossibilities.size() > 2)
-			logGlobal->warn("More than one possibility of assembling on %s... taking only first", art->artType->getNameTranslated());
-		return true;
-	}
-	return false;
-}
-
-bool CHeroArtPlace::askToDisassemble(const CGHeroInstance * hero, ArtifactPosition slot)
-{
-	assert(hero);
-	const auto art = hero->getArt(slot);
-	assert(art);
-
-	if(art->canBeDisassembled())
-	{
-		if(ArtifactUtils::isSlotBackpack(slot) && !ArtifactUtils::isBackpackFreeSlots(hero, art->artType->constituents->size() - 1))
-			return false;
-		
-		LOCPLINT->showArtifactAssemblyDialog(
-			art->artType,
-			nullptr,
-			std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), hero, slot, false, ArtifactID()));
-		return true;
-	}
-	return false;
-}
-
-void CHeroArtPlace::clickRight(tribool down, bool previousState)
-{
-	if(ourArt && down && !locked && text.size() && !picked)  //if there is no description or it's a lock, do nothing ;]
-	{
-		if(ourOwner->allowedAssembling)
-		{
-			// If the artifact can be assembled, display dialog.
-			if(askToAssemble(ourOwner->curHero, slotID))
-			{
-				return;
-			}
-			if(askToDisassemble(ourOwner->curHero, slotID))
-			{
-				return;
-			}
-		}
-
-		// Lastly just show the artifact description.
-		LRClickableAreaWTextComp::clickRight(down, previousState);
-	}
-}
-
-void CArtifactsOfHero::activate()
-{
-	if (commonInfo->src.AOH == this && commonInfo->src.art)
-		CCS->curh->dragAndDropCursor("artifact", commonInfo->src.art->artType->getIconIndex());
-
-	CIntObject::activate();
-}
-
-void CArtifactsOfHero::deactivate()
-{
-	if (commonInfo->src.AOH == this && commonInfo->src.art)
-		CCS->curh->dragAndDropCursor(nullptr);
-
-	CIntObject::deactivate();
-}
-
-/**
- * Selects artifact slot so that the containing artifact looks like it's picked up.
- */
-void CHeroArtPlace::select()
-{
-	if(locked)
-		return;
-
-	pickSlot(true);
-	if(ourArt->canBeDisassembled() && ArtifactUtils::isSlotEquipment(slotID)) //worn combined artifact -> locks have to disappear
-	{
-		for(auto slot : ArtifactUtils::constituentWornSlots())
-		{
-			auto ap = ourOwner->getArtPlace(slot);
-			if(ap)//getArtPlace may return null
-				ap->pickSlot(ourArt->isPart(ap->ourArt));
-		}
-	}
-
-	ourOwner->commonInfo->src.setTo(this, false);
-	ourOwner->commonInfo->src.slotID = ArtifactPosition::TRANSITION_POS;
-
-	LOCPLINT->cb->swapArtifacts(ArtifactLocation(ourOwner->curHero, slotID),
-		ArtifactLocation(ourOwner->curHero, ArtifactPosition::TRANSITION_POS));
-}
-
-void CHeroArtPlace::showAll(SDL_Surface * to)
-{
-	if (ourArt && !picked && ourArt == ourOwner->curHero->getArt(slotID, false)) //last condition is needed for disassembling -> artifact may be gone, but we don't know yet TODO: real, nice solution
-	{
-		CIntObject::showAll(to);
-	}
-
-	if(marked && active)
-	{
-		// Draw vertical bars.
-		for (int i = 0; i < pos.h; ++i)
-		{
-			CSDL_Ext::putPixelWithoutRefresh(to, pos.x,             pos.y + i, 240, 220, 120);
-			CSDL_Ext::putPixelWithoutRefresh(to, pos.x + pos.w - 1, pos.y + i, 240, 220, 120);
-		}
-
-		// Draw horizontal bars.
-		for (int i = 0; i < pos.w; ++i)
-		{
-			CSDL_Ext::putPixelWithoutRefresh(to, pos.x + i, pos.y,             240, 220, 120);
-			CSDL_Ext::putPixelWithoutRefresh(to, pos.x + i, pos.y + pos.h - 1, 240, 220, 120);
-		}
-	}
-}
-
-bool CHeroArtPlace::fitsHere(const CArtifactInstance * art) const
-{
-	// You can place 'no artifact' anywhere.
-	if(!art)
-		return true;
-
-	// Anything but War Machines can be placed in backpack.
-	if (slotID >= GameConstants::BACKPACK_START)
-		return !art->artType->isBig();
-
-	return art->canBePutAt(ArtifactLocation(ourOwner->curHero, slotID), true);
-}
-
-void CHeroArtPlace::setMeAsDest(bool backpackAsVoid)
-{
-	ourOwner->commonInfo->dst.setTo(this, backpackAsVoid);
-}
-
-void CHeroArtPlace::setArtifact(const CArtifactInstance *art)
-{
-	baseType = -1; //by default we don't store any component
-	ourArt = art;
-	if(!art)
+	baseType = -1; // By default we don't store any component
+	ourArt = artInst;
+	if(!artInst)
 	{
 		image->disable();
 		text.clear();
 		hoverText = CGI->generaltexth->allTexts[507];
 		return;
 	}
-
 	image->enable();
-	image->setFrame(locked ? ArtifactID::ART_LOCK : art->artType->getIconIndex());
-
-	text = art->getDescription();
-
-	// Display info about set
-	if(ourOwner && ourOwner->curHero && !art->canBeDisassembled())
+	image->setFrame(artInst->artType->getIconIndex());
+	if(artInst->getTypeId() == ArtifactID::SPELL_SCROLL)
 	{
-		for(const auto combinedArt : art->artType->constituentOf)
+		auto spellID = artInst->getScrollSpellID();
+		if(spellID.num >= 0)
 		{
-			std::string artList;
-			text += "\n\n";
-			text += "{" + combinedArt->getNameTranslated() + "}";
-			int wornArtifacts = 0;
-			for(const auto part : *combinedArt->constituents)
-			{
-				if(art->artType->constituentOf.size() <= 1)
-					artList += "\n" + part->getNameTranslated();
-				if(ourOwner->curHero->hasArt(part->getId(), true))
-					wornArtifacts++;
-			}
-			text += " (" + boost::str(boost::format("%d") % wornArtifacts) + " / " +
-				boost::str(boost::format("%d") % combinedArt->constituents->size()) + ")" + artList;
-		}
-	}
-
-	if(art->artType->getId() == ArtifactID::SPELL_SCROLL)
-	{
-		int spellID = art->getScrollSpellID();
-		if(spellID >= 0)
-		{
-			//add spell component info (used to provide a pic in r-click popup)
+			// Add spell component info (used to provide a pic in r-click popup)
 			baseType = CComponent::spell;
 			type = spellID;
 			bonusValue = 0;
@@ -396,687 +52,275 @@ void CHeroArtPlace::setArtifact(const CArtifactInstance *art)
 	else
 	{
 		baseType = CComponent::artifact;
-		type = art->artType->getId();
+		type = artInst->getTypeId();
 		bonusValue = 0;
 	}
-
-	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());
+	text = artInst->getDescription();
 }
 
-void CArtifactsOfHero::SCommonPart::reset()
+CArtPlace::CArtPlace(Point position, const CArtifactInstance * Art) 
+	: ourArt(Art)
 {
-	src.clear();
-	dst.clear();
-	CCS->curh->dragAndDropCursor(nullptr);
-}
-
-void CArtifactsOfHero::setHero(const CGHeroInstance * hero)
-{
-	curHero = hero;
-	if (curHero->artifactsInBackpack.size() > 0)
-		backpackPos %= curHero->artifactsInBackpack.size();
-	else
-		backpackPos = 0;
-
-	// Fill the slots for worn artifacts and backpack.
-
-	for(auto p : artWorn)
-	{
-		setSlotData(p.second, p.first);
-	}
-
-	scrollBackpack(0);
+	image = nullptr;
+	pos += position;
+	pos.w = pos.h = 44;
 }
 
-void CArtifactsOfHero::dispose()
+void CArtPlace::clickLeft(tribool down, bool previousState)
 {
-	CCS->curh->dragAndDropCursor(nullptr);
+	LRClickableAreaWTextComp::clickLeft(down, previousState);
 }
 
-void CArtifactsOfHero::scrollBackpack(int dir)
+void CArtPlace::clickRight(tribool down, bool previousState)
 {
-	int artsInBackpack = static_cast<int>(curHero->artifactsInBackpack.size());
-	backpackPos += dir;
-	if(backpackPos < 0)// No guarantee of modulus behavior with negative operands -> we keep it positive
-		backpackPos += artsInBackpack;
-
-	if(artsInBackpack)
-		backpackPos %= artsInBackpack;
-
-	std::multiset<const CArtifactInstance *> toOmit = artifactsOnAltar;
-	if(commonInfo->src.art) //if we picked an art from backapck, its slot has to be omitted
-		toOmit.insert(commonInfo->src.art);
-
-	int omitedSoFar = 0;
-
-	//set new data
-	size_t s = 0;
-	for( ; s < artsInBackpack; ++s)
-	{
-
-		if (s < artsInBackpack)
-		{
-			auto slotID = ArtifactPosition(GameConstants::BACKPACK_START + (s + backpackPos)%artsInBackpack);
-			const CArtifactInstance *art = curHero->getArt(slotID);
-			assert(art);
-			if(!vstd::contains(toOmit, art))
-			{
-				if(s - omitedSoFar < backpack.size())
-					setSlotData(backpack[s-omitedSoFar], slotID);
-			}
-			else
-			{
-				toOmit -= art;
-				omitedSoFar++;
-				continue;
-			}
-		}
-	}
-	for( ; s - omitedSoFar < backpack.size(); s++)
-		eraseSlotData(backpack[s-omitedSoFar], ArtifactPosition(GameConstants::BACKPACK_START + (si32)s));
-
-	//in artifact merchant selling artifacts we may have highlight on one of backpack artifacts -> market needs update, cause artifact under highlight changed
-	if(highlightModeCallback)
-	{
-		for(auto & elem : backpack)
-		{
-			if(elem->marked)
-			{
-				highlightModeCallback(elem.get());
-				break;
-			}
-		}
-	}
-
-	//blocking scrolling if there is not enough artifacts to scroll
-	bool scrollingPossible = artsInBackpack - omitedSoFar > backpack.size();
-	leftArtRoll->block(!scrollingPossible);
-	rightArtRoll->block(!scrollingPossible);
-
-	safeRedraw();
+	LRClickableAreaWTextComp::clickRight(down, previousState);
 }
 
-/**
- * Marks possible slots where a given artifact can be placed, except backpack.
- *
- * @param art Artifact checked against.
- */
-void CArtifactsOfHero::markPossibleSlots(const CArtifactInstance* art, bool withRedraw)
+const CArtifactInstance * CArtPlace::getArt()
 {
-	for(CArtifactsOfHero *aoh : commonInfo->participants)
-		for(auto p : aoh->artWorn)
-			p.second->selectSlot(art->canBePutAt(ArtifactLocation(aoh->curHero, p.second->slotID), true));
-
-	if(withRedraw)
-		safeRedraw();
+	return ourArt;
 }
 
-/**
- * Unamarks all slots.
- */
-void CArtifactsOfHero::unmarkSlots(bool withRedraw)
+CCommanderArtPlace::CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot, const CArtifactInstance * Art)
+	: CArtPlace(position, Art),
+	commanderOwner(commanderOwner),
+	commanderSlotID(artSlot.num)
 {
-	if(commonInfo)
-		for(CArtifactsOfHero *aoh : commonInfo->participants)
-			aoh->unmarkLocalSlots(false);
-	else
-		unmarkLocalSlots(false);
-
-	if(withRedraw)
-		safeRedraw();
+	createImage();
+	setArtifact(Art);
 }
 
-void CArtifactsOfHero::unmarkLocalSlots(bool withRedraw)
+void CCommanderArtPlace::createImage()
 {
-	for(auto & p : artWorn)
-		p.second->selectSlot(false);
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
 
-	for(auto & place : backpack)
-		place->selectSlot(false);
+	int imageIndex = 0;
+	if(ourArt)
+		imageIndex = ourArt->artType->getIconIndex();
 
-	if(withRedraw)
-		safeRedraw();
+	image = std::make_shared<CAnimImage>("artifact", imageIndex);
+	if(!ourArt)
+		image->disable();
 }
 
-/**
- * Assigns an artifacts to an artifact place depending on it's new slot ID.
- */
-void CArtifactsOfHero::setSlotData(ArtPlacePtr artPlace, ArtifactPosition slotID)
+void CCommanderArtPlace::returnArtToHeroCallback()
 {
-	if(!artPlace && slotID >= GameConstants::BACKPACK_START) //spurious call from artifactMoved in attempt to update hidden backpack slot
-	{
-		return;
-	}
-
-	artPlace->pickSlot(false);
-	artPlace->slotID = slotID;
-
-	if(const ArtSlotInfo *asi = curHero->getSlot(slotID))
+	ArtifactPosition artifactPos = commanderSlotID;
+	ArtifactPosition freeSlot = ArtifactUtils::getArtBackpackPosition(commanderOwner, ourArt->getTypeId());
+	if(freeSlot == ArtifactPosition::PRE_FIRST)
 	{
-		artPlace->lockSlot(asi->locked);
-		artPlace->setArtifact(asi->artifact);
+		LOCPLINT->showInfoDialog(CGI->generaltexth->translate("core.genrltxt.152"));
 	}
 	else
-		artPlace->setArtifact(nullptr);
-}
-
-/**
- * Makes given artifact slot appear as empty with a certain slot ID.
- */
-void CArtifactsOfHero::eraseSlotData(ArtPlacePtr artPlace, ArtifactPosition slotID)
-{
-	artPlace->pickSlot(false);
-	artPlace->slotID = slotID;
-	artPlace->setArtifact(nullptr);
-}
-
-CArtifactsOfHero::CArtifactsOfHero(ArtPlaceMap ArtWorn, std::vector<ArtPlacePtr> Backpack,
-		std::shared_ptr<CButton> leftScroll, std::shared_ptr<CButton> rightScroll, bool createCommonPart)
-	: curHero(nullptr),
-	artWorn(ArtWorn),
-	backpack(Backpack),
-	backpackPos(0),
-	commonInfo(nullptr),
-	leftArtRoll(leftScroll),
-	rightArtRoll(rightScroll),
-	allowedAssembling(true),
-	highlightModeCallback(nullptr)
-{
-	if(createCommonPart)
-	{
-		commonInfo = std::make_shared<CArtifactsOfHero::SCommonPart>();
-		commonInfo->participants.insert(this);
-	}
-
-	// Init slots for worn artifacts.
-	for(auto p : artWorn)
-	{
-		p.second->ourOwner = this;
-		eraseSlotData(p.second, p.first);
-	}
-
-	// Init slots for the backpack.
-	for(size_t s=0; s<backpack.size(); ++s)
-	{
-		backpack[s]->ourOwner = this;
-		eraseSlotData(backpack[s], ArtifactPosition(GameConstants::BACKPACK_START + (si32)s));
-	}
-
-	leftArtRoll->addCallback(std::bind(&CArtifactsOfHero::scrollBackpack, this,-1));
-	rightArtRoll->addCallback(std::bind(&CArtifactsOfHero::scrollBackpack, this,+1));
-}
-
-CArtifactsOfHero::CArtifactsOfHero(const Point & position, bool createCommonPart)
-	: curHero(nullptr),
-	backpackPos(0),
-	commonInfo(nullptr),
-	allowedAssembling(true),
-	highlightModeCallback(nullptr)
-{
-	if(createCommonPart)
-	{
-		commonInfo = std::make_shared<CArtifactsOfHero::SCommonPart>();
-		commonInfo->participants.insert(this);
-	}
-
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	pos += position;
-
-	std::vector<Point> slotPos =
-	{
-		Point(509,30),  Point(567,240), Point(509,80),  //0-2
-		Point(383,68),  Point(564,183), Point(509,130), //3-5
-		Point(431,68),  Point(610,183), Point(515,295), //6-8
-		Point(383,143), Point(399,194), Point(415,245), //9-11
-		Point(431,296), Point(564,30),  Point(610,30), //12-14
-		Point(610,76),  Point(610,122), Point(610,310), //15-17
-		Point(381,296) //18
-	};
-
-	// Create slots for worn artifacts.
-	for(si32 g = 0; g < GameConstants::BACKPACK_START; g++)
 	{
-		artWorn[ArtifactPosition(g)] = std::make_shared<CHeroArtPlace>(slotPos[g]);
-		artWorn[ArtifactPosition(g)]->ourOwner = this;
-		eraseSlotData(artWorn[ArtifactPosition(g)], ArtifactPosition(g));
-	}
-
-	// Create slots for the backpack.
-	for(int s=0; s<5; ++s)
-	{
-		auto add = std::make_shared<CHeroArtPlace>(Point(403 + 46 * s, 365));
-
-		add->ourOwner = this;
-		eraseSlotData(add, ArtifactPosition(GameConstants::BACKPACK_START + s));
-
-		backpack.push_back(add);
-	}
-
-	leftArtRoll = std::make_shared<CButton>(Point(379, 364), "hsbtns3.def", CButton::tooltip(), [&](){ scrollBackpack(-1);}, SDLK_LEFT);
-	rightArtRoll = std::make_shared<CButton>(Point(632, 364), "hsbtns5.def", CButton::tooltip(), [&](){ scrollBackpack(+1);}, SDLK_RIGHT);
-}
+		ArtifactLocation src(commanderOwner->commander.get(), artifactPos);
+		ArtifactLocation dst(commanderOwner, freeSlot);
 
-CArtifactsOfHero::~CArtifactsOfHero()
-{
-	dispose();
-	// Artifact located in artifactsTransitionPos should be returned
-	if(!curHero->artifactsTransitionPos.empty())
-	{
-		auto artPlace = getArtPlace(
-			ArtifactUtils::getArtAnyPosition(curHero, curHero->artifactsTransitionPos.begin()->artifact->getTypeId()));
-		if(artPlace)
-		{
-			assert(artPlace->ourOwner);
-			artPlace->setMeAsDest();
-			artPlace->ourOwner->realizeCurrentTransaction();
-		}
-		else
+		if(ourArt->canBePutAt(dst, true))
 		{
-			LOCPLINT->cb->eraseArtifactByClient(ArtifactLocation(curHero, ArtifactPosition::TRANSITION_POS));
+			LOCPLINT->cb->swapArtifacts(src, dst);
+			setArtifact(nullptr);
+			parent->redraw();
 		}
 	}
 }
 
-void CArtifactsOfHero::updateParentWindow()
+void CCommanderArtPlace::clickLeft(tribool down, bool previousState)
 {
-	if(CHeroWindow * chw = dynamic_cast<CHeroWindow*>(GH.topInt().get()))
-	{
-		chw->update(curHero, true);
-	}
-	else if(CExchangeWindow * cew = dynamic_cast<CExchangeWindow*>(GH.topInt().get()))
-	{
-		cew->updateWidgets();
-	}
+	if(ourArt && text.size() && down)
+		LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.commanderWindow.artifactMessage"), [this]() { returnArtToHeroCallback(); }, []() {});
 }
 
-void CArtifactsOfHero::safeRedraw()
+void CCommanderArtPlace::clickRight(tribool down, bool previousState)
 {
-	if (active)
-	{
-		if(parent)
-			parent->redraw();
-		else
-			redraw();
-	}
+	if(ourArt && text.size() && down)
+		CArtPlace::clickRight(down, previousState);
 }
 
-void CArtifactsOfHero::realizeCurrentTransaction()
+void CCommanderArtPlace::setArtifact(const CArtifactInstance * art)
 {
-	assert(commonInfo->src.AOH);
-	assert(commonInfo->dst.AOH);
-	LOCPLINT->cb->swapArtifacts(ArtifactLocation(commonInfo->src.AOH->curHero, commonInfo->src.slotID),
-								ArtifactLocation(commonInfo->dst.AOH->curHero, commonInfo->dst.slotID));
+	setInternals(art);
 }
 
-void CArtifactsOfHero::artifactMoved(const ArtifactLocation & src, const ArtifactLocation & dst, bool withUIUpdate)
+CHeroArtPlace::CHeroArtPlace(Point position, const CArtifactInstance * Art)
+	: CArtPlace(position, Art),
+	locked(false),
+	marked(false)
 {
-	bool isCurHeroSrc = src.isHolder(curHero),
-		isCurHeroDst = dst.isHolder(curHero);
-	if(isCurHeroSrc && ArtifactUtils::isSlotBackpack(src.slot))
-		updateSlot(src.slot);
-	if(isCurHeroDst && ArtifactUtils::isSlotBackpack(dst.slot))
-		updateSlot(dst.slot);
-	// We need to update all slots, artifact might be combined and affect more slots
-	if(isCurHeroSrc || isCurHeroDst)
-		updateWornSlots(false);
-
-	if(!isCurHeroSrc && !isCurHeroDst)
-		return;
-
-	// When moving one artifact onto another it leads to two art movements: dst->TRANSITION_POS; src->dst
-	// however after first movement we pick the art from TRANSITION_POS and the second movement coming when
-	// we have a different artifact may look surprising... but it's valid.
-
-	// Used when doing dragAndDrop and artifact swap multiple times
-	if(src.slot == ArtifactPosition::TRANSITION_POS && 
-		commonInfo->src.slotID == ArtifactPosition::TRANSITION_POS &&
-		commonInfo->dst.slotID == ArtifactPosition::PRE_FIRST && 
-		isCurHeroDst)
-	{
-		auto art = curHero->getArt(ArtifactPosition::TRANSITION_POS);
-		assert(art);
-		CCS->curh->dragAndDropCursor("artifact", art->artType->getIconIndex());
-		if(withUIUpdate)
-			markPossibleSlots(art);
-
-		commonInfo->src.art = art;
-		commonInfo->src.slotID = src.slot;
-	}
-	// Artifact was taken from us
-	else if(commonInfo->src == src && dst.slot != ArtifactPosition::TRANSITION_POS)
-	{
-		// Expected movement from slot ot slot
-		assert(commonInfo->dst == dst
-			// Artifact moved back to backpack (eg. to make place for art we are moving)
-			|| dst.slot == dst.getHolderArtSet()->artifactsInBackpack.size() + GameConstants::BACKPACK_START
-			|| dst.getHolderArtSet()->bearerType() != ArtBearer::HERO);
-		commonInfo->reset();
-		unmarkSlots();
-	}
-	else
-	{
-		// The dest artifact was moved after the swap -> we are picking it
-		if(commonInfo->dst == src)
-		{
-			assert(dst.slot == ArtifactPosition::TRANSITION_POS);
-			commonInfo->reset();
-
-			for(CArtifactsOfHero * aoh : commonInfo->participants)
-			{
-				if(dst.isHolder(aoh->curHero))
-				{
-					commonInfo->src.AOH = aoh;
-					break;
-				}
-			}
-
-			commonInfo->src.art = dst.getArt();
-			commonInfo->src.slotID = dst.slot;
-			assert(commonInfo->src.AOH);
-			CCS->curh->dragAndDropCursor("artifact", dst.getArt()->artType->getIconIndex());
-		}
-		if(!curHero->artifactsTransitionPos.empty() && withUIUpdate)
-		{
-			auto artInst = curHero->getArt(ArtifactPosition::TRANSITION_POS);
-			markPossibleSlots(artInst);
-			CCS->curh->dragAndDropCursor("artifact", artInst->artType->getIconIndex());
-		}
-	}
-
-	if(withUIUpdate)
-	{
-		updateParentWindow();
-		scrollBackpack(0);
-	}
+	createImage();
 }
 
-void CArtifactsOfHero::artifactRemoved(const ArtifactLocation &al)
+void CHeroArtPlace::lockSlot(bool on)
 {
-	if(al.isHolder(curHero))
-	{
-		if(al.slot < GameConstants::BACKPACK_START)
-			updateWornSlots(0);
-		else
-			scrollBackpack(0); //update backpack slots
-	}
-}
+	if(locked == on)
+		return;
 
-CArtifactsOfHero::ArtPlacePtr CArtifactsOfHero::getArtPlace(ArtifactPosition slot)
-{
-	if(ArtifactUtils::isSlotEquipment(slot))
-	{
-		if(artWorn.find(slot) == artWorn.end())
-		{
-			logGlobal->error("CArtifactsOfHero::getArtPlace: invalid slot %d", slot);
-			return nullptr;
-		}
+	locked = on;
 
-		return artWorn[slot];
-	}
-	if(ArtifactUtils::isSlotBackpack(slot))
-	{
-		for(ArtPlacePtr ap : backpack)
-			if(ap->slotID == slot)
-				return ap;
-		return nullptr;
-	}
+	if(on)
+		image->setFrame(ArtifactID::ART_LOCK);
+	else if(ourArt)
+		image->setFrame(ourArt->artType->getIconIndex());
 	else
-	{
-		return nullptr;
-	}
-}
-
-void CArtifactsOfHero::artifactUpdateSlots(const ArtifactLocation & al)
-{
-	if(al.isHolder(curHero))
-	{
-		if(ArtifactUtils::isSlotBackpack(al.slot))
-			updateBackpackSlots(true);
-		else
-			updateWornSlots(true);
-	}
+		image->setFrame(0);
 }
 
-void CArtifactsOfHero::updateWornSlots(bool redrawParent)
+bool CHeroArtPlace::isLocked()
 {
-	for(auto p : artWorn)
-		updateSlot(p.first);
-
-	if(redrawParent)
-		updateParentWindow();
+	return locked;
 }
 
-void CArtifactsOfHero::updateBackpackSlots(bool redrawParent)
+void CHeroArtPlace::selectSlot(bool on)
 {
-	for(auto artPlace : backpack)
-		updateSlot(artPlace->slotID);
-	scrollBackpack(0);
+	if(marked == on)
+		return;
 
-	if(redrawParent)
-		updateParentWindow();
+	marked = on;
+	if(on)
+		selection->enable();
+	else
+		selection->disable();
 }
 
-const CGHeroInstance * CArtifactsOfHero::getHero() const
+bool CHeroArtPlace::isMarked() const
 {
-	return curHero;
+	return marked;
 }
 
-void CArtifactsOfHero::updateSlot(ArtifactPosition slotID)
+void CHeroArtPlace::clickLeft(tribool down, bool previousState)
 {
-	setSlotData(getArtPlace(slotID), slotID);
-}
+	if(down || !previousState)
+		return;
 
-CArtifactHolder::CArtifactHolder()
-{
+	if(leftClickCallback)
+		leftClickCallback(*this);
 }
 
-void CWindowWithArtifacts::addSet(std::shared_ptr<CArtifactsOfHero> artSet)
-{
-	artSets.emplace_back(artSet);
-}
-
-std::shared_ptr<CArtifactsOfHero::SCommonPart> CWindowWithArtifacts::getCommonPart()
+void CHeroArtPlace::clickRight(tribool down, bool previousState)
 {
-	for(auto artSetWeak : artSets)
+	if(down)
 	{
-		std::shared_ptr<CArtifactsOfHero> realPtr = artSetWeak.lock();
-		if(realPtr)
-			return realPtr->commonInfo;
+		if(rightClickCallback)
+			rightClickCallback(*this);
 	}
-
-	return std::shared_ptr<CArtifactsOfHero::SCommonPart>();
 }
 
-void CWindowWithArtifacts::artifactRemoved(const ArtifactLocation &artLoc)
+void CHeroArtPlace::showAll(SDL_Surface* to)
 {
-	for(auto artSetWeak : artSets)
+	if(ourArt)
 	{
-		std::shared_ptr<CArtifactsOfHero> realPtr = artSetWeak.lock();
-		if(realPtr)
-			realPtr->artifactRemoved(artLoc);
+		CIntObject::showAll(to);
 	}
-}
-
-void CWindowWithArtifacts::artifactMoved(const ArtifactLocation &artLoc, const ArtifactLocation &destLoc, bool withRedraw)
-{
-	CArtifactsOfHero * destaoh = nullptr;
 
-	for(auto artSetWeak : artSets)
+	if(marked && active)
 	{
-		std::shared_ptr<CArtifactsOfHero> realPtr = artSetWeak.lock();
-		if(realPtr)
+		// Draw vertical bars.
+		for(int i = 0; i < pos.h; ++i)
 		{
-			realPtr->artifactMoved(artLoc, destLoc, withRedraw);
-			if(destLoc.isHolder(realPtr->getHero()))
-				destaoh = realPtr.get();
+			CSDL_Ext::putPixelWithoutRefresh(to, pos.x, pos.y + i, 240, 220, 120);
+			CSDL_Ext::putPixelWithoutRefresh(to, pos.x + pos.w - 1, pos.y + i, 240, 220, 120);
 		}
-	}
 
-	//Make sure the status bar is updated so it does not display old text
-	if(destaoh != nullptr && destaoh->getArtPlace(destLoc.slot) != nullptr)
-	{
-		destaoh->getArtPlace(destLoc.slot)->hover(true);
+		// Draw horizontal bars.
+		for(int i = 0; i < pos.w; ++i)
+		{
+			CSDL_Ext::putPixelWithoutRefresh(to, pos.x + i, pos.y, 240, 220, 120);
+			CSDL_Ext::putPixelWithoutRefresh(to, pos.x + i, pos.y + pos.h - 1, 240, 220, 120);
+		}
 	}
 }
 
-void CWindowWithArtifacts::artifactDisassembled(const ArtifactLocation &artLoc)
+void CHeroArtPlace::setArtifact(const CArtifactInstance * art)
 {
-	for(auto artSetWeak : artSets)
+	setInternals(art);
+	if(art)
 	{
-		std::shared_ptr<CArtifactsOfHero> realPtr = artSetWeak.lock();
-		if(realPtr)
-			realPtr->artifactUpdateSlots(artLoc);
-	}
-}
+		image->setFrame(locked ? ArtifactID::ART_LOCK : art->artType->getIconIndex());
 
-void CWindowWithArtifacts::artifactAssembled(const ArtifactLocation &artLoc)
-{
-	for(auto artSetWeak : artSets)
-	{
-		std::shared_ptr<CArtifactsOfHero> realPtr = artSetWeak.lock();
-		if(realPtr)
-			realPtr->artifactUpdateSlots(artLoc);
+		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());
 	}
-}
-
-void CArtifactsOfHero::SCommonPart::Artpos::clear()
-{
-	slotID = ArtifactPosition::PRE_FIRST;
-	AOH = nullptr;
-	art = nullptr;
-}
-
-void CArtifactsOfHero::SCommonPart::Artpos::setTo(const CHeroArtPlace *place, bool dontTakeBackpack)
-{
-	slotID = place->slotID;
-	AOH = place->ourOwner;
-
-	if(ArtifactUtils::isSlotBackpack(slotID) && dontTakeBackpack)
-		art = nullptr;
 	else
-		art = place->ourArt;
-}
-
-bool CArtifactsOfHero::SCommonPart::Artpos::operator==(const ArtifactLocation &al) const
-{
-	if(!AOH)
-		return false;
-	bool ret = al.isHolder(AOH->curHero)  &&  al.slot == slotID;
-
-	//assert(al.getArt() == art);
-	return ret;
-}
-
-bool CArtifactsOfHero::SCommonPart::Artpos::valid()
-{
-	assert(AOH && art);
-	return art == AOH->curHero->getArt(slotID);
-}
-
-CArtPlace::CArtPlace(Point position, const CArtifactInstance * Art) : ourArt(Art)
-{
-	image = nullptr;
-	pos += position;
-	pos.w = pos.h = 44;
-}
-
-void CArtPlace::clickLeft(tribool down, bool previousState)
-{
-	LRClickableAreaWTextComp::clickLeft(down, previousState);
-}
-
-void CArtPlace::clickRight(tribool down, bool previousState)
-{
-	LRClickableAreaWTextComp::clickRight(down, previousState);
-}
-
-CCommanderArtPlace::CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot, const CArtifactInstance * Art) : CArtPlace(position, Art), commanderOwner(commanderOwner), commanderSlotID(artSlot.num)
-{
-	createImage();
-	setArtifact(Art);
+	{
+		lockSlot(false);
+	}
 }
 
-void CCommanderArtPlace::clickLeft(tribool down, bool previousState)
+void CHeroArtPlace::addCombinedArtInfo(std::map<const CArtifact*, int> & arts)
 {
-	if (ourArt && text.size() && down)
-		LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.commanderWindow.artifactMessage"), [this](){ returnArtToHeroCallback(); }, [](){});
-}
-
-void CCommanderArtPlace::clickRight(tribool down, bool previousState)
-{
-	if (ourArt && text.size() && down)
-		CArtPlace::clickRight(down, previousState);
+	for(const auto & combinedArt : arts)
+	{
+		std::string artList;
+		text += "\n\n";
+		text += "{" + combinedArt.first->getNameTranslated() + "}";
+		if(arts.size() == 1)
+		{
+			for(const auto part : *combinedArt.first->constituents)
+				artList += "\n" + part->getNameTranslated();
+		}
+		text += " (" + boost::str(boost::format("%d") % combinedArt.second) + " / " +
+			boost::str(boost::format("%d") % combinedArt.first->constituents->size()) + ")" + artList;
+	}
 }
 
-void CCommanderArtPlace::createImage()
+void CHeroArtPlace::createImage()
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
-	int imageIndex = 0;
-	if(ourArt)
+	si32 imageIndex = 0;
+
+	if(locked)
+		imageIndex = ArtifactID::ART_LOCK;
+	else if(ourArt)
 		imageIndex = ourArt->artType->getIconIndex();
 
 	image = std::make_shared<CAnimImage>("artifact", imageIndex);
 	if(!ourArt)
 		image->disable();
+
+	selection = std::make_shared<CAnimImage>("artifact", ArtifactID::ART_SELECTION);
+	selection->disable();
 }
 
-void CCommanderArtPlace::returnArtToHeroCallback()
+bool ArtifactUtilsClient::askToAssemble(const CGHeroInstance * hero, const ArtifactPosition & slot)
 {
-	ArtifactPosition artifactPos = commanderSlotID;
-	ArtifactPosition freeSlot = ArtifactUtils::getArtBackpackPosition(commanderOwner, ourArt->getTypeId());
-	if(freeSlot == ArtifactPosition::PRE_FIRST)
-	{
-		LOCPLINT->showInfoDialog(CGI->generaltexth->translate("core.genrltxt.152"));
-	}
-	else
+	assert(hero);
+	const auto art = hero->getArt(slot);
+	assert(art);
+	auto assemblyPossibilities = ArtifactUtils::assemblyPossibilities(hero, art->getTypeId(), ArtifactUtils::isSlotEquipment(slot));
+
+	for(const auto combinedArt : assemblyPossibilities)
 	{
-		ArtifactLocation src(commanderOwner->commander.get(), artifactPos);
-		ArtifactLocation dst(commanderOwner, freeSlot);
+		LOCPLINT->showArtifactAssemblyDialog(
+			art->artType,
+			combinedArt,
+			std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), hero, slot, true, combinedArt->getId()));
 
-		if(ourArt->canBePutAt(dst, true))
-		{
-			LOCPLINT->cb->swapArtifacts(src, dst);
-			setArtifact(nullptr);
-			parent->redraw();
-		}
+		if(assemblyPossibilities.size() > 2)
+			logGlobal->warn("More than one possibility of assembling on %s... taking only first", art->artType->getNameTranslated());
+		return true;
 	}
+	return false;
 }
 
-void CCommanderArtPlace::setArtifact(const CArtifactInstance * art)
+bool ArtifactUtilsClient::askToDisassemble(const CGHeroInstance * hero, const ArtifactPosition & slot)
 {
-	baseType = -1; //by default we don't store any component
-	ourArt = art;
-	if (!art)
-	{
-		image->disable();
-		text.clear();
-		return;
-	}
-
-	image->enable();
-	image->setFrame(art->artType->getIconIndex());
-
-	text = art->getDescription();
+	assert(hero);
+	const auto art = hero->getArt(slot);
+	assert(art);
 
-	if (art->artType->getId() == ArtifactID::SPELL_SCROLL)
-	{
-		int spellID = art->getScrollSpellID();
-		if (spellID >= 0)
-		{
-			//add spell component info (used to provide a pic in r-click popup)
-			baseType = CComponent::spell;
-			type = spellID;
-			bonusValue = 0;
-		}
-	}
-	else
+	if(art->canBeDisassembled())
 	{
-		baseType = CComponent::artifact;
-		type = art->artType->getId();
-		bonusValue = 0;
+		if(ArtifactUtils::isSlotBackpack(slot) && !ArtifactUtils::isBackpackFreeSlots(hero, art->artType->constituents->size() - 1))
+			return false;
+
+		LOCPLINT->showArtifactAssemblyDialog(
+			art->artType,
+			nullptr,
+			std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), hero, slot, false, ArtifactID()));
+		return true;
 	}
+	return false;
 }

+ 30 - 123
client/widgets/CArtifactHolder.h

@@ -14,37 +14,38 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 struct ArtifactLocation;
+class CArtifactSet;
 
 VCMI_LIB_NAMESPACE_END
 
-class CArtifactsOfHero;
 class CAnimImage;
 class CButton;
 
 class CArtifactHolder
 {
 public:
-	CArtifactHolder();
-
-	virtual void artifactRemoved(const ArtifactLocation &artLoc)=0;
-	virtual void artifactMoved(const ArtifactLocation &artLoc, const ArtifactLocation &destLoc, bool withRedraw)=0;
-	virtual void artifactDisassembled(const ArtifactLocation &artLoc)=0;
-	virtual void artifactAssembled(const ArtifactLocation &artLoc)=0;
+	virtual void artifactRemoved(const ArtifactLocation & artLoc)=0;
+	virtual void artifactMoved(const ArtifactLocation & artLoc, const ArtifactLocation & destLoc, bool withRedraw)=0;
+	virtual void artifactDisassembled(const ArtifactLocation & artLoc)=0;
+	virtual void artifactAssembled(const ArtifactLocation & artLoc)=0;
 };
 
 class CArtPlace : public LRClickableAreaWTextComp
 {
 protected:
 	std::shared_ptr<CAnimImage> image;
+	const CArtifactInstance * ourArt;
+
+	void setInternals(const CArtifactInstance * artInst);
 	virtual void createImage()=0;
-public:
-	const CArtifactInstance * ourArt; // should be changed only with setArtifact()
 
+public:
 	CArtPlace(Point position, const CArtifactInstance * Art = nullptr);
 	void clickLeft(tribool down, bool previousState) override;
 	void clickRight(tribool down, bool previousState) override;
+	const CArtifactInstance * getArt();
 
-	virtual void setArtifact(const CArtifactInstance *art)=0;
+	virtual void setArtifact(const CArtifactInstance * art)=0;
 };
 
 class CCommanderArtPlace : public CArtPlace
@@ -55,138 +56,44 @@ protected:
 
 	void createImage() override;
 	void returnArtToHeroCallback();
+
 public:
 	CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot, const CArtifactInstance * Art = nullptr);
 	void clickLeft(tribool down, bool previousState) override;
 	void clickRight(tribool down, bool previousState) override;
-
-	virtual void setArtifact(const CArtifactInstance * art) override;
-
+	void setArtifact(const CArtifactInstance * art) override;
 };
 
-/// Artifacts can be placed there. Gets shown at the hero window
 class CHeroArtPlace: public CArtPlace
 {
-	std::shared_ptr<CAnimImage> selection;
-
-	void createImage() override;
-
 public:
-	// consider these members as const - change them only with appropriate methods e.g. lockSlot()
-	bool locked;
-	bool picked;
-	bool marked;
-
-	ArtifactPosition slotID; //Arts::EPOS enum + backpack starting from Arts::BACKPACK_START
+	using ClickHandler = std::function<void(CHeroArtPlace&)>;
 
-	CArtifactsOfHero * ourOwner;
+	ArtifactPosition slot;
+	ClickHandler leftClickCallback;
+	ClickHandler rightClickCallback;
 
 	CHeroArtPlace(Point position, const CArtifactInstance * Art = nullptr);
-
 	void lockSlot(bool on);
-	void pickSlot(bool on);
+	bool isLocked();
 	void selectSlot(bool on);
-
+	bool isMarked() const;
 	void clickLeft(tribool down, bool previousState) override;
 	void clickRight(tribool down, bool previousState) override;
-	void select();
 	void showAll(SDL_Surface * to) override;
-	bool fitsHere (const CArtifactInstance * art) const; //returns true if given artifact can be placed here
+	void setArtifact(const CArtifactInstance * art) override;
+	void addCombinedArtInfo(std::map<const CArtifact*, int> & arts);
 
-	void setMeAsDest(bool backpackAsVoid = true);
-	void setArtifact(const CArtifactInstance *art) override;
-	static bool askToAssemble(const CGHeroInstance * hero, ArtifactPosition slot);
-	static bool askToDisassemble(const CGHeroInstance * hero, ArtifactPosition slot);
-};
+protected:
+	std::shared_ptr<CAnimImage> selection;
+	bool locked;
+	bool marked;
 
-/// Contains artifacts of hero. Distincts which artifacts are worn or backpacked
-class CArtifactsOfHero : public CIntObject
-{
-public:
-	using ArtPlacePtr = std::shared_ptr<CHeroArtPlace>;
-	using ArtPlaceMap = std::map<ArtifactPosition, ArtPlacePtr>;
-
-	struct SCommonPart
-	{
-		struct Artpos
-		{
-			ArtifactPosition slotID;
-			const CArtifactsOfHero *AOH;
-			const CArtifactInstance *art;
-
-			void clear();
-			void setTo(const CHeroArtPlace *place, bool dontTakeBackpack);
-			bool valid();
-			bool operator==(const ArtifactLocation &al) const;
-		} src, dst;
-
-		std::set<CArtifactsOfHero *> participants; // Needed to mark slots.
-
-		void reset();
-	};
-	std::shared_ptr<SCommonPart> commonInfo; //when we have more than one CArtifactsOfHero in one window with exchange possibility, we use this (eg. in exchange window); to be provided externally
-
-	std::shared_ptr<CButton> leftArtRoll;
-	std::shared_ptr<CButton> rightArtRoll;
-	bool allowedAssembling;
-
-	std::multiset<const CArtifactInstance*> artifactsOnAltar; //artifacts id that are technically present in backpack but in GUI are moved to the altar - they'll be omitted in backpack slots
-	std::function<void(CHeroArtPlace*)> highlightModeCallback; //if set, clicking on art place doesn't pick artifact but highlights the slot and calls this function
-
-	void realizeCurrentTransaction(); //calls callback with parameters stored in commonInfo
-	void artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst, bool withUIUpdate);
-	void artifactRemoved(const ArtifactLocation &al);
-	void artifactUpdateSlots(const ArtifactLocation &al);
-	ArtPlacePtr getArtPlace(ArtifactPosition slot);//may return null
-
-	void setHero(const CGHeroInstance * hero);
-	const CGHeroInstance *getHero() const;
-	void dispose(); //free resources not needed after closing windows and reset state
-	void scrollBackpack(int dir); //dir==-1 => to left; dir==1 => to right
-
-	void activate() override;
-	void deactivate() override;
-
-	void safeRedraw();
-	void markPossibleSlots(const CArtifactInstance * art, bool withRedraw = false);
-	void unmarkSlots(bool withRedraw = false); //unmarks slots in all visible AOHs
-	void unmarkLocalSlots(bool withRedraw = false); //unmarks slots in that particular AOH
-	void updateWornSlots(bool redrawParent = false);
-	void updateBackpackSlots(bool redrawParent = false);
-
-	void updateSlot(ArtifactPosition i);
-
-	CArtifactsOfHero(const Point& position, bool createCommonPart = false);
-	//Alternative constructor, used if custom artifacts positioning required (Kingdom interface)
-	CArtifactsOfHero(ArtPlaceMap ArtWorn, std::vector<ArtPlacePtr> Backpack,
-		std::shared_ptr<CButton> leftScroll, std::shared_ptr<CButton> rightScroll, bool createCommonPart = false);
-	~CArtifactsOfHero();
-	void updateParentWindow();
-	friend class CHeroArtPlace;
-
-private:
-
-	const CGHeroInstance * curHero;
-
-	ArtPlaceMap artWorn;
-
-	std::vector<ArtPlacePtr> backpack; //hero's visible backpack (only 5 elements!)
-	int backpackPos; //number of first art visible in backpack (in hero's vector)
-
-	void eraseSlotData(ArtPlacePtr artPlace, ArtifactPosition slotID);
-	void setSlotData(ArtPlacePtr artPlace, ArtifactPosition slotID);
+	void createImage() override;
 };
 
-class CWindowWithArtifacts : public CArtifactHolder
+namespace ArtifactUtilsClient
 {
-	std::vector<std::weak_ptr<CArtifactsOfHero>> artSets;
-public:
-	void addSet(std::shared_ptr<CArtifactsOfHero> artSet);
-
-	std::shared_ptr<CArtifactsOfHero::SCommonPart> getCommonPart();
-
-	void artifactRemoved(const ArtifactLocation &artLoc) override;
-	void artifactMoved(const ArtifactLocation &artLoc, const ArtifactLocation &destLoc, bool withRedraw) override;
-	void artifactDisassembled(const ArtifactLocation &artLoc) override;
-	void artifactAssembled(const ArtifactLocation &artLoc) override;
-};
+	bool askToAssemble(const CGHeroInstance * hero, const ArtifactPosition & slot);
+	bool askToDisassemble(const CGHeroInstance * hero, const ArtifactPosition & slot);
+}

+ 109 - 0
client/widgets/CArtifactsOfHeroAltar.cpp

@@ -0,0 +1,109 @@
+/*
+ * CArtifactsOfHeroAltar.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CArtifactsOfHeroAltar.h"
+
+#include "../CPlayerInterface.h"
+
+#include "../../CCallback.h"
+
+#include "../../lib/mapObjects/CGHeroInstance.h"
+
+CArtifactsOfHeroAltar::CArtifactsOfHeroAltar(const Point & position)
+	: visibleArtSet(ArtBearer::ArtBearer::HERO)
+{
+	init(
+		std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1), 
+		std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1), 
+		position,
+		std::bind(&CArtifactsOfHeroAltar::scrollBackpack, this, _1));
+	pickedArtFromSlot = ArtifactPosition::PRE_FIRST;
+};
+
+void CArtifactsOfHeroAltar::setHero(const CGHeroInstance * hero)
+{
+	if(hero)
+	{
+		visibleArtSet.artifactsWorn = hero->artifactsWorn;
+		visibleArtSet.artifactsInBackpack = hero->artifactsInBackpack;
+		CArtifactsOfHeroBase::setHero(hero);
+	}
+}
+
+void CArtifactsOfHeroAltar::updateWornSlots()
+{
+	for(auto place : artWorn)
+		setSlotData(getArtPlace(place.first), place.first, visibleArtSet);
+}
+
+void CArtifactsOfHeroAltar::updateBackpackSlots()
+{
+	for(auto artPlace : backpack)
+		setSlotData(getArtPlace(artPlace->slot), artPlace->slot, visibleArtSet);
+}
+
+void CArtifactsOfHeroAltar::scrollBackpack(int offset)
+{
+	CArtifactsOfHeroBase::scrollBackpackForArtSet(offset, visibleArtSet);
+	safeRedraw();
+}
+
+void CArtifactsOfHeroAltar::pickUpArtifact(CHeroArtPlace & artPlace)
+{
+	if(const auto art = artPlace.getArt())
+	{
+		pickedArtFromSlot = artPlace.slot;
+		artPlace.setArtifact(nullptr);
+		deleteFromVisible(art);
+		if(ArtifactUtils::isSlotBackpack(pickedArtFromSlot))
+			pickedArtFromSlot = curHero->getSlotByInstance(art);
+		assert(pickedArtFromSlot != ArtifactPosition::PRE_FIRST);
+		LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero, pickedArtFromSlot), ArtifactLocation(curHero, ArtifactPosition::TRANSITION_POS));
+	}
+}
+
+void CArtifactsOfHeroAltar::swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc)
+{
+	LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc);
+	const auto pickedArtInst = curHero->getArt(ArtifactPosition::TRANSITION_POS);
+	assert(pickedArtInst);
+	visibleArtSet.putArtifact(dstLoc.slot, const_cast<CArtifactInstance*>(pickedArtInst));
+}
+
+void CArtifactsOfHeroAltar::pickedArtMoveToAltar(const ArtifactPosition & slot)
+{
+	if(ArtifactUtils::isSlotBackpack(slot) || ArtifactUtils::isSlotEquipment(slot) || slot == ArtifactPosition::TRANSITION_POS)
+	{
+		assert(!curHero->getSlot(pickedArtFromSlot)->getArt());
+		LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero, slot), ArtifactLocation(curHero, pickedArtFromSlot));
+		pickedArtFromSlot = ArtifactPosition::PRE_FIRST;
+	}
+}
+
+void CArtifactsOfHeroAltar::deleteFromVisible(const CArtifactInstance * artInst)
+{
+	const auto slot = visibleArtSet.getSlotByInstance(artInst);
+	visibleArtSet.removeArtifact(slot);
+	if(ArtifactUtils::isSlotBackpack(slot))
+	{
+		scrollBackpackForArtSet(0, visibleArtSet);
+	}
+	else
+	{
+		if(artInst->canBeDisassembled())
+		{
+			for(const auto & part : dynamic_cast<const CCombinedArtifactInstance*>(artInst)->constituentsInfo)
+			{
+				if(part.slot != ArtifactPosition::PRE_FIRST)
+					getArtPlace(part.slot)->setArtifact(nullptr);
+			}
+		}
+	}
+}

+ 32 - 0
client/widgets/CArtifactsOfHeroAltar.h

@@ -0,0 +1,32 @@
+/*
+ * CArtifactsOfHeroAltar.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "CArtifactsOfHeroBase.h"
+
+#include "../../lib/CArtHandler.h"
+
+class CArtifactsOfHeroAltar : public CArtifactsOfHeroBase
+{
+public:
+	std::set<const CArtifactInstance*> artifactsOnAltar;
+	ArtifactPosition pickedArtFromSlot;
+	CArtifactFittingSet visibleArtSet;
+
+	CArtifactsOfHeroAltar(const Point & position);
+	void setHero(const CGHeroInstance * hero) override;
+	void updateWornSlots() override;
+	void updateBackpackSlots() override;
+	void scrollBackpack(int offset) override;
+	void pickUpArtifact(CHeroArtPlace & artPlace);
+	void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
+	void pickedArtMoveToAltar(const ArtifactPosition & slot);
+	void deleteFromVisible(const CArtifactInstance * artInst);
+};

+ 277 - 0
client/widgets/CArtifactsOfHeroBase.cpp

@@ -0,0 +1,277 @@
+/*
+ * CArtifactsOfHeroBase.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CArtifactsOfHeroBase.h"
+
+#include "../gui/CGuiHandler.h"
+#include "../gui/CursorHandler.h"
+
+#include "Buttons.h"
+
+#include "../renderSDL/SDL_Extensions.h"
+#include "../CPlayerInterface.h"
+#include "../CGameInfo.h"
+
+#include "../../CCallback.h"
+
+#include "../../lib/mapObjects/CGHeroInstance.h"
+
+CArtifactsOfHeroBase::CArtifactsOfHeroBase()
+	: backpackPos(0),
+	curHero(nullptr)
+{
+}
+
+CArtifactsOfHeroBase::~CArtifactsOfHeroBase()
+{
+	// TODO: cursor handling is CWindowWithArtifacts level. Should be moved when trading, kingdom and hero window are reworked
+	// This will interfere with the implementation of a separate backpack window
+	CCS->curh->dragAndDropCursor(nullptr);
+
+	// Artifact located in artifactsTransitionPos should be returned
+	if(getPickedArtifact())
+	{
+		auto slot = ArtifactUtils::getArtAnyPosition(curHero, curHero->artifactsTransitionPos.begin()->artifact->getTypeId());
+		if(slot == ArtifactPosition::PRE_FIRST)
+		{
+			LOCPLINT->cb->eraseArtifactByClient(ArtifactLocation(curHero, ArtifactPosition::TRANSITION_POS));
+		}
+		else
+		{
+			LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero, ArtifactPosition::TRANSITION_POS), ArtifactLocation(curHero, slot));
+		}
+	}
+}
+
+void CArtifactsOfHeroBase::init(
+	CHeroArtPlace::ClickHandler lClickCallback,
+	CHeroArtPlace::ClickHandler rClickCallback,
+	const Point & position,
+	BpackScrollHandler scrollHandler)
+{
+	// CArtifactsOfHeroBase::init may be transform to CArtifactsOfHeroBase::CArtifactsOfHeroBase if OBJECT_CONSTRUCTION_CAPTURING is removed
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
+	pos += position;
+	for(int g = 0; g < GameConstants::BACKPACK_START; g++)
+	{
+		artWorn[ArtifactPosition(g)] = std::make_shared<CHeroArtPlace>(slotPos[g]);
+	}
+	backpack.clear();
+	for(int s = 0; s < 5; s++)
+	{
+		auto artPlace = std::make_shared<CHeroArtPlace>(Point(403 + 46 * s, 365));
+		backpack.push_back(artPlace);
+	}
+	for(auto artPlace : artWorn)
+	{
+		artPlace.second->slot = artPlace.first;
+		artPlace.second->setArtifact(nullptr);
+		artPlace.second->leftClickCallback = lClickCallback;
+		artPlace.second->rightClickCallback = rClickCallback;
+	}
+	for(auto artPlace : backpack)
+	{
+		artPlace->setArtifact(nullptr);
+		artPlace->leftClickCallback = lClickCallback;
+		artPlace->rightClickCallback = rClickCallback;
+	}
+	leftBackpackRoll = std::make_shared<CButton>(Point(379, 364), "hsbtns3.def", CButton::tooltip(), [scrollHandler]() { scrollHandler(-1); }, SDLK_LEFT);
+	rightBackpackRoll = std::make_shared<CButton>(Point(632, 364), "hsbtns5.def", CButton::tooltip(), [scrollHandler]() { scrollHandler(+1); }, SDLK_RIGHT);
+	leftBackpackRoll->block(true);
+	rightBackpackRoll->block(true);
+}
+
+void CArtifactsOfHeroBase::leftClickArtPlace(CHeroArtPlace & artPlace)
+{
+	if(leftClickCallback)
+		leftClickCallback(*this, artPlace);
+}
+
+void CArtifactsOfHeroBase::rightClickArtPlace(CHeroArtPlace & artPlace)
+{
+	if(rightClickCallback)
+		rightClickCallback(*this, artPlace);
+}
+
+void CArtifactsOfHeroBase::setHero(const CGHeroInstance * hero)
+{
+	curHero = hero;
+	if(curHero->artifactsInBackpack.size() > 0)
+		backpackPos %= curHero->artifactsInBackpack.size();
+	else
+		backpackPos = 0;
+
+	for(auto slot : artWorn)
+	{
+		setSlotData(slot.second, slot.first, *curHero);
+	}
+	scrollBackpackForArtSet(0, *curHero);
+}
+
+const CGHeroInstance * CArtifactsOfHeroBase::getHero() const
+{
+	return curHero;
+}
+
+void CArtifactsOfHeroBase::scrollBackpack(int offset)
+{
+	scrollBackpackForArtSet(offset, *curHero);
+	safeRedraw();
+}
+
+void CArtifactsOfHeroBase::scrollBackpackForArtSet(int offset, const CArtifactSet & artSet)
+{
+	// offset==-1 => to left; offset==1 => to right
+	using slotInc = std::function<ArtifactPosition(ArtifactPosition&)>;
+	auto artsInBackpack = static_cast<int>(artSet.artifactsInBackpack.size());
+	auto scrollingPossible = artsInBackpack > backpack.size();
+
+	slotInc inc_straight = [](ArtifactPosition & slot) -> ArtifactPosition
+	{
+		return slot + 1;
+	};
+	slotInc inc_ring = [artsInBackpack](ArtifactPosition & slot) -> ArtifactPosition
+	{
+		return ArtifactPosition(GameConstants::BACKPACK_START + (slot - GameConstants::BACKPACK_START + 1) % artsInBackpack);
+	};
+	slotInc inc;
+	if(scrollingPossible)
+		inc = inc_ring;
+	else
+		inc = inc_straight;
+
+	backpackPos += offset;
+	if(backpackPos < 0)
+		backpackPos += artsInBackpack;
+
+	if(artsInBackpack)
+		backpackPos %= artsInBackpack;
+
+	auto slot = ArtifactPosition(GameConstants::BACKPACK_START + backpackPos);
+	for(auto artPlace : backpack)
+	{
+		setSlotData(artPlace, slot, artSet);
+		slot = inc(slot);
+	}
+
+	// Blocking scrolling if there is not enough artifacts to scroll
+	leftBackpackRoll->block(!scrollingPossible);
+	rightBackpackRoll->block(!scrollingPossible);
+}
+
+void CArtifactsOfHeroBase::safeRedraw()
+{
+	if(active)
+	{
+		if(parent)
+			parent->redraw();
+		else
+			redraw();
+	}
+}
+
+void CArtifactsOfHeroBase::markPossibleSlots(const CArtifactInstance * art, bool assumeDestRemoved)
+{
+	for(auto artPlace : artWorn)
+		artPlace.second->selectSlot(art->artType->canBePutAt(curHero, artPlace.second->slot, assumeDestRemoved));
+}
+
+void CArtifactsOfHeroBase::unmarkSlots()
+{
+	for(auto & artPlace : artWorn)
+		artPlace.second->selectSlot(false);
+
+	for(auto & artPlace : backpack)
+		artPlace->selectSlot(false);
+}
+
+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;
+		}
+		return artWorn[slot];
+	}
+	if(ArtifactUtils::isSlotBackpack(slot))
+	{
+		for(ArtPlacePtr artPlace : backpack)
+			if(artPlace->slot == slot)
+				return artPlace;
+		return nullptr;
+	}
+	else
+	{
+		return nullptr;
+	}
+}
+
+void CArtifactsOfHeroBase::updateWornSlots()
+{
+	for(auto place : artWorn)
+		updateSlot(place.first);
+}
+
+void CArtifactsOfHeroBase::updateBackpackSlots()
+{
+	for(auto artPlace : backpack)
+		updateSlot(artPlace->slot);
+	scrollBackpackForArtSet(0, *curHero);
+}
+
+void CArtifactsOfHeroBase::updateSlot(const ArtifactPosition & slot)
+{
+	setSlotData(getArtPlace(slot), slot, *curHero);
+}
+
+const CArtifactInstance * CArtifactsOfHeroBase::getPickedArtifact()
+{
+	// Returns only the picked up artifact. Not just highlighted like in the trading window.
+	if(!curHero || curHero->artifactsTransitionPos.empty())
+		return nullptr;
+	else
+		return curHero->getArt(ArtifactPosition::TRANSITION_POS);
+}
+
+void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosition & slot, const CArtifactSet & artSet)
+{
+	// Spurious call from artifactMoved in attempt to update hidden backpack slot
+	if(!artPlace && ArtifactUtils::isSlotBackpack(slot))
+	{
+		return;
+	}
+
+	artPlace->slot = slot;
+	if(auto slotInfo = artSet.getSlot(slot))
+	{
+		artPlace->lockSlot(slotInfo->locked);
+		artPlace->setArtifact(slotInfo->artifact);
+		if(!slotInfo->artifact->canBeDisassembled())
+		{
+			// If the artifact is part of at least one combined artifact, add additional information
+			std::map<const CArtifact*, int> arts;
+			for(const auto combinedArt : slotInfo->artifact->artType->constituentOf)
+			{
+				arts.insert(std::pair(combinedArt, 0));
+				for(const auto part : *combinedArt->constituents)
+					if(artSet.hasArt(part->getId(), true))
+						arts.at(combinedArt)++;
+			}
+			artPlace->addCombinedArtInfo(arts);
+		}
+	}
+	else
+	{
+		artPlace->setArtifact(nullptr);
+	}
+}

+ 67 - 0
client/widgets/CArtifactsOfHeroBase.h

@@ -0,0 +1,67 @@
+/*
+ * CArtifactsOfHeroBase.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "CArtifactHolder.h"
+
+class CArtifactsOfHeroBase : public CIntObject
+{
+protected:
+	using ArtPlacePtr = std::shared_ptr<CHeroArtPlace>;
+	using BpackScrollHandler = std::function<void(int)>;
+
+public:
+	using ArtPlaceMap = std::map<ArtifactPosition, ArtPlacePtr>;
+	using ClickHandler = std::function<void(CArtifactsOfHeroBase&, CHeroArtPlace&)>;
+
+	const CGHeroInstance * curHero;
+	ClickHandler leftClickCallback;
+	ClickHandler rightClickCallback;
+	
+	CArtifactsOfHeroBase();
+	virtual ~CArtifactsOfHeroBase();
+	virtual void leftClickArtPlace(CHeroArtPlace & artPlace);
+	virtual void rightClickArtPlace(CHeroArtPlace & artPlace);
+	virtual void setHero(const CGHeroInstance * hero);
+	virtual const CGHeroInstance * getHero() const;
+	virtual void scrollBackpack(int offset);
+	virtual void safeRedraw();
+	virtual void markPossibleSlots(const CArtifactInstance * art, bool assumeDestRemoved = true);
+	virtual void unmarkSlots();
+	virtual ArtPlacePtr getArtPlace(const ArtifactPosition & slot);
+	virtual void updateWornSlots();
+	virtual void updateBackpackSlots();
+	virtual void updateSlot(const ArtifactPosition & slot);
+	virtual const CArtifactInstance * getPickedArtifact();
+
+protected:
+	ArtPlaceMap artWorn;
+	std::vector<ArtPlacePtr> backpack;
+	std::shared_ptr<CButton> leftBackpackRoll;
+	std::shared_ptr<CButton> rightBackpackRoll;
+	int backpackPos; // Position to display artifacts in heroes backpack
+
+	const std::vector<Point> slotPos =
+	{
+		Point(509,30),  Point(567,240), Point(509,80),  //0-2
+		Point(383,68),  Point(564,183), Point(509,130), //3-5
+		Point(431,68),  Point(610,183), Point(515,295), //6-8
+		Point(383,143), Point(399,194), Point(415,245), //9-11
+		Point(431,296), Point(564,30),  Point(610,30), //12-14
+		Point(610,76),  Point(610,122), Point(610,310), //15-17
+		Point(381,296) //18
+	};
+
+	virtual void init(CHeroArtPlace::ClickHandler lClickCallback, CHeroArtPlace::ClickHandler rClickCallback,
+		const Point & position, BpackScrollHandler scrollHandler);
+	// Assigns an artifacts to an artifact place depending on it's new slot ID
+	virtual void setSlotData(ArtPlacePtr artPlace, const ArtifactPosition & slot, const CArtifactSet & artSet);
+	virtual void scrollBackpackForArtSet(int offset, const CArtifactSet & artSet);
+};

+ 54 - 0
client/widgets/CArtifactsOfHeroKingdom.cpp

@@ -0,0 +1,54 @@
+/*
+ * CArtifactsOfHeroKingdom.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CArtifactsOfHeroKingdom.h"
+
+#include "Buttons.h"
+
+#include "../CPlayerInterface.h"
+
+#include "../../CCallback.h"
+
+CArtifactsOfHeroKingdom::CArtifactsOfHeroKingdom(ArtPlaceMap ArtWorn, std::vector<ArtPlacePtr> Backpack,
+	std::shared_ptr<CButton> leftScroll, std::shared_ptr<CButton> rightScroll)
+{
+	artWorn = ArtWorn;
+	backpack = Backpack;
+	leftBackpackRoll = leftScroll;
+	rightBackpackRoll = rightScroll;
+
+	for(auto artPlace : artWorn)
+	{
+		artPlace.second->slot = artPlace.first;
+		artPlace.second->setArtifact(nullptr);
+		artPlace.second->leftClickCallback = std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1);
+		artPlace.second->rightClickCallback = std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1);
+	}
+	for(auto artPlace : backpack)
+	{
+		artPlace->setArtifact(nullptr);
+		artPlace->leftClickCallback = std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1);
+		artPlace->rightClickCallback = std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1);
+	}
+	leftBackpackRoll->addCallback(std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, -1));
+	rightBackpackRoll->addCallback(std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, +1));
+}
+
+void CArtifactsOfHeroKingdom::swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc)
+{
+	LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc);
+}
+
+void CArtifactsOfHeroKingdom::pickUpArtifact(CHeroArtPlace & artPlace)
+{
+	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero, artPlace.slot),
+		ArtifactLocation(curHero, ArtifactPosition::TRANSITION_POS));
+}
+

+ 27 - 0
client/widgets/CArtifactsOfHeroKingdom.h

@@ -0,0 +1,27 @@
+/*
+ * CArtifactsOfHeroKingdom.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "CArtifactsOfHeroBase.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct ArtifactLocation;
+
+VCMI_LIB_NAMESPACE_END
+
+class CArtifactsOfHeroKingdom : public CArtifactsOfHeroBase
+{
+public:
+	CArtifactsOfHeroKingdom(ArtPlaceMap ArtWorn, std::vector<ArtPlacePtr> Backpack,
+		std::shared_ptr<CButton> leftScroll, std::shared_ptr<CButton> rightScroll);
+	void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
+	void pickUpArtifact(CHeroArtPlace & artPlace);
+};

+ 35 - 0
client/widgets/CArtifactsOfHeroMain.cpp

@@ -0,0 +1,35 @@
+/*
+ * CArtifactsOfHeroMain.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CArtifactsOfHeroMain.h"
+
+#include "../CPlayerInterface.h"
+
+#include "../../CCallback.h"
+
+CArtifactsOfHeroMain::CArtifactsOfHeroMain(const Point & position)
+{
+	init(
+		std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1),
+		std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1),
+		position,
+		std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1));
+}
+
+void CArtifactsOfHeroMain::swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc)
+{
+	LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc);
+}
+
+void CArtifactsOfHeroMain::pickUpArtifact(CHeroArtPlace & artPlace)
+{
+	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero, artPlace.slot),
+		ArtifactLocation(curHero, ArtifactPosition::TRANSITION_POS));
+}

+ 26 - 0
client/widgets/CArtifactsOfHeroMain.h

@@ -0,0 +1,26 @@
+/*
+ * CArtifactsOfHeroMain.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "CArtifactsOfHeroBase.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct ArtifactLocation;
+
+VCMI_LIB_NAMESPACE_END
+
+class CArtifactsOfHeroMain : public CArtifactsOfHeroBase
+{
+public:
+	CArtifactsOfHeroMain(const Point & position);
+	void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
+	void pickUpArtifact(CHeroArtPlace & artPlace);
+};

+ 41 - 0
client/widgets/CArtifactsOfHeroMarket.cpp

@@ -0,0 +1,41 @@
+/*
+ * CArtifactsOfHeroMarket.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CArtifactsOfHeroMarket.h"
+
+#include "../../lib/mapObjects/CGHeroInstance.h"
+
+CArtifactsOfHeroMarket::CArtifactsOfHeroMarket(const Point & position)
+{
+	init(
+		std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1), 
+		std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1), 
+		position,
+		std::bind(&CArtifactsOfHeroMarket::scrollBackpack, this, _1));
+};
+
+void CArtifactsOfHeroMarket::scrollBackpack(int offset)
+{
+	CArtifactsOfHeroBase::scrollBackpackForArtSet(offset, *curHero);
+
+	// We may have highlight on one of backpack artifacts
+	if(selectArtCallback)
+	{
+		for(auto & artPlace : backpack)
+		{
+			if(artPlace->isMarked())
+			{
+				selectArtCallback(artPlace.get());
+				break;
+			}
+		}
+	}
+	safeRedraw();
+}

+ 21 - 0
client/widgets/CArtifactsOfHeroMarket.h

@@ -0,0 +1,21 @@
+/*
+ * CArtifactsOfHeroMarket.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "CArtifactsOfHeroBase.h"
+
+class CArtifactsOfHeroMarket : public CArtifactsOfHeroBase
+{
+public:
+	std::function<void(CHeroArtPlace*)> selectArtCallback;
+
+	CArtifactsOfHeroMarket(const Point & position);
+	void scrollBackpack(int offset) override;
+};

+ 5 - 8
client/widgets/CGarrisonInt.cpp

@@ -185,20 +185,17 @@ bool CGarrisonSlot::highlightOrDropArtifact()
 	bool artSelected = false;
 	if (CWindowWithArtifacts* chw = dynamic_cast<CWindowWithArtifacts*>(GH.topInt().get())) //dirty solution
 	{
-		std::shared_ptr<CArtifactsOfHero::SCommonPart> commonInfo = chw->getCommonPart();
-		const CArtifactInstance * art = nullptr;
-		if(commonInfo)
-			art = commonInfo->src.art;
+		const auto pickedArtInst = chw->getPickedArtifact();
 
-		if(art)
+		if(pickedArtInst)
 		{
-			const CGHeroInstance *srcHero = commonInfo->src.AOH->getHero();
+			const auto * srcHero = chw->getHeroPickedArtifact();
 			artSelected = true;
 			if (myStack) // try dropping the artifact only if the slot isn't empty
 			{
-				ArtifactLocation src(srcHero, commonInfo->src.slotID);
+				ArtifactLocation src(srcHero, ArtifactPosition::TRANSITION_POS);
 				ArtifactLocation dst(myStack, ArtifactPosition::CREATURE_SLOT);
-				if (art->canBePutAt(dst, true))
+				if(pickedArtInst->canBePutAt(dst, true))
 				{	//equip clicked stack
 					if(dst.getArt())
 					{

+ 363 - 0
client/widgets/CWindowWithArtifacts.cpp

@@ -0,0 +1,363 @@
+/*
+ * CWindowWithArtifacts.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CWindowWithArtifacts.h"
+
+#include "../gui/CGuiHandler.h"
+#include "../gui/CursorHandler.h"
+
+#include "CComponent.h"
+
+#include "../windows/CHeroWindow.h"
+#include "../windows/CSpellWindow.h"
+#include "../windows/GUIClasses.h"
+#include "../CPlayerInterface.h"
+#include "../CGameInfo.h"
+
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+
+void CWindowWithArtifacts::addSet(CArtifactsOfHeroPtr artSet)
+{
+	artSets.emplace_back(artSet);
+	std::visit([this](auto artSetWeak)
+		{
+			auto artSet = artSetWeak.lock();
+			artSet->leftClickCallback = std::bind(&CWindowWithArtifacts::leftClickArtPlaceHero, this, _1, _2);
+			artSet->rightClickCallback = std::bind(&CWindowWithArtifacts::rightClickArtPlaceHero, this, _1, _2);
+		}, artSet);
+}
+
+const CGHeroInstance * CWindowWithArtifacts::getHeroPickedArtifact()
+{
+	auto res = getState();
+	if(res.has_value())
+		return std::get<const CGHeroInstance*>(res.value());
+	else
+		return nullptr;
+}
+
+const CArtifactInstance * CWindowWithArtifacts::getPickedArtifact()
+{
+	auto res = getState();
+	if(res.has_value())
+		return std::get<const CArtifactInstance*>(res.value());
+	else
+		return nullptr;
+}
+
+void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst, CHeroArtPlace & artPlace)
+{
+	const auto artSetWeak = findAOHbyRef(artsInst);
+	assert(artSetWeak.has_value());
+
+	if(artPlace.isLocked())
+		return;
+
+	const auto checkSpecialArts = [](const CGHeroInstance * hero, CHeroArtPlace & artPlace) -> bool
+	{
+		if(artPlace.getArt()->getTypeId() == ArtifactID::SPELLBOOK)
+		{
+			GH.pushIntT<CSpellWindow>(hero, LOCPLINT, LOCPLINT->battleInt.get());
+			return false;
+		}
+		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));
+			LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[312], catapult);
+			return false;
+		}
+		return true;
+	};
+
+	std::visit(
+		[checkSpecialArts, this, &artPlace](auto artSetWeak) -> void
+		{
+			const auto artSetPtr = artSetWeak.lock();
+
+			// Hero(Main, Exchange) window, Kingdom window, Altar window left click handler
+			if constexpr(
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMain>> || 
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroKingdom>> ||
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroAltar>>)
+			{
+				const auto pickedArtInst = getPickedArtifact();
+				const auto heroPickedArt = getHeroPickedArtifact();
+				const auto hero = artSetPtr->getHero();
+
+				if(pickedArtInst)
+				{
+					auto srcLoc = ArtifactLocation(heroPickedArt, ArtifactPosition::TRANSITION_POS);
+					auto dstLoc = ArtifactLocation(hero, artPlace.slot);
+					auto isTransferAllowed = false;
+
+					if(ArtifactUtils::isSlotBackpack(artPlace.slot))
+					{
+						if(pickedArtInst->artType->isBig())
+						{
+							// War machines cannot go to backpack
+							LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[153]) % pickedArtInst->artType->getNameTranslated()));
+						}
+						else
+						{
+							if(ArtifactUtils::isBackpackFreeSlots(heroPickedArt))
+								isTransferAllowed = true;
+							else
+								LOCPLINT->showInfoDialog(CGI->generaltexth->translate("core.genrltxt.152"));
+						}
+					}
+					// Check if artifact transfer is possible
+					else if(pickedArtInst->canBePutAt(dstLoc, true) && (!artPlace.getArt() || hero->tempOwner == LOCPLINT->playerID))
+					{
+						isTransferAllowed = true;
+					}
+					if constexpr(std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroKingdom>>)
+					{
+						if(hero != heroPickedArt)
+							isTransferAllowed = false;
+					}
+					if(isTransferAllowed)
+						artSetPtr->swapArtifacts(srcLoc, dstLoc);
+				}
+				else
+				{
+					if(artPlace.getArt())
+					{			
+						if(artSetPtr->getHero()->tempOwner == LOCPLINT->playerID)
+						{
+							if(checkSpecialArts(hero, artPlace))
+								artSetPtr->pickUpArtifact(artPlace);
+						}
+						else
+						{
+							for(const auto artSlot : ArtifactUtils::unmovableSlots())
+								if(artPlace.slot == artSlot)
+								{
+									LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[21]);
+									break;
+								}
+						}
+					}
+				}
+			}
+			// Market window left click handler
+			else if constexpr(std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMarket>>)
+			{
+				if(artSetPtr->selectArtCallback && artPlace.getArt())
+				{
+					if(artPlace.getArt()->artType->isTradable())
+					{
+						artSetPtr->unmarkSlots();
+						artPlace.selectSlot(true);
+						artSetPtr->selectArtCallback(&artPlace);
+					}
+					else
+					{
+						// This item can't be traded
+						LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[21]);
+					}
+				}
+			}
+		}, artSetWeak.value());
+}
+
+void CWindowWithArtifacts::rightClickArtPlaceHero(CArtifactsOfHeroBase & artsInst, CHeroArtPlace & artPlace)
+{
+	const auto artSetWeak = findAOHbyRef(artsInst);
+	assert(artSetWeak.has_value());
+
+	if(artPlace.isLocked())
+		return;
+
+	std::visit(
+		[&artPlace](auto artSetWeak) -> void
+		{
+			const auto artSetPtr = artSetWeak.lock();
+
+			// Hero(Main, Exchange) window, Kingdom window right click handler
+			if constexpr(
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMain>> ||
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroKingdom>>)
+			{
+				if(artPlace.getArt())
+				{
+					if(ArtifactUtilsClient::askToDisassemble(artSetPtr->getHero(), artPlace.slot))
+					{
+						return;
+					}
+					if(ArtifactUtilsClient::askToAssemble(artSetPtr->getHero(), artPlace.slot))
+					{
+						return;
+					}
+					if(artPlace.text.size())
+						artPlace.LRClickableAreaWTextComp::clickRight(boost::logic::tribool::true_value, false);
+				}
+			}
+			// Altar window, Market window right click handler
+			else if constexpr(
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroAltar>> ||
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMarket>>)
+			{
+				if(artPlace.getArt() && artPlace.text.size())
+					artPlace.LRClickableAreaWTextComp::clickRight(boost::logic::tribool::true_value, false);
+			}
+		}, artSetWeak.value());
+}
+
+void CWindowWithArtifacts::artifactRemoved(const ArtifactLocation & artLoc)
+{
+	updateSlots(artLoc.slot);
+}
+
+void CWindowWithArtifacts::artifactMoved(const ArtifactLocation & srcLoc, const ArtifactLocation & destLoc, bool withRedraw)
+{
+	auto curState = getState();
+	if(!curState.has_value())
+		// Transition state. Nothing to do here. Just skip. Need to wait for final state.
+		return;
+
+	// When moving one artifact onto another it leads to two art movements: dst->TRANSITION_POS; src->dst
+	// However after first movement we pick the art from TRANSITION_POS and the second movement coming when
+	// we have a different artifact may look surprising... but it's valid.
+
+	auto pickedArtInst = std::get<const CArtifactInstance*>(curState.value());
+	assert(srcLoc.isHolder(std::get<const CGHeroInstance*>(curState.value())));
+	assert(srcLoc.getArt() == pickedArtInst);
+
+	auto artifactMovedBody = [this, withRedraw, &srcLoc, &destLoc, &pickedArtInst](auto artSetWeak) -> void
+	{
+		auto artSetPtr = artSetWeak.lock();
+		if(artSetPtr)
+		{
+			const auto hero = artSetPtr->getHero();
+			if(artSetPtr->active)
+			{
+				if(pickedArtInst)
+				{
+					CCS->curh->dragAndDropCursor("artifact", pickedArtInst->artType->getIconIndex());
+					if(srcLoc.isHolder(hero) || !std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroKingdom>>)
+						artSetPtr->markPossibleSlots(pickedArtInst, hero->tempOwner == LOCPLINT->playerID);
+				}
+				else
+				{
+					artSetPtr->unmarkSlots();
+					CCS->curh->dragAndDropCursor(nullptr);
+				}
+			}
+			if(withRedraw)
+			{
+				artSetPtr->updateWornSlots();
+				artSetPtr->updateBackpackSlots();
+
+				// Update arts bonuses on window.
+				// TODO rework this part when CHeroWindow and CExchangeWindow are reworked
+				if(auto * chw = dynamic_cast<CHeroWindow*>(this))
+				{
+					chw->update(hero, true);
+				}
+				else if(auto * cew = dynamic_cast<CExchangeWindow*>(this))
+				{
+					cew->updateWidgets();
+				}
+				artSetPtr->safeRedraw();
+			}
+
+			// Make sure the status bar is updated so it does not display old text
+			if(destLoc.isHolder(hero))
+			{
+				if(auto artPlace = artSetPtr->getArtPlace(destLoc.slot))
+					artPlace->hover(true);
+			}
+		}
+	};
+
+	for(auto artSetWeak : artSets)
+		std::visit(artifactMovedBody, artSetWeak);
+}
+
+void CWindowWithArtifacts::artifactDisassembled(const ArtifactLocation & artLoc)
+{
+	updateSlots(artLoc.slot);
+}
+
+void CWindowWithArtifacts::artifactAssembled(const ArtifactLocation & artLoc)
+{
+	updateSlots(artLoc.slot);
+}
+
+void CWindowWithArtifacts::updateSlots(const ArtifactPosition & slot)
+{
+	auto updateSlotBody = [slot](auto artSetWeak) -> void
+	{
+		if(const auto artSetPtr = artSetWeak.lock())
+		{
+			if(ArtifactUtils::isSlotEquipment(slot))
+				artSetPtr->updateWornSlots();
+			else if(ArtifactUtils::isSlotBackpack(slot))
+				artSetPtr->updateBackpackSlots();
+
+			artSetPtr->safeRedraw();
+		}
+	};
+
+	for(auto artSetWeak : artSets)
+		std::visit(updateSlotBody, artSetWeak);
+}
+
+std::optional<std::tuple<const CGHeroInstance*, const CArtifactInstance*>> CWindowWithArtifacts::getState()
+{
+	const CArtifactInstance * artInst = nullptr;
+	const CGHeroInstance * hero = nullptr;
+	size_t pickedCnt = 0;
+
+	auto getHeroArtBody = [&hero, &artInst, &pickedCnt](auto artSetWeak) -> void
+	{
+		auto artSetPtr = artSetWeak.lock();
+		if(artSetPtr)
+		{
+			if(const auto art = artSetPtr->getPickedArtifact())
+			{
+				artInst = art;
+				hero = artSetPtr->getHero();
+				pickedCnt += hero->artifactsTransitionPos.size();
+			}
+		}
+	};
+	for(auto artSetWeak : artSets)
+		std::visit(getHeroArtBody, artSetWeak);
+
+	// The state is possible when the hero has placed an artifact in the ArtifactPosition::TRANSITION_POS,
+	// and the previous artifact has not yet removed from the ArtifactPosition::TRANSITION_POS.
+	// This is a transitional state. Then return nullopt.
+	if(pickedCnt > 1)
+		return std::nullopt;
+	else
+		return std::make_tuple(hero, artInst);
+}
+
+std::optional<CWindowWithArtifacts::CArtifactsOfHeroPtr> CWindowWithArtifacts::findAOHbyRef(CArtifactsOfHeroBase & artsInst)
+{
+	std::optional<CArtifactsOfHeroPtr> res;
+
+	auto findAOHBody = [&res, &artsInst](auto & artSetWeak) -> void
+	{
+		if(&artsInst == artSetWeak.lock().get())
+			res = artSetWeak;
+	};
+
+	for(auto artSetWeak : artSets)
+	{
+		std::visit(findAOHBody, artSetWeak);
+		if(res.has_value())
+			return res;
+	}
+	return res;
+}

+ 44 - 0
client/widgets/CWindowWithArtifacts.h

@@ -0,0 +1,44 @@
+/*
+ * CWindowWithArtifacts.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "CArtifactHolder.h"
+#include "CArtifactsOfHeroMain.h"
+#include "CArtifactsOfHeroKingdom.h"
+#include "CArtifactsOfHeroAltar.h"
+#include "CArtifactsOfHeroMarket.h"
+
+class CWindowWithArtifacts : public CArtifactHolder
+{
+public:
+	using CArtifactsOfHeroPtr = std::variant<
+		std::weak_ptr<CArtifactsOfHeroMarket>,
+		std::weak_ptr<CArtifactsOfHeroAltar>,
+		std::weak_ptr<CArtifactsOfHeroKingdom>,
+		std::weak_ptr<CArtifactsOfHeroMain>>;
+
+	void addSet(CArtifactsOfHeroPtr artSet);
+	const CGHeroInstance * getHeroPickedArtifact();
+	const CArtifactInstance * getPickedArtifact();
+	void leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst, CHeroArtPlace & artPlace);
+	void rightClickArtPlaceHero(CArtifactsOfHeroBase & artsInst, CHeroArtPlace & artPlace);
+
+	void artifactRemoved(const ArtifactLocation & artLoc) override;
+	void artifactMoved(const ArtifactLocation & srcLoc, const ArtifactLocation & destLoc, bool withRedraw) override;
+	void artifactDisassembled(const ArtifactLocation & artLoc) override;
+	void artifactAssembled(const ArtifactLocation & artLoc) override;
+
+private:
+	std::vector<CArtifactsOfHeroPtr> artSets;
+
+	void updateSlots(const ArtifactPosition & slot);
+	std::optional<std::tuple<const CGHeroInstance*, const CArtifactInstance*>> getState();
+	std::optional<CArtifactsOfHeroPtr> findAOHbyRef(CArtifactsOfHeroBase & artsInst);
+};

+ 1 - 1
client/windows/CCastleInterface.cpp

@@ -774,7 +774,7 @@ void CCastleBuildings::buildingClicked(BuildingID building, BuildingSubID::EBuil
 						break;
 
 				case BuildingSubID::CREATURE_TRANSFORMER: //Skeleton Transformer
-						GH.pushIntT<CTransformerWindow>(getHero(), town);
+						GH.pushIntT<CTransformerWindow>(town, getHero());
 						break;
 
 				case BuildingSubID::PORTAL_OF_SUMMONING:

+ 13 - 14
client/windows/CHeroWindow.cpp

@@ -42,9 +42,10 @@ TConstBonusListPtr CHeroWithMaybePickedArtifact::getAllBonuses(const CSelector &
 	TConstBonusListPtr heroBonuses = hero->getAllBonuses(selector, limit, hero, cachingStr);
 	TConstBonusListPtr bonusesFromPickedUpArtifact;
 
-	std::shared_ptr<CArtifactsOfHero::SCommonPart> cp = cww->getCommonPart();
-	if(cp && cp->src.art && cp->src.valid() && cp->src.AOH && cp->src.AOH->getHero() == hero)
-		bonusesFromPickedUpArtifact = cp->src.art->getAllBonuses(selector, limit, hero);
+	const auto pickedArtInst = cww->getPickedArtifact();
+
+	if(pickedArtInst)
+		bonusesFromPickedUpArtifact = pickedArtInst->getAllBonuses(selector, limit, hero);
 	else
 		bonusesFromPickedUpArtifact = TBonusListPtr(new BonusList());
 
@@ -244,7 +245,7 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded)
 		}
 		if(!arts)
 		{
-			arts = std::make_shared<CArtifactsOfHero>(Point(-65, -8), true);
+			arts = std::make_shared<CArtifactsOfHeroMain>(Point(-65, -8));
 			arts->setHero(curHero);
 			addSet(arts);
 		}
@@ -354,25 +355,23 @@ void CHeroWindow::dismissCurrent()
 
 void CHeroWindow::commanderWindow()
 {
-	//bool artSelected = false;
-	std::shared_ptr<CArtifactsOfHero::SCommonPart> commonInfo = getCommonPart();
+	const auto pickedArtInst = getPickedArtifact();
+	const auto hero = getHeroPickedArtifact();
 
-	if(const CArtifactInstance *art = commonInfo->src.art)
+	if(pickedArtInst)
 	{
-		const CGHeroInstance *srcHero = commonInfo->src.AOH->getHero();
-		//artSelected = true;
-		const auto freeSlot = ArtifactUtils::getArtAnyPosition(curHero->commander, art->artType->getId());
+		const auto freeSlot = ArtifactUtils::getArtAnyPosition(curHero->commander, pickedArtInst->getTypeId());
 		if(freeSlot < ArtifactPosition::COMMANDER_AFTER_LAST) //we don't want to put it in commander's backpack!
 		{
-			ArtifactLocation src(srcHero, commonInfo->src.slotID);
+			ArtifactLocation src(hero, ArtifactPosition::TRANSITION_POS);
 			ArtifactLocation dst(curHero->commander.get(), freeSlot);
 
-			if(art->canBePutAt(dst, true))
+			if(pickedArtInst->canBePutAt(dst, true))
 			{	//equip clicked stack
 				if(dst.getArt())
 				{
-					LOCPLINT->cb->swapArtifacts (dst, ArtifactLocation(srcHero,
-						ArtifactUtils::getArtBackpackPosition(srcHero, art->getTypeId())));
+					LOCPLINT->cb->swapArtifacts(dst, ArtifactLocation(hero,
+						ArtifactUtils::getArtBackpackPosition(hero, pickedArtInst->getTypeId())));
 				}
 				LOCPLINT->cb->swapArtifacts(src, dst);
 			}

+ 3 - 3
client/windows/CHeroWindow.h

@@ -10,7 +10,7 @@
 #pragma once
 
 #include "../../lib/HeroBonus.h"
-#include "../widgets/CArtifactHolder.h"
+#include "../widgets/CWindowWithArtifacts.h"
 #include "../widgets/CGarrisonInt.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -25,7 +25,7 @@ class CHeroWindow;
 class LClickableAreaHero;
 class LRClickableAreaWText;
 class LRClickableAreaWTextComp;
-class CArtifactsOfHero;
+class CArtifactsOfHeroMain;
 class MoraleLuckBox;
 class CToggleButton;
 class CToggleGroup;
@@ -105,7 +105,7 @@ class CHeroWindow : public CStatusbarWindow, public CGarrisonHolder, public CWin
 	std::shared_ptr<CToggleGroup> formations;
 
 	std::shared_ptr<CGarrisonInt> garr;
-	std::shared_ptr<CArtifactsOfHero> arts;
+	std::shared_ptr<CArtifactsOfHeroMain> arts;
 
 	std::vector<std::shared_ptr<CLabel>> labels;
 

+ 4 - 4
client/windows/CKingdomInterface.cpp

@@ -826,7 +826,7 @@ public:
 		background = std::make_shared<CAnimImage>("OVSLOT", 4);
 		pos = background->pos;
 		for(int i=0; i<9; i++)
-			arts.push_back(std::make_shared<CHeroArtPlace>(Point(270+i*48, 65)));
+			arts.push_back(std::make_shared<CHeroArtPlace>(Point(269+i*48, 66)));
 	}
 };
 
@@ -846,7 +846,7 @@ public:
 		btnLeft = std::make_shared<CButton>(Point(269, 66), "HSBTNS3", CButton::tooltip(), 0);
 		btnRight = std::make_shared<CButton>(Point(675, 66), "HSBTNS5", CButton::tooltip(), 0);
 		for(int i=0; i<8; i++)
-			arts.push_back(std::make_shared<CHeroArtPlace>(Point(295+i*48, 65)));
+			arts.push_back(std::make_shared<CHeroArtPlace>(Point(294+i*48, 66)));
 	}
 };
 
@@ -872,7 +872,7 @@ CHeroItem::CHeroItem(const CGHeroInstance * Hero)
 	assert(arts1->arts.size() == 9);
 	assert(arts2->arts.size() == 9);
 
-	CArtifactsOfHero::ArtPlaceMap arts =
+	CArtifactsOfHeroMain::ArtPlaceMap arts =
 	{
 		{ArtifactPosition::HEAD, arts1->arts[0]},
 		{ArtifactPosition::SHOULDERS,arts1->arts[1]},
@@ -896,7 +896,7 @@ CHeroItem::CHeroItem(const CGHeroInstance * Hero)
 	};
 
 
-	heroArts = std::make_shared<CArtifactsOfHero>(arts, backpack->arts, backpack->btnLeft, backpack->btnRight, true);
+	heroArts = std::make_shared<CArtifactsOfHeroKingdom>(arts, backpack->arts, backpack->btnLeft, backpack->btnRight);
 	heroArts->setHero(hero);
 
 	artsTabs = std::make_shared<CTabbedInt>(std::bind(&CHeroItem::onTabSelected, this, _1));

+ 2 - 2
client/windows/CKingdomInterface.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "../widgets/CArtifactHolder.h"
+#include "../widgets/CWindowWithArtifacts.h"
 #include "../widgets/CGarrisonInt.h"
 
 class CButton;
@@ -309,7 +309,7 @@ class CHeroItem : public CIntObject, public CGarrisonHolder
 	std::shared_ptr<CIntObject> onTabSelected(size_t index);
 
 public:
-	std::shared_ptr<CArtifactsOfHero> heroArts;
+	std::shared_ptr<CArtifactsOfHeroKingdom> heroArts;
 
 	void updateGarrisons() override;
 

+ 75 - 98
client/windows/CTradeWindow.cpp

@@ -179,24 +179,26 @@ void CTradeWindow::CTradeableItem::clickLeft(tribool down, bool previousState)
 		if(type == ARTIFACT_PLACEHOLDER)
 		{
 			CAltarWindow *aw = static_cast<CAltarWindow *>(mw);
-			if(const CArtifactInstance *movedArt = aw->arts->commonInfo->src.art)
+			const auto pickedArtInst = aw->getPickedArtifact();
+
+			auto artifactsOfHero = std::dynamic_pointer_cast<CArtifactsOfHeroAltar>(aw->arts);
+			if(pickedArtInst)
 			{
-				aw->moveFromSlotToAltar(aw->arts->commonInfo->src.slotID, this->shared_from_this(), movedArt);
+				artifactsOfHero->pickedArtMoveToAltar(ArtifactPosition::TRANSITION_POS);
+				aw->moveArtToAltar(this->shared_from_this(), pickedArtInst);
 			}
 			else if(const CArtifactInstance *art = getArtInstance())
 			{
-				aw->arts->commonInfo->src.AOH = aw->arts.get();
-				aw->arts->commonInfo->src.art = art;
-				aw->arts->commonInfo->src.slotID = aw->hero->getArtPos(art);
-				aw->arts->markPossibleSlots(art);
-
-				//aw->arts->commonInfo->dst.AOH = aw->arts;
-				CCS->curh->dragAndDropCursor("artifact", art->artType->getIconIndex());
-
-				aw->arts->artifactsOnAltar.erase(art);
+				const auto hero = artifactsOfHero->getHero();
+				const auto slot = hero->getSlotByInstance(art);
+				assert(slot != ArtifactPosition::PRE_FIRST);
+				LOCPLINT->cb->swapArtifacts(ArtifactLocation(hero, slot),
+					ArtifactLocation(hero, ArtifactPosition::TRANSITION_POS));
+				artifactsOfHero->pickedArtFromSlot = slot;
+				artifactsOfHero->artifactsOnAltar.erase(art);
 				setID(-1);
 				subtitle.clear();
-				aw->deal->block(!aw->arts->artifactsOnAltar.size());
+				aw->deal->block(!artifactsOfHero->artifactsOnAltar.size());
 			}
 
 			aw->calcTotalExp();
@@ -387,18 +389,25 @@ void CTradeWindow::initItems(bool Left)
 		}
 		else //ARTIFACT_EXP
 		{
-			xOffset = -363;
+			xOffset = -365;
 			yOffset = -12;
 		}
 
-		arts = std::make_shared<CArtifactsOfHero>(Point(xOffset, yOffset), true);
-		arts->recActions = 255-DISPOSE;
-		arts->setHero(hero);
-		arts->allowedAssembling = false;
-		addSet(arts);
-
 		if(mode == EMarketMode::ARTIFACT_RESOURCE)
-			arts->highlightModeCallback = std::bind(&CTradeWindow::artifactSelected, this, _1);
+		{
+			auto artifactsOfHero = std::make_shared<CArtifactsOfHeroMarket>(Point(xOffset, yOffset));
+			artifactsOfHero->selectArtCallback = std::bind(&CTradeWindow::artifactSelected, this, _1);
+			artifactsOfHero->setHero(hero);
+			addSet(artifactsOfHero);
+			arts = artifactsOfHero;
+		}
+		else
+		{
+			auto artifactsOfHero = std::make_shared<CArtifactsOfHeroAltar>(Point(xOffset, yOffset));
+			artifactsOfHero->setHero(hero);
+			addSet(artifactsOfHero);
+			arts = artifactsOfHero;
+		}
 	}
 	else
 	{
@@ -630,8 +639,8 @@ void CTradeWindow::setMode(EMarketMode::EMarketMode Mode)
 void CTradeWindow::artifactSelected(CHeroArtPlace *slot)
 {
 	assert(mode == EMarketMode::ARTIFACT_RESOURCE);
-	items[1][0]->setArtInstance(slot->ourArt);
-	if(slot->ourArt && !slot->locked)
+	items[1][0]->setArtInstance(slot->getArt());
+	if(slot->getArt())
 		hLeft = items[1][0];
 	else
 		hLeft = nullptr;
@@ -664,13 +673,13 @@ CMarketplaceWindow::CMarketplaceWindow(const IMarket * Market, const CGHeroInsta
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
 	madeTransaction = false;
-	bool sliderNeeded = true;
+	bool sliderNeeded = (mode != EMarketMode::RESOURCE_ARTIFACT && mode != EMarketMode::ARTIFACT_RESOURCE);
 
 	statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
 
 	std::string title;
 
-	if(market->o->ID == Obj::TOWN)
+	if(auto * o = dynamic_cast<const CGTownInstance *>(market))
 	{
 		switch (mode)
 		{
@@ -678,40 +687,23 @@ CMarketplaceWindow::CMarketplaceWindow(const IMarket * Market, const CGHeroInsta
 			title = (*CGI->townh)[ETownType::STRONGHOLD]->town->buildings[BuildingID::FREELANCERS_GUILD]->getNameTranslated();
 			break;
 		case EMarketMode::RESOURCE_ARTIFACT:
-			title = (*CGI->townh)[market->o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated();
-			sliderNeeded = false;
+			title = (*CGI->townh)[o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated();
 			break;
 		case EMarketMode::ARTIFACT_RESOURCE:
-			title = (*CGI->townh)[market->o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated();
+			title = (*CGI->townh)[o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated();
 
 			// create image that copies part of background containing slot MISC_1 into position of slot MISC_5
 			// this is workaround for bug in H3 files where this slot for ragdoll on this screen is missing
 			images.push_back(std::make_shared<CPicture>(background->getSurface(), Rect(20, 187, 47, 47), 18, 339 ));
-			sliderNeeded = false;
 			break;
 		default:
 			title = CGI->generaltexth->allTexts[158];
 			break;
 		}
 	}
-	else
+	else if(auto * o = dynamic_cast<const CGMarket *>(market))
 	{
-		switch(market->o->ID)
-		{
-		case Obj::BLACK_MARKET:
-			title = CGI->generaltexth->allTexts[349];
-			sliderNeeded = false;
-			break;
-		case Obj::TRADING_POST:
-			title = CGI->generaltexth->allTexts[159];
-			break;
-		case Obj::TRADING_POST_SNOW:
-			title = CGI->generaltexth->allTexts[159];
-			break;
-		default:
-			title = market->o->getObjectName();
-			break;
-		}
+		title = o->title;
 	}
 
 	titleLabel = std::make_shared<CLabel>(300, 27, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, title);
@@ -831,12 +823,12 @@ void CMarketplaceWindow::makeDeal()
 	{
 		if(slider)
 		{
-			LOCPLINT->cb->trade(market->o, mode, leftIdToSend, hRight->id, slider->getValue() * r1, hero);
+			LOCPLINT->cb->trade(market, mode, leftIdToSend, hRight->id, slider->getValue() * r1, hero);
 			slider->moveTo(0);
 		}
 		else
 		{
-			LOCPLINT->cb->trade(market->o, mode, leftIdToSend, hRight->id, r2, hero);
+			LOCPLINT->cb->trade(market, mode, leftIdToSend, hRight->id, r2, hero);
 		}
 	}
 
@@ -858,7 +850,7 @@ void CMarketplaceWindow::selectionChanged(bool side)
 		readyToTrade = readyToTrade && (hLeft->id != hRight->id); //for resource trade, two DIFFERENT resources must be selected
 
 	if(mode == EMarketMode::ARTIFACT_RESOURCE && !hLeft)
-		arts->unmarkSlots(false);
+		arts->unmarkSlots();
 
 	if(readyToTrade)
 	{
@@ -1236,7 +1228,7 @@ void CAltarWindow::makeDeal()
 			}
 		}
 
-		LOCPLINT->cb->trade(market->o, mode, ids, {}, toSacrifice, hero);
+		LOCPLINT->cb->trade(market, mode, ids, {}, toSacrifice, hero);
 
 		for(int& val : sacrificedUnits)
 			val = 0;
@@ -1250,13 +1242,15 @@ void CAltarWindow::makeDeal()
 	else
 	{
 		std::vector<ui32> positions;
-		for(const CArtifactInstance *art : arts->artifactsOnAltar) //sacrifice each artifact on the list
+		auto artifactsOfHero = std::dynamic_pointer_cast<CArtifactsOfHeroAltar>(arts);
+		for(const CArtifactInstance * art : artifactsOfHero->artifactsOnAltar)
 		{
-			positions.push_back(hero->getArtPos(art));
+			positions.push_back(hero->getSlotByInstance(art));
 		}
+		std::sort(positions.begin(), positions.end(), std::greater<>());
 
-		LOCPLINT->cb->trade(market->o, mode, positions, {}, {}, hero);
-		arts->artifactsOnAltar.clear();
+		LOCPLINT->cb->trade(market, mode, positions, {}, {}, hero);
+		artifactsOfHero->artifactsOnAltar.clear();
 
 		for(auto item : items[0])
 		{
@@ -1264,7 +1258,6 @@ void CAltarWindow::makeDeal()
 			item->subtitle = "";
 		}
 
-		arts->commonInfo->reset();
 		//arts->scrollBackpack(0);
 		deal->block(true);
 	}
@@ -1294,12 +1287,13 @@ void CAltarWindow::SacrificeAll()
 	}
 	else
 	{
-		for(auto i = hero->artifactsWorn.cbegin(); i != hero->artifactsWorn.cend(); i++)
+		auto artifactsOfHero = std::dynamic_pointer_cast<CArtifactsOfHeroAltar>(arts);
+		for(const auto & aw : artifactsOfHero->visibleArtSet.artifactsWorn)
 		{
-			if(!i->second.locked) //ignore locks from assembled artifacts
-				moveFromSlotToAltar(i->first, nullptr, i->second.artifact);
+			if(!aw.second.locked)
+				moveArtToAltar(nullptr, aw.second.artifact);
 		}
-
+		artifactsOfHero->updateWornSlots();
 		SacrificeBackpack();
 	}
 	redraw();
@@ -1414,7 +1408,8 @@ void CAltarWindow::calcTotalExp()
 	}
 	else
 	{
-		for(const CArtifactInstance *art : arts->artifactsOnAltar)
+		auto artifactsOfHero = std::dynamic_pointer_cast<CArtifactsOfHeroAltar>(arts);
+		for(const CArtifactInstance * art : artifactsOfHero->artifactsOnAltar)
 		{
 			int dmp, valOfArt;
 			market->getOffer(art->artType->getId(), 0, dmp, valOfArt, mode);
@@ -1458,21 +1453,12 @@ int CAltarWindow::firstFreeSlot()
 
 void CAltarWindow::SacrificeBackpack()
 {
-	std::multiset<const CArtifactInstance *> toOmmit = arts->artifactsOnAltar;
-
-	for (auto & elem : hero->artifactsInBackpack)
+	auto artsAltar = std::dynamic_pointer_cast<CArtifactsOfHeroAltar>(arts);
+	while(!artsAltar->visibleArtSet.artifactsInBackpack.empty())
 	{
-
-		if(vstd::contains(toOmmit, elem.artifact))
-		{
-			toOmmit -= elem.artifact;
-			continue;
-		}
-
-		putOnAltar(nullptr, elem.artifact);
-	}
-
-	arts->scrollBackpack(0);
+		if(!putOnAltar(nullptr, artsAltar->visibleArtSet.artifactsInBackpack[0].artifact))
+			break;
+	};
 	calcTotalExp();
 }
 
@@ -1484,15 +1470,18 @@ void CAltarWindow::artifactPicked()
 void CAltarWindow::showAll(SDL_Surface * to)
 {
 	CTradeWindow::showAll(to);
-	if(mode == EMarketMode::ARTIFACT_EXP && arts && arts->commonInfo->src.art)
+	if(mode == EMarketMode::ARTIFACT_EXP && arts)
 	{
-		artIcon->setFrame(arts->commonInfo->src.art->artType->getIconIndex());
-		artIcon->showAll(to);
+		if(auto pickedArt = arts->getPickedArtifact())
+		{
+			artIcon->setFrame(pickedArt->artType->getIconIndex());
+			artIcon->showAll(to);
 
-		int dmp, val;
-		market->getOffer(arts->commonInfo->src.art->artType->getId(), 0, dmp, val, EMarketMode::ARTIFACT_EXP);
-		val = static_cast<int>(hero->calculateXp(val));
-		printAtMiddleLoc(std::to_string(val), 304, 498, FONT_SMALL, Colors::WHITE, to);
+			int dmp, val;
+			market->getOffer(pickedArt->getTypeId(), 0, dmp, val, EMarketMode::ARTIFACT_EXP);
+			val = static_cast<int>(hero->calculateXp(val));
+			printAtMiddleLoc(std::to_string(val), 304, 498, FONT_SMALL, Colors::WHITE, to);
+		}
 	}
 }
 
@@ -1519,7 +1508,9 @@ bool CAltarWindow::putOnAltar(std::shared_ptr<CTradeableItem> altarSlot, const C
 	market->getOffer(art->artType->getId(), 0, dmp, val, EMarketMode::ARTIFACT_EXP);
 	val = static_cast<int>(hero->calculateXp(val));
 
-	arts->artifactsOnAltar.insert(art);
+	auto artsAltar = std::dynamic_pointer_cast<CArtifactsOfHeroAltar>(arts);
+	artsAltar->artifactsOnAltar.insert(art);
+	artsAltar->deleteFromVisible(art);
 	altarSlot->setArtInstance(art);
 	altarSlot->subtitle = std::to_string(val);
 
@@ -1527,25 +1518,11 @@ bool CAltarWindow::putOnAltar(std::shared_ptr<CTradeableItem> altarSlot, const C
 	return true;
 }
 
-void CAltarWindow::moveFromSlotToAltar(ArtifactPosition slotID, std::shared_ptr<CTradeableItem> altarSlot, const CArtifactInstance *art)
+void CAltarWindow::moveArtToAltar(std::shared_ptr<CTradeableItem> altarSlot, const CArtifactInstance *art)
 {
-	auto freeBackpackSlot = ArtifactPosition((si32)hero->artifactsInBackpack.size() + GameConstants::BACKPACK_START);
-	if(arts->commonInfo->src.art)
-	{
-		arts->commonInfo->dst.slotID = freeBackpackSlot;
-		arts->commonInfo->dst.AOH = arts.get();
-	}
-
 	if(putOnAltar(altarSlot, art))
 	{
-		if(slotID < GameConstants::BACKPACK_START)
-			LOCPLINT->cb->swapArtifacts(ArtifactLocation(hero, slotID), ArtifactLocation(hero, freeBackpackSlot));
-		else
-		{
-			arts->commonInfo->src.clear();
-			arts->commonInfo->dst.clear();
-			CCS->curh->dragAndDropCursor(nullptr);
-			arts->unmarkSlots(false);
-		}
+		CCS->curh->dragAndDropCursor(nullptr);
+		arts->unmarkSlots();
 	}
 }

+ 3 - 3
client/windows/CTradeWindow.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "../widgets/CArtifactHolder.h"
+#include "../widgets/CWindowWithArtifacts.h"
 #include "CWindowObject.h"
 #include "../../lib/FunctionList.h"
 
@@ -67,7 +67,7 @@ public:
 	const IMarket * market;
 	const CGHeroInstance * hero;
 
-	std::shared_ptr<CArtifactsOfHero> arts;
+	std::shared_ptr<CArtifactsOfHeroBase> arts;
 	//all indexes: 1 = left, 0 = right
 	std::array<std::vector<std::shared_ptr<CTradeableItem>>, 2> items;
 
@@ -186,5 +186,5 @@ public:
 
 	void artifactPicked();
 	int firstFreeSlot();
-	void moveFromSlotToAltar(ArtifactPosition slotID, std::shared_ptr<CTradeableItem>, const CArtifactInstance * art);
+	void moveArtToAltar(std::shared_ptr<CTradeableItem>, const CArtifactInstance * art);
 };

+ 31 - 29
client/windows/GUIClasses.cpp

@@ -909,13 +909,9 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 	portraits[0] = std::make_shared<CAnimImage>("PortraitsLarge", heroInst[0]->portrait, 0, 257, 13);
 	portraits[1] = std::make_shared<CAnimImage>("PortraitsLarge", heroInst[1]->portrait, 0, 485, 13);
 
-	artifs[0] = std::make_shared<CArtifactsOfHero>(Point(-334, 150));
-	artifs[0]->commonInfo = std::make_shared<CArtifactsOfHero::SCommonPart>();
-	artifs[0]->commonInfo->participants.insert(artifs[0].get());
+	artifs[0] = std::make_shared<CArtifactsOfHeroMain>(Point(-334, 150));
 	artifs[0]->setHero(heroInst[0]);
-	artifs[1] = std::make_shared<CArtifactsOfHero>(Point(96, 150));
-	artifs[1]->commonInfo = artifs[0]->commonInfo;
-	artifs[1]->commonInfo->participants.insert(artifs[1].get());
+	artifs[1] = std::make_shared<CArtifactsOfHeroMain>(Point(98, 150));
 	artifs[1]->setHero(heroInst[1]);
 
 	addSet(artifs[0]);
@@ -1166,7 +1162,7 @@ void CTransformerWindow::makeDeal()
 	for(auto & elem : items)
 	{
 		if(!elem->left)
-			LOCPLINT->cb->trade(town, EMarketMode::CREATURE_UNDEAD, elem->id, 0, 0, hero);
+			LOCPLINT->cb->trade(market, EMarketMode::CREATURE_UNDEAD, elem->id, 0, 0, hero);
 	}
 }
 
@@ -1186,21 +1182,24 @@ void CTransformerWindow::updateGarrisons()
 		item->update();
 }
 
-CTransformerWindow::CTransformerWindow(const CGHeroInstance * _hero, const CGTownInstance * _town)
+CTransformerWindow::CTransformerWindow(const IMarket * _market, const CGHeroInstance * _hero)
 	: CStatusbarWindow(PLAYER_COLORED, "SKTRNBK"),
 	hero(_hero),
-	town(_town)
+	market(_market)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	if(hero)
 		army = hero;
 	else
-		army = town;
-
-	for(int i=0; i<GameConstants::ARMY_SIZE; i++)
+		army = dynamic_cast<const CArmedInstance *>(market); //for town only
+	
+	if(army)
 	{
-		if(army->getCreature(SlotID(i)))
-			items.push_back(std::make_shared<CItem>(this, army->getStackCount(SlotID(i)), i));
+		for(int i = 0; i < GameConstants::ARMY_SIZE; i++)
+		{
+			if(army->getCreature(SlotID(i)))
+				items.push_back(std::make_shared<CItem>(this, army->getStackCount(SlotID(i)), i));
+		}
 	}
 
 	all = std::make_shared<CButton>(Point(146, 416), "ALTARMY.DEF", CGI->generaltexth->zelp[590], [&](){ addAll(); }, SDLK_a);
@@ -1291,30 +1290,33 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, const IMarket
 	bars->setCustom("UNIVGOLD", 1, 0);
 	bars->setCustom("UNIVGREN", 2, 0);
 	bars->preload();
+	
+	std::string titleStr = CGI->generaltexth->allTexts[602];
+	std::string speechStr = CGI->generaltexth->allTexts[603];
 
-	if(market->o->ID == Obj::TOWN)
+	if(auto town = dynamic_cast<const CGTownInstance *>(_market))
 	{
-		auto town = dynamic_cast<const CGTownInstance *>(_market);
-
-		if(town)
-		{
-			auto faction = town->town->faction->getId();
-			auto bid = town->town->getSpecialBuilding(BuildingSubID::MAGIC_UNIVERSITY)->bid;
-			titlePic = std::make_shared<CAnimImage>((*CGI->townh)[faction]->town->clientInfo.buildingsIcons, bid);
-		}
-		else
-			titlePic = std::make_shared<CAnimImage>((*CGI->townh)[ETownType::CONFLUX]->town->clientInfo.buildingsIcons, BuildingID::MAGIC_UNIVERSITY);
+		auto faction = town->town->faction->getId();
+		auto bid = town->town->getSpecialBuilding(BuildingSubID::MAGIC_UNIVERSITY)->bid;
+		titlePic = std::make_shared<CAnimImage>((*CGI->townh)[faction]->town->clientInfo.buildingsIcons, bid);
+	}
+	else if(auto uni = dynamic_cast<const CGUniversity *>(_market); uni->appearance)
+	{
+		titlePic = std::make_shared<CAnimImage>(uni->appearance->animationFile, 0);
+		titleStr = uni->title;
+		speechStr = uni->speech;
 	}
 	else
+	{
 		titlePic = std::make_shared<CPicture>("UNIVBLDG");
+	}
 
 	titlePic->center(Point(232 + pos.x, 76 + pos.y));
 
-	clerkSpeech = std::make_shared<CTextBox>(CGI->generaltexth->allTexts[603], Rect(24, 129, 413, 70), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
-	title = std::make_shared<CLabel>(231, 26, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[602]);
+	clerkSpeech = std::make_shared<CTextBox>(speechStr, Rect(24, 129, 413, 70), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
+	title = std::make_shared<CLabel>(231, 26, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, titleStr);
 
 	std::vector<int> goods = market->availableItemsIds(EMarketMode::RESOURCE_SKILL);
-	assert(goods.size() == 4);
 
 	for(int i=0; i<goods.size(); i++)//prepare clickable items
 		items.push_back(std::make_shared<CItem>(this, goods[i], 54+i*104, 234));
@@ -1325,7 +1327,7 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, const IMarket
 
 void CUniversityWindow::makeDeal(int skill)
 {
-	LOCPLINT->cb->trade(market->o, EMarketMode::RESOURCE_SKILL, 6, skill, 1, hero);
+	LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_SKILL, 6, skill, 1, hero);
 }
 
 

+ 4 - 4
client/windows/GUIClasses.h

@@ -14,7 +14,7 @@
 #include "../lib/ResourceSet.h"
 #include "../lib/CConfigHandler.h"
 #include "../lib/int3.h"
-#include "../widgets/CArtifactHolder.h"
+#include "../widgets/CWindowWithArtifacts.h"
 #include "../widgets/CGarrisonInt.h"
 #include "../widgets/Images.h"
 
@@ -327,7 +327,7 @@ class CExchangeWindow : public CStatusbarWindow, public CGarrisonHolder, public
 
 public:
 	std::array<const CGHeroInstance *, 2> heroInst;
-	std::array<std::shared_ptr<CArtifactsOfHero>, 2> artifs;
+	std::array<std::shared_ptr<CArtifactsOfHeroMain>, 2> artifs;
 
 	void updateGarrisons() override;
 
@@ -382,7 +382,7 @@ class CTransformerWindow : public CStatusbarWindow, public CGarrisonHolder
 
 	const CArmedInstance * army;//object with army for transforming (hero or town)
 	const CGHeroInstance * hero;//only if we have hero in town
-	const CGTownInstance * town;//market, town garrison is used if hero == nullptr
+	const IMarket * market;//market, town garrison is used if hero == nullptr
 
 	std::shared_ptr<CLabel> titleLeft;
 	std::shared_ptr<CLabel> titleRight;
@@ -399,7 +399,7 @@ public:
 	void makeDeal();
 	void addAll();
 	void updateGarrisons() override;
-	CTransformerWindow(const CGHeroInstance * _hero, const CGTownInstance * _town);
+	CTransformerWindow(const IMarket * _market, const CGHeroInstance * _hero);
 };
 
 class CUniversityWindow : public CStatusbarWindow

+ 34 - 14
config/objects/generic.json

@@ -15,7 +15,7 @@
 	},
 
 	"altarOfSacrifice" : {
-		"index" :2,
+		"index" :2, 
 		"handler" : "market",
 		"base" : {
 			"sounds" : {
@@ -30,12 +30,13 @@
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"rarity"	: 20
-				}
+				},
+				"modes" : ["creature-experience", "artifact-experience"]
 			}
 		}
 	},
 	"tradingPost" : {
-		"index" :221,
+		"index" :221, 
 		"handler" : "market",
 		"base" : {
 			"sounds" : {
@@ -51,12 +52,15 @@
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"rarity"	: 100
-				}
+				},
+				"modes" : ["resource-resource", "resource-player"],
+				"efficiency" : 5,
+				"title" : "core.genrltxt.159"
 			}
 		}
 	},
 	"tradingPostDUPLICATE"		: {
-		"index" :99,
+		"index" :99, 
 		"handler" : "market",
 		"base" : {
 			"sounds" : {
@@ -72,12 +76,15 @@
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"rarity"	: 100
-				}
+				},
+				"modes" : ["resource-resource", "resource-player"],
+				"efficiency" : 5,
+				"title" : "core.genrltxt.159"
 			}
 		}
 	},
 	"freelancersGuild" : {
-		"index" :213,
+		"index" :213, 
 		"handler" : "market",
 		"types" : {
 			"object" : {
@@ -87,14 +94,15 @@
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"rarity"	: 100
-				}
+				},
+				"modes" : ["creature-resource"]
 			}
 		}
 	},
 
 	"blackMarket" : {
-		"index" :7,
-		"handler" : "blackMarket",
+		"index" :7, 
+		"handler" : "market",
 		"base" : {
 			"sounds" : {
 				"ambient" : ["LOOPMARK"],
@@ -108,7 +116,9 @@
 				"rmg" : {
 					"value"		: 8000,
 					"rarity"	: 20
-				}
+				},
+				"modes" : ["resource-artifact"],
+				"title" : "core.genrltxt.349"
 			}
 		}
 	},
@@ -535,8 +545,8 @@
 		}
 	},
 	"university" : {
-		"index" :104,
-		"handler" : "university",
+		"index" :104, 
+		"handler" : "market",
 		"base" : {
 			"sounds" : {
 				"visit" : ["GAZEBO"]
@@ -549,7 +559,17 @@
 				"rmg" : {
 					"value"		: 2500,
 					"rarity"	: 20
-				}
+				},
+				"modes" : ["resource-skill"],
+				"title" : "core.genrltxt.602",
+				"speech" : "core.genrltxt.603",
+				"offer": 
+				[
+					{ "noneOf" : ["necromancy"] },
+					{ "noneOf" : ["necromancy"] },
+					{ "noneOf" : ["necromancy"] },
+					{ "noneOf" : ["necromancy"] }
+				]
 			}
 		}
 	},

+ 19 - 0
lib/CArtHandler.cpp

@@ -1206,6 +1206,25 @@ const CArtifactInstance * CArtifactSet::getArtByInstanceId(const ArtifactInstanc
 	return nullptr;
 }
 
+const ArtifactPosition CArtifactSet::getSlotByInstance(const CArtifactInstance * artInst) const
+{
+	if(artInst)
+	{
+		for(auto & slot : artInst->artType->possibleSlots.at(bearerType()))
+			if(getArt(slot) == artInst)
+				return slot;
+
+		auto backpackSlot = GameConstants::BACKPACK_START;
+		for(auto & slotInfo : artifactsInBackpack)
+		{
+			if(slotInfo.getArt() == artInst)
+				return backpackSlot;
+			backpackSlot = ArtifactPosition(backpackSlot + 1);
+		}
+	}
+	return ArtifactPosition::PRE_FIRST;
+}
+
 bool CArtifactSet::hasArt(const ArtifactID & aid, bool onlyWorn, bool searchBackpackAssemblies, bool allowLocked) const
 {
 	return getArtPosCount(aid, onlyWorn, searchBackpackAssemblies, allowLocked) > 0;

+ 1 - 0
lib/CArtHandler.h

@@ -328,6 +328,7 @@ public:
 	std::vector<ArtifactPosition> getAllArtPositions(const ArtifactID & aid, bool onlyWorn, bool allowLocked, bool getAll) const;
 	std::vector<ArtifactPosition> getBackpackArtPositions(const ArtifactID & aid) const;
 	const CArtifactInstance * getArtByInstanceId(const ArtifactInstanceID & artInstId) const;
+	const ArtifactPosition getSlotByInstance(const CArtifactInstance * artInst) const;
 	/// Search for constituents of assemblies in backpack which do not have an ArtifactPosition
 	const CArtifactInstance * getHiddenArt(const ArtifactID & aid) const;
 	const CCombinedArtifactInstance * getAssemblyByConstituent(const ArtifactID & aid) const;

+ 29 - 16
lib/GameConstants.h

@@ -585,6 +585,16 @@ namespace BuildingSubID
 	};
 }
 
+namespace EMarketMode
+{
+	enum EMarketMode
+	{
+		RESOURCE_RESOURCE, RESOURCE_PLAYER, CREATURE_RESOURCE, RESOURCE_ARTIFACT,
+		ARTIFACT_RESOURCE, ARTIFACT_EXP, CREATURE_EXP, CREATURE_UNDEAD, RESOURCE_SKILL,
+		MARTKET_AFTER_LAST_PLACEHOLDER
+	};
+}
+
 namespace MappedKeys
 {
 
@@ -634,6 +644,19 @@ namespace MappedKeys
 		{ "lighthouse", BuildingSubID::LIGHTHOUSE },
 		{ "treasury", BuildingSubID::TREASURY }
 	};
+
+	static const std::map<std::string, EMarketMode::EMarketMode> MARKET_NAMES_TO_TYPES =
+	{
+		{ "resource-resource", EMarketMode::RESOURCE_RESOURCE },
+		{ "resource-player", EMarketMode::RESOURCE_PLAYER },
+		{ "creature-resource", EMarketMode::CREATURE_RESOURCE },
+		{ "resource-artifact", EMarketMode::RESOURCE_ARTIFACT },
+		{ "artifact-resource", EMarketMode::ARTIFACT_RESOURCE },
+		{ "artifact-experience", EMarketMode::ARTIFACT_EXP },
+		{ "creature-experience", EMarketMode::CREATURE_EXP },
+		{ "creature-undead", EMarketMode::CREATURE_UNDEAD },
+		{ "resource-skill", EMarketMode::RESOURCE_SKILL },
+	};
 }
 
 namespace EAiTactic
@@ -670,16 +693,6 @@ namespace ESpellCastProblem
 	};
 }
 
-namespace EMarketMode
-{
-	enum EMarketMode
-	{
-		RESOURCE_RESOURCE, RESOURCE_PLAYER, CREATURE_RESOURCE, RESOURCE_ARTIFACT,
-		ARTIFACT_RESOURCE, ARTIFACT_EXP, CREATURE_EXP, CREATURE_UNDEAD, RESOURCE_SKILL,
-		MARTKET_AFTER_LAST_PLACEHOLDER
-	};
-}
-
 namespace ECommander
 {
 	enum SecondarySkills {ATTACK, DEFENSE, HEALTH, DAMAGE, SPEED, SPELL_POWER, CASTS, RESISTANCE};
@@ -750,12 +763,12 @@ public:
 	enum EObj
 	{
 		NO_OBJ = -1,
-		ALTAR_OF_SACRIFICE = 2,
+		ALTAR_OF_SACRIFICE [[deprecated]] = 2,
 		ANCHOR_POINT = 3,
 		ARENA = 4,
 		ARTIFACT = 5,
 		PANDORAS_BOX = 6,
-		BLACK_MARKET = 7,
+		BLACK_MARKET [[deprecated]] = 7,
 		BOAT = 8,
 		BORDERGUARD = 9,
 		KEYMASTER = 10,
@@ -847,12 +860,12 @@ public:
 		TEMPLE = 96,
 		DEN_OF_THIEVES = 97,
 		TOWN = 98,
-		TRADING_POST = 99,
+		TRADING_POST [[deprecated]] = 99,
 		LEARNING_STONE = 100,
 		TREASURE_CHEST = 101,
 		TREE_OF_KNOWLEDGE = 102,
 		SUBTERRANEAN_GATE = 103,
-		UNIVERSITY = 104,
+		UNIVERSITY [[deprecated]] = 104,
 		WAGON = 105,
 		WAR_MACHINE_FACTORY = 106,
 		SCHOOL_OF_WAR = 107,
@@ -867,7 +880,7 @@ public:
 		RANDOM_MONSTER_L6 = 163,
 		RANDOM_MONSTER_L7 = 164,
 		BORDER_GATE = 212,
-		FREELANCERS_GUILD = 213,
+		FREELANCERS_GUILD [[deprecated]] = 213,
 		HERO_PLACEHOLDER = 214,
 		QUEST_GUARD = 215,
 		RANDOM_DWELLING = 216,
@@ -875,7 +888,7 @@ public:
 		RANDOM_DWELLING_FACTION = 218, //subtype = faction
 		GARRISON2 = 219,
 		ABANDONED_MINE = 220,
-		TRADING_POST_SNOW = 221,
+		TRADING_POST_SNOW [[deprecated]] = 221,
 		CLOVER_FIELD = 222,
 		CURSED_GROUND2 = 223,
 		EVIL_FOG = 224,

+ 1 - 1
lib/HeroBonus.cpp

@@ -1755,7 +1755,7 @@ BonusParams::BonusParams(std::string deprecatedTypeStr, std::string deprecatedSu
 		}
 		else if(deprecatedSubtype == SecondarySkill::LOGISTICS || deprecatedSubtypeStr == "skill.logistics")
 		{
-			subtype = 0;
+			subtype = 1;
 			subtypeRelevant = true;
 			valueType = Bonus::PERCENT_TO_BASE;
 			valueTypeRelevant = true;

+ 12 - 8
lib/JsonRandom.cpp

@@ -32,22 +32,26 @@ namespace JsonRandom
 {
 	si32 loadValue(const JsonNode & value, CRandomGenerator & rng, si32 defaultValue)
 	{
-		if (value.isNull())
+		if(value.isNull())
 			return defaultValue;
-		if (value.isNumber())
+		if(value.isNumber())
 			return static_cast<si32>(value.Float());
-		if (value.isVector())
+		if(value.isVector())
 		{
 			const auto & vector = value.Vector();
 
 			size_t index= rng.getIntRange(0, vector.size()-1)();
 			return loadValue(vector[index], rng, 0);
 		}
-		if (!value["amount"].isNull())
-			return static_cast<si32>(loadValue(value["amount"], rng, defaultValue));
-		si32 min = static_cast<si32>(loadValue(value["min"], rng, 0));
-		si32 max = static_cast<si32>(loadValue(value["max"], rng, 0));
-		return rng.getIntRange(min, max)();
+		if(value.isStruct())
+		{
+			if (!value["amount"].isNull())
+				return static_cast<si32>(loadValue(value["amount"], rng, defaultValue));
+			si32 min = static_cast<si32>(loadValue(value["min"], rng, 0));
+			si32 max = static_cast<si32>(loadValue(value["max"], rng, 0));
+			return rng.getIntRange(min, max)();
+		}
+		return defaultValue;
 	}
 
 	std::string loadKey(const JsonNode & value, CRandomGenerator & rng, const std::set<std::string> & valuesSet)

+ 20 - 71
lib/mapObjects/CGMarket.cpp

@@ -19,6 +19,7 @@
 #include "CGTownInstance.h"
 #include "../GameSettings.h"
 #include "../CSkillHandler.h"
+#include "CObjectClassesHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -158,29 +159,14 @@ std::vector<int> IMarket::availableItemsIds(EMarketMode::EMarketMode mode) const
 
 const IMarket * IMarket::castFrom(const CGObjectInstance *obj, bool verbose)
 {
-	switch(obj->ID)
-	{
-	case Obj::TOWN:
-		return dynamic_cast<const CGTownInstance *>(obj);
-	case Obj::ALTAR_OF_SACRIFICE:
-	case Obj::BLACK_MARKET:
-	case Obj::TRADING_POST:
-	case Obj::TRADING_POST_SNOW:
-	case Obj::FREELANCERS_GUILD:
-		return dynamic_cast<const CGMarket *>(obj);
-	case Obj::UNIVERSITY:
-		return dynamic_cast<const CGUniversity *>(obj);
-	default:
-		if(verbose)
-			logGlobal->error("Cannot cast to IMarket object with ID %d", obj->ID);
-		return nullptr;
-	}
+	auto * imarket = dynamic_cast<const IMarket *>(obj);
+	if(verbose && !imarket)
+		logGlobal->error("Cannot cast to IMarket object type %s", obj->typeName);
+	return imarket;
 }
 
-IMarket::IMarket(const CGObjectInstance *O)
-	:o(O)
+IMarket::IMarket()
 {
-
 }
 
 std::vector<EMarketMode::EMarketMode> IMarket::availableModes() const
@@ -193,43 +179,24 @@ std::vector<EMarketMode::EMarketMode> IMarket::availableModes() const
 	return ret;
 }
 
+void CGMarket::initObj(CRandomGenerator & rand)
+{
+	VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand);
+}
+
 void CGMarket::onHeroVisit(const CGHeroInstance * h) const
 {
-	openWindow(EOpenWindowMode::MARKET_WINDOW,id.getNum(),h->id.getNum());
+	openWindow(EOpenWindowMode::MARKET_WINDOW, id.getNum(), h->id.getNum());
 }
 
 int CGMarket::getMarketEfficiency() const
 {
-	return 5;
+	return marketEfficiency;
 }
 
 bool CGMarket::allowsTrade(EMarketMode::EMarketMode mode) const
 {
-	switch(mode)
-	{
-	case EMarketMode::RESOURCE_RESOURCE:
-	case EMarketMode::RESOURCE_PLAYER:
-		switch(ID)
-		{
-		case Obj::TRADING_POST:
-		case Obj::TRADING_POST_SNOW:
-			return true;
-		default:
-			return false;
-		}
-	case EMarketMode::CREATURE_RESOURCE:
-		return ID == Obj::FREELANCERS_GUILD;
-	//case ARTIFACT_RESOURCE:
-	case EMarketMode::RESOURCE_ARTIFACT:
-		return ID == Obj::BLACK_MARKET;
-	case EMarketMode::ARTIFACT_EXP:
-	case EMarketMode::CREATURE_EXP:
-		return ID == Obj::ALTAR_OF_SACRIFICE; //TODO? check here for alignment of visiting hero? - would not be coherent with other checks here
-	case EMarketMode::RESOURCE_SKILL:
-		return ID == Obj::UNIVERSITY;
-	default:
-		return false;
-	}
+	return marketModes.count(mode);
 }
 
 int CGMarket::availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const
@@ -239,18 +206,12 @@ int CGMarket::availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial
 
 std::vector<int> CGMarket::availableItemsIds(EMarketMode::EMarketMode mode) const
 {
-	switch(mode)
-	{
-	case EMarketMode::RESOURCE_RESOURCE:
-	case EMarketMode::RESOURCE_PLAYER:
+	if(allowsTrade(mode))
 		return IMarket::availableItemsIds(mode);
-	default:
-		return std::vector<int>();
-	}
+	return std::vector<int>();
 }
 
 CGMarket::CGMarket()
-	:IMarket(this)
 {
 }
 
@@ -293,28 +254,16 @@ void CGBlackMarket::newTurn(CRandomGenerator & rand) const
 
 void CGUniversity::initObj(CRandomGenerator & rand)
 {
+	CGMarket::initObj(rand);
+	
 	std::vector<int> toChoose;
 	for(int i = 0; i < VLC->skillh->size(); ++i)
 	{
-		if(cb->isAllowed(2, i))
+		if(!vstd::contains(skills, i) && cb->isAllowed(2, i))
 		{
 			toChoose.push_back(i);
 		}
 	}
-	if(toChoose.size() < 4)
-	{
-		logGlobal->warn("Warning: less then 4 available skills was found by University initializer!");
-		return;
-	}
-
-	// get 4 skills
-	for(int i = 0; i < 4; ++i)
-	{
-		// move randomly one skill to selected and remove from list
-		auto it = RandomGeneratorUtil::nextItem(toChoose, rand);
-		skills.push_back(*it);
-		toChoose.erase(it);
-	}
 }
 
 std::vector<int> CGUniversity::availableItemsIds(EMarketMode::EMarketMode mode) const
@@ -325,7 +274,7 @@ std::vector<int> CGUniversity::availableItemsIds(EMarketMode::EMarketMode mode)
 			return skills;
 
 		default:
-			return std::vector <int> ();
+			return std::vector<int>();
 	}
 }
 

+ 16 - 11
lib/mapObjects/CGMarket.h

@@ -16,12 +16,10 @@ VCMI_LIB_NAMESPACE_BEGIN
 class DLL_LINKAGE IMarket
 {
 public:
-	const CGObjectInstance *o;
-
-	IMarket(const CGObjectInstance *O);
+	IMarket();
 	virtual ~IMarket() {}
 
-	virtual int getMarketEfficiency() const =0;
+	virtual int getMarketEfficiency() const = 0;
 	virtual bool allowsTrade(EMarketMode::EMarketMode mode) const;
 	virtual int availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const; //-1 if unlimited
 	virtual std::vector<int> availableItemsIds(EMarketMode::EMarketMode mode) const;
@@ -30,19 +28,23 @@ public:
 	std::vector<EMarketMode::EMarketMode> availableModes() const;
 
 	static const IMarket *castFrom(const CGObjectInstance *obj, bool verbose = true);
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & o;
-	}
 };
 
 class DLL_LINKAGE CGMarket : public CGObjectInstance, public IMarket
 {
 public:
+	
+	std::set<EMarketMode::EMarketMode> marketModes;
+	int marketEfficiency;
+	
+	//window variables
+	std::string title;
+	std::string speech; //currently shown only in university
+	
 	CGMarket();
-	///IObjectIntercae
+	///IObjectInterface
 	void onHeroVisit(const CGHeroInstance * h) const override; //open trading window
+	void initObj(CRandomGenerator & rand) override;//set skills for trade
 
 	///IMarket
 	int getMarketEfficiency() const override;
@@ -53,7 +55,10 @@ public:
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & static_cast<CGObjectInstance&>(*this);
-		h & static_cast<IMarket&>(*this);
+		h & marketModes;
+		h & marketEfficiency;
+		h & title;
+		h & speech;
 	}
 };
 

+ 1 - 23
lib/mapObjects/CGTownInstance.cpp

@@ -220,7 +220,7 @@ bool CGTownInstance::hasCapitol() const
 
 CGTownInstance::CGTownInstance():
 	IShipyard(this),
-	IMarket(this),
+	IMarket(),
 	town(nullptr),
 	builded(0),
 	destroyed(0),
@@ -365,23 +365,6 @@ bool CGTownInstance::isBonusingBuildingAdded(BuildingID::EBuildingID bid) const
 	return present != bonusingBuildings.end();
 }
 
-//it does not check hasBuilt because this check is in the OnHeroVisit handler
-void CGTownInstance::tryAddOnePerWeekBonus(BuildingSubID::EBuildingSubID subID)
-{
-	auto bid = town->getBuildingType(subID);
-
-	if(bid != BuildingID::NONE && !isBonusingBuildingAdded(bid))
-		bonusingBuildings.push_back(new COPWBonus(bid, subID, this));
-}
-
-void CGTownInstance::tryAddVisitingBonus(BuildingSubID::EBuildingSubID subID)
-{
-	auto bid = town->getBuildingType(subID);
-
-	if(bid != BuildingID::NONE && !isBonusingBuildingAdded(bid))
-		bonusingBuildings.push_back(new CTownBonus(bid, subID, this));
-}
-
 void CGTownInstance::addTownBonuses()
 {
 	for(const auto & kvp : town->buildings)
@@ -487,11 +470,6 @@ void CGTownInstance::initObj(CRandomGenerator & rand) ///initialize town structu
 	updateAppearance();
 }
 
-bool CGTownInstance::hasBuiltInOldWay(ETownType::ETownType type, const BuildingID & bid) const
-{
-	return (this->town->faction != nullptr && this->town->faction->getIndex() == type && hasBuilt(bid));
-}
-
 void CGTownInstance::newTurn(CRandomGenerator & rand) const
 {
 	if (cb->getDate(Date::DAY_OF_WEEK) == 1) //reset on new week

+ 0 - 4
lib/mapObjects/CGTownInstance.h

@@ -74,7 +74,6 @@ public:
 	{
 		h & static_cast<CGDwelling&>(*this);
 		h & static_cast<IShipyard&>(*this);
-		h & static_cast<IMarket&>(*this);
 		h & name;
 		h & builded;
 		h & destroyed;
@@ -212,11 +211,8 @@ private:
 	void setOwner(const PlayerColor & owner) const;
 	void onTownCaptured(const PlayerColor & winner) const;
 	int getDwellingBonus(const std::vector<CreatureID>& creatureIds, const std::vector<ConstTransitivePtr<CGDwelling> >& dwellings) const;
-	bool hasBuiltInOldWay(ETownType::ETownType type, const BuildingID & bid) const;
 	bool townEnvisagesBuilding(BuildingSubID::EBuildingSubID bid) const;
 	bool isBonusingBuildingAdded(BuildingID::EBuildingID bid) const;
-	void tryAddOnePerWeekBonus(BuildingSubID::EBuildingSubID subID);
-	void tryAddVisitingBonus(BuildingSubID::EBuildingSubID subID);
 	void initOverriddenBids();
 	void addTownBonuses();
 };

+ 1 - 3
lib/mapObjects/CObjectClassesHandler.cpp

@@ -40,6 +40,7 @@ CObjectClassesHandler::CObjectClassesHandler()
 	SET_HANDLER_CLASS("town", CTownInstanceConstructor);
 	SET_HANDLER_CLASS("bank", CBankInstanceConstructor);
 	SET_HANDLER_CLASS("boat", BoatInstanceConstructor);
+	SET_HANDLER_CLASS("market", MarketInstanceConstructor);
 
 	SET_HANDLER_CLASS("static", CObstacleConstructor);
 	SET_HANDLER_CLASS("", CObstacleConstructor);
@@ -54,7 +55,6 @@ CObjectClassesHandler::CObjectClassesHandler()
 	SET_HANDLER("generic", CGObjectInstance);
 	SET_HANDLER("cartographer", CCartographer);
 	SET_HANDLER("artifact", CGArtifact);
-	SET_HANDLER("blackMarket", CGBlackMarket);
 	SET_HANDLER("borderGate", CGBorderGate);
 	SET_HANDLER("borderGuard", CGBorderGuard);
 	SET_HANDLER("monster", CGCreature);
@@ -65,7 +65,6 @@ CObjectClassesHandler::CObjectClassesHandler()
 	SET_HANDLER("keymaster", CGKeymasterTent);
 	SET_HANDLER("lighthouse", CGLighthouse);
 	SET_HANDLER("magi", CGMagi);
-	SET_HANDLER("market", CGMarket);
 	SET_HANDLER("mine", CGMine);
 	SET_HANDLER("obelisk", CGObelisk);
 	SET_HANDLER("observatory", CGObservatory);
@@ -82,7 +81,6 @@ CObjectClassesHandler::CObjectClassesHandler()
 	SET_HANDLER("monolith", CGMonolith);
 	SET_HANDLER("subterraneanGate", CGSubterraneanGate);
 	SET_HANDLER("whirlpool", CGWhirlpool);
-	SET_HANDLER("university", CGUniversity);
 	SET_HANDLER("witch", CGWitchHut);
 	SET_HANDLER("terrain", CGTerrainPatch);
 

+ 62 - 0
lib/mapObjects/CommonConstructors.cpp

@@ -291,6 +291,68 @@ void BoatInstanceConstructor::configureObject(CGObjectInstance * object, CRandom
 
 }
 
+void MarketInstanceConstructor::initTypeData(const JsonNode & input)
+{
+	for(auto & element : input["modes"].Vector())
+	{
+		if(MappedKeys::MARKET_NAMES_TO_TYPES.count(element.String()))
+			marketModes.insert(MappedKeys::MARKET_NAMES_TO_TYPES.at(element.String()));
+	}
+	
+	marketEfficiency = input["efficiency"].isNull() ? 5 : input["efficiency"].Integer();
+	predefinedOffer = input["offer"];
+	
+	title = input["title"].String();
+	speech = input["speech"].String();
+}
+
+CGObjectInstance * MarketInstanceConstructor::create(std::shared_ptr<const ObjectTemplate> tmpl) const
+{
+	CGMarket * market = nullptr;
+	if(marketModes.size() == 1)
+	{
+		switch(*marketModes.begin())
+		{
+			case EMarketMode::ARTIFACT_RESOURCE:
+			case EMarketMode::RESOURCE_ARTIFACT:
+				market = new CGBlackMarket;
+				break;
+				
+			case EMarketMode::RESOURCE_SKILL:
+				market = new CGUniversity;
+				break;
+		}
+	}
+	
+	if(!market)
+		market = new CGMarket;
+	
+	preInitObject(market);
+
+	if(tmpl)
+		market->appearance = tmpl;
+	market->marketModes = marketModes;
+	market->marketEfficiency = marketEfficiency;
+	
+	market->title = market->getObjectName();
+	if(!title.empty())
+		market->title = VLC->generaltexth->translate(title);
+	
+	market->speech = VLC->generaltexth->translate(speech);
+	
+	return market;
+}
+
+void MarketInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const
+{
+	if(auto * university = dynamic_cast<CGUniversity *>(object))
+	{
+		for(auto skill : JsonRandom::loadSecondary(predefinedOffer, rng))
+			university->skills.push_back(skill.first.getNum());
+	}
+}
+
+
 bool CBankInstanceConstructor::hasNameTextID() const
 {
 	return true;

+ 23 - 0
lib/mapObjects/CommonConstructors.h

@@ -255,4 +255,27 @@ public:
 	}
 };
 
+class MarketInstanceConstructor : public CDefaultObjectTypeHandler<CGMarket>
+{
+protected:
+	void initTypeData(const JsonNode & config) override;
+	
+	std::set<EMarketMode::EMarketMode> marketModes;
+	JsonNode predefinedOffer;
+	int marketEfficiency;
+	
+	std::string title, speech;
+	
+public:
+	CGObjectInstance * create(std::shared_ptr<const ObjectTemplate> tmpl = nullptr) const override;
+	void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CDefaultObjectTypeHandler<CGMarket>&>(*this);
+		h & marketModes;
+		h & marketEfficiency;
+	}
+};
+
 VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/mapObjects/MiscObjects.cpp

@@ -2145,7 +2145,7 @@ void CGLighthouse::giveBonusTo(const PlayerColor & player, bool onInit) const
 	gb.bonus.duration = Bonus::PERMANENT;
 	gb.bonus.source = Bonus::OBJECT;
 	gb.bonus.sid = id.getNum();
-	gb.bonus.subtype = 1;
+	gb.bonus.subtype = 0;
 
 	// FIXME: This is really dirty hack
 	// Proper fix would be to make CGLighthouse into bonus system node

+ 3 - 3
lib/registerTypes/RegisterTypes.h

@@ -57,7 +57,7 @@ void registerTypesMapObjects1(Serializer &s)
 	s.template registerType<CGObjectInstance, CGDenOfthieves>();
 	s.template registerType<CGObjectInstance, CGLighthouse>();
 	s.template registerType<CGObjectInstance, CGTerrainPatch>();
-	s.template registerType<CGObjectInstance, CGMarket>(); s.template registerType<IMarket, CGMarket>();
+	s.template registerType<CGObjectInstance, CGMarket>();
 		s.template registerType<CGMarket, CGBlackMarket>();
 		s.template registerType<CGMarket, CGUniversity>();
 	s.template registerType<CGObjectInstance, CGHeroPlaceholder>();
@@ -67,7 +67,7 @@ void registerTypesMapObjects1(Serializer &s)
 	// Armed objects
 	s.template registerType<CArmedInstance, CGHeroInstance>(); s.template registerType<IBoatGenerator, CGHeroInstance>(); s.template registerType<CArtifactSet, CGHeroInstance>();
 	s.template registerType<CArmedInstance, CGDwelling>();
-		s.template registerType<CGDwelling, CGTownInstance>(); s.template registerType<IShipyard, CGTownInstance>(); s.template registerType<IMarket, CGTownInstance>();
+		s.template registerType<CGDwelling, CGTownInstance>(); s.template registerType<IShipyard, CGTownInstance>();
 	s.template registerType<CArmedInstance, CGPandoraBox>();
 		s.template registerType<CGPandoraBox, CGEvent>();
 	s.template registerType<CArmedInstance, CGCreature>();
@@ -89,12 +89,12 @@ void registerTypesMapObjectTypes(Serializer &s)
 	s.template registerType<AObjectTypeHandler, CDwellingInstanceConstructor>();
 	s.template registerType<AObjectTypeHandler, CBankInstanceConstructor>();
 	s.template registerType<AObjectTypeHandler, BoatInstanceConstructor>();
+	s.template registerType<AObjectTypeHandler, MarketInstanceConstructor>();
 	s.template registerType<AObjectTypeHandler, CObstacleConstructor>();
 
 #define REGISTER_GENERIC_HANDLER(TYPENAME) s.template registerType<AObjectTypeHandler, CDefaultObjectTypeHandler<TYPENAME> >()
 
 	REGISTER_GENERIC_HANDLER(CGObjectInstance);
-	REGISTER_GENERIC_HANDLER(CGMarket);
 	REGISTER_GENERIC_HANDLER(CCartographer);
 	REGISTER_GENERIC_HANDLER(CGArtifact);
 	REGISTER_GENERIC_HANDLER(CGBlackMarket);

+ 1 - 10
mapeditor/mainwindow.cpp

@@ -540,19 +540,16 @@ void MainWindow::addGroupIntoCatalog(const std::string & groupName, bool useCust
 			//create object to extract name
 			std::unique_ptr<CGObjectInstance> temporaryObj(factory->create(templ));
 			QString translated = useCustomName ? tr(temporaryObj->getObjectName().c_str()) : subGroupName;
+			itemType->setText(translated);
 
 			//do not have extra level
 			if(singleTemplate)
 			{
-				if(useCustomName)
-					itemType->setText(translated);
 				itemType->setIcon(QIcon(preview));
 				itemType->setData(data);
 			}
 			else
 			{
-				if(useCustomName)
-					itemType->setText(translated);
 				auto * item = new QStandardItem(QIcon(preview), QString::fromStdString(templ->stringID));
 				item->setData(data);
 				itemType->appendRow(item);
@@ -623,9 +620,7 @@ void MainWindow::loadObjectsTree()
 	addGroupIntoCatalog("TOWNS", true, false, Obj::SHIPYARD);
 	addGroupIntoCatalog("TOWNS", true, false, Obj::GARRISON);
 	addGroupIntoCatalog("TOWNS", true, false, Obj::GARRISON2);
-	addGroupIntoCatalog("OBJECTS", true, false, Obj::ALTAR_OF_SACRIFICE);
 	addGroupIntoCatalog("OBJECTS", true, false, Obj::ARENA);
-	addGroupIntoCatalog("OBJECTS", true, false, Obj::BLACK_MARKET);
 	addGroupIntoCatalog("OBJECTS", true, false, Obj::BUOY);
 	addGroupIntoCatalog("OBJECTS", true, false, Obj::CARTOGRAPHER);
 	addGroupIntoCatalog("OBJECTS", true, false, Obj::SWAN_POND);
@@ -662,17 +657,13 @@ void MainWindow::loadObjectsTree()
 	addGroupIntoCatalog("OBJECTS", true, false, Obj::TAVERN);
 	addGroupIntoCatalog("OBJECTS", true, false, Obj::TEMPLE);
 	addGroupIntoCatalog("OBJECTS", true, false, Obj::DEN_OF_THIEVES);
-	addGroupIntoCatalog("OBJECTS", true, false, Obj::TRADING_POST);
-	addGroupIntoCatalog("OBJECTS", true, false, Obj::TRADING_POST_SNOW);
 	addGroupIntoCatalog("OBJECTS", true, false, Obj::LEARNING_STONE);
 	addGroupIntoCatalog("OBJECTS", true, false, Obj::TREE_OF_KNOWLEDGE);
-	addGroupIntoCatalog("OBJECTS", true, false, Obj::UNIVERSITY);
 	addGroupIntoCatalog("OBJECTS", true, false, Obj::WAGON);
 	addGroupIntoCatalog("OBJECTS", true, false, Obj::SCHOOL_OF_WAR);
 	addGroupIntoCatalog("OBJECTS", true, false, Obj::WAR_MACHINE_FACTORY);
 	addGroupIntoCatalog("OBJECTS", true, false, Obj::WARRIORS_TOMB);
 	addGroupIntoCatalog("OBJECTS", true, false, Obj::WITCH_HUT);
-	addGroupIntoCatalog("OBJECTS", true, false, Obj::FREELANCERS_GUILD);
 	addGroupIntoCatalog("OBJECTS", true, false, Obj::SANCTUARY);
 	addGroupIntoCatalog("OBJECTS", true, false, Obj::MARLETTO_TOWER);
 	addGroupIntoCatalog("HEROES", true, false, Obj::PRISON);

+ 4 - 4
server/CGameHandler.cpp

@@ -3902,7 +3902,7 @@ bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocat
 
 	if(srcArtifact == nullptr)
 		COMPLAIN_RET("No artifact to move!");
-	if(destArtifact && srcPlayer != dstPlayer)
+	if(destArtifact && srcPlayer != dstPlayer && !isDstSlotBackpack)
 		COMPLAIN_RET("Can't touch artifact on hero of another player!");
 
 	// Check if src/dest slots are appropriate for the artifacts exchanged.
@@ -4177,12 +4177,12 @@ bool CGameHandler::buyArtifact(const IMarket *m, const CGHeroInstance *h, GameRe
 	giveResource(h->tempOwner, rid, -b1);
 
 	SetAvailableArtifacts saa;
-	if (m->o->ID == Obj::TOWN)
+	if(dynamic_cast<const CGTownInstance *>(m))
 	{
 		saa.id = -1;
 		saa.arts = CGTownInstance::merchantArtifacts;
 	}
-	else if (const CGBlackMarket *bm = dynamic_cast<const CGBlackMarket *>(m->o)) //black market
+	else if(const CGBlackMarket *bm = dynamic_cast<const CGBlackMarket *>(m)) //black market
 	{
 		saa.id = bm->id.getNum();
 		saa.arts = bm->artifacts;
@@ -4309,7 +4309,7 @@ bool CGameHandler::transformInUndead(const IMarket *market, const CGHeroInstance
 	if (hero)
 		army = hero;
 	else
-		army = dynamic_cast<const CGTownInstance *>(market->o);
+		army = dynamic_cast<const CGTownInstance *>(market);
 
 	if (!army)
 		COMPLAIN_RET("Incorrect call to transform in undead!");

+ 7 - 4
server/CVCMIServer.cpp

@@ -375,10 +375,10 @@ class CVCMIServerPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor)
 {
 private:
 	CVCMIServer & handler;
-	CGameHandler & gh;
+	std::shared_ptr<CGameHandler> gh;
 
 public:
-	CVCMIServerPackVisitor(CVCMIServer & handler, CGameHandler & gh)
+	CVCMIServerPackVisitor(CVCMIServer & handler, std::shared_ptr<CGameHandler> gh)
 			:handler(handler), gh(gh)
 	{
 	}
@@ -392,7 +392,10 @@ public:
 
 	virtual void visitForServer(CPackForServer & serverPack) override
 	{
-		gh.handleReceivedPack(&serverPack);
+		if (gh)
+			gh->handleReceivedPack(&serverPack);
+		else
+			logNetwork->error("Received pack for game server while in lobby!");
 	}
 
 	virtual void visitForClient(CPackForClient & clientPack) override
@@ -432,7 +435,7 @@ void CVCMIServer::threadHandleClient(std::shared_ptr<CConnection> c)
 				break;
 			}
 
-			CVCMIServerPackVisitor visitor(*this, *this->gh);
+			CVCMIServerPackVisitor visitor(*this, this->gh);
 			pack->visit(visitor);
 		}
 #ifndef _MSC_VER