From 83bc27d99dacbb249b95387a9511d9b38830bf55 Mon Sep 17 00:00:00 2001 From: cx384 Date: Fri, 20 Dec 2024 11:02:20 +0100 Subject: [PATCH] Move formspec code from game.cpp to separate file --- src/client/CMakeLists.txt | 1 + src/client/game.cpp | 512 ++-------------------------------- src/client/game_formspec.cpp | 524 +++++++++++++++++++++++++++++++++++ src/client/game_formspec.h | 62 +++++ src/client/gameui.cpp | 12 - src/client/gameui.h | 15 - 6 files changed, 605 insertions(+), 521 deletions(-) create mode 100644 src/client/game_formspec.cpp create mode 100644 src/client/game_formspec.h diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index 0bcb667bc..c17148a35 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -51,6 +51,7 @@ set(client_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/fontengine.cpp ${CMAKE_CURRENT_SOURCE_DIR}/game.cpp ${CMAKE_CURRENT_SOURCE_DIR}/gameui.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/game_formspec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiscalingfilter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/hud.cpp ${CMAKE_CURRENT_SOURCE_DIR}/imagefilters.cpp diff --git a/src/client/game.cpp b/src/client/game.cpp index 8bd6f5f29..97b50183c 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -11,6 +11,7 @@ #include "client.h" #include "client/clientevent.h" #include "client/gameui.h" +#include "client/game_formspec.h" #include "client/inputhandler.h" #include "client/texturepaths.h" #include "client/keys.h" @@ -26,18 +27,13 @@ #include "client/event_manager.h" #include "fontengine.h" #include "gui/touchcontrols.h" -#include "gui/touchscreeneditor.h" #include "itemdef.h" #include "log.h" #include "log_internal.h" #include "gameparams.h" #include "gettext.h" #include "gui/guiChatConsole.h" -#include "gui/guiFormSpecMenu.h" -#include "gui/guiKeyChangeMenu.h" -#include "gui/guiPasswordChange.h" -#include "gui/guiOpenURL.h" -#include "gui/guiVolumeChange.h" +#include "texturesource.h" #include "gui/mainmenumanager.h" #include "gui/profilergraph.h" #include "minimap.h" @@ -69,176 +65,6 @@ #include "client/sound/sound_openal.h" #endif -/* - Text input system -*/ - -struct TextDestNodeMetadata : public TextDest -{ - TextDestNodeMetadata(v3s16 p, Client *client) - { - m_p = p; - m_client = client; - } - // This is deprecated I guess? -celeron55 - void gotText(const std::wstring &text) - { - std::string ntext = wide_to_utf8(text); - infostream << "Submitting 'text' field of node at (" << m_p.X << "," - << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl; - StringMap fields; - fields["text"] = ntext; - m_client->sendNodemetaFields(m_p, "", fields); - } - void gotText(const StringMap &fields) - { - m_client->sendNodemetaFields(m_p, "", fields); - } - - v3s16 m_p; - Client *m_client; -}; - -struct TextDestPlayerInventory : public TextDest -{ - TextDestPlayerInventory(Client *client) - { - m_client = client; - m_formname.clear(); - } - TextDestPlayerInventory(Client *client, const std::string &formname) - { - m_client = client; - m_formname = formname; - } - void gotText(const StringMap &fields) - { - m_client->sendInventoryFields(m_formname, fields); - } - - Client *m_client; -}; - -struct LocalFormspecHandler : public TextDest -{ - LocalFormspecHandler(const std::string &formname) - { - m_formname = formname; - } - - LocalFormspecHandler(const std::string &formname, Client *client): - m_client(client) - { - m_formname = formname; - } - - void gotText(const StringMap &fields) - { - if (m_formname == "MT_PAUSE_MENU") { - if (fields.find("btn_sound") != fields.end()) { - g_gamecallback->changeVolume(); - return; - } - - if (fields.find("btn_key_config") != fields.end()) { - g_gamecallback->keyConfig(); - return; - } - - if (fields.find("btn_touchscreen_layout") != fields.end()) { - g_gamecallback->touchscreenLayout(); - return; - } - - if (fields.find("btn_exit_menu") != fields.end()) { - g_gamecallback->disconnect(); - return; - } - - if (fields.find("btn_exit_os") != fields.end()) { - g_gamecallback->exitToOS(); -#ifndef __ANDROID__ - RenderingEngine::get_raw_device()->closeDevice(); -#endif - return; - } - - if (fields.find("btn_change_password") != fields.end()) { - g_gamecallback->changePassword(); - return; - } - - return; - } - - if (m_formname == "MT_DEATH_SCREEN") { - assert(m_client != nullptr); - - if (fields.find("quit") != fields.end()) - m_client->sendRespawnLegacy(); - - return; - } - - if (m_client->modsLoaded()) - m_client->getScript()->on_formspec_input(m_formname, fields); - } - - Client *m_client = nullptr; -}; - -/* Form update callback */ - -class NodeMetadataFormSource: public IFormSource -{ -public: - NodeMetadataFormSource(ClientMap *map, v3s16 p): - m_map(map), - m_p(p) - { - } - const std::string &getForm() const - { - static const std::string empty_string = ""; - NodeMetadata *meta = m_map->getNodeMetadata(m_p); - - if (!meta) - return empty_string; - - return meta->getString("formspec"); - } - - virtual std::string resolveText(const std::string &str) - { - NodeMetadata *meta = m_map->getNodeMetadata(m_p); - - if (!meta) - return str; - - return meta->resolveString(str); - } - - ClientMap *m_map; - v3s16 m_p; -}; - -class PlayerInventoryFormSource: public IFormSource -{ -public: - PlayerInventoryFormSource(Client *client): - m_client(client) - { - } - - const std::string &getForm() const - { - LocalPlayer *player = m_client->getEnv().getLocalPlayer(); - return player->inventory_formspec; - } - - Client *m_client; -}; - class NodeDugEvent : public MtEvent { public: @@ -596,8 +422,6 @@ public: } }; -#define SIZE_TAG "size[11,5.5,true]" // Fixed size (ignored in touchscreen mode) - /**************************************************************************** ****************************************************************************/ @@ -699,7 +523,6 @@ protected: void updateInteractTimers(f32 dtime); bool checkConnection(); - bool handleCallbacks(); void processQueues(); void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime); void updateDebugState(); @@ -713,7 +536,6 @@ protected: bool shouldShowTouchControls(); void dropSelectedItem(bool single_item = false); - void openInventory(); void openConsole(float scale, const wchar_t *line=NULL); void toggleFreeMove(); void toggleFreeMoveAlt(); @@ -815,9 +637,6 @@ private: bool disable_camera_update = false; }; - void showDeathFormspecLegacy(); - void showPauseMenu(); - void pauseAnimation(); void resumeAnimation(); @@ -882,6 +701,7 @@ private: irr_ptr sky; Hud *hud = nullptr; Minimap *mapper = nullptr; + GameFormSpec m_game_formspec; // Map server hud ids to client hud ids std::unordered_map m_hud_server_to_client; @@ -1089,6 +909,8 @@ bool Game::startup(bool *kill, m_rendering_engine->initialize(client, hud); + m_game_formspec.init(client, m_rendering_engine, input); + return true; } @@ -1159,7 +981,7 @@ void Game::run() if (!checkConnection()) break; - if (!handleCallbacks()) + if (!m_game_formspec.handleCallbacks()) break; processQueues(); @@ -1191,7 +1013,7 @@ void Game::run() updateProfilerGraphs(&graph); if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) { - showPauseMenu(); + m_game_formspec.showPauseMenu(); } } @@ -1203,10 +1025,6 @@ void Game::run() void Game::shutdown() { - auto formspec = m_game_ui->getFormspecGUI(); - if (formspec) - formspec->quitMenu(); - // Clear text when exiting. m_game_ui->clearText(); @@ -1228,8 +1046,6 @@ void Game::shutdown() g_menumgr.deleteFront(); } - m_game_ui->deleteFormspec(); - chat_backend->addMessage(L"", L"# Disconnected."); chat_backend->addMessage(L"", L""); @@ -1822,55 +1638,6 @@ inline bool Game::checkConnection() return true; } - -/* returns false if game should exit, otherwise true - */ -inline bool Game::handleCallbacks() -{ - if (g_gamecallback->disconnect_requested) { - g_gamecallback->disconnect_requested = false; - return false; - } - - if (g_gamecallback->changepassword_requested) { - (void)make_irr(guienv, guiroot, -1, - &g_menumgr, client, texture_src); - g_gamecallback->changepassword_requested = false; - } - - if (g_gamecallback->changevolume_requested) { - (void)make_irr(guienv, guiroot, -1, - &g_menumgr, texture_src); - g_gamecallback->changevolume_requested = false; - } - - if (g_gamecallback->keyconfig_requested) { - (void)make_irr(guienv, guiroot, -1, - &g_menumgr, texture_src); - g_gamecallback->keyconfig_requested = false; - } - - if (g_gamecallback->touchscreenlayout_requested) { - (new GUITouchscreenLayout(guienv, guiroot, -1, - &g_menumgr, texture_src))->drop(); - g_gamecallback->touchscreenlayout_requested = false; - } - - if (!g_gamecallback->show_open_url_dialog.empty()) { - (void)make_irr(guienv, guiroot, -1, - &g_menumgr, texture_src, g_gamecallback->show_open_url_dialog); - g_gamecallback->show_open_url_dialog.clear(); - } - - if (g_gamecallback->keyconfig_changed) { - input->keycache.populate(); // update the cache with new settings - g_gamecallback->keyconfig_changed = false; - } - - return true; -} - - void Game::processQueues() { texture_src->processQueue(); @@ -1897,10 +1664,7 @@ void Game::updateDebugState() if (!has_debug) { draw_control->show_wireframe = false; m_flags.disable_camera_update = false; - auto formspec = m_game_ui->getFormspecGUI(); - if (formspec) { - formspec->setDebugView(false); - } + m_game_formspec.disableDebugView(); } // noclip @@ -2044,10 +1808,7 @@ void Game::processUserInput(f32 dtime) input->step(dtime); #ifdef __ANDROID__ - auto formspec = m_game_ui->getFormspecGUI(); - if (formspec) - formspec->getAndroidUIInput(); - else + if (!m_game_formspec.handleAndroidUIInput()) handleAndroidChatInput(); #endif @@ -2072,13 +1833,13 @@ void Game::processKeyInput() if (g_settings->getBool("continuous_forward")) toggleAutoforward(); } else if (wasKeyDown(KeyType::INVENTORY)) { - openInventory(); + m_game_formspec.showPlayerInventory(); } else if (input->cancelPressed()) { #ifdef __ANDROID__ m_android_chat_open = false; #endif if (!gui_chat_console->isOpenInhibited()) { - showPauseMenu(); + m_game_formspec.showPauseMenu(); } } else if (wasKeyDown(KeyType::CHAT)) { openConsole(0.2, L""); @@ -2246,45 +2007,6 @@ void Game::dropSelectedItem(bool single_item) client->inventoryAction(a); } - -void Game::openInventory() -{ - /* - * Don't permit to open inventory is CAO or player doesn't exists. - * This prevent showing an empty inventory at player load - */ - - LocalPlayer *player = client->getEnv().getLocalPlayer(); - if (!player || !player->getCAO()) - return; - - infostream << "Game: Launching inventory" << std::endl; - - PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client); - - InventoryLocation inventoryloc; - inventoryloc.setCurrentPlayer(); - - if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) { - delete fs_src; - return; - } - - if (fs_src->getForm().empty()) { - delete fs_src; - return; - } - - TextDest *txt_dst = new TextDestPlayerInventory(client); - auto *&formspec = m_game_ui->updateFormspec(""); - GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), - &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), - sound_manager.get()); - - formspec->setFormSpec(fs_src->getForm(), inventoryloc); -} - - void Game::openConsole(float scale, const wchar_t *line) { assert(scale > 0.0f && scale <= 1.0f); @@ -2886,28 +2608,13 @@ void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientati void Game::handleClientEvent_DeathscreenLegacy(ClientEvent *event, CameraOrientation *cam) { - showDeathFormspecLegacy(); + m_game_formspec.showDeathFormspecLegacy(); } void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam) { - if (event->show_formspec.formspec->empty()) { - auto formspec = m_game_ui->getFormspecGUI(); - if (formspec && (event->show_formspec.formname->empty() - || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) { - formspec->quitMenu(); - } - } else { - FormspecFormSource *fs_src = - new FormspecFormSource(*(event->show_formspec.formspec)); - TextDestPlayerInventory *txt_dst = - new TextDestPlayerInventory(client, *(event->show_formspec.formname)); - - auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname)); - GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), - &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), - sound_manager.get()); - } + m_game_formspec.showFormSpec(*event->show_formspec.formspec, + *event->show_formspec.formname); delete event->show_formspec.formspec; delete event->show_formspec.formname; @@ -2915,11 +2622,8 @@ void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam) { - FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec); - LocalFormspecHandler *txt_dst = - new LocalFormspecHandler(*event->show_formspec.formname, client); - GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(), - &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound_manager.get()); + m_game_formspec.showLocalFormSpec(*event->show_formspec.formspec, + *event->show_formspec.formname); delete event->show_formspec.formspec; delete event->show_formspec.formname; @@ -3657,21 +3361,7 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, if (nodedef_manager->get(map.getNode(nodepos)).rightclickable) client->interact(INTERACT_PLACE, pointed); - infostream << "Launching custom inventory view" << std::endl; - - InventoryLocation inventoryloc; - inventoryloc.setNodeMeta(nodepos); - - NodeMetadataFormSource *fs_src = new NodeMetadataFormSource( - &client->getEnv().getClientMap(), nodepos); - TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client); - - auto *&formspec = m_game_ui->updateFormspec(""); - GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), - &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), - sound_manager.get()); - - formspec->setFormSpec(meta->getString("formspec"), inventoryloc); + m_game_formspec.showNodeFormspec(meta->getString("formspec"), nodepos); return false; } @@ -4207,34 +3897,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console.get(), dtime); - /* - make sure menu is on top - 1. Delete formspec menu reference if menu was removed - 2. Else, make sure formspec menu is on top - */ - auto formspec = m_game_ui->getFormspecGUI(); - do { // breakable. only runs for one iteration - if (!formspec) - break; - - if (formspec->getReferenceCount() == 1) { - // See GUIFormSpecMenu::create what refcnt = 1 means - m_game_ui->deleteFormspec(); - break; - } - - auto &loc = formspec->getFormspecLocation(); - if (loc.type == InventoryLocation::NODEMETA) { - NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p); - if (!meta || meta->getString("formspec").empty()) { - formspec->quitMenu(); - break; - } - } - - if (isMenuActive()) - guiroot->bringToFront(formspec); - } while (false); + m_game_formspec.update(); /* ==================== Drawing begins ==================== @@ -4451,145 +4114,6 @@ void Game::readSettings() m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus"); } -/****************************************************************************/ -/**************************************************************************** - Shutdown / cleanup - ****************************************************************************/ -/****************************************************************************/ - -void Game::showDeathFormspecLegacy() -{ - static std::string formspec_str = - std::string("formspec_version[1]") + - SIZE_TAG - "bgcolor[#320000b4;true]" - "label[4.85,1.35;" + gettext("You died") + "]" - "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]" - ; - - /* Create menu */ - /* Note: FormspecFormSource and LocalFormspecHandler * - * are deleted by guiFormSpecMenu */ - FormspecFormSource *fs_src = new FormspecFormSource(formspec_str); - LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client); - - auto *&formspec = m_game_ui->getFormspecGUI(); - GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), - &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), - sound_manager.get()); - formspec->setFocus("btn_respawn"); -} - -#define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name()) -void Game::showPauseMenu() -{ - std::string control_text; - - if (g_touchcontrols) { - control_text = strgettext("Controls:\n" - "No menu open:\n" - "- slide finger: look around\n" - "- tap: place/punch/use (default)\n" - "- long tap: dig/use (default)\n" - "Menu/inventory open:\n" - "- double tap (outside):\n" - " --> close\n" - "- touch stack, touch slot:\n" - " --> move stack\n" - "- touch&drag, tap 2nd finger\n" - " --> place single item to slot\n" - ); - } - - float ypos = simple_singleplayer_mode ? 0.7f : 0.1f; - std::ostringstream os; - - os << "formspec_version[1]" << SIZE_TAG - << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;" - << strgettext("Continue") << "]"; - - if (!simple_singleplayer_mode) { - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;" - << strgettext("Change Password") << "]"; - } else { - os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]"; - } - -#ifndef __ANDROID__ -#if USE_SOUND - if (g_settings->getBool("enable_sound")) { - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;" - << strgettext("Sound Volume") << "]"; - } -#endif -#endif - if (g_touchcontrols) { - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_touchscreen_layout;" - << strgettext("Touchscreen Layout") << "]"; - } else { - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;" - << strgettext("Controls") << "]"; - } - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;" - << strgettext("Exit to Menu") << "]"; - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" - << strgettext("Exit to OS") << "]"; - if (!control_text.empty()) { - os << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"; - } - os << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n" - << "\n" - << strgettext("Game info:") << "\n"; - const std::string &address = client->getAddressName(); - os << strgettext("- Mode: "); - if (!simple_singleplayer_mode) { - if (address.empty()) - os << strgettext("Hosting server"); - else - os << strgettext("Remote server"); - } else { - os << strgettext("Singleplayer"); - } - os << "\n"; - if (simple_singleplayer_mode || address.empty()) { - static const std::string on = strgettext("On"); - static const std::string off = strgettext("Off"); - // Note: Status of enable_damage and creative_mode settings is intentionally - // NOT shown here because the game might roll its own damage system and/or do - // a per-player Creative Mode, in which case writing it here would mislead. - bool damage = g_settings->getBool("enable_damage"); - const std::string &announced = g_settings->getBool("server_announce") ? on : off; - if (!simple_singleplayer_mode) { - if (damage) { - const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off; - //~ PvP = Player versus Player - os << strgettext("- PvP: ") << pvp << "\n"; - } - os << strgettext("- Public: ") << announced << "\n"; - std::string server_name = g_settings->get("server_name"); - str_formspec_escape(server_name); - if (announced == on && !server_name.empty()) - os << strgettext("- Server Name: ") << server_name; - - } - } - os << ";]"; - - /* Create menu */ - /* Note: FormspecFormSource and LocalFormspecHandler * - * are deleted by guiFormSpecMenu */ - FormspecFormSource *fs_src = new FormspecFormSource(os.str()); - LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU"); - - auto *&formspec = m_game_ui->getFormspecGUI(); - GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), - &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), - sound_manager.get()); - formspec->setFocus("btn_continue"); - // game will be paused in next step, if in singleplayer (see m_is_paused) - formspec->doPause = true; -} - /****************************************************************************/ /**************************************************************************** extern function for launching the game diff --git a/src/client/game_formspec.cpp b/src/client/game_formspec.cpp new file mode 100644 index 000000000..4c3bbb04a --- /dev/null +++ b/src/client/game_formspec.cpp @@ -0,0 +1,524 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2010-2013 celeron55, Perttu Ahola + +#include "game_formspec.h" + +#include "gettext.h" +#include "nodemetadata.h" +#include "renderingengine.h" +#include "client.h" +#include "scripting_client.h" +#include "clientmap.h" +#include "gui/guiFormSpecMenu.h" +#include "gui/mainmenumanager.h" +#include "gui/touchcontrols.h" +#include "gui/touchscreeneditor.h" +#include "gui/guiPasswordChange.h" +#include "gui/guiKeyChangeMenu.h" +#include "gui/guiPasswordChange.h" +#include "gui/guiOpenURL.h" +#include "gui/guiVolumeChange.h" + +/* + Text input system +*/ + +struct TextDestNodeMetadata : public TextDest +{ + TextDestNodeMetadata(v3s16 p, Client *client) + { + m_p = p; + m_client = client; + } + // This is deprecated I guess? -celeron55 + void gotText(const std::wstring &text) + { + std::string ntext = wide_to_utf8(text); + infostream << "Submitting 'text' field of node at (" << m_p.X << "," + << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl; + StringMap fields; + fields["text"] = ntext; + m_client->sendNodemetaFields(m_p, "", fields); + } + void gotText(const StringMap &fields) + { + m_client->sendNodemetaFields(m_p, "", fields); + } + + v3s16 m_p; + Client *m_client; +}; + +struct TextDestPlayerInventory : public TextDest +{ + TextDestPlayerInventory(Client *client) + { + m_client = client; + m_formname.clear(); + } + TextDestPlayerInventory(Client *client, const std::string &formname) + { + m_client = client; + m_formname = formname; + } + void gotText(const StringMap &fields) + { + m_client->sendInventoryFields(m_formname, fields); + } + + Client *m_client; +}; + +struct LocalFormspecHandler : public TextDest +{ + LocalFormspecHandler(const std::string &formname) + { + m_formname = formname; + } + + LocalFormspecHandler(const std::string &formname, Client *client): + m_client(client) + { + m_formname = formname; + } + + void gotText(const StringMap &fields) + { + if (m_formname == "MT_PAUSE_MENU") { + if (fields.find("btn_sound") != fields.end()) { + g_gamecallback->changeVolume(); + return; + } + + if (fields.find("btn_key_config") != fields.end()) { + g_gamecallback->keyConfig(); + return; + } + + if (fields.find("btn_touchscreen_layout") != fields.end()) { + g_gamecallback->touchscreenLayout(); + return; + } + + if (fields.find("btn_exit_menu") != fields.end()) { + g_gamecallback->disconnect(); + return; + } + + if (fields.find("btn_exit_os") != fields.end()) { + g_gamecallback->exitToOS(); +#ifndef __ANDROID__ + RenderingEngine::get_raw_device()->closeDevice(); +#endif + return; + } + + if (fields.find("btn_change_password") != fields.end()) { + g_gamecallback->changePassword(); + return; + } + + return; + } + + if (m_formname == "MT_DEATH_SCREEN") { + assert(m_client != nullptr); + + if (fields.find("quit") != fields.end()) + m_client->sendRespawnLegacy(); + + return; + } + + if (m_client->modsLoaded()) + m_client->getScript()->on_formspec_input(m_formname, fields); + } + + Client *m_client = nullptr; +}; + +/* Form update callback */ + +class NodeMetadataFormSource: public IFormSource +{ +public: + NodeMetadataFormSource(ClientMap *map, v3s16 p): + m_map(map), + m_p(p) + { + } + const std::string &getForm() const + { + static const std::string empty_string = ""; + NodeMetadata *meta = m_map->getNodeMetadata(m_p); + + if (!meta) + return empty_string; + + return meta->getString("formspec"); + } + + virtual std::string resolveText(const std::string &str) + { + NodeMetadata *meta = m_map->getNodeMetadata(m_p); + + if (!meta) + return str; + + return meta->resolveString(str); + } + + ClientMap *m_map; + v3s16 m_p; +}; + +class PlayerInventoryFormSource: public IFormSource +{ +public: + PlayerInventoryFormSource(Client *client): + m_client(client) + { + } + + const std::string &getForm() const + { + LocalPlayer *player = m_client->getEnv().getLocalPlayer(); + return player->inventory_formspec; + } + + Client *m_client; +}; + + +//// GameFormSpec + +void GameFormSpec::deleteFormspec() +{ + if (m_formspec) { + m_formspec->drop(); + m_formspec = nullptr; + } + m_formname.clear(); +} + +GameFormSpec::~GameFormSpec() { + if (m_formspec) + m_formspec->quitMenu(); + this->deleteFormspec(); +} + +void GameFormSpec::showFormSpec(const std::string &formspec, const std::string &formname) +{ + if (formspec.empty()) { + if (m_formspec && (formname.empty() || formname == m_formname)) { + m_formspec->quitMenu(); + } + } else { + FormspecFormSource *fs_src = + new FormspecFormSource(formspec); + TextDestPlayerInventory *txt_dst = + new TextDestPlayerInventory(m_client, formname); + + m_formname = formname; + GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), + &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), + m_client->getSoundManager()); + } +} + +void GameFormSpec::showLocalFormSpec(const std::string &formspec, const std::string &formname) +{ + FormspecFormSource *fs_src = new FormspecFormSource(formspec); + LocalFormspecHandler *txt_dst = + new LocalFormspecHandler(formname, m_client); + GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), + &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), + m_client->getSoundManager()); +} + +void GameFormSpec::showNodeFormspec(const std::string &formspec, const v3s16 &nodepos) +{ + infostream << "Launching custom inventory view" << std::endl; + + InventoryLocation inventoryloc; + inventoryloc.setNodeMeta(nodepos); + + NodeMetadataFormSource *fs_src = new NodeMetadataFormSource( + &m_client->getEnv().getClientMap(), nodepos); + TextDest *txt_dst = new TextDestNodeMetadata(nodepos, m_client); + + m_formname = ""; + GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), + &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), + m_client->getSoundManager()); + + m_formspec->setFormSpec(formspec, inventoryloc); +} + +void GameFormSpec::showPlayerInventory() +{ + /* + * Don't permit to open inventory is CAO or player doesn't exists. + * This prevent showing an empty inventory at player load + */ + + LocalPlayer *player = m_client->getEnv().getLocalPlayer(); + if (!player || !player->getCAO()) + return; + + infostream << "Game: Launching inventory" << std::endl; + + PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(m_client); + + InventoryLocation inventoryloc; + inventoryloc.setCurrentPlayer(); + + if (m_client->modsLoaded() && m_client->getScript()->on_inventory_open(m_client->getInventory(inventoryloc))) { + delete fs_src; + return; + } + + if (fs_src->getForm().empty()) { + delete fs_src; + return; + } + + TextDest *txt_dst = new TextDestPlayerInventory(m_client); + m_formname = ""; + GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), + &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), + m_client->getSoundManager()); + + m_formspec->setFormSpec(fs_src->getForm(), inventoryloc); +} + +#define SIZE_TAG "size[11,5.5,true]" // Fixed size (ignored in touchscreen mode) + +void GameFormSpec::showPauseMenu() +{ + std::string control_text; + + if (g_touchcontrols) { + control_text = strgettext("Controls:\n" + "No menu open:\n" + "- slide finger: look around\n" + "- tap: place/punch/use (default)\n" + "- long tap: dig/use (default)\n" + "Menu/inventory open:\n" + "- double tap (outside):\n" + " --> close\n" + "- touch stack, touch slot:\n" + " --> move stack\n" + "- touch&drag, tap 2nd finger\n" + " --> place single item to slot\n" + ); + } + + auto simple_singleplayer_mode = m_client->m_simple_singleplayer_mode; + + float ypos = simple_singleplayer_mode ? 0.7f : 0.1f; + std::ostringstream os; + + os << "formspec_version[1]" << SIZE_TAG + << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;" + << strgettext("Continue") << "]"; + + if (!simple_singleplayer_mode) { + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;" + << strgettext("Change Password") << "]"; + } else { + os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]"; + } + +#ifndef __ANDROID__ +#if USE_SOUND + if (g_settings->getBool("enable_sound")) { + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;" + << strgettext("Sound Volume") << "]"; + } +#endif +#endif + if (g_touchcontrols) { + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_touchscreen_layout;" + << strgettext("Touchscreen Layout") << "]"; + } else { + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;" + << strgettext("Controls") << "]"; + } + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;" + << strgettext("Exit to Menu") << "]"; + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" + << strgettext("Exit to OS") << "]"; + if (!control_text.empty()) { + os << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"; + } + os << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n" + << "\n" + << strgettext("Game info:") << "\n"; + const std::string &address = m_client->getAddressName(); + os << strgettext("- Mode: "); + if (!simple_singleplayer_mode) { + if (address.empty()) + os << strgettext("Hosting server"); + else + os << strgettext("Remote server"); + } else { + os << strgettext("Singleplayer"); + } + os << "\n"; + if (simple_singleplayer_mode || address.empty()) { + static const std::string on = strgettext("On"); + static const std::string off = strgettext("Off"); + // Note: Status of enable_damage and creative_mode settings is intentionally + // NOT shown here because the game might roll its own damage system and/or do + // a per-player Creative Mode, in which case writing it here would mislead. + bool damage = g_settings->getBool("enable_damage"); + const std::string &announced = g_settings->getBool("server_announce") ? on : off; + if (!simple_singleplayer_mode) { + if (damage) { + const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off; + //~ PvP = Player versus Player + os << strgettext("- PvP: ") << pvp << "\n"; + } + os << strgettext("- Public: ") << announced << "\n"; + std::string server_name = g_settings->get("server_name"); + str_formspec_escape(server_name); + if (announced == on && !server_name.empty()) + os << strgettext("- Server Name: ") << server_name; + + } + } + os << ";]"; + + /* Create menu */ + /* Note: FormspecFormSource and LocalFormspecHandler * + * are deleted by guiFormSpecMenu */ + FormspecFormSource *fs_src = new FormspecFormSource(os.str()); + LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU"); + + GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), + &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), + m_client->getSoundManager()); + m_formspec->setFocus("btn_continue"); + // game will be paused in next step, if in singleplayer (see m_is_paused) + m_formspec->doPause = true; +} + +void GameFormSpec::showDeathFormspecLegacy() +{ + static std::string formspec_str = + std::string("formspec_version[1]") + + SIZE_TAG + "bgcolor[#320000b4;true]" + "label[4.85,1.35;" + gettext("You died") + "]" + "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]" + ; + + /* Create menu */ + /* Note: FormspecFormSource and LocalFormspecHandler * + * are deleted by guiFormSpecMenu */ + FormspecFormSource *fs_src = new FormspecFormSource(formspec_str); + LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", m_client); + + GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), + &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), + m_client->getSoundManager()); + m_formspec->setFocus("btn_respawn"); +} + +void GameFormSpec::update() +{ + /* + make sure menu is on top + 1. Delete formspec menu reference if menu was removed + 2. Else, make sure formspec menu is on top + */ + if (!m_formspec) + return; + + if (m_formspec->getReferenceCount() == 1) { + // See GUIFormSpecMenu::create what refcnt = 1 means + this->deleteFormspec(); + return; + } + + auto &loc = m_formspec->getFormspecLocation(); + if (loc.type == InventoryLocation::NODEMETA) { + NodeMetadata *meta = m_client->getEnv().getClientMap().getNodeMetadata(loc.p); + if (!meta || meta->getString("formspec").empty()) { + m_formspec->quitMenu(); + return; + } + } + + if (isMenuActive()) + guiroot->bringToFront(m_formspec); +} + +void GameFormSpec::disableDebugView() +{ + if (m_formspec) { + m_formspec->setDebugView(false); + } +} + +/* returns false if game should exit, otherwise true + */ +bool GameFormSpec::handleCallbacks() +{ + auto texture_src = m_client->getTextureSource(); + + if (g_gamecallback->disconnect_requested) { + g_gamecallback->disconnect_requested = false; + return false; + } + + if (g_gamecallback->changepassword_requested) { + (void)make_irr(guienv, guiroot, -1, + &g_menumgr, m_client, texture_src); + g_gamecallback->changepassword_requested = false; + } + + if (g_gamecallback->changevolume_requested) { + (void)make_irr(guienv, guiroot, -1, + &g_menumgr, texture_src); + g_gamecallback->changevolume_requested = false; + } + + if (g_gamecallback->keyconfig_requested) { + (void)make_irr(guienv, guiroot, -1, + &g_menumgr, texture_src); + g_gamecallback->keyconfig_requested = false; + } + + if (g_gamecallback->touchscreenlayout_requested) { + (new GUITouchscreenLayout(guienv, guiroot, -1, + &g_menumgr, texture_src))->drop(); + g_gamecallback->touchscreenlayout_requested = false; + } + + if (!g_gamecallback->show_open_url_dialog.empty()) { + (void)make_irr(guienv, guiroot, -1, + &g_menumgr, texture_src, g_gamecallback->show_open_url_dialog); + g_gamecallback->show_open_url_dialog.clear(); + } + + if (g_gamecallback->keyconfig_changed) { + m_input->keycache.populate(); // update the cache with new settings + g_gamecallback->keyconfig_changed = false; + } + + return true; +} + +#ifdef __ANDROID__ +bool GameFormSpec::handleAndroidUIInput() +{ + if (m_formspec) { + m_formspec->getAndroidUIInput(); + return true; + } + return false; +} +#endif diff --git a/src/client/game_formspec.h b/src/client/game_formspec.h new file mode 100644 index 000000000..370370151 --- /dev/null +++ b/src/client/game_formspec.h @@ -0,0 +1,62 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2024 cx384 + +#pragma once + +#include +#include "irr_v3d.h" + +class Client; +class RenderingEngine; +class InputHandler; +class ISoundManager; +class GUIFormSpecMenu; + +/* +This object intend to contain the core fromspec functionality. +It includes: + - methods to show specific formspec menus + - storing the opened fromspec + - handling fromspec related callbacks + */ +struct GameFormSpec +{ + void init(Client *client, RenderingEngine *rendering_engine, InputHandler *input) + { + m_client = client; + m_rendering_engine = rendering_engine; + m_input = input; + } + + ~GameFormSpec(); + + void showFormSpec(const std::string &formspec, const std::string &formname); + void showLocalFormSpec(const std::string &formspec, const std::string &formname); + void showNodeFormspec(const std::string &formspec, const v3s16 &nodepos); + void showPlayerInventory(); + void showDeathFormspecLegacy(); + void showPauseMenu(); + + void update(); + void disableDebugView(); + + bool handleCallbacks(); + +#ifdef __ANDROID__ + // Returns false if no formspec open + bool handleAndroidUIInput(); +#endif + +private: + Client *m_client; + RenderingEngine *m_rendering_engine; + InputHandler *m_input; + + // Default: "". If other than "": Empty show_formspec packets will only + // close the formspec when the formname matches + std::string m_formname; + GUIFormSpecMenu *m_formspec = nullptr; + + void deleteFormspec(); +}; diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index c2693c6ae..e09875934 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -8,7 +8,6 @@ #include #include "gui/mainmenumanager.h" #include "gui/guiChatConsole.h" -#include "gui/guiFormSpecMenu.h" #include "gui/touchcontrols.h" #include "util/enriched_string.h" #include "util/pointedthing.h" @@ -319,17 +318,6 @@ void GameUI::toggleProfiler() } } - -void GameUI::deleteFormspec() -{ - if (m_formspec) { - m_formspec->drop(); - m_formspec = nullptr; - } - - m_formname.clear(); -} - void GameUI::clearText() { if (m_guitext_chat) { diff --git a/src/client/gameui.h b/src/client/gameui.h index 95674232a..03cbb3b2c 100644 --- a/src/client/gameui.h +++ b/src/client/gameui.h @@ -13,7 +13,6 @@ using namespace irr; class Client; class EnrichedString; class GUIChatConsole; -class GUIFormSpecMenu; struct MapDrawControl; struct PointedThing; @@ -79,15 +78,6 @@ public: void toggleHud(); void toggleProfiler(); - GUIFormSpecMenu *&updateFormspec(const std::string &formname) - { - m_formname = formname; - return m_formspec; - } - - const std::string &getFormspecName() { return m_formname; } - GUIFormSpecMenu *&getFormspecGUI() { return m_formspec; } - void deleteFormspec(); void clearText(); private: @@ -113,9 +103,4 @@ private: gui::IGUIStaticText *m_guitext_profiler = nullptr; // Profiler text u8 m_profiler_current_page = 0; const u8 m_profiler_max_page = 3; - - // Default: "". If other than "": Empty show_formspec packets will only - // close the formspec when the formname matches - std::string m_formname; - GUIFormSpecMenu *m_formspec = nullptr; };