From 6352e8e3196f78388b6c771073f9e03eaa612673 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Tue, 20 Jun 2023 14:33:09 +1000 Subject: everything everywhere all at once --- source/frontend/StarMainInterface.cpp | 1472 +++++++++++++++++++++++++++++++++ 1 file changed, 1472 insertions(+) create mode 100644 source/frontend/StarMainInterface.cpp (limited to 'source/frontend/StarMainInterface.cpp') diff --git a/source/frontend/StarMainInterface.cpp b/source/frontend/StarMainInterface.cpp new file mode 100644 index 0000000..1f007a5 --- /dev/null +++ b/source/frontend/StarMainInterface.cpp @@ -0,0 +1,1472 @@ +#include "StarMainInterface.hpp" +#include "StarJsonExtra.hpp" +#include "StarLogging.hpp" +#include "StarLexicalCast.hpp" +#include "StarContainerInterface.hpp" +#include "StarCraftingInterface.hpp" +#include "StarMerchantInterface.hpp" +#include "StarRoot.hpp" +#include "StarUniverseClient.hpp" +#include "StarCodexInterface.hpp" +#include "StarSongbookInterface.hpp" +#include "StarQuestInterface.hpp" +#include "StarQuestManager.hpp" +#include "StarPopupInterface.hpp" +#include "StarConfirmationDialog.hpp" +#include "StarJoinRequestDialog.hpp" +#include "StarImageMetadataDatabase.hpp" +#include "StarGuiReader.hpp" +#include "StarPaneManager.hpp" +#include "StarClientCommandProcessor.hpp" +#include "StarChat.hpp" +#include "StarOptionsMenu.hpp" +#include "StarActionBar.hpp" +#include "StarWireInterface.hpp" +#include "StarTeamBar.hpp" +#include "StarStatusPane.hpp" +#include "StarLabelWidget.hpp" +#include "StarItemSlotWidget.hpp" +#include "StarPlayer.hpp" +#include "StarPlayerLog.hpp" +#include "StarMonster.hpp" +#include "StarItemDrop.hpp" +#include "StarAssets.hpp" +#include "StarPlayerInventory.hpp" +#include "StarCelestialDatabase.hpp" +#include "StarItem.hpp" +#include "StarAiInterface.hpp" +#include "StarDrawable.hpp" +#include "StarFireableItem.hpp" +#include "StarClientContext.hpp" +#include "StarToolUserEntity.hpp" +#include "StarTeleportDialog.hpp" +#include "StarCinematic.hpp" +#include "StarNameplatePainter.hpp" +#include "StarQuestIndicatorPainter.hpp" +#include "StarScriptPane.hpp" +#include "StarContainerEntity.hpp" +#include "StarWarpTargetEntity.hpp" +#include "StarPlayerUniverseMap.hpp" +#include "StarWorldTemplate.hpp" +#include "StarRadioMessagePopup.hpp" +#include "StarAiTypes.hpp" +#include "StarActiveItem.hpp" +#include "StarInspectionTool.hpp" +#include "StarQuestTracker.hpp" +#include "StarContainerInteractor.hpp" +#include "StarChatBubbleManager.hpp" +#include "StarNpc.hpp" + +namespace Star { + +GuiMessage::GuiMessage() : message(), cooldown(), springState() {} + +GuiMessage::GuiMessage(String const& message, float cooldown) + : message(message), cooldown(cooldown), springState(0) {} + +MainInterface::MainInterface(UniverseClientPtr client, WorldPainterPtr painter, CinematicPtr cinematicOverlay) { + m_state = Running; + + m_guiContext = GuiContext::singletonPtr(); + + m_client = client; + m_worldPainter = painter; + m_cinematicOverlay = cinematicOverlay; + + m_disableHud = false; + + m_cursorScreenPos = Vec2I(); + m_state = Running; + + m_config = MainInterfaceConfig::loadFromAssets(); + + m_containerInteractor = make_shared(); + + GuiReader itemSlotReader; + m_cursorItem = convert(itemSlotReader.makeSingle("cursorItemSlot", m_config->cursorItemSlot)); + + m_planetNameTimer = GameTimer(m_config->planetNameTime); + + m_debugSpatialClearTimer = GameTimer(m_config->debugSpatialClearTime); + m_debugMapClearTimer = GameTimer(m_config->debugMapClearTime); + m_debugTextRect = RectF::null(); + + m_lastMouseoverTarget = NullEntityId; + m_stickyTargetingTimer = GameTimer(m_config->monsterHealthBarTime); + + m_inventoryWindow = make_shared(this, m_client->mainPlayer(), m_containerInteractor); + m_paneManager.registerPane(MainInterfacePanes::Inventory, PaneLayer::Window, m_inventoryWindow, [this](PanePtr const&) { + m_client->mainPlayer()->clearSwap(); + if (m_containerPane) { + m_containerPane->dismiss(); + m_containerPane = {}; + m_containerInteractor->closeContainer(); + } + for (EntityId id : m_interactionScriptPanes.keys()) { + if (m_paneManager.isDisplayed(m_interactionScriptPanes[id]) && as(m_interactionScriptPanes[id])->openWithInventory()) + m_interactionScriptPanes[id]->dismiss(); + } + }); + + m_overflowMessage = make_shared("", 0); + + m_plainCraftingWindow = make_shared(m_client->worldClient(), m_client->mainPlayer(), JsonObject{{"filter", JsonArray{"plain"}}}, m_client->mainPlayer()->entityId()); + m_paneManager.registerPane(MainInterfacePanes::CraftingPlain, PaneLayer::Window, m_plainCraftingWindow); + + m_paneManager.registerPane(MainInterfacePanes::EscapeDialog, PaneLayer::ModalWindow, createEscapeDialog()); + + auto songbookInterface = make_shared(m_client->mainPlayer()); + m_paneManager.registerPane(MainInterfacePanes::Songbook, PaneLayer::Window, songbookInterface); + + m_questLogInterface = make_shared(m_client->questManager(), m_client->mainPlayer(), m_cinematicOverlay, m_client); + m_paneManager.registerPane(MainInterfacePanes::QuestLog, PaneLayer::Window, m_questLogInterface); + + auto aiInterface = make_shared(m_client, m_cinematicOverlay, &m_paneManager); + m_paneManager.registerPane(MainInterfacePanes::Ai, PaneLayer::Window, aiInterface); + + m_codexInterface = make_shared(m_client->mainPlayer()); + m_paneManager.registerPane(MainInterfacePanes::Codex, PaneLayer::Window, m_codexInterface); + + m_optionsMenu = make_shared(&m_paneManager); + m_paneManager.registerPane(MainInterfacePanes::Options, PaneLayer::ModalWindow, m_optionsMenu); + + m_popupInterface = make_shared(); + m_paneManager.registerPane(MainInterfacePanes::Popup, PaneLayer::Window, m_popupInterface); + + m_confirmationDialog = make_shared(); + m_paneManager.registerPane(MainInterfacePanes::Confirmation, PaneLayer::ModalWindow, m_confirmationDialog); + + m_joinRequestDialog = make_shared(); + m_paneManager.registerPane(MainInterfacePanes::JoinRequest, PaneLayer::ModalWindow, m_joinRequestDialog); + + m_actionBar = make_shared(&m_paneManager, m_client->mainPlayer()); + m_paneManager.registerPane(MainInterfacePanes::ActionBar, PaneLayer::Hud, m_actionBar); + + m_questTracker = make_shared(); + m_paneManager.registerPane(MainInterfacePanes::QuestTracker, PaneLayer::Hud, m_questTracker); + + m_mmUpgrade = make_shared(m_client, "/interface/scripted/mmupgrade/mmupgradegui.config"); + m_paneManager.registerPane(MainInterfacePanes::MmUpgrade, PaneLayer::Window, m_mmUpgrade); + + m_collections = make_shared(m_client, "/interface/scripted/collections/collectionsgui.config"); + m_paneManager.registerPane(MainInterfacePanes::Collections, PaneLayer::Window, m_collections); + + m_chat = make_shared(m_client); + m_paneManager.registerPane(MainInterfacePanes::Chat, PaneLayer::Hud, m_chat); + m_clientCommandProcessor = make_shared(m_client, m_cinematicOverlay, &m_paneManager, m_config->macroCommands); + + m_radioMessagePopup = make_shared(); + m_paneManager.registerPane(MainInterfacePanes::RadioMessagePopup, PaneLayer::Hud, m_radioMessagePopup); + + m_wireInterface = make_shared(m_client->worldClient(), m_client->mainPlayer(), m_worldPainter); + m_paneManager.registerPane(MainInterfacePanes::WireInterface, PaneLayer::World, m_wireInterface); + m_client->mainPlayer()->setWireConnector(m_wireInterface.get()); + + auto teamBar = make_shared(this, m_client); + m_paneManager.registerPane(MainInterfacePanes::TeamBar, PaneLayer::Hud, teamBar); + + auto statusPane = make_shared(&m_paneManager, m_client); + m_paneManager.registerPane(MainInterfacePanes::StatusPane, PaneLayer::Hud, statusPane); + + auto planetName = make_shared(); + m_planetText = make_shared(); + m_planetText->setFontSize(m_config->planetNameFontSize); + m_planetText->setAnchor(HorizontalAnchor::HMidAnchor, VerticalAnchor::VMidAnchor); + m_planetText->setDirectives(m_config->planetNameDirectives); + planetName->disableScissoring(); + planetName->setPosition(m_config->planetNameOffset); + planetName->setAnchor(PaneAnchor::Center); + planetName->addChild("planetText", m_planetText); + m_paneManager.registerPane(MainInterfacePanes::PlanetText, PaneLayer::Hud, planetName); + + m_nameplatePainter = make_shared(); + m_questIndicatorPainter = make_shared(client); + m_chatBubbleManager = make_shared(); + + m_paneManager.displayRegisteredPane(MainInterfacePanes::ActionBar); + m_paneManager.displayRegisteredPane(MainInterfacePanes::Chat); + m_paneManager.displayRegisteredPane(MainInterfacePanes::TeamBar); + m_paneManager.displayRegisteredPane(MainInterfacePanes::StatusPane); +} + +MainInterface::~MainInterface() { + m_paneManager.dismissAllPanes(); +} + +MainInterface::RunningState MainInterface::currentState() const { + return m_state; +} + +MainInterfacePaneManager* MainInterface::paneManager() { + return &m_paneManager; +} + +bool MainInterface::escapeDialogOpen() const { + return m_paneManager.registeredPaneIsDisplayed(MainInterfacePanes::EscapeDialog) || m_paneManager.registeredPaneIsDisplayed(MainInterfacePanes::Options); +} + +void MainInterface::openCraftingWindow(Json const& config, EntityId sourceEntityId) { + if (m_craftingWindow && m_paneManager.isDisplayed(m_craftingWindow)) { + m_paneManager.dismissPane(m_craftingWindow); + if (m_craftingWindow->sourceEntityId() == sourceEntityId) { + m_craftingWindow.reset(); + return; + } + } + + m_craftingWindow = make_shared(m_client->worldClient(), m_client->mainPlayer(), config, sourceEntityId); + m_paneManager.displayPane(PaneLayer::Window, m_craftingWindow, [this](PanePtr const&) { + m_client->mainPlayer()->clearSwap(); + }); +} + +void MainInterface::openMerchantWindow(Json const& config, EntityId sourceEntityId) { + if (m_merchantWindow && m_paneManager.isDisplayed(m_merchantWindow)) { + m_paneManager.dismissPane(m_merchantWindow); + if (m_merchantWindow->sourceEntityId() == sourceEntityId) { + m_merchantWindow.reset(); + return; + } + } + + m_merchantWindow = make_shared(m_client->worldClient(), m_client->mainPlayer(), config, sourceEntityId); + m_paneManager.displayPane(PaneLayer::Window, + m_merchantWindow, + [this](PanePtr const&) { + m_client->mainPlayer()->clearSwap(); + m_paneManager.dismissRegisteredPane(MainInterfacePanes::Inventory); + }); + m_paneManager.displayRegisteredPane(MainInterfacePanes::Inventory); + + m_paneManager.bringPaneAdjacent(m_paneManager.registeredPane(MainInterfacePanes::Inventory), + m_merchantWindow, Root::singleton().assets()->json("/interface.config:bringAdjacentWindowGap").toFloat()); +} + +void MainInterface::togglePlainCraftingWindow() { + m_paneManager.toggleRegisteredPane(MainInterfacePanes::CraftingPlain); + + if (m_craftingWindow && m_craftingWindow->isDisplayed() + && m_craftingWindow != m_paneManager.registeredPane(MainInterfacePanes::CraftingPlain)) + m_paneManager.dismissPane(m_craftingWindow); + + m_craftingWindow = m_plainCraftingWindow; +} + +bool MainInterface::windowsOpen() const { + return (bool)m_paneManager.topPane({PaneLayer::Window}); +} + +MerchantPanePtr MainInterface::activeMerchantPane() const { + if (m_paneManager.isDisplayed(m_merchantWindow)) + return m_merchantWindow; + else + return {}; +} + +bool MainInterface::handleInputEvent(InputEvent const& event) { + auto player = m_client->mainPlayer(); + auto inv = player->inventory(); + auto& root = Root::singleton(); + + if (auto mouseMove = event.ptr()) + m_cursorScreenPos = mouseMove->mousePosition; + + if (m_paneManager.sendInputEvent(event)) { + if (!event.is() && !event.is()) + return true; + } + + if (event.is()) { + if (m_chat->hasFocus()) { + if (m_guiContext->actions(event).contains(InterfaceAction::ChatSendLine)) { + doChat(m_chat->currentChat(), true); + m_chat->clearCurrentChat(); + m_chat->stopChat(); + return true; + } + } else if (!m_paneManager.keyboardCapturedPane()) { + Maybe swapSlot; + + for (auto action : m_guiContext->actions(event)) { + switch (action) { + default: + break; + case InterfaceAction::GuiShifting: + m_guiContext->setShiftHeld(true); + break; + case InterfaceAction::ChatBegin: + m_chat->startChat(); + break; + + case InterfaceAction::InterfaceHideHud: + m_disableHud = !m_disableHud; + break; + + case InterfaceAction::InterfaceRepeatCommand: + if (!m_lastCommand.empty()) + doChat(m_lastCommand, false); + break; + + case InterfaceAction::InterfaceToggleFullscreen: + m_optionsMenu->toggleFullscreen(); + break; + + case InterfaceAction::InterfaceReload: + root.reload(); + root.fullyLoad(); + break; + + case InterfaceAction::ChatBeginCommand: + m_chat->startCommand(); + break; + + case InterfaceAction::InterfaceEscapeMenu: + m_paneManager.toggleRegisteredPane(MainInterfacePanes::EscapeDialog); + break; + + case InterfaceAction::InterfaceInventory: + m_paneManager.toggleRegisteredPane(MainInterfacePanes::Inventory); + break; + + case InterfaceAction::InterfaceCodex: + m_paneManager.toggleRegisteredPane(MainInterfacePanes::Codex); + break; + + case InterfaceAction::InterfaceQuest: + m_paneManager.toggleRegisteredPane(MainInterfacePanes::QuestLog); + break; + + case InterfaceAction::InterfaceCrafting: + togglePlainCraftingWindow(); + break; + } + } + } + + return false; + + } else if (auto keyUp = event.ptr()) { + if (m_guiContext->actionsForKey(keyUp->key).contains(InterfaceAction::GuiShifting)) + m_guiContext->setShiftHeld(false); + + return false; + + } else if (auto mouseDown = event.ptr()) { + if (mouseDown->mouseButton == MouseButton::Left || mouseDown->mouseButton == MouseButton::Right + || mouseDown->mouseButton == MouseButton::Middle) + overlayClick(mouseDown->mousePosition, mouseDown->mouseButton); + + } else if (auto mouseUp = event.ptr()) { + if (mouseUp->mouseButton == MouseButton::Left) + player->endPrimaryFire(); + if (mouseUp->mouseButton == MouseButton::Right) + player->endAltFire(); + if (mouseUp->mouseButton == MouseButton::Middle) + player->endTrigger(); + } + + return true; +} + +bool MainInterface::inputFocus() const { + return (bool)m_paneManager.keyboardCapturedPane(); +} + +bool MainInterface::textInputActive() const { + return m_paneManager.keyboardCapturedForTextInput(); +} + +void MainInterface::handleInteractAction(InteractAction interactAction) { + auto assets = Root::singleton().assets(); + auto world = m_client->worldClient(); + + if (interactAction.type == InteractActionType::OpenContainer) { + // If we're currently displaying this container, close it. + if (m_containerPane && m_containerInteractor->openContainerId() == interactAction.entityId) { + m_paneManager.dismissPane(m_containerPane); + return; + } + + // If we're currently displaying another container, close it before we open. + if (m_containerPane) + m_paneManager.dismissPane(m_containerPane); + + auto containerEntity = world->get(interactAction.entityId); + if (!containerEntity) + return; + + m_containerInteractor->openContainer(containerEntity); + + m_paneManager.displayRegisteredPane(MainInterfacePanes::Inventory); + + m_containerPane = make_shared(world, m_client->mainPlayer(), m_containerInteractor); + m_paneManager.displayPane(PaneLayer::Window, m_containerPane, [this](PanePtr const&) { + m_client->mainPlayer()->clearSwap(); + m_paneManager.dismissRegisteredPane(MainInterfacePanes::Inventory); + }); + + m_paneManager.bringPaneAdjacent(m_paneManager.registeredPane(MainInterfacePanes::Inventory), + m_containerPane, Root::singleton().assets()->json("/interface.config:bringAdjacentWindowGap").toFloat()); + } else if (interactAction.type == InteractActionType::SitDown) { + m_client->mainPlayer()->lounge(interactAction.entityId, interactAction.data.toUInt()); + } else if (interactAction.type == InteractActionType::OpenCraftingInterface) { + if (!world->entity(interactAction.entityId)) + return; + + openCraftingWindow(interactAction.data, interactAction.entityId); + } else if (interactAction.type == InteractActionType::OpenSongbookInterface) { + m_paneManager.displayRegisteredPane(MainInterfacePanes::Songbook); + } else if (interactAction.type == InteractActionType::OpenNpcCraftingInterface) { + if (!world->entity(interactAction.entityId)) + return; + + openCraftingWindow(interactAction.data, interactAction.entityId); + } else if (interactAction.type == InteractActionType::OpenMerchantInterface) { + if (!world->entity(interactAction.entityId)) + return; + + openMerchantWindow(interactAction.data, interactAction.entityId); + } else if (interactAction.type == InteractActionType::OpenAiInterface) { + as(m_paneManager.registeredPane(MainInterfacePanes::Ai))->setSourceEntityId(interactAction.entityId); + m_paneManager.displayRegisteredPane(MainInterfacePanes::Ai); + } else if (interactAction.type == InteractActionType::OpenTeleportDialog) { + if (m_teleportDialog) + m_teleportDialog->dismiss(); + + if (!m_client->canTeleport()) + return; + + auto currentLocation = TeleportBookmark(); + + auto config = assets->fetchJson(interactAction.data); + if (config.getBool("canBookmark", false)) { + if (auto entity = world->entity(interactAction.entityId)) { + if (auto uniqueEntityId = entity->uniqueId()) { + auto worldTemplate = m_client->worldClient()->currentTemplate(); + + String icon, planetName; + if (m_client->playerWorld().is()) { + icon = "ship"; + planetName = "Player Ship"; + } else if (m_client->playerWorld().is()) { + icon = worldTemplate->worldParameters()->typeName; + planetName = worldTemplate->worldName(); + } else if (m_client->playerWorld().is()) { + icon = worldTemplate->worldParameters()->typeName; + planetName = worldTemplate->worldName(); + } else { + icon = "default"; + planetName = "???"; + } + + currentLocation = TeleportBookmark { + {m_client->playerWorld(), SpawnTargetUniqueEntity(*uniqueEntityId)}, + planetName, + config.getString("bookmarkName", ""), + icon + }; + + if (!m_client->mainPlayer()->universeMap()->teleportBookmarks().contains(currentLocation) || !config.getBool("canTeleport", true)) { + auto editBookmarkDialog = make_shared(m_client->mainPlayer()->universeMap()); + editBookmarkDialog->setBookmark(currentLocation); + m_paneManager.displayPane(PaneLayer::ModalWindow, editBookmarkDialog); + return; + } + } + } + } + + if (config.getBool("canTeleport", true)) { + m_teleportDialog = make_shared(m_client, &m_paneManager, interactAction.data, interactAction.entityId, currentLocation); + m_paneManager.displayPane(PaneLayer::ModalWindow, m_teleportDialog); + } + } else if (interactAction.type == InteractActionType::ShowPopup) { + m_paneManager.displayRegisteredPane(MainInterfacePanes::Popup); + m_popupInterface->displayMessage(interactAction.data.getString("message"), interactAction.data.getString("title", ""), interactAction.data.getString("subtitle", ""), interactAction.data.optString("sound")); + } else if (interactAction.type == InteractActionType::ScriptPane) { + auto sourceEntity = interactAction.entityId; + // dismiss if there's already a scriptpane open for this source entity + if (sourceEntity != NullEntityId && m_interactionScriptPanes.contains(sourceEntity) && m_paneManager.isDisplayed(m_interactionScriptPanes[sourceEntity])) + m_paneManager.dismissPane(m_interactionScriptPanes[sourceEntity]); + + ScriptPanePtr scriptPane = make_shared(m_client, interactAction.data, sourceEntity); + // keep any number of script panes open with null source entities + if (sourceEntity != NullEntityId) + m_interactionScriptPanes[sourceEntity] = scriptPane; + + if (scriptPane->openWithInventory()) { + m_paneManager.displayPane(PaneLayer::Window, scriptPane, [this](PanePtr const&) { + m_client->mainPlayer()->clearSwap(); + m_paneManager.dismissRegisteredPane(MainInterfacePanes::Inventory); + }); + m_paneManager.displayRegisteredPane(MainInterfacePanes::Inventory); + m_paneManager.bringPaneAdjacent(m_paneManager.registeredPane(MainInterfacePanes::Inventory), + scriptPane, Root::singleton().assets()->json("/interface.config:bringAdjacentWindowGap").toFloat()); + } else { + m_paneManager.displayPane(PaneLayer::Window, scriptPane); + } + } else if (interactAction.type == InteractActionType::Message) { + m_client->mainPlayer()->receiveMessage(connectionForEntity(interactAction.entityId), + interactAction.data.getString("messageType"), interactAction.data.getArray("messageArgs")); + } +} + +void MainInterface::update() { + m_paneManager.update(); + + m_questLogInterface->pollDialog(&m_paneManager); + + if (!m_paneManager.topPane({PaneLayer::ModalWindow}) && m_codexInterface->showNewCodex()) + m_paneManager.displayRegisteredPane(MainInterfacePanes::Codex); + + auto player = m_client->mainPlayer(); + auto cursorWorldPos = cursorWorldPosition(); + if (!m_client->paused()) + player->aim(cursorWorldPos); + if (player->wireToolInUse()) { + m_paneManager.displayRegisteredPane(MainInterfacePanes::WireInterface); + player->setWireConnector(m_wireInterface.get()); + } else { + m_paneManager.dismissRegisteredPane(MainInterfacePanes::WireInterface); + } + + // update inventory pane items, to know if item slots changed + m_inventoryWindow->updateItems(); + + // update mouseover target + EntityId newMouseOverTarget = NullEntityId; + m_stickyTargetingTimer.tick(); + auto mouseoverEntities = m_client->worldClient()->query(RectF::withCenter(cursorWorldPos, Vec2F(1, 1)), [=](shared_ptr const& entity) { + return entity != player + && entity->damageBar() == DamageBarType::Default + && (entity->getTeam().type == TeamType::Enemy || entity->getTeam().type == TeamType::PVP) + && m_client->worldClient()->lightLevel(entity->position()) > 0; + }); + sortByComputedValue(mouseoverEntities, [&](DamageBarEntityPtr const& a) { + return m_client->worldClient()->geometry().diff(a->position(), cursorWorldPos).magnitude(); + }); + if (mouseoverEntities.size() > 0) { + newMouseOverTarget = mouseoverEntities[0]->entityId(); + } else if (m_lastMouseoverTarget == NullEntityId && player->lastDamagedTarget() != NullEntityId && player->timeSinceLastGaveDamage() < m_stickyTargetingTimer.time / 2) { + if (auto targetEntity = as(m_client->worldClient()->entity(player->lastDamagedTarget()))) { + if (targetEntity->damageBar() == DamageBarType::Default && (targetEntity->getTeam().type == TeamType::Enemy || targetEntity->getTeam().type == TeamType::PVP)) { + newMouseOverTarget = targetEntity->entityId(); + } + } + } + if (newMouseOverTarget != NullEntityId && newMouseOverTarget != m_lastMouseoverTarget) { + m_lastMouseoverTarget = newMouseOverTarget; + m_portraitScale = 0; + m_stickyTargetingTimer.reset(); + } + + if (m_stickyTargetingTimer.ready()) + m_lastMouseoverTarget = NullEntityId; + + // special damage bar entity + if (m_specialDamageBarTarget != NullEntityId) { + auto damageBarEntity = as(m_client->worldClient()->entity(m_specialDamageBarTarget)); + if (damageBarEntity && damageBarEntity->damageBar() == DamageBarType::Special) { + float targetHealth = damageBarEntity->health() / damageBarEntity->maxHealth(); + float fillSpeed = 1.0f / Root::singleton().assets()->json("/interface.config:specialDamageBar.fillTime").toFloat(); + if (abs(targetHealth - m_specialDamageBarValue) < fillSpeed * WorldTimestep) + m_specialDamageBarValue = targetHealth; + else + m_specialDamageBarValue += copysign(1.0f, targetHealth - m_specialDamageBarValue) * fillSpeed * WorldTimestep; + } else { + m_specialDamageBarTarget = NullEntityId; + } + } + + if (m_specialDamageBarTarget == NullEntityId) + m_specialDamageBarValue = 0.0f; + + if (m_specialDamageBarTarget == NullEntityId && m_client->mainPlayer()->inWorld()) { + List specialDamageTargets; + m_client->worldClient()->forAllEntities([&specialDamageTargets](EntityPtr const& entity) { + if (auto damageBarEntity = as(entity)) + if (damageBarEntity->damageBar() == DamageBarType::Special) + specialDamageTargets.append(damageBarEntity); + }); + sortByComputedValue(specialDamageTargets, [&](DamageBarEntityPtr entity) { + return m_client->worldClient()->geometry().diff(entity->position(), m_client->mainPlayer()->position()); + }); + + if (specialDamageTargets.size() > 0) + m_specialDamageBarTarget = specialDamageTargets[0]->entityId(); + } + + for (auto const& message : m_client->mainPlayer()->pullQueuedMessages()) + queueMessage(message); + + auto chatHeight = (m_chat->active() && m_chat->visible() > 0.1) ? m_chat->size()[1] : 0; + m_radioMessagePopup->setChatHeight(chatHeight); + if (!m_cinematicOverlay || m_cinematicOverlay->completed()) { + if (m_client->mainPlayer()->interruptRadioMessage()) + m_radioMessagePopup->interrupt(); + if (!m_radioMessagePopup->messageActive()) { + if (auto radioMessage = m_client->mainPlayer()->pullPendingRadioMessage()) { + m_radioMessagePopup->setMessage(*radioMessage); + m_paneManager.displayRegisteredPane(MainInterfacePanes::RadioMessagePopup); + ChatReceivedMessage message = { + {MessageContext::RadioMessage}, + ServerConnectionId, + Text::stripEscapeCodes(radioMessage->senderName), + Text::stripEscapeCodes(radioMessage->text), + Text::stripEscapeCodes(radioMessage->portraitImage.replace("", "0")) + }; + m_chat->addMessages({message}, false); + } else { + m_paneManager.dismissRegisteredPane(MainInterfacePanes::RadioMessagePopup); + } + } + + m_client->mainPlayer()->setInCinematic(false); + } else { + m_client->mainPlayer()->setInCinematic(true); + } + + for (auto const& drop : m_client->mainPlayer()->pullQueuedItemDrops()) + queueItemPickupText(drop); + + m_chat->addMessages(m_client->pullChatMessages()); + + if (auto worldClient = m_client->worldClient()) { + if (worldClient->inWorld()) { + if (auto cinematic = m_client->mainPlayer()->pullPendingCinematic()) { + if (*cinematic) + m_cinematicOverlay->load(Root::singleton().assets()->fetchJson(cinematic.take())); + else + m_cinematicOverlay->stop(); + } + } + } + + if (!m_confirmationDialog->isDisplayed()) { + if (auto confirmation = m_client->mainPlayer()->pullPendingConfirmation()) { + m_paneManager.displayRegisteredPane(MainInterfacePanes::Confirmation); + m_confirmationDialog->displayConfirmation(confirmation->first, confirmation->second); + } + } else { + auto confirmationSource = m_confirmationDialog->sourceEntityId(); + if (confirmationSource && !m_client->worldClient()->playerCanReachEntity(*confirmationSource)) + m_confirmationDialog->dismiss(); + } + + if (!m_joinRequestDialog->isDisplayed()) { + if (auto req = m_queuedJoinRequests.maybeTakeLast()) { + m_paneManager.displayRegisteredPane(MainInterfacePanes::JoinRequest); + m_joinRequestDialog->displayRequest(req->first, [req](P2PJoinRequestReply reply) mutable { + req->second.fulfill(reply); + }); + } + } + + for (EntityId id : m_interactionScriptPanes.keys()) { + if (!m_paneManager.isDisplayed(m_interactionScriptPanes[id])) + m_interactionScriptPanes.remove(id); + } + + if (!m_messages.contains(m_overflowMessage)) + m_messageOverflow = 0; + unsigned maxMessages = m_messageOverflow == 0 ? m_config->maxMessageCount : m_config->maxMessageCount + 1; // exclude overflow message + if (m_messages.size() > maxMessages) { + if (m_messageOverflow == 0) { + m_messages.prepend(m_overflowMessage); + } + + m_messageOverflow++; + m_overflowMessage->message = m_config->overflowMessageText.replace("", strf("%s", m_messageOverflow)); + m_overflowMessage->cooldown = m_config->messageTime; + if (auto oldest = m_messages.sorted([](GuiMessagePtr a, GuiMessagePtr b) { return a->cooldown < b->cooldown; }).maybeFirst()) + m_overflowMessage->cooldown = oldest.value()->cooldown; + + if (auto bottom = m_messages.filtered([this](GuiMessagePtr m) { return m != m_overflowMessage; }).maybeFirst()) + bottom.value()->cooldown = 0; + } + + for (auto it = m_messages.begin(); it != m_messages.end();) { + auto& message = *it; + message->cooldown -= WorldTimestep; + if (message->cooldown < 0) + it = m_messages.erase(it); + else + it++; + } + + for (auto it = m_itemDropMessages.begin(); it != m_itemDropMessages.end();) { + auto& message = *it; + if (message.second.second->cooldown < 0) + it = m_itemDropMessages.erase(it); + else + it++; + } + + auto worldId = m_client->playerWorld(); + if (worldId.is()) { + if (m_planetNameTimer.tick()) + m_paneManager.dismissRegisteredPane(MainInterfacePanes::PlanetText); + else + m_paneManager.displayRegisteredPane(MainInterfacePanes::PlanetText); + + if (auto parameters = m_client->celestialDatabase()->parameters(worldId.get())) + m_planetText->setText(strf(m_config->planetNameFormatString.utf8Ptr(), parameters->name())); + + Color textColor = Color::White; // probably need to make this jsonable + float fadeTimer = m_planetNameTimer.percent(); + if (fadeTimer < m_config->planetNameFadeTime) + textColor.setAlphaF(fadeTimer / m_config->planetNameFadeTime); + + m_planetText->setColor(textColor); + + } else { + m_paneManager.dismissRegisteredPane(MainInterfacePanes::PlanetText); + m_planetNameTimer.reset(); + } + + for (auto containerResult : m_containerInteractor->pullContainerResults()) { + if (!m_containerPane || !m_containerPane->giveContainerResult(containerResult)) { + if (!m_inventoryWindow->giveContainerResult(containerResult)) { + for (auto item : containerResult) { + if (m_containerInteractor->containerOpen()) + m_containerInteractor->addToContainer(m_client->mainPlayer()->pickupItems(item)); + else + m_client->mainPlayer()->giveItem(item); + } + } + } + } + + if (auto currentQuest = m_client->questManager()->currentQuest()) { + m_paneManager.displayRegisteredPane(MainInterfacePanes::QuestTracker); + m_questTracker->setQuest(*currentQuest); + } else { + m_paneManager.dismissRegisteredPane(MainInterfacePanes::QuestTracker); + } + + updateCursor(); + + m_nameplatePainter->update(m_client->worldClient(), m_worldPainter->camera(), m_client->worldClient()->interactiveHighlightMode()); + m_questIndicatorPainter->update(m_client->worldClient(), m_worldPainter->camera()); + + if (auto worldClient = m_client->worldClient()) { + auto chatActions = worldClient->pullPendingChatActions(); + auto portraitActions = chatActions.filtered([](ChatAction action) { return action.is(); }); + + for (auto action : portraitActions) { + PortraitChatAction portraitAction = action.get(); + + String name; + if (auto npc = as(worldClient->entity(portraitAction.entity))) + name = npc->name(); + + ChatReceivedMessage message = { + { MessageContext::World }, + ServerConnectionId, + Text::stripEscapeCodes(name), + Text::stripEscapeCodes(portraitAction.text), + Text::stripEscapeCodes(portraitAction.portrait.replace("", "0")) + }; + m_chat->addMessages({message}, false); + } + + m_chatBubbleManager->addChatActions(chatActions); + m_chatBubbleManager->update(worldClient); + } + m_chatBubbleManager->setCamera(m_worldPainter->camera()); + + if (auto container = m_client->worldClient()->get(m_containerInteractor->openContainerId())) { + if (!m_client->worldClient()->playerCanReachEntity(container->entityId()) + || !container->isInteractive()) + m_containerInteractor->closeContainer(); + } + + if (m_paneManager.topPane({PaneLayer::Window, PaneLayer::ModalWindow})) + m_client->mainPlayer()->setBusyState(PlayerBusyState::Menu); + else if (m_chat->hasFocus()) + m_client->mainPlayer()->setBusyState(PlayerBusyState::Chatting); + else + m_client->mainPlayer()->setBusyState(PlayerBusyState::None); +} + +void MainInterface::renderInWorldElements() { + if (m_disableHud) + return; + + m_guiContext->setFontColor(Vec4B::filled(255)); + m_questIndicatorPainter->render(); + m_nameplatePainter->render(); + m_chatBubbleManager->render(); +} + +void MainInterface::render() { + if (m_disableHud) + return; + + m_guiContext->setFontColor(Vec4B::filled(255)); + renderBreath(); + renderMessages(); + renderMonsterHealthBar(); + renderSpecialDamageBar(); + renderMainBar(); + + renderWindows(); + renderCursor(); + + renderDebug(); +} + +Vec2F MainInterface::cursorWorldPosition() const { + return m_worldPainter->camera().screenToWorld(Vec2F(m_cursorScreenPos)); +} + +bool MainInterface::isDebugDisplayed() { + return m_clientCommandProcessor->debugDisplayEnabled(); +} + +void MainInterface::doChat(String const& chat, bool addToHistory) { + if (chat.empty()) + return; + + if (chat.beginsWith("/")) { + m_lastCommand = chat; + + for (auto const& result : m_clientCommandProcessor->handleCommand(chat)) + m_chat->addLine(result); + } else { + m_client->sendChat(chat, m_chat->sendMode()); + } + + if (addToHistory) + m_chat->addHistory(chat); +} + +void MainInterface::queueMessage(String const& message) { + auto guiMessage = make_shared(message, m_config->messageTime); + m_messages.append(guiMessage); +} + +void MainInterface::queueJoinRequest(pair> request) +{ + m_queuedJoinRequests.push_back(request); +} + +void MainInterface::queueItemPickupText(ItemPtr const& item) { + auto descriptor = item->descriptor(); + if (m_itemDropMessages.contains(descriptor.singular())) { + auto countMessPair = m_itemDropMessages.get(descriptor.singular()); + auto newCount = item->count() + countMessPair.first; + auto message = countMessPair.second; + message->message = strf("%s - %s", item->friendlyName(), newCount); + message->cooldown = m_config->messageTime; + m_itemDropMessages[descriptor.singular()] = {newCount, message}; + } else { + auto message = make_shared(strf("%s - %s", item->friendlyName(), item->count()), m_config->messageTime); + m_messages.append(message); + m_itemDropMessages[descriptor.singular()] = {item->count(), message}; + } +} + +bool MainInterface::fixedCamera() const { + return m_clientCommandProcessor->fixedCameraEnabled(); +} + +void MainInterface::warpToOrbitedWorld(bool deploy) { + if (m_client->canBeamDown(deploy)) { + if (deploy) + m_client->warpPlayer(WarpAlias::OrbitedWorld, true, "deploy", true); + else + m_client->warpPlayer(WarpAlias::OrbitedWorld, true, "beam"); + return; + } + m_guiContext->playAudio("/sfx/interface/clickon_error.ogg"); +} + +void MainInterface::warpToOwnShip() { + if (m_client->canBeamUp()) { + warpTo(WarpAlias::OwnShip); + } else { + m_guiContext->playAudio("/sfx/interface/clickon_error.ogg"); + } +} + +void MainInterface::warpTo(WarpAction const& warpAction) { + if (m_client->beamUpRule() == BeamUpRule::AnywhereWithWarning) { + if (m_confirmationDialog->isDisplayed()) + m_confirmationDialog->dismiss(); + + m_paneManager.displayRegisteredPane(MainInterfacePanes::Confirmation); + m_confirmationDialog->displayConfirmation("/interface/windowconfig/beamupconfirmation.config", [this, warpAction] (Widget*) { + m_client->warpPlayer(warpAction, true, "beam"); + }, [](Widget*) {}); + } else { + m_client->warpPlayer(warpAction, true, "beam"); + } +} + +PanePtr MainInterface::createEscapeDialog() { + auto assets = Root::singleton().assets(); + + auto escapeDialog = make_shared(); + auto escapeDialogPtr = escapeDialog.get(); + + GuiReader escapeDialogReader; + escapeDialogReader.registerCallback("returnToGame", [escapeDialogPtr](Widget*) { + escapeDialogPtr->dismiss(); + }); + escapeDialogReader.registerCallback("showOptions", [escapeDialogPtr, this](Widget*) { + escapeDialogPtr->dismiss(); + m_paneManager.displayRegisteredPane(MainInterfacePanes::Options); + }); + escapeDialogReader.registerCallback("saveAndQuit", [escapeDialogPtr, this](Widget*) { + m_state = ReturnToTitle; + escapeDialogPtr->dismiss(); + }); + + escapeDialogReader.construct(assets->json("/interface.config:escapeDialog"), escapeDialogPtr); + escapeDialog->fetchChild("lblversion")->setText(strf("Starbound - %s (%s)", StarVersionString, StarArchitectureString)); + return escapeDialog; +} + +float MainInterface::interfaceScale() const { + return m_guiContext->interfaceScale(); +} + +unsigned MainInterface::windowHeight() const { + return m_guiContext->windowHeight(); +} + +unsigned MainInterface::windowWidth() const { + return m_guiContext->windowWidth(); +} + +Vec2I MainInterface::mainBarPosition() const { + return Vec2I(windowWidth(), windowHeight()) - m_config->mainBarSize * interfaceScale(); +} + +void MainInterface::renderBreath() { + auto assets = Root::singleton().assets(); + auto imgMetadata = Root::singleton().imageMetadataDatabase(); + + Vec2I breathBarSize = Vec2I(Vec2F(m_guiContext->textureSize("/interface/breath/empty.png")) * interfaceScale()); + Vec2I breathOffset = jsonToVec2I(assets->json("/interface.config:breathPos")); + + Vec2F breathBackgroundCenterPos(windowWidth() * 0.5f + breathOffset[0] * interfaceScale(), windowHeight() - breathOffset[1] * interfaceScale()); + Vec2F breathBarPos = breathBackgroundCenterPos + Vec2F(jsonToVec2I(assets->json("/interface.config:breathBarPos")) * interfaceScale()); + + float breath = m_client->mainPlayer()->breath(); + float breathMax = m_client->mainPlayer()->maxBreath(); + + size_t blocks = round((10 * breath) / breathMax); + + if (blocks < 10) { + m_guiContext->drawQuad("/interface/breath/breath.png", + RectF::withCenter(breathBackgroundCenterPos, Vec2F(imgMetadata->imageSize("/interface/breath/breath.png")) * interfaceScale())); + for (size_t i = 0; i < 10; i++) { + if (i >= blocks) { + if (blocks == 0 && Time::monotonicMilliseconds() % 500 > 250) + m_guiContext->drawQuad("/interface/breath/warning.png", breathBarPos + Vec2F(breathBarSize[0] * i, 0), interfaceScale()); + else + m_guiContext->drawQuad("/interface/breath/empty.png", breathBarPos + Vec2F(breathBarSize[0] * i, 0), interfaceScale()); + } else { + m_guiContext->drawQuad("/interface/breath/breathbar.png", breathBarPos + Vec2F(breathBarSize[0] * i, 0), interfaceScale()); + } + } + } +} + +void MainInterface::renderMessages() { + Vec2F totalOffset = {}; + auto imgMetadata = Root::singleton().imageMetadataDatabase(); + for (auto& message : m_messages) { + Vec2F hiddenOffset = Vec2F(m_config->messageHiddenOffset); + Vec2F messageOffset = lerp(message->springState, Vec2F(), Vec2F(m_config->messageActiveOffset) - hiddenOffset); + totalOffset += messageOffset; + messageOffset = totalOffset + hiddenOffset; + + Vec2F backgroundCenterPos = Vec2F(windowWidth() * 0.5f + messageOffset[0] * interfaceScale(), messageOffset[1] * interfaceScale()); + + Vec2F backgroundTextCenterPos = backgroundCenterPos + Vec2F(m_config->messageTextContainerOffset * interfaceScale()); + Vec2F messageTextOffset = backgroundTextCenterPos + Vec2F(m_config->messageTextOffset * interfaceScale()); + + if (message->cooldown > m_config->messageHideTime) + message->springState = (message->springState * m_config->messageWindowSpring + 1.0f) / (m_config->messageWindowSpring + 1.0f); + else + message->springState = (message->springState * m_config->messageWindowSpring) / (m_config->messageWindowSpring + 1.0f); + + m_guiContext->drawQuad(m_config->messageTextContainer, + RectF::withCenter(backgroundTextCenterPos, Vec2F(imgMetadata->imageSize(m_config->messageTextContainer) * interfaceScale()))); + + m_guiContext->setFontSize(m_config->fontSize); + m_guiContext->setFontColor(Color::White.toRgba()); + m_guiContext->renderText(message->message, {messageTextOffset, HorizontalAnchor::HMidAnchor, VerticalAnchor::VMidAnchor}); + } +} + +void MainInterface::renderMonsterHealthBar() { + auto assets = Root::singleton().assets(); + auto imgMetadata = Root::singleton().imageMetadataDatabase(); + if (m_lastMouseoverTarget != NullEntityId && !m_stickyTargetingTimer.ready()) { + auto world = m_client->worldClient(); + + auto entity = world->entity(m_lastMouseoverTarget); + auto showDamageEntity = as(entity); + + if (!showDamageEntity) { + m_lastMouseoverTarget = NullEntityId; + return; + } + + Vec2F backgroundCenterPos = Vec2F(windowWidth() / 2.0f, windowHeight()); + + auto container = assets->json("/interface.config:monsterHealth.container").toString(); + auto offset = jsonToVec2F(assets->json("/interface.config:monsterHealth.offset")) * interfaceScale(); + m_guiContext->drawQuad(container, RectF::withCenter(backgroundCenterPos + offset, Vec2F(imgMetadata->imageSize(container) * interfaceScale()))); + + auto nameTextOffset = jsonToVec2F(assets->json("/interface.config:monsterHealth.nameTextOffset")) * interfaceScale(); + m_guiContext->setFontSize(m_config->fontSize); + m_guiContext->setFontColor(Color::White.toRgba()); + m_guiContext->renderText(showDamageEntity->name(), backgroundCenterPos + nameTextOffset); + + auto empty = assets->json("/interface.config:monsterHealth.progressEmpty").toString(); + auto filled = assets->json("/interface.config:monsterHealth.progressFilled").toString(); + auto progressBarOffset = jsonToVec2F(assets->json("/interface.config:monsterHealth.progressBarOffset")) * interfaceScale(); + auto chunks = assets->json("/interface.config:monsterHealth.progressChunks").toInt(); + int blocks = round(showDamageEntity->health() / showDamageEntity->maxHealth() * chunks); + Vec2F barPos = backgroundCenterPos + progressBarOffset; + Vec2F barItemOffset = Vec2F(imgMetadata->imageSize(filled)) * interfaceScale(); + barItemOffset[1] = 0; + + m_guiContext->drawQuad(empty, RectF::withSize(backgroundCenterPos + barPos, Vec2F(imgMetadata->imageSize(empty) * interfaceScale()))); + + for (int i = 0; i < blocks; i++) + m_guiContext->drawQuad(filled, barPos + barItemOffset * i, interfaceScale()); + + auto portraitOffset = jsonToVec2F(assets->json("/interface.config:monsterHealth.portraitOffset")) * interfaceScale(); + auto portraitScale = assets->json("/interface.config:monsterHealth.portraitScale").toFloat() * interfaceScale(); + + auto portraitScissorRect = jsonToRectF(assets->json("/interface.config:monsterHealth.portraitScissorRect")).scaled(interfaceScale()); + auto rect = portraitScissorRect.translated(backgroundCenterPos + portraitOffset); + m_guiContext->setInterfaceScissorRect(RectI(RectF(rect).scaled(1.0f / interfaceScale()))); + auto portraitMaxSize = jsonToVec2I(assets->json("/interface.config:monsterHealth.portraitMaxSize")); + List portrait = showDamageEntity->portrait(PortraitMode::Full); + + auto bounds = Drawable::boundBoxAll(portrait, true); + if (m_portraitScale == 0) + m_portraitScale = max(1, ceil(max(bounds.size().x() / portraitMaxSize.x(), bounds.size().y() / portraitMaxSize.y()))); + Drawable::translateAll(portrait, {-bounds.xMin() - (bounds.width() * 0.5f), -bounds.yMin() }); // crop out whitespace, align bottom center + Drawable::scaleAll(portrait, 1.0f / m_portraitScale); + + for (auto drawable : portrait) + m_guiContext->drawDrawable(move(drawable), backgroundCenterPos + portraitOffset, portraitScale); + + m_guiContext->resetInterfaceScissorRect(); + } +} + +void MainInterface::renderSpecialDamageBar() { + if (m_specialDamageBarTarget == NullEntityId) + return; + + auto assets = Root::singleton().assets(); + auto imgMetadata = Root::singleton().imageMetadataDatabase(); + + if (auto target = as(m_client->worldClient()->entity(m_specialDamageBarTarget))) { + Vec2F bottomCenter = Vec2F(windowWidth() / 2.0f, 0); + + auto barConfig = assets->json("/interface.config:specialDamageBar"); + + auto background = barConfig.getString("background"); + auto backgroundOffset = jsonToVec2F(barConfig.get("backgroundOffset")) * interfaceScale(); + auto screenPos = RectF::withSize(bottomCenter + backgroundOffset, Vec2F(imgMetadata->imageSize(background) * interfaceScale())); + m_guiContext->drawQuad(background, screenPos); + + auto fill = barConfig.getString("fill"); + auto fillOffset = jsonToVec2F(barConfig.get("fillOffset")) * interfaceScale(); + Vec2F size = Vec2F(barConfig.getInt("fillWidth") * m_specialDamageBarValue, imgMetadata->imageSize(fill).y()); + m_guiContext->drawQuad(fill, RectF::withSize(bottomCenter + fillOffset, size * interfaceScale())); + + auto nameOffset = jsonToVec2F(barConfig.get("nameOffset")) * interfaceScale(); + m_guiContext->setFontColor(jsonToColor(barConfig.get("nameColor")).toRgba()); + m_guiContext->setFontSize(barConfig.getUInt("nameSize")); + m_guiContext->setFontProcessingDirectives(barConfig.getString("nameDirectives")); + m_guiContext->renderText(target->name(), TextPositioning(bottomCenter + nameOffset, HorizontalAnchor::HMidAnchor, VerticalAnchor::BottomAnchor)); + m_guiContext->setFontProcessingDirectives(""); + } +} + +void MainInterface::renderMainBar() { + Vec2I barPos = mainBarPosition(); + + m_cursorTooltip = {}; + + auto assets = Root::singleton().assets(); + + Vec2I inventoryButtonPos = barPos + m_config->mainBarInventoryButtonOffset * interfaceScale(); + if (m_paneManager.registeredPaneIsDisplayed(MainInterfacePanes::Inventory)) { + if (overButton(m_config->mainBarInventoryButtonPoly, m_cursorScreenPos)) { + m_guiContext->drawQuad(m_config->inventoryImageOpenHover, Vec2F(inventoryButtonPos), interfaceScale()); + m_cursorTooltip = assets->json("/interface.config:cursorTooltip.inventoryText").toString(); + } else { + m_guiContext->drawQuad(m_config->inventoryImageOpen, Vec2F(inventoryButtonPos), interfaceScale()); + } + } else if (overButton(m_config->mainBarInventoryButtonPoly, m_cursorScreenPos)) { + if (m_inventoryWindow->containsNewItems()) + m_guiContext->drawQuad(m_config->inventoryImageGlowHover, Vec2F(inventoryButtonPos), interfaceScale()); + else + m_guiContext->drawQuad(m_config->inventoryImageHover, Vec2F(inventoryButtonPos), interfaceScale()); + m_cursorTooltip = assets->json("/interface.config:cursorTooltip.inventoryText").toString(); + } else { + if (m_inventoryWindow->containsNewItems()) + m_guiContext->drawQuad(m_config->inventoryImageGlow, Vec2F(inventoryButtonPos), interfaceScale()); + else + m_guiContext->drawQuad(m_config->inventoryImage, Vec2F(inventoryButtonPos), interfaceScale()); + } + + auto drawStateButton = [this](MainInterfacePanes paneType, Vec2I pos, PolyI poly, + String image, String hoverImage, String openImage, String hoverOpenImage, String toolTip) { + if (m_paneManager.registeredPaneIsDisplayed(paneType)) { + if (overButton(poly, m_cursorScreenPos)) { + m_guiContext->drawQuad(hoverOpenImage, Vec2F(pos), interfaceScale()); + m_cursorTooltip = toolTip; + } else { + m_guiContext->drawQuad(openImage, Vec2F(pos), interfaceScale()); + } + } else if (overButton(poly, m_cursorScreenPos)) { + m_guiContext->drawQuad(hoverImage, Vec2F(pos), interfaceScale()); + m_cursorTooltip = toolTip; + } else { + m_guiContext->drawQuad(image, Vec2F(pos), interfaceScale()); + } + }; + + Vec2I craftButtonPos = barPos + m_config->mainBarCraftButtonOffset * interfaceScale(); + drawStateButton(MainInterfacePanes::CraftingPlain, + craftButtonPos, + m_config->mainBarCraftButtonPoly, + m_config->craftImage, + m_config->craftImageHover, + m_config->craftImageOpen, + m_config->craftImageOpenHover, + assets->json("/interface.config:cursorTooltip.craftingText").toString()); + + Vec2I codexButtonPos = barPos + m_config->mainBarCodexButtonOffset * interfaceScale(); + drawStateButton(MainInterfacePanes::Codex, + codexButtonPos, + m_config->mainBarCodexButtonPoly, + m_config->codexImage, + m_config->codexImageHover, + m_config->codexImageOpen, + m_config->codexImageHoverOpen, + assets->json("/interface.config:cursorTooltip.codexText").toString()); + + Vec2I mmUpgradeButtonPos = barPos + m_config->mainBarMmUpgradeButtonOffset * interfaceScale(); + if (m_client->mainPlayer()->inventory()->essentialItem(EssentialItem::BeamAxe)) { + drawStateButton(MainInterfacePanes::MmUpgrade, + mmUpgradeButtonPos, + m_config->mainBarMmUpgradeButtonPoly, + m_config->mmUpgradeImage, + m_config->mmUpgradeImageHover, + m_config->mmUpgradeImageOpen, + m_config->mmUpgradeImageHoverOpen, + assets->json("/interface.config:cursorTooltip.mmUpgradeText").toString()); + } else { + drawStateButton(MainInterfacePanes::MmUpgrade, + mmUpgradeButtonPos, + m_config->mainBarMmUpgradeButtonPoly, + m_config->mmUpgradeImageDisabled, + m_config->mmUpgradeImageDisabled, + m_config->mmUpgradeImageDisabled, + m_config->mmUpgradeImageDisabled, + assets->json("/interface.config:cursorTooltip.disabledText").toString()); + } + + Vec2I collectionsButtonPos = barPos + m_config->mainBarCollectionsButtonOffset * interfaceScale(); + drawStateButton(MainInterfacePanes::Collections, + collectionsButtonPos, + m_config->mainBarCollectionsButtonPoly, + m_config->collectionsImage, + m_config->collectionsImageHover, + m_config->collectionsImageOpen, + m_config->collectionsImageHoverOpen, + assets->json("/interface.config:cursorTooltip.collectionsText").toString()); + + // when the player can't deploy or beam, show the deploy button disabled + // when the player can beam up they can't deploy down, show beaming up button in deploy button's place + // when the player can only deploy, only show deploy button + // when the player can deploy or beam down, show both buttons + + Vec2F deployButtonPos(barPos + m_config->mainBarDeployButtonOffset * interfaceScale()); + if (m_client->canBeamUp()) { + if (overButton(m_config->mainBarDeployButtonPoly, m_cursorScreenPos)) { + m_guiContext->drawQuad(m_config->beamUpImageHover, deployButtonPos, interfaceScale()); + m_cursorTooltip = assets->json("/interface.config:cursorTooltip.beamUpText").toString(); + } else { + m_guiContext->drawQuad(m_config->beamUpImage, deployButtonPos, interfaceScale()); + } + } else if (m_client->canBeamDown(true)) { + if (overButton(m_config->mainBarDeployButtonPoly, m_cursorScreenPos)) { + m_guiContext->drawQuad(m_config->deployImageHover, deployButtonPos, interfaceScale()); + m_cursorTooltip = assets->json("/interface.config:cursorTooltip.deployText").toString(); + } else { + m_guiContext->drawQuad(m_config->deployImage, deployButtonPos, interfaceScale()); + } + } else { + m_guiContext->drawQuad(m_config->deployImageDisabled, deployButtonPos, interfaceScale()); + } + + Vec2F beamButtonPos(barPos + m_config->mainBarBeamButtonOffset * interfaceScale()); + if (m_client->canBeamDown()) { + if (overButton(m_config->mainBarBeamButtonPoly, m_cursorScreenPos)) { + m_guiContext->drawQuad(m_config->beamDownImageHover, beamButtonPos, interfaceScale()); + m_cursorTooltip = assets->json("/interface.config:cursorTooltip.beamDownText").toString(); + } else { + m_guiContext->drawQuad(m_config->beamDownImage, beamButtonPos, interfaceScale()); + } + } + + Vec2I questLogButtonPos = barPos + m_config->mainBarQuestLogButtonOffset * interfaceScale(); + drawStateButton(MainInterfacePanes::QuestLog, + questLogButtonPos, + m_config->mainBarQuestLogButtonPoly, + m_config->questLogImage, + m_config->questLogImageHover, + m_config->questLogImageOpen, + m_config->questLogImageHoverOpen, + assets->json("/interface.config:cursorTooltip.questsText").toString()); +} + +void MainInterface::renderWindows() { + m_paneManager.render(); +} + +void MainInterface::renderDebug() { + if (!isDebugDisplayed()) { + SpatialLogger::clear(); + m_debugTextRect = RectF::null(); + LogMap::clear(); + return; + } + + auto assets = Root::singleton().assets(); + m_guiContext->setFontSize(m_config->debugFontSize); + int counter = 0; + m_guiContext->setFontColor(Color::Green.toRgba()); + + bool clearMap = m_debugMapClearTimer.wrapTick(); + auto logMapValues = LogMap::getValues(); + if (clearMap) + LogMap::clear(); + + for (auto const& pair : logMapValues) { + TextPositioning positioning = {Vec2F(m_config->debugOffset[0], windowHeight() - m_config->debugOffset[1] - m_config->fontSize * interfaceScale() * counter)}; + m_debugTextRect.combine(m_guiContext->determineTextSize(strf("%s: %s", pair.first, pair.second), positioning).padded(m_config->debugBackgroundPad)); + ++counter; + } + + if (!m_debugTextRect.isNull()) + m_guiContext->drawQuad(m_debugTextRect, m_config->debugBackgroundColor.toRgba()); + + if (clearMap) + m_debugTextRect = RectF::null(); + + counter = 0; + for (auto const& pair : logMapValues) { + TextPositioning positioning = {Vec2F(m_config->debugOffset[0], windowHeight() - m_config->debugOffset[1] - m_config->fontSize * interfaceScale() * counter)}; + m_guiContext->renderText(strf("%s: %s", pair.first, pair.second), positioning); + ++counter; + } + m_guiContext->setFontColor(Vec4B::filled(255)); + + auto const& camera = m_worldPainter->camera(); + + bool clearSpatial = m_debugSpatialClearTimer.wrapTick(); + + for (auto const& line : SpatialLogger::getLines("world", clearSpatial)) { + Vec2F begin = camera.worldToScreen(line.begin); + Vec2F end = camera.worldGeometry().diff(line.end, line.begin) * camera.pixelRatio() * TilePixels + begin; + m_guiContext->drawLine(begin, end, line.color, 1); + } + + for (auto const& line : SpatialLogger::getLines("screen", clearSpatial)) + m_guiContext->drawLine(Vec2F(line.begin), Vec2F(line.end), line.color, 1); + + for (auto const& point : SpatialLogger::getPoints("world", clearSpatial)) { + auto position = camera.worldToScreen(point.position); + m_guiContext->drawLine(position + Vec2F(-2, -2), position + Vec2F(-2, 2), point.color, 1); + m_guiContext->drawLine(position + Vec2F(-2, 2), position + Vec2F(2, 2), point.color, 1); + m_guiContext->drawLine(position + Vec2F(2, 2), position + Vec2F(2, -2), point.color, 1); + m_guiContext->drawLine(position + Vec2F(2, -2), position + Vec2F(-2, -2), point.color, 1); + } + + for (auto const& point : SpatialLogger::getPoints("screen", clearSpatial)) { + auto position = point.position; + m_guiContext->drawLine(position + Vec2F(-2, -2), position + Vec2F(-2, 2), point.color, 1); + m_guiContext->drawLine(position + Vec2F(-2, 2), position + Vec2F(2, 2), point.color, 1); + m_guiContext->drawLine(position + Vec2F(2, 2), position + Vec2F(2, -2), point.color, 1); + m_guiContext->drawLine(position + Vec2F(2, -2), position + Vec2F(-2, -2), point.color, 1); + } + + m_guiContext->setFontSize(assets->json("/interface.config:debugFontSize").toInt()); + for (auto const& logText : SpatialLogger::getText("world", clearSpatial)) { + m_guiContext->setFontColor(logText.color); + m_guiContext->renderText(logText.text.utf8Ptr(), camera.worldToScreen(logText.position)); + } + + for (auto const& logText : SpatialLogger::getText("screen", clearSpatial)) { + m_guiContext->setFontColor(logText.color); + m_guiContext->renderText(logText.text.utf8Ptr(), logText.position); + } + m_guiContext->setFontColor(Vec4B::filled(255)); +} + +void MainInterface::updateCursor() { + Maybe cursorOverride = m_actionBar->cursorOverride(m_cursorScreenPos); + + if (!cursorOverride) { + if (auto pane = m_paneManager.getPaneAt(m_cursorScreenPos / interfaceScale())) { + cursorOverride = cursorOverride.orMaybe(pane->cursorOverride(m_cursorScreenPos / interfaceScale())); + } else { + auto player = m_client->mainPlayer(); + if (auto anchorState = m_client->mainPlayer()->loungingIn()) { + if (auto loungeable = m_client->worldClient()->get(anchorState->entityId)) { + if (auto loungeAnchor = loungeable->loungeAnchor(anchorState->positionIndex)) + cursorOverride = cursorOverride.orMaybe(loungeAnchor->cursorOverride); + } + } + if (!cursorOverride) { + for (auto item : {player->primaryHandItem(), player->altHandItem()}) { + if (auto activeItem = as(item)) { + if (auto cursor = activeItem->cursor()) { + cursorOverride = cursor; + break; + } + } else if (auto inspectionTool = as(item)) { + cursorOverride = String("/cursors/inspect.cursor"); + break; + } + } + } + } + } + + if (cursorOverride) + m_cursor.setCursor(cursorOverride.take()); + else + m_cursor.resetCursor(); +} + +void MainInterface::renderCursor() { + // if we're currently playing a cinematic, we should not render the mouse. + if (m_cinematicOverlay && !m_cinematicOverlay->completed()) + return; + + m_cursor.update(WorldTimestep); + + Vec2I cursorPos = m_cursorScreenPos; + Vec2I cursorSize = m_cursor.size(); + Vec2I cursorOffset = m_cursor.offset(); + cursorPos[0] -= cursorOffset[0] * interfaceScale(); + cursorPos[1] -= (cursorSize[1] - cursorOffset[1]) * interfaceScale(); + m_guiContext->drawDrawable(m_cursor.drawable(), Vec2F(cursorPos), interfaceScale()); + + if (m_cursorTooltip) { + auto assets = Root::singleton().assets(); + auto imgDb = Root::singleton().imageMetadataDatabase(); + + auto backgroundImage = assets->json("/interface.config:cursorTooltip.background").toString(); + auto rawCursorOffset = jsonToVec2I(assets->json("/interface.config:cursorTooltip.offset")); + + Vec2I tooltipSize = Vec2I(imgDb->imageSize(backgroundImage)) * interfaceScale(); + Vec2I cursorOffset = (Vec2I{0, -m_cursor.size().y()} + rawCursorOffset) * interfaceScale(); + Vec2I tooltipOffset = m_cursorScreenPos + cursorOffset; + size_t fontSize = assets->json("/interface.config:cursorTooltip.fontSize").toUInt(); + Vec4B fontColor = jsonToColor(assets->json("/interface.config:cursorTooltip.color")).toRgba(); + + m_guiContext->drawQuad(backgroundImage, Vec2F(tooltipOffset) + Vec2F(-tooltipSize.x(), 0), interfaceScale()); + m_guiContext->setFontSize(fontSize); + m_guiContext->setFontColor(fontColor); + m_guiContext->renderText(*m_cursorTooltip, + TextPositioning(Vec2F(tooltipOffset) + Vec2F(-tooltipSize.x(), tooltipSize.y()) / 2, + HorizontalAnchor::HMidAnchor, + VerticalAnchor::VMidAnchor)); + } + + m_cursorItem->setPosition(m_cursorScreenPos / interfaceScale() + m_config->inventoryItemMouseOffset); + + if (auto swapItem = m_client->mainPlayer()->inventory()->swapSlotItem()) + m_cursorItem->setItem(swapItem); + else + m_cursorItem->setItem({}); + + m_cursorItem->render(RectI::withSize({}, {(int)windowWidth(), (int)windowHeight()})); + m_guiContext->resetInterfaceScissorRect(); +} + +bool MainInterface::overButton(PolyI buttonPoly, Vec2I const& mousePos) const { + Vec2I barPos = mainBarPosition(); + buttonPoly.translate(barPos); + buttonPoly.scale(interfaceScale(), barPos); + return buttonPoly.contains(mousePos); +} + +void MainInterface::overlayClick(Vec2I const& mousePos, MouseButton mouseButton) { + PolyI mainBarPoly = m_config->mainBarPoly; + Vec2I barPos = mainBarPosition(); + mainBarPoly.translate(barPos); + mainBarPoly.scale(interfaceScale(), barPos); + + if (overButton(m_config->mainBarInventoryButtonPoly, mousePos)) { + m_paneManager.toggleRegisteredPane(MainInterfacePanes::Inventory); + return; + } + + if (overButton(m_config->mainBarCraftButtonPoly, mousePos)) { + togglePlainCraftingWindow(); + return; + } + + if (overButton(m_config->mainBarCodexButtonPoly, mousePos)) { + m_paneManager.toggleRegisteredPane(MainInterfacePanes::Codex); + return; + } + + if (overButton(m_config->mainBarDeployButtonPoly, mousePos)) { + if (m_client->canBeamDown(true)) + warpToOrbitedWorld(true); + else if (m_client->canBeamUp()) + warpToOwnShip(); + return; + } + + if (overButton(m_config->mainBarBeamButtonPoly, mousePos)) { + if (m_client->canBeamDown()) + warpToOrbitedWorld(); + return; + } + + if (overButton(m_config->mainBarQuestLogButtonPoly, mousePos)) { + m_paneManager.toggleRegisteredPane(MainInterfacePanes::QuestLog); + return; + } + + if (overButton(m_config->mainBarMmUpgradeButtonPoly, mousePos)) { + if (m_client->mainPlayer()->inventory()->essentialItem(EssentialItem::BeamAxe)) + m_paneManager.toggleRegisteredPane(MainInterfacePanes::MmUpgrade); + return; + } + + if (overButton(m_config->mainBarCollectionsButtonPoly, mousePos)) { + m_paneManager.toggleRegisteredPane(MainInterfacePanes::Collections); + return; + } + + if (mouseButton == MouseButton::Left) + m_client->mainPlayer()->beginPrimaryFire(); + if (mouseButton == MouseButton::Right) + m_client->mainPlayer()->beginAltFire(); + if (mouseButton == MouseButton::Middle) + m_client->mainPlayer()->beginTrigger(); +} + +} \ No newline at end of file -- cgit v1.2.3