#include "StarQuestInterface.hpp" #include "StarQuestManager.hpp" #include "StarCinematic.hpp" #include "StarGuiReader.hpp" #include "StarRoot.hpp" #include "StarUniverseClient.hpp" #include "StarPaneManager.hpp" #include "StarListWidget.hpp" #include "StarItemGridWidget.hpp" #include "StarButtonWidget.hpp" #include "StarLabelWidget.hpp" #include "StarLabelWidget.hpp" #include "StarImageWidget.hpp" #include "StarAssets.hpp" #include "StarItemDatabase.hpp" #include "StarRandom.hpp" #include "StarJsonExtra.hpp" #include "StarPlayer.hpp" #include "StarVerticalLayout.hpp" #include "StarItemTooltip.hpp" namespace Star { QuestLogInterface::QuestLogInterface(QuestManagerPtr manager, PlayerPtr player, CinematicPtr cinematic, UniverseClientPtr client) { m_manager = manager; m_player = player; m_cinematic = cinematic; m_client = client; auto assets = Root::singleton().assets(); auto config = assets->json("/interface/windowconfig/questlog.config"); m_trackLabel = config.getString("trackLabel"); m_untrackLabel = config.getString("untrackLabel"); GuiReader reader; reader.registerCallback("close", [=](Widget*) { dismiss(); }); reader.registerCallback("btnToggleTracking", [=](Widget*) { toggleTracking(); }); reader.registerCallback("btnAbandon", [=](Widget*) { abandon(); }); reader.registerCallback("filter", [=](Widget*) { fetchData(); }); reader.construct(config.get("paneLayout"), this); auto mainQuestList = fetchChild("scrollArea.verticalLayout.mainQuestList"); auto sideQuestList = fetchChild("scrollArea.verticalLayout.sideQuestList"); mainQuestList->setCallback([sideQuestList](Widget* widget) { auto listWidget = as(widget); if (listWidget->selectedItem() != NPos) sideQuestList->clearSelected(); }); sideQuestList->setCallback([mainQuestList](Widget* widget) { auto listWidget = as(widget); if (listWidget->selectedItem() != NPos) mainQuestList->clearSelected(); }); mainQuestList->disableScissoring(); sideQuestList->disableScissoring(); m_rewardItems = make_shared(5); fetchChild("rewardItems")->setItemBag(m_rewardItems); m_refreshRate = 30; m_refreshTimer = 0; } void QuestLogInterface::pollDialog(PaneManager* paneManager) { if (paneManager->topPane({PaneLayer::ModalWindow})) return; if (auto failableQuest = m_manager->getFirstFailableQuest()) { auto qfi = make_shared(failableQuest.value(), m_player); (*failableQuest)->setDialogShown(); paneManager->displayPane(PaneLayer::ModalWindow, qfi); } else if (auto completableQuest = m_manager->getFirstCompletableQuest()) { auto qci = make_shared(completableQuest.value(), m_player, m_cinematic); (*completableQuest)->setDialogShown(); paneManager->displayPane(PaneLayer::ModalWindow, qci); } else if (auto newQuest = m_manager->getFirstNewQuest()) { auto nqd = make_shared(m_manager, newQuest.value(), m_player); paneManager->displayPane(PaneLayer::ModalWindow, nqd); } } void QuestLogInterface::displayed() { Pane::displayed(); tick(0); fetchData(); } void QuestLogInterface::tick(float dt) { Pane::tick(dt); auto selected = getSelected(); if (selected && m_manager->hasQuest(selected->data().toString())) { auto quest = m_manager->getQuest(selected->data().toString()); m_manager->markAsRead(quest->questId()); if (m_manager->isActive(quest->questId())) { fetchChild("btnToggleTracking")->enable(); fetchChild("btnToggleTracking")->setText(m_manager->isCurrent(quest->questId()) ? m_untrackLabel : m_trackLabel); if (quest->canBeAbandoned()) fetchChild("btnAbandon")->enable(); else fetchChild("btnAbandon")->disable(); } else { fetchChild("btnToggleTracking")->disable(); fetchChild("btnToggleTracking")->setText(m_trackLabel); fetchChild("btnAbandon")->disable(); } fetchChild("lblQuestTitle")->setText(quest->title()); fetchChild("lblQuestBody")->setText(quest->text()); auto portraitName = "Objective"; auto imagePortrait = quest->portrait(portraitName); if (imagePortrait) { auto portraitTitleLabel = fetchChild("lblPortraitTitle"); String portraitTitle = quest->portraitTitle(portraitName).value(""); Maybe charLimit = portraitTitleLabel->getTextCharLimit(); portraitTitleLabel->setText(portraitTitle); Drawable::scaleAll(*imagePortrait, Vec2F(-1, 1)); fetchChild("imgPortrait")->setDrawables(*imagePortrait); fetchChild("imgPolaroid")->setVisibility(true); fetchChild("imgPolaroidBack")->setVisibility(true); } else { fetchChild("lblPortraitTitle")->setText(""); fetchChild("imgPortrait")->setDrawables({}); fetchChild("imgPolaroid")->setVisibility(false); fetchChild("imgPolaroidBack")->setVisibility(false); } m_rewardItems->clearItems(); if (quest->rewards().size() > 0) { fetchChild("lblRewards")->setVisibility(true); fetchChild("rewardItems")->setVisibility(true); for (auto const& reward : quest->rewards()) m_rewardItems->addItems(reward->clone()); } else { fetchChild("lblRewards")->setVisibility(false); fetchChild("rewardItems")->setVisibility(false); } } else { fetchChild("btnToggleTracking")->disable(); fetchChild("btnToggleTracking")->setText(m_trackLabel); fetchChild("btnAbandon")->disable(); fetchChild("lblQuestTitle")->setText(""); fetchChild("lblQuestBody")->setText(""); fetchChild("lblPortraitTitle")->setText(""); fetchChild("imgPortrait")->setDrawables({}); fetchChild("lblRewards")->setVisibility(false); fetchChild("rewardItems")->setVisibility(false); fetchChild("imgPolaroid")->setVisibility(false); fetchChild("imgPolaroidBack")->setVisibility(false); m_rewardItems->clearItems(); } m_refreshTimer--; if (m_refreshTimer < 0) { fetchData(); m_refreshTimer = m_refreshRate; } } PanePtr QuestLogInterface::createTooltip(Vec2I const& screenPosition) { ItemPtr item; if (auto child = getChildAt(screenPosition)) { if (auto itemSlot = as(child)) item = itemSlot->item(); if (auto itemGrid = as(child)) item = itemGrid->itemAt(screenPosition); } if (item) return ItemTooltipBuilder::buildItemTooltip(item, m_player); return {}; } WidgetPtr QuestLogInterface::getSelected() { auto mainQuestList = fetchChild("scrollArea.verticalLayout.mainQuestList"); if (auto selected = mainQuestList->selectedWidget()) return selected; auto sideQuestList = fetchChild("scrollArea.verticalLayout.sideQuestList"); if (auto selected = sideQuestList->selectedWidget()) return selected; return {}; } void QuestLogInterface::setSelected(WidgetPtr selected) { auto mainQuestList = fetchChild("scrollArea.verticalLayout.mainQuestList"); auto mainQuestListPos = mainQuestList->itemPosition(selected); if (mainQuestListPos != NPos) { mainQuestList->setSelected(mainQuestListPos); return; } auto sideQuestList = fetchChild("scrollArea.verticalLayout.sideQuestList"); auto sideQuestListPos = sideQuestList->itemPosition(selected); if (sideQuestListPos != NPos) { sideQuestList->setSelected(sideQuestListPos); return; } } void QuestLogInterface::toggleTracking() { if (auto selected = getSelected()) { String questId = selected->data().toString(); if (!m_manager->isCurrent(questId)) m_manager->setAsTracked(questId); else m_manager->setAsTracked({}); } } void QuestLogInterface::abandon() { if (auto selected = getSelected()) { m_manager->getQuest(selected->data().toString())->abandon(); } } void QuestLogInterface::fetchData() { auto filter = fetchChild("filter")->checkedButton()->data().toString(); if (filter.equalsIgnoreCase("inProgress")) showQuests(m_manager->listActiveQuests()); else if (filter.equalsIgnoreCase("completed")) showQuests(m_manager->listCompletedQuests()); else throw StarException(strf("Unknown quest filter '{}'", filter)); } void QuestLogInterface::showQuests(List quests) { auto mainQuestList = fetchChild("scrollArea.verticalLayout.mainQuestList"); auto sideQuestList = fetchChild("scrollArea.verticalLayout.sideQuestList"); auto mainQuestHeader = fetchChild("scrollArea.verticalLayout.mainQuestHeader"); auto sideQuestHeader = fetchChild("scrollArea.verticalLayout.sideQuestHeader"); String selectedQuest; if (auto selected = getSelected()) selectedQuest = selected->data().toString(); else if (m_manager->currentQuest()) selectedQuest = (*m_manager->currentQuest())->questId(); mainQuestList->clear(); mainQuestHeader->hide(); sideQuestList->clear(); sideQuestHeader->hide(); for (auto const& quest : quests) { WidgetPtr entry; if (quest->mainQuest()) { entry = mainQuestList->addItem(); mainQuestHeader->show(); } else { entry = sideQuestList->addItem(); sideQuestHeader->show(); } entry->setData(quest->questId()); entry->fetchChild("lblQuestEntry")->setText(quest->title()); entry->fetchChild("imgNew")->setVisibility(quest->unread()); entry->fetchChild("imgTracked")->setVisibility(m_manager->isCurrent(quest->questId())); bool currentWorld = false; if (auto questWorld = quest->worldId()) { currentWorld = m_client->playerWorld() == *questWorld; } entry->fetchChild("imgCurrent")->setVisibility(currentWorld); entry->fetchChild("imgPortrait")->setDrawables(quest->portrait("QuestStarted").value({})); entry->show(); if (quest->questId() == selectedQuest) setSelected(entry); } auto verticalLayout = fetchChild("scrollArea.verticalLayout"); verticalLayout->update(0); } QuestPane::QuestPane(QuestPtr const& quest, PlayerPtr player) : Pane(), m_quest(quest), m_player(std::move(player)) {} void QuestPane::commonSetup(Json config, String bodyText, String const& portraitName) { GuiReader reader; reader.registerCallback("close", [=](Widget*) { close(); }); reader.registerCallback("btnDecline", [=](Widget*) { decline(); }); reader.registerCallback("btnAccept", [=](Widget*) { accept(); }); reader.construct(config.get("paneLayout"), this); if (auto titleLabel = fetchChild("lblQuestTitle")) titleLabel->setText(m_quest->title()); if (auto bodyLabel = fetchChild("lblQuestBody")) bodyLabel->setText(bodyText); if (auto portraitImage = fetchChild("portraitImage")) { Maybe> portrait = m_quest->portrait(portraitName); portraitImage->setDrawables(portrait.value({})); } if (auto portraitTitleLabel = fetchChild("portraitTitle")) { Maybe portraitTitle = m_quest->portraitTitle(portraitName); String text = m_quest->portraitTitle(portraitName).value(""); Maybe charLimit = portraitTitleLabel->getTextCharLimit(); portraitTitleLabel->setText(text); } if (auto rewardItemsWidget = fetchChild("rewardItems")) { auto rewardItems = make_shared(5); for (auto const& reward : m_quest->rewards()) rewardItems->addItems(reward->clone()); rewardItemsWidget->setItemBag(rewardItems); } auto sound = Random::randValueFrom(config.get("onShowSound").toArray(), "").toString(); if (!sound.empty()) context()->playAudio(sound); } void QuestPane::close() { dismiss(); } void QuestPane::decline() { close(); } void QuestPane::accept() { close(); } PanePtr QuestPane::createTooltip(Vec2I const& screenPosition) { ItemPtr item; if (auto child = getChildAt(screenPosition)) { if (auto itemSlot = as(child)) item = itemSlot->item(); if (auto itemGrid = as(child)) item = itemGrid->itemAt(screenPosition); } if (item) return ItemTooltipBuilder::buildItemTooltip(item, m_player); return {}; } NewQuestInterface::NewQuestInterface(QuestManagerPtr const& manager, QuestPtr const& quest, PlayerPtr player) : QuestPane(quest, std::move(player)), m_manager(manager), m_decision(QuestDecision::Cancelled) { auto assets = Root::singleton().assets(); List objectivePortrait = m_quest->portrait("Objective").value({}); bool shortDialog = objectivePortrait.size() == 0; String configFile; if (shortDialog) configFile = m_quest->getTemplate()->newQuestGuiConfig.value(assets->json("/quests/quests.config:defaultGuiConfigs.newQuest").toString()); else configFile = m_quest->getTemplate()->newQuestGuiConfig.value(assets->json("/quests/quests.config:defaultGuiConfigs.newQuestPortrait").toString()); Json config = assets->json(configFile); commonSetup(config, m_quest->text(), "QuestStarted"); if (!m_quest->canBeAbandoned()) { if (auto declineButton = fetchChild("btnDecline")) declineButton->disable(); } if (!shortDialog) { if (auto objectivePortraitImage = fetchChild("objectivePortraitImage")) { Drawable::scaleAll(objectivePortrait, Vec2F(-1, 1)); objectivePortraitImage->setDrawables(objectivePortrait); String objectivePortraitTitle = m_quest->portraitTitle("Objective").value(""); auto portraitLabel = fetchChild("objectivePortraitTitle"); portraitLabel->setText(objectivePortraitTitle); portraitLabel->setVisibility(objectivePortrait.size() > 0); fetchChild("imgPolaroid")->setVisibility(objectivePortrait.size() > 0); fetchChild("imgPolaroidBack")->setVisibility(objectivePortrait.size() > 0); } } if (auto rewardItemsWidget = fetchChild("rewardItems")) rewardItemsWidget->setVisibility(m_quest->rewards().size() > 0); if (auto rewardsLabel = fetchChild("lblRewards")) rewardsLabel->setVisibility(m_quest->rewards().size() > 0); } void NewQuestInterface::close() { m_decision = QuestDecision::Cancelled; dismiss(); } void NewQuestInterface::decline() { m_decision = QuestDecision::Declined; dismiss(); } void NewQuestInterface::accept() { m_decision = QuestDecision::Accepted; dismiss(); } void NewQuestInterface::dismissed() { QuestPane::dismissed(); if (m_decision == QuestDecision::Declined && m_quest->canBeAbandoned()) { m_manager->getQuest(m_quest->questId())->declineOffer(); } else if (m_decision == QuestDecision::Accepted) { m_manager->getQuest(m_quest->questId())->start(); } else { m_manager->getQuest(m_quest->questId())->cancelOffer(); } } QuestCompleteInterface::QuestCompleteInterface(QuestPtr const& quest, PlayerPtr player, CinematicPtr cinematic) : QuestPane(quest, player) { auto assets = Root::singleton().assets(); String configFile = m_quest->getTemplate()->questCompleteGuiConfig.value(assets->json("/quests/quests.config:defaultGuiConfigs.questComplete").toString()); Json config = assets->json(configFile); m_player = player; m_cinematic = cinematic; commonSetup(config, m_quest->completionText(), "QuestComplete"); if (auto moneyLabel = fetchChild("lblMoneyAmount")) moneyLabel->setText(toString(m_quest->money())); disableScissoring(); } void QuestCompleteInterface::close() { auto assets = Root::singleton().assets(); if (m_quest->completionCinema() && m_cinematic) { String cinema = m_quest->completionCinema()->replaceTags( StringMap{{"species", m_player->species()}, {"gender", GenderNames.getRight(m_player->gender())}}); m_cinematic->load(assets->fetchJson(cinema)); } dismiss(); } QuestFailedInterface::QuestFailedInterface(QuestPtr const& quest, PlayerPtr player) : QuestPane(quest, std::move(player)) { auto assets = Root::singleton().assets(); String configFile = m_quest->getTemplate()->questFailedGuiConfig.value(assets->json("/quests/quests.config:defaultGuiConfigs.questFailed").toString()); Json config = assets->json(configFile); commonSetup(config, m_quest->failureText(), "QuestFailed"); disableScissoring(); } }