Initial commit
This commit is contained in:
commit
9a139e44b7
22
LICENSE.md
Normal file
22
LICENSE.md
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
# The MIT License (MIT)
|
||||
|
||||
Copyright © 2019 by luk3yx.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
49
README.md
Normal file
49
README.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Minetest snippets mod
|
||||
|
||||
A way for admins to run and save lua snippets.
|
||||
|
||||
More documentation coming soon.
|
||||
|
||||
## API
|
||||
|
||||
- `snippets.register_snippet(name, <code or def>)`: Registers a snippet.
|
||||
`def` can be a table containing `code` (or `func`), and optionally `owner`.
|
||||
If `persistent` is specified, this snippet will remain registered across
|
||||
reboots.
|
||||
- `snippets.unregister_snippet(name)`: The opposite of
|
||||
`snippets.register_snippet`.
|
||||
- `snippets.registered_snippets`: A table containing the above snippets.
|
||||
- `snippets.log(level, msg)`: For use inside snippets: Logs a message. `level`
|
||||
can be `none`, `debug`, `info`, `warning`, or `error`.
|
||||
- `snippets.register_on_log(function(snippet, level, msg))`: Run when
|
||||
snippets.log is called. `snippet` is the name of the snippet. Newest
|
||||
functions are called first. If a callback returns `true`, any remaining
|
||||
functions are not called (including the built-in log function). Callbacks
|
||||
can check what player (if any) owns a snippet with
|
||||
`snippets.registered_snippets[snippet].owner`.
|
||||
- `snippets.log_levels`: A table containing functions that run
|
||||
`minetest.colorize` on log levels (if applicable).
|
||||
Example: `snippets.log_levels.error('Hello')` →
|
||||
`minetest.colorize('red', 'Hello')`
|
||||
- `snippets.exec_as_player(player_or_name, code)`: Executes `code` (a string)
|
||||
inside an "anonymous snippet" owned by the player.
|
||||
- `snippets.exec(code)`: Executes `code` inside a generic snippet.
|
||||
- `snippets.run(name, ...)`: Executes a snippet.
|
||||
|
||||
## Example snippets
|
||||
|
||||
`get_connected_names`:
|
||||
```lua
|
||||
local res = {}
|
||||
for _, player in ipairs(minetest.get_connected_players()) do
|
||||
table.insert(res, player:get_player_name())
|
||||
end
|
||||
return res
|
||||
```
|
||||
|
||||
`greeting_test`:
|
||||
```lua
|
||||
for _, name in ipairs(snippets.run 'get_connected_names') do
|
||||
minetest.chat_send_player(name, 'Hello ' .. name .. '!')
|
||||
end
|
||||
```
|
238
console.lua
Normal file
238
console.lua
Normal file
@ -0,0 +1,238 @@
|
||||
--
|
||||
-- Snippet console - Allows players to create and edit persistent snippets
|
||||
--
|
||||
|
||||
local snippet_list = {}
|
||||
local selected_snippet = {}
|
||||
local console_code = {}
|
||||
local console_text = {}
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
local name = player:get_player_name()
|
||||
if snippet_list[name] then
|
||||
snippet_list[name] = nil
|
||||
selected_snippet[name] = nil
|
||||
console_code[name] = nil
|
||||
console_text[name] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
function snippets.show_console(name)
|
||||
local formspec = 'size[14,10]' ..
|
||||
'label[0,0;My snippets]' ..
|
||||
'textlist[0,0.5;3.5,7.4;snippetlist;#aaaaaaNew snippet'
|
||||
|
||||
snippet_list[name] = {}
|
||||
for k, v in pairs(snippets.registered_snippets) do
|
||||
if v.persistent then
|
||||
table.insert(snippet_list[name], k)
|
||||
end
|
||||
end
|
||||
table.sort(snippet_list[name])
|
||||
|
||||
local selected = 0
|
||||
local unsaved = false
|
||||
for id, snippet in ipairs(snippet_list[name]) do
|
||||
formspec = formspec .. ',##' .. minetest.formspec_escape(snippet)
|
||||
if snippet == selected_snippet[name] then
|
||||
selected = id
|
||||
local def = snippets.registered_snippets[snippet]
|
||||
if (def and def.code or '') ~= console_code[name] then
|
||||
formspec = formspec .. ' (unsaved)'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
formspec = formspec .. ';' .. tostring(selected + 1) .. ']' ..
|
||||
'button[0,8.1;3.7,0.75;save;Save]' ..
|
||||
'button[0,8.85;3.7,0.75;save_as;Save as]' ..
|
||||
'button_exit[0,9.6;3.7,0.75;quit;Quit]'
|
||||
|
||||
formspec = formspec ..
|
||||
'textlist[3.9,6.01;10,4.04;ignore;'
|
||||
if console_text[name] then
|
||||
if #console_text[name] > 0 then
|
||||
for id, msg in ipairs(console_text[name]) do
|
||||
if id > 1 then formspec = formspec .. ',' end
|
||||
formspec = formspec .. minetest.formspec_escape(msg)
|
||||
end
|
||||
formspec = formspec .. ',;' .. (#console_text[name] + 1)
|
||||
else
|
||||
formspec = formspec .. ';1'
|
||||
end
|
||||
formspec = formspec ..
|
||||
']button[3.9,5.14;10.21,0.81;reset;Reset]' ..
|
||||
'box[3.9,0.4;10,4.5;#ffffff]'
|
||||
else
|
||||
formspec = formspec .. ';1]' ..
|
||||
'button[3.9,5.14;10.21,0.81;run;Run]'
|
||||
end
|
||||
|
||||
if not console_code[name] then console_code[name] = '' end
|
||||
local code = minetest.formspec_escape(console_code[name])
|
||||
if code == '' and console_text[name] then code = '(no code)' end
|
||||
|
||||
local snippet, owner
|
||||
if selected_snippet[name] then
|
||||
snippet = minetest.colorize('#aaa', selected_snippet[name])
|
||||
else
|
||||
snippet = minetest.colorize('#888', 'New snippet')
|
||||
end
|
||||
|
||||
local def = snippets.registered_snippets[selected_snippet[name]]
|
||||
if def and def.owner then
|
||||
owner = minetest.colorize('#aaa', def.owner)
|
||||
elseif selected_snippet[name] then
|
||||
owner = minetest.colorize('#888', 'none')
|
||||
else
|
||||
owner = minetest.colorize('#aaa', name)
|
||||
end
|
||||
|
||||
formspec = formspec .. ']textarea[4.2,0.4;10.2,5.31;' ..
|
||||
(console_text[name] and '' or 'code') .. ';Snippet: ' ..
|
||||
minetest.formspec_escape(snippet .. ', owner: ' .. owner) .. ';' ..
|
||||
code .. ']'
|
||||
|
||||
minetest.show_formspec(name, 'snippets:console', formspec)
|
||||
end
|
||||
|
||||
function snippets.push_console_msg(name, msg, col)
|
||||
if not col or col:sub(1, 1) ~= '#' or #col ~= 7 then
|
||||
col = '##'
|
||||
end
|
||||
|
||||
if console_text[name] then
|
||||
table.insert(console_text[name], col .. tostring(msg))
|
||||
snippets.show_console(name)
|
||||
end
|
||||
end
|
||||
|
||||
snippets.register_on_log(function(snippet, level, msg)
|
||||
local owner = snippets.registered_snippets[snippet].owner
|
||||
if not owner or not console_text[owner] then return end
|
||||
if level ~= 'none' then
|
||||
msg = level:sub(1, 1):upper() .. level:sub(2) .. ': ' .. msg
|
||||
end
|
||||
|
||||
local col
|
||||
if level == 'warning' then
|
||||
col = '#FFFF00'
|
||||
elseif level == 'error' then
|
||||
col = '#FF0000'
|
||||
elseif level == 'debug' then
|
||||
col = '#888888'
|
||||
end
|
||||
|
||||
local p = snippet:sub(1, 16) == 'snippets:player_'
|
||||
if not p then msg = 'From snippet "' .. snippet .. '": ' .. msg end
|
||||
|
||||
snippets.push_console_msg(owner, msg, col)
|
||||
|
||||
if p then return true end
|
||||
end)
|
||||
|
||||
minetest.register_chatcommand('snippets', {
|
||||
description = 'Opens the snippets console.',
|
||||
privs = {server=true},
|
||||
func = function(name, param)
|
||||
snippets.show_console(name)
|
||||
return true, 'Opened the snippets console.'
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= 'snippets:console' and
|
||||
formname ~= 'snippets:console_save_as' then
|
||||
return
|
||||
end
|
||||
local name = player:get_player_name()
|
||||
|
||||
-- Sanity check
|
||||
if not minetest.check_player_privs(name, 'server') then
|
||||
if console_text[name] then
|
||||
console_text[name] = nil
|
||||
minetest.close_formspec(name, 'snippets:console')
|
||||
elseif not fields.quit then
|
||||
minetest.kick_player(name,
|
||||
'You appear to be using a "hacked" client.')
|
||||
end
|
||||
return
|
||||
elseif not console_code[name] then
|
||||
return
|
||||
end
|
||||
|
||||
-- Handle "Save as"
|
||||
if formname == 'snippets:console_save_as' then
|
||||
if not fields.filename or fields.filename == '' then
|
||||
minetest.chat_send_player(name, 'Save operation cancelled.')
|
||||
snippets.show_console(name)
|
||||
return
|
||||
end
|
||||
|
||||
-- Don't overwrite non-persistent snippets
|
||||
local filename = fields.filename:gsub(':', '/')
|
||||
while snippets.registered_snippets[filename] and
|
||||
not snippets.registered_snippets[filename].persistent do
|
||||
filename = filename .. '_'
|
||||
end
|
||||
|
||||
-- Actually save it
|
||||
snippets.register_snippet(filename, {
|
||||
owner = name,
|
||||
code = console_code[name],
|
||||
persistent = true,
|
||||
})
|
||||
|
||||
selected_snippet[name] = filename
|
||||
snippets.show_console(name)
|
||||
return
|
||||
end
|
||||
|
||||
if fields.code then console_code[name] = fields.code end
|
||||
|
||||
if fields.ignore then
|
||||
return
|
||||
elseif fields.run then
|
||||
local code = fields.code
|
||||
console_text[name] = {}
|
||||
snippets.show_console(name)
|
||||
if not code or code == '' then return end
|
||||
local good, msg = loadstring('return ' .. code)
|
||||
if good then code = 'return ' .. code end
|
||||
local res = snippets.exec_as_player(name, code)
|
||||
if res ~= nil then
|
||||
snippets.push_console_msg(name, res)
|
||||
end
|
||||
elseif fields.reset then
|
||||
console_text[name] = nil
|
||||
snippets.show_console(name)
|
||||
elseif fields.snippetlist and snippet_list[name] then
|
||||
local event = minetest.explode_textlist_event(fields.snippetlist)
|
||||
local selected = snippet_list[name][event.index - 1]
|
||||
if selected_snippet[name] == selected then return end
|
||||
selected_snippet[name] = selected
|
||||
if console_text[name] then console_text[name] = nil end
|
||||
local def = snippets.registered_snippets[selected]
|
||||
console_code[name] = def and def.code or ''
|
||||
snippets.show_console(name)
|
||||
elseif fields.save and selected_snippet[name] then
|
||||
if console_code[name] == '' then
|
||||
snippets.unregister_snippet(selected_snippet[name])
|
||||
selected_snippet[name] = nil
|
||||
else
|
||||
snippets.register_snippet(selected_snippet[name], {
|
||||
owner = name,
|
||||
code = console_code[name],
|
||||
persistent = true,
|
||||
})
|
||||
end
|
||||
snippets.show_console(name)
|
||||
elseif fields.save or fields.save_as and console_code[name] ~= '' then
|
||||
console_text[name] = nil
|
||||
minetest.show_formspec(name, 'snippets:console_save_as',
|
||||
'field[filename;Please enter a new snippet name.;]')
|
||||
elseif fields.quit then
|
||||
-- console_code[name] = nil
|
||||
console_text[name] = nil
|
||||
end
|
||||
end)
|
268
core.lua
Normal file
268
core.lua
Normal file
@ -0,0 +1,268 @@
|
||||
--
|
||||
-- Minetest snippets mod: Attempt to prevent snippets from crashing the server
|
||||
--
|
||||
|
||||
-- Make loadstring a local variable
|
||||
local loadstring
|
||||
if minetest.global_exists('loadstring') then
|
||||
loadstring = _G.loadstring
|
||||
else
|
||||
loadstring = assert(load)
|
||||
end
|
||||
|
||||
local copy = table.copy
|
||||
local safe_funcs = {}
|
||||
local orig_funcs, running_snippet
|
||||
|
||||
function snippets.get_current_snippet()
|
||||
if running_snippet then return copy(running_snippet) end
|
||||
end
|
||||
|
||||
-- Apply "safe functions": These wrap normal registration functions so that
|
||||
-- snippets can't crash them as easily.
|
||||
local function apply_safe_funcs()
|
||||
if orig_funcs then return end
|
||||
orig_funcs = {}
|
||||
for k, v in pairs(safe_funcs) do
|
||||
if k ~= 'print' then
|
||||
orig_funcs[k] = minetest[k]
|
||||
minetest[k] = v
|
||||
end
|
||||
end
|
||||
orig_funcs.print, print = print, safe_funcs.print
|
||||
end
|
||||
|
||||
local function remove_safe_funcs()
|
||||
if not orig_funcs then return end
|
||||
for k, v in pairs(orig_funcs) do
|
||||
minetest[k] = orig_funcs[k]
|
||||
end
|
||||
print = orig_funcs.print
|
||||
orig_funcs = nil
|
||||
end
|
||||
|
||||
-- "Break out" of wrapped functions.
|
||||
local function wrap_unsafe(func)
|
||||
return function(...)
|
||||
if orig_funcs then
|
||||
remove_safe_funcs()
|
||||
local res = {func(...)}
|
||||
apply_safe_funcs()
|
||||
return (table.unpack or unpack)(res)
|
||||
else
|
||||
return func(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Logging
|
||||
snippets.registered_on_log = {}
|
||||
snippets.log_levels = {}
|
||||
function snippets.log_levels.error(n)
|
||||
return minetest.colorize('red', n)
|
||||
end
|
||||
function snippets.log_levels.warning(n)
|
||||
return minetest.colorize('yellow', n)
|
||||
end
|
||||
function snippets.log_levels.info(n)
|
||||
return n
|
||||
end
|
||||
snippets.log_levels.none = snippets.log_levels.info
|
||||
function snippets.log_levels.debug(n)
|
||||
return minetest.colorize('grey', n)
|
||||
end
|
||||
|
||||
function snippets.log(level, msg)
|
||||
local snippet = running_snippet or 'snippets:anonymous'
|
||||
if msg == nil then level, msg = 'none', level end
|
||||
level, msg = tostring(level), tostring(msg)
|
||||
|
||||
if level == 'warn' then
|
||||
level = 'warning'
|
||||
elseif not snippets.log_levels[level] then
|
||||
level = 'none'
|
||||
end
|
||||
|
||||
for _, func in ipairs(snippets.registered_on_log) do
|
||||
if func(snippet, level, msg) then return end
|
||||
end
|
||||
end
|
||||
snippets.log = wrap_unsafe(snippets.log)
|
||||
|
||||
function snippets.register_on_log(func)
|
||||
assert(type(func) == 'function')
|
||||
table.insert(snippets.registered_on_log, 1, func)
|
||||
end
|
||||
|
||||
-- Create the default log action
|
||||
-- Only notify the player of errors or warnings
|
||||
snippets.register_on_log(function(snippet, level, msg)
|
||||
local rawmsg
|
||||
if level == 'warning' then
|
||||
rawmsg = 'Warning'
|
||||
elseif level == 'error' then
|
||||
rawmsg = 'Error'
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
rawmsg = snippets.log_levels[level](rawmsg .. ' in snippet "' .. snippet ..
|
||||
'": ' .. msg)
|
||||
|
||||
local def = snippets.registered_snippets[snippet]
|
||||
if def and def.owner then
|
||||
minetest.chat_send_player(def.owner, rawmsg)
|
||||
else
|
||||
minetest.chat_send_all(rawmsg)
|
||||
end
|
||||
end)
|
||||
|
||||
-- Create a safe print()
|
||||
function safe_funcs.print(...)
|
||||
local msg = ''
|
||||
for i = 1, select('#', ...) do
|
||||
if i > 1 then msg = msg .. '\t' end
|
||||
msg = msg .. tostring(select(i, ...))
|
||||
end
|
||||
snippets.log('none', msg)
|
||||
end
|
||||
|
||||
-- Mostly copied from https://stackoverflow.com/a/26367080
|
||||
local function wrap_raw(snippet, func, ...)
|
||||
local old_running = running_snippet
|
||||
running_snippet = snippet
|
||||
local use_safe_funcs = not orig_funcs
|
||||
if use_safe_funcs then apply_safe_funcs() end
|
||||
local good, msg = pcall(func, ...)
|
||||
if use_safe_funcs then remove_safe_funcs() end
|
||||
if good then
|
||||
running_snippet = old_running
|
||||
return msg
|
||||
else
|
||||
snippets.log('error', msg)
|
||||
running_snippet = old_running
|
||||
end
|
||||
end
|
||||
|
||||
local function wrap(snippet, func)
|
||||
if not snippet then return func end
|
||||
return function(...) return wrap_raw(snippet, func, ...) end
|
||||
end
|
||||
|
||||
do
|
||||
local after_ = minetest.after
|
||||
function safe_funcs.after(after, func, ...)
|
||||
after = tonumber(after)
|
||||
assert(after and after == after, 'Invalid core.ater invocation')
|
||||
after_(after, wrap_raw, running_snippet, func, ...)
|
||||
end
|
||||
|
||||
function snippets.wrap_register_on(orig)
|
||||
return function(func, ...)
|
||||
return orig(wrap(running_snippet, func), ...)
|
||||
end
|
||||
end
|
||||
|
||||
for k, v in pairs(minetest) do
|
||||
if type(k) == 'string' and k:sub(1, 12) == 'register_on_' then
|
||||
safe_funcs[k] = snippets.wrap_register_on(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Register a snippet
|
||||
snippets.registered_snippets = {}
|
||||
function snippets.register_snippet(name, def)
|
||||
if def == nil and type(name) == 'table' then
|
||||
name, def = name.name, name
|
||||
elseif type(name) ~= 'string' then
|
||||
error('Invalid name passed to snippets.register_snippet!', 2)
|
||||
elseif type(def) == 'string' then
|
||||
def = {code=def}
|
||||
elseif type(def) ~= 'table' then
|
||||
error('Invalid definition passed to snippets.register_snippet!', 2)
|
||||
elseif def.owner and type(def.owner) ~= 'string' then
|
||||
error('Invalid owner passed to snippets.register_snippet!', 2)
|
||||
end
|
||||
def = table.copy(def)
|
||||
def.name = name
|
||||
|
||||
if def.code then
|
||||
local msg
|
||||
def.func, msg = loadstring(def.code, name)
|
||||
if def.func then
|
||||
if name ~= 'snippets:anonymous' then
|
||||
local old_def = snippets.registered_snippets[name]
|
||||
def.env = old_def and old_def.env
|
||||
end
|
||||
if not def.env then
|
||||
local g = {}
|
||||
def.env = setmetatable({}, {__index = function(self, key)
|
||||
local res = rawget(_G, key)
|
||||
if res == nil and not g[key] then
|
||||
snippets.log('warning', 'Undeclared global variable "'
|
||||
.. key .. '" accessed.')
|
||||
g[key] = true
|
||||
end
|
||||
return res
|
||||
end})
|
||||
end
|
||||
setfenv(def.func, def.env)
|
||||
else
|
||||
local r, s = running_snippet, snippets.registered_snippets[name]
|
||||
function def.func() end
|
||||
running_snippet, snippets.registered_snippets[name] = name, def
|
||||
snippets.log('error', 'Load error: ' .. tostring(msg))
|
||||
running_snippet, snippets.registered_snippets[name] = r, s
|
||||
end
|
||||
else
|
||||
def.persistent = nil
|
||||
end
|
||||
if not def.persistent then def.code = nil end
|
||||
if type(def.func) ~= 'function' then return false end
|
||||
|
||||
snippets.registered_snippets[name] = def
|
||||
return true
|
||||
end
|
||||
snippets.register_snippet('snippets:anonymous', '')
|
||||
|
||||
-- Run a snippet
|
||||
function snippets.run(snippet, ...)
|
||||
local def = snippets.registered_snippets[snippet]
|
||||
if not def then error('Invalid snippet specified!', 2) end
|
||||
return wrap_raw(snippet, def.func, ...)
|
||||
end
|
||||
|
||||
-- Run code as player
|
||||
function snippets.exec_as_player(name, code)
|
||||
if minetest.is_player(name) then name = name:get_player_name() end
|
||||
local owner
|
||||
if name and name ~= '' then
|
||||
owner = name
|
||||
name = 'snippets:player_' .. tostring(name)
|
||||
else
|
||||
name = 'snippets:anonymous'
|
||||
end
|
||||
|
||||
local def = {
|
||||
code = tostring(code),
|
||||
owner = owner,
|
||||
}
|
||||
if not snippets.register_snippet(name, def) then return end
|
||||
|
||||
return snippets.run(name)
|
||||
end
|
||||
|
||||
function snippets.exec(code) return snippets.exec_as_player(nil, code) end
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
snippets.registered_snippets['snippets:player_' ..
|
||||
player:get_player_name()] = nil
|
||||
end)
|
||||
|
||||
-- In case console.lua isn't loaded
|
||||
function snippets.unregister_snippet(name)
|
||||
if snippets.registered_snippets[name] ~= nil then
|
||||
snippets.registered_snippets[name] = nil
|
||||
end
|
||||
end
|
20
init.lua
Normal file
20
init.lua
Normal file
@ -0,0 +1,20 @@
|
||||
--
|
||||
-- Minetest snippets mod: Allows admins to run a bunch of predefined snippets
|
||||
--
|
||||
|
||||
assert(minetest.get_current_modname() == 'snippets')
|
||||
snippets = {}
|
||||
|
||||
local modpath = minetest.get_modpath('snippets')
|
||||
|
||||
-- Load the core sandbox
|
||||
dofile(modpath .. '/core.lua')
|
||||
|
||||
-- Load persistence
|
||||
loadfile(modpath .. '/persistence.lua')(minetest.get_mod_storage())
|
||||
|
||||
-- Load the "console"
|
||||
dofile(modpath .. '/console.lua')
|
||||
|
||||
-- Load "snippet buttons"
|
||||
dofile(modpath .. '/nodes.lua')
|
49
nodes.lua
Normal file
49
nodes.lua
Normal file
@ -0,0 +1,49 @@
|
||||
--
|
||||
-- Buttons that run snippets
|
||||
--
|
||||
|
||||
minetest.register_node('snippets:button', {
|
||||
description = 'Snippets button',
|
||||
tiles = {'default_steel_block.png', 'default_steel_block.png',
|
||||
'default_steel_block.png^snippets_button.png'},
|
||||
groups = {cracky = 2},
|
||||
|
||||
on_construct = function(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string('infotext', 'Unconfigured snippets button')
|
||||
meta:set_string('formspec', 'field[snippet;Snippet to run:;]')
|
||||
end,
|
||||
|
||||
on_receive_fields = function(pos, formname, fields, sender)
|
||||
if not fields.snippet or fields.snippet == '' then return end
|
||||
|
||||
local name = sender:get_player_name()
|
||||
if not minetest.check_player_privs(name, {server=true}) then
|
||||
minetest.chat_send_player(name, 'Insufficient privileges!')
|
||||
return
|
||||
end
|
||||
|
||||
local snippet = fields.snippet
|
||||
if not snippets.registered_snippets[snippet] or
|
||||
snippet:sub(1, 9) == 'snippets:' then
|
||||
minetest.chat_send_player(name, 'Unknown snippet!')
|
||||
else
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string('snippet', snippet)
|
||||
meta:set_string('infotext', 'Snippet: ' .. fields.snippet)
|
||||
meta:set_string('formspec', '')
|
||||
end
|
||||
end,
|
||||
|
||||
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
|
||||
local meta, name = minetest.get_meta(pos), clicker:get_player_name()
|
||||
local snippet = meta:get_string('snippet')
|
||||
if not snippet or snippet == '' then return end
|
||||
if snippets.registered_snippets[snippet] then
|
||||
snippets.run(snippet, name)
|
||||
else
|
||||
minetest.chat_send_player(name, 'Invalid snippet: "' .. snippet ..
|
||||
'"')
|
||||
end
|
||||
end,
|
||||
})
|
56
persistence.lua
Normal file
56
persistence.lua
Normal file
@ -0,0 +1,56 @@
|
||||
--
|
||||
-- Persistent snippets
|
||||
--
|
||||
|
||||
-- Get storage
|
||||
local storage = ...
|
||||
assert(storage)
|
||||
|
||||
-- Load persistent snippets
|
||||
local register_snippet_raw = snippets.register_snippet
|
||||
do
|
||||
for name, def in pairs(storage:to_table().fields) do
|
||||
if name:sub(1, 1) == '>' then
|
||||
def = minetest.deserialize(def)
|
||||
if def then
|
||||
def.persistent = true
|
||||
register_snippet_raw(name:sub(2), def)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Override snippets.register_snippet so it accepts the "persistent" field.
|
||||
function snippets.register_snippet(name, def)
|
||||
if def == nil and type(name) == 'table' then
|
||||
name, def = name.name, name
|
||||
end
|
||||
|
||||
-- Fix tracebacks
|
||||
local good, msg = pcall(register_snippet_raw, name, def)
|
||||
if not good then error(msg, 2) end
|
||||
if not msg then return msg end
|
||||
|
||||
-- Check for def.persistent
|
||||
def = snippets.registered_snippets[name]
|
||||
if type(def) == 'table' and def.persistent and def.code then
|
||||
print('Saving snippet', name)
|
||||
storage:set_string('>' .. name, minetest.serialize({
|
||||
code = def.code,
|
||||
owner = def.owner,
|
||||
}))
|
||||
end
|
||||
|
||||
-- Return the same value as register_snippet_raw.
|
||||
return msg
|
||||
end
|
||||
|
||||
-- Override snippets.unregister_snippet
|
||||
local unregister_snippet_raw = snippets.unregister_snippet
|
||||
function snippets.unregister_snippet(name)
|
||||
local def = snippets.registered_snippets[name]
|
||||
if def and def.persistent then
|
||||
storage:set_string('>' .. name, '')
|
||||
end
|
||||
return unregister_snippet_raw(name)
|
||||
end
|
BIN
textures/snippets_button.png
Normal file
BIN
textures/snippets_button.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 211 B |
Loading…
x
Reference in New Issue
Block a user