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.
This commit is contained in:
est31 2015-07-17 16:40:41 +02:00
parent 1e0e85f82e
commit 3b50b2766a
25 changed files with 232 additions and 109 deletions

@ -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() function ui.update()
local formspec = "" local formspec = ""
-- handle errors -- handle errors
if gamedata ~= nil and gamedata.errormessage ~= nil then if gamedata ~= nil and gamedata.reconnect_requested then
local ar = gamedata.errormessage:split("\n") formspec = wordwrap_quickhack(gamedata.errormessage or "")
for i = 1, #ar do formspec = "size[12,5]" ..
local text = ar[i] "label[0.5,0;" .. fgettext("The server has requested a reconnect:") ..
-- Hack to add word wrapping. "]textlist[0.2,0.8;11.5,3.5;;" .. formspec ..
-- TODO: Add engine support for wrapping in formspecs "]button[6,4.6;3,0.5;btn_reconnect_no;" .. fgettext("Main menu") .. "]" ..
while #text > 80 do "button[3,4.6;3,0.5;btn_reconnect_yes;" .. fgettext("Reconnect") .. "]"
if formspec ~= "" then elseif gamedata ~= nil and gamedata.errormessage ~= nil then
formspec = formspec .. "," formspec = wordwrap_quickhack(gamedata.errormessage)
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
local error_title local error_title
if string.find(gamedata.errormessage, "ModError") then if string.find(gamedata.errormessage, "ModError") then
error_title = fgettext("An error occured in a Lua script, such as a mod:") error_title = fgettext("An error occured in a Lua script, such as a mod:")
@ -128,13 +141,6 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function ui.handle_buttons(fields) 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 for key,value in pairs(ui.childlist) do
local retval = value:handle_buttons(fields) local retval = value:handle_buttons(fields)
@ -168,8 +174,15 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
core.button_handler = function(fields) 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.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() ui.update()
return return
end end

@ -2174,7 +2174,8 @@ These functions return the leftover itemstack.
* Optional: Variable number of arguments that are passed to `func` * Optional: Variable number of arguments that are passed to `func`
### Server ### 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 * `minetest.get_server_status()`: returns server status string
### Bans ### Bans

@ -393,6 +393,9 @@
# A message to be displayed to all clients when the server shuts down # 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. #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 # 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 profiler
#mod_profiling = false #mod_profiling = false

@ -244,6 +244,7 @@ Client::Client(
m_chosen_auth_mech(AUTH_MECHANISM_NONE), m_chosen_auth_mech(AUTH_MECHANISM_NONE),
m_auth_data(NULL), m_auth_data(NULL),
m_access_denied(false), m_access_denied(false),
m_access_denied_reconnect(false),
m_itemdef_received(false), m_itemdef_received(false),
m_nodedef_received(false), m_nodedef_received(false),
m_media_downloader(new ClientMediaDownloader()), m_media_downloader(new ClientMediaDownloader()),

@ -489,6 +489,8 @@ public:
bool accessDenied() bool accessDenied()
{ return m_access_denied; } { return m_access_denied; }
bool reconnectRequested() { return m_access_denied_reconnect; }
std::string accessDeniedReason() std::string accessDeniedReason()
{ return m_access_denied_reason; } { return m_access_denied_reason; }
@ -636,6 +638,7 @@ private:
bool m_access_denied; bool m_access_denied;
bool m_access_denied_reconnect;
std::string m_access_denied_reason; std::string m_access_denied_reason;
std::queue<ClientEvent> m_client_event_queue; std::queue<ClientEvent> m_client_event_queue;
bool m_itemdef_received; bool m_itemdef_received;

@ -168,8 +168,9 @@ bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args)
ChatBackend chat_backend; ChatBackend chat_backend;
// If an error occurs, this is set to something by menu(). // 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; std::string error_message;
bool reconnect_requested = false;
bool first_loop = true; bool first_loop = true;
@ -197,7 +198,8 @@ bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args)
*/ */
guiroot = guienv->addStaticText(L"", core::rect<s32>(0, 0, 10000, 10000)); guiroot = guienv->addStaticText(L"", core::rect<s32>(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, we only want to startup once
if (skip_main_menu && !first_loop) 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); receiver->m_touchscreengui = new TouchScreenGUI(device, receiver);
g_touchscreengui = receiver->m_touchscreengui; g_touchscreengui = receiver->m_touchscreengui;
#endif #endif
the_game( the_game(
kill, kill,
random_input, random_input,
@ -245,6 +248,7 @@ bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args)
current_port, current_port,
error_message, error_message,
chat_backend, chat_backend,
&reconnect_requested,
gamespec, gamespec,
simple_singleplayer_mode simple_singleplayer_mode
); );
@ -325,14 +329,16 @@ bool ClientLauncher::init_engine(int log_level)
} }
bool ClientLauncher::launch_game(std::string &error_message, 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 // Initialize menu data
MainMenuData menudata; MainMenuData menudata;
menudata.address = address; menudata.address = address;
menudata.name = playername; menudata.name = playername;
menudata.port = itos(game_params.socket_port); menudata.port = itos(game_params.socket_port);
menudata.errormessage = error_message; menudata.script_data.errormessage = error_message;
menudata.script_data.reconnect_requested = reconnect_requested;
error_message.clear(); 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 /* The calling function will pass this back into this function upon the
* next iteration (if any) causing it to be displayed by the GUI * next iteration (if any) causing it to be displayed by the GUI
*/ */
error_message = menudata.errormessage; error_message = menudata.script_data.errormessage;
return false; return false;
} }

@ -92,8 +92,8 @@ protected:
void init_args(GameParams &game_params, const Settings &cmd_args); void init_args(GameParams &game_params, const Settings &cmd_args);
bool init_engine(int log_level); bool init_engine(int log_level);
bool launch_game(std::string &error_message, GameParams &game_params, bool launch_game(std::string &error_message, bool reconnect_requested,
const Settings &cmd_args); GameParams &game_params, const Settings &cmd_args);
void main_menu(MainMenuData *menudata); void main_menu(MainMenuData *menudata);
bool create_engine_device(int log_level); bool create_engine_device(int log_level);

@ -254,6 +254,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("kick_msg_shutdown", "Server shutting down."); 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("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("profiler_print_interval", "0");
settings->setDefault("enable_mapgen_debug_info", "false"); settings->setDefault("enable_mapgen_debug_info", "false");

@ -426,13 +426,15 @@ bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize, v3s16
return true; 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<Player*>::iterator it = m_players.begin(); for (std::vector<Player*>::iterator it = m_players.begin();
it != m_players.end(); it != m_players.end();
++it) { ++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);
} }
} }

@ -40,6 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapnode.h" #include "mapnode.h"
#include "mapblock.h" #include "mapblock.h"
#include "jthread/jmutex.h" #include "jthread/jmutex.h"
#include "network/networkprotocol.h" // for AccessDeniedCode
class ServerEnvironment; class ServerEnvironment;
class ActiveBlockModifier; class ActiveBlockModifier;
@ -221,7 +222,8 @@ public:
float getSendRecommendedInterval() float getSendRecommendedInterval()
{ return m_recommended_send_interval; } { return m_recommended_send_interval; }
void kickAllPlayers(const std::string &reason); void kickAllPlayers(AccessDeniedCode reason,
const std::string &str_reason, bool reconnect);
// Save players // Save players
void saveLoadedPlayers(); void saveLoadedPlayers();
void savePlayer(const std::string &playername); void savePlayer(const std::string &playername);

@ -1417,8 +1417,7 @@ struct VolatileRunFlags {
* hides most of the stuff in this class (nothing in this class is required * 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. * by any other file) but exposes the public methods/data only.
*/ */
class Game class Game {
{
public: public:
Game(); Game();
~Game(); ~Game();
@ -1434,6 +1433,7 @@ public:
std::string *address, std::string *address,
u16 port, u16 port,
std::string &error_message, std::string &error_message,
bool *reconnect,
ChatBackend *chat_backend, ChatBackend *chat_backend,
const SubgameSpec &gamespec, // Used for local game const SubgameSpec &gamespec, // Used for local game
bool simple_singleplayer_mode); bool simple_singleplayer_mode);
@ -1588,6 +1588,7 @@ private:
scene::ISceneManager *smgr; scene::ISceneManager *smgr;
bool *kill; bool *kill;
std::string *error_message; std::string *error_message;
bool *reconnect_requested;
IGameDef *gamedef; // Convenience (same as *client) IGameDef *gamedef; // Convenience (same as *client)
scene::ISceneNode *skybox; scene::ISceneNode *skybox;
@ -1716,17 +1717,19 @@ bool Game::startup(bool *kill,
std::string *address, // can change if simple_singleplayer_mode std::string *address, // can change if simple_singleplayer_mode
u16 port, u16 port,
std::string &error_message, std::string &error_message,
bool *reconnect,
ChatBackend *chat_backend, ChatBackend *chat_backend,
const SubgameSpec &gamespec, const SubgameSpec &gamespec,
bool simple_singleplayer_mode) bool simple_singleplayer_mode)
{ {
// "cache" // "cache"
this->device = device; this->device = device;
this->kill = kill; this->kill = kill;
this->error_message = &error_message; this->error_message = &error_message;
this->random_input = random_input; this->reconnect_requested = reconnect;
this->input = input; this->random_input = random_input;
this->chat_backend = chat_backend; this->input = input;
this->chat_backend = chat_backend;
this->simple_singleplayer_mode = simple_singleplayer_mode; this->simple_singleplayer_mode = simple_singleplayer_mode;
driver = device->getVideoDriver(); driver = device->getVideoDriver();
@ -2239,6 +2242,7 @@ bool Game::connectToServer(const std::string &playername,
if (client->accessDenied()) { if (client->accessDenied()) {
*error_message = "Access denied. Reason: " *error_message = "Access denied. Reason: "
+ client->accessDeniedReason(); + client->accessDeniedReason();
*reconnect_requested = client->reconnectRequested();
errorstream << *error_message << std::endl; errorstream << *error_message << std::endl;
break; break;
} }
@ -2376,6 +2380,7 @@ inline bool Game::checkConnection()
if (client->accessDenied()) { if (client->accessDenied()) {
*error_message = "Access denied. Reason: " *error_message = "Access denied. Reason: "
+ client->accessDeniedReason(); + client->accessDeniedReason();
*reconnect_requested = client->reconnectRequested();
errorstream << *error_message << std::endl; errorstream << *error_message << std::endl;
return false; return false;
} }
@ -4330,6 +4335,7 @@ void the_game(bool *kill,
std::string &error_message, std::string &error_message,
ChatBackend &chat_backend, ChatBackend &chat_backend,
bool *reconnect_requested,
const SubgameSpec &gamespec, // Used for local game const SubgameSpec &gamespec, // Used for local game
bool simple_singleplayer_mode) bool simple_singleplayer_mode)
{ {
@ -4344,8 +4350,8 @@ void the_game(bool *kill,
try { try {
if (game.startup(kill, random_input, input, device, map_dir, if (game.startup(kill, random_input, input, device, map_dir,
playername, password, &server_address, port, playername, password, &server_address, port, error_message,
error_message, &chat_backend, gamespec, reconnect_requested, &chat_backend, gamespec,
simple_singleplayer_mode)) { simple_singleplayer_mode)) {
game.run(); game.run();
game.shutdown(); game.shutdown();

@ -147,6 +147,7 @@ void the_game(bool *kill,
u16 port, u16 port,
std::string &error_message, std::string &error_message,
ChatBackend &chat_backend, ChatBackend &chat_backend,
bool *reconnect_requested,
const SubgameSpec &gamespec, // Used for local game const SubgameSpec &gamespec, // Used for local game
bool simple_singleplayer_mode); bool simple_singleplayer_mode);

@ -208,10 +208,8 @@ GUIEngine::GUIEngine( irr::IrrlichtDevice* dev,
m_script = new MainMenuScripting(this); m_script = new MainMenuScripting(this);
try { try {
if (m_data->errormessage != "") { m_script->setMainMenuData(&m_data->script_data);
m_script->setMainMenuErrorMessage(m_data->errormessage); m_data->script_data.errormessage = "";
m_data->errormessage = "";
}
if (!loadMainMenuScript()) { if (!loadMainMenuScript()) {
errorstream << "No future without mainmenu" << std::endl; errorstream << "No future without mainmenu" << std::endl;
@ -219,10 +217,9 @@ GUIEngine::GUIEngine( irr::IrrlichtDevice* dev,
} }
run(); run();
} } catch (LuaError &e) {
catch(LuaError &e) {
errorstream << "MAINMENU ERROR: " << e.what() << std::endl; errorstream << "MAINMENU ERROR: " << e.what() << std::endl;
m_data->errormessage = e.what(); m_data->script_data.errormessage = e.what();
} }
m_menu->quitMenu(); m_menu->quitMenu();

@ -25,17 +25,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <string> #include <string>
#include <list> #include <list>
enum struct MainMenuDataForScript {
{
TAB_SINGLEPLAYER=0, MainMenuDataForScript() :
TAB_MULTIPLAYER, reconnect_requested(false)
TAB_ADVANCED, {}
TAB_SETTINGS,
TAB_CREDITS // Whether the server has requested a reconnect
bool reconnect_requested;
std::string errormessage;
}; };
struct MainMenuData struct MainMenuData {
{
// Client options // Client options
std::string servername; std::string servername;
std::string serverdescription; std::string serverdescription;
@ -43,19 +45,22 @@ struct MainMenuData
std::string port; std::string port;
std::string name; std::string name;
std::string password; std::string password;
// Whether to reconnect
bool do_reconnect;
// Server options // Server options
bool enable_public; bool enable_public;
int selected_world; int selected_world;
bool simple_singleplayer_mode; bool simple_singleplayer_mode;
//error handling // Data to be passed to the script
std::string errormessage; MainMenuDataForScript script_data;
MainMenuData(): MainMenuData():
do_reconnect(false),
enable_public(false), enable_public(false),
selected_world(0), selected_world(0),
simple_singleplayer_mode(false), simple_singleplayer_mode(false)
errormessage("")
{} {}
}; };

@ -215,11 +215,28 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt)
u8 denyCode = SERVER_ACCESSDENIED_UNEXPECTED_DATA; u8 denyCode = SERVER_ACCESSDENIED_UNEXPECTED_DATA;
*pkt >> denyCode; *pkt >> denyCode;
if (denyCode == SERVER_ACCESSDENIED_CUSTOM_STRING) { if (denyCode == SERVER_ACCESSDENIED_SHUTDOWN ||
denyCode == SERVER_ACCESSDENIED_CRASH) {
*pkt >> m_access_denied_reason; *pkt >> m_access_denied_reason;
} if (m_access_denied_reason == "") {
else if (denyCode < SERVER_ACCESSDENIED_MAX) { 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]; 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 // 13/03/15 Legacy code from 0.4.12 and lesser. must stay 1 year

@ -202,7 +202,8 @@ enum ToClientCommand
TOCLIENT_ACCESS_DENIED = 0x0A, TOCLIENT_ACCESS_DENIED = 0x0A,
/* /*
u8 reason 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_BLOCKDATA = 0x20, //TODO: Multiple blocks
TOCLIENT_ADDNODE = 0x21, TOCLIENT_ADDNODE = 0x21,
@ -937,6 +938,8 @@ enum AccessDeniedCode {
SERVER_ACCESSDENIED_ALREADY_CONNECTED, SERVER_ACCESSDENIED_ALREADY_CONNECTED,
SERVER_ACCESSDENIED_SERVER_FAIL, SERVER_ACCESSDENIED_SERVER_FAIL,
SERVER_ACCESSDENIED_CUSTOM_STRING, SERVER_ACCESSDENIED_CUSTOM_STRING,
SERVER_ACCESSDENIED_SHUTDOWN,
SERVER_ACCESSDENIED_CRASH,
SERVER_ACCESSDENIED_MAX, SERVER_ACCESSDENIED_MAX,
}; };
@ -954,8 +957,10 @@ const static std::string accessDeniedStrings[SERVER_ACCESSDENIED_MAX] = {
"Too many users.", "Too many users.",
"Empty passwords are disallowed. Set a password and try again.", "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.", "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 #endif

@ -44,6 +44,7 @@ Player::Player(IGameDef *gamedef, const char *name):
hp(PLAYER_MAX_HP), hp(PLAYER_MAX_HP),
hurt_tilt_timer(0), hurt_tilt_timer(0),
hurt_tilt_strength(0), hurt_tilt_strength(0),
protocol_version(0),
peer_id(PEER_ID_INEXISTENT), peer_id(PEER_ID_INEXISTENT),
keyPressed(0), keyPressed(0),
// protected // protected

@ -362,6 +362,7 @@ public:
float hurt_tilt_timer; float hurt_tilt_timer;
float hurt_tilt_strength; float hurt_tilt_strength;
u16 protocol_version;
u16 peer_id; u16 peer_id;
std::string inventory_formspec; std::string inventory_formspec;

@ -21,15 +21,21 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "cpp_api/s_internal.h" #include "cpp_api/s_internal.h"
#include "common/c_converter.h" #include "common/c_converter.h"
void ScriptApiMainMenu::setMainMenuErrorMessage(std::string errormessage) void ScriptApiMainMenu::setMainMenuData(MainMenuDataForScript *data)
{ {
SCRIPTAPI_PRECHECKHEADER SCRIPTAPI_PRECHECKHEADER
lua_getglobal(L, "gamedata"); lua_getglobal(L, "gamedata");
int gamedata_idx = lua_gettop(L); int gamedata_idx = lua_gettop(L);
lua_pushstring(L, "errormessage"); 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); lua_settable(L, gamedata_idx);
setboolfield(L, gamedata_idx, "reconnect_requested",
data->reconnect_requested);
lua_pop(L, 1); lua_pop(L, 1);
} }

@ -22,16 +22,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "cpp_api/s_base.h" #include "cpp_api/s_base.h"
#include "util/string.h" #include "util/string.h"
#include "../guiMainMenu.h"
class ScriptApiMainMenu class ScriptApiMainMenu : virtual public ScriptApiBase {
: virtual public ScriptApiBase
{
public: public:
/** /**
* set gamedata.errormessage to inform lua of an error * Hand over MainMenuDataForScript to lua to inform lua of the content
* @param errormessage the error message * @param data the data
*/ */
void setMainMenuErrorMessage(std::string errormessage); void setMainMenuData(MainMenuDataForScript *data);
/** /**
* process events received from formspec * process events received from formspec

@ -114,15 +114,19 @@ int ModApiMainMenu::l_start(lua_State *L)
bool valid = false; bool valid = false;
MainMenuData *data = engine->m_data;
engine->m_data->selected_world = getIntegerData(L, "selected_world",valid) -1; data->selected_world = getIntegerData(L, "selected_world",valid) -1;
engine->m_data->simple_singleplayer_mode = getBoolData(L,"singleplayer",valid); data->simple_singleplayer_mode = getBoolData(L,"singleplayer",valid);
engine->m_data->name = getTextData(L,"playername"); data->do_reconnect = getBoolData(L, "do_reconnect", valid);
engine->m_data->password = getTextData(L,"password"); if (!data->do_reconnect) {
engine->m_data->address = getTextData(L,"address"); data->name = getTextData(L,"playername");
engine->m_data->port = getTextData(L,"port"); data->password = getTextData(L,"password");
engine->m_data->serverdescription = getTextData(L,"serverdescription"); data->address = getTextData(L,"address");
engine->m_data->servername = getTextData(L,"servername"); data->port = getTextData(L,"port");
}
data->serverdescription = getTextData(L,"serverdescription");
data->servername = getTextData(L,"servername");
//close menu next time //close menu next time
engine->m_startgame = true; engine->m_startgame = true;

@ -30,7 +30,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
// request_shutdown() // request_shutdown()
int ModApiServer::l_request_shutdown(lua_State *L) 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; return 0;
} }

@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
class ModApiServer : public ModApiBase { class ModApiServer : public ModApiBase {
private: private:
// request_shutdown() // request_shutdown([message], [reconnect])
static int l_request_shutdown(lua_State *L); static int l_request_shutdown(lua_State *L);
// get_server_status() // get_server_status()

@ -191,6 +191,7 @@ Server::Server(
m_uptime(0), m_uptime(0),
m_clients(&m_con), m_clients(&m_con),
m_shutdown_requested(false), m_shutdown_requested(false),
m_shutdown_ask_reconnect(false),
m_ignore_map_edit_events(false), m_ignore_map_edit_events(false),
m_ignore_map_edit_events_peer_id(0), m_ignore_map_edit_events_peer_id(0),
m_next_sound_id(0) m_next_sound_id(0)
@ -398,7 +399,17 @@ Server::~Server()
m_env->saveLoadedPlayers(); m_env->saveLoadedPlayers();
infostream << "Server: Kicking players" << std::endl; 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; infostream << "Server: Saving environment metadata" << std::endl;
m_env->saveMeta(); m_env->saveMeta();
@ -502,7 +513,9 @@ void Server::step(float dtime)
throw ServerError(async_err); throw ServerError(async_err);
} }
else { 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. " errorstream << "UNRECOVERABLE error occurred. Stopping server. "
<< "Please fix the following error:" << std::endl << "Please fix the following error:" << std::endl
<< async_err << 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); RemoteClient* client = m_clients.lockedGetClientNoEx(peer_id, CS_InitDone);
if (client != NULL) { if (client != NULL) {
playername = client->getName(); 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) { } catch (std::exception &e) {
m_clients.Unlock(); m_clients.Unlock();
@ -1523,16 +1536,18 @@ void Server::SendBreath(u16 peer_id, u16 breath)
Send(&pkt); 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); NetworkPacket pkt(TOCLIENT_ACCESS_DENIED, 1, peer_id);
pkt << (u8) reason; pkt << (u8)reason;
if (reason == SERVER_ACCESSDENIED_CUSTOM_STRING)
if (reason == SERVER_ACCESSDENIED_CUSTOM_STRING) {
pkt << custom_reason; pkt << custom_reason;
} else if (reason == SERVER_ACCESSDENIED_SHUTDOWN ||
reason == SERVER_ACCESSDENIED_CRASH)
pkt << custom_reason << (u8)reconnect;
Send(&pkt); Send(&pkt);
} }
@ -2567,6 +2582,8 @@ void Server::RespawnPlayer(u16 peer_id)
playersao->setPos(pos); playersao->setPos(pos);
} }
} }
void Server::DenySudoAccess(u16 peer_id) void Server::DenySudoAccess(u16 peer_id)
{ {
DSTACK(__FUNCTION_NAME); DSTACK(__FUNCTION_NAME);
@ -2575,6 +2592,24 @@ void Server::DenySudoAccess(u16 peer_id)
Send(&pkt); 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) void Server::DenyAccess(u16 peer_id, AccessDeniedCode reason, const std::string &custom_reason)
{ {
DSTACK(__FUNCTION_NAME); DSTACK(__FUNCTION_NAME);
@ -3330,7 +3365,7 @@ v3f Server::findSpawnPos()
return intToFloat(nodepos, BS); 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; bool newplayer = false;
@ -3383,6 +3418,8 @@ PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id)
getPlayerEffectivePrivs(player->getName()), getPlayerEffectivePrivs(player->getName()),
isSingleplayer()); isSingleplayer());
player->protocol_version = proto_version;
/* Clean up old HUD elements from previous sessions */ /* Clean up old HUD elements from previous sessions */
player->clearHud(); player->clearHud();

@ -244,8 +244,13 @@ public:
{ return m_shutdown_requested; } { return m_shutdown_requested; }
// request server to shutdown // request server to shutdown
inline void requestShutdown(void) inline void requestShutdown() { m_shutdown_requested = true; }
{ 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 // Returns -1 if failed, sound handle on success
// Envlock // Envlock
@ -366,6 +371,8 @@ public:
void deletingPeer(con::Peer *peer, bool timeout); void deletingPeer(con::Peer *peer, bool timeout);
void DenySudoAccess(u16 peer_id); 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 DenyAccess(u16 peer_id, AccessDeniedCode reason, const std::string &custom_reason="");
void acceptAuth(u16 peer_id, bool forSudoMode); void acceptAuth(u16 peer_id, bool forSudoMode);
void DenyAccess_Legacy(u16 peer_id, const std::wstring &reason); void DenyAccess_Legacy(u16 peer_id, const std::wstring &reason);
@ -390,7 +397,8 @@ private:
void SendMovement(u16 peer_id); void SendMovement(u16 peer_id);
void SendHP(u16 peer_id, u8 hp); void SendHP(u16 peer_id, u8 hp);
void SendBreath(u16 peer_id, u16 breath); 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 SendAccessDenied_Legacy(u16 peer_id, const std::wstring &reason);
void SendDeathscreen(u16 peer_id,bool set_camera_point_target, v3f camera_point_target); void SendDeathscreen(u16 peer_id,bool set_camera_point_target, v3f camera_point_target);
void SendItemDef(u16 peer_id,IItemDefManager *itemdef, u16 protocol_version); void SendItemDef(u16 peer_id,IItemDefManager *itemdef, u16 protocol_version);
@ -490,7 +498,7 @@ private:
Call with env and con locked. 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(); void handlePeerChanges();
@ -596,6 +604,8 @@ private:
*/ */
bool m_shutdown_requested; bool m_shutdown_requested;
std::string m_shutdown_msg;
bool m_shutdown_ask_reconnect;
/* /*
Map edit event queue. Automatically receives all map edits. Map edit event queue. Automatically receives all map edits.