~jadedctrl/gem-xwx-moe

~jadedctrl/gem-xwx-moe/gemujo_ludo/mods.niaj/fasado/formspecs/init.lua
 ..
0 --------------------------------------------------------
1 -- Minetest :: ActiveFormspecs Mod v2.6 (formspecs)
2 --
3 -- See README.txt for licensing and release notes.
4 -- Copyright (c) 2016-2019, Leslie Ellen Krause
5 --
6 -- ./games/just_test_tribute/mods/formspecs/init.lua
7 --------------------------------------------------------
8
9 local S = minetest.get_translator("formspecs")
10
11 print( "Loading ActiveFormspecs Mod" )
12
13 minetest.FORMSPEC_SIGEXIT = "true" -- player clicked exit button or pressed esc key (boolean for backward compatibility)
14 minetest.FORMSPEC_SIGQUIT = 1 -- player logged off
15 minetest.FORMSPEC_SIGKILL = 2 -- player was killed
16 minetest.FORMSPEC_SIGTERM = 3 -- server is shutting down
17 minetest.FORMSPEC_SIGPROC = 4 -- procedural closure
18 minetest.FORMSPEC_SIGTIME = 5 -- timeout reached
19 minetest.FORMSPEC_SIGSTOP = 6 -- procedural closure (cannot be trapped)
20 minetest.FORMSPEC_SIGHOLD = 7 -- child form opened, parent is suspended
21 minetest.FORMSPEC_SIGCONT = 8 -- child form closed, parent can continue
22
23 local afs = { } -- obtain localized, protected namespace
24
25 afs.forms = { }
26 afs.timers = { }
27 afs.session_id = 0
28 afs.session_seed = math.random( 0, 65535 )
29
30 afs.stats = { active = 0, opened = 0, closed = 0 }
31
32 afs.stats.on_open = function ( self )
33 self.active = self.active + 1
34 self.opened = self.opened + 1
35 end
36
37 afs.stats.on_close = function ( self )
38 self.active = self.active - 1
39 self.closed = self.closed + 1
40 end
41
42 -----------------------------------------------------------------
43 -- trigger callbacks at set intervals within timer queue
44 -----------------------------------------------------------------
45
46 do
47 -- localize needed object references for efficiency
48 local get_us_time = minetest.get_us_time
49 local timers = afs.timers
50 local t_cur = get_us_time( )
51 local t_off = -t_cur
52
53 -- step monotonic clock with graceful 32-bit overflow
54 local step_clock = function( )
55 local t_new = get_us_time( )
56
57 if t_new < t_cur then
58 t_off = t_off + 4294967290
59 end
60
61 t_cur = t_new
62 return t_off + t_new
63 end
64 afs.get_uptime = function( )
65 return ( t_off + t_cur ) / 1000000
66 end
67
68 minetest.register_globalstep( function( dtime )
69 --local x = get_us_time( )
70 local curtime = step_clock( ) / 1000000
71 local idx = #timers
72
73 -- iterate through table in reverse order to allow removal
74 while idx > 0 do
75 local self = timers[ idx ]
76
77 if curtime >= self.exptime then
78 self.counter = self.counter + 1
79 self.overrun = curtime - self.exptime
80 self.exptime = curtime + self.form.timeout
81
82 self.form.newtime = math.floor( curtime )
83 self.form.on_close( self.form.meta, self.form.player, { quit = minetest.FORMSPEC_SIGTIME } )
84
85 self.overrun = 0.0
86 end
87 idx = idx - 1
88 end
89 end )
90 end
91
92 -----------------------------------------------------------------
93 -- override node registrations for attached formspecs
94 -----------------------------------------------------------------
95
96 local on_rightclick = function( pos, node, player )
97 local nodedef = minetest.registered_nodes[ node.name ]
98 local meta = nodedef.before_open and nodedef.before_open( pos, node, player ) or pos
99 local formspec = nodedef.on_open( meta, player )
100
101 if formspec then
102 local player_name = player:get_player_name( )
103 minetest.create_form( meta, player_name, formspec, nodedef.on_close )
104 afs.forms[ player_name ].origin = node.name
105 end
106 end
107
108 local old_register_node = minetest.register_node
109 local old_override_item = minetest.override_item
110
111 minetest.register_node = function ( name, def )
112 if def.on_open and not def.on_rightclick then
113 def.on_rightclick = on_rightclick
114 end
115 old_register_node( name, def )
116 end
117
118 minetest.override_item = function ( name, def )
119 if minetest.registered_nodes[ name ] and def.on_open then
120 def.on_rightclick = on_rightclick
121 end
122 old_override_item( name, def )
123 end
124
125 -----------------------------------------------------------------
126 -- trigger callbacks during formspec events
127 -----------------------------------------------------------------
128
129 minetest.register_on_player_receive_fields( function( player, formname, fields )
130 local player_name = player:get_player_name( )
131 local form = afs.forms[ player_name ]
132
133 -- perform a basic sanity check, since these shouldn't technically occur
134 if not form or player ~= form.player or formname ~= form.name then return end
135
136 -- handle reverse-lookups of dropdown indexes
137 for name, keys in pairs( form.dropdowns ) do
138 if fields[ name ] then
139 fields[ name ] = keys[ fields[ name ] ]
140 end
141 end
142
143 form.newtime = os.time( )
144 form.on_close( form.meta, form.player, fields )
145
146 -- end current session when closing formspec
147 if fields.quit then
148 minetest.get_form_timer( player_name ).stop( )
149
150 afs.stats:on_close( )
151 if form.parent_form then
152 -- restore previous session
153 form = form.parent_form
154 afs.forms[ player_name ] = form
155
156 -- delay a single tick to ensure formspec updates are handled by client
157 minetest.after( 0.0, function ( )
158 form.on_close( form.meta, form.player, { quit = minetest.FORMSPEC_SIGCONT } )
159 end )
160 else
161 afs.forms[ player_name ] = nil
162 end
163 end
164 end )
165
166 -----------------------------------------------------------------
167 -- expose timer functionality within a helper object
168 -----------------------------------------------------------------
169
170 minetest.get_form_timer = function ( player_name, form_name )
171 local self = { }
172 local form = afs.forms[ player_name ]
173
174 if not form or form_name and form_name ~= form.name then return end
175
176 self.start = function ( timeout )
177 if not form.timeout and timeout >= 0.5 then
178 local curtime = afs.get_uptime( )
179
180 form.timeout = timeout
181 table.insert( afs.timers, { form = form, counter = 0, oldtime = curtime, exptime = curtime + timeout, overrun = 0.0 } )
182 end
183 end
184 self.stop = function ( )
185 if not form.timeout then return end
186
187 form.timeout = nil
188
189 for i, v in ipairs( afs.timers ) do
190 if v.form == form then
191 table.remove( afs.timers, i )
192 return
193 end
194 end
195 end
196 self.get_state = function ( )
197 if not form.timeout then return end
198
199 for i, v in ipairs( afs.timers ) do
200 local curtime = afs.get_uptime( )
201
202 if v.form == form then
203 return { elapsed = curtime - v.oldtime, remain = v.exptime - curtime, overrun = v.overrun, counter = v.counter }
204 end
205 end
206 end
207
208 return self
209 end
210
211 -----------------------------------------------------------------
212 -- parse specialized formspec elements and escapes codes
213 -----------------------------------------------------------------
214
215 local _
216 local function is_match( str, pat )
217 -- use array for captures
218 _ = { string.match( str, pat ) }
219 return #_ > 0 and _ or nil
220 end
221
222 local function escape( str )
223 return string.gsub( str, "\\.",
224 { ["\\]"] = "\\x5D", ["\\["] = "\\x5B", ["\\,"] = "\\x2C", ["\\;"] = "\\x3B" } )
225 end
226
227 local function unescape( str, is_raw )
228 return string.gsub( str, "\\x..",
229 { ["\\x5D"] = "\\]", ["\\x5B"] = "\\[", ["\\x2C"] = "\\,", ["\\x3B"] = "\\;" } )
230 end
231
232 local function unescape_raw( str, is_raw )
233 return string.gsub( str, "\\x..",
234 { ["\\x5D"] = "]", ["\\x5B"] = "[", ["\\x2C"] = ",", ["\\x3B"] = ";" } )
235 end
236
237 local function parse_elements( form, formspec )
238 formspec = escape( formspec )
239 form.dropdowns = { } -- reset the dropdown lookup
240
241 -- dropdown elements can optionally return the selected
242 -- index rather than the value of the option itself
243 formspec = string.gsub( formspec, "dropdown%[(.-)%]", function( params )
244 if is_match( params, "^([^;]*;[^;]*;([^;]*);([^;]*);[^;]*);([^;]*)$" ) then
245 local prefix = _[ 1 ]
246 local name = _[ 2 ]
247 local options = _[ 3 ]
248 local use_index = _[ 4 ]
249
250 if use_index == "true" then
251 form.dropdowns[ name ] = { }
252 for idx, val in ipairs( string.split( options, ",", true ) ) do
253 form.dropdowns[ name ][ unescape_raw( val ) ] = idx -- add to reverse lookup table
254 end
255 return string.format( "dropdown[%s]", prefix )
256 elseif use_index == "false" or use_index == "" then
257 return string.format( "dropdown[%s]", prefix )
258 else
259 return "" -- strip invalid dropdown elements
260 end
261 end
262 return string.format( "dropdown[%s]", params )
263 end )
264
265 -- hidden elements only provide default, initial values
266 -- for state table and are always stripped afterward
267 formspec = string.gsub( formspec, "hidden%[(.-)%]", function( params )
268 if is_match( params, "^([^;]*);([^;]*)$" ) or is_match( params, "^([^;]*);([^;]*);([^;]*)$" ) then
269 local key = _[ 1 ]
270 local value = _[ 2 ]
271 local type = _[ 3 ]
272
273 if key ~= "" and form.meta[ key ] == nil then
274 -- parse according to specified data type
275 if type == "string" or type == "" or type == nil then
276 form.meta[ key ] = unescape_raw( value )
277 elseif type == "number" then
278 form.meta[ key ] = tonumber( value )
279 elseif type == "boolean" then
280 form.meta[ key ] = ( { ["1"] = true, ["0"] = false, ["true"] = true, ["false"] = false } )[ value ]
281 end
282 end
283 end
284 return "" -- strip hidden elements prior to showing formspec
285 end )
286
287 return unescape( formspec )
288 end
289
290 -----------------------------------------------------------------
291 -- open detached formspec with session-based state table
292 -----------------------------------------------------------------
293
294 minetest.create_form = function ( meta, player_name, formspec, on_close, signal )
295 -- short circuit whenever required params are missing
296 if not player_name or not formspec then return end
297
298 if type( player_name ) ~= "string" then
299 player_name = player_name:get_player_name( )
300 end
301
302 local form = afs.forms[ player_name ]
303
304 -- trigger previous callback before formspec closure
305 if form then
306 minetest.get_form_timer( player_name, form.name ).stop( )
307 if signal ~= minetest.FORMSPEC_SIGSTOP then
308 form.on_close( form.meta, form.player, { quit = signal or minetest.FORMSPEC_SIGPROC } )
309 end
310 if signal ~= minetest.FORMSPEC_SIGHOLD then
311 form = nil
312 afs.stats:on_close( )
313 end
314 end
315
316 -- start new session when opening formspec
317 afs.session_id = afs.session_id + 1
318
319 form = { parent_form = form }
320 form.id = afs.session_id
321 form.name = minetest.get_password_hash( player_name, afs.session_seed + afs.session_id )
322 form.player = minetest.get_player_by_name( player_name )
323 form.origin = string.match( debug.getinfo( 2 ).source, "^@.*[/\\]mods[/\\](.-)[/\\]" ) or "?"
324 form.on_close = on_close or function ( ) end
325 form.meta = meta or { }
326 form.oldtime = math.floor( afs.get_uptime( ) )
327 form.newtime = form.oldtime
328
329 afs.forms[ player_name ] = form
330 afs.stats:on_open( )
331 minetest.show_formspec( player_name, form.name, parse_elements( form, formspec ) )
332
333 return form.name
334 end
335
336 minetest.update_form = function ( player, formspec )
337 local pname = type( player ) == "string" and player or player:get_player_name( )
338 local form = afs.forms[ pname ]
339
340 if form then
341 form.oldtime = math.floor( afs.get_uptime( ) )
342 minetest.show_formspec( pname, form.name, parse_elements( form, formspec ) )
343 end
344 end
345
346 minetest.destroy_form = function ( player, signal )
347 local pname = type( player ) == "string" and player or player:get_player_name( )
348 local form = afs.forms[ pname ]
349
350 if form then
351 minetest.close_formspec( pname, form.name )
352 minetest.get_form_timer( pname ):stop( )
353
354 if signal ~= minetest.FORMSPEC_SIGSTOP then
355 form.on_close( form.meta, form.player, { quit = signal or minetest.FORMSPEC_SIGPROC } )
356 end
357
358 afs.stats:on_close( )
359 afs.forms[ pname ] = nil
360 end
361 end
362
363 -----------------------------------------------------------------
364 -- trigger callbacks after unexpected formspec closure
365 -----------------------------------------------------------------
366
367 minetest.register_on_leaveplayer( function( player, is_timeout )
368 local pname = player:get_player_name( )
369 local form = afs.forms[ pname ]
370
371 if form then
372 minetest.get_form_timer( pname, form.name ).stop( )
373
374 form.newtime = os.time( )
375 form.on_close( form.meta, form.player, { quit = minetest.FORMSPEC_SIGQUIT } )
376
377 afs.stats:on_close( )
378 afs.forms[ pname ] = nil
379 end
380 end )
381
382 minetest.register_on_dieplayer( function( player )
383 local pname = player:get_player_name( )
384 local form = afs.forms[ pname ]
385
386 if form then
387 minetest.get_form_timer( pname, form.name ).stop( )
388
389 form.newtime = os.time( )
390 form.on_close( form.meta, form.player, { quit = minetest.FORMSPEC_SIGKILL } )
391
392 afs.stats:on_close( )
393 afs.forms[ pname ] = nil
394 end
395 end )
396
397 minetest.register_on_shutdown( function( )
398 for _, form in pairs( afs.forms ) do
399 minetest.get_form_timer( form.player:get_player_name( ), form.name ).stop( )
400
401 form.newtime = os.time( )
402 form.on_close( form.meta, form.player, { quit = minetest.FORMSPEC_SIGTERM } )
403
404 afs.stats:on_close( )
405 end
406 afs.forms = { }
407 end )
408
409 -----------------------------------------------------------------
410 -- display realtime information about form sessions
411 -----------------------------------------------------------------
412
413 minetest.register_chatcommand( "fs", {
414 description = S("Display realtime information about form sessions"),
415 privs = { server = true },
416 func = function( pname, param )
417 local page_idx = 1
418 local page_size = 10
419 local sorted_forms
420
421 local get_sorted_forms = function( )
422 local f = { }
423 for k, v in pairs( afs.forms ) do
424 table.insert( f, v )
425 end
426 table.sort( f, function( a, b ) return a.id < b.id end )
427 return f
428 end
429 local get_formspec = function( )
430 local uptime = math.floor( afs.get_uptime( ) )
431
432 local formspec = "size[9.5,7.5]"
433 .. default.gui_bg
434 .. default.gui_bg_img
435
436 .. "label[0.1,6.7;ActiveFormspecs v2.6"
437 .. string.format( "label[0.1,0.0;%s]label[0.1,0.5;" .. S("%d min") .. " " .. S("%02d sec") .. "]",
438 minetest.colorize( "#888888", S("uptime:") ), math.floor( uptime / 60 ), uptime % 60 )
439 .. string.format( "label[5.6,0.0;%s]label[5.6,0.5;%d]",
440 minetest.colorize( "#888888", S("active") ), afs.stats.active )
441 .. string.format( "label[6.9,0.0;%s]label[6.9,0.5;%d]",
442 minetest.colorize( "#888888", S("opened") ), afs.stats.opened )
443 .. string.format( "label[8.2,0.0;%s]label[8.2,0.5;%d]",
444 minetest.colorize( "#888888", S("closed") ), afs.stats.closed )
445
446 .. string.format( "label[0.5,1.5;%s]label[3.5,1.5;%s]label[6.9,1.5;%s]label[8.2,1.5;%s]",
447 minetest.colorize( "#888888", S("player") ),
448 minetest.colorize( "#888888", S("origin") ),
449 minetest.colorize( "#888888", S("idletime") ),
450 minetest.colorize( "#888888", S("lifetime") )
451 )
452
453 .. "box[0,1.2;9.2,0.1;#111111]"
454 .. "box[0,6.2;9.2,0.1;#111111]"
455
456 local num = 0
457 for idx = ( page_idx - 1 ) * page_size + 1, math.min( page_idx * page_size, #sorted_forms ) do
458 local form = sorted_forms[ idx ]
459
460 local player_name = form.player:get_player_name( )
461 local lifetime = uptime - form.oldtime
462 local idletime = uptime - form.newtime
463
464 local vert = 2.0 + num * 0.5
465
466 formspec = formspec
467 .. string.format( "button[0.1,%0.1f;0.5,0.3;del:%s;x]", vert + 0.1, player_name )
468 .. string.format( "label[0.5,%0.1f;%s]", vert, player_name )
469 .. string.format( "label[3.5,%0.1f;%s]", vert, form.origin )
470 .. string.format( "label[6.9,%0.1f;%dm %02ds]", vert, math.floor( idletime / 60 ), idletime % 60 )
471 .. string.format( "label[8.2,%0.1f;%dm %02ds]", vert, math.floor( lifetime / 60 ), lifetime % 60 )
472 num = num + 1
473 end
474
475 formspec = formspec
476 .. "button[6.4,6.5;1,1;prev;<<]"
477 .. string.format( "label[7.4,6.7;%d of %d]", page_idx, math.max( 1, math.ceil( #sorted_forms / page_size ) ) )
478 .. "button[8.4,6.5;1,1;next;>>]"
479
480 return formspec
481 end
482 local on_close = function( meta, player, fields )
483 if fields.quit == minetest.FORMSPEC_SIGTIME then
484 sorted_forms = get_sorted_forms( )
485 minetest.update_form( pname, get_formspec( ) )
486
487 elseif fields.prev and page_idx > 1 then
488 page_idx = page_idx - 1
489 minetest.update_form( pname, get_formspec( ) )
490
491 elseif fields.next and page_idx < #sorted_forms / page_size then
492 page_idx = page_idx + 1
493 minetest.update_form( pname, get_formspec( ) )
494
495 else
496 local player_name = string.match( next( fields, nil ), "del:(.+)" )
497 if player_name and afs.forms[ player_name ] then
498 minetest.destroy_form( player_name )
499 end
500 end
501 end
502
503 sorted_forms = get_sorted_forms( )
504
505 minetest.create_form( nil, pname, get_formspec( ), on_close )
506 minetest.get_form_timer( pname ).start( 1 )
507
508 return true
509 end,
510 } )