Selaa lähdekoodia

Merge pull request #2325 from SoundSSGood/hero-backpack-window

Hero backpack window prototyping
Ivan Savenko 2 vuotta sitten
vanhempi
sitoutus
3f4e88c396

+ 4 - 0
client/CMakeLists.txt

@@ -105,6 +105,7 @@ set(client_SRCS
 	widgets/CArtifactsOfHeroKingdom.cpp
 	widgets/CArtifactsOfHeroAltar.cpp
 	widgets/CArtifactsOfHeroMarket.cpp
+	widgets/CArtifactsOfHeroBackpack.cpp
 	widgets/CWindowWithArtifacts.cpp
 
 	windows/CCastleInterface.cpp
@@ -121,6 +122,7 @@ set(client_SRCS
 	windows/GUIClasses.cpp
 	windows/InfoWindows.cpp
 	windows/QuickRecruitmentWindow.cpp
+	windows/CHeroBackpackWindow.cpp
 	windows/settings/GeneralOptionsTab.cpp
 	windows/settings/OtherOptionsTab.cpp
 	windows/settings/SettingsMainWindow.cpp
@@ -258,6 +260,7 @@ set(client_HEADERS
 	widgets/CArtifactsOfHeroKingdom.h
 	widgets/CArtifactsOfHeroAltar.h
 	widgets/CArtifactsOfHeroMarket.h
+	widgets/CArtifactsOfHeroBackpack.h
 	widgets/CWindowWithArtifacts.h
 
 	windows/CCastleInterface.h
@@ -274,6 +277,7 @@ set(client_HEADERS
 	windows/GUIClasses.h
 	windows/InfoWindows.h
 	windows/QuickRecruitmentWindow.h
+	windows/CHeroBackpackWindow.h
 	windows/settings/GeneralOptionsTab.h
 	windows/settings/OtherOptionsTab.h
 	windows/settings/SettingsMainWindow.h

+ 1 - 0
client/gui/Shortcut.h

@@ -155,6 +155,7 @@ enum class EShortcut
 	HERO_LOOSE_FORMATION,
 	HERO_TIGHT_FORMATION,
 	HERO_TOGGLE_TACTICS, // b
+	HERO_BACKPACK,
 
 	// Spellbook screen
 	SPELLBOOK_TAB_ADVENTURE,

+ 5 - 0
client/widgets/CArtifactsOfHeroAltar.cpp

@@ -28,6 +28,11 @@ CArtifactsOfHeroAltar::CArtifactsOfHeroAltar(const Point & position)
 	pickedArtFromSlot = ArtifactPosition::PRE_FIRST;
 };
 
+CArtifactsOfHeroAltar::~CArtifactsOfHeroAltar()
+{
+	putBackPickedArtifact();
+}
+
 void CArtifactsOfHeroAltar::setHero(const CGHeroInstance * hero)
 {
 	if(hero)

+ 1 - 0
client/widgets/CArtifactsOfHeroAltar.h

@@ -21,6 +21,7 @@ public:
 	CArtifactFittingSet visibleArtSet;
 
 	CArtifactsOfHeroAltar(const Point & position);
+	~CArtifactsOfHeroAltar();
 	void setHero(const CGHeroInstance * hero) override;
 	void updateWornSlots() override;
 	void updateBackpackSlots() override;

+ 99 - 0
client/widgets/CArtifactsOfHeroBackpack.cpp

@@ -0,0 +1,99 @@
+/*
+ * CArtifactsOfHeroBackpack.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 "CArtifactsOfHeroBackpack.h"
+
+#include "../gui/CGuiHandler.h"
+#include "../gui/Shortcut.h"
+
+#include "Buttons.h"
+#include "GameSettings.h"
+#include "IHandlerBase.h"
+#include "ObjectLists.h"
+
+#include "../CPlayerInterface.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+
+#include "../../CCallback.h"
+
+CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack(const Point & position)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
+	pos += position;
+
+	const auto backpackCap = VLC->settings()->getInteger(EGameSettings::HEROES_BACKPACK_CAP);
+	auto visibleCapasityMax = HERO_BACKPACK_WINDOW_SLOT_LINES * HERO_BACKPACK_WINDOW_SLOT_COLUMNS;
+	if(backpackCap >= 0)
+		visibleCapasityMax = visibleCapasityMax > backpackCap ? backpackCap : visibleCapasityMax;
+
+	backpack.resize(visibleCapasityMax);
+	size_t artPlaceIdx = 0;
+	for(auto & artPlace : backpack)
+	{
+		artPlace = std::make_shared<CHeroArtPlace>(
+			Point(46 * (artPlaceIdx % HERO_BACKPACK_WINDOW_SLOT_COLUMNS), 46 * (artPlaceIdx / HERO_BACKPACK_WINDOW_SLOT_COLUMNS)));
+		artPlace->setArtifact(nullptr);
+		artPlace->leftClickCallback = std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1);
+		artPlace->rightClickCallback = std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1);
+		artPlaceIdx++;
+	}
+
+	if(backpackCap < 0 || visibleCapasityMax < backpackCap)
+	{
+		auto onCreate = [](size_t index) -> std::shared_ptr<CIntObject>
+		{
+			return std::make_shared<CIntObject>();
+		};
+		CListBoxWithCallback::MovedPosCallback posMoved = [this](size_t pos) -> void
+		{
+			scrollBackpack(static_cast<int>(pos) * HERO_BACKPACK_WINDOW_SLOT_COLUMNS - backpackPos);
+		};
+		backpackListBox = std::make_shared<CListBoxWithCallback>(
+			posMoved, onCreate, Point(0, 0), Point(0, 0), HERO_BACKPACK_WINDOW_SLOT_LINES, 0, 0, 1,
+			Rect(HERO_BACKPACK_WINDOW_SLOT_COLUMNS * 46 + 10, 0, HERO_BACKPACK_WINDOW_SLOT_LINES * 46 - 5, 0));
+	}
+}
+
+void CArtifactsOfHeroBackpack::swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc)
+{
+	LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc);
+}
+
+void CArtifactsOfHeroBackpack::pickUpArtifact(CHeroArtPlace & artPlace)
+{
+	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero, artPlace.slot),
+		ArtifactLocation(curHero, ArtifactPosition::TRANSITION_POS));
+}
+
+void CArtifactsOfHeroBackpack::scrollBackpack(int offset)
+{
+	if(backpackListBox)
+		backpackListBox->resize(getActiveSlotLinesNum());
+	backpackPos += offset;
+	auto slot = ArtifactPosition(GameConstants::BACKPACK_START + backpackPos);
+	for(auto artPlace : backpack)
+	{
+		setSlotData(artPlace, slot, *curHero);
+		slot = slot + 1;
+	}
+	redraw();
+}
+
+void CArtifactsOfHeroBackpack::updateBackpackSlots()
+{
+	if(backpackListBox)
+		backpackListBox->resize(getActiveSlotLinesNum());
+	CArtifactsOfHeroBase::updateBackpackSlots();
+}
+
+size_t CArtifactsOfHeroBackpack::getActiveSlotLinesNum()
+{
+	return (curHero->artifactsInBackpack.size() + HERO_BACKPACK_WINDOW_SLOT_COLUMNS - 1) / HERO_BACKPACK_WINDOW_SLOT_COLUMNS;
+}

+ 36 - 0
client/widgets/CArtifactsOfHeroBackpack.h

@@ -0,0 +1,36 @@
+/*
+ * CArtifactsOfHeroBackpack.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 CListBoxWithCallback;
+
+class CArtifactsOfHeroBackpack : public CArtifactsOfHeroBase
+{
+public:
+	CArtifactsOfHeroBackpack(const Point & position);
+	void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
+	void pickUpArtifact(CHeroArtPlace & artPlace);
+	void scrollBackpack(int offset) override;
+	void updateBackpackSlots() override;
+	size_t getActiveSlotLinesNum();
+
+private:
+	std::shared_ptr<CListBoxWithCallback> backpackListBox;
+	const size_t HERO_BACKPACK_WINDOW_SLOT_COLUMNS = 8;
+	const size_t HERO_BACKPACK_WINDOW_SLOT_LINES = 8;
+};

+ 16 - 11
client/widgets/CArtifactsOfHeroBase.cpp

@@ -11,7 +11,6 @@
 #include "CArtifactsOfHeroBase.h"
 
 #include "../gui/CGuiHandler.h"
-#include "../gui/CursorHandler.h"
 #include "../gui/Shortcut.h"
 
 #include "Buttons.h"
@@ -26,16 +25,13 @@
 
 CArtifactsOfHeroBase::CArtifactsOfHeroBase()
 	: backpackPos(0),
-	curHero(nullptr)
+	curHero(nullptr),
+	putBackPickedArtCallback(nullptr)
 {
 }
 
-CArtifactsOfHeroBase::~CArtifactsOfHeroBase()
+void CArtifactsOfHeroBase::putBackPickedArtifact()
 {
-	// 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())
 	{
@@ -49,6 +45,13 @@ CArtifactsOfHeroBase::~CArtifactsOfHeroBase()
 			LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero, ArtifactPosition::TRANSITION_POS), ArtifactLocation(curHero, slot));
 		}
 	}
+	if(putBackPickedArtCallback)
+		putBackPickedArtCallback();
+}
+
+void CArtifactsOfHeroBase::setPutBackPickedArtifactCallback(PutBackPickedArtCallback callback)
+{
+	putBackPickedArtCallback = callback;
 }
 
 void CArtifactsOfHeroBase::init(
@@ -113,7 +116,7 @@ void CArtifactsOfHeroBase::setHero(const CGHeroInstance * hero)
 	{
 		setSlotData(slot.second, slot.first, *curHero);
 	}
-	scrollBackpackForArtSet(0, *curHero);
+	scrollBackpack(0);
 }
 
 const CGHeroInstance * CArtifactsOfHeroBase::getHero() const
@@ -163,8 +166,10 @@ void CArtifactsOfHeroBase::scrollBackpackForArtSet(int offset, const CArtifactSe
 	}
 
 	// Blocking scrolling if there is not enough artifacts to scroll
-	leftBackpackRoll->block(!scrollingPossible);
-	rightBackpackRoll->block(!scrollingPossible);
+	if(leftBackpackRoll)
+		leftBackpackRoll->block(!scrollingPossible);
+	if(rightBackpackRoll)
+		rightBackpackRoll->block(!scrollingPossible);
 }
 
 void CArtifactsOfHeroBase::safeRedraw()
@@ -227,7 +232,7 @@ void CArtifactsOfHeroBase::updateBackpackSlots()
 {
 	if(curHero->artifactsInBackpack.size() <= backpack.size() && backpackPos != 0)
 		backpackPos = 0;
-	scrollBackpackForArtSet(0, *curHero);
+	scrollBackpack(0);
 }
 
 void CArtifactsOfHeroBase::updateSlot(const ArtifactPosition & slot)

+ 5 - 2
client/widgets/CArtifactsOfHeroBase.h

@@ -20,13 +20,14 @@ protected:
 public:
 	using ArtPlaceMap = std::map<ArtifactPosition, ArtPlacePtr>;
 	using ClickHandler = std::function<void(CArtifactsOfHeroBase&, CHeroArtPlace&)>;
+	using PutBackPickedArtCallback = std::function<void()>;
 
-	const CGHeroInstance * curHero;
 	ClickHandler leftClickCallback;
 	ClickHandler rightClickCallback;
 	
 	CArtifactsOfHeroBase();
-	virtual ~CArtifactsOfHeroBase();
+	virtual void putBackPickedArtifact();
+	virtual void setPutBackPickedArtifactCallback(PutBackPickedArtCallback callback);
 	virtual void leftClickArtPlace(CHeroArtPlace & artPlace);
 	virtual void rightClickArtPlace(CHeroArtPlace & artPlace);
 	virtual void setHero(const CGHeroInstance * hero);
@@ -42,11 +43,13 @@ public:
 	virtual const CArtifactInstance * getPickedArtifact();
 
 protected:
+	const CGHeroInstance * curHero;
 	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
+	PutBackPickedArtCallback putBackPickedArtCallback;
 
 	const std::vector<Point> slotPos =
 	{

+ 5 - 0
client/widgets/CArtifactsOfHeroKingdom.cpp

@@ -41,6 +41,11 @@ CArtifactsOfHeroKingdom::CArtifactsOfHeroKingdom(ArtPlaceMap ArtWorn, std::vecto
 	rightBackpackRoll->addCallback(std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, +1));
 }
 
+CArtifactsOfHeroKingdom::~CArtifactsOfHeroKingdom()
+{
+	putBackPickedArtifact();
+}
+
 void CArtifactsOfHeroKingdom::swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc)
 {
 	LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc);

+ 1 - 0
client/widgets/CArtifactsOfHeroKingdom.h

@@ -22,6 +22,7 @@ class CArtifactsOfHeroKingdom : public CArtifactsOfHeroBase
 public:
 	CArtifactsOfHeroKingdom(ArtPlaceMap ArtWorn, std::vector<ArtPlacePtr> Backpack,
 		std::shared_ptr<CButton> leftScroll, std::shared_ptr<CButton> rightScroll);
+	~CArtifactsOfHeroKingdom();
 	void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
 	void pickUpArtifact(CHeroArtPlace & artPlace);
 };

+ 5 - 0
client/widgets/CArtifactsOfHeroMain.cpp

@@ -23,6 +23,11 @@ CArtifactsOfHeroMain::CArtifactsOfHeroMain(const Point & position)
 		std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1));
 }
 
+CArtifactsOfHeroMain::~CArtifactsOfHeroMain()
+{
+	putBackPickedArtifact();
+}
+
 void CArtifactsOfHeroMain::swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc)
 {
 	LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc);

+ 1 - 0
client/widgets/CArtifactsOfHeroMain.h

@@ -21,6 +21,7 @@ class CArtifactsOfHeroMain : public CArtifactsOfHeroBase
 {
 public:
 	CArtifactsOfHeroMain(const Point & position);
+	~CArtifactsOfHeroMain();
 	void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
 	void pickUpArtifact(CHeroArtPlace & artPlace);
 };

+ 58 - 25
client/widgets/CWindowWithArtifacts.cpp

@@ -28,15 +28,26 @@
 
 void CWindowWithArtifacts::addSet(CArtifactsOfHeroPtr artSet)
 {
+	CArtifactsOfHeroBase::PutBackPickedArtCallback artPutBackHandler = []() -> void
+	{
+		CCS->curh->dragAndDropCursor(nullptr);
+	};
+
 	artSets.emplace_back(artSet);
-	std::visit([this](auto artSetWeak)
+	std::visit([this, artPutBackHandler](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->setPutBackPickedArtifactCallback(artPutBackHandler);
 		}, artSet);
 }
 
+void CWindowWithArtifacts::addCloseCallback(CloseCallback callback)
+{
+	closeCallback = callback;
+}
+
 const CGHeroInstance * CWindowWithArtifacts::getHeroPickedArtifact()
 {
 	auto res = getState();
@@ -85,35 +96,37 @@ void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst
 		{
 			const auto artSetPtr = artSetWeak.lock();
 
-			// Hero(Main, Exchange) window, Kingdom window, Altar window left click handler
+			// Hero(Main, Exchange) window, Kingdom window, Altar window, Backpack 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>>)
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroAltar>> ||
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroBackpack>>)
 			{
 				const auto pickedArtInst = getPickedArtifact();
 				const auto heroPickedArt = getHeroPickedArtifact();
 				const auto hero = artSetPtr->getHero();
+				auto isTransferAllowed = false;
+				std::string msg;
 
 				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()));
+							msg = 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"));
+								msg = CGI->generaltexth->translate("core.genrltxt.152");
 						}
 					}
 					// Check if artifact transfer is possible
@@ -132,7 +145,7 @@ void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst
 				else
 				{
 					if(artPlace.getArt())
-					{			
+					{
 						if(artSetPtr->getHero()->tempOwner == LOCPLINT->playerID)
 						{
 							if(checkSpecialArts(hero, artPlace))
@@ -143,12 +156,26 @@ void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst
 							for(const auto artSlot : ArtifactUtils::unmovableSlots())
 								if(artPlace.slot == artSlot)
 								{
-									LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[21]);
+									msg = CGI->generaltexth->allTexts[21];
 									break;
 								}
 						}
 					}
 				}
+
+				if constexpr(std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroBackpack>>)
+				{
+					if(!isTransferAllowed)
+					{
+						if(closeCallback)
+							closeCallback();
+					}
+				}
+				else
+				{
+					if(!msg.empty())
+						LOCPLINT->showInfoDialog(msg);
+				}
 			}
 			// Market window left click handler
 			else if constexpr(std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMarket>>)
@@ -184,10 +211,11 @@ void CWindowWithArtifacts::rightClickArtPlaceHero(CArtifactsOfHeroBase & artsIns
 		{
 			const auto artSetPtr = artSetWeak.lock();
 
-			// Hero(Main, Exchange) window, Kingdom window right click handler
+			// Hero (Main, Exchange) window, Kingdom window, Backpack 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>>)
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroKingdom>> ||
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroBackpack>>)
 			{
 				if(artPlace.getArt())
 				{
@@ -239,19 +267,19 @@ void CWindowWithArtifacts::artifactMoved(const ArtifactLocation & srcLoc, const
 		if(artSetPtr)
 		{
 			const auto hero = artSetPtr->getHero();
-			if(artSetPtr->isActive())
+			if(pickedArtInst)
 			{
-				if(pickedArtInst)
+				if(artSetPtr->isActive())
 				{
 					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);
-				}
+			}
+			else
+			{
+				artSetPtr->unmarkSlots();
+				CCS->curh->dragAndDropCursor(nullptr);
 			}
 			if(withRedraw)
 			{
@@ -316,19 +344,21 @@ void CWindowWithArtifacts::updateSlots(const ArtifactPosition & slot)
 std::optional<std::tuple<const CGHeroInstance*, const CArtifactInstance*>> CWindowWithArtifacts::getState()
 {
 	const CArtifactInstance * artInst = nullptr;
-	const CGHeroInstance * hero = nullptr;
-	size_t pickedCnt = 0;
+	std::map<const CGHeroInstance*, size_t> pickedCnt;
 
-	auto getHeroArtBody = [&hero, &artInst, &pickedCnt](auto artSetWeak) -> void
+	auto getHeroArtBody = [&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();
+				const auto hero = artSetPtr->getHero();
+				if(pickedCnt.count(hero) == 0)
+				{
+					pickedCnt.insert({ hero, hero->artifactsTransitionPos.size() });
+					artInst = art;
+				}
 			}
 		}
 	};
@@ -338,10 +368,13 @@ std::optional<std::tuple<const CGHeroInstance*, const CArtifactInstance*>> CWind
 	// 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)
+	if(std::accumulate(std::begin(pickedCnt), std::end(pickedCnt), 0, [](size_t accum, const auto & value)
+		{
+			return accum + value.second;
+		}) > 1)
 		return std::nullopt;
 	else
-		return std::make_tuple(hero, artInst);
+		return std::make_tuple(pickedCnt.begin()->first, artInst);
 }
 
 std::optional<CWindowWithArtifacts::CArtifactsOfHeroPtr> CWindowWithArtifacts::findAOHbyRef(CArtifactsOfHeroBase & artsInst)

+ 6 - 1
client/widgets/CWindowWithArtifacts.h

@@ -14,6 +14,7 @@
 #include "CArtifactsOfHeroKingdom.h"
 #include "CArtifactsOfHeroAltar.h"
 #include "CArtifactsOfHeroMarket.h"
+#include "CArtifactsOfHeroBackpack.h"
 
 class CWindowWithArtifacts : public CArtifactHolder
 {
@@ -22,9 +23,12 @@ public:
 		std::weak_ptr<CArtifactsOfHeroMarket>,
 		std::weak_ptr<CArtifactsOfHeroAltar>,
 		std::weak_ptr<CArtifactsOfHeroKingdom>,
-		std::weak_ptr<CArtifactsOfHeroMain>>;
+		std::weak_ptr<CArtifactsOfHeroMain>,
+		std::weak_ptr<CArtifactsOfHeroBackpack>>;
+	using CloseCallback = std::function<void()>;
 
 	void addSet(CArtifactsOfHeroPtr artSet);
+	void addCloseCallback(CloseCallback callback);
 	const CGHeroInstance * getHeroPickedArtifact();
 	const CArtifactInstance * getPickedArtifact();
 	void leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst, CHeroArtPlace & artPlace);
@@ -37,6 +41,7 @@ public:
 
 private:
 	std::vector<CArtifactsOfHeroPtr> artSets;
+	CloseCallback closeCallback;
 
 	void updateSlots(const ArtifactPosition & slot);
 	std::optional<std::tuple<const CGHeroInstance*, const CArtifactInstance*>> getState();

+ 35 - 0
client/widgets/ObjectLists.cpp

@@ -245,3 +245,38 @@ const std::list<std::shared_ptr<CIntObject>> & CListBox::getItems()
 {
 	return items;
 }
+
+CListBoxWithCallback::CListBoxWithCallback(CListBoxWithCallback::MovedPosCallback callback, CreateFunc create, Point pos, Point itemOffset, 
+	size_t visibleSize,	size_t totalSize, size_t initialPos, int slider, Rect sliderPos)
+	: CListBox(create, pos, itemOffset, visibleSize, totalSize, initialPos, slider, sliderPos)
+{
+	movedPosCallback = callback;
+}
+
+void CListBoxWithCallback::scrollTo(size_t pos)
+{
+	CListBox::scrollTo(pos);
+	if(movedPosCallback)
+		movedPosCallback(getPos());
+}
+
+void CListBoxWithCallback::moveToPos(size_t pos)
+{
+	CListBox::moveToPos(pos);
+	if(movedPosCallback)
+		movedPosCallback(getPos());
+}
+
+void CListBoxWithCallback::moveToNext()
+{
+	CListBox::moveToNext();
+	if(movedPosCallback)
+		movedPosCallback(getPos());
+}
+
+void CListBoxWithCallback::moveToPrev()
+{
+	CListBox::moveToPrev();
+	if(movedPosCallback)
+		movedPosCallback(getPos());
+}

+ 20 - 4
client/widgets/ObjectLists.h

@@ -99,12 +99,28 @@ public:
 	size_t getIndexOf(std::shared_ptr<CIntObject> item);
 
 	//scroll list to make item which visible
-	void scrollTo(size_t which);
+	virtual void scrollTo(size_t which);
 
 	//scroll list to specified position
-	void moveToPos(size_t which);
-	void moveToNext();
-	void moveToPrev();
+	virtual void moveToPos(size_t which);
+	virtual void moveToNext();
+	virtual void moveToPrev();
 
 	size_t getPos();
 };
+
+class CListBoxWithCallback : public CListBox
+{
+public:
+	using MovedPosCallback = std::function<void(size_t)>;
+
+	CListBoxWithCallback(MovedPosCallback callback, CreateFunc create, Point pos, Point itemOffset, size_t visibleSize,
+		size_t totalSize, size_t initialPos = 0, int slider = 0, Rect sliderPos = Rect());
+	void scrollTo(size_t pos) override;
+	void moveToPos(size_t pos) override;
+	void moveToNext() override;
+	void moveToPrev() override;
+
+private:
+	MovedPosCallback movedPosCallback;
+};

+ 30 - 0
client/windows/CHeroBackpackWindow.cpp

@@ -0,0 +1,30 @@
+/*
+ * CHeroBackpackWindow.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 "CHeroBackpackWindow.h"
+
+#include "../gui/CGuiHandler.h"
+#include "../gui/Shortcut.h"
+
+#include "../widgets/Buttons.h"
+
+CHeroBackpackWindow::CHeroBackpackWindow(const CGHeroInstance * hero)
+	: CWindowObject(PLAYER_COLORED)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	
+	arts = std::make_shared<CArtifactsOfHeroBackpack>(Point(-100, -170));
+	arts->setHero(hero);
+	addSet(arts);
+
+	addCloseCallback(std::bind(&CHeroBackpackWindow::close, this));
+
+	quitButton = std::make_shared<CButton>(Point(242, 200), "hsbtns.def", CButton::tooltip(""), [this]() { close(); }, EShortcut::GLOBAL_RETURN);
+}

+ 23 - 0
client/windows/CHeroBackpackWindow.h

@@ -0,0 +1,23 @@
+/*
+ * CHeroBackpackWindow.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 "../widgets/CWindowWithArtifacts.h"
+#include "CWindowObject.h"
+
+class CHeroBackpackWindow : public CWindowObject, public CWindowWithArtifacts
+{
+public:
+	CHeroBackpackWindow(const CGHeroInstance * hero);
+	
+private:
+	std::shared_ptr<CArtifactsOfHeroBackpack> arts;
+	std::shared_ptr<CButton> quitButton;
+};

+ 6 - 0
client/windows/CHeroWindow.cpp

@@ -11,6 +11,7 @@
 #include "CHeroWindow.h"
 
 #include "CCreatureWindow.h"
+#include "CHeroBackpackWindow.h"
 #include "CKingdomInterface.h"
 #include "GUIClasses.h"
 
@@ -309,6 +310,11 @@ void CHeroWindow::dismissCurrent()
 	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[22], ony, nullptr);
 }
 
+void CHeroWindow::createBackpackWindow()
+{
+	GH.windows().createAndPushWindow<CHeroBackpackWindow>(curHero);
+}
+
 void CHeroWindow::commanderWindow()
 {
 	const auto pickedArtInst = getPickedArtifact();

+ 2 - 0
client/windows/CHeroWindow.h

@@ -85,6 +85,7 @@ class CHeroWindow : public CStatusbarWindow, public CGarrisonHolder, public CWin
 	std::shared_ptr<CTextBox> questlogLabel;
 	std::shared_ptr<CButton> questlogButton;
 	std::shared_ptr<CButton> commanderButton;
+	std::shared_ptr<CButton> backpackButton;
 
 	std::shared_ptr<CToggleButton> tacticsButton;
 	std::shared_ptr<CToggleGroup> formations;
@@ -105,6 +106,7 @@ public:
 	void commanderWindow();
 	void switchHero(); //changes displayed hero
 	void updateGarrisons() override;
+	void createBackpackWindow();
 
 	//friends
 	friend void CHeroArtPlace::clickPressed(const Point & cursorPosition);

+ 24 - 44
client/windows/CTradeWindow.cpp

@@ -180,24 +180,23 @@ void CTradeWindow::CTradeableItem::clickPressed(const Point & cursorPosition)
 			CAltarWindow *aw = static_cast<CAltarWindow *>(mw);
 			const auto pickedArtInst = aw->getPickedArtifact();
 
-			auto artifactsOfHero = std::dynamic_pointer_cast<CArtifactsOfHeroAltar>(aw->arts);
 			if(pickedArtInst)
 			{
-				artifactsOfHero->pickedArtMoveToAltar(ArtifactPosition::TRANSITION_POS);
+				aw->arts->pickedArtMoveToAltar(ArtifactPosition::TRANSITION_POS);
 				aw->moveArtToAltar(this->shared_from_this(), pickedArtInst);
 			}
 			else if(const CArtifactInstance *art = getArtInstance())
 			{
-				const auto hero = artifactsOfHero->getHero();
+				const auto hero = aw->arts->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);
+				aw->arts->pickedArtFromSlot = slot;
+				aw->arts->artifactsOnAltar.erase(art);
 				setID(-1);
 				subtitle.clear();
-				aw->deal->block(!artifactsOfHero->artifactsOnAltar.size());
+				aw->deal->block(!aw->arts->artifactsOnAltar.size());
 			}
 
 			aw->calcTotalExp();
@@ -371,37 +370,12 @@ void CTradeWindow::initItems(bool Left)
 
 	if(Left && (itemsType[1] == ARTIFACT_TYPE || itemsType[1] == ARTIFACT_INSTANCE))
 	{
-		int xOffset = 0, yOffset = 0;
 		if(mode == EMarketMode::ARTIFACT_RESOURCE)
 		{
-			xOffset = -361;
-			yOffset = +46;
-
 			auto item = std::make_shared<CTradeableItem>(Point(137, 469), itemsType[Left], -1, 1, 0);
 			item->recActions &= ~(UPDATE | SHOWALL);
 			items[Left].push_back(item);
 		}
-		else //ARTIFACT_EXP
-		{
-			xOffset = -365;
-			yOffset = -12;
-		}
-
-		if(mode == EMarketMode::ARTIFACT_RESOURCE)
-		{
-			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
 	{
@@ -701,7 +675,13 @@ CMarketplaceWindow::CMarketplaceWindow(const IMarket * Market, const CGHeroInsta
 	}
 
 	titleLabel = std::make_shared<CLabel>(300, 27, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, title);
-
+	if(mode == EMarketMode::ARTIFACT_RESOURCE)
+	{
+		arts = std::make_shared<CArtifactsOfHeroMarket>(Point(-361, 46));
+		arts->selectArtCallback = std::bind(&CTradeWindow::artifactSelected, this, _1);
+		arts->setHero(hero);
+		addSet(arts);
+	}
 	initItems(false);
 	initItems(true);
 
@@ -1133,6 +1113,10 @@ CAltarWindow::CAltarWindow(const IMarket * Market, const CGHeroInstance * Hero,
 		sacrificeBackpack = std::make_shared<CButton>(Point(147, 520), "ALTEMBK.DEF", CGI->generaltexth->zelp[570], std::bind(&CAltarWindow::SacrificeBackpack,this));
 		sacrificeBackpack->block(hero->artifactsInBackpack.empty());
 
+		arts = std::make_shared<CArtifactsOfHeroAltar>(Point(-365, -12));
+		arts->setHero(hero);
+		addSet(arts);
+
 		initItems(true);
 		initItems(false);
 		artIcon = std::make_shared<CAnimImage>("ARTIFACT", 0, 0, 281, 442);
@@ -1234,15 +1218,14 @@ void CAltarWindow::makeDeal()
 	else
 	{
 		std::vector<ui32> positions;
-		auto artifactsOfHero = std::dynamic_pointer_cast<CArtifactsOfHeroAltar>(arts);
-		for(const CArtifactInstance * art : artifactsOfHero->artifactsOnAltar)
+		for(const CArtifactInstance * art : arts->artifactsOnAltar)
 		{
 			positions.push_back(hero->getSlotByInstance(art));
 		}
 		std::sort(positions.begin(), positions.end(), std::greater<>());
 
 		LOCPLINT->cb->trade(market, mode, positions, {}, {}, hero);
-		artifactsOfHero->artifactsOnAltar.clear();
+		arts->artifactsOnAltar.clear();
 
 		for(auto item : items[0])
 		{
@@ -1279,13 +1262,12 @@ void CAltarWindow::SacrificeAll()
 	}
 	else
 	{
-		auto artifactsOfHero = std::dynamic_pointer_cast<CArtifactsOfHeroAltar>(arts);
-		for(const auto & aw : artifactsOfHero->visibleArtSet.artifactsWorn)
+		for(const auto & aw : arts->visibleArtSet.artifactsWorn)
 		{
 			if(!aw.second.locked)
 				moveArtToAltar(nullptr, aw.second.artifact);
 		}
-		artifactsOfHero->updateWornSlots();
+		arts->updateWornSlots();
 		SacrificeBackpack();
 	}
 	redraw();
@@ -1445,10 +1427,9 @@ int CAltarWindow::firstFreeSlot()
 
 void CAltarWindow::SacrificeBackpack()
 {
-	auto artsAltar = std::dynamic_pointer_cast<CArtifactsOfHeroAltar>(arts);
-	while(!artsAltar->visibleArtSet.artifactsInBackpack.empty())
+	while(!arts->visibleArtSet.artifactsInBackpack.empty())
 	{
-		if(!putOnAltar(nullptr, artsAltar->visibleArtSet.artifactsInBackpack[0].artifact))
+		if(!putOnAltar(nullptr, arts->visibleArtSet.artifactsInBackpack[0].artifact))
 			break;
 	};
 	calcTotalExp();
@@ -1501,9 +1482,8 @@ 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));
 
-	auto artsAltar = std::dynamic_pointer_cast<CArtifactsOfHeroAltar>(arts);
-	artsAltar->artifactsOnAltar.insert(art);
-	artsAltar->deleteFromVisible(art);
+	arts->artifactsOnAltar.insert(art);
+	arts->deleteFromVisible(art);
 	altarSlot->setArtInstance(art);
 	altarSlot->subtitle = std::to_string(val);
 

+ 2 - 1
client/windows/CTradeWindow.h

@@ -67,7 +67,6 @@ public:
 	const IMarket * market;
 	const CGHeroInstance * hero;
 
-	std::shared_ptr<CArtifactsOfHeroBase> arts;
 	//all indexes: 1 = left, 0 = right
 	std::array<std::vector<std::shared_ptr<CTradeableItem>>, 2> items;
 
@@ -117,6 +116,7 @@ protected:
 class CMarketplaceWindow : public CTradeWindow
 {
 	std::shared_ptr<CLabel> titleLabel;
+	std::shared_ptr<CArtifactsOfHeroMarket> arts;
 
 	bool printButtonFor(EMarketMode::EMarketMode M) const;
 
@@ -155,6 +155,7 @@ public:
 	std::shared_ptr<CButton> sacrificeBackpack;
 	std::shared_ptr<CLabel> expToLevel;
 	std::shared_ptr<CLabel> expOnAltar;
+	std::shared_ptr<CArtifactsOfHeroAltar> arts;
 
 	CAltarWindow(const IMarket * Market, const CGHeroInstance * Hero, EMarketMode::EMarketMode Mode);
 	~CAltarWindow();