1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-22 22:13:35 +02:00
vcmi/client/windows/CWindowWithArtifacts.cpp

568 lines
17 KiB
C++

/*
* 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 "CHeroWindow.h"
#include "CSpellWindow.h"
#include "CExchangeWindow.h"
#include "CHeroBackpackWindow.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 "../widgets/CComponent.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"
CWindowWithArtifacts::CWindowWithArtifacts(const std::vector<CArtifactsOfHeroPtr> * artSets)
{
if(artSets)
this->artSets.insert(this->artSets.end(), artSets->begin(), artSets->end());
}
void CWindowWithArtifacts::addSet(CArtifactsOfHeroPtr newArtSet)
{
artSets.emplace_back(newArtSet);
}
void CWindowWithArtifacts::addSetAndCallbacks(CArtifactsOfHeroPtr newArtSet)
{
addSet(newArtSet);
std::visit([this](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);
}, newArtSet);
}
void CWindowWithArtifacts::addCloseCallback(const CloseCallback & callback)
{
closeCallback = callback;
}
const CGHeroInstance * CWindowWithArtifacts::getHeroPickedArtifact()
{
auto res = getState();
if(res.has_value())
return std::get<const CGHeroInstance*>(res.value());
else
return nullptr;
}
const CArtifactInstance * CWindowWithArtifacts::getPickedArtifact()
{
auto res = getState();
if(res.has_value())
return std::get<const CArtifactInstance*>(res.value());
else
return nullptr;
}
void CWindowWithArtifacts::clickPressedArtPlaceHero(const CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition)
{
const auto currentArtSet = findAOHbyRef(artsInst);
assert(currentArtSet.has_value());
if(artPlace.isLocked())
return;
if (!LOCPLINT->makingTurn)
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<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMain>> ||
std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroKingdom>> ||
std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroAltar>> ||
std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroBackpack>>)
{
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<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroKingdom>>)
{
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<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroAltar>> ? true : false))
{
assert(artSetPtr->getHero()->getSlotByInstance(art) != ArtifactPosition::PRE_FIRST);
if(GH.isKeyboardCmdDown())
{
std::shared_ptr<CArtifactsOfHeroMain> anotherHeroEquipmentPointer = nullptr;
for(auto set : artSets)
{
if(std::holds_alternative<std::weak_ptr<CArtifactsOfHeroMain>>(set))
{
std::shared_ptr<CArtifactsOfHeroMain> heroEquipmentPointer = std::get<std::weak_ptr<CArtifactsOfHeroMain>>(set).lock();
if(heroEquipmentPointer->getHero()->id != artSetPtr->getHero()->id)
{
anotherHeroEquipmentPointer = heroEquipmentPointer;
break;
}
}
}
if(anotherHeroEquipmentPointer != nullptr)
{
ArtifactPosition availablePosition = ArtifactUtils::getArtAnyPosition(anotherHeroEquipmentPointer->getHero(), art->getTypeId());
if(availablePosition != ArtifactPosition::PRE_FIRST)
{
LOCPLINT->cb->swapArtifacts(ArtifactLocation(artSetPtr->getHero()->id, artSetPtr->getHero()->getSlotByInstance(art)),
ArtifactLocation(anotherHeroEquipmentPointer->getHero()->id, availablePosition));
}
}
}
else if(GH.isKeyboardAltDown())
{
ArtifactPosition destinationPosition = ArtifactPosition::PRE_FIRST;
if(ArtifactUtils::isSlotEquipment(artPlace.slot))
{
ArtifactPosition availablePosition = ArtifactUtils::getArtBackpackPosition(artSetPtr->getHero(), art->getTypeId());
if(availablePosition != ArtifactPosition::PRE_FIRST)
{
destinationPosition = availablePosition;
}
}
else if(ArtifactUtils::isSlotBackpack(artPlace.slot))
{
ArtifactPosition availablePosition = ArtifactUtils::getArtAnyPosition(artSetPtr->getHero(), art->getTypeId());
if(availablePosition != ArtifactPosition::PRE_FIRST && availablePosition != ArtifactPosition::BACKPACK_START)
{
destinationPosition = availablePosition;
}
}
if(destinationPosition != ArtifactPosition::PRE_FIRST)
{
LOCPLINT->cb->swapArtifacts(ArtifactLocation(artSetPtr->getHero()->id, artPlace.slot),
ArtifactLocation(artSetPtr->getHero()->id, destinationPosition));
}
}
else
{
LOCPLINT->cb->swapArtifacts(ArtifactLocation(artSetPtr->getHero()->id, artPlace.slot),
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<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroBackpack>>)
{
if(!isTransferAllowed && artPlace.getArt() && closeCallback)
closeCallback();
}
else
{
if(!msg.empty())
LOCPLINT->showInfoDialog(msg);
}
}
// Market window left click handler
else if constexpr(std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMarket>>)
{
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<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroQuickBackpack>>)
{
const auto hero = artSetPtr->getHero();
LOCPLINT->cb->swapArtifacts(ArtifactLocation(hero->id, artPlace.slot), ArtifactLocation(hero->id, artSetPtr->getFilterSlot()));
if(closeCallback)
closeCallback();
}
}, currentArtSet.value());
}
void CWindowWithArtifacts::showPopupArtPlaceHero(const CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition)
{
const auto currentArtSet = findAOHbyRef(artsInst);
assert(currentArtSet.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<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroAltar>> ||
std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMain>> ||
std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroKingdom>> ||
std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroBackpack>>)
{
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<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(cursorPosition);
}
}, currentArtSet.value());
}
void CWindowWithArtifacts::gestureArtPlaceHero(const CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition)
{
const auto currentArtSet = findAOHbyRef(artsInst);
assert(currentArtSet.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>>)
{
if(!settings["general"]["enableUiEnhancements"].Bool())
return;
GH.windows().createAndPushWindow<CHeroQuickBackpackWindow>(artSetPtr->getHero(), artPlace.slot);
auto backpackWindow = GH.windows().topWindow<CHeroQuickBackpackWindow>();
backpackWindow->moveTo(cursorPosition - Point(1, 1));
backpackWindow->fitToScreen(15);
}
}, currentArtSet.value());
}
void CWindowWithArtifacts::activate()
{
if(const auto art = getPickedArtifact())
setCursorAnimation(*art);
CWindowObject::activate();
}
void CWindowWithArtifacts::deactivate()
{
CCS->curh->dragAndDropCursor(nullptr);
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();
}
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;
auto pickedArtInst = std::get<const CArtifactInstance*>(curState.value());
auto artifactMovedBody = [this, withRedraw, &destLoc, &pickedArtInst](auto artSetWeak) -> void
{
auto artSetPtr = artSetWeak.lock();
if(artSetPtr)
{
const auto hero = artSetPtr->getHero();
if(pickedArtInst)
{
setCursorAnimation(*pickedArtInst);
}
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<CHeroWindow*>(this))
{
chw->update(hero, true);
}
else if(auto * cew = dynamic_cast<CExchangeWindow*>(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)
{
update();
}
void CWindowWithArtifacts::artifactAssembled(const ArtifactLocation & artLoc)
{
markPossibleSlots();
update();
}
void CWindowWithArtifacts::update() const
{
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<std::tuple<const CGHeroInstance*, const CArtifactInstance*>> CWindowWithArtifacts::getState()
{
const CArtifactInstance * artInst = nullptr;
std::map<const CGHeroInstance*, size_t> 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::CArtifactsOfHeroPtr> CWindowWithArtifacts::findAOHbyRef(const CArtifactsOfHeroBase & artsInst)
{
std::optional<CArtifactsOfHeroPtr> 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<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroKingdom>>)
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
{
const auto artId = artInst.getTypeId();
if(artId == ArtifactID::SPELLBOOK)
{
GH.windows().createAndPushWindow<CSpellWindow>(hero, LOCPLINT, LOCPLINT->battleInt.get());
return false;
}
if(artId == ArtifactID::CATAPULT)
{
// The Catapult must be equipped
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[312],
std::vector<std::shared_ptr<CComponent>>(1, std::make_shared<CComponent>(ComponentType::ARTIFACT, ArtifactID(ArtifactID::CATAPULT))));
return false;
}
if(isTrade && !artInst.artType->isTradable())
{
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[21],
std::vector<std::shared_ptr<CComponent>>(1, std::make_shared<CComponent>(ComponentType::ARTIFACT, artId)));
return false;
}
return true;
}
void CWindowWithArtifacts::setCursorAnimation(const CArtifactInstance & artInst)
{
markPossibleSlots();
if(artInst.isScroll() && settings["general"]["enableUiEnhancements"].Bool())
{
assert(artInst.getScrollSpellID().num >= 0);
const auto animation = GH.renderHandler().loadAnimation(AnimationPath::builtin("spellscr"));
animation->load(artInst.getScrollSpellID().num);
CCS->curh->dragAndDropCursor(animation->getImage(artInst.getScrollSpellID().num)->scaleFast(Point(44, 34)));
}
else
{
CCS->curh->dragAndDropCursor(AnimationPath::builtin("artifact"), artInst.artType->getIconIndex());
}
}