diff --git a/CCallback.cpp b/CCallback.cpp index b9af639f7..6545c9729 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -181,6 +181,12 @@ bool CCallback::assembleArtifacts (const CGHeroInstance * hero, ArtifactPosition return true; } +void CCallback::bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap) +{ + BulkExchangeArtifacts bma(srcHero, dstHero, swap); + sendRequest(&bma); +} + bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID) { if(town->tempOwner!=player) diff --git a/CCallback.h b/CCallback.h index 49e1a9970..d96d9dd83 100644 --- a/CCallback.h +++ b/CCallback.h @@ -94,6 +94,10 @@ public: virtual int bulkSplitStack(ObjectInstanceID armyId, SlotID srcSlot, int howMany = 1) = 0; virtual int bulkSmartSplitStack(ObjectInstanceID armyId, SlotID srcSlot) = 0; virtual int bulkMergeStacks(ObjectInstanceID armyId, SlotID srcSlot) = 0; + + + // Moves all artifacts from one hero to another + virtual void bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap) = 0; }; class CBattleCallback : public IBattleCallback, public CPlayerBattleCallback @@ -151,6 +155,7 @@ public: bool dismissHero(const CGHeroInstance * hero) override; bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2) override; bool assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override; + void bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap) 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; bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override; diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 428149a33..05236c2cd 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -2591,6 +2591,12 @@ void CPlayerInterface::artifactMoved(const ArtifactLocation &src, const Artifact if (artWin) artWin->artifactMoved(src, dst); } + if(!GH.objsToBlit.empty()) + GH.objsToBlit.back()->redraw(); +} + +void CPlayerInterface::artifactPossibleAssembling(const ArtifactLocation & dst) +{ askToAssembleArtifact(dst); } diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index f7907594c..14a81895f 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -133,6 +133,7 @@ public: void artifactRemoved(const ArtifactLocation &al) override; void artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst) override; void artifactAssembled(const ArtifactLocation &al) override; + void artifactPossibleAssembling(const ArtifactLocation & dst) override; void artifactDisassembled(const ArtifactLocation &al) override; void heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start) override; diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index d44434b18..f2d27ffdc 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -275,8 +275,31 @@ void EraseArtifact::applyCl(CClient *cl) void MoveArtifact::applyCl(CClient *cl) { callInterfaceIfPresent(cl, src.owningPlayer(), &IGameEventsReceiver::artifactMoved, src, dst); + callInterfaceIfPresent(cl, src.owningPlayer(), &IGameEventsReceiver::artifactPossibleAssembling, dst); if(src.owningPlayer() != dst.owningPlayer()) + { callInterfaceIfPresent(cl, dst.owningPlayer(), &IGameEventsReceiver::artifactMoved, src, dst); + callInterfaceIfPresent(cl, dst.owningPlayer(), &IGameEventsReceiver::artifactPossibleAssembling, dst); + } +} + +void BulkMoveArtifacts::applyCl(CClient * cl) +{ + auto applyMove = [this, cl](std::vector & artsPack) -> void + { + for(auto & slotToMove : artsPack) + { + auto srcLoc = ArtifactLocation(srcArtHolder, slotToMove.srcPos); + auto dstLoc = ArtifactLocation(dstArtHolder, slotToMove.dstPos); + callInterfaceIfPresent(cl, srcLoc.owningPlayer(), &IGameEventsReceiver::artifactMoved, srcLoc, dstLoc); + if(srcLoc.owningPlayer() != dstLoc.owningPlayer()) + callInterfaceIfPresent(cl, dstLoc.owningPlayer(), &IGameEventsReceiver::artifactMoved, srcLoc, dstLoc); + } + }; + + applyMove(artsPack0); + if(swap) + applyMove(artsPack1); } void AssembledArtifact::applyCl(CClient *cl) diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 4bce0f2d0..0f9875d96 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -872,41 +872,6 @@ std::function CExchangeController::onMoveArmyToRight() return [&]() { moveArmy(true); }; } -void CExchangeController::swapArtifacts(ArtifactPosition slot) -{ - bool leftHasArt = !left->isPositionFree(slot); - bool rightHasArt = !right->isPositionFree(slot); - - if(!leftHasArt && !rightHasArt) - return; - - ArtifactLocation leftLocation = ArtifactLocation(left, slot); - ArtifactLocation rightLocation = ArtifactLocation(right, slot); - - if(leftHasArt && !left->artifactsWorn.at(slot).artifact->canBePutAt(rightLocation, true)) - return; - - if(rightHasArt && !right->artifactsWorn.at(slot).artifact->canBePutAt(leftLocation, true)) - return; - - if(leftHasArt) - { - if(rightHasArt) - { - auto art = right->getArt(slot); - - cb->swapArtifacts(leftLocation, rightLocation); - cb->swapArtifacts(ArtifactLocation(right, right->getArtPos(art)), leftLocation); - } - else - cb->swapArtifacts(leftLocation, rightLocation); - } - else - { - cb->swapArtifacts(rightLocation, leftLocation); - } -} - std::vector getBackpackArts(const CGHeroInstance * hero) { std::vector result; @@ -919,92 +884,13 @@ std::vector getBackpackArts(const CGHeroInstance * hero) return result; } -const std::vector unmovablePositions = {ArtifactPosition::SPELLBOOK, ArtifactPosition::MACH4}; - -bool isArtRemovable(const std::pair & slot) -{ - return slot.second.artifact - && !slot.second.locked - && !vstd::contains(unmovablePositions, slot.first); -} - -// Puts all composite arts to backpack and returns their previous location -std::vector CExchangeController::moveCompositeArtsToBackpack() -{ - std::vector sides = {left, right}; - std::vector artPositions; - - for(auto hero : sides) - { - for(int i = ArtifactPosition::HEAD; i < ArtifactPosition::AFTER_LAST; i++) - { - auto artPosition = ArtifactPosition(i); - auto art = hero->getArt(artPosition); - - if(art && art->canBeDisassembled()) - { - cb->swapArtifacts( - ArtifactLocation(hero, artPosition), - ArtifactLocation(hero, ArtifactPosition(GameConstants::BACKPACK_START))); - - artPositions.push_back(HeroArtifact(hero, art, artPosition)); - } - } - } - - return artPositions; -} - -void CExchangeController::swapArtifacts() -{ - for(int i = ArtifactPosition::HEAD; i < ArtifactPosition::AFTER_LAST; i++) - { - if(vstd::contains(unmovablePositions, i)) - continue; - - swapArtifacts(ArtifactPosition(i)); - } - - auto leftHeroBackpack = getBackpackArts(left); - auto rightHeroBackpack = getBackpackArts(right); - - for(auto leftArt : leftHeroBackpack) - { - cb->swapArtifacts( - ArtifactLocation(left, left->getArtPos(leftArt)), - ArtifactLocation(right, ArtifactPosition(GameConstants::BACKPACK_START))); - } - - for(auto rightArt : rightHeroBackpack) - { - cb->swapArtifacts( - ArtifactLocation(right, right->getArtPos(rightArt)), - ArtifactLocation(left, ArtifactPosition(GameConstants::BACKPACK_START))); - } -} - std::function CExchangeController::onSwapArtifacts() { return [&]() { GsThread::run([=] { - // it is not possible directly exchange composite artifacts like Angelic Alliance and Armor of Damned - auto compositeArtLocations = moveCompositeArtsToBackpack(); - - swapArtifacts(); - - for(HeroArtifact artLocation : compositeArtLocations) - { - auto target = artLocation.hero == left ? right : left; - auto currentPos = target->getArtPos(artLocation.artifact); - - cb->swapArtifacts( - ArtifactLocation(target, currentPos), - ArtifactLocation(target, artLocation.artPosition)); - } - - view->redraw(); + cb->bulkMoveArtifacts(left->id, right->id, true); }); }; } @@ -1160,20 +1046,8 @@ void CExchangeController::moveArtifacts(bool leftToRight) } GsThread::run([=] - { - while(vstd::contains_if(source->artifactsWorn, isArtRemovable)) - { - auto art = std::find_if(source->artifactsWorn.begin(), source->artifactsWorn.end(), isArtRemovable); - - moveArtifact(source, target, art->first); - } - - while(!source->artifactsInBackpack.empty()) - { - moveArtifact(source, target, source->getArtPos(source->artifactsInBackpack.begin()->artifact)); - } - - view->redraw(); + { + cb->bulkMoveArtifacts(source->id, target->id, false); }); } @@ -1182,26 +1056,11 @@ void CExchangeController::moveArtifact( const CGHeroInstance * target, ArtifactPosition srcPosition) { - auto artifact = source->getArt(srcPosition); auto srcLocation = ArtifactLocation(source, srcPosition); + auto dstLocation = ArtifactLocation(target, + ArtifactUtils::getArtifactDstPosition(source->getArt(srcPosition), target, target->bearerType())); - for(auto slot : artifact->artType->possibleSlots.at(target->bearerType())) - { - auto existingArtifact = target->getArt(slot); - auto existingArtInfo = target->getSlot(slot); - ArtifactLocation destLocation(target, slot); - - if(!existingArtifact - && (!existingArtInfo || !existingArtInfo->locked) - && artifact->canBePutAt(destLocation)) - { - cb->swapArtifacts(srcLocation, ArtifactLocation(target, slot)); - - return; - } - } - - cb->swapArtifacts(srcLocation, ArtifactLocation(target, ArtifactPosition(GameConstants::BACKPACK_START))); + cb->swapArtifacts(srcLocation, dstLocation); } CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID queryID) diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index a56f87d35..56097d0b4 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -324,9 +324,6 @@ private: void moveArtifacts(bool leftToRight); void moveArtifact(const CGHeroInstance * source, const CGHeroInstance * target, ArtifactPosition srcPosition); void moveStack(const CGHeroInstance * source, const CGHeroInstance * target, SlotID sourceSlot); - void swapArtifacts(ArtifactPosition artPosition); - std::vector moveCompositeArtsToBackpack(); - void swapArtifacts(); }; class CExchangeWindow : public CStatusbarWindow, public CGarrisonHolder, public CWindowWithArtifacts diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 9480e718e..5d7c68e1d 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -1457,4 +1457,83 @@ void CArtifactSet::serializeJsonSlot(JsonSerializeFormat & handler, const Artifa } } +CArtifactFittingSet::CArtifactFittingSet(ArtBearer::ArtBearer Bearer) +{ + this->Bearer = Bearer; +} + +void CArtifactFittingSet::setNewArtSlot(ArtifactPosition slot, CArtifactInstance * art, bool locked) +{ + ArtSlotInfo & asi = retrieveNewArtSlot(slot); + asi.artifact = art; + asi.locked = locked; +} + +void CArtifactFittingSet::putArtifact(ArtifactPosition pos, CArtifactInstance * art) +{ + if(art && art->canBeDisassembled() && (pos < ArtifactPosition::AFTER_LAST)) + { + for(auto & part : dynamic_cast(art)->constituentsInfo) + { + // For the ArtFittingSet is no needed to do figureMainConstituent, just lock slots + this->setNewArtSlot(part.art->firstAvailableSlot(this), part.art, true); + } + } + else + { + this->setNewArtSlot(pos, art, false); + } +} + +ArtBearer::ArtBearer CArtifactFittingSet::bearerType() const +{ + return this->Bearer; +} + +DLL_LINKAGE ArtifactPosition ArtifactUtils::getArtifactDstPosition( const CArtifactInstance * artifact, + const CArtifactSet * target, + ArtBearer::ArtBearer bearer) +{ + for(auto slot : artifact->artType->possibleSlots.at(bearer)) + { + auto existingArtifact = target->getArt(slot); + auto existingArtInfo = target->getSlot(slot); + + if(!existingArtifact + && (!existingArtInfo || !existingArtInfo->locked) + && artifact->canBePutAt(target, slot)) + { + return slot; + } + } + return ArtifactPosition(GameConstants::BACKPACK_START); +} + +DLL_LINKAGE std::vector ArtifactUtils::unmovablePositions() +{ + return { ArtifactPosition::SPELLBOOK, ArtifactPosition::MACH4 }; +} + +DLL_LINKAGE bool ArtifactUtils::isArtRemovable(const std::pair & slot) +{ + return slot.second.artifact + && !slot.second.locked + && !vstd::contains(unmovablePositions(), slot.first); +} + +DLL_LINKAGE bool ArtifactUtils::checkSpellbookIsNeeded(const CGHeroInstance * heroPtr, ArtifactID artID, ArtifactPosition slot) +{ + // TODO what'll happen if Titan's thunder is equipped by pickin git up or the start of game? + // Titan's Thunder creates new spellbook on equip + if(artID == ArtifactID::TITANS_THUNDER && slot == ArtifactPosition::RIGHT_HAND) + { + if(heroPtr) + { + if(heroPtr && !heroPtr->hasSpellbook()) + return true; + } + } + return false; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index a933b70a3..46f14d490 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -303,8 +303,9 @@ struct DLL_LINKAGE ArtSlotInfo ui8 locked; //if locked, then artifact points to the combined artifact ArtSlotInfo() : locked(false) {} + const CArtifactInstance * getArt() const; - template void serialize(Handler &h, const int version) + template void serialize(Handler & h, const int version) { h & artifact; h & locked; @@ -363,4 +364,28 @@ private: void serializeJsonSlot(JsonSerializeFormat & handler, const ArtifactPosition & slot, CMap * map);//normal slots }; +// Used to try on artifacts before the claimed changes have been applied +class DLL_LINKAGE CArtifactFittingSet : public CArtifactSet +{ +public: + CArtifactFittingSet(ArtBearer::ArtBearer Bearer); + void setNewArtSlot(ArtifactPosition slot, CArtifactInstance * art, bool locked); + void putArtifact(ArtifactPosition pos, CArtifactInstance * art) override; + ArtBearer::ArtBearer bearerType() const override; + +protected: + ArtBearer::ArtBearer Bearer; +}; + +namespace ArtifactUtils +{ + // Calculates where an artifact gets placed when it gets transferred from one hero to another. + DLL_LINKAGE ArtifactPosition getArtifactDstPosition( const CArtifactInstance * artifact, + const CArtifactSet * target, + ArtBearer::ArtBearer bearer); + DLL_LINKAGE std::vector unmovablePositions(); // TODO: Make this constexpr when the toolset is upgraded + DLL_LINKAGE bool isArtRemovable(const std::pair & slot); + DLL_LINKAGE bool checkSpellbookIsNeeded(const CGHeroInstance * heroPtr, ArtifactID artID, ArtifactPosition slot); +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/IGameEventsReceiver.h b/lib/IGameEventsReceiver.h index 632489c76..9970b2c39 100644 --- a/lib/IGameEventsReceiver.h +++ b/lib/IGameEventsReceiver.h @@ -91,6 +91,7 @@ public: virtual void artifactAssembled(const ArtifactLocation &al){}; virtual void artifactDisassembled(const ArtifactLocation &al){}; virtual void artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst){}; + virtual void artifactPossibleAssembling(const ArtifactLocation & dst) {}; virtual void heroVisit(const CGHeroInstance *visitor, const CGObjectInstance *visitedObj, bool start){}; virtual void heroCreated(const CGHeroInstance*){}; diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 171ebdace..115f1647f 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -994,18 +994,64 @@ struct EraseArtifact : CArtifactOperationPack struct MoveArtifact : CArtifactOperationPack { + MoveArtifact() {} + MoveArtifact(ArtifactLocation * src, ArtifactLocation * dst) + : src(*src), dst(*dst) {} ArtifactLocation src, dst; void applyCl(CClient *cl); DLL_LINKAGE void applyGs(CGameState *gs); - template void serialize(Handler &h, const int version) + template void serialize(Handler & h, const int version) { h & src; h & dst; } }; +struct BulkMoveArtifacts : CArtifactOperationPack +{ + struct LinkedSlots + { + ArtifactPosition srcPos; + ArtifactPosition dstPos; + + LinkedSlots() {} + LinkedSlots(ArtifactPosition srcPos, ArtifactPosition dstPos) + : srcPos(srcPos), dstPos(dstPos) {} + template void serialize(Handler & h, const int version) + { + h & srcPos; + h & dstPos; + } + }; + + TArtHolder srcArtHolder; + TArtHolder dstArtHolder; + + BulkMoveArtifacts() + : swap(false) {} + BulkMoveArtifacts(TArtHolder srcArtHolder, TArtHolder dstArtHolder, bool swap) + : srcArtHolder(srcArtHolder), dstArtHolder(dstArtHolder), swap(swap) {} + + void applyCl(CClient * cl); + DLL_LINKAGE void applyGs(CGameState * gs); + + std::vector artsPack0; + std::vector artsPack1; + bool swap; + CArtifactSet * getSrcHolderArtSet(); + CArtifactSet * getDstHolderArtSet(); + template void serialize(Handler & h, const int version) + { + h & artsPack0; + h & artsPack1; + h & srcArtHolder; + h & dstArtHolder; + h & swap; + } +}; + struct AssembledArtifact : CArtifactOperationPack { ArtifactLocation al; //where assembly will be put @@ -2197,6 +2243,27 @@ struct ExchangeArtifacts : public CPackForServer } }; +struct BulkExchangeArtifacts : public CPackForServer +{ + ObjectInstanceID srcHero; + ObjectInstanceID dstHero; + bool swap; + + BulkExchangeArtifacts() + : swap(false) {} + BulkExchangeArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap) + : srcHero(srcHero), dstHero(dstHero), swap(swap) {} + + bool applyGh(CGameHandler * gh); + template void serialize(Handler & h, const int version) + { + h & static_cast(*this); + h & srcHero; + h & dstHero; + h & swap; + } +}; + struct AssembleArtifacts : public CPackForServer { AssembleArtifacts():assemble(false){}; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 2eb8dc604..ca8ae4c8b 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -845,18 +845,11 @@ DLL_LINKAGE CBonusSystemNode *ArtifactLocation::getHolderNode() DLL_LINKAGE const CArtifactInstance *ArtifactLocation::getArt() const { - const ArtSlotInfo *s = getSlot(); - if(s && s->artifact) - { - if(!s->locked) - return s->artifact; - else - { - logNetwork->warn("ArtifactLocation::getArt: This location is locked!"); - return nullptr; - } - } - return nullptr; + auto s = getSlot(); + if(s) + return s->getArt(); + else + return nullptr; } DLL_LINKAGE const CArtifactSet * ArtifactLocation::getHolderArtSet() const @@ -1093,24 +1086,82 @@ DLL_LINKAGE void EraseArtifact::applyGs(CGameState *gs) al.removeArtifact(); } -DLL_LINKAGE void MoveArtifact::applyGs(CGameState *gs) +DLL_LINKAGE void MoveArtifact::applyGs(CGameState * gs) { - CArtifactInstance *a = src.getArt(); + CArtifactInstance * art = src.getArt(); if(dst.slot < GameConstants::BACKPACK_START) assert(!dst.getArt()); - a->move(src, dst); + art->move(src, dst); +} - //TODO what'll happen if Titan's thunder is equipped by pickin git up or the start of game? - if (a->artType->id == ArtifactID::TITANS_THUNDER && dst.slot == ArtifactPosition::RIGHT_HAND) //Titan's Thunder creates new spellbook on equip +DLL_LINKAGE void BulkMoveArtifacts::applyGs(CGameState * gs) +{ + enum class EBulkArtsOp { - auto hPtr = boost::get >(&dst.artHolder); - if(hPtr) + BULK_MOVE, + BULK_REMOVE, + BULK_PUT + }; + + auto bulkArtsOperation = [this](std::vector & artsPack, + CArtifactSet * artSet, EBulkArtsOp operation) -> void + { + int numBackpackArtifactsMoved = 0; + for(auto & slot : artsPack) { - CGHeroInstance *h = *hPtr; - if(h && !h->hasSpellbook()) - gs->giveHeroArtifact(h, ArtifactID::SPELLBOOK); + // 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((srcPos >= GameConstants::BACKPACK_START) && (operation != EBulkArtsOp::BULK_PUT)) + { + srcPos = ArtifactPosition(srcPos.num - numBackpackArtifactsMoved); + } + auto slotInfo = artSet->getSlot(srcPos); + assert(slotInfo); + auto art = const_cast(slotInfo->getArt()); + assert(art); + switch(operation) + { + case EBulkArtsOp::BULK_MOVE: + const_cast(art)->move( + ArtifactLocation(srcArtHolder, srcPos), ArtifactLocation(dstArtHolder, slot.dstPos)); + break; + case EBulkArtsOp::BULK_REMOVE: + art->removeFrom(ArtifactLocation(dstArtHolder, srcPos)); + break; + case EBulkArtsOp::BULK_PUT: + art->putAt(ArtifactLocation(srcArtHolder, slot.dstPos)); + break; + default: + break; + } + + if(srcPos >= GameConstants::BACKPACK_START) + { + numBackpackArtifactsMoved++; + } } + }; + + if(swap) + { + // Swap + auto leftSet = getSrcHolderArtSet(); + auto rightSet = getDstHolderArtSet(); + 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, getSrcHolderArtSet(), EBulkArtsOp::BULK_MOVE); } } @@ -1712,4 +1763,24 @@ DLL_LINKAGE void EntitiesChanged::applyGs(CGameState * gs) gs->updateEntity(change.metatype, change.entityIndex, change.data); } +const CArtifactInstance * ArtSlotInfo::getArt() const +{ + if(locked) + { + logNetwork->warn("ArtifactLocation::getArt: This location is locked!"); + return nullptr; + } + return artifact; +} + +CArtifactSet * BulkMoveArtifacts::getSrcHolderArtSet() +{ + return boost::apply_visitor(GetBase(), srcArtHolder); +} + +CArtifactSet * BulkMoveArtifacts::getDstHolderArtSet() +{ + return boost::apply_visitor(GetBase(), dstArtHolder); +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 519643e33..d2c67e8c1 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -319,6 +319,7 @@ void registerTypesClientPacks2(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); + s.template registerType(); s.template registerType(); s.template registerType(); @@ -358,6 +359,7 @@ void registerTypesServerPacks(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); + s.template registerType(); } template diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index a4f8dedad..f6e628382 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3916,10 +3916,101 @@ bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocat moveArtifact(dst, ArtifactLocation(dst.artHolder, ArtifactPosition( (si32)dst.getHolderArtSet()->artifactsInBackpack.size() + GameConstants::BACKPACK_START))); } + auto hero = boost::get>(dst.artHolder); + if(ArtifactUtils::checkSpellbookIsNeeded(hero, srcArtifact->artType->id, dst.slot)) + giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); - MoveArtifact ma; - ma.src = src; - ma.dst = dst; + MoveArtifact ma(&src, &dst); + sendAndApply(&ma); + return true; +} + +bool CGameHandler::bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap) +{ + // Make sure exchange is even possible between the two heroes. + if(!isAllowedExchange(srcHero, dstHero)) + COMPLAIN_RET("That heroes cannot make any exchange!"); + + auto psrcHero = getHero(srcHero); + auto pdstHero = getHero(dstHero); + if((!psrcHero) || (!pdstHero)) + COMPLAIN_RET("bulkMoveArtifacts: wrong hero's ID"); + + BulkMoveArtifacts ma(static_cast>(psrcHero), + static_cast>(pdstHero), swap); + auto & slotsSrcDst = ma.artsPack0; + auto & slotsDstSrc = ma.artsPack1; + + if(swap) + { + auto moveArtsWorn = [this](const CGHeroInstance * srcHero, const CGHeroInstance * dstHero, + std::vector & slots) -> void + { + for(auto & artifact : srcHero->artifactsWorn) + { + if(artifact.second.locked) + continue; + if(!ArtifactUtils::isArtRemovable(artifact)) + continue; + slots.push_back(BulkMoveArtifacts::LinkedSlots(artifact.first, artifact.first)); + + auto art = artifact.second.getArt(); + assert(art); + if(ArtifactUtils::checkSpellbookIsNeeded(dstHero, art->artType->id, artifact.first)) + giveHeroNewArtifact(dstHero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); + } + }; + auto moveArtsInBackpack = [](const CGHeroInstance * pHero, + std::vector & slots) -> void + { + for(auto & slotInfo : pHero->artifactsInBackpack) + { + auto slot = pHero->getArtPos(slotInfo.artifact); + slots.push_back(BulkMoveArtifacts::LinkedSlots(slot, slot)); + } + }; + // Move over artifacts that are worn srcHero -> dstHero + moveArtsWorn(psrcHero, pdstHero, slotsSrcDst); + // Move over artifacts that are worn dstHero -> srcHero + moveArtsWorn(pdstHero, psrcHero, slotsDstSrc); + // Move over artifacts that are in backpack srcHero -> dstHero + moveArtsInBackpack(psrcHero, slotsSrcDst); + // Move over artifacts that are in backpack dstHero -> srcHero + moveArtsInBackpack(pdstHero, slotsDstSrc); + } + else + { + // Temporary fitting set for artifacts. Used to select available slots before sending data. + CArtifactFittingSet artFittingSet(pdstHero->bearerType()); + artFittingSet.artifactsInBackpack = pdstHero->artifactsInBackpack; + artFittingSet.artifactsWorn = pdstHero->artifactsWorn; + + auto moveArtifact = [this, &artFittingSet, &slotsSrcDst](const CArtifactInstance * artifact, + ArtifactPosition srcSlot, const CGHeroInstance * pdstHero) -> void + { + assert(artifact); + auto dstSlot = ArtifactUtils::getArtifactDstPosition(artifact, &artFittingSet, pdstHero->bearerType()); + artFittingSet.putArtifact(dstSlot, static_cast>(artifact)); + slotsSrcDst.push_back(BulkMoveArtifacts::LinkedSlots(srcSlot, dstSlot)); + + if(ArtifactUtils::checkSpellbookIsNeeded(pdstHero, artifact->artType->id, dstSlot)) + giveHeroNewArtifact(pdstHero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); + }; + + // Move over artifacts that are worn + for(auto & artInfo : psrcHero->artifactsWorn) + { + if(ArtifactUtils::isArtRemovable(artInfo)) + { + moveArtifact(psrcHero->getArt(artInfo.first), artInfo.first, pdstHero); + } + } + // Move over artifacts that are in backpack + for(auto & slotInfo : psrcHero->artifactsInBackpack) + { + moveArtifact(psrcHero->getArt(psrcHero->getArtPos(slotInfo.artifact)), psrcHero->getArtPos(slotInfo.artifact), pdstHero); + } + } sendAndApply(&ma); return true; } @@ -3947,6 +4038,9 @@ bool CGameHandler::assembleArtifacts (ObjectInstanceID heroID, ArtifactPosition COMPLAIN_RET("assembleArtifacts: Artifact being attempted to assemble is not a combined artifacts!"); if (!vstd::contains(destArtifact->assemblyPossibilities(hero), combinedArt)) COMPLAIN_RET("assembleArtifacts: It's impossible to assemble requested artifact!"); + + if(ArtifactUtils::checkSpellbookIsNeeded(hero, assembleTo, artifactSlot)) + giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); AssembledArtifact aa; aa.al = ArtifactLocation(hero, artifactSlot); diff --git a/server/CGameHandler.h b/server/CGameHandler.h index e2b68c312..5d9800e49 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -180,7 +180,8 @@ public: void giveHeroArtifact(const CGHeroInstance *h, const CArtifactInstance *a, ArtifactPosition pos) override; void putArtifact(const ArtifactLocation &al, const CArtifactInstance *a) override; void removeArtifact(const ArtifactLocation &al) override; - bool moveArtifact(const ArtifactLocation &al1, const ArtifactLocation &al2) override; + bool moveArtifact(const ArtifactLocation & al1, const ArtifactLocation & al2) override; + bool bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap); void synchronizeArtifactHandlerLists(); void showCompInfo(ShowInInfobox * comp) override; diff --git a/server/CQuery.cpp b/server/CQuery.cpp index 4f8270a6f..7c1df47be 100644 --- a/server/CQuery.cpp +++ b/server/CQuery.cpp @@ -370,6 +370,9 @@ bool CGarrisonDialogQuery::blocksPack(const CPack * pack) const } if(auto dismiss = dynamic_ptr_cast(pack)) return !vstd::contains(ourIds, dismiss->id); + + if(auto arts = dynamic_ptr_cast(pack)) + return !vstd::contains(ourIds, arts->srcHero) || !vstd::contains(ourIds, arts->dstHero); if(auto dismiss = dynamic_ptr_cast(pack)) return !vstd::contains(ourIds, dismiss->heroID); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 907a0e470..bc80920a3 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -180,6 +180,13 @@ bool ExchangeArtifacts::applyGh(CGameHandler * gh) return gh->moveArtifact(src, dst); } +bool BulkExchangeArtifacts::applyGh(CGameHandler * gh) +{ + const CGHeroInstance * pSrcHero = gh->getHero(srcHero); + throwOnWrongPlayer(gh, pSrcHero->getOwner()); + return gh->bulkMoveArtifacts(srcHero, dstHero, swap); +} + bool AssembleArtifacts::applyGh(CGameHandler * gh) { throwOnWrongOwner(gh, heroID);