Переглянути джерело

Merge pull request #3833 from SoundSSGood/artifacts-costumes

Artifacts costumes
Ivan Savenko 1 рік тому
батько
коміт
c74d728f5a

+ 6 - 0
CCallback.cpp

@@ -193,6 +193,12 @@ void CCallback::scrollBackpackArtifacts(ObjectInstanceID hero, bool left)
 	sendRequest(&mba);
 }
 
+void CCallback::manageHeroCostume(ObjectInstanceID hero, size_t costumeIndex, bool saveCostume)
+{
+	ManageEquippedArtifacts mea(hero, costumeIndex, saveCostume);
+	sendRequest(&mea);
+}
+
 void CCallback::eraseArtifactByClient(const ArtifactLocation & al)
 {
 	EraseArtifactByClient ea(al);

+ 2 - 0
CCallback.h

@@ -92,6 +92,7 @@ public:
 	//virtual bool swapArtifacts(const CGHeroInstance * hero1, ui16 pos1, const CGHeroInstance * hero2, ui16 pos2)=0; //swaps artifacts between two given heroes
 	virtual bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2)=0;
 	virtual void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) = 0;
+	virtual void manageHeroCostume(ObjectInstanceID hero, size_t costumeIndex, bool saveCostume) = 0;
 	virtual void assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0;
 	virtual void eraseArtifactByClient(const ArtifactLocation & al)=0;
 	virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0;
@@ -178,6 +179,7 @@ public:
 	void assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override;
 	void bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap, bool equipped = true, bool backpack = true) override;
 	void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) override;
+	void manageHeroCostume(ObjectInstanceID hero, size_t costumeIdx, bool saveCostume) override;
 	void eraseArtifactByClient(const ArtifactLocation & al) override;
 	bool buildBuilding(const CGTownInstance *town, BuildingID buildingID) override;
 	void recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1) override;

+ 1 - 2
client/gui/CIntObject.h

@@ -133,14 +133,13 @@ public:
 /// Classes wanting use it should have it as one of their base classes
 class CKeyShortcut : public virtual CIntObject
 {
-	bool shortcutPressed;
 public:
+	bool shortcutPressed;
 	EShortcut assignedKey;
 	CKeyShortcut();
 	CKeyShortcut(EShortcut key);
 	void keyPressed(EShortcut key) override;
 	void keyReleased(EShortcut key) override;
-
 };
 
 class WindowBase : public CIntObject

+ 10 - 0
client/gui/Shortcut.h

@@ -158,6 +158,16 @@ enum class EShortcut
 	HERO_TIGHT_FORMATION,
 	HERO_TOGGLE_TACTICS, // b
 	HERO_BACKPACK,
+	HERO_COSTUME_0,
+	HERO_COSTUME_1,
+	HERO_COSTUME_2,
+	HERO_COSTUME_3,
+	HERO_COSTUME_4,
+	HERO_COSTUME_5,
+	HERO_COSTUME_6,
+	HERO_COSTUME_7,
+	HERO_COSTUME_8,
+	HERO_COSTUME_9,
 
 	// Spellbook screen
 	SPELLBOOK_TAB_ADVENTURE,

+ 10 - 0
client/gui/ShortcutHandler.cpp

@@ -180,6 +180,16 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
 		{"heroLooseFormation",       EShortcut::HERO_LOOSE_FORMATION      },
 		{"heroTightFormation",       EShortcut::HERO_TIGHT_FORMATION      },
 		{"heroToggleTactics",        EShortcut::HERO_TOGGLE_TACTICS       },
+		{"heroCostume0",             EShortcut::HERO_COSTUME_0            },
+		{"heroCostume1",             EShortcut::HERO_COSTUME_1            },
+		{"heroCostume2",             EShortcut::HERO_COSTUME_2            },
+		{"heroCostume3",             EShortcut::HERO_COSTUME_3            },
+		{"heroCostume4",             EShortcut::HERO_COSTUME_4            },
+		{"heroCostume5",             EShortcut::HERO_COSTUME_5            },
+		{"heroCostume6",             EShortcut::HERO_COSTUME_6            },
+		{"heroCostume7",             EShortcut::HERO_COSTUME_7            },
+		{"heroCostume8",             EShortcut::HERO_COSTUME_8            },
+		{"heroCostume9",             EShortcut::HERO_COSTUME_9            },
 		{"spellbookTabAdventure",    EShortcut::SPELLBOOK_TAB_ADVENTURE   },
 		{"spellbookTabCombat",       EShortcut::SPELLBOOK_TAB_COMBAT      }
 	};

+ 1 - 1
client/widgets/CArtifactsOfHeroBase.h

@@ -13,7 +13,7 @@
 
 class CButton;
 
-class CArtifactsOfHeroBase : public CIntObject
+class CArtifactsOfHeroBase : virtual public CIntObject
 {
 protected:
 	using ArtPlacePtr = std::shared_ptr<CHeroArtPlace>;

+ 58 - 0
client/widgets/CArtifactsOfHeroMain.cpp

@@ -10,6 +10,8 @@
 #include "StdInc.h"
 #include "CArtifactsOfHeroMain.h"
 
+#include "../gui/CGuiHandler.h"
+
 #include "../CPlayerInterface.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 
@@ -30,3 +32,59 @@ CArtifactsOfHeroMain::~CArtifactsOfHeroMain()
 {
 	CArtifactsOfHeroBase::putBackPickedArtifact();
 }
+
+void CArtifactsOfHeroMain::enableArtifactsCostumeSwitcher()
+{
+	addUsedEvents(AEventsReceiver::KEYBOARD);
+}
+
+void CArtifactsOfHeroMain::keyPressed(EShortcut key)
+{
+	if(!shortcutPressed)
+	{
+		uint32_t costumeIdx;
+		switch(key)
+		{
+		case EShortcut::HERO_COSTUME_0:
+			costumeIdx = 0;
+			break;
+		case EShortcut::HERO_COSTUME_1:
+			costumeIdx = 1;
+			break;
+		case EShortcut::HERO_COSTUME_2:
+			costumeIdx = 2;
+			break;
+		case EShortcut::HERO_COSTUME_3:
+			costumeIdx = 3;
+			break;
+		case EShortcut::HERO_COSTUME_4:
+			costumeIdx = 4;
+			break;
+		case EShortcut::HERO_COSTUME_5:
+			costumeIdx = 5;
+			break;
+		case EShortcut::HERO_COSTUME_6:
+			costumeIdx = 6;
+			break;
+		case EShortcut::HERO_COSTUME_7:
+			costumeIdx = 7;
+			break;
+		case EShortcut::HERO_COSTUME_8:
+			costumeIdx = 8;
+			break;
+		case EShortcut::HERO_COSTUME_9:
+			costumeIdx = 9;
+			break;
+		default:
+			return;
+		}
+		shortcutPressed = true;
+		LOCPLINT->cb->manageHeroCostume(getHero()->id, costumeIdx, GH.isKeyboardCtrlDown());
+	}
+}
+
+void CArtifactsOfHeroMain::keyReleased(EShortcut key)
+{
+	if(vstd::contains(costumesSwitcherHotkeys, key))
+		shortcutPressed = false;
+}

+ 20 - 6
client/widgets/CArtifactsOfHeroMain.h

@@ -11,15 +11,29 @@
 
 #include "CArtifactsOfHeroBase.h"
 
-VCMI_LIB_NAMESPACE_BEGIN
+#include "../gui/Shortcut.h"
 
-struct ArtifactLocation;
-
-VCMI_LIB_NAMESPACE_END
-
-class CArtifactsOfHeroMain : public CArtifactsOfHeroBase
+class CArtifactsOfHeroMain : public CArtifactsOfHeroBase, public CKeyShortcut
 {
 public:
 	CArtifactsOfHeroMain(const Point & position);
 	~CArtifactsOfHeroMain() override;
+	void enableArtifactsCostumeSwitcher();
+	void keyPressed(EShortcut key) override;
+	void keyReleased(EShortcut key) override;
+
+private:
+	const std::vector<EShortcut> costumesSwitcherHotkeys =
+	{
+		EShortcut::HERO_COSTUME_0,
+		EShortcut::HERO_COSTUME_1,
+		EShortcut::HERO_COSTUME_2,
+		EShortcut::HERO_COSTUME_3,
+		EShortcut::HERO_COSTUME_4,
+		EShortcut::HERO_COSTUME_5,
+		EShortcut::HERO_COSTUME_6,
+		EShortcut::HERO_COSTUME_7,
+		EShortcut::HERO_COSTUME_8,
+		EShortcut::HERO_COSTUME_9
+	};
 };

+ 1 - 0
client/windows/CHeroWindow.cpp

@@ -221,6 +221,7 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded)
 			arts = std::make_shared<CArtifactsOfHeroMain>(Point(-65, -8));
 			arts->setHero(curHero);
 			addSetAndCallbacks(arts);
+			enableArtifactsCostumeSwitcher();
 		}
 
 		int serial = LOCPLINT->cb->getHeroSerial(curHero, false);

+ 14 - 0
client/windows/CWindowWithArtifacts.cpp

@@ -343,6 +343,20 @@ void CWindowWithArtifacts::deactivate()
 	CWindowObject::deactivate();
 }
 
+void CWindowWithArtifacts::enableArtifactsCostumeSwitcher() const
+{
+	for(auto artSet : artSets)
+		std::visit(
+			[](auto artSetWeak)
+			{
+				if constexpr(std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMain>>)
+				{
+					const auto artSetPtr = artSetWeak.lock();
+					artSetPtr->enableArtifactsCostumeSwitcher();
+				}
+			}, artSet);
+}
+
 void CWindowWithArtifacts::artifactRemoved(const ArtifactLocation & artLoc)
 {
 	update();

+ 1 - 0
client/windows/CWindowWithArtifacts.h

@@ -42,6 +42,7 @@ public:
 	void gestureArtPlaceHero(const CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition);
 	void activate() override;
 	void deactivate() override;
+	void enableArtifactsCostumeSwitcher() const;
 
 	virtual void artifactRemoved(const ArtifactLocation & artLoc);
 	virtual void artifactMoved(const ArtifactLocation & srcLoc, const ArtifactLocation & destLoc, bool withRedraw);

+ 11 - 1
config/shortcutsConfig.json

@@ -125,6 +125,16 @@
 		"heroTightFormation":       "T",
 		"heroToggleTactics":        "B",
 		"spellbookTabAdventure":    "A",
-		"spellbookTabCombat":       "C"
+		"spellbookTabCombat":       "C",
+		"heroCostume0":             "0",
+		"heroCostume1":             "1",
+		"heroCostume2":             "2",
+		"heroCostume3":             "3",
+		"heroCostume4":             "4",
+		"heroCostume5":             "5",
+		"heroCostume6":             "6",
+		"heroCostume7":             "7",
+		"heroCostume8":             "8",
+		"heroCostume9":             "9"
 	}
 }

+ 1 - 1
lib/ArtifactUtils.cpp

@@ -52,7 +52,7 @@ DLL_LINKAGE const std::vector<ArtifactPosition> & ArtifactUtils::unmovableSlots(
 	return positions;
 }
 
-DLL_LINKAGE const std::vector<ArtifactPosition> & ArtifactUtils::constituentWornSlots()
+DLL_LINKAGE const std::vector<ArtifactPosition> & ArtifactUtils::commonWornSlots()
 {
 	static const std::vector<ArtifactPosition> positions =
 	{

+ 1 - 1
lib/ArtifactUtils.h

@@ -30,7 +30,7 @@ namespace ArtifactUtils
 	DLL_LINKAGE ArtifactPosition getArtBackpackPosition(const CArtifactSet * target, const ArtifactID & aid);
 	// TODO: Make this constexpr when the toolset is upgraded
 	DLL_LINKAGE const std::vector<ArtifactPosition> & unmovableSlots();
-	DLL_LINKAGE const std::vector<ArtifactPosition> & constituentWornSlots();
+	DLL_LINKAGE const std::vector<ArtifactPosition> & commonWornSlots();
 	DLL_LINKAGE const std::vector<ArtifactPosition> & allWornSlots();
 	DLL_LINKAGE const std::vector<ArtifactPosition> & commanderSlots();
 	DLL_LINKAGE bool isArtRemovable(const std::pair<ArtifactPosition, ArtSlotInfo> & slot);

+ 8 - 0
lib/CArtHandler.cpp

@@ -1086,6 +1086,14 @@ CArtifactFittingSet::CArtifactFittingSet(ArtBearer::ArtBearer Bearer):
 {
 }
 
+CArtifactFittingSet::CArtifactFittingSet(const CArtifactSet & artSet)
+	: CArtifactFittingSet(artSet.bearerType())
+{
+	artifactsWorn = artSet.artifactsWorn;
+	artifactsInBackpack = artSet.artifactsInBackpack;
+	artifactsTransitionPos = artSet.artifactsTransitionPos;
+}
+
 ArtBearer::ArtBearer CArtifactFittingSet::bearerType() const
 {
 	return this->Bearer;

+ 1 - 0
lib/CArtHandler.h

@@ -249,6 +249,7 @@ class DLL_LINKAGE CArtifactFittingSet : public CArtifactSet
 {
 public:
 	CArtifactFittingSet(ArtBearer::ArtBearer Bearer);
+	explicit CArtifactFittingSet(const CArtifactSet & artSet);
 	ArtBearer::ArtBearer bearerType() const override;
 
 protected:

+ 2 - 0
lib/CPlayerState.h

@@ -63,6 +63,7 @@ public:
 	std::vector<ConstTransitivePtr<CGDwelling> > dwellings; //used for town growth
 	std::vector<QuestInfo> quests; //store info about all received quests
 	std::vector<Bonus> battleBonuses; //additional bonuses to be added during battle with neutrals
+	std::map<uint32_t, std::map<ArtifactPosition, ArtifactID>> costumesArtifacts;
 
 	bool cheated;
 	bool enteredWinningCheatCode, enteredLosingCheatCode; //if true, this player has entered cheat codes for loss / victory
@@ -111,6 +112,7 @@ public:
 		h & daysWithoutCastle;
 		h & cheated;
 		h & battleBonuses;
+		h & costumesArtifacts;
 		h & enteredLosingCheatCode;
 		h & enteredWinningCheatCode;
 		h & static_cast<CBonusSystemNode&>(*this);

+ 2 - 0
lib/networkPacks/NetPackVisitor.h

@@ -87,6 +87,7 @@ public:
 	virtual void visitInfoWindow(InfoWindow & pack) {}
 	virtual void visitSetObjectProperty(SetObjectProperty & pack) {}
 	virtual void visitChangeObjectVisitors(ChangeObjectVisitors & pack) {}
+	virtual void visitChangeArtifactsCostume(ChangeArtifactsCostume & pack) {}
 	virtual void visitHeroLevelUp(HeroLevelUp & pack) {}
 	virtual void visitCommanderLevelUp(CommanderLevelUp & pack) {}
 	virtual void visitBlockingDialog(BlockingDialog & pack) {}
@@ -132,6 +133,7 @@ public:
 	virtual void visitExchangeArtifacts(ExchangeArtifacts & pack) {}
 	virtual void visitBulkExchangeArtifacts(BulkExchangeArtifacts & pack) {}
 	virtual void visitManageBackpackArtifacts(ManageBackpackArtifacts & pack) {}
+	virtual void visitManageEquippedArtifacts(ManageEquippedArtifacts & pack) {}
 	virtual void visitAssembleArtifacts(AssembleArtifacts & pack) {}
 	virtual void visitEraseArtifactByClient(EraseArtifactByClient & pack) {}
 	virtual void visitBuyArtifact(BuyArtifact & pack) {}

+ 50 - 53
lib/networkPacks/NetPacksLib.cpp

@@ -383,6 +383,11 @@ void ChangeObjectVisitors::visitTyped(ICPackVisitor & visitor)
 	visitor.visitChangeObjectVisitors(*this);
 }
 
+void ChangeArtifactsCostume::visitTyped(ICPackVisitor & visitor)
+{
+	visitor.visitChangeArtifactsCostume(*this);
+}
+
 void HeroLevelUp::visitTyped(ICPackVisitor & visitor)
 {
 	visitor.visitHeroLevelUp(*this);
@@ -613,6 +618,11 @@ void ManageBackpackArtifacts::visitTyped(ICPackVisitor & visitor)
 	visitor.visitManageBackpackArtifacts(*this);
 }
 
+void ManageEquippedArtifacts::visitTyped(ICPackVisitor & visitor)
+{
+	visitor.visitManageEquippedArtifacts(*this);
+}
+
 void AssembleArtifacts::visitTyped(ICPackVisitor & visitor)
 {
 	visitor.visitAssembleArtifacts(*this);
@@ -1062,6 +1072,15 @@ void ChangeObjectVisitors::applyGs(CGameState * gs) const
 	}
 }
 
+void ChangeArtifactsCostume::applyGs(CGameState * gs) const
+{
+	auto & allCostumes = gs->getPlayerState(player)->costumesArtifacts;
+	if(const auto & costume = allCostumes.find(costumeIdx); costume != allCostumes.end())
+		costume->second = costumeSet;
+	else
+		allCostumes.try_emplace(costumeIdx, costumeSet);
+}
+
 void PlayerEndsGame::applyGs(CGameState * gs) const
 {
 	PlayerState *p = gs->getPlayerState(player);
@@ -1783,69 +1802,47 @@ void MoveArtifact::applyGs(CGameState * gs)
 
 void BulkMoveArtifacts::applyGs(CGameState * gs)
 {
-	enum class EBulkArtsOp
+	const auto bulkArtsRemove = [](std::vector<LinkedSlots> & artsPack, CArtifactSet & artSet)
 	{
-		BULK_MOVE,
-		BULK_REMOVE,
-		BULK_PUT
+		std::vector<ArtifactPosition> packToRemove;
+		for(const auto & slotsPair : artsPack)
+			packToRemove.push_back(slotsPair.srcPos);
+		std::sort(packToRemove.begin(), packToRemove.end(), [](const ArtifactPosition & slot0, const ArtifactPosition & slot1) -> bool
+			{
+				return slot0.num > slot1.num;
+			});
+
+		for(const auto & slot : packToRemove)
+		{
+			auto * art = artSet.getArt(slot);
+			assert(art);
+			art->removeFrom(artSet, slot);
+		}
 	};
 
-	auto bulkArtsOperation = [this, gs](std::vector<LinkedSlots> & artsPack, 
-		CArtifactSet & artSet, EBulkArtsOp operation) -> void
+	const auto bulkArtsPut = [](std::vector<LinkedSlots> & artsPack, CArtifactSet & initArtSet, CArtifactSet & dstArtSet)
 	{
-		int numBackpackArtifactsMoved = 0;
-		for(auto & slot : artsPack)
+		for(const auto & slotsPair : artsPack)
 		{
-			// When an object gets removed from the backpack, the backpack shrinks
-			// so all the following indices will be affected. Thus, we need to update
-			// the subsequent artifact slots to account for that
-			auto srcPos = slot.srcPos;
-			if(ArtifactUtils::isSlotBackpack(srcPos) && (operation != EBulkArtsOp::BULK_PUT))
-			{
-				srcPos = ArtifactPosition(srcPos.num - numBackpackArtifactsMoved);
-			}
-			auto * art = artSet.getArt(srcPos);
+			auto * art = initArtSet.getArt(slotsPair.srcPos);
 			assert(art);
-			switch(operation)
-			{
-			case EBulkArtsOp::BULK_MOVE:
-				art->move(artSet, srcPos, *gs->getArtSet(ArtifactLocation(dstArtHolder, dstCreature)), slot.dstPos);
-				break;
-			case EBulkArtsOp::BULK_REMOVE:
-				art->removeFrom(artSet, srcPos);
-				break;
-			case EBulkArtsOp::BULK_PUT:
-				art->putAt(*gs->getArtSet(ArtifactLocation(srcArtHolder, srcCreature)), slot.dstPos);
-				break;
-			default:
-				break;
-			}
-
-			if(srcPos >= ArtifactPosition::BACKPACK_START)
-			{
-				numBackpackArtifactsMoved++;
-			}
+			art->putAt(dstArtSet, slotsPair.dstPos);
 		}
 	};
 	
 	auto * leftSet = gs->getArtSet(ArtifactLocation(srcArtHolder, srcCreature));
-	if(swap)
-	{
-		// Swap
-		auto * rightSet = gs->getArtSet(ArtifactLocation(dstArtHolder, dstCreature));
-		CArtifactFittingSet artFittingSet(leftSet->bearerType());
-
-		artFittingSet.artifactsWorn = rightSet->artifactsWorn;
-		artFittingSet.artifactsInBackpack = rightSet->artifactsInBackpack;
-
-		bulkArtsOperation(artsPack1, *rightSet, EBulkArtsOp::BULK_REMOVE);
-		bulkArtsOperation(artsPack0, *leftSet, EBulkArtsOp::BULK_MOVE);
-		bulkArtsOperation(artsPack1, artFittingSet, EBulkArtsOp::BULK_PUT);
-	}
-	else
-	{
-		bulkArtsOperation(artsPack0, *leftSet, EBulkArtsOp::BULK_MOVE);
-	}
+	assert(leftSet);
+	auto * rightSet = gs->getArtSet(ArtifactLocation(dstArtHolder, dstCreature));
+	assert(rightSet);
+	CArtifactFittingSet artInitialSetLeft(*leftSet);
+	bulkArtsRemove(artsPack0, *leftSet);
+	if(!artsPack1.empty())
+	{
+		CArtifactFittingSet artInitialSetRight(*rightSet);
+		bulkArtsRemove(artsPack1, *rightSet);
+		bulkArtsPut(artsPack1, artInitialSetRight, *leftSet);
+	}
+	bulkArtsPut(artsPack0, artInitialSetLeft, *rightSet);
 }
 
 void AssembledArtifact::applyGs(CGameState *gs)

+ 24 - 0
lib/networkPacks/PacksForClient.h

@@ -1288,6 +1288,30 @@ struct DLL_LINKAGE ChangeObjectVisitors : public CPackForClient
 	}
 };
 
+struct DLL_LINKAGE ChangeArtifactsCostume : public CPackForClient
+{
+	std::map<ArtifactPosition, ArtifactID> costumeSet;
+	uint32_t costumeIdx = 0;
+	const PlayerColor player = PlayerColor::NEUTRAL;
+
+	void applyGs(CGameState * gs) const;
+	void visitTyped(ICPackVisitor & visitor) override;
+
+	ChangeArtifactsCostume() = default;
+	ChangeArtifactsCostume(const PlayerColor & player, const uint32_t costumeIdx)
+		: costumeIdx(costumeIdx)
+		, player(player)
+	{
+	}
+
+	template <typename Handler> void serialize(Handler & h)
+	{
+		h & costumeSet;
+		h & costumeIdx;
+		h & player;
+	}
+};
+
 struct DLL_LINKAGE HeroLevelUp : public Query
 {
 	PlayerColor player;

+ 25 - 0
lib/networkPacks/PacksForServer.h

@@ -429,6 +429,31 @@ struct DLL_LINKAGE ManageBackpackArtifacts : public CPackForServer
 	}
 };
 
+struct DLL_LINKAGE ManageEquippedArtifacts : public CPackForServer
+{
+	ManageEquippedArtifacts() = default;
+	ManageEquippedArtifacts(const ObjectInstanceID & artHolder, const uint32_t costumeIdx, bool saveCostume = false)
+		: artHolder(artHolder)
+		, costumeIdx(costumeIdx)
+		, saveCostume(saveCostume)
+	{
+	}
+
+	ObjectInstanceID artHolder;
+	uint32_t costumeIdx;
+	bool saveCostume;
+
+	void visitTyped(ICPackVisitor & visitor) override;
+
+	template <typename Handler> void serialize(Handler & h)
+	{
+		h & static_cast<CPackForServer&>(*this);
+		h & artHolder;
+		h & costumeIdx;
+		h & saveCostume;
+	}
+};
+
 struct DLL_LINKAGE AssembleArtifacts : public CPackForServer
 {
 	AssembleArtifacts() = default;

+ 1 - 0
lib/registerTypes/RegisterTypesClientPacks.h

@@ -67,6 +67,7 @@ void registerTypesClientPacks(Serializer &s)
 	s.template registerType<CPackForClient, HeroVisit>();
 	s.template registerType<CPackForClient, SetCommanderProperty>();
 	s.template registerType<CPackForClient, ChangeObjectVisitors>();
+	s.template registerType<CPackForClient, ChangeArtifactsCostume>();
 	s.template registerType<CPackForClient, ShowWorldViewEx>();
 	s.template registerType<CPackForClient, EntitiesChanged>();
 	s.template registerType<CPackForClient, BattleStart>();

+ 2 - 1
lib/registerTypes/RegisterTypesServerPacks.h

@@ -49,7 +49,8 @@ void registerTypesServerPacks(Serializer &s)
 	s.template registerType<CPackForServer, BulkSmartSplitStack>();
 	s.template registerType<CPackForServer, BulkMoveArmy>();
 	s.template registerType<CPackForServer, BulkExchangeArtifacts>();
-	s.template registerType < CPackForServer, ManageBackpackArtifacts>();
+	s.template registerType<CPackForServer, ManageBackpackArtifacts>();
+	s.template registerType<CPackForServer, ManageEquippedArtifacts>();
 	s.template registerType<CPackForServer, EraseArtifactByClient>();
 	s.template registerType<CPackForServer, GamePause>();
 }

+ 72 - 0
server/CGameHandler.cpp

@@ -2892,6 +2892,78 @@ bool CGameHandler::scrollBackpackArtifacts(const PlayerColor & player, const Obj
 	return true;
 }
 
+bool CGameHandler::saveArtifactsCostume(const PlayerColor & player, const ObjectInstanceID heroID, uint32_t costumeIdx)
+{
+	auto artSet = getArtSet(heroID);
+	COMPLAIN_RET_FALSE_IF(artSet == nullptr, "saveArtifactsCostume: wrong hero's ID");
+
+	ChangeArtifactsCostume costume(player, costumeIdx);
+	for(const auto & slot : ArtifactUtils::commonWornSlots())
+	{
+		if(const auto slotInfo = artSet->getSlot(slot); slotInfo != nullptr && !slotInfo->locked)
+			costume.costumeSet.emplace(slot, slotInfo->getArt()->getTypeId());
+	}
+
+	sendAndApply(&costume);
+	return true;
+}
+
+bool CGameHandler::switchArtifactsCostume(const PlayerColor & player, const ObjectInstanceID heroID, uint32_t costumeIdx)
+{
+	const auto artSet = getArtSet(heroID);
+	COMPLAIN_RET_FALSE_IF(artSet == nullptr, "switchArtifactsCostume: wrong hero's ID");
+	const auto playerState = getPlayerState(player);
+	COMPLAIN_RET_FALSE_IF(playerState == nullptr, "switchArtifactsCostume: wrong player");
+
+	if(auto costume = playerState->costumesArtifacts.find(costumeIdx); costume != playerState->costumesArtifacts.end())
+	{
+		CArtifactFittingSet artFittingSet(*artSet);
+		BulkMoveArtifacts bma(player, heroID, heroID, false);
+		auto costumeArtMap = costume->second;
+		auto estimateBackpackSize = artSet->artifactsInBackpack.size();
+
+		// First, find those artifacts that are already in place
+		for(const auto & slot : ArtifactUtils::commonWornSlots())
+		{
+			if(const auto * slotInfo = artFittingSet.getSlot(slot); slotInfo != nullptr && !slotInfo->locked)
+				if(const auto artPos = costumeArtMap.find(slot); artPos != costumeArtMap.end() && artPos->second == slotInfo->getArt()->getTypeId())
+				{
+					costumeArtMap.erase(artPos);
+					artFittingSet.removeArtifact(slot);
+				}
+		}
+		
+		// Second, find the necessary artifacts for the costume
+		for(const auto & artPos : costumeArtMap)
+		{
+			if(const auto availableArts = artFittingSet.getAllArtPositions(artPos.second, false, false, false); !availableArts.empty())
+			{
+				bma.artsPack0.emplace_back(BulkMoveArtifacts::LinkedSlots
+					{
+						artSet->getSlotByInstance(artFittingSet.getArt(availableArts.front())),
+						artPos.first
+					});
+				artFittingSet.removeArtifact(availableArts.front());
+				if(ArtifactUtils::isSlotBackpack(availableArts.front()))
+					estimateBackpackSize--;
+			}
+		}
+
+		// Third, put unnecessary artifacts into backpack
+		for(const auto & slot : ArtifactUtils::commonWornSlots())
+			if(artFittingSet.getArt(slot))
+			{
+				bma.artsPack0.emplace_back(BulkMoveArtifacts::LinkedSlots{slot, ArtifactPosition::BACKPACK_START});
+				estimateBackpackSize++;
+			}
+		
+		const auto backpackCap = VLC->settings()->getInteger(EGameSettings::HEROES_BACKPACK_CAP);
+		if((backpackCap < 0 || estimateBackpackSize <= backpackCap) && !bma.artsPack0.empty())
+			sendAndApply(&bma);
+	}
+	return true;
+}
+
 /**
  * Assembles or disassembles a combination artifact.
  * @param heroID ID of hero holding the artifact(s).

+ 2 - 0
server/CGameHandler.h

@@ -130,6 +130,8 @@ public:
 	bool moveArtifact(const PlayerColor & player, const ArtifactLocation & src, const ArtifactLocation & dst) override;
 	bool bulkMoveArtifacts(const PlayerColor & player, ObjectInstanceID srcId, ObjectInstanceID dstId, bool swap, bool equipped, bool backpack);
 	bool scrollBackpackArtifacts(const PlayerColor & player, const ObjectInstanceID heroID, bool left);
+	bool saveArtifactsCostume(const PlayerColor & player, const ObjectInstanceID heroID, uint32_t costumeIdx);
+	bool switchArtifactsCostume(const PlayerColor & player, const ObjectInstanceID heroID, uint32_t costumeIdx);
 	bool eraseArtifactByClient(const ArtifactLocation & al);
 	void synchronizeArtifactHandlerLists();
 

+ 9 - 0
server/NetPacksServer.cpp

@@ -179,6 +179,15 @@ void ApplyGhNetPackVisitor::visitManageBackpackArtifacts(ManageBackpackArtifacts
 	}
 }
 
+void ApplyGhNetPackVisitor::visitManageEquippedArtifacts(ManageEquippedArtifacts & pack)
+{
+	gh.throwIfWrongOwner(&pack, pack.artHolder);
+	if(pack.saveCostume)
+		result = gh.saveArtifactsCostume(pack.player, pack.artHolder, pack.costumeIdx);
+	else
+		result = gh.switchArtifactsCostume(pack.player, pack.artHolder, pack.costumeIdx);
+}
+
 void ApplyGhNetPackVisitor::visitAssembleArtifacts(AssembleArtifacts & pack)
 {
 	gh.throwIfWrongOwner(&pack, pack.heroID);

+ 1 - 0
server/ServerNetPackVisitors.h

@@ -47,6 +47,7 @@ public:
 	void visitExchangeArtifacts(ExchangeArtifacts & pack) override;
 	void visitBulkExchangeArtifacts(BulkExchangeArtifacts & pack) override;
 	void visitManageBackpackArtifacts(ManageBackpackArtifacts & pack) override;
+	void visitManageEquippedArtifacts(ManageEquippedArtifacts & pack) override;
 	void visitAssembleArtifacts(AssembleArtifacts & pack) override;
 	void visitEraseArtifactByClient(EraseArtifactByClient & pack) override;
 	void visitBuyArtifact(BuyArtifact & pack) override;