Browse Source

Merge pull request #3186 from SoundSSGood/quick-backpack-part1

Quick backpack window
Ivan Savenko 1 year ago
parent
commit
27d2fc0a13

+ 30 - 6
client/widgets/CArtifactHolder.cpp

@@ -139,7 +139,7 @@ void CCommanderArtPlace::clickPressed(const Point & cursorPosition)
 		LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.commanderWindow.artifactMessage"), [this]() { returnArtToHeroCallback(); }, []() {});
 }
 
-void CCommanderArtPlace::showPopupWindow(const Point & cursorPosition)
+void CCommanderArtPlace::showPopupWindow(const Point& cursorPosition)
 {
 	if(ourArt && text.size())
 		CArtPlace::showPopupWindow(cursorPosition);
@@ -180,16 +180,25 @@ bool CArtPlace::isSelected() const
 	return selection->visible;
 }
 
-void CHeroArtPlace::clickPressed(const Point & cursorPosition)
+void CArtPlace::clickPressed(const Point & cursorPosition)
 {
-	if(leftClickCallback)
-		leftClickCallback(*this);
+	if(clickPressedCallback)
+		clickPressedCallback(*this, cursorPosition);
 }
 
-void CHeroArtPlace::showPopupWindow(const Point & cursorPosition)
+void CArtPlace::showPopupWindow(const Point & cursorPosition)
 {
 	if(showPopupCallback)
-		showPopupCallback(*this);
+		showPopupCallback(*this, cursorPosition);
+}
+
+void CArtPlace::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
+{
+	if(!on)
+		return;
+
+	if(gestureCallback)
+		gestureCallback(*this, initialPosition);
 }
 
 void CArtPlace::showAll(Canvas & to)
@@ -216,6 +225,21 @@ void CArtPlace::setArtifact(const CArtifactInstance * art)
 	}
 }
 
+void CArtPlace::setClickPressedCallback(ClickFunctor callback)
+{
+	clickPressedCallback = callback;
+}
+
+void CArtPlace::setShowPopupCallback(ClickFunctor callback)
+{
+	showPopupCallback = callback;
+}
+
+void CArtPlace::setGestureCallback(ClickFunctor callback)
+{
+	gestureCallback = callback;
+}
+
 void CHeroArtPlace::addCombinedArtInfo(std::map<const CArtifact*, int> & arts)
 {
 	for(const auto & combinedArt : arts)

+ 14 - 9
client/widgets/CArtifactHolder.h

@@ -32,14 +32,24 @@ public:
 class CArtPlace : public LRClickableAreaWTextComp
 {
 public:
+	using ClickFunctor = std::function<void(CArtPlace&, const Point&)>;
+
+	ArtifactPosition slot;
+
 	CArtPlace(Point position, const CArtifactInstance * art = nullptr);
-	const CArtifactInstance* getArt();
+	const CArtifactInstance * getArt();
 	void lockSlot(bool on);
 	bool isLocked() const;
 	void selectSlot(bool on);
 	bool isSelected() const;
 	void showAll(Canvas & to) override;
 	void setArtifact(const CArtifactInstance * art);
+	void setClickPressedCallback(ClickFunctor callback);
+	void setShowPopupCallback(ClickFunctor callback);
+	void setGestureCallback(ClickFunctor callback);
+	void clickPressed(const Point & cursorPosition) override;
+	void showPopupWindow(const Point & cursorPosition) override;
+	void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
 
 protected:
 	std::shared_ptr<CAnimImage> image;
@@ -47,6 +57,9 @@ protected:
 	int imageIndex;
 	std::shared_ptr<CAnimImage> selection;
 	bool locked;
+	ClickFunctor clickPressedCallback;
+	ClickFunctor showPopupCallback;
+	ClickFunctor gestureCallback;
 
 	void setInternals(const CArtifactInstance * artInst);
 };
@@ -68,15 +81,7 @@ public:
 class CHeroArtPlace: public CArtPlace
 {
 public:
-	using ClickFunctor = std::function<void(CHeroArtPlace&)>;
-
-	ArtifactPosition slot;
-	ClickFunctor leftClickCallback;
-	ClickFunctor showPopupCallback;
-
 	CHeroArtPlace(Point position, const CArtifactInstance * art = nullptr);
-	void clickPressed(const Point & cursorPosition) override;
-	void showPopupWindow(const Point & cursorPosition) override;
 	void addCombinedArtInfo(std::map<const CArtifact*, int> & arts);
 };
 

+ 3 - 3
client/widgets/CArtifactsOfHeroAltar.cpp

@@ -23,8 +23,8 @@ CArtifactsOfHeroAltar::CArtifactsOfHeroAltar(const Point & position)
 	: visibleArtSet(ArtBearer::ArtBearer::HERO)
 {
 	init(
-		std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1), 
-		std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1), 
+		std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2),
+		std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2),
 		position,
 		std::bind(&CArtifactsOfHeroAltar::scrollBackpack, this, _1));
 	pickedArtFromSlot = ArtifactPosition::PRE_FIRST;
@@ -69,7 +69,7 @@ void CArtifactsOfHeroAltar::scrollBackpack(int offset)
 	redraw();
 }
 
-void CArtifactsOfHeroAltar::pickUpArtifact(CHeroArtPlace & artPlace)
+void CArtifactsOfHeroAltar::pickUpArtifact(CArtPlace & artPlace)
 {
 	if(const auto art = artPlace.getArt())
 	{

+ 1 - 1
client/widgets/CArtifactsOfHeroAltar.h

@@ -26,7 +26,7 @@ public:
 	void updateWornSlots() override;
 	void updateBackpackSlots() override;
 	void scrollBackpack(int offset) override;
-	void pickUpArtifact(CHeroArtPlace & artPlace);
+	void pickUpArtifact(CArtPlace & artPlace);
 	void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
 	void pickedArtMoveToAltar(const ArtifactPosition & slot);
 	void deleteFromVisible(const CArtifactInstance * artInst);

+ 148 - 40
client/widgets/CArtifactsOfHeroBackpack.cpp

@@ -17,37 +17,91 @@
 #include "ObjectLists.h"
 
 #include "../CPlayerInterface.h"
+#include "../../lib/ArtifactUtils.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/networkPacks/ArtifactLocation.h"
 
 #include "../../CCallback.h"
 
-CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack(const Point & position)
+CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack(size_t slotsColumnsMax, size_t slotsRowsMax)
+	: slotsColumnsMax(slotsColumnsMax)
+	, slotsRowsMax(slotsRowsMax)
 {
-	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
-	pos += position;
 	setRedrawParent(true);
+}
 
+CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack()
+	: CArtifactsOfHeroBackpack(8, 8)
+{
 	const auto backpackCap = VLC->settings()->getInteger(EGameSettings::HEROES_BACKPACK_CAP);
-	auto visibleCapacityMax = HERO_BACKPACK_WINDOW_SLOT_ROWS * HERO_BACKPACK_WINDOW_SLOT_COLUMNS;
+	auto visibleCapacityMax = slotsRowsMax * slotsColumnsMax;
 	if(backpackCap >= 0)
 		visibleCapacityMax = visibleCapacityMax > backpackCap ? backpackCap : visibleCapacityMax;
 
-	backpack.resize(visibleCapacityMax);
+	initAOHbackpack(visibleCapacityMax, backpackCap < 0 || visibleCapacityMax < backpackCap);
+}
+
+void CArtifactsOfHeroBackpack::swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc)
+{
+	LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc);
+}
+
+void CArtifactsOfHeroBackpack::pickUpArtifact(CArtPlace & artPlace)
+{
+	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, artPlace.slot),
+		ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS));
+}
+
+void CArtifactsOfHeroBackpack::scrollBackpack(int offset)
+{
+	if(backpackListBox)
+		backpackListBox->resize(getActiveSlotRowsNum());
+	backpackPos += offset;
+	auto slot = ArtifactPosition::BACKPACK_START + backpackPos;
+	for(auto artPlace : backpack)
+	{
+		setSlotData(artPlace, slot, *curHero);
+		slot = slot + 1;
+	}
+	redraw();
+}
+
+void CArtifactsOfHeroBackpack::updateBackpackSlots()
+{
+	if(backpackListBox)
+		backpackListBox->resize(getActiveSlotRowsNum());
+	CArtifactsOfHeroBase::updateBackpackSlots();
+}
+
+size_t CArtifactsOfHeroBackpack::getActiveSlotRowsNum()
+{
+	return (curHero->artifactsInBackpack.size() + slotsColumnsMax - 1) / slotsColumnsMax;
+}
+
+size_t CArtifactsOfHeroBackpack::getSlotsNum()
+{
+	return backpack.size();
+}
+
+void CArtifactsOfHeroBackpack::initAOHbackpack(size_t slots, bool slider)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
+
+	backpack.resize(slots);
 	size_t artPlaceIdx = 0;
 	for(auto & artPlace : backpack)
 	{
-		const auto pos = Point(slotSizeWithMargin * (artPlaceIdx % HERO_BACKPACK_WINDOW_SLOT_COLUMNS),
-			slotSizeWithMargin * (artPlaceIdx / HERO_BACKPACK_WINDOW_SLOT_COLUMNS));
+		const auto pos = Point(slotSizeWithMargin * (artPlaceIdx % slotsColumnsMax),
+			slotSizeWithMargin * (artPlaceIdx / slotsColumnsMax));
 		backpackSlotsBackgrounds.emplace_back(std::make_shared<CPicture>(ImagePath::builtin("heroWindow/artifactSlotEmpty"), pos));
 		artPlace = std::make_shared<CHeroArtPlace>(pos);
 		artPlace->setArtifact(nullptr);
-		artPlace->leftClickCallback = std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1);
-		artPlace->showPopupCallback = std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1);
+		artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
+		artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
 		artPlaceIdx++;
 	}
 
-	if(backpackCap < 0 || visibleCapacityMax < backpackCap)
+	if(slider)
 	{
 		auto onCreate = [](size_t index) -> std::shared_ptr<CIntObject>
 		{
@@ -55,57 +109,111 @@ CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack(const Point & position)
 		};
 		CListBoxWithCallback::MovedPosCallback posMoved = [this](size_t pos) -> void
 		{
-			scrollBackpack(static_cast<int>(pos) * HERO_BACKPACK_WINDOW_SLOT_COLUMNS - backpackPos);
+			scrollBackpack(static_cast<int>(pos) * slotsColumnsMax - backpackPos);
 		};
 		backpackListBox = std::make_shared<CListBoxWithCallback>(
-				posMoved, onCreate, Point(0, 0), Point(0, 0), HERO_BACKPACK_WINDOW_SLOT_ROWS, 0, 0, 1,
-				Rect(HERO_BACKPACK_WINDOW_SLOT_COLUMNS * slotSizeWithMargin + sliderPosOffsetX, 0, HERO_BACKPACK_WINDOW_SLOT_ROWS * slotSizeWithMargin - 2, 0));
+			posMoved, onCreate, Point(0, 0), Point(0, 0), slotsRowsMax, 0, 0, 1,
+			Rect(slotsColumnsMax * slotSizeWithMargin + sliderPosOffsetX, 0, slotsRowsMax * slotSizeWithMargin - 2, 0));
 	}
 
-	pos.w = visibleCapacityMax > HERO_BACKPACK_WINDOW_SLOT_COLUMNS ? HERO_BACKPACK_WINDOW_SLOT_COLUMNS : visibleCapacityMax;
+	pos.w = slots > slotsColumnsMax ? slotsColumnsMax : slots;
 	pos.w *= slotSizeWithMargin;
-	if(backpackListBox)
+	if(slider)
 		pos.w += sliderPosOffsetX + 16; // 16 is slider width. TODO: get it from CListBox directly;
-
-	pos.h = (visibleCapacityMax / HERO_BACKPACK_WINDOW_SLOT_COLUMNS);
-	if(visibleCapacityMax % HERO_BACKPACK_WINDOW_SLOT_COLUMNS != 0)
-		pos.h += 1;
-	pos.h *= slotSizeWithMargin;
+	pos.h = calcRows(slots) * slotSizeWithMargin;
 }
 
-void CArtifactsOfHeroBackpack::swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc)
+size_t CArtifactsOfHeroBackpack::calcRows(size_t slots)
 {
-	LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc);
+	size_t rows = 0;
+	if(slotsColumnsMax != 0)
+	{
+		rows = slots / slotsColumnsMax;
+		if(slots % slotsColumnsMax != 0)
+			rows += 1;
+	}
+	return rows;
 }
 
-void CArtifactsOfHeroBackpack::pickUpArtifact(CHeroArtPlace & artPlace)
+CArtifactsOfHeroQuickBackpack::CArtifactsOfHeroQuickBackpack(const ArtifactPosition filterBySlot)
+	: CArtifactsOfHeroBackpack(0, 0)
 {
-	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, artPlace.slot),
-		ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS));
+	assert(filterBySlot != ArtifactPosition::FIRST_AVAILABLE);
+
+	if(!ArtifactUtils::isSlotEquipment(filterBySlot))
+		return;
+
+	this->filterBySlot = filterBySlot;
 }
 
-void CArtifactsOfHeroBackpack::scrollBackpack(int offset)
+void CArtifactsOfHeroQuickBackpack::setHero(const CGHeroInstance * hero)
 {
-	if(backpackListBox)
-		backpackListBox->resize(getActiveSlotRowsNum());
-	backpackPos += offset;
-	auto slot = ArtifactPosition::BACKPACK_START + backpackPos;
-	for(auto artPlace : backpack)
+	if(curHero == hero)
+		return;
+	
+	curHero = hero;
+	if(curHero)
 	{
-		setSlotData(artPlace, slot, *curHero);
-		slot = slot + 1;
+		ArtifactID artInSlotId = ArtifactID::NONE;
+		SpellID scrollInSlotSpellId = SpellID::NONE;
+		if(auto artInSlot = curHero->getArt(filterBySlot))
+		{
+			artInSlotId = artInSlot->getTypeId();
+			scrollInSlotSpellId = artInSlot->getScrollSpellID();
+		}
+
+		std::map<const ArtifactID, const CArtifactInstance*> filteredArts;
+		for(auto & slotInfo : curHero->artifactsInBackpack)
+			if(slotInfo.artifact->getTypeId() != artInSlotId &&	!slotInfo.artifact->isScroll() &&
+				slotInfo.artifact->artType->canBePutAt(curHero, filterBySlot, true))
+			{
+				filteredArts.insert(std::pair(slotInfo.artifact->getTypeId(), slotInfo.artifact));
+			}
+
+		std::map<const SpellID, const CArtifactInstance*> filteredScrolls;
+		if(filterBySlot == ArtifactPosition::MISC1 || filterBySlot == ArtifactPosition::MISC2 || filterBySlot == ArtifactPosition::MISC3 ||
+			filterBySlot == ArtifactPosition::MISC4 || filterBySlot == ArtifactPosition::MISC5)
+		{
+			for(auto & slotInfo : curHero->artifactsInBackpack)
+			{
+				if(slotInfo.artifact->isScroll() && slotInfo.artifact->getScrollSpellID() != scrollInSlotSpellId)
+					filteredScrolls.insert(std::pair(slotInfo.artifact->getScrollSpellID(), slotInfo.artifact));
+			}
+		}
+
+		backpack.clear();
+		auto requiredSlots = filteredArts.size() + filteredScrolls.size();
+		slotsColumnsMax = ceilf(sqrtf(requiredSlots));
+		slotsRowsMax = calcRows(requiredSlots);
+		initAOHbackpack(requiredSlots, false);
+		auto artPlace = backpack.begin();
+		for(auto & art : filteredArts)
+			setSlotData(*artPlace++, curHero->getSlotByInstance(art.second), *curHero);
+		for(auto & art : filteredScrolls)
+			setSlotData(*artPlace++, curHero->getSlotByInstance(art.second), *curHero);
 	}
-	redraw();
 }
 
-void CArtifactsOfHeroBackpack::updateBackpackSlots()
+ArtifactPosition CArtifactsOfHeroQuickBackpack::getFilterSlot()
 {
-	if(backpackListBox)
-		backpackListBox->resize(getActiveSlotRowsNum());
-	CArtifactsOfHeroBase::updateBackpackSlots();
+	return filterBySlot;
 }
 
-size_t CArtifactsOfHeroBackpack::getActiveSlotRowsNum()
+void CArtifactsOfHeroQuickBackpack::selectSlotAt(const Point & position)
 {
-	return (curHero->artifactsInBackpack.size() + HERO_BACKPACK_WINDOW_SLOT_COLUMNS - 1) / HERO_BACKPACK_WINDOW_SLOT_COLUMNS;
+	for(auto & artPlace : backpack)
+		artPlace->selectSlot(artPlace->pos.isInside(position));
 }
+
+void CArtifactsOfHeroQuickBackpack::swapSelected()
+{
+	ArtifactLocation backpackLoc(curHero->id, ArtifactPosition::PRE_FIRST);
+	for(auto & artPlace : backpack)
+		if(artPlace->isSelected())
+		{
+			backpackLoc.slot = artPlace->slot;
+			break;
+		}
+	if(backpackLoc.slot != ArtifactPosition::PRE_FIRST && filterBySlot != ArtifactPosition::PRE_FIRST && curHero)
+		swapArtifacts(backpackLoc, ArtifactLocation(curHero->id, filterBySlot));
+}

+ 24 - 6
client/widgets/CArtifactsOfHeroBackpack.h

@@ -22,18 +22,36 @@ class CListBoxWithCallback;
 class CArtifactsOfHeroBackpack : public CArtifactsOfHeroBase
 {
 public:
-	CArtifactsOfHeroBackpack(const Point & position);
+	CArtifactsOfHeroBackpack(size_t slotsColumnsMax, size_t slotsRowsMax);
+	CArtifactsOfHeroBackpack();
 	void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
-	void pickUpArtifact(CHeroArtPlace & artPlace);
+	void pickUpArtifact(CArtPlace & artPlace);
 	void scrollBackpack(int offset) override;
 	void updateBackpackSlots() override;
 	size_t getActiveSlotRowsNum();
+	size_t getSlotsNum();
 
-private:
+protected:
 	std::shared_ptr<CListBoxWithCallback> backpackListBox;
 	std::vector<std::shared_ptr<CPicture>> backpackSlotsBackgrounds;
-	const size_t HERO_BACKPACK_WINDOW_SLOT_COLUMNS = 8;
-	const size_t HERO_BACKPACK_WINDOW_SLOT_ROWS = 8;
+	size_t slotsColumnsMax;
+	size_t slotsRowsMax;
 	const int slotSizeWithMargin = 46;
-	const int sliderPosOffsetX = 10;
+	const int sliderPosOffsetX = 5;
+
+	void initAOHbackpack(size_t slots, bool slider);
+	size_t calcRows(size_t slots);
+};
+
+class CArtifactsOfHeroQuickBackpack : public CArtifactsOfHeroBackpack
+{
+public:
+	CArtifactsOfHeroQuickBackpack(const ArtifactPosition filterBySlot);
+	void setHero(const CGHeroInstance * hero);
+	ArtifactPosition getFilterSlot();
+	void selectSlotAt(const Point & position);
+	void swapSelected();
+
+private:
+	ArtifactPosition filterBySlot;
 };

+ 26 - 11
client/widgets/CArtifactsOfHeroBase.cpp

@@ -56,8 +56,8 @@ void CArtifactsOfHeroBase::setPutBackPickedArtifactCallback(PutBackPickedArtCall
 }
 
 void CArtifactsOfHeroBase::init(
-	CHeroArtPlace::ClickFunctor lClickCallback,
-	CHeroArtPlace::ClickFunctor showPopupCallback,
+	CArtPlace::ClickFunctor lClickCallback,
+	CArtPlace::ClickFunctor showPopupCallback,
 	const Point & position,
 	BpackScrollFunctor scrollCallback)
 {
@@ -78,14 +78,14 @@ void CArtifactsOfHeroBase::init(
 	{
 		artPlace.second->slot = artPlace.first;
 		artPlace.second->setArtifact(nullptr);
-		artPlace.second->leftClickCallback = lClickCallback;
-		artPlace.second->showPopupCallback = showPopupCallback;
+		artPlace.second->setClickPressedCallback(lClickCallback);
+		artPlace.second->setShowPopupCallback(showPopupCallback);
 	}
 	for(auto artPlace : backpack)
 	{
 		artPlace->setArtifact(nullptr);
-		artPlace->leftClickCallback = lClickCallback;
-		artPlace->showPopupCallback = showPopupCallback;
+		artPlace->setClickPressedCallback(lClickCallback);
+		artPlace->setShowPopupCallback(showPopupCallback);
 	}
 	leftBackpackRoll = std::make_shared<CButton>(Point(379, 364), AnimationPath::builtin("hsbtns3.def"), CButton::tooltip(), [scrollCallback]() {scrollCallback(-1);}, EShortcut::MOVE_LEFT);
 	rightBackpackRoll = std::make_shared<CButton>(Point(632, 364), AnimationPath::builtin("hsbtns5.def"), CButton::tooltip(), [scrollCallback]() {scrollCallback(+1);}, EShortcut::MOVE_RIGHT);
@@ -95,16 +95,22 @@ void CArtifactsOfHeroBase::init(
 	setRedrawParent(true);
 }
 
-void CArtifactsOfHeroBase::leftClickArtPlace(CHeroArtPlace & artPlace)
+void CArtifactsOfHeroBase::clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition)
 {
-	if(leftClickCallback)
-		leftClickCallback(*this, artPlace);
+	if(clickPressedCallback)
+		clickPressedCallback(*this, artPlace, cursorPosition);
 }
 
-void CArtifactsOfHeroBase::rightClickArtPlace(CHeroArtPlace & artPlace)
+void CArtifactsOfHeroBase::showPopupArtPlace(CArtPlace & artPlace, const Point & cursorPosition)
 {
 	if(showPopupCallback)
-		showPopupCallback(*this, artPlace);
+		showPopupCallback(*this, artPlace, cursorPosition);
+}
+
+void CArtifactsOfHeroBase::gestureArtPlace(CArtPlace & artPlace, const Point & cursorPosition)
+{
+	if(gestureCallback)
+		gestureCallback(*this, artPlace, cursorPosition);
 }
 
 void CArtifactsOfHeroBase::setHero(const CGHeroInstance * hero)
@@ -241,6 +247,15 @@ const CArtifactInstance * CArtifactsOfHeroBase::getPickedArtifact()
 		return curHero->getArt(ArtifactPosition::TRANSITION_POS);
 }
 
+void CArtifactsOfHeroBase::addGestureCallback(CArtPlace::ClickFunctor callback)
+{
+	for(auto & artPlace : artWorn)
+	{
+		artPlace.second->setGestureCallback(callback);
+		artPlace.second->addUsedEvents(GESTURE);
+	}
+}
+
 void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosition & slot, const CArtifactSet & artSet)
 {
 	// Spurious call from artifactMoved in attempt to update hidden backpack slot

+ 7 - 4
client/widgets/CArtifactsOfHeroBase.h

@@ -21,17 +21,19 @@ protected:
 
 public:
 	using ArtPlaceMap = std::map<ArtifactPosition, ArtPlacePtr>;
-	using ClickFunctor = std::function<void(CArtifactsOfHeroBase&, CHeroArtPlace&)>;
+	using ClickFunctor = std::function<void(CArtifactsOfHeroBase&, CArtPlace&, const Point&)>;
 	using PutBackPickedArtCallback = std::function<void()>;
 
-	ClickFunctor leftClickCallback;
+	ClickFunctor clickPressedCallback;
 	ClickFunctor showPopupCallback;
+	ClickFunctor gestureCallback;
 	
 	CArtifactsOfHeroBase();
 	virtual void putBackPickedArtifact();
 	virtual void setPutBackPickedArtifactCallback(PutBackPickedArtCallback callback);
-	virtual void leftClickArtPlace(CHeroArtPlace & artPlace);
-	virtual void rightClickArtPlace(CHeroArtPlace & artPlace);
+	virtual void clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition);
+	virtual void showPopupArtPlace(CArtPlace & artPlace, const Point & cursorPosition);
+	virtual void gestureArtPlace(CArtPlace & artPlace, const Point & cursorPosition);
 	virtual void setHero(const CGHeroInstance * hero);
 	virtual const CGHeroInstance * getHero() const;
 	virtual void scrollBackpack(int offset);
@@ -42,6 +44,7 @@ public:
 	virtual void updateBackpackSlots();
 	virtual void updateSlot(const ArtifactPosition & slot);
 	virtual const CArtifactInstance * getPickedArtifact();
+	void addGestureCallback(CArtPlace::ClickFunctor callback);
 
 protected:
 	const CGHeroInstance * curHero;

+ 6 - 5
client/widgets/CArtifactsOfHeroKingdom.cpp

@@ -30,14 +30,15 @@ CArtifactsOfHeroKingdom::CArtifactsOfHeroKingdom(ArtPlaceMap ArtWorn, std::vecto
 	{
 		artPlace.second->slot = artPlace.first;
 		artPlace.second->setArtifact(nullptr);
-		artPlace.second->leftClickCallback = std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1);
-		artPlace.second->showPopupCallback = std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1);
+		artPlace.second->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
+		artPlace.second->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
 	}
+	addGestureCallback(std::bind(&CArtifactsOfHeroBase::gestureArtPlace, this, _1, _2));
 	for(auto artPlace : backpack)
 	{
 		artPlace->setArtifact(nullptr);
-		artPlace->leftClickCallback = std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1);
-		artPlace->showPopupCallback = std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1);
+		artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
+		artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
 	}
 	leftBackpackRoll->addCallback(std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, -1));
 	rightBackpackRoll->addCallback(std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, +1));
@@ -55,7 +56,7 @@ void CArtifactsOfHeroKingdom::swapArtifacts(const ArtifactLocation & srcLoc, con
 	LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc);
 }
 
-void CArtifactsOfHeroKingdom::pickUpArtifact(CHeroArtPlace & artPlace)
+void CArtifactsOfHeroKingdom::pickUpArtifact(CArtPlace & artPlace)
 {
 	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, artPlace.slot),
 		ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS));

+ 1 - 1
client/widgets/CArtifactsOfHeroKingdom.h

@@ -24,5 +24,5 @@ public:
 		std::shared_ptr<CButton> leftScroll, std::shared_ptr<CButton> rightScroll);
 	~CArtifactsOfHeroKingdom();
 	void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
-	void pickUpArtifact(CHeroArtPlace & artPlace);
+	void pickUpArtifact(CArtPlace & artPlace);
 };

+ 4 - 3
client/widgets/CArtifactsOfHeroMain.cpp

@@ -19,10 +19,11 @@
 CArtifactsOfHeroMain::CArtifactsOfHeroMain(const Point & position)
 {
 	init(
-		std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1),
-		std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1),
+		std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2),
+		std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2),
 		position,
 		std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1));
+	addGestureCallback(std::bind(&CArtifactsOfHeroBase::gestureArtPlace, this, _1, _2));
 }
 
 CArtifactsOfHeroMain::~CArtifactsOfHeroMain()
@@ -35,7 +36,7 @@ void CArtifactsOfHeroMain::swapArtifacts(const ArtifactLocation & srcLoc, const
 	LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc);
 }
 
-void CArtifactsOfHeroMain::pickUpArtifact(CHeroArtPlace & artPlace)
+void CArtifactsOfHeroMain::pickUpArtifact(CArtPlace & artPlace)
 {
 	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, artPlace.slot),
 		ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS));

+ 1 - 1
client/widgets/CArtifactsOfHeroMain.h

@@ -23,5 +23,5 @@ public:
 	CArtifactsOfHeroMain(const Point & position);
 	~CArtifactsOfHeroMain();
 	void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
-	void pickUpArtifact(CHeroArtPlace & artPlace);
+	void pickUpArtifact(CArtPlace & artPlace);
 };

+ 2 - 2
client/widgets/CArtifactsOfHeroMarket.cpp

@@ -15,8 +15,8 @@
 CArtifactsOfHeroMarket::CArtifactsOfHeroMarket(const Point & position)
 {
 	init(
-		std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1), 
-		std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1), 
+		std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2),
+		std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2),
 		position,
 		std::bind(&CArtifactsOfHeroMarket::scrollBackpack, this, _1));
 };

+ 1 - 1
client/widgets/CArtifactsOfHeroMarket.h

@@ -14,7 +14,7 @@
 class CArtifactsOfHeroMarket : public CArtifactsOfHeroBase
 {
 public:
-	std::function<void(CHeroArtPlace*)> selectArtCallback;
+	std::function<void(CArtPlace*)> selectArtCallback;
 
 	CArtifactsOfHeroMarket(const Point & position);
 	void scrollBackpack(int offset) override;

+ 46 - 12
client/widgets/CWindowWithArtifacts.cpp

@@ -23,6 +23,7 @@
 #include "../windows/CHeroWindow.h"
 #include "../windows/CSpellWindow.h"
 #include "../windows/GUIClasses.h"
+#include "../windows/CHeroBackpackWindow.h"
 #include "../CPlayerInterface.h"
 #include "../CGameInfo.h"
 
@@ -39,18 +40,19 @@ void CWindowWithArtifacts::addSet(CArtifactsOfHeroPtr artSet)
 
 void CWindowWithArtifacts::addSetAndCallbacks(CArtifactsOfHeroPtr artSet)
 {
-	CArtifactsOfHeroBase::PutBackPickedArtCallback artPutBackHandler = []() -> void
+	CArtifactsOfHeroBase::PutBackPickedArtCallback artPutBackFunctor = []() -> void
 	{
 		CCS->curh->dragAndDropCursor(nullptr);
 	};
 
 	addSet(artSet);
-	std::visit([this, artPutBackHandler](auto artSetWeak)
+	std::visit([this, artPutBackFunctor](auto artSetWeak)
 		{
 			auto artSet = artSetWeak.lock();
-			artSet->leftClickCallback = std::bind(&CWindowWithArtifacts::leftClickArtPlaceHero, this, _1, _2);
-			artSet->showPopupCallback = std::bind(&CWindowWithArtifacts::rightClickArtPlaceHero, this, _1, _2);
-			artSet->setPutBackPickedArtifactCallback(artPutBackHandler);
+			artSet->clickPressedCallback = std::bind(&CWindowWithArtifacts::clickPressedArtPlaceHero, this, _1, _2, _3);
+			artSet->showPopupCallback = std::bind(&CWindowWithArtifacts::showPopupArtPlaceHero, this, _1, _2, _3);
+			artSet->gestureCallback = std::bind(&CWindowWithArtifacts::gestureArtPlaceHero, this, _1, _2, _3);
+			artSet->setPutBackPickedArtifactCallback(artPutBackFunctor);
 		}, artSet);
 }
 
@@ -77,7 +79,7 @@ const CArtifactInstance * CWindowWithArtifacts::getPickedArtifact()
 		return nullptr;
 }
 
-void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst, CHeroArtPlace & artPlace)
+void CWindowWithArtifacts::clickPressedArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition)
 {
 	const auto artSetWeak = findAOHbyRef(artsInst);
 	assert(artSetWeak.has_value());
@@ -85,7 +87,7 @@ void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst
 	if(artPlace.isLocked())
 		return;
 
-	const auto checkSpecialArts = [](const CGHeroInstance * hero, CHeroArtPlace & artPlace) -> bool
+	const auto checkSpecialArts = [](const CGHeroInstance * hero, CArtPlace & artPlace) -> bool
 	{
 		if(artPlace.getArt()->getTypeId() == ArtifactID::SPELLBOOK)
 		{
@@ -206,10 +208,18 @@ void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst
 					}
 				}
 			}
+			else if constexpr(std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroQuickBackpack>>)
+			{
+				const auto hero = artSetPtr->getHero();
+				artSetPtr->swapArtifacts(ArtifactLocation(hero->id, artPlace.slot),
+					ArtifactLocation(hero->id, artSetPtr->getFilterSlot()));
+				if(closeCallback)
+					closeCallback();
+			}
 		}, artSetWeak.value());
 }
 
-void CWindowWithArtifacts::rightClickArtPlaceHero(CArtifactsOfHeroBase & artsInst, CHeroArtPlace & artPlace)
+void CWindowWithArtifacts::showPopupArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition)
 {
 	const auto artSetWeak = findAOHbyRef(artsInst);
 	assert(artSetWeak.has_value());
@@ -218,7 +228,7 @@ void CWindowWithArtifacts::rightClickArtPlaceHero(CArtifactsOfHeroBase & artsIns
 		return;
 
 	std::visit(
-		[&artPlace](auto artSetWeak) -> void
+		[&artPlace, &cursorPosition](auto artSetWeak) -> void
 		{
 			const auto artSetPtr = artSetWeak.lock();
 
@@ -239,16 +249,40 @@ void CWindowWithArtifacts::rightClickArtPlaceHero(CArtifactsOfHeroBase & artsIns
 						return;
 					}
 					if(artPlace.text.size())
-						artPlace.LRClickableAreaWTextComp::showPopupWindow(GH.getCursorPosition());
+						artPlace.LRClickableAreaWTextComp::showPopupWindow(cursorPosition);
 				}
 			}
 			// 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>>)
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMarket>> ||
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroQuickBackpack>>)
 			{
 				if(artPlace.getArt() && artPlace.text.size())
-					artPlace.LRClickableAreaWTextComp::showPopupWindow(GH.getCursorPosition());
+					artPlace.LRClickableAreaWTextComp::showPopupWindow(cursorPosition);
+			}
+		}, artSetWeak.value());
+}
+
+void CWindowWithArtifacts::gestureArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition)
+{
+	const auto artSetWeak = findAOHbyRef(artsInst);
+	assert(artSetWeak.has_value());
+	if(artPlace.isLocked())
+		return;
+
+	std::visit(
+		[&artPlace, cursorPosition](auto artSetWeak) -> void
+		{
+			const auto artSetPtr = artSetWeak.lock();
+			if constexpr(
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMain>> ||
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroKingdom>>)
+			{
+				GH.windows().createAndPushWindow<CHeroQuickBackpackWindow>(artSetPtr->getHero(), artPlace.slot);
+				auto backpackWindow = GH.windows().topWindow<CHeroQuickBackpackWindow>();
+				backpackWindow->moveTo(cursorPosition - Point(1, 1));
+				backpackWindow->fitToScreen(15);
 			}
 		}, artSetWeak.value());
 }

+ 5 - 3
client/widgets/CWindowWithArtifacts.h

@@ -24,7 +24,8 @@ public:
 		std::weak_ptr<CArtifactsOfHeroAltar>,
 		std::weak_ptr<CArtifactsOfHeroKingdom>,
 		std::weak_ptr<CArtifactsOfHeroMain>,
-		std::weak_ptr<CArtifactsOfHeroBackpack>>;
+		std::weak_ptr<CArtifactsOfHeroBackpack>,
+		std::weak_ptr<CArtifactsOfHeroQuickBackpack>>;
 	using CloseCallback = std::function<void()>;
 
 	void addSet(CArtifactsOfHeroPtr artSet);
@@ -32,8 +33,9 @@ public:
 	void addCloseCallback(CloseCallback callback);
 	const CGHeroInstance * getHeroPickedArtifact();
 	const CArtifactInstance * getPickedArtifact();
-	void leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst, CHeroArtPlace & artPlace);
-	void rightClickArtPlaceHero(CArtifactsOfHeroBase & artsInst, CHeroArtPlace & artPlace);
+	void clickPressedArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition);
+	void showPopupArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition);
+	void gestureArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition);
 
 	void artifactRemoved(const ArtifactLocation & artLoc) override;
 	void artifactMoved(const ArtifactLocation & srcLoc, const ArtifactLocation & destLoc, bool withRedraw) override;

+ 51 - 14
client/windows/CHeroBackpackWindow.cpp

@@ -22,25 +22,19 @@
 CHeroBackpackWindow::CHeroBackpackWindow(const CGHeroInstance * hero)
 	: CWindowObject((EOptions)0)
 {
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
 
-	stretchedBackground = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, 410, 425));
-
-	arts = std::make_shared<CArtifactsOfHeroBackpack>(Point(windowMargin, windowMargin));
-	arts->setHero(hero);
+	stretchedBackground = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, 0, 0));
+	arts = std::make_shared<CArtifactsOfHeroBackpack>();
+	arts->moveBy(Point(windowMargin, windowMargin));
 	addSetAndCallbacks(arts);
-
+	arts->setHero(hero);
 	addCloseCallback(std::bind(&CHeroBackpackWindow::close, this));
-
 	quitButton = std::make_shared<CButton>(Point(), AnimationPath::builtin("IOKAY32.def"), CButton::tooltip(""), [this]() { close(); }, EShortcut::GLOBAL_RETURN);
-
-	stretchedBackground->pos.w = arts->pos.w + 2 * windowMargin;
-	stretchedBackground->pos.h = arts->pos.h + quitButton->pos.h + 3 * windowMargin;
-	pos.w = stretchedBackground->pos.w;
-	pos.h = stretchedBackground->pos.h;
+	pos.w = stretchedBackground->pos.w = arts->pos.w + 2 * windowMargin;
+	pos.h = stretchedBackground->pos.h = arts->pos.h + quitButton->pos.h + 3 * windowMargin;
+	quitButton->moveTo(Point(pos.x + pos.w / 2 - quitButton->pos.w / 2, pos.y + arts->pos.h + 2 * windowMargin));
 	center();
-
-	quitButton->moveBy(Point(GH.screenDimensions().x / 2 - quitButton->pos.w / 2 - quitButton->pos.x, arts->pos.h + 2 * windowMargin));
 }
 
 void CHeroBackpackWindow::showAll(Canvas & to)
@@ -48,3 +42,46 @@ void CHeroBackpackWindow::showAll(Canvas & to)
 	CIntObject::showAll(to);
 	CMessage::drawBorder(PlayerColor(LOCPLINT->playerID), to.getInternalSurface(), pos.w+28, pos.h+29, pos.x-14, pos.y-15);
 }
+
+CHeroQuickBackpackWindow::CHeroQuickBackpackWindow(const CGHeroInstance * hero, ArtifactPosition targetSlot)
+	: CWindowObject((EOptions)0)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
+
+	stretchedBackground = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, 0, 0));
+	arts = std::make_shared<CArtifactsOfHeroQuickBackpack>(targetSlot);
+	arts->moveBy(Point(windowMargin, windowMargin));
+	addSetAndCallbacks(static_cast<std::weak_ptr<CArtifactsOfHeroQuickBackpack>>(arts));
+	arts->setHero(hero);
+	addCloseCallback(std::bind(&CHeroQuickBackpackWindow::close, this));
+	addUsedEvents(GESTURE);
+	pos.w = stretchedBackground->pos.w = arts->pos.w + 2 * windowMargin;
+	pos.h = stretchedBackground->pos.h = arts->pos.h + windowMargin;
+}
+
+void CHeroQuickBackpackWindow::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
+{
+	if(on)
+		return;
+
+	arts->swapSelected();
+	close();
+}
+
+void CHeroQuickBackpackWindow::gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance)
+{
+	arts->selectSlotAt(currentPosition);
+	redraw();
+}
+
+void CHeroQuickBackpackWindow::showAll(Canvas & to)
+{
+	if(arts->getSlotsNum() == 0)
+	{
+		// Dirty solution for closing that window
+		close();
+		return;
+	}
+	CMessage::drawBorder(PlayerColor(LOCPLINT->playerID), to.getInternalSurface(), pos.w + 28, pos.h + 29, pos.x - 14, pos.y - 15);
+	CIntObject::showAll(to);
+}

+ 17 - 2
client/windows/CHeroBackpackWindow.h

@@ -19,11 +19,26 @@ class CHeroBackpackWindow : public CWindowObject, public CWindowWithArtifacts
 public:
 	CHeroBackpackWindow(const CGHeroInstance * hero);
 	
-private:
+protected:
 	std::shared_ptr<CArtifactsOfHeroBackpack> arts;
 	std::shared_ptr<CButton> quitButton;
 	std::shared_ptr<CFilledTexture> stretchedBackground;
-	const int windowMargin = 10;
+	const int windowMargin = 5;
+
+	void showAll(Canvas & to) override;
+};
+
+class CHeroQuickBackpackWindow : public CWindowObject, public CWindowWithArtifacts
+{
+public:
+	CHeroQuickBackpackWindow(const CGHeroInstance * hero, ArtifactPosition targetSlot);
+	void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
+	void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override;
+
+private:
+	std::shared_ptr<CArtifactsOfHeroQuickBackpack> arts;
+	std::shared_ptr<CFilledTexture> stretchedBackground;
+	const int windowMargin = 5;
 
 	void showAll(Canvas & to) override;
 };

+ 0 - 1
client/windows/CHeroWindow.h

@@ -111,6 +111,5 @@ public:
 	void createBackpackWindow();
 
 	//friends
-	friend void CHeroArtPlace::clickPressed(const Point & cursorPosition);
 	friend class CPlayerInterface;
 };

+ 1 - 1
client/windows/CTradeWindow.cpp

@@ -331,7 +331,7 @@ void CTradeWindow::setMode(EMarketMode Mode)
 	}
 }
 
-void CTradeWindow::artifactSelected(CHeroArtPlace *slot)
+void CTradeWindow::artifactSelected(CArtPlace * slot)
 {
 	assert(mode == EMarketMode::ARTIFACT_RESOURCE);
 	items[1][0]->setArtInstance(slot->getArt());

+ 1 - 1
client/windows/CTradeWindow.h

@@ -40,7 +40,7 @@ public:
 	void getPositionsFor(std::vector<Rect> &poss, bool Left, EType type) const;
 	void setMode(EMarketMode Mode); //mode setter
 
-	void artifactSelected(CHeroArtPlace *slot); //used when selling artifacts -> called when user clicked on artifact slot
+	void artifactSelected(CArtPlace * slot); //used when selling artifacts -> called when user clicked on artifact slot
 	virtual void selectionChanged(bool side) = 0; //true == left
 	virtual Point selectionOffset(bool Left) const = 0;
 	virtual std::string updateSlotSubtitle(bool Left) const = 0;

+ 5 - 3
lib/CArtifactInstance.cpp

@@ -64,10 +64,7 @@ SpellID CScrollArtifactInstance::getScrollSpellID() const
 	auto artInst = static_cast<const CArtifactInstance*>(this);
 	const auto bonus = artInst->getBonusLocalFirst(Selector::type()(BonusType::SPELL));
 	if(!bonus)
-	{
-		logMod->warn("Warning: %s doesn't bear any spell!", artInst->nodeName());
 		return SpellID::NONE;
-	}
 	return bonus->subtype.as<SpellID>();
 }
 
@@ -165,6 +162,11 @@ bool CArtifactInstance::isCombined() const
 	return artType->isCombined();
 }
 
+bool CArtifactInstance::isScroll() const
+{
+	return artType->isScroll();
+}
+
 void CArtifactInstance::putAt(CArtifactSet & set, const ArtifactPosition slot)
 {
 	auto placementMap = set.putArtifact(slot, this);

+ 1 - 0
lib/CArtifactInstance.h

@@ -87,6 +87,7 @@ public:
 	bool canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot = ArtifactPosition::FIRST_AVAILABLE,
 		bool assumeDestRemoved = false) const;
 	bool isCombined() const;
+	bool isScroll() const;
 	void putAt(CArtifactSet & set, const ArtifactPosition slot);
 	void removeFrom(CArtifactSet & set, const ArtifactPosition slot);
 	void move(CArtifactSet & srcSet, const ArtifactPosition srcSlot, CArtifactSet & dstSet, const ArtifactPosition dstSlot);