-- LUALOCALS < --------------------------------------------------------- local io, ipairs, math, minetest, pairs, string, tonumber = io, ipairs, math, minetest, pairs, string, tonumber local io_open, math_floor, string_format = io.open, math.floor, string.format -- LUALOCALS > --------------------------------------------------------- local modname = minetest.get_current_modname() ------------------------------------------------------------------------ -- Global configuration local function conf(n) return minetest.settings:get(modname .. "_" .. n) end -- Default restart grace time. local grace = tonumber(conf("grace")) or 300 -- Amount of time before a restart that the countdown will be -- announced/displayed. local countdown = conf("countdown") or grace -- Amount of time during which the countdown will flash. local critical = conf("countdown") or 10 -- Primary color of the countdown HUD. local hudcolor = conf("hudcolor") or 0xFFFF00 -- Flashing color of HUD during critical countdown. local hudcolorflash = conf("hudcolor") or 0xFF0000 -- Don't restart the server too often; give players at least this -- much time after a restart, if any are on. local minuptime = tonumber(conf("minuptime")) or 7200 -- Always restart after the server has been up this long. local maxuptime = tonumber(conf("maxuptime")) or 86400 -- Restart shutdown message. local shtudownmsg = string_format("\n\n%s\n%s", conf("shutdownmsg1") or "SERVER RESTARTING FOR UPDATES", conf("shutdownmsg2") or "Please reconnect in about 10 seconds") -- Message when players are kicked off for restart local kickmsg = conf("kickmsg") or "*** Kicking players off for restart" -- Message when restarting but there are no players online local restartmsg = conf("restartmsg") or "*** Restarting server" -- Message used to announce pending restart in chat. local chatmsg = conf("chatmsg") or "*** Server restart in %s" -- Message used to display pending restart in HUD. local hudmsg = conf("hudmsg") or "RESTART IN %s" ------------------------------------------------------------------------ -- Global time functions -- Function to get current uptime of server. local uptime do local starttime = minetest.get_us_time() uptime = function() return (minetest.get_us_time() - starttime) / 1000000 end end -- If restart requested, this is the uptime value at which the -- restart should happen, nil if none pending. local req -- Get number of seconds until restart, if any. local function remain() return req and req - uptime() end -- Get formatted restart time, if any local function remaintext() if not req then return end local cdown = math_floor(remain()) local min = cdown / 60 local sec = cdown % 60 if min < 60 then return string_format("%d:%02d", min, sec) end local hr = min / 60 min = min % 60 return string_format("%d:%02d:%02d", hr, min, sec) end ------------------------------------------------------------------------ -- Trigger conditions do local function restarttrigger(reason, time) minetest.log("RESTART REQUEST (" .. reason .. ")") req = uptime() + (time or grace) if time then return end if req < minuptime then req = minuptime elseif req > maxuptime then req = maxuptime end end -- Server admins can manually request a restart, including with a -- custom grace time. Restarts cannot be canceled entirely (nor -- should they be, probably), but can be delayed indefinitely. minetest.register_chatcommand("trigger_restart", { description = "Signal a restart request manually, or reset countdown", privs = {server = true}, func = function(name, param) restarttrigger("manual by " .. name, tonumber(param)) end }) -- Automatically detect a restart condition. local function restartcheck() if req then return end -- Trigger restart if the server has reached its maximum uptime. if uptime() >= maxuptime - grace then return restarttrigger("max uptime") end -- Trigger a restart if a file exists in the world dir to allow -- an external script to request it. local f = io_open(minetest.get_worldpath() .. "/restart") if f then f:close() return restarttrigger("file trigger") end return minetest.after(2, restartcheck) end restartcheck() end ------------------------------------------------------------------------ -- Announce pending restarts in chat streams local announced do local lastsent minetest.register_globalstep(function() -- Skip if no countdown yet. if not req then return end -- Never announce if no players online. if #minetest.get_connected_players() < 1 then lastsent = nil return end -- Announce if the remaining time has changed, or -- we've crossed to/from the active countdown phase. if (announced ~= req) or (not lastsent) or ((lastsent > countdown) ~= (remain() > countdown)) then announced = req lastsent = remain() minetest.chat_send_all(string_format(chatmsg, remaintext())) end end) end ------------------------------------------------------------------------ -- Handle actual restart event do local shuttingdown minetest.register_globalstep(function() if shuttingdown or not req then return end local pcount = #minetest.get_connected_players() if pcount > 0 and remain() > 0 then return end shuttingdown = true if #minetest.get_connected_players() > 0 then minetest.chat_send_all(kickmsg) elseif announced then minetest.chat_send_all(restartmsg) end return minetest.request_shutdown(shtudownmsg, true) end) end ------------------------------------------------------------------------ -- Announce pending restarts via HUD do local huds = {} minetest.register_on_leaveplayer(function(player) huds[player:get_player_name()] = nil end) minetest.register_globalstep(function() if #minetest.get_connected_players() < 1 then return end if (not req) or (remain() > countdown) then for pname, hud in pairs(huds) do local player = minetest.get_player_by_name(pname) if player then player:hud_remove(hud.id) end huds[pname] = nil end return end local number = (remain() < critical and (remain() - math_floor(remain()) < 0.5)) and hudcolorflash or hudcolor local text = string_format(hudmsg, remaintext()) for _, player in ipairs(minetest.get_connected_players()) do local pname = player:get_player_name() local hud = huds[pname] if hud then if hud.number ~= number then player:hud_change(hud.id, "number", number) hud.number = number end if hud.text ~= text then player:hud_change(hud.id, "text", text) hud.text = text end else huds[pname] = { number = number, text = text, id = player:hud_add({ label = "restart_warn", hud_elem_type = "text", position = {x = 0.5, y = 0.8}, text = text, number = number, alignment = {x = 0, y = 1}, offset = {x = 0, y = 0} }) } end end end) end