diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua index 8df3903d2..25cc06178 100644 --- a/builtin/game/chatcommands.lua +++ b/builtin/game/chatcommands.lua @@ -763,14 +763,20 @@ core.register_chatcommand("days", { core.register_chatcommand("shutdown", { description = "Shutdown server", - params = "[reconnect] [message]", + params = "[delay_in_seconds(0..inf) or -1 for cancel] [reconnect] [message]", privs = {server=true}, func = function(name, param) - core.log("action", name .. " shuts down server") - core.chat_send_all("*** Server shutting down (operator request).") - local reconnect, message = param:match("([^ ]+)(.*)") + local delay, reconnect, message = param:match("([^ ][-]?[0-9]+)([^ ]+)(.*)") message = message or "" - core.request_shutdown(message:trim(), core.is_yes(reconnect)) + + if delay ~= "" then + delay = tonumber(param) or 0 + else + delay = 0 + core.log("action", name .. " shuts down server") + core.chat_send_all("*** Server shutting down (operator request).") + end + core.request_shutdown(message:trim(), core.is_yes(reconnect), delay) end, }) diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index 618d4d97f..a3eb26ac2 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -173,3 +173,8 @@ end function core.close_formspec(player_name, formname) return minetest.show_formspec(player_name, formname, "") end + +function core.cancel_shutdown_requests() + core.request_shutdown("", false, -1) +end + diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 721f5448a..7b967726d 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2576,8 +2576,12 @@ These functions return the leftover itemstack. * Optional: Variable number of arguments that are passed to `func` ### Server -* `minetest.request_shutdown([message],[reconnect])`: request for server shutdown. Will display `message` to clients, - and `reconnect` == true displays a reconnect button. +* `minetest.request_shutdown([message],[reconnect],[delay])`: request for server shutdown. Will display `message` to clients, + `reconnect` == true displays a reconnect button, + `delay` adds an optional delay (in seconds) before shutdown + negative delay cancels the current active shutdown + zero delay triggers an immediate shutdown. +* `minetest.cancel_shutdown_requests()`: cancel current delayed shutdown * `minetest.get_server_status()`: returns server status string * `minetest.get_server_uptime()`: returns the server uptime in seconds diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 27c33a4f6..2e4c5b6be 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -722,6 +722,13 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt) m_clients.event(peer_id, CSE_SetClientReady); m_script->on_joinplayer(playersao); + // Send shutdown timer if shutdown has been scheduled + if (m_shutdown_timer > 0.0f) { + std::wstringstream ws; + ws << L"*** Server shutting down in " + << duration_to_string(round(m_shutdown_timer)).c_str() << "."; + SendChatMessage(pkt->getPeerId(), ws.str()); + } } void Server::handleCommand_GotBlocks(NetworkPacket* pkt) diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index b6d44e0ff..343d11b7e 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -33,7 +33,8 @@ int ModApiServer::l_request_shutdown(lua_State *L) NO_MAP_LOCK_REQUIRED; const char *msg = lua_tolstring(L, 1, NULL); bool reconnect = lua_toboolean(L, 2); - getServer(L)->requestShutdown(msg ? msg : "", reconnect); + float seconds_before_shutdown = lua_tonumber(L, 3); + getServer(L)->requestShutdown(msg ? msg : "", reconnect, seconds_before_shutdown); return 0; } diff --git a/src/server.cpp b/src/server.cpp index 7ed8a8bf4..5328b6897 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -177,6 +177,7 @@ Server::Server( m_clients(&m_con), m_shutdown_requested(false), m_shutdown_ask_reconnect(false), + m_shutdown_timer(0.0f), m_admin_chat(iface), m_ignore_map_edit_events(false), m_ignore_map_edit_events_peer_id(0), @@ -1029,6 +1030,39 @@ void Server::AsyncRunStep(bool initial_step) m_env->saveMeta(); } } + + // Timed shutdown + static const float shutdown_msg_times[] = + { + 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 45, 60, 120, 180, 300, 600, 1200, 1800, 3600 + }; + + if (m_shutdown_timer > 0.0f) { + // Automated messages + if (m_shutdown_timer < shutdown_msg_times[ARRLEN(shutdown_msg_times) - 1]) { + for (u16 i = 0; i < ARRLEN(shutdown_msg_times) - 1; i++) { + // If shutdown timer matches an automessage, shot it + if (m_shutdown_timer > shutdown_msg_times[i] && + m_shutdown_timer - dtime < shutdown_msg_times[i]) { + std::wstringstream ws; + + ws << L"*** Server shutting down in " + << duration_to_string(round(m_shutdown_timer - dtime)).c_str() + << "."; + + infostream << wide_to_utf8(ws.str()).c_str() << std::endl; + SendChatMessage(PEER_ID_INEXISTENT, ws.str()); + break; + } + } + } + + m_shutdown_timer -= dtime; + if (m_shutdown_timer < 0.0f) { + m_shutdown_timer = 0.0f; + m_shutdown_requested = true; + } + } } void Server::Receive() @@ -3443,6 +3477,39 @@ v3f Server::findSpawnPos() return nodeposf; } +void Server::requestShutdown(const std::string &msg, bool reconnect, float delay) +{ + if (delay == 0.0f) { + // No delay, shutdown immediately + m_shutdown_requested = true; + } else if (delay < 0.0f && m_shutdown_timer > 0.0f) { + // Negative delay, cancel shutdown if requested + m_shutdown_timer = 0.0f; + m_shutdown_msg = ""; + m_shutdown_ask_reconnect = false; + m_shutdown_requested = false; + std::wstringstream ws; + + ws << L"*** Server shutdown canceled."; + + infostream << wide_to_utf8(ws.str()).c_str() << std::endl; + SendChatMessage(PEER_ID_INEXISTENT, ws.str()); + } else if (delay > 0.0f) { + // Positive delay, delay the shutdown + m_shutdown_timer = delay; + m_shutdown_msg = msg; + m_shutdown_ask_reconnect = reconnect; + std::wstringstream ws; + + ws << L"*** Server shutting down in " + << duration_to_string(round(m_shutdown_timer)).c_str() + << "."; + + infostream << wide_to_utf8(ws.str()).c_str() << std::endl; + SendChatMessage(PEER_ID_INEXISTENT, ws.str()); + } +} + PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id, u16 proto_version) { bool newplayer = false; diff --git a/src/server.h b/src/server.h index 1df9d0a93..e2445f833 100644 --- a/src/server.h +++ b/src/server.h @@ -226,12 +226,7 @@ public: inline bool getShutdownRequested() const { return m_shutdown_requested; } // request server to shutdown - void requestShutdown(const std::string &msg, bool reconnect) - { - m_shutdown_requested = true; - m_shutdown_msg = msg; - m_shutdown_ask_reconnect = reconnect; - } + void requestShutdown(const std::string &msg, bool reconnect, float delay = 0.0f); // Returns -1 if failed, sound handle on success // Envlock @@ -602,6 +597,7 @@ private: bool m_shutdown_requested; std::string m_shutdown_msg; bool m_shutdown_ask_reconnect; + float m_shutdown_timer; ChatInterface *m_admin_chat; std::string m_admin_nick; diff --git a/src/util/string.h b/src/util/string.h index 572c37150..c155d2f4a 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -614,4 +614,28 @@ inline const char *bool_to_cstr(bool val) return val ? "true" : "false"; } +inline const std::string duration_to_string(int sec) +{ + int min = floor(sec / 60); + sec %= 60; + int hour = floor(min / 60); + min %= 60; + + std::stringstream ss; + if (hour > 0) { + ss << hour << "h "; + } + + if (min > 0) { + ss << min << "m "; + } + + if (sec > 0) { + ss << sec << "s "; + } + + return ss.str(); +} + + #endif