Jelajahi Sumber

CArtifactHolder divided into files

SoundSSGood 2 tahun lalu
induk
melakukan
8b531ca6c4

+ 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
@@ -213,6 +219,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

+ 1 - 1
client/CPlayerInterface.cpp

@@ -1925,7 +1925,7 @@ void CPlayerInterface::askToAssembleArtifact(const ArtifactLocation &al)
 							 al.slot.num);
 			return;
 		}
-		ArtifactUtils::askToAssemble(hero, al.slot);
+		ArtifactUtilsClient::askToAssemble(hero, al.slot);
 	}
 }
 

+ 2 - 775
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,7 +21,6 @@
 
 #include "../../CCallback.h"
 
-#include "../../lib/CArtHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
 
 #include "../../lib/mapObjects/CGHeroInstance.h"
@@ -289,775 +284,7 @@ void CHeroArtPlace::createImage()
 	selection->disable();
 }
 
-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);
-	}
-}
-
-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));
-}
-
-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));
-}
-
-CArtifactsOfHeroAltar::CArtifactsOfHeroAltar(const Point & position)
-{
-	init(
-		std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1), 
-		std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1), 
-		position,
-		std::bind(&CArtifactsOfHeroAltar::scrollBackpack, this, _1));
-	visibleArtSet = std::make_shared<CArtifactFittingSet>(ArtBearer::ArtBearer::HERO);
-	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);
-			}
-		}
-	}
-}
-
-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();
-}
-
-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();
-			constexpr auto isMainWindow = std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMain>>;
-			constexpr auto isKingdomWindow = std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroKingdom>>;
-			constexpr auto isAltarWindow = std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroAltar>>;
-			constexpr auto isMarketWindow = std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMarket>>;
-
-			// Hero(Main, Exchange) window, Kingdom window, Altar window left click handler
-			if constexpr(isMainWindow || isKingdomWindow || isAltarWindow)
-			{
-				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(isKingdomWindow)
-					{
-						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(isMarketWindow)
-			{
-				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();
-			constexpr auto isMainWindow = std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMain>>;
-			constexpr auto isKingdomWindow = std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroKingdom>>;
-			constexpr auto isAltarWindow = std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroAltar>>;
-			constexpr auto isMarketWindow = std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMarket>>;
-
-			// Hero(Main, Exchange) window, Kingdom window right click handler
-			if constexpr(isMainWindow || isKingdomWindow)
-			{
-				if(artPlace.getArt())
-				{
-					if(ArtifactUtils::askToDisassemble(artSetPtr->getHero(), artPlace.slot))
-					{
-						return;
-					}
-					if(ArtifactUtils::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(isAltarWindow || isMarketWindow)
-			{
-				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;
-}
-
-bool ArtifactUtils::askToAssemble(const CGHeroInstance * hero, const ArtifactPosition & slot)
+bool ArtifactUtilsClient::askToAssemble(const CGHeroInstance * hero, const ArtifactPosition & slot)
 {
 	assert(hero);
 	const auto art = hero->getArt(slot);
@@ -1078,7 +305,7 @@ bool ArtifactUtils::askToAssemble(const CGHeroInstance * hero, const ArtifactPos
 	return false;
 }
 
-bool ArtifactUtils::askToDisassemble(const CGHeroInstance * hero, const ArtifactPosition & slot)
+bool ArtifactUtilsClient::askToDisassemble(const CGHeroInstance * hero, const ArtifactPosition & slot)
 {
 	assert(hero);
 	const auto art = hero->getArt(slot);

+ 3 - 132
client/widgets/CArtifactHolder.h

@@ -15,11 +15,9 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 struct ArtifactLocation;
 class CArtifactSet;
-class CArtifactFittingSet;
 
 VCMI_LIB_NAMESPACE_END
 
-class CArtifactsOfHeroBase;
 class CAnimImage;
 class CButton;
 
@@ -94,135 +92,8 @@ protected:
 	void createImage() override;
 };
 
-class CArtifactsOfHeroBase : public CIntObject
+namespace ArtifactUtilsClient
 {
-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);
-};
-
-class CArtifactsOfHeroMain : public CArtifactsOfHeroBase
-{
-public:
-	CArtifactsOfHeroMain(const Point & position);
-	void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
-	void pickUpArtifact(CHeroArtPlace & artPlace);
-};
-
-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);
-};
-
-class CArtifactsOfHeroAltar : public CArtifactsOfHeroBase
-{
-public:
-	std::set<const CArtifactInstance*> artifactsOnAltar;
-	ArtifactPosition pickedArtFromSlot;
-	std::shared_ptr<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);
-};
-
-class CArtifactsOfHeroMarket : public CArtifactsOfHeroBase
-{
-public:
-	std::function<void(CHeroArtPlace*)> selectArtCallback;
-
-	CArtifactsOfHeroMarket(const Point & position);
-	void scrollBackpack(int offset) override;
-};
-
-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);
-};
-
-namespace ArtifactUtils
-{
-	bool askToAssemble(const CGHeroInstance* hero, const ArtifactPosition& slot);
-	bool askToDisassemble(const CGHeroInstance* hero, const ArtifactPosition& slot);
+	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;
+};

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

+ 1 - 1
client/windows/CKingdomInterface.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "../widgets/CArtifactHolder.h"
+#include "../widgets/CWindowWithArtifacts.h"
 #include "../widgets/CGarrisonInt.h"
 
 class CButton;

+ 3 - 3
client/windows/CTradeWindow.cpp

@@ -1305,7 +1305,7 @@ void CAltarWindow::SacrificeAll()
 	else
 	{
 		auto artifactsOfHero = std::dynamic_pointer_cast<CArtifactsOfHeroAltar>(arts);
-		for(const auto & aw : artifactsOfHero->visibleArtSet->artifactsWorn)
+		for(const auto & aw : artifactsOfHero->visibleArtSet.artifactsWorn)
 		{
 			if(!aw.second.locked)
 				moveArtToAltar(nullptr, aw.second.artifact);
@@ -1471,9 +1471,9 @@ int CAltarWindow::firstFreeSlot()
 void CAltarWindow::SacrificeBackpack()
 {
 	auto artsAltar = std::dynamic_pointer_cast<CArtifactsOfHeroAltar>(arts);
-	while(!artsAltar->visibleArtSet->artifactsInBackpack.empty())
+	while(!artsAltar->visibleArtSet.artifactsInBackpack.empty())
 	{
-		if(!putOnAltar(nullptr, artsAltar->visibleArtSet->artifactsInBackpack[0].artifact))
+		if(!putOnAltar(nullptr, artsAltar->visibleArtSet.artifactsInBackpack[0].artifact))
 			break;
 	};
 	calcTotalExp();

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

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