/* * CWindowWithArtifacts.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * * License: GNU General Public License v2.0 or later * Full text of license available in license.txt file, in main folder * */ #include "StdInc.h" #include "CWindowWithArtifacts.h" #include "../gui/CGuiHandler.h" #include "../gui/CursorHandler.h" #include "../gui/WindowHandler.h" #include "../render/IRenderHandler.h" #include "../render/CAnimation.h" #include "../render/IImage.h" #include "CComponent.h" #include "../windows/CHeroWindow.h" #include "../windows/CSpellWindow.h" #include "../windows/GUIClasses.h" #include "../windows/CHeroBackpackWindow.h" #include "../CPlayerInterface.h" #include "../CGameInfo.h" #include "../../lib/ArtifactUtils.h" #include "../../lib/CGeneralTextHandler.h" #include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/networkPacks/ArtifactLocation.h" #include "../../lib/CConfigHandler.h" #include "../../CCallback.h" void CWindowWithArtifacts::addSet(CArtifactsOfHeroPtr artSet) { artSets.emplace_back(artSet); } void CWindowWithArtifacts::addSetAndCallbacks(CArtifactsOfHeroPtr artSet) { CArtifactsOfHeroBase::PutBackPickedArtCallback artPutBackFunctor = []() -> void { CCS->curh->dragAndDropCursor(nullptr); }; addSet(artSet); std::visit([this, artPutBackFunctor](auto artSetWeak) { auto artSet = artSetWeak.lock(); 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); } void CWindowWithArtifacts::addCloseCallback(CloseCallback callback) { closeCallback = callback; } const CGHeroInstance * CWindowWithArtifacts::getHeroPickedArtifact() { auto res = getState(); if(res.has_value()) return std::get(res.value()); else return nullptr; } const CArtifactInstance * CWindowWithArtifacts::getPickedArtifact() { auto res = getState(); if(res.has_value()) return std::get(res.value()); else return nullptr; } void CWindowWithArtifacts::clickPressedArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition) { const auto artSet = findAOHbyRef(artsInst); assert(artSet.has_value()); if(artPlace.isLocked()) return; std::visit( [this, &artPlace](auto artSetWeak) -> void { const auto artSetPtr = artSetWeak.lock(); // Hero(Main, Exchange) window, Kingdom window, Altar window, Backpack window left click handler if constexpr( std::is_same_v> || std::is_same_v> || std::is_same_v> || std::is_same_v>) { const auto pickedArtInst = getPickedArtifact(); const auto heroPickedArt = getHeroPickedArtifact(); const auto hero = artSetPtr->getHero(); auto isTransferAllowed = false; std::string msg; if(pickedArtInst) { auto srcLoc = ArtifactLocation(heroPickedArt->id, ArtifactPosition::TRANSITION_POS); auto dstLoc = ArtifactLocation(hero->id, artPlace.slot); if(ArtifactUtils::isSlotBackpack(artPlace.slot)) { if(pickedArtInst->artType->isBig()) { // War machines cannot go to backpack msg = boost::str(boost::format(CGI->generaltexth->allTexts[153]) % pickedArtInst->artType->getNameTranslated()); } else { if(ArtifactUtils::isBackpackFreeSlots(heroPickedArt)) isTransferAllowed = true; else msg = CGI->generaltexth->translate("core.genrltxt.152"); } } // Check if artifact transfer is possible else if(pickedArtInst->canBePutAt(hero, artPlace.slot, true) && (!artPlace.getArt() || hero->tempOwner == LOCPLINT->playerID)) { isTransferAllowed = true; } if constexpr(std::is_same_v>) { if(hero != heroPickedArt) isTransferAllowed = false; } if(isTransferAllowed) LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc); } else if(auto art = artPlace.getArt()) { if(artSetPtr->getHero()->getOwner() == LOCPLINT->playerID) { if(checkSpecialArts(*art, hero, std::is_same_v> ? true : false)) { assert(artSetPtr->getHero()->getSlotByInstance(art)); LOCPLINT->cb->swapArtifacts(ArtifactLocation(artSetPtr->getHero()->id, artSetPtr->getHero()->getSlotByInstance(art)), ArtifactLocation(artSetPtr->getHero()->id, ArtifactPosition::TRANSITION_POS)); } } else { for(const auto artSlot : ArtifactUtils::unmovableSlots()) if(artPlace.slot == artSlot) { msg = CGI->generaltexth->allTexts[21]; break; } } } if constexpr(std::is_same_v>) { if(!isTransferAllowed && artPlace.getArt()) { if(closeCallback) closeCallback(); } } else { if(!msg.empty()) LOCPLINT->showInfoDialog(msg); } } // Market window left click handler else if constexpr(std::is_same_v>) { if(artSetPtr->selectArtCallback && artPlace.getArt()) { if(artPlace.getArt()->artType->isTradable()) { artSetPtr->unmarkSlots(); artPlace.selectSlot(true); artSetPtr->selectArtCallback(&artPlace); } else { // This item can't be traded LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[21]); } } } else if constexpr(std::is_same_v>) { const auto hero = artSetPtr->getHero(); LOCPLINT->cb->swapArtifacts(ArtifactLocation(hero->id, artPlace.slot), ArtifactLocation(hero->id, artSetPtr->getFilterSlot())); if(closeCallback) closeCallback(); } }, artSet.value()); } void CWindowWithArtifacts::showPopupArtPlaceHero(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(); // Hero (Main, Exchange) window, Kingdom window, Backpack window right click handler if constexpr( std::is_same_v> || std::is_same_v> || std::is_same_v> || std::is_same_v>) { if(artPlace.getArt()) { if(ArtifactUtilsClient::askToDisassemble(artSetPtr->getHero(), artPlace.slot)) { return; } if(ArtifactUtilsClient::askToAssemble(artSetPtr->getHero(), artPlace.slot)) { return; } if(artPlace.text.size()) artPlace.LRClickableAreaWTextComp::showPopupWindow(cursorPosition); } } // Altar window, Market window right click handler else if constexpr( std::is_same_v> || std::is_same_v>) { if(artPlace.getArt() && artPlace.text.size()) 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> || std::is_same_v>) { if(!settings["general"]["enableUiEnhancements"].Bool()) return; GH.windows().createAndPushWindow(artSetPtr->getHero(), artPlace.slot); auto backpackWindow = GH.windows().topWindow(); backpackWindow->moveTo(cursorPosition - Point(1, 1)); backpackWindow->fitToScreen(15); } }, artSetWeak.value()); } void CWindowWithArtifacts::artifactRemoved(const ArtifactLocation & artLoc) { updateSlots(); } void CWindowWithArtifacts::artifactMoved(const ArtifactLocation & srcLoc, const ArtifactLocation & destLoc, bool withRedraw) { auto curState = getState(); if(!curState.has_value()) // Transition state. Nothing to do here. Just skip. Need to wait for final state. return; // When moving one artifact onto another it leads to two art movements: dst->TRANSITION_POS; src->dst // However after first movement we pick the art from TRANSITION_POS and the second movement coming when // we have a different artifact may look surprising... but it's valid. auto pickedArtInst = std::get(curState.value()); assert(!pickedArtInst || destLoc.artHolder == std::get(curState.value())->id); auto artifactMovedBody = [this, withRedraw, &destLoc, &pickedArtInst](auto artSetWeak) -> void { auto artSetPtr = artSetWeak.lock(); if(artSetPtr) { const auto hero = artSetPtr->getHero(); if(pickedArtInst) { markPossibleSlots(); if(pickedArtInst->getTypeId() == ArtifactID::SPELL_SCROLL && pickedArtInst->getScrollSpellID().num >= 0 && settings["general"]["enableUiEnhancements"].Bool()) { auto anim = GH.renderHandler().loadAnimation(AnimationPath::builtin("spellscr")); anim->load(pickedArtInst->getScrollSpellID().num); std::shared_ptr img = anim->getImage(pickedArtInst->getScrollSpellID().num); CCS->curh->dragAndDropCursor(img->scaleFast(Point(44, 34))); } else CCS->curh->dragAndDropCursor(AnimationPath::builtin("artifact"), pickedArtInst->artType->getIconIndex()); } else { artSetPtr->unmarkSlots(); CCS->curh->dragAndDropCursor(nullptr); } if(withRedraw) { artSetPtr->updateWornSlots(); artSetPtr->updateBackpackSlots(); // Update arts bonuses on window. // TODO rework this part when CHeroWindow and CExchangeWindow are reworked if(auto * chw = dynamic_cast(this)) { chw->update(hero, true); } else if(auto * cew = dynamic_cast(this)) { cew->updateWidgets(); } artSetPtr->redraw(); } // Make sure the status bar is updated so it does not display old text if(destLoc.artHolder == hero->id) { if(auto artPlace = artSetPtr->getArtPlace(destLoc.slot)) artPlace->hover(true); } } }; for(auto artSetWeak : artSets) std::visit(artifactMovedBody, artSetWeak); } void CWindowWithArtifacts::artifactDisassembled(const ArtifactLocation & artLoc) { updateSlots(); } void CWindowWithArtifacts::artifactAssembled(const ArtifactLocation & artLoc) { markPossibleSlots(); updateSlots(); } void CWindowWithArtifacts::updateSlots() { auto updateSlotBody = [](auto artSetWeak) -> void { if(const auto artSetPtr = artSetWeak.lock()) { artSetPtr->updateWornSlots(); artSetPtr->updateBackpackSlots(); artSetPtr->redraw(); } }; for(auto artSetWeak : artSets) std::visit(updateSlotBody, artSetWeak); } std::optional> CWindowWithArtifacts::getState() { const CArtifactInstance * artInst = nullptr; std::map pickedCnt; auto getHeroArtBody = [&artInst, &pickedCnt](auto artSetWeak) -> void { auto artSetPtr = artSetWeak.lock(); if(artSetPtr) { if(const auto art = artSetPtr->getPickedArtifact()) { const auto hero = artSetPtr->getHero(); if(pickedCnt.count(hero) == 0) { pickedCnt.insert({ hero, hero->artifactsTransitionPos.size() }); artInst = art; } } } }; for(auto artSetWeak : artSets) std::visit(getHeroArtBody, artSetWeak); // The state is possible when the hero has placed an artifact in the ArtifactPosition::TRANSITION_POS, // and the previous artifact has not yet removed from the ArtifactPosition::TRANSITION_POS. // This is a transitional state. Then return nullopt. if(std::accumulate(std::begin(pickedCnt), std::end(pickedCnt), 0, [](size_t accum, const auto & value) { return accum + value.second; }) > 1) return std::nullopt; else return std::make_tuple(pickedCnt.begin()->first, artInst); } std::optional CWindowWithArtifacts::findAOHbyRef(CArtifactsOfHeroBase & artsInst) { std::optional res; auto findAOHBody = [&res, &artsInst](auto & artSetWeak) -> void { if(&artsInst == artSetWeak.lock().get()) res = artSetWeak; }; for(auto artSetWeak : artSets) { std::visit(findAOHBody, artSetWeak); if(res.has_value()) return res; } return res; } void CWindowWithArtifacts::markPossibleSlots() { if(const auto pickedArtInst = getPickedArtifact()) { const auto heroArtOwner = getHeroPickedArtifact(); auto artifactAssembledBody = [&pickedArtInst, &heroArtOwner](auto artSetWeak) -> void { if(auto artSetPtr = artSetWeak.lock()) { if(artSetPtr->isActive()) { const auto hero = artSetPtr->getHero(); if(heroArtOwner == hero || !std::is_same_v>) artSetPtr->markPossibleSlots(pickedArtInst, hero->tempOwner == LOCPLINT->playerID); } } }; for(auto artSetWeak : artSets) std::visit(artifactAssembledBody, artSetWeak); } } bool CWindowWithArtifacts::checkSpecialArts(const CArtifactInstance & artInst, const CGHeroInstance * hero, bool isTrade) { const auto artId = artInst.getTypeId(); if(artId == ArtifactID::SPELLBOOK) { GH.windows().createAndPushWindow(hero, LOCPLINT, LOCPLINT->battleInt.get()); return false; } if(artId == ArtifactID::CATAPULT) { // The Catapult must be equipped LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[312], std::vector>(1, std::make_shared(ComponentType::ARTIFACT, ArtifactID(ArtifactID::CATAPULT)))); return false; } if(isTrade) { if(!artInst.artType->isTradable()) { LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[21], std::vector>(1, std::make_shared(ComponentType::ARTIFACT, artId))); return false; } } return true; }