Герхард PICCORO Lenz McKAY
cfb83aab12
* using https://codeberg.org/minenux/minetest-mod-formspecs * using https://codeberg.org/minenux/minetest-mod-auth_rx * changes: ** provide a way to initialize files if there is not one currently doe snot touch the auth.txt file neither converted ** solves: close: https://codeberg.org/minenux/minetest-mod-auth_rx/issues/6 ** solved: close: https://bitbucket.org/sorcerykid/auth_rx/issues/7 ** init the files when are fresh install, still do not convert from auth.txt ** player object check for problematic joins on inpcomplete auth process ** close fixed https://codeberg.org/minenux/minetest-mod-auth_rx/issues/2 ** added missing depends formspecs (it work without in basics but, some commands needs) ** we will later aded formspecs checks to made optional
510 lines
16 KiB
Lua
510 lines
16 KiB
Lua
--------------------------------------------------------
|
|
-- Minetest :: ActiveFormspecs Mod v2.6 (formspecs)
|
|
--
|
|
-- See README.txt for licensing and release notes.
|
|
-- Copyright (c) 2016-2019, Leslie Ellen Krause
|
|
--
|
|
-- ./games/just_test_tribute/mods/formspecs/init.lua
|
|
--------------------------------------------------------
|
|
|
|
print( "Loading ActiveFormspecs Mod" )
|
|
|
|
minetest.FORMSPEC_SIGEXIT = "true" -- player clicked exit button or pressed esc key (boolean for backward compatibility)
|
|
minetest.FORMSPEC_SIGQUIT = 1 -- player logged off
|
|
minetest.FORMSPEC_SIGKILL = 2 -- player was killed
|
|
minetest.FORMSPEC_SIGTERM = 3 -- server is shutting down
|
|
minetest.FORMSPEC_SIGPROC = 4 -- procedural closure
|
|
minetest.FORMSPEC_SIGTIME = 5 -- timeout reached
|
|
minetest.FORMSPEC_SIGSTOP = 6 -- procedural closure (cannot be trapped)
|
|
minetest.FORMSPEC_SIGHOLD = 7 -- child form opened, parent is suspended
|
|
minetest.FORMSPEC_SIGCONT = 8 -- child form closed, parent can continue
|
|
|
|
local afs = { } -- obtain localized, protected namespace
|
|
|
|
afs.forms = { }
|
|
afs.timers = { }
|
|
afs.session_id = 0
|
|
afs.session_seed = math.random( 0, 65535 )
|
|
|
|
afs.stats = { active = 0, opened = 0, closed = 0 }
|
|
|
|
afs.stats.on_open = function ( self )
|
|
self.active = self.active + 1
|
|
self.opened = self.opened + 1
|
|
end
|
|
|
|
afs.stats.on_close = function ( self )
|
|
self.active = self.active - 1
|
|
self.closed = self.closed + 1
|
|
end
|
|
|
|
-----------------------------------------------------------------
|
|
-- trigger callbacks at set intervals within timer queue
|
|
-----------------------------------------------------------------
|
|
|
|
do
|
|
-- localize needed object references for efficiency
|
|
local get_us_time = minetest.get_us_time
|
|
local timers = afs.timers
|
|
local t_cur = get_us_time( )
|
|
local t_off = -t_cur
|
|
|
|
-- step monotonic clock with graceful 32-bit overflow
|
|
local step_clock = function( )
|
|
local t_new = get_us_time( )
|
|
|
|
if t_new < t_cur then
|
|
t_off = t_off + 4294967290
|
|
end
|
|
|
|
t_cur = t_new
|
|
return t_off + t_new
|
|
end
|
|
afs.get_uptime = function( )
|
|
return ( t_off + t_cur ) / 1000000
|
|
end
|
|
|
|
minetest.register_globalstep( function( dtime )
|
|
--local x = get_us_time( )
|
|
local curtime = step_clock( ) / 1000000
|
|
local idx = #timers
|
|
|
|
-- iterate through table in reverse order to allow removal
|
|
while idx > 0 do
|
|
local self = timers[ idx ]
|
|
|
|
if curtime >= self.exptime then
|
|
self.counter = self.counter + 1
|
|
self.overrun = curtime - self.exptime
|
|
self.exptime = curtime + self.form.timeout
|
|
|
|
self.form.newtime = math.floor( curtime )
|
|
self.form.on_close( self.form.meta, self.form.player, { quit = minetest.FORMSPEC_SIGTIME } )
|
|
|
|
self.overrun = 0.0
|
|
end
|
|
idx = idx - 1
|
|
end
|
|
end )
|
|
end
|
|
|
|
-----------------------------------------------------------------
|
|
-- override node registrations for attached formspecs
|
|
-----------------------------------------------------------------
|
|
|
|
local on_rightclick = function( pos, node, player )
|
|
local nodedef = minetest.registered_nodes[ node.name ]
|
|
local meta = nodedef.before_open and nodedef.before_open( pos, node, player ) or pos
|
|
local formspec = nodedef.on_open( meta, player )
|
|
|
|
if formspec then
|
|
local player_name = player:get_player_name( )
|
|
minetest.create_form( meta, player_name, formspec, nodedef.on_close )
|
|
afs.forms[ player_name ].origin = node.name
|
|
end
|
|
end
|
|
|
|
local old_register_node = minetest.register_node
|
|
local old_override_item = minetest.override_item
|
|
|
|
minetest.register_node = function ( name, def )
|
|
if def.on_open and not def.on_rightclick then
|
|
def.on_rightclick = on_rightclick
|
|
end
|
|
old_register_node( name, def )
|
|
end
|
|
|
|
minetest.override_item = function ( name, def )
|
|
if minetest.registered_nodes[ name ] and def.on_open then
|
|
def.on_rightclick = on_rightclick
|
|
end
|
|
old_override_item( name, def )
|
|
end
|
|
|
|
-----------------------------------------------------------------
|
|
-- trigger callbacks during formspec events
|
|
-----------------------------------------------------------------
|
|
|
|
minetest.register_on_player_receive_fields( function( player, formname, fields )
|
|
local player_name = player:get_player_name( )
|
|
local form = afs.forms[ player_name ]
|
|
|
|
-- perform a basic sanity check, since these shouldn't technically occur
|
|
if not form or player ~= form.player or formname ~= form.name then return end
|
|
|
|
-- handle reverse-lookups of dropdown indexes
|
|
for name, keys in pairs( form.dropdowns ) do
|
|
if fields[ name ] then
|
|
fields[ name ] = keys[ fields[ name ] ]
|
|
end
|
|
end
|
|
|
|
form.newtime = os.time( )
|
|
form.on_close( form.meta, form.player, fields )
|
|
|
|
-- end current session when closing formspec
|
|
if fields.quit then
|
|
minetest.get_form_timer( player_name ).stop( )
|
|
|
|
afs.stats:on_close( )
|
|
if form.parent_form then
|
|
-- restore previous session
|
|
form = form.parent_form
|
|
afs.forms[ player_name ] = form
|
|
|
|
-- delay a single tick to ensure formspec updates are handled by client
|
|
minetest.after( 0.0, function ( )
|
|
form.on_close( form.meta, form.player, { quit = minetest.FORMSPEC_SIGCONT } )
|
|
end )
|
|
else
|
|
afs.forms[ player_name ] = nil
|
|
end
|
|
end
|
|
end )
|
|
|
|
-----------------------------------------------------------------
|
|
-- expose timer functionality within a helper object
|
|
-----------------------------------------------------------------
|
|
|
|
minetest.get_form_timer = function ( player_name, form_name )
|
|
local self = { }
|
|
local form = afs.forms[ player_name ]
|
|
|
|
if not form or form_name and form_name ~= form.name then return end
|
|
|
|
self.start = function ( timeout )
|
|
if not form.timeout and timeout >= 0.5 then
|
|
local curtime = afs.get_uptime( )
|
|
|
|
form.timeout = timeout
|
|
table.insert( afs.timers, { form = form, counter = 0, oldtime = curtime, exptime = curtime + timeout, overrun = 0.0 } )
|
|
end
|
|
end
|
|
self.stop = function ( )
|
|
if not form.timeout then return end
|
|
|
|
form.timeout = nil
|
|
|
|
for i, v in ipairs( afs.timers ) do
|
|
if v.form == form then
|
|
table.remove( afs.timers, i )
|
|
return
|
|
end
|
|
end
|
|
end
|
|
self.get_state = function ( )
|
|
if not form.timeout then return end
|
|
|
|
for i, v in ipairs( afs.timers ) do
|
|
local curtime = afs.get_uptime( )
|
|
|
|
if v.form == form then
|
|
return { elapsed = curtime - v.oldtime, remain = v.exptime - curtime, overrun = v.overrun, counter = v.counter }
|
|
end
|
|
end
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
-----------------------------------------------------------------
|
|
-- parse specialized formspec elements and escapes codes
|
|
-----------------------------------------------------------------
|
|
|
|
local _
|
|
local function is_match( str, pat )
|
|
-- use array for captures
|
|
_ = { string.match( str, pat ) }
|
|
return #_ > 0 and _ or nil
|
|
end
|
|
|
|
local function escape( str )
|
|
return string.gsub( str, "\\.",
|
|
{ ["\\]"] = "\\x5D", ["\\["] = "\\x5B", ["\\,"] = "\\x2C", ["\\;"] = "\\x3B" } )
|
|
end
|
|
|
|
local function unescape( str, is_raw )
|
|
return string.gsub( str, "\\x..",
|
|
{ ["\\x5D"] = "\\]", ["\\x5B"] = "\\[", ["\\x2C"] = "\\,", ["\\x3B"] = "\\;" } )
|
|
end
|
|
|
|
local function unescape_raw( str, is_raw )
|
|
return string.gsub( str, "\\x..",
|
|
{ ["\\x5D"] = "]", ["\\x5B"] = "[", ["\\x2C"] = ",", ["\\x3B"] = ";" } )
|
|
end
|
|
|
|
local function parse_elements( form, formspec )
|
|
formspec = escape( formspec )
|
|
form.dropdowns = { } -- reset the dropdown lookup
|
|
|
|
-- dropdown elements can optionally return the selected
|
|
-- index rather than the value of the option itself
|
|
formspec = string.gsub( formspec, "dropdown%[(.-)%]", function( params )
|
|
if is_match( params, "^([^;]*;[^;]*;([^;]*);([^;]*);[^;]*);([^;]*)$" ) then
|
|
local prefix = _[ 1 ]
|
|
local name = _[ 2 ]
|
|
local options = _[ 3 ]
|
|
local use_index = _[ 4 ]
|
|
|
|
if use_index == "true" then
|
|
form.dropdowns[ name ] = { }
|
|
for idx, val in ipairs( string.split( options, ",", true ) ) do
|
|
form.dropdowns[ name ][ unescape_raw( val ) ] = idx -- add to reverse lookup table
|
|
end
|
|
return string.format( "dropdown[%s]", prefix )
|
|
elseif use_index == "false" or use_index == "" then
|
|
return string.format( "dropdown[%s]", prefix )
|
|
else
|
|
return "" -- strip invalid dropdown elements
|
|
end
|
|
end
|
|
return string.format( "dropdown[%s]", params )
|
|
end )
|
|
|
|
-- hidden elements only provide default, initial values
|
|
-- for state table and are always stripped afterward
|
|
formspec = string.gsub( formspec, "hidden%[(.-)%]", function( params )
|
|
if is_match( params, "^([^;]*);([^;]*)$" ) or is_match( params, "^([^;]*);([^;]*);([^;]*)$" ) then
|
|
local key = _[ 1 ]
|
|
local value = _[ 2 ]
|
|
local type = _[ 3 ]
|
|
|
|
if key ~= "" and form.meta[ key ] == nil then
|
|
-- parse according to specified data type
|
|
if type == "string" or type == "" or type == nil then
|
|
form.meta[ key ] = unescape_raw( value )
|
|
elseif type == "number" then
|
|
form.meta[ key ] = tonumber( value )
|
|
elseif type == "boolean" then
|
|
form.meta[ key ] = ( { ["1"] = true, ["0"] = false, ["true"] = true, ["false"] = false } )[ value ]
|
|
end
|
|
end
|
|
end
|
|
return "" -- strip hidden elements prior to showing formspec
|
|
end )
|
|
|
|
return unescape( formspec )
|
|
end
|
|
|
|
-----------------------------------------------------------------
|
|
-- open detached formspec with session-based state table
|
|
-----------------------------------------------------------------
|
|
|
|
minetest.create_form = function ( meta, player_name, formspec, on_close, signal )
|
|
-- short circuit whenever required params are missing
|
|
if not player_name or not formspec then return end
|
|
|
|
if type( player_name ) ~= "string" then
|
|
player_name = player_name:get_player_name( )
|
|
end
|
|
|
|
local form = afs.forms[ player_name ]
|
|
|
|
-- trigger previous callback before formspec closure
|
|
if form then
|
|
minetest.get_form_timer( player_name, form.name ).stop( )
|
|
if signal ~= minetest.FORMSPEC_SIGSTOP then
|
|
form.on_close( form.meta, form.player, { quit = signal or minetest.FORMSPEC_SIGPROC } )
|
|
end
|
|
if signal ~= minetest.FORMSPEC_SIGHOLD then
|
|
form = nil
|
|
afs.stats:on_close( )
|
|
end
|
|
end
|
|
|
|
-- start new session when opening formspec
|
|
afs.session_id = afs.session_id + 1
|
|
|
|
form = { parent_form = form }
|
|
form.id = afs.session_id
|
|
form.name = minetest.get_password_hash( player_name, afs.session_seed + afs.session_id )
|
|
form.player = minetest.get_player_by_name( player_name )
|
|
form.origin = string.match( debug.getinfo( 2 ).source, "^@.*[/\\]mods[/\\](.-)[/\\]" ) or "?"
|
|
form.on_close = on_close or function ( ) end
|
|
form.meta = meta or { }
|
|
form.oldtime = math.floor( afs.get_uptime( ) )
|
|
form.newtime = form.oldtime
|
|
|
|
afs.forms[ player_name ] = form
|
|
afs.stats:on_open( )
|
|
minetest.show_formspec( player_name, form.name, parse_elements( form, formspec ) )
|
|
|
|
return form.name
|
|
end
|
|
|
|
minetest.update_form = function ( player, formspec )
|
|
local pname = type( player ) == "string" and player or player:get_player_name( )
|
|
local form = afs.forms[ pname ]
|
|
|
|
if form then
|
|
form.oldtime = math.floor( afs.get_uptime( ) )
|
|
minetest.show_formspec( pname, form.name, parse_elements( form, formspec ) )
|
|
end
|
|
end
|
|
|
|
minetest.destroy_form = function ( player, signal )
|
|
local pname = type( player ) == "string" and player or player:get_player_name( )
|
|
local form = afs.forms[ pname ]
|
|
|
|
if form then
|
|
minetest.close_formspec( pname, form.name )
|
|
minetest.get_form_timer( pname ):stop( )
|
|
|
|
if signal ~= minetest.FORMSPEC_SIGSTOP then
|
|
form.on_close( form.meta, form.player, { quit = signal or minetest.FORMSPEC_SIGPROC } )
|
|
end
|
|
|
|
afs.stats:on_close( )
|
|
afs.forms[ pname ] = nil
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------
|
|
-- trigger callbacks after unexpected formspec closure
|
|
-----------------------------------------------------------------
|
|
|
|
minetest.register_on_leaveplayer( function( player, is_timeout )
|
|
local pname = player:get_player_name( )
|
|
local form = afs.forms[ pname ]
|
|
|
|
if form then
|
|
minetest.get_form_timer( pname, form.name ).stop( )
|
|
|
|
form.newtime = os.time( )
|
|
form.on_close( form.meta, form.player, { quit = minetest.FORMSPEC_SIGQUIT } )
|
|
|
|
afs.stats:on_close( )
|
|
afs.forms[ pname ] = nil
|
|
end
|
|
end )
|
|
|
|
minetest.register_on_dieplayer( function( player )
|
|
local pname = player:get_player_name( )
|
|
local form = afs.forms[ pname ]
|
|
|
|
if form then
|
|
minetest.get_form_timer( pname, form.name ).stop( )
|
|
|
|
form.newtime = os.time( )
|
|
form.on_close( form.meta, form.player, { quit = minetest.FORMSPEC_SIGKILL } )
|
|
|
|
afs.stats:on_close( )
|
|
afs.forms[ pname ] = nil
|
|
end
|
|
end )
|
|
|
|
minetest.register_on_shutdown( function( )
|
|
for _, form in pairs( afs.forms ) do
|
|
minetest.get_form_timer( form.player:get_player_name( ), form.name ).stop( )
|
|
|
|
form.newtime = os.time( )
|
|
form.on_close( form.meta, form.player, { quit = minetest.FORMSPEC_SIGTERM } )
|
|
|
|
afs.stats:on_close( )
|
|
end
|
|
afs.forms = { }
|
|
end )
|
|
|
|
-----------------------------------------------------------------
|
|
-- display realtime information about form sessions
|
|
-----------------------------------------------------------------
|
|
|
|
minetest.register_chatcommand( "fs", {
|
|
description = "Display realtime information about form sessions",
|
|
privs = { server = true },
|
|
func = function( pname, param )
|
|
local page_idx = 1
|
|
local page_size = 10
|
|
local sorted_forms
|
|
|
|
local get_sorted_forms = function( )
|
|
local f = { }
|
|
for k, v in pairs( afs.forms ) do
|
|
table.insert( f, v )
|
|
end
|
|
table.sort( f, function( a, b ) return a.id < b.id end )
|
|
return f
|
|
end
|
|
local get_formspec = function( )
|
|
local uptime = math.floor( afs.get_uptime( ) )
|
|
|
|
local formspec = "size[9.5,7.5]"
|
|
.. default.gui_bg
|
|
.. default.gui_bg_img
|
|
|
|
.. "label[0.1,6.7;ActiveFormspecs v2.6"
|
|
.. string.format( "label[0.1,0.0;%s]label[0.1,0.5;%d min %02d sec]",
|
|
minetest.colorize( "#888888", "uptime:" ), math.floor( uptime / 60 ), uptime % 60 )
|
|
.. string.format( "label[5.6,0.0;%s]label[5.6,0.5;%d]",
|
|
minetest.colorize( "#888888", "active" ), afs.stats.active )
|
|
.. string.format( "label[6.9,0.0;%s]label[6.9,0.5;%d]",
|
|
minetest.colorize( "#888888", "opened" ), afs.stats.opened )
|
|
.. string.format( "label[8.2,0.0;%s]label[8.2,0.5;%d]",
|
|
minetest.colorize( "#888888", "closed" ), afs.stats.closed )
|
|
|
|
.. 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]",
|
|
minetest.colorize( "#888888", "player" ),
|
|
minetest.colorize( "#888888", "origin" ),
|
|
minetest.colorize( "#888888", "idletime" ),
|
|
minetest.colorize( "#888888", "lifetime" )
|
|
)
|
|
|
|
.. "box[0,1.2;9.2,0.1;#111111]"
|
|
.. "box[0,6.2;9.2,0.1;#111111]"
|
|
|
|
local num = 0
|
|
for idx = ( page_idx - 1 ) * page_size + 1, math.min( page_idx * page_size, #sorted_forms ) do
|
|
local form = sorted_forms[ idx ]
|
|
|
|
local player_name = form.player:get_player_name( )
|
|
local lifetime = uptime - form.oldtime
|
|
local idletime = uptime - form.newtime
|
|
|
|
local vert = 2.0 + num * 0.5
|
|
|
|
formspec = formspec
|
|
.. string.format( "button[0.1,%0.1f;0.5,0.3;del:%s;x]", vert + 0.1, player_name )
|
|
.. string.format( "label[0.5,%0.1f;%s]", vert, player_name )
|
|
.. string.format( "label[3.5,%0.1f;%s]", vert, form.origin )
|
|
.. string.format( "label[6.9,%0.1f;%dm %02ds]", vert, math.floor( idletime / 60 ), idletime % 60 )
|
|
.. string.format( "label[8.2,%0.1f;%dm %02ds]", vert, math.floor( lifetime / 60 ), lifetime % 60 )
|
|
num = num + 1
|
|
end
|
|
|
|
formspec = formspec
|
|
.. "button[6.4,6.5;1,1;prev;<<]"
|
|
.. string.format( "label[7.4,6.7;%d of %d]", page_idx, math.max( 1, math.ceil( #sorted_forms / page_size ) ) )
|
|
.. "button[8.4,6.5;1,1;next;>>]"
|
|
|
|
return formspec
|
|
end
|
|
local on_close = function( meta, player, fields )
|
|
if fields.quit == minetest.FORMSPEC_SIGTIME then
|
|
sorted_forms = get_sorted_forms( )
|
|
minetest.update_form( pname, get_formspec( ) )
|
|
|
|
elseif fields.prev and page_idx > 1 then
|
|
page_idx = page_idx - 1
|
|
minetest.update_form( pname, get_formspec( ) )
|
|
|
|
elseif fields.next and page_idx < #sorted_forms / page_size then
|
|
page_idx = page_idx + 1
|
|
minetest.update_form( pname, get_formspec( ) )
|
|
|
|
else
|
|
local player_name = string.match( next( fields, nil ), "del:(.+)" )
|
|
if player_name and afs.forms[ player_name ] then
|
|
minetest.destroy_form( player_name )
|
|
end
|
|
end
|
|
end
|
|
|
|
sorted_forms = get_sorted_forms( )
|
|
|
|
minetest.create_form( nil, pname, get_formspec( ), on_close )
|
|
minetest.get_form_timer( pname ).start( 1 )
|
|
|
|
return true
|
|
end,
|
|
} )
|