diff --git a/CCallback.cpp b/CCallback.cpp index 5f4edfd50..7367c1edc 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -195,8 +195,6 @@ void CCallback::scrollBackpackArtifacts(ObjectInstanceID hero, bool left) void CCallback::manageHeroCostume(ObjectInstanceID hero, size_t costumeIndex, bool saveCostume) { - assert(costumeIndex < GameConstants::HERO_COSTUMES_ARTIFACTS); - ManageEquippedArtifacts mea(hero, costumeIndex, saveCostume); sendRequest(&mea); } diff --git a/client/gui/CIntObject.cpp b/client/gui/CIntObject.cpp index 3f136151e..debc01927 100644 --- a/client/gui/CIntObject.cpp +++ b/client/gui/CIntObject.cpp @@ -314,12 +314,20 @@ CKeyShortcut::CKeyShortcut(EShortcut key) { } +CKeyShortcut::CKeyShortcut(const EShortcut & key, const KeyPressedFunctor & keyPressedCallback) + : CKeyShortcut(key) +{ + this->keyPressedCallback = keyPressedCallback; +} + void CKeyShortcut::keyPressed(EShortcut key) { if( assignedKey == key && assignedKey != EShortcut::NONE && !shortcutPressed) { shortcutPressed = true; clickPressed(GH.getCursorPosition()); + if(keyPressedCallback) + keyPressedCallback(); } } diff --git a/client/gui/CIntObject.h b/client/gui/CIntObject.h index 71bda7911..61224a122 100644 --- a/client/gui/CIntObject.h +++ b/client/gui/CIntObject.h @@ -133,14 +133,19 @@ public: /// Classes wanting use it should have it as one of their base classes class CKeyShortcut : public virtual CIntObject { - bool shortcutPressed; public: + using KeyPressedFunctor = std::function; + EShortcut assignedKey; CKeyShortcut(); CKeyShortcut(EShortcut key); + CKeyShortcut(const EShortcut & key, const KeyPressedFunctor & keyPressedCallback); void keyPressed(EShortcut key) override; void keyReleased(EShortcut key) override; +private: + bool shortcutPressed; + KeyPressedFunctor keyPressedCallback; }; class WindowBase : public CIntObject diff --git a/client/widgets/CArtifactsOfHeroMain.cpp b/client/widgets/CArtifactsOfHeroMain.cpp index 62e98db03..991ff6b68 100644 --- a/client/widgets/CArtifactsOfHeroMain.cpp +++ b/client/widgets/CArtifactsOfHeroMain.cpp @@ -18,7 +18,7 @@ #include "../../CCallback.h" #include "../../lib/networkPacks/ArtifactLocation.h" -CArtifactsOfHeroMain::CArtifactsOfHeroMain(const Point & position, bool costumesEnabled) +CArtifactsOfHeroMain::CArtifactsOfHeroMain(const Point & position) { init( std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2), @@ -26,21 +26,6 @@ CArtifactsOfHeroMain::CArtifactsOfHeroMain(const Point & position, bool costumes position, std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1)); addGestureCallback(std::bind(&CArtifactsOfHeroBase::gestureArtPlace, this, _1, _2)); - - if(costumesEnabled) - { - size_t costumeIndex = 0; - for(const auto & hotkey : hotkeys) - { - auto keyProc = costumesSwitchers.emplace_back(std::make_shared(hotkey, - [this, hotkey, costumeIndex]() - { - CArtifactsOfHeroMain::onCostumeSelect(costumeIndex); - })); - keyProc->addUsedEvents(AEventsReceiver::KEYBOARD); - costumeIndex++; - } - } } CArtifactsOfHeroMain::~CArtifactsOfHeroMain() @@ -48,19 +33,17 @@ CArtifactsOfHeroMain::~CArtifactsOfHeroMain() CArtifactsOfHeroBase::putBackPickedArtifact(); } -void CArtifactsOfHeroMain::onCostumeSelect(const size_t costumeIndex) +void CArtifactsOfHeroMain::enableArtifactsCostumeSwitcher() { - LOCPLINT->cb->manageHeroCostume(getHero()->id, costumeIndex, GH.isKeyboardCtrlDown()); -} - -CArtifactsOfHeroMain::CKeyShortcutWrapper::CKeyShortcutWrapper(const EShortcut & key, const KeyPressedFunctor & onCostumeSelect) - : CKeyShortcut(key) - , onCostumeSelect(onCostumeSelect) -{ -} - -void CArtifactsOfHeroMain::CKeyShortcutWrapper::clickPressed(const Point & cursorPosition) -{ - if(onCostumeSelect) - onCostumeSelect(); + size_t costumeIdx = 0; + for(const auto & hotkey : costumesSwitcherHotkeys) + { + auto keyProc = costumesSwitcherProcessors.emplace_back(std::make_shared(hotkey, + [this, costumeIdx]() + { + LOCPLINT->cb->manageHeroCostume(getHero()->id, costumeIdx, GH.isKeyboardCtrlDown()); + })); + keyProc->addUsedEvents(AEventsReceiver::KEYBOARD); + costumeIdx++; + } } diff --git a/client/widgets/CArtifactsOfHeroMain.h b/client/widgets/CArtifactsOfHeroMain.h index 31f157274..ee28ace28 100644 --- a/client/widgets/CArtifactsOfHeroMain.h +++ b/client/widgets/CArtifactsOfHeroMain.h @@ -16,24 +16,12 @@ class CArtifactsOfHeroMain : public CArtifactsOfHeroBase { public: - CArtifactsOfHeroMain(const Point & position, bool costumesEnabled = false); + CArtifactsOfHeroMain(const Point & position); ~CArtifactsOfHeroMain() override; + void enableArtifactsCostumeSwitcher(); private: - // TODO may be removed if CKeyShortcut supports callbacks - class CKeyShortcutWrapper : public CKeyShortcut - { - public: - using KeyPressedFunctor = std::function; - - CKeyShortcutWrapper(const EShortcut & key, const KeyPressedFunctor & onCostumeSelect); - void clickPressed(const Point & cursorPosition) override; - - private: - KeyPressedFunctor onCostumeSelect; - }; - - const std::array hotkeys = + const std::vector costumesSwitcherHotkeys = { EShortcut::HERO_COSTUME_0, EShortcut::HERO_COSTUME_1, @@ -46,7 +34,5 @@ private: EShortcut::HERO_COSTUME_8, EShortcut::HERO_COSTUME_9 }; - std::vector> costumesSwitchers; - - void onCostumeSelect(const size_t costumeIndex); + std::vector> costumesSwitcherProcessors; }; diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index 8b5219cd7..5c9b1dd6b 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -218,9 +218,10 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded) } if(!arts) { - arts = std::make_shared(Point(-65, -8), true); + arts = std::make_shared(Point(-65, -8)); arts->setHero(curHero); addSetAndCallbacks(arts); + enableArtifactsCostumeSwitcher(); } int serial = LOCPLINT->cb->getHeroSerial(curHero, false); diff --git a/client/windows/CWindowWithArtifacts.cpp b/client/windows/CWindowWithArtifacts.cpp index a1bfdcf73..b8ce160a9 100644 --- a/client/windows/CWindowWithArtifacts.cpp +++ b/client/windows/CWindowWithArtifacts.cpp @@ -343,6 +343,20 @@ void CWindowWithArtifacts::deactivate() CWindowObject::deactivate(); } +void CWindowWithArtifacts::enableArtifactsCostumeSwitcher() +{ + for(auto artSet : artSets) + std::visit( + [](auto artSetWeak) + { + if constexpr(std::is_same_v>) + { + const auto artSetPtr = artSetWeak.lock(); + artSetPtr->enableArtifactsCostumeSwitcher(); + } + }, artSet); +} + void CWindowWithArtifacts::artifactRemoved(const ArtifactLocation & artLoc) { update(); diff --git a/client/windows/CWindowWithArtifacts.h b/client/windows/CWindowWithArtifacts.h index 311e6a74c..e8d98673d 100644 --- a/client/windows/CWindowWithArtifacts.h +++ b/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(); virtual void artifactRemoved(const ArtifactLocation & artLoc); virtual void artifactMoved(const ArtifactLocation & srcLoc, const ArtifactLocation & destLoc, bool withRedraw); diff --git a/lib/ArtifactUtils.cpp b/lib/ArtifactUtils.cpp index 66aafc53e..c5c8e191b 100644 --- a/lib/ArtifactUtils.cpp +++ b/lib/ArtifactUtils.cpp @@ -52,7 +52,7 @@ DLL_LINKAGE const std::vector & ArtifactUtils::unmovableSlots( return positions; } -DLL_LINKAGE const std::vector & ArtifactUtils::constituentWornSlots() +DLL_LINKAGE const std::vector & ArtifactUtils::commonWornSlots() { static const std::vector positions = { diff --git a/lib/ArtifactUtils.h b/lib/ArtifactUtils.h index 74fe67db1..de3a5a027 100644 --- a/lib/ArtifactUtils.h +++ b/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 & unmovableSlots(); - DLL_LINKAGE const std::vector & constituentWornSlots(); + DLL_LINKAGE const std::vector & commonWornSlots(); DLL_LINKAGE const std::vector & allWornSlots(); DLL_LINKAGE const std::vector & commanderSlots(); DLL_LINKAGE bool isArtRemovable(const std::pair & slot); diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 79ee52d83..c85d67918 100644 --- a/lib/CArtHandler.cpp +++ b/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; diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index a4c416028..1453325a6 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -249,6 +249,7 @@ class DLL_LINKAGE CArtifactFittingSet : public CArtifactSet { public: CArtifactFittingSet(ArtBearer::ArtBearer Bearer); + CArtifactFittingSet(const CArtifactSet & artSet); ArtBearer::ArtBearer bearerType() const override; protected: diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index c39d8c76c..6035faa2d 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -1075,7 +1075,7 @@ void ChangeObjectVisitors::applyGs(CGameState * gs) const void ChangeArtifactsCostume::applyGs(CGameState * gs) const { auto & allCostumes = gs->getPlayerState(player)->costumesArtifacts; - if(auto & costume = allCostumes.find(costumeIdx); costume != allCostumes.end()) + if(const auto & costume = allCostumes.find(costumeIdx); costume != allCostumes.end()) costume->second = costumeSet; else allCostumes.emplace(costumeIdx, costumeSet); @@ -1802,69 +1802,47 @@ void MoveArtifact::applyGs(CGameState * gs) void BulkMoveArtifacts::applyGs(CGameState * gs) { - enum class EBulkArtsOp + const auto bulkArtsRemove = [](std::vector & artsPack, CArtifactSet & artSet) { - BULK_MOVE, - BULK_REMOVE, - BULK_PUT + std::vector 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 & artsPack, - CArtifactSet & artSet, EBulkArtsOp operation) -> void + const auto bulkArtsPut = [](std::vector & 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) + assert(leftSet); + auto * rightSet = gs->getArtSet(ArtifactLocation(dstArtHolder, dstCreature)); + assert(rightSet); + CArtifactFittingSet artInitialSetLeft(*leftSet); + bulkArtsRemove(artsPack0, *leftSet); + if(!artsPack1.empty()) { - // 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); + CArtifactFittingSet artInitialSetRight(*rightSet); + bulkArtsRemove(artsPack1, *rightSet); + bulkArtsPut(artsPack1, artInitialSetRight, *leftSet); } + bulkArtsPut(artsPack0, artInitialSetLeft, *rightSet); } void AssembledArtifact::applyGs(CGameState *gs) diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index 8ece152dc..ce22f1c34 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -1291,8 +1291,8 @@ struct DLL_LINKAGE ChangeObjectVisitors : public CPackForClient struct DLL_LINKAGE ChangeArtifactsCostume : public CPackForClient { std::map costumeSet; - size_t costumeIdx; - const PlayerColor player; + size_t costumeIdx = 0; + const PlayerColor player = PlayerColor::NEUTRAL; void applyGs(CGameState * gs) const; void visitTyped(ICPackVisitor & visitor) override; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index c8899c065..24ba4e5ba 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2896,20 +2896,74 @@ bool CGameHandler::saveArtifactsCostume(const PlayerColor & player, const Object { auto artSet = getArtSet(heroID); COMPLAIN_RET_FALSE_IF(artSet == nullptr, "saveArtifactsCostume: wrong hero's ID"); - COMPLAIN_RET_FALSE_IF(costumeIdx >= GameConstants::HERO_COSTUMES_ARTIFACTS, "saveArtifactsCostume: wrong costume index"); ChangeArtifactsCostume costume(player, costumeIdx); - for(const auto & slot : ArtifactUtils::constituentWornSlots()) + for(const auto & slot : ArtifactUtils::commonWornSlots()) { - if(const auto & slotInfo = artSet->getSlot(slot)) - if(!slotInfo->locked) - costume.costumeSet.emplace(slot, slotInfo->getArt()->getTypeId()); + 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, size_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). diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 8de08316b..199da23c8 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -131,6 +131,7 @@ public: 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, size_t costumeIdx); + bool switchArtifactsCostume(const PlayerColor & player, const ObjectInstanceID heroID, size_t costumeIdx); bool eraseArtifactByClient(const ArtifactLocation & al); void synchronizeArtifactHandlerLists(); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index e35049fda..746664090 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -183,8 +183,9 @@ void ApplyGhNetPackVisitor::visitManageEquippedArtifacts(ManageEquippedArtifacts { gh.throwIfWrongOwner(&pack, pack.artHolder); if(pack.saveCostume) - gh.saveArtifactsCostume(pack.player, pack.artHolder, pack.costumeIdx); - result = true; + result = gh.saveArtifactsCostume(pack.player, pack.artHolder, pack.costumeIdx); + else + result = gh.switchArtifactsCostume(pack.player, pack.artHolder, pack.costumeIdx); } void ApplyGhNetPackVisitor::visitAssembleArtifacts(AssembleArtifacts & pack)