1601 lines
66 KiB
C++
1601 lines
66 KiB
C++
#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 "StarCanvasWidget.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, float spring)
|
|
: message(message), cooldown(cooldown), springState(spring) {}
|
|
|
|
MainInterface::MainInterface(UniverseClientPtr client, WorldPainterPtr painter, CinematicPtr cinematicOverlay)
|
|
: m_guiContext(GuiContext::singletonPtr())
|
|
, m_config(MainInterfaceConfig::loadFromAssets())
|
|
, m_client(std::move(client))
|
|
, m_worldPainter(std::move(painter))
|
|
, m_cinematicOverlay(std::move(cinematicOverlay))
|
|
, m_containerInteractor(make_shared<ContainerInteractor>())
|
|
{
|
|
GuiReader itemSlotReader;
|
|
m_cursorItem = convert<ItemSlotWidget>(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_stickyTargetingTimer = GameTimer(m_config->monsterHealthBarTime);
|
|
|
|
m_inventoryWindow = make_shared<InventoryPane>(this, m_client->mainPlayer(), m_containerInteractor);
|
|
m_paneManager.registerPane(MainInterfacePanes::Inventory, PaneLayer::Window, m_inventoryWindow, [this](PanePtr const&) {
|
|
if (auto player = m_client->mainPlayer())
|
|
player->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<ScriptPane>(m_interactionScriptPanes[id])->openWithInventory())
|
|
m_interactionScriptPanes[id]->dismiss();
|
|
}
|
|
});
|
|
|
|
m_overflowMessage = make_shared<GuiMessage>("", 0);
|
|
|
|
m_plainCraftingWindow = make_shared<CraftingPane>(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<SongbookInterface>(m_client->mainPlayer());
|
|
m_paneManager.registerPane(MainInterfacePanes::Songbook, PaneLayer::Window, songbookInterface);
|
|
|
|
m_questLogInterface = make_shared<QuestLogInterface>(m_client->questManager(), m_client->mainPlayer(), m_cinematicOverlay, m_client);
|
|
m_paneManager.registerPane(MainInterfacePanes::QuestLog, PaneLayer::Window, m_questLogInterface);
|
|
|
|
auto aiInterface = make_shared<AiInterface>(m_client, m_cinematicOverlay, &m_paneManager);
|
|
m_paneManager.registerPane(MainInterfacePanes::Ai, PaneLayer::Window, aiInterface);
|
|
|
|
m_codexInterface = make_shared<CodexInterface>(m_client->mainPlayer());
|
|
m_paneManager.registerPane(MainInterfacePanes::Codex, PaneLayer::Window, m_codexInterface);
|
|
|
|
m_optionsMenu = make_shared<OptionsMenu>(&m_paneManager);
|
|
m_paneManager.registerPane(MainInterfacePanes::Options, PaneLayer::ModalWindow, m_optionsMenu);
|
|
|
|
m_popupInterface = make_shared<PopupInterface>();
|
|
m_paneManager.registerPane(MainInterfacePanes::Popup, PaneLayer::Window, m_popupInterface);
|
|
|
|
m_confirmationDialog = make_shared<ConfirmationDialog>();
|
|
m_paneManager.registerPane(MainInterfacePanes::Confirmation, PaneLayer::ModalWindow, m_confirmationDialog);
|
|
|
|
m_joinRequestDialog = make_shared<JoinRequestDialog>();
|
|
m_paneManager.registerPane(MainInterfacePanes::JoinRequest, PaneLayer::ModalWindow, m_joinRequestDialog);
|
|
|
|
m_actionBar = make_shared<ActionBar>(&m_paneManager, m_client->mainPlayer());
|
|
m_paneManager.registerPane(MainInterfacePanes::ActionBar, PaneLayer::Hud, m_actionBar);
|
|
|
|
m_questTracker = make_shared<QuestTrackerPane>();
|
|
m_paneManager.registerPane(MainInterfacePanes::QuestTracker, PaneLayer::Hud, m_questTracker);
|
|
|
|
m_mmUpgrade = make_shared<ScriptPane>(m_client, "/interface/scripted/mmupgrade/mmupgradegui.config");
|
|
m_paneManager.registerPane(MainInterfacePanes::MmUpgrade, PaneLayer::Window, m_mmUpgrade);
|
|
|
|
m_collections = make_shared<ScriptPane>(m_client, "/interface/scripted/collections/collectionsgui.config");
|
|
m_paneManager.registerPane(MainInterfacePanes::Collections, PaneLayer::Window, m_collections);
|
|
|
|
m_chat = make_shared<Chat>(m_client);
|
|
m_paneManager.registerPane(MainInterfacePanes::Chat, PaneLayer::Hud, m_chat);
|
|
m_clientCommandProcessor = make_shared<ClientCommandProcessor>(m_client, m_cinematicOverlay, &m_paneManager, m_config->macroCommands);
|
|
|
|
m_radioMessagePopup = make_shared<RadioMessagePopup>();
|
|
m_paneManager.registerPane(MainInterfacePanes::RadioMessagePopup, PaneLayer::Hud, m_radioMessagePopup);
|
|
|
|
m_wireInterface = make_shared<WirePane>(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<TeamBar>(this, m_client);
|
|
m_paneManager.registerPane(MainInterfacePanes::TeamBar, PaneLayer::Hud, teamBar);
|
|
|
|
auto statusPane = make_shared<StatusPane>(&m_paneManager, m_client);
|
|
m_paneManager.registerPane(MainInterfacePanes::StatusPane, PaneLayer::Hud, statusPane);
|
|
|
|
auto planetName = make_shared<Pane>();
|
|
m_planetText = make_shared<LabelWidget>();
|
|
m_planetText->setFontSize(m_config->planetNameFontSize);
|
|
m_planetText->setFontMode(FontMode::Normal);
|
|
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<NameplatePainter>();
|
|
m_questIndicatorPainter = make_shared<QuestIndicatorPainter>(m_client);
|
|
m_chatBubbleManager = make_shared<ChatBubbleManager>();
|
|
|
|
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 (sourceEntityId != NullEntityId && m_craftingWindow->sourceEntityId() == sourceEntityId) {
|
|
m_craftingWindow.reset();
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_craftingWindow = make_shared<CraftingPane>(m_client->worldClient(), m_client->mainPlayer(), config, sourceEntityId);
|
|
m_paneManager.displayPane(PaneLayer::Window, m_craftingWindow, [this](PanePtr const&) {
|
|
if (auto player = m_client->mainPlayer())
|
|
player->clearSwap();
|
|
});
|
|
}
|
|
|
|
void MainInterface::openMerchantWindow(Json const& config, EntityId sourceEntityId) {
|
|
if (m_merchantWindow && m_paneManager.isDisplayed(m_merchantWindow)) {
|
|
m_paneManager.dismissPane(m_merchantWindow);
|
|
if (sourceEntityId != NullEntityId && m_merchantWindow->sourceEntityId() == sourceEntityId) {
|
|
m_merchantWindow.reset();
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool openWithInventory = config.getBool("openWithInventory", true);
|
|
m_merchantWindow = make_shared<MerchantPane>(m_client->worldClient(), m_client->mainPlayer(), config, sourceEntityId);
|
|
m_paneManager.displayPane(PaneLayer::Window,
|
|
m_merchantWindow,
|
|
[this, openWithInventory](PanePtr const&) {
|
|
if (auto player = m_client->mainPlayer())
|
|
player->clearSwap();
|
|
if (openWithInventory)
|
|
m_paneManager.dismissRegisteredPane(MainInterfacePanes::Inventory);
|
|
});
|
|
if (openWithInventory)
|
|
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<MouseMoveEvent>())
|
|
m_cursorScreenPos = mouseMove->mousePosition;
|
|
|
|
if (m_paneManager.sendInputEvent(event)) {
|
|
if (!event.is<MouseButtonUpEvent>() && !event.is<KeyUpEvent>())
|
|
return true;
|
|
}
|
|
|
|
if (event.is<KeyDownEvent>()) {
|
|
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<InventorySlot> 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<KeyUpEvent>()) {
|
|
if (m_guiContext->actionsForKey(keyUp->key).contains(InterfaceAction::GuiShifting))
|
|
m_guiContext->setShiftHeld(false);
|
|
|
|
return false;
|
|
|
|
} else if (auto mouseDown = event.ptr<MouseButtonDownEvent>()) {
|
|
auto mouseButton = mouseDown->mouseButton;
|
|
if (mouseButton >= MouseButton::Left && mouseButton <= MouseButton::Right
|
|
&& overlayClick(mouseDown->mousePosition, mouseDown->mouseButton)) {
|
|
return true;
|
|
} else {
|
|
if (mouseButton == MouseButton::Left)
|
|
player->beginPrimaryFire();
|
|
if (mouseButton == MouseButton::Right)
|
|
player->beginAltFire();
|
|
if (mouseButton == MouseButton::Middle)
|
|
player->beginTrigger();
|
|
}
|
|
} else if (auto mouseUp = event.ptr<MouseButtonUpEvent>()) {
|
|
if (mouseUp->mouseButton == MouseButton::Left)
|
|
player->endPrimaryFire();
|
|
if (mouseUp->mouseButton == MouseButton::Right)
|
|
player->endAltFire();
|
|
if (mouseUp->mouseButton == MouseButton::Middle)
|
|
player->endTrigger();
|
|
}
|
|
|
|
bool captured = false;
|
|
|
|
for (auto& pair : m_canvases)
|
|
captured |= pair.second->sendEvent(event);
|
|
|
|
return captured;
|
|
}
|
|
|
|
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<ContainerEntity>(interactAction.entityId);
|
|
if (!containerEntity)
|
|
return;
|
|
|
|
m_containerInteractor->openContainer(containerEntity);
|
|
|
|
m_paneManager.displayRegisteredPane(MainInterfacePanes::Inventory);
|
|
|
|
m_containerPane = make_shared<ContainerPane>(world, m_client->mainPlayer(), m_containerInteractor);
|
|
m_paneManager.displayPane(PaneLayer::Window, m_containerPane, [this](PanePtr const&) {
|
|
if (auto player = m_client->mainPlayer())
|
|
player->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 (interactAction.entityId != NullEntityId && !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 (interactAction.entityId != NullEntityId && !world->entity(interactAction.entityId))
|
|
return;
|
|
// wait, this is literally the exact same as OpenCraftingInterface. what the fuck? lol
|
|
openCraftingWindow(interactAction.data, interactAction.entityId);
|
|
} else if (interactAction.type == InteractActionType::OpenMerchantInterface) {
|
|
if (interactAction.entityId != NullEntityId && !world->entity(interactAction.entityId))
|
|
return;
|
|
|
|
openMerchantWindow(interactAction.data, interactAction.entityId);
|
|
} else if (interactAction.type == InteractActionType::OpenAiInterface) {
|
|
as<AiInterface>(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<ClientShipWorldId>()) {
|
|
icon = "ship";
|
|
planetName = "Player Ship";
|
|
} else if (m_client->playerWorld().is<CelestialWorldId>()) {
|
|
icon = worldTemplate->worldParameters()->typeName;
|
|
planetName = worldTemplate->worldName();
|
|
} else if (m_client->playerWorld().is<InstanceWorldId>()) {
|
|
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<EditBookmarkDialog>(m_client->mainPlayer()->universeMap());
|
|
editBookmarkDialog->setBookmark(currentLocation);
|
|
m_paneManager.displayPane(PaneLayer::ModalWindow, editBookmarkDialog);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (config.getBool("canTeleport", true)) {
|
|
m_teleportDialog = make_shared<TeleportDialog>(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<ScriptPane>(m_client, interactAction.data, sourceEntity);
|
|
displayScriptPane(scriptPane, sourceEntity);
|
|
|
|
} else if (interactAction.type == InteractActionType::Message) {
|
|
m_client->mainPlayer()->receiveMessage(connectionForEntity(interactAction.entityId),
|
|
interactAction.data.getString("messageType"), interactAction.data.getArray("messageArgs"));
|
|
}
|
|
}
|
|
|
|
void MainInterface::preUpdate(float dt) {
|
|
auto player = m_client->mainPlayer();
|
|
if (!m_client->paused())
|
|
player->aim(cursorWorldPosition());
|
|
}
|
|
|
|
void MainInterface::update(float dt) {
|
|
m_paneManager.update(dt);
|
|
m_cursor.update(dt);
|
|
|
|
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 (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(dt);
|
|
auto mouseoverEntities = m_client->worldClient()->query<DamageBarEntity>(RectF::withCenter(cursorWorldPos, Vec2F(1, 1)), [=](shared_ptr<DamageBarEntity> 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<DamageBarEntity>(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<DamageBarEntity>(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 * dt)
|
|
m_specialDamageBarValue = targetHealth;
|
|
else
|
|
m_specialDamageBarValue += copysign(1.0f, targetHealth - m_specialDamageBarValue) * fillSpeed * dt;
|
|
} else {
|
|
m_specialDamageBarTarget = NullEntityId;
|
|
}
|
|
}
|
|
|
|
if (m_specialDamageBarTarget == NullEntityId)
|
|
m_specialDamageBarValue = 0.0f;
|
|
|
|
if (m_specialDamageBarTarget == NullEntityId && m_client->mainPlayer()->inWorld()) {
|
|
List<DamageBarEntityPtr> specialDamageTargets;
|
|
m_client->worldClient()->forAllEntities([&specialDamageTargets](EntityPtr const& entity) {
|
|
if (auto damageBarEntity = as<DamageBarEntity>(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("<frame>", "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("<count>", toString(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 -= dt;
|
|
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++;
|
|
}
|
|
|
|
bool playerInWorld = m_client->mainPlayer()->inWorld();
|
|
if (m_cinematicOverlay->completed()) {
|
|
if (m_planetNameTimer.tick(dt)) {
|
|
m_paneManager.dismissRegisteredPane(MainInterfacePanes::PlanetText);
|
|
} else {
|
|
if (playerInWorld) {
|
|
String worldName;
|
|
if (auto worldTemplate = m_client->worldClient()->currentTemplate())
|
|
worldName = worldTemplate->worldName();
|
|
|
|
if (!worldName.empty()) {
|
|
m_planetText->setText(strf(m_config->planetNameFormatString.utf8Ptr(), worldName));
|
|
m_paneManager.displayRegisteredPane(MainInterfacePanes::PlanetText);
|
|
}
|
|
}
|
|
|
|
Color textColor = Color::White; // probably need to make this jsonable
|
|
float fadeTimer = m_planetNameTimer.timer;
|
|
if (fadeTimer < m_config->planetNameFadeTime)
|
|
textColor.setAlphaF(fadeTimer / m_config->planetNameFadeTime);
|
|
|
|
m_planetText->setColor(textColor);
|
|
}
|
|
} else if (!playerInWorld) {
|
|
m_planetNameTimer.reset();
|
|
m_paneManager.dismissRegisteredPane(MainInterfacePanes::PlanetText);
|
|
}
|
|
|
|
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(dt, m_client->worldClient(), m_worldPainter->camera(), m_client->worldClient()->interactiveHighlightMode());
|
|
m_questIndicatorPainter->update(dt, m_client->worldClient(), m_worldPainter->camera());
|
|
|
|
m_chatBubbleManager->setCamera(m_worldPainter->camera());
|
|
if (auto worldClient = m_client->worldClient()) {
|
|
auto chatActions = worldClient->pullPendingChatActions();
|
|
auto portraitActions = chatActions.filtered([](ChatAction action) { return action.is<PortraitChatAction>(); });
|
|
|
|
for (auto action : portraitActions) {
|
|
PortraitChatAction portraitAction = action.get<PortraitChatAction>();
|
|
|
|
String name;
|
|
if (auto npc = as<Npc>(worldClient->entity(portraitAction.entity)))
|
|
name = npc->name();
|
|
|
|
ChatReceivedMessage message = {
|
|
{ MessageContext::World },
|
|
ServerConnectionId,
|
|
Text::stripEscapeCodes(name),
|
|
Text::stripEscapeCodes(portraitAction.text),
|
|
Text::stripEscapeCodes(portraitAction.portrait.replace("<frame>", "0"))
|
|
};
|
|
m_chat->addMessages({message}, false);
|
|
}
|
|
|
|
m_chatBubbleManager->addChatActions(chatActions);
|
|
m_chatBubbleManager->update(dt, worldClient);
|
|
}
|
|
|
|
if (auto container = m_client->worldClient()->get<ContainerEntity>(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);
|
|
|
|
for (auto& pair : m_canvases) {
|
|
pair.second->setPosition(Vec2I());
|
|
if (pair.second->ignoreInterfaceScale())
|
|
pair.second->setSize(Vec2I(m_guiContext->windowSize()));
|
|
else
|
|
pair.second->setSize(Vec2I(m_guiContext->windowInterfaceSize()));
|
|
pair.second->update(dt);
|
|
}
|
|
}
|
|
|
|
void MainInterface::renderInWorldElements() {
|
|
if (m_disableHud)
|
|
return;
|
|
|
|
m_guiContext->setDefaultFont();
|
|
m_guiContext->setFontProcessingDirectives("");
|
|
m_guiContext->setFontColor(Vec4B::filled(255));
|
|
m_questIndicatorPainter->render();
|
|
m_nameplatePainter->render();
|
|
m_chatBubbleManager->render();
|
|
}
|
|
|
|
void MainInterface::render() {
|
|
if (m_disableHud)
|
|
return;
|
|
|
|
m_guiContext->setDefaultFont();
|
|
m_guiContext->setFontProcessingDirectives("");
|
|
m_guiContext->setFontColor(Vec4B::filled(255));
|
|
renderBreath();
|
|
renderMessages();
|
|
renderMonsterHealthBar();
|
|
renderSpecialDamageBar();
|
|
renderMainBar();
|
|
renderDebug();
|
|
|
|
RectI screenRect = RectI::withSize(Vec2I(), Vec2I(m_guiContext->windowSize()));
|
|
for (auto& pair : m_canvases)
|
|
pair.second->render(screenRect);
|
|
|
|
renderWindows();
|
|
renderCursor();
|
|
}
|
|
|
|
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, Maybe<float> cooldown, float spring) {
|
|
auto guiMessage = make_shared<GuiMessage>(message, cooldown.value(m_config->messageTime), spring);
|
|
m_messages.append(guiMessage);
|
|
}
|
|
|
|
void MainInterface::queueMessage(String const& message) {
|
|
queueMessage(message, m_config->messageTime, 0.0f);
|
|
}
|
|
|
|
void MainInterface::queueJoinRequest(pair<String, RpcPromiseKeeper<P2PJoinRequestReply>> 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("{} - {}", item->friendlyName(), newCount);
|
|
message->cooldown = m_config->messageTime;
|
|
m_itemDropMessages[descriptor.singular()] = {newCount, message};
|
|
} else {
|
|
auto message = make_shared<GuiMessage>(strf("{} - {}", 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");
|
|
}
|
|
}
|
|
|
|
CanvasWidgetPtr MainInterface::fetchCanvas(String const& canvasName, bool ignoreInterfaceScale) {
|
|
CanvasWidgetPtr canvas;
|
|
|
|
if (auto canvasPtr = m_canvases.ptr(canvasName))
|
|
canvas = *canvasPtr;
|
|
else {
|
|
m_canvases.emplace(canvasName, canvas = make_shared<CanvasWidget>());
|
|
canvas->setPosition(Vec2I());
|
|
if (ignoreInterfaceScale)
|
|
canvas->setSize(Vec2I(m_guiContext->windowSize()));
|
|
else
|
|
canvas->setSize(Vec2I(m_guiContext->windowInterfaceSize()));
|
|
}
|
|
|
|
canvas->setIgnoreInterfaceScale(ignoreInterfaceScale);
|
|
return canvas;
|
|
}
|
|
|
|
// For when the player swaps characters. We need to completely reload ScriptPanes,
|
|
// because a lot of ScriptPanes do not expect the character to suddenly change and may break or spill data over.
|
|
void MainInterface::takeScriptPanes(List<ScriptPaneInfo>& out) {
|
|
m_paneManager.dismissWhere([&](PanePtr const& pane) {
|
|
if (auto scriptPane = as<ScriptPane>(pane)) {
|
|
if (scriptPane->isDismissed())
|
|
return false;
|
|
auto sourceEntityId = scriptPane->sourceEntityId();
|
|
m_interactionScriptPanes.remove(sourceEntityId);
|
|
auto& info = out.emplaceAppend();
|
|
info.scriptPane = scriptPane;
|
|
info.config = scriptPane->rawConfig();
|
|
info.sourceEntityId = sourceEntityId;
|
|
info.visible = scriptPane->visibility();
|
|
info.position = scriptPane->relativePosition();
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
void MainInterface::reviveScriptPanes(List<ScriptPaneInfo>& panes) {
|
|
for (auto& info : panes) { // this is evil and stupid
|
|
info.scriptPane->~ScriptPane();
|
|
new(info.scriptPane.get()) ScriptPane(m_client, info.config, info.sourceEntityId);
|
|
info.scriptPane->setVisibility(info.visible);
|
|
displayScriptPane(info.scriptPane, info.sourceEntityId);
|
|
info.scriptPane->setPosition(info.position);
|
|
}
|
|
}
|
|
|
|
PanePtr MainInterface::createEscapeDialog() {
|
|
auto assets = Root::singleton().assets();
|
|
|
|
auto escapeDialog = make_shared<Pane>();
|
|
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<LabelWidget>("lblversion")->setText(strf("OpenStarbound - {} ({})", 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) {
|
|
String breathPath = "/interface/breath/breath.png";
|
|
m_guiContext->drawQuad(breathPath, RectF::withCenter(breathBackgroundCenterPos, Vec2F(imgMetadata->imageSize(breathPath)) * 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() {
|
|
if (m_messages.empty())
|
|
return;
|
|
|
|
Vec2F totalOffset = {};
|
|
auto imgMetadata = Root::singleton().imageMetadataDatabase();
|
|
unsigned bottomOffset = Root::singleton().configuration()->getPath("inventory.bottomActionBar").optBool().value(false) ? 32 : 0;
|
|
for (auto& message : m_messages) {
|
|
Vec2F hiddenOffset = Vec2F(m_config->messageHiddenOffset);
|
|
Vec2F activeOffset = Vec2F(m_config->messageActiveOffset);
|
|
if (bottomOffset) {
|
|
activeOffset[1] += bottomOffset;
|
|
bottomOffset = 0;
|
|
}
|
|
Vec2F messageOffset = lerp(message->springState, Vec2F(), activeOffset - 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->setFont(m_config->font);
|
|
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<DamageBarEntity>(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->setFont(m_config->font);
|
|
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<Drawable> portrait = showDamageEntity->portrait(PortraitMode::Full);
|
|
|
|
auto bounds = Drawable::boundBoxAll(portrait, true);
|
|
if (m_portraitScale == 0)
|
|
m_portraitScale = max<int>(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(std::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<DamageBarEntity>(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();
|
|
SpatialLogger::setObserved(false);
|
|
return;
|
|
}
|
|
SpatialLogger::setObserved(true);
|
|
|
|
if (m_clientCommandProcessor->debugHudEnabled()) {
|
|
auto assets = Root::singleton().assets();
|
|
m_guiContext->setFontSize(m_config->debugFontSize);
|
|
m_guiContext->setFont(m_config->debugFont);
|
|
m_guiContext->setLineSpacing(0.5f);
|
|
m_guiContext->setFontProcessingDirectives(m_config->debugFontDirectives);
|
|
m_guiContext->setFontColor(Color::White.toRgba());
|
|
m_guiContext->setFontMode(FontMode::Normal);
|
|
|
|
bool clearMap = m_debugMapClearTimer.wrapTick();
|
|
auto logMapValues = LogMap::getValues();
|
|
if (clearMap)
|
|
LogMap::clear();
|
|
|
|
List<String> formatted;
|
|
formatted.reserve(logMapValues.size());
|
|
|
|
int counter = 0;
|
|
for (auto const& pair : logMapValues) {
|
|
TextPositioning positioning = { Vec2F(m_config->debugOffset[0], windowHeight() - m_config->debugOffset[1] - m_config->fontSize * interfaceScale() * counter++) };
|
|
String& text = formatted.emplace_back(strf("{}^lightgray;:^green,set; {}", pair.first, pair.second));
|
|
m_debugTextRect.combine(m_guiContext->determineTextSize(text, positioning).padded(m_config->debugBackgroundPad));
|
|
}
|
|
|
|
if (!m_debugTextRect.isNull()) {
|
|
RenderQuad& quad = m_guiContext->renderer()->immediatePrimitives()
|
|
.emplace_back(std::in_place_type_t<RenderQuad>(), m_debugTextRect, m_config->debugBackgroundColor.toRgba(), 0.0f).get<RenderQuad>();
|
|
|
|
quad.b.color[3] = quad.c.color[3] = 0;
|
|
};
|
|
|
|
m_debugTextRect = RectF::null();
|
|
|
|
for (size_t index = 0; index != formatted.size(); ++index) {
|
|
TextPositioning positioning = { Vec2F(m_config->debugOffset[0], windowHeight() - m_config->debugOffset[1] - m_config->fontSize * interfaceScale() * index) };
|
|
m_guiContext->renderText(formatted[index], positioning);
|
|
}
|
|
|
|
m_guiContext->setFontSize(8);
|
|
m_guiContext->setDefaultFont();
|
|
m_guiContext->setDefaultLineSpacing();
|
|
m_guiContext->setFontColor(Vec4B::filled(255));
|
|
m_guiContext->setFontProcessingDirectives("");
|
|
}
|
|
|
|
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(m_config->debugFontSize);
|
|
|
|
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<String> 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<LoungeableEntity>(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<ActiveItem>(item)) {
|
|
if (auto cursor = activeItem->cursor()) {
|
|
cursorOverride = cursor;
|
|
break;
|
|
}
|
|
} else if (auto inspectionTool = as<InspectionTool>(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_guiContext->applicationController()->setCursorVisible(false);
|
|
|
|
Vec2I cursorPos = m_cursorScreenPos;
|
|
Vec2I cursorSize = m_cursor.size();
|
|
Vec2I cursorOffset = m_cursor.offset();
|
|
unsigned int cursorScale = m_cursor.scale(interfaceScale());
|
|
Drawable cursorDrawable = m_cursor.drawable();
|
|
|
|
cursorPos[0] -= cursorOffset[0] * cursorScale;
|
|
cursorPos[1] -= (cursorSize[1] - cursorOffset[1]) * cursorScale;
|
|
if (!m_guiContext->trySetCursor(cursorDrawable, cursorOffset, cursorScale))
|
|
m_guiContext->drawDrawable(cursorDrawable, Vec2F(cursorPos), cursorScale);
|
|
|
|
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) * cursorScale;
|
|
Vec2I tooltipOffset = m_cursorScreenPos + cursorOffset;
|
|
size_t fontSize = assets->json("/interface.config:cursorTooltip.fontSize").toUInt();
|
|
String font = assets->json("/interface.config:cursorTooltip.font").toString();
|
|
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->setFont(font);
|
|
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);
|
|
}
|
|
|
|
bool MainInterface::overlayClick(Vec2I const& mousePos, 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 true;
|
|
}
|
|
|
|
if (overButton(m_config->mainBarCraftButtonPoly, mousePos)) {
|
|
togglePlainCraftingWindow();
|
|
return true;
|
|
}
|
|
|
|
if (overButton(m_config->mainBarCodexButtonPoly, mousePos)) {
|
|
m_paneManager.toggleRegisteredPane(MainInterfacePanes::Codex);
|
|
return true;
|
|
}
|
|
|
|
if (overButton(m_config->mainBarDeployButtonPoly, mousePos)) {
|
|
if (m_client->canBeamDown(true))
|
|
warpToOrbitedWorld(true);
|
|
else if (m_client->canBeamUp())
|
|
warpToOwnShip();
|
|
return true;
|
|
}
|
|
|
|
if (overButton(m_config->mainBarBeamButtonPoly, mousePos)) {
|
|
if (m_client->canBeamDown())
|
|
warpToOrbitedWorld();
|
|
return true;
|
|
}
|
|
|
|
if (overButton(m_config->mainBarQuestLogButtonPoly, mousePos)) {
|
|
m_paneManager.toggleRegisteredPane(MainInterfacePanes::QuestLog);
|
|
return true;
|
|
}
|
|
|
|
if (overButton(m_config->mainBarMmUpgradeButtonPoly, mousePos)) {
|
|
if (m_client->mainPlayer()->inventory()->essentialItem(EssentialItem::BeamAxe))
|
|
m_paneManager.toggleRegisteredPane(MainInterfacePanes::MmUpgrade);
|
|
return true;
|
|
}
|
|
|
|
if (overButton(m_config->mainBarCollectionsButtonPoly, mousePos)) {
|
|
m_paneManager.toggleRegisteredPane(MainInterfacePanes::Collections);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void MainInterface::displayScriptPane(ScriptPanePtr& scriptPane, EntityId sourceEntity) {
|
|
// keep any number of script panes open with null source entities
|
|
if (sourceEntity != NullEntityId)
|
|
m_interactionScriptPanes[sourceEntity] = scriptPane;
|
|
|
|
PaneLayer layer = PaneLayer::Window;
|
|
if (auto layerName = scriptPane->config().optString("paneLayer"))
|
|
layer = PaneLayerNames.getLeft(*layerName);
|
|
|
|
if (scriptPane->openWithInventory()) {
|
|
m_paneManager.displayPane(layer, scriptPane, [this](PanePtr const&) {
|
|
if (auto player = m_client->mainPlayer())
|
|
player->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(layer, scriptPane);
|
|
}
|
|
}
|
|
|
|
} |