// 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