1 -- Description: Configure and enforce server opening hours
2 -- Author: Elias Fleckenstein
3 -- Author: Isidor Zeuner
4 -- For license see LICENSE
5 -- -------------------------------------------------------------------
7 local modname = minetest.get_current_modname()
8 local storage = minetest.get_mod_storage()
9 local S = minetest.get_translator("lmz_opening_hours")
13 local opening_hours_default = {
16 day0_start_minute = 0,
20 day1_start_minute = 0,
24 day2_start_minute = 0,
28 day3_start_minute = 0,
32 day4_start_minute = 0,
36 day5_start_minute = 0,
40 day6_start_minute = 0,
47 local warn_cooldown = 0
49 local function get_date()
53 local function get_date_formated()
54 return os.date("%d.%m.%y")
57 local function upgrade_configuration(old)
60 day0_start_hour = tonumber(old.weekend_start),
61 day0_start_minute = 0,
62 day0_end_hour = tonumber(old.weekend_end),
64 day1_start_hour = tonumber(old.weekday_start),
65 day1_start_minute = 0,
66 day1_end_hour = tonumber(old.weekday_end),
68 day2_start_hour = tonumber(old.weekday_start),
69 day2_start_minute = 0,
70 day2_end_hour = tonumber(old.weekday_end),
72 day3_start_hour = tonumber(old.weekday_start),
73 day3_start_minute = 0,
74 day3_end_hour = tonumber(old.weekday_end),
76 day4_start_hour = tonumber(old.weekday_start),
77 day4_start_minute = 0,
78 day4_end_hour = tonumber(old.weekday_end),
80 day5_start_hour = tonumber(old.weekday_start),
81 day5_start_minute = 0,
82 day5_end_hour = tonumber(old.weekday_end),
84 day6_start_hour = tonumber(old.weekend_start),
85 day6_start_minute = 0,
86 day6_end_hour = tonumber(old.weekend_end),
88 warn_offset = tonumber(old.warn_offset),
89 warn_interval = tonumber(old.warn_interval)
92 new.exception_today = old.today
93 new.exception_start_hour = old.today_start
94 new.exception_start_minute = 0
95 new.exception_end_hour = old.today_end
96 new.exception_end_minute = 0
101 local function save_data()
102 storage:from_table({fields = opening_hours})
105 local function load_data()
106 opening_hours = storage:to_table().fields
107 if opening_hours.weekday_start then
108 opening_hours = upgrade_configuration(opening_hours)
109 elseif not opening_hours.version then
110 opening_hours = opening_hours_default
112 for k, v in pairs(opening_hours) do
113 if k ~= "exception_today" then
114 opening_hours[k] = tonumber(v)
119 local function reset_execption()
120 opening_hours.exception_today = nil
123 local function opening_today()
124 local exception = opening_hours.exception_today
126 if exception and exception == get_date_formated() then
127 day_key = "exception"
129 local d = os.date("%w")
133 start_hour = opening_hours[day_key .. "_start_hour"],
134 start_minute = opening_hours[day_key .. "_start_minute"],
135 end_hour = opening_hours[day_key .. "_end_hour"],
136 end_minute = opening_hours[day_key .. "_end_minute"]
140 local function create_exception()
141 local today = opening_today()
142 opening_hours.exception_today = get_date_formated()
143 opening_hours.exception_start_hour = today.start_hour
144 opening_hours.exception_start_minute = today.start_minute
145 opening_hours.exception_end_hour = today.end_hour
146 opening_hours.exception_end_minute = today.end_minute
149 local function tick(dtime)
151 local exception = opening_hours.exception_today
152 if exception and exception ~= get_date_formated() then
155 local today = opening_today()
156 local end_time = today.end_hour * 60 + today.end_minute
157 local now_time = d.hour * 60 + d.min
158 local minutes_remaining = end_time - now_time
159 if 0 < minutes_remaining
160 and minutes_remaining <= opening_hours.warn_offset then
161 if warn_cooldown <= 0 then
162 minetest.chat_send_all(minetest.colorize("#FF4D00", S("The server will close in @1 minutes.", minutes_remaining)))
163 warn_cooldown = tonumber(opening_hours.warn_interval) * 60
165 warn_cooldown = warn_cooldown - dtime
167 elseif minutes_remaining <= 0 then
168 for _, player in pairs(minetest.get_connected_players()) do
169 local name = player:get_player_name()
170 if not minetest.check_player_privs(name, {server = true}) then
171 minetest.kick_player(name, S("The server is closing!"))
177 local function on_join(name)
178 if minetest.check_player_privs(name, {server = true}) then return end
179 local today = opening_today()
181 local start_time = today.start_hour * 60 + today.start_minute
182 local end_time = today.end_hour * 60 + today.end_minute
183 local now_time = d.hour * 60 + d.min
184 local diff = start_time - now_time
186 return S("You visited outside of the opening hours") .. ". " .. S("The server will open again in @1 hours", math.ceil(diff / 60)) .. "."
187 elseif end_time <= now_time then
188 return S("You visited outside of the opening hours") .. ". " .. S("The server has already closed and will open again tomorrow") .. "."
192 local minute_step = 5
194 local function show_gui(name)
196 local fld_h = 0.82429501084599
197 local fld_sz = fld_w .. "," .. fld_h
198 local inline_off = 0.2427394885132
199 local lab_close_y = 6.3935847420893
200 local fld_close_y = lab_close_y + 0.2427394885132
201 local pre_colon_off = 0.4
202 local minute_off = 0.04
209 x.day1.fld_f_hour = 0.64
210 x.day1.fld_f_minute = x.day1.fld_f_hour + fld_w - minute_off
211 x.day1.lab_f_colon = x.day1.fld_f_minute - pre_colon_off
212 x.day1.fld_t_hour = x.day1.lab_f_colon + to_off
213 x.day1.fld_t_minute = x.day1.fld_t_hour + fld_w - minute_off
214 x.day1.lab_t_colon = x.day1.fld_t_minute - pre_colon_off
219 for k, v in pairs(x["day" .. last]) do
220 x["day" .. day][k] = v + day_off
225 local below_off = 1.1530125704378
226 local lab_b_y = 0.28175119202427
227 local fld_b_y = lab_b_y + below_off
228 local lab_b_colon_y = fld_b_y - inline_off
229 local lab_w_y = 2.0156046814044
230 local fld_w_y = lab_w_y + below_off
231 local lab_w_colon_y = fld_w_y - inline_off
232 local lab_e_y = 3.7494581707846
233 local fld_e_y = lab_e_y + below_off
234 local lab_e_colon_y = fld_e_y - inline_off
235 local o = opening_hours
236 local day_abbreviations = {
245 local player_info = minetest.get_player_information(name)
246 local warning_config_translation_string = S(
247 "@1 minutes before closing, warn the players every @2 minutes.",
251 local warning_config = minetest.get_translated_string(
252 player_info.lang_code,
253 warning_config_translation_string
255 local formspec_warning = ""
256 local warning_x = 0.34
257 for fragment in warning_config:gmatch("([^<>]+<?)") do
258 local label = fragment:match("<$")
260 fragment = fragment:gsub("<$", "")
263 formspec_warning = formspec_warning ..
264 "label[" .. warning_x .. "," .. lab_close_y .. ";" .. fragment .. "]"
265 warning_x = warning_x + 0.125 * fragment:len()
267 formspec_warning = formspec_warning ..
268 "field[" .. (warning_x + 0.2) .. "," .. fld_close_y .. ";" .. fld_sz .. ";fld_" .. fragment .. ";;" .. o[fragment] .. "]"
269 warning_x = warning_x + 0.6
272 local formspec_business_days = ""
274 formspec_business_days = formspec_business_days
275 .. "label[" .. x["day" .. day].lab .. "," .. lab_b_y .. ";" .. day_abbreviations[day] .. "]"
276 .. "field[" .. x["day" .. day].fld_f_hour .. "," .. fld_b_y .. ";" .. fld_sz .. ";fld_day" .. day .. "_start_hour;" .. S("from") .. ";" .. string.format("%02d", o["day" .. day .. "_start_hour"]) .. "]"
277 .. "label[" .. x["day" .. day].lab_f_colon .. "," .. lab_b_colon_y .. ";:]"
278 .. "field[" .. x["day" .. day].fld_f_minute .. "," .. fld_b_y .. ";" .. fld_sz .. ";fld_day" .. day .. "_start_minute;;" .. string.format("%02d", o["day" .. day .. "_start_minute"]) .. "]"
279 .. "field[" .. x["day" .. day].fld_t_hour .. "," .. fld_b_y .. ";" .. fld_sz .. ";fld_day" .. day .. "_end_hour;" .. S("to") .. ";" .. string.format("%02d", o["day" .. day .. "_end_hour"]) .. "]"
280 .. "label[" .. x["day" .. day].lab_t_colon .. "," .. lab_b_colon_y .. ";:]"
281 .. "field[" .. x["day" .. day].fld_t_minute .. "," .. fld_b_y .. ";" .. fld_sz .. ";fld_day" .. day .. "_end_minute;;" .. string.format("%02d", o["day" .. day .. "_end_minute"]) .. "]"
283 local formspec_weekend = ""
285 local day = (5 + col) % 7
286 formspec_weekend = formspec_weekend
287 .. "label[" .. x["day" .. col].lab .. "," .. lab_w_y .. ";" .. day_abbreviations[day] .. "]"
288 .. "field[" .. x["day" .. col].fld_f_hour .. "," .. fld_w_y .. ";" .. fld_sz .. ";fld_day" .. day .. "_start_hour;" .. S("from") .. ";" .. string.format("%02d", o["day" .. day .. "_start_hour"]) .. "]"
289 .. "label[" .. x["day" .. col].lab_f_colon .. "," .. lab_w_colon_y .. ";:]"
290 .. "field[" .. x["day" .. col].fld_f_minute .. "," .. fld_w_y .. ";" .. fld_sz .. ";fld_day" .. day .. "_start_minute;;" .. string.format("%02d", o["day" .. day .. "_start_minute"]) .. "]"
291 .. "field[" .. x["day" .. col].fld_t_hour .. "," .. fld_w_y .. ";" .. fld_sz .. ";fld_day" .. day .. "_end_hour;" .. S("to") .. ";" .. string.format("%02d", o["day" .. day .. "_end_hour"]) .. "]"
292 .. "label[" .. x["day" .. col].lab_t_colon .. "," .. lab_w_colon_y .. ";:]"
293 .. "field[" .. x["day" .. col].fld_t_minute .. "," .. fld_w_y .. ";" .. fld_sz .. ";fld_day" .. day .. "_end_minute;;" .. string.format("%02d", o["day" .. day .. "_end_minute"]) .. "]"
295 local formspec_exception = (o.exception_today
297 .. "label[" .. x.day1.lab .. "," .. lab_e_y .. ";" .. S("Today") .. "]"
298 .. "field[" .. x.day1.fld_f_hour .. "," .. fld_e_y .. ";" .. fld_sz .. ";fld_exception_start_hour;" .. S("from") .. ";" .. string.format("%02d", o.exception_start_hour) .. "]"
299 .. "label[" .. x.day1.lab_f_colon .. "," .. lab_e_colon_y .. ";:]"
300 .. "field[" .. x.day1.fld_f_minute .. "," .. fld_e_y .. ";" .. fld_sz .. ";fld_exception_start_minute;;" .. string.format("%02d", o.exception_start_minute) .. "]"
301 .. "field[" .. x.day1.fld_t_hour .. "," .. fld_e_y .. ";" .. fld_sz .. ";fld_exception_end_hour;" .. S("to") .. ";" .. string.format("%02d", o.exception_end_hour) .. "]"
302 .. "label[" .. x.day1.lab_t_colon .. "," .. lab_e_colon_y .. ";:]"
303 .. "field[" .. x.day1.fld_t_minute .. "," .. fld_e_y .. ";" .. fld_sz .. ";fld_exception_end_minute;;" .. string.format("%02d", o.exception_end_minute) .. "]"
304 or "image_button[0.34,4.5296922410056;4.205,0.7835;;add_exception;" .. S("Add exception") .. "]"
306 local formspec = "size[18.01,7.9267895878525]"
307 .. "label[-0.14,-0.23840485478977;" .. S("Opening hours") .. "]"
308 .. formspec_business_days
310 .. formspec_exception
311 .. "label[" .. x.day1.lab .. ",5.4833116601647;" .. S("Settings") .. "]"
313 .. "image_button[5.14,7.6072821846554;2.605,0.7835;;save;" .. S("Save") .. "]"
314 .. "image_button_exit[7.62,7.6072821846554;2.605,0.7835;;close;" .. S("Close") .. "]"
315 minetest.show_formspec(name, "lmz_opening_hours:gui", formspec)
318 local function progress_gui_input(player, formname, fields)
319 local name = player:get_player_name()
320 if formname ~= "lmz_opening_hours:gui" or not minetest.check_player_privs(name, {server = true}) then return end
321 if fields.add_exception then
324 for k, v in pairs(fields) do
325 if k:sub(1, 4) == "fld_" then
326 local field = k:gsub("fld_", "")
327 local old = opening_hours[field]
328 opening_hours[field] = tonumber(v) or old
331 for k, v in pairs(opening_hours) do
332 if k:match("_hour$") then
333 opening_hours[k] = math.max(0, math.min(23, v))
334 elseif k:match("_minute$") then
335 opening_hours[k] = math.max(
339 minute_step * math.floor(
340 v / minute_step + 0.5
346 for k, v in pairs(opening_hours) do
347 if k:match("_end_hour$") then
348 local start = opening_hours[k:gsub("_end_", "_start_")]
350 opening_hours[k] = start
352 elseif k:match("_end_minute$") then
353 local hour_k = k:gsub("_minute$", "_hour")
354 local hour_start_k = hour_k:gsub("_end_", "_start_")
355 local start_k = k:gsub("_end_", "_start_")
356 if opening_hours[hour_start_k] >= opening_hours[hour_k]
357 and opening_hours[start_k] > v then
358 opening_hours[k] = opening_hours[start_k]
362 if not fields.quit and not fields.close then show_gui(name) end
367 minetest.register_globalstep(tick)
368 minetest.register_on_shutdown(save_data)
369 minetest.register_on_prejoinplayer(on_join)
370 minetest.register_chatcommand(
373 privs = {server = true},
374 description = S("Configure the opening hours"),
378 minetest.register_chatcommand(
381 privs = {server = true},
382 description = S("Configure the opening hours"),
386 minetest.register_on_player_receive_fields(progress_gui_input)