From 3b50b2766aeb09c9fc0ad0ea07426bb2187df3d7 Mon Sep 17 00:00:00 2001 From: est31 Date: Fri, 17 Jul 2015 16:40:41 +0200 Subject: [PATCH] Optional reconnect functionality Enable the server to request the client to reconnect. This can be done with the now extended minetest.request_shutdown([reason], [reconnect]) setting. --- builtin/fstk/ui.lua | 65 +++++++++++++++++------------ doc/lua_api.txt | 3 +- minetest.conf.example | 3 ++ src/client.cpp | 1 + src/client.h | 3 ++ src/client/clientlauncher.cpp | 24 +++++++---- src/client/clientlauncher.h | 4 +- src/defaultsettings.cpp | 1 + src/environment.cpp | 8 ++-- src/environment.h | 4 +- src/game.cpp | 26 +++++++----- src/game.h | 1 + src/guiEngine.cpp | 11 ++--- src/guiMainMenu.h | 31 ++++++++------ src/network/clientpackethandler.cpp | 23 ++++++++-- src/network/networkprotocol.h | 9 +++- src/player.cpp | 1 + src/player.h | 1 + src/script/cpp_api/s_mainmenu.cpp | 10 ++++- src/script/cpp_api/s_mainmenu.h | 11 +++-- src/script/lua_api/l_mainmenu.cpp | 20 +++++---- src/script/lua_api/l_server.cpp | 4 +- src/script/lua_api/l_server.h | 2 +- src/server.cpp | 57 ++++++++++++++++++++----- src/server.h | 18 ++++++-- 25 files changed, 232 insertions(+), 109 deletions(-) diff --git a/builtin/fstk/ui.lua b/builtin/fstk/ui.lua index 478a78ad..de8ae4d2 100644 --- a/builtin/fstk/ui.lua +++ b/builtin/fstk/ui.lua @@ -54,29 +54,42 @@ end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- +local function wordwrap_quickhack(str) + local res = "" + local ar = str:split("\n") + for i = 1, #ar do + local text = ar[i] + -- Hack to add word wrapping. + -- TODO: Add engine support for wrapping in formspecs + while #text > 80 do + if res ~= "" then + res = res .. "," + end + res = res .. core.formspec_escape(string.sub(text, 1, 79)) + text = string.sub(text, 80, #text) + end + if res ~= "" then + res = res .. "," + end + res = res .. core.formspec_escape(text) + end + return res +end + -------------------------------------------------------------------------------- function ui.update() local formspec = "" -- handle errors - if gamedata ~= nil and gamedata.errormessage ~= nil then - local ar = gamedata.errormessage:split("\n") - for i = 1, #ar do - local text = ar[i] - -- Hack to add word wrapping. - -- TODO: Add engine support for wrapping in formspecs - while #text > 80 do - if formspec ~= "" then - formspec = formspec .. "," - end - formspec = formspec .. core.formspec_escape(string.sub(text, 1, 79)) - text = string.sub(text, 80, #text) - end - if formspec ~= "" then - formspec = formspec .. "," - end - formspec = formspec .. core.formspec_escape(text) - end + if gamedata ~= nil and gamedata.reconnect_requested then + formspec = wordwrap_quickhack(gamedata.errormessage or "") + formspec = "size[12,5]" .. + "label[0.5,0;" .. fgettext("The server has requested a reconnect:") .. + "]textlist[0.2,0.8;11.5,3.5;;" .. formspec .. + "]button[6,4.6;3,0.5;btn_reconnect_no;" .. fgettext("Main menu") .. "]" .. + "button[3,4.6;3,0.5;btn_reconnect_yes;" .. fgettext("Reconnect") .. "]" + elseif gamedata ~= nil and gamedata.errormessage ~= nil then + formspec = wordwrap_quickhack(gamedata.errormessage) local error_title if string.find(gamedata.errormessage, "ModError") then error_title = fgettext("An error occured in a Lua script, such as a mod:") @@ -128,13 +141,6 @@ end -------------------------------------------------------------------------------- function ui.handle_buttons(fields) - - if fields["btn_error_confirm"] then - gamedata.errormessage = nil - update_menu() - return - end - for key,value in pairs(ui.childlist) do local retval = value:handle_buttons(fields) @@ -168,8 +174,15 @@ end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- core.button_handler = function(fields) - if fields["btn_error_confirm"] then + if fields["btn_reconnect_yes"] then + gamedata.reconnect_requested = false gamedata.errormessage = nil + gamedata.do_reconnect = true + core.start() + return + elseif fields["btn_reconnect_no"] or fields["btn_error_confirm"] then + gamedata.errormessage = nil + gamedata.reconnect_requested = false ui.update() return end diff --git a/doc/lua_api.txt b/doc/lua_api.txt index b11acb92..75bbbdb0 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2174,7 +2174,8 @@ These functions return the leftover itemstack. * Optional: Variable number of arguments that are passed to `func` ### Server -* `minetest.request_shutdown()`: request for server shutdown +* `minetest.request_shutdown([message],[reconnect])`: request for server shutdown. Will display `message` to clients, + and `reconnect` == true displays a reconnect button. * `minetest.get_server_status()`: returns server status string ### Bans diff --git a/minetest.conf.example b/minetest.conf.example index 51660e33..b9e7d59e 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -393,6 +393,9 @@ # A message to be displayed to all clients when the server shuts down #kick_msg_crash = This server has experienced an internal error. You will now be disconnected. # A message to be displayed to all clients when the server crashes +#ask_reconnect_on_crash = false +# Whether to ask clients to reconnect after a (lua) crash. +# Set this to true if your server is set up to restart automatically. # Mod profiler #mod_profiling = false diff --git a/src/client.cpp b/src/client.cpp index 1bd8c39a..74072d9d 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -244,6 +244,7 @@ Client::Client( m_chosen_auth_mech(AUTH_MECHANISM_NONE), m_auth_data(NULL), m_access_denied(false), + m_access_denied_reconnect(false), m_itemdef_received(false), m_nodedef_received(false), m_media_downloader(new ClientMediaDownloader()), diff --git a/src/client.h b/src/client.h index 7c1a19ee..547edfea 100644 --- a/src/client.h +++ b/src/client.h @@ -489,6 +489,8 @@ public: bool accessDenied() { return m_access_denied; } + bool reconnectRequested() { return m_access_denied_reconnect; } + std::string accessDeniedReason() { return m_access_denied_reason; } @@ -636,6 +638,7 @@ private: bool m_access_denied; + bool m_access_denied_reconnect; std::string m_access_denied_reason; std::queue m_client_event_queue; bool m_itemdef_received; diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 50f0690d..bad5c384 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -168,8 +168,9 @@ bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args) ChatBackend chat_backend; // If an error occurs, this is set to something by menu(). - // It is then displayed before the menu shows on the next call to menu() + // It is then displayed before the menu shows on the next call to menu() std::string error_message; + bool reconnect_requested = false; bool first_loop = true; @@ -197,7 +198,8 @@ bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args) */ guiroot = guienv->addStaticText(L"", core::rect(0, 0, 10000, 10000)); - bool game_has_run = launch_game(error_message, game_params, cmd_args); + bool game_has_run = launch_game(error_message, reconnect_requested, + game_params, cmd_args); // If skip_main_menu, we only want to startup once if (skip_main_menu && !first_loop) @@ -233,6 +235,7 @@ bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args) receiver->m_touchscreengui = new TouchScreenGUI(device, receiver); g_touchscreengui = receiver->m_touchscreengui; #endif + the_game( kill, random_input, @@ -245,6 +248,7 @@ bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args) current_port, error_message, chat_backend, + &reconnect_requested, gamespec, simple_singleplayer_mode ); @@ -325,14 +329,16 @@ bool ClientLauncher::init_engine(int log_level) } bool ClientLauncher::launch_game(std::string &error_message, - GameParams &game_params, const Settings &cmd_args) + bool reconnect_requested, GameParams &game_params, + const Settings &cmd_args) { // Initialize menu data MainMenuData menudata; - menudata.address = address; - menudata.name = playername; - menudata.port = itos(game_params.socket_port); - menudata.errormessage = error_message; + menudata.address = address; + menudata.name = playername; + menudata.port = itos(game_params.socket_port); + menudata.script_data.errormessage = error_message; + menudata.script_data.reconnect_requested = reconnect_requested; error_message.clear(); @@ -379,11 +385,11 @@ bool ClientLauncher::launch_game(std::string &error_message, } } - if (!menudata.errormessage.empty()) { + if (!menudata.script_data.errormessage.empty()) { /* The calling function will pass this back into this function upon the * next iteration (if any) causing it to be displayed by the GUI */ - error_message = menudata.errormessage; + error_message = menudata.script_data.errormessage; return false; } diff --git a/src/client/clientlauncher.h b/src/client/clientlauncher.h index 09f8c039..49ceefc5 100644 --- a/src/client/clientlauncher.h +++ b/src/client/clientlauncher.h @@ -92,8 +92,8 @@ protected: void init_args(GameParams &game_params, const Settings &cmd_args); bool init_engine(int log_level); - bool launch_game(std::string &error_message, GameParams &game_params, - const Settings &cmd_args); + bool launch_game(std::string &error_message, bool reconnect_requested, + GameParams &game_params, const Settings &cmd_args); void main_menu(MainMenuData *menudata); bool create_engine_device(int log_level); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 8df0fbbd..42c61751 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -254,6 +254,7 @@ void set_default_settings(Settings *settings) settings->setDefault("kick_msg_shutdown", "Server shutting down."); settings->setDefault("kick_msg_crash", "This server has experienced an internal error. You will now be disconnected."); + settings->setDefault("ask_reconnect_on_crash", "false"); settings->setDefault("profiler_print_interval", "0"); settings->setDefault("enable_mapgen_debug_info", "false"); diff --git a/src/environment.cpp b/src/environment.cpp index d22982b6..5d7321f6 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -426,13 +426,15 @@ bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize, v3s16 return true; } -void ServerEnvironment::kickAllPlayers(const std::string &reason) +void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason, + const std::string &str_reason, bool reconnect) { - std::wstring wreason = utf8_to_wide(reason); for (std::vector::iterator it = m_players.begin(); it != m_players.end(); ++it) { - ((Server*)m_gamedef)->DenyAccess_Legacy((*it)->peer_id, wreason); + ((Server*)m_gamedef)->DenyAccessVerCompliant((*it)->peer_id, + (*it)->protocol_version, (AccessDeniedCode)reason, + str_reason, reconnect); } } diff --git a/src/environment.h b/src/environment.h index 794f1971..2bc128f4 100644 --- a/src/environment.h +++ b/src/environment.h @@ -40,6 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapnode.h" #include "mapblock.h" #include "jthread/jmutex.h" +#include "network/networkprotocol.h" // for AccessDeniedCode class ServerEnvironment; class ActiveBlockModifier; @@ -221,7 +222,8 @@ public: float getSendRecommendedInterval() { return m_recommended_send_interval; } - void kickAllPlayers(const std::string &reason); + void kickAllPlayers(AccessDeniedCode reason, + const std::string &str_reason, bool reconnect); // Save players void saveLoadedPlayers(); void savePlayer(const std::string &playername); diff --git a/src/game.cpp b/src/game.cpp index e3d6b0bc..09b8aab4 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1417,8 +1417,7 @@ struct VolatileRunFlags { * hides most of the stuff in this class (nothing in this class is required * by any other file) but exposes the public methods/data only. */ -class Game -{ +class Game { public: Game(); ~Game(); @@ -1434,6 +1433,7 @@ public: std::string *address, u16 port, std::string &error_message, + bool *reconnect, ChatBackend *chat_backend, const SubgameSpec &gamespec, // Used for local game bool simple_singleplayer_mode); @@ -1588,6 +1588,7 @@ private: scene::ISceneManager *smgr; bool *kill; std::string *error_message; + bool *reconnect_requested; IGameDef *gamedef; // Convenience (same as *client) scene::ISceneNode *skybox; @@ -1716,17 +1717,19 @@ bool Game::startup(bool *kill, std::string *address, // can change if simple_singleplayer_mode u16 port, std::string &error_message, + bool *reconnect, ChatBackend *chat_backend, const SubgameSpec &gamespec, bool simple_singleplayer_mode) { // "cache" - this->device = device; - this->kill = kill; - this->error_message = &error_message; - this->random_input = random_input; - this->input = input; - this->chat_backend = chat_backend; + this->device = device; + this->kill = kill; + this->error_message = &error_message; + this->reconnect_requested = reconnect; + this->random_input = random_input; + this->input = input; + this->chat_backend = chat_backend; this->simple_singleplayer_mode = simple_singleplayer_mode; driver = device->getVideoDriver(); @@ -2239,6 +2242,7 @@ bool Game::connectToServer(const std::string &playername, if (client->accessDenied()) { *error_message = "Access denied. Reason: " + client->accessDeniedReason(); + *reconnect_requested = client->reconnectRequested(); errorstream << *error_message << std::endl; break; } @@ -2376,6 +2380,7 @@ inline bool Game::checkConnection() if (client->accessDenied()) { *error_message = "Access denied. Reason: " + client->accessDeniedReason(); + *reconnect_requested = client->reconnectRequested(); errorstream << *error_message << std::endl; return false; } @@ -4330,6 +4335,7 @@ void the_game(bool *kill, std::string &error_message, ChatBackend &chat_backend, + bool *reconnect_requested, const SubgameSpec &gamespec, // Used for local game bool simple_singleplayer_mode) { @@ -4344,8 +4350,8 @@ void the_game(bool *kill, try { if (game.startup(kill, random_input, input, device, map_dir, - playername, password, &server_address, port, - error_message, &chat_backend, gamespec, + playername, password, &server_address, port, error_message, + reconnect_requested, &chat_backend, gamespec, simple_singleplayer_mode)) { game.run(); game.shutdown(); diff --git a/src/game.h b/src/game.h index 358b26c3..e1f4e934 100644 --- a/src/game.h +++ b/src/game.h @@ -147,6 +147,7 @@ void the_game(bool *kill, u16 port, std::string &error_message, ChatBackend &chat_backend, + bool *reconnect_requested, const SubgameSpec &gamespec, // Used for local game bool simple_singleplayer_mode); diff --git a/src/guiEngine.cpp b/src/guiEngine.cpp index eac9db0c..c616bc32 100644 --- a/src/guiEngine.cpp +++ b/src/guiEngine.cpp @@ -208,10 +208,8 @@ GUIEngine::GUIEngine( irr::IrrlichtDevice* dev, m_script = new MainMenuScripting(this); try { - if (m_data->errormessage != "") { - m_script->setMainMenuErrorMessage(m_data->errormessage); - m_data->errormessage = ""; - } + m_script->setMainMenuData(&m_data->script_data); + m_data->script_data.errormessage = ""; if (!loadMainMenuScript()) { errorstream << "No future without mainmenu" << std::endl; @@ -219,10 +217,9 @@ GUIEngine::GUIEngine( irr::IrrlichtDevice* dev, } run(); - } - catch(LuaError &e) { + } catch (LuaError &e) { errorstream << "MAINMENU ERROR: " << e.what() << std::endl; - m_data->errormessage = e.what(); + m_data->script_data.errormessage = e.what(); } m_menu->quitMenu(); diff --git a/src/guiMainMenu.h b/src/guiMainMenu.h index 34362dba..711ad10f 100644 --- a/src/guiMainMenu.h +++ b/src/guiMainMenu.h @@ -25,17 +25,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include -enum -{ - TAB_SINGLEPLAYER=0, - TAB_MULTIPLAYER, - TAB_ADVANCED, - TAB_SETTINGS, - TAB_CREDITS +struct MainMenuDataForScript { + + MainMenuDataForScript() : + reconnect_requested(false) + {} + + // Whether the server has requested a reconnect + bool reconnect_requested; + + std::string errormessage; }; -struct MainMenuData -{ +struct MainMenuData { // Client options std::string servername; std::string serverdescription; @@ -43,19 +45,22 @@ struct MainMenuData std::string port; std::string name; std::string password; + // Whether to reconnect + bool do_reconnect; // Server options bool enable_public; int selected_world; bool simple_singleplayer_mode; - //error handling - std::string errormessage; + // Data to be passed to the script + MainMenuDataForScript script_data; + MainMenuData(): + do_reconnect(false), enable_public(false), selected_world(0), - simple_singleplayer_mode(false), - errormessage("") + simple_singleplayer_mode(false) {} }; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 15d5456f..2133543d 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -215,11 +215,28 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt) u8 denyCode = SERVER_ACCESSDENIED_UNEXPECTED_DATA; *pkt >> denyCode; - if (denyCode == SERVER_ACCESSDENIED_CUSTOM_STRING) { + if (denyCode == SERVER_ACCESSDENIED_SHUTDOWN || + denyCode == SERVER_ACCESSDENIED_CRASH) { *pkt >> m_access_denied_reason; - } - else if (denyCode < SERVER_ACCESSDENIED_MAX) { + if (m_access_denied_reason == "") { + m_access_denied_reason = accessDeniedStrings[denyCode]; + } + u8 reconnect; + *pkt >> reconnect; + m_access_denied_reconnect = reconnect & 1; + } else if (denyCode == SERVER_ACCESSDENIED_CUSTOM_STRING) { + *pkt >> m_access_denied_reason; + } else if (denyCode < SERVER_ACCESSDENIED_MAX) { m_access_denied_reason = accessDeniedStrings[denyCode]; + } else { + // Allow us to add new error messages to the + // protocol without raising the protocol version, if we want to. + // Until then (which may be never), this is outside + // of the defined protocol. + *pkt >> m_access_denied_reason; + if (m_access_denied_reason == "") { + m_access_denied_reason = "Unknown"; + } } } // 13/03/15 Legacy code from 0.4.12 and lesser. must stay 1 year diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index e4b56674..feb18e50 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -202,7 +202,8 @@ enum ToClientCommand TOCLIENT_ACCESS_DENIED = 0x0A, /* u8 reason - std::string custom reason (if reason == SERVER_ACCESSDENIED_CUSTOM_STRING) + std::string custom reason (if needed, otherwise "") + u8 (bool) reconnect */ TOCLIENT_BLOCKDATA = 0x20, //TODO: Multiple blocks TOCLIENT_ADDNODE = 0x21, @@ -937,6 +938,8 @@ enum AccessDeniedCode { SERVER_ACCESSDENIED_ALREADY_CONNECTED, SERVER_ACCESSDENIED_SERVER_FAIL, SERVER_ACCESSDENIED_CUSTOM_STRING, + SERVER_ACCESSDENIED_SHUTDOWN, + SERVER_ACCESSDENIED_CRASH, SERVER_ACCESSDENIED_MAX, }; @@ -954,8 +957,10 @@ const static std::string accessDeniedStrings[SERVER_ACCESSDENIED_MAX] = { "Too many users.", "Empty passwords are disallowed. Set a password and try again.", "Another client is connected with this name. If your client closed unexpectedly, try again in a minute.", - "Server authention failed. This is likely a server error." + "Server authentication failed. This is likely a server error.", "", + "Server shutting down.", + "This server has experienced an internal error. You will now be disconnected." }; #endif diff --git a/src/player.cpp b/src/player.cpp index 668d490c..0e8fd86d 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -44,6 +44,7 @@ Player::Player(IGameDef *gamedef, const char *name): hp(PLAYER_MAX_HP), hurt_tilt_timer(0), hurt_tilt_strength(0), + protocol_version(0), peer_id(PEER_ID_INEXISTENT), keyPressed(0), // protected diff --git a/src/player.h b/src/player.h index c84cc1c9..3a336afc 100644 --- a/src/player.h +++ b/src/player.h @@ -362,6 +362,7 @@ public: float hurt_tilt_timer; float hurt_tilt_strength; + u16 protocol_version; u16 peer_id; std::string inventory_formspec; diff --git a/src/script/cpp_api/s_mainmenu.cpp b/src/script/cpp_api/s_mainmenu.cpp index 0bb247fa..7430b0f4 100644 --- a/src/script/cpp_api/s_mainmenu.cpp +++ b/src/script/cpp_api/s_mainmenu.cpp @@ -21,15 +21,21 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_internal.h" #include "common/c_converter.h" -void ScriptApiMainMenu::setMainMenuErrorMessage(std::string errormessage) +void ScriptApiMainMenu::setMainMenuData(MainMenuDataForScript *data) { SCRIPTAPI_PRECHECKHEADER lua_getglobal(L, "gamedata"); int gamedata_idx = lua_gettop(L); lua_pushstring(L, "errormessage"); - lua_pushstring(L, errormessage.c_str()); + if (!data->errormessage.empty()) { + lua_pushstring(L, data->errormessage.c_str()); + } else { + lua_pushnil(L); + } lua_settable(L, gamedata_idx); + setboolfield(L, gamedata_idx, "reconnect_requested", + data->reconnect_requested); lua_pop(L, 1); } diff --git a/src/script/cpp_api/s_mainmenu.h b/src/script/cpp_api/s_mainmenu.h index 6994b578..8d589581 100644 --- a/src/script/cpp_api/s_mainmenu.h +++ b/src/script/cpp_api/s_mainmenu.h @@ -22,16 +22,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_base.h" #include "util/string.h" +#include "../guiMainMenu.h" -class ScriptApiMainMenu - : virtual public ScriptApiBase -{ +class ScriptApiMainMenu : virtual public ScriptApiBase { public: /** - * set gamedata.errormessage to inform lua of an error - * @param errormessage the error message + * Hand over MainMenuDataForScript to lua to inform lua of the content + * @param data the data */ - void setMainMenuErrorMessage(std::string errormessage); + void setMainMenuData(MainMenuDataForScript *data); /** * process events received from formspec diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index d209582e..92311d6f 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -114,15 +114,19 @@ int ModApiMainMenu::l_start(lua_State *L) bool valid = false; + MainMenuData *data = engine->m_data; - engine->m_data->selected_world = getIntegerData(L, "selected_world",valid) -1; - engine->m_data->simple_singleplayer_mode = getBoolData(L,"singleplayer",valid); - engine->m_data->name = getTextData(L,"playername"); - engine->m_data->password = getTextData(L,"password"); - engine->m_data->address = getTextData(L,"address"); - engine->m_data->port = getTextData(L,"port"); - engine->m_data->serverdescription = getTextData(L,"serverdescription"); - engine->m_data->servername = getTextData(L,"servername"); + data->selected_world = getIntegerData(L, "selected_world",valid) -1; + data->simple_singleplayer_mode = getBoolData(L,"singleplayer",valid); + data->do_reconnect = getBoolData(L, "do_reconnect", valid); + if (!data->do_reconnect) { + data->name = getTextData(L,"playername"); + data->password = getTextData(L,"password"); + data->address = getTextData(L,"address"); + data->port = getTextData(L,"port"); + } + data->serverdescription = getTextData(L,"serverdescription"); + data->servername = getTextData(L,"servername"); //close menu next time engine->m_startgame = true; diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 558cc088..96c0327d 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -30,7 +30,9 @@ with this program; if not, write to the Free Software Foundation, Inc., // request_shutdown() int ModApiServer::l_request_shutdown(lua_State *L) { - getServer(L)->requestShutdown(); + const char *msg = lua_tolstring(L, 1, NULL); + bool reconnect = lua_toboolean(L, 2); + getServer(L)->requestShutdown(msg ? msg : "", reconnect); return 0; } diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index fd85a897..e14bef04 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., class ModApiServer : public ModApiBase { private: - // request_shutdown() + // request_shutdown([message], [reconnect]) static int l_request_shutdown(lua_State *L); // get_server_status() diff --git a/src/server.cpp b/src/server.cpp index 2d6de1c9..cb7e35ec 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -191,6 +191,7 @@ Server::Server( m_uptime(0), m_clients(&m_con), m_shutdown_requested(false), + m_shutdown_ask_reconnect(false), m_ignore_map_edit_events(false), m_ignore_map_edit_events_peer_id(0), m_next_sound_id(0) @@ -398,7 +399,17 @@ Server::~Server() m_env->saveLoadedPlayers(); infostream << "Server: Kicking players" << std::endl; - m_env->kickAllPlayers(g_settings->get("kick_msg_shutdown")); + std::string kick_msg; + bool reconnect = false; + if (getShutdownRequested()) { + reconnect = m_shutdown_ask_reconnect; + kick_msg = m_shutdown_msg; + } + if (kick_msg == "") { + kick_msg = g_settings->get("kick_msg_shutdown"); + } + m_env->kickAllPlayers(SERVER_ACCESSDENIED_SHUTDOWN, + kick_msg, reconnect); infostream << "Server: Saving environment metadata" << std::endl; m_env->saveMeta(); @@ -502,7 +513,9 @@ void Server::step(float dtime) throw ServerError(async_err); } else { - m_env->kickAllPlayers(g_settings->get("kick_msg_crash")); + m_env->kickAllPlayers(SERVER_ACCESSDENIED_CRASH, + g_settings->get("kick_msg_crash"), + g_settings->getBool("ask_reconnect_on_crash")); errorstream << "UNRECOVERABLE error occurred. Stopping server. " << "Please fix the following error:" << std::endl << async_err << std::endl; @@ -1070,7 +1083,7 @@ PlayerSAO* Server::StageTwoClientInit(u16 peer_id) RemoteClient* client = m_clients.lockedGetClientNoEx(peer_id, CS_InitDone); if (client != NULL) { playername = client->getName(); - playersao = emergePlayer(playername.c_str(), peer_id); + playersao = emergePlayer(playername.c_str(), peer_id, client->net_proto_version); } } catch (std::exception &e) { m_clients.Unlock(); @@ -1523,16 +1536,18 @@ void Server::SendBreath(u16 peer_id, u16 breath) Send(&pkt); } -void Server::SendAccessDenied(u16 peer_id, AccessDeniedCode reason, const std::string &custom_reason) +void Server::SendAccessDenied(u16 peer_id, AccessDeniedCode reason, + const std::string &custom_reason, bool reconnect) { - DSTACK(__FUNCTION_NAME); + assert(reason < SERVER_ACCESSDENIED_MAX); NetworkPacket pkt(TOCLIENT_ACCESS_DENIED, 1, peer_id); - pkt << (u8) reason; - - if (reason == SERVER_ACCESSDENIED_CUSTOM_STRING) { + pkt << (u8)reason; + if (reason == SERVER_ACCESSDENIED_CUSTOM_STRING) pkt << custom_reason; - } + else if (reason == SERVER_ACCESSDENIED_SHUTDOWN || + reason == SERVER_ACCESSDENIED_CRASH) + pkt << custom_reason << (u8)reconnect; Send(&pkt); } @@ -2567,6 +2582,8 @@ void Server::RespawnPlayer(u16 peer_id) playersao->setPos(pos); } } + + void Server::DenySudoAccess(u16 peer_id) { DSTACK(__FUNCTION_NAME); @@ -2575,6 +2592,24 @@ void Server::DenySudoAccess(u16 peer_id) Send(&pkt); } + +void Server::DenyAccessVerCompliant(u16 peer_id, u16 proto_ver, AccessDeniedCode reason, + const std::string &str_reason, bool reconnect) +{ + if (proto_ver >= 25) { + SendAccessDenied(peer_id, reason, str_reason); + } else { + std::wstring wreason = utf8_to_wide( + reason == SERVER_ACCESSDENIED_CUSTOM_STRING ? str_reason : + accessDeniedStrings[(u8)reason]); + SendAccessDenied_Legacy(peer_id, wreason); + } + + m_clients.event(peer_id, CSE_SetDenied); + m_con.DisconnectPeer(peer_id); +} + + void Server::DenyAccess(u16 peer_id, AccessDeniedCode reason, const std::string &custom_reason) { DSTACK(__FUNCTION_NAME); @@ -3330,7 +3365,7 @@ v3f Server::findSpawnPos() return intToFloat(nodepos, BS); } -PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id) +PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id, u16 proto_version) { bool newplayer = false; @@ -3383,6 +3418,8 @@ PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id) getPlayerEffectivePrivs(player->getName()), isSingleplayer()); + player->protocol_version = proto_version; + /* Clean up old HUD elements from previous sessions */ player->clearHud(); diff --git a/src/server.h b/src/server.h index fbca05f2..5f390500 100644 --- a/src/server.h +++ b/src/server.h @@ -244,8 +244,13 @@ public: { return m_shutdown_requested; } // request server to shutdown - inline void requestShutdown(void) - { m_shutdown_requested = true; } + inline void requestShutdown() { m_shutdown_requested = true; } + void requestShutdown(const std::string &msg, bool reconnect) + { + m_shutdown_requested = true; + m_shutdown_msg = msg; + m_shutdown_ask_reconnect = reconnect; + } // Returns -1 if failed, sound handle on success // Envlock @@ -366,6 +371,8 @@ public: void deletingPeer(con::Peer *peer, bool timeout); void DenySudoAccess(u16 peer_id); + void DenyAccessVerCompliant(u16 peer_id, u16 proto_ver, AccessDeniedCode reason, + const std::string &str_reason = "", bool reconnect = false); void DenyAccess(u16 peer_id, AccessDeniedCode reason, const std::string &custom_reason=""); void acceptAuth(u16 peer_id, bool forSudoMode); void DenyAccess_Legacy(u16 peer_id, const std::wstring &reason); @@ -390,7 +397,8 @@ private: void SendMovement(u16 peer_id); void SendHP(u16 peer_id, u8 hp); void SendBreath(u16 peer_id, u16 breath); - void SendAccessDenied(u16 peer_id, AccessDeniedCode reason, const std::string &custom_reason); + void SendAccessDenied(u16 peer_id, AccessDeniedCode reason, + const std::string &custom_reason, bool reconnect = false); void SendAccessDenied_Legacy(u16 peer_id, const std::wstring &reason); void SendDeathscreen(u16 peer_id,bool set_camera_point_target, v3f camera_point_target); void SendItemDef(u16 peer_id,IItemDefManager *itemdef, u16 protocol_version); @@ -490,7 +498,7 @@ private: Call with env and con locked. */ - PlayerSAO *emergePlayer(const char *name, u16 peer_id); + PlayerSAO *emergePlayer(const char *name, u16 peer_id, u16 proto_version); void handlePeerChanges(); @@ -596,6 +604,8 @@ private: */ bool m_shutdown_requested; + std::string m_shutdown_msg; + bool m_shutdown_ask_reconnect; /* Map edit event queue. Automatically receives all map edits.