]> git.lizzy.rs Git - lmz_opening_hours.git/blob - init.lua
Merge pull request #1 from MinetestEDU/lmz-20210303
[lmz_opening_hours.git] / init.lua
1 -- Description: Configure and enforce server opening hours
2 -- Author: Elias Fleckenstein
3 -- Author: Isidor Zeuner
4 -- For license see LICENSE
5 -- -------------------------------------------------------------------
6
7 local modname = minetest.get_current_modname()
8 local storage = minetest.get_mod_storage()
9 local S = minetest.get_translator("lmz_opening_hours")
10
11 opening_hours = {}
12
13 local opening_hours_default = {
14         version = 2,
15         day0_start_hour = 8,
16         day0_start_minute = 0,
17         day0_end_hour = 21,
18         day0_end_minute = 0,
19         day1_start_hour = 14,
20         day1_start_minute = 0,
21         day1_end_hour = 21,
22         day1_end_minute = 0,
23         day2_start_hour = 14,
24         day2_start_minute = 0,
25         day2_end_hour = 21,
26         day2_end_minute = 0,
27         day3_start_hour = 14,
28         day3_start_minute = 0,
29         day3_end_hour = 21,
30         day3_end_minute = 0,
31         day4_start_hour = 14,
32         day4_start_minute = 0,
33         day4_end_hour = 21,
34         day4_end_minute = 0,
35         day5_start_hour = 14,
36         day5_start_minute = 0,
37         day5_end_hour = 21,
38         day5_end_minute = 0,
39         day6_start_hour = 8,
40         day6_start_minute = 0,
41         day6_end_hour = 21,
42         day6_end_minute = 0,
43         warn_offset = 15,
44         warn_interval = 5
45 }
46
47 local warn_cooldown = 0
48
49 local function get_date()
50         return os.date("*t")
51 end
52
53 local function get_date_formated()
54         return os.date("%d.%m.%y")
55 end
56
57 local function upgrade_configuration(old)
58         local new = {
59                 version = 2,
60                 day0_start_hour = tonumber(old.weekend_start),
61                 day0_start_minute = 0,
62                 day0_end_hour = tonumber(old.weekend_end),
63                 day0_end_minute = 0,
64                 day1_start_hour = tonumber(old.weekday_start),
65                 day1_start_minute = 0,
66                 day1_end_hour = tonumber(old.weekday_end),
67                 day1_end_minute = 0,
68                 day2_start_hour = tonumber(old.weekday_start),
69                 day2_start_minute = 0,
70                 day2_end_hour = tonumber(old.weekday_end),
71                 day2_end_minute = 0,
72                 day3_start_hour = tonumber(old.weekday_start),
73                 day3_start_minute = 0,
74                 day3_end_hour = tonumber(old.weekday_end),
75                 day3_end_minute = 0,
76                 day4_start_hour = tonumber(old.weekday_start),
77                 day4_start_minute = 0,
78                 day4_end_hour = tonumber(old.weekday_end),
79                 day4_end_minute = 0,
80                 day5_start_hour = tonumber(old.weekday_start),
81                 day5_start_minute = 0,
82                 day5_end_hour = tonumber(old.weekday_end),
83                 day5_end_minute = 0,
84                 day6_start_hour = tonumber(old.weekend_start),
85                 day6_start_minute = 0,
86                 day6_end_hour = tonumber(old.weekend_end),
87                 day6_end_minute = 0,
88                 warn_offset = tonumber(old.warn_offset),
89                 warn_interval = tonumber(old.warn_interval)
90         }
91         if old.today then
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
97         end
98         return new
99 end
100
101 local function save_data()
102         storage:from_table({fields = opening_hours})
103 end
104
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
111         end
112         for k, v in pairs(opening_hours) do
113                 if k ~= "exception_today" then
114                         opening_hours[k] = tonumber(v)
115                 end
116         end
117 end
118
119 local function reset_execption()
120         opening_hours.exception_today = nil
121 end
122
123 local function opening_today()
124         local exception = opening_hours.exception_today
125         local day_key
126         if exception and exception == get_date_formated() then
127                 day_key = "exception"
128         else
129                 local d = os.date("%w")
130                 day_key = "day" .. d
131         end
132         return {
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"]
137         }
138 end
139
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
147 end
148
149 local function tick(dtime)
150         local d = get_date()
151         local exception = opening_hours.exception_today
152         if exception and exception ~= get_date_formated() then
153                 reset_execption()
154         end
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
164                 else
165                         warn_cooldown = warn_cooldown - dtime
166                 end
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!"))
172                         end
173                 end
174         end
175 end
176
177 local function on_join(name)
178         if minetest.check_player_privs(name, {server = true}) then return end
179         local today = opening_today()
180         local d = get_date()
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
185         if diff > 0 then
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") .. "."
189         end
190 end
191
192 local minute_step = 5
193
194 local function show_gui(name)
195         local fld_w = 0.88
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
203         local to_off = 1.24
204         local day_off = 3.6
205         local x = {
206                 day1 = {}
207         }
208         x.day1.lab = 0.1
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
215         local last
216         for day = 1, 5, 1 do
217                 if last then
218                         x["day" .. day] = {}
219                         for k, v in pairs(x["day" .. last]) do
220                                 x["day" .. day][k] = v + day_off
221                         end
222                 end
223                 last = day
224         end
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 = {
237                 [0] = S("Su."),
238                 [1] = S("Mo."),
239                 [2] = S("Tu."),
240                 [3] = S("We."),
241                 [4] = S("Th."),
242                 [5] = S("Fr."),
243                 [6] = S("Sa.")
244         }
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.",
248                 "<warn_offset>",
249                 "<warn_interval>"
250         )
251         local warning_config = minetest.get_translated_string(
252                 player_info.lang_code,
253                 warning_config_translation_string
254         ) .. "<"
255         local formspec_warning = ""
256         local warning_x = 0.34
257         for fragment in warning_config:gmatch("([^<>]+<?)") do
258                 local label = fragment:match("<$")
259                 if label then
260                         fragment = fragment:gsub("<$", "")
261                 end
262                 if label then
263                         formspec_warning = formspec_warning ..
264                         "label[" .. warning_x .. "," .. lab_close_y .. ";" .. fragment .. "]"
265                         warning_x = warning_x + 0.125 * fragment:len()
266                 else
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
270                 end
271         end
272         local formspec_business_days = ""
273         for day = 1, 5, 1 do
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"]) .. "]"
282         end
283         local formspec_weekend = ""
284         for col = 1, 2, 1 do
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"]) .. "]"
294         end
295         local formspec_exception = (o.exception_today
296                         and ""
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") .. "]"
305                 )
306         local formspec = "size[18.01,7.9267895878525]"
307         .. "label[-0.14,-0.23840485478977;" .. S("Opening hours") .. "]"
308         .. formspec_business_days
309         .. formspec_weekend
310         .. formspec_exception
311         .. "label[" .. x.day1.lab .. ",5.4833116601647;" .. S("Settings") .. "]"
312         .. formspec_warning
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)
316 end
317
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
322                 create_exception()
323         end
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
329                 end
330         end
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(
336                                 0,
337                                 math.min(
338                                         60 - minute_step,
339                                         minute_step * math.floor(
340                                                 v / minute_step + 0.5
341                                         )
342                                 )
343                         )
344                 end
345         end
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_")]
349                         if start > v then
350                                 opening_hours[k] = start
351                         end
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]
359                         end
360                 end
361         end
362         if not fields.quit and not fields.close then show_gui(name) end
363 end
364
365 load_data()
366
367 minetest.register_globalstep(tick)
368 minetest.register_on_shutdown(save_data)
369 minetest.register_on_prejoinplayer(on_join)
370 minetest.register_chatcommand(
371         "opening_hours",
372         {
373                 privs = {server = true},
374                 description = S("Configure the opening hours"),
375                 func = show_gui
376         }
377 )
378 minetest.register_chatcommand(
379         "öffnungszeiten",
380         {
381                 privs = {server = true},
382                 description = S("Configure the opening hours"),
383                 func = show_gui
384         }
385 )
386 minetest.register_on_player_receive_fields(progress_gui_input)
387
388