Spaces → tabs, double quotes (in some files)
This makes the code style more consistent with the Lua code built into the engine.master
parent
cd954713ab
commit
05e500857c
404
csm/init.lua
404
csm/init.lua
|
@ -24,13 +24,13 @@ local ENABLE_SPECTRE_MITIGATIONS = true
|
|||
-- Mostly copied from https://stackoverflow.com/a/26367080
|
||||
-- Don't copy metatables
|
||||
local function copy(obj, s)
|
||||
if s and s[obj] ~= nil then return s[obj] end
|
||||
if type(obj) ~= 'table' then return obj end
|
||||
s = s or {}
|
||||
local res = {}
|
||||
s[obj] = res
|
||||
for k, v in pairs(obj) do res[copy(k, s)] = copy(v, s) end
|
||||
return res
|
||||
if s and s[obj] ~= nil then return s[obj] end
|
||||
if type(obj) ~= "table" then return obj end
|
||||
s = s or {}
|
||||
local res = {}
|
||||
s[obj] = res
|
||||
for k, v in pairs(obj) do res[copy(k, s)] = copy(v, s) end
|
||||
return res
|
||||
end
|
||||
|
||||
-- Safe functions
|
||||
|
@ -38,276 +38,276 @@ local Env = {}
|
|||
local safe_funcs = {}
|
||||
|
||||
-- No getmetatable()
|
||||
if rawget(_G, 'getmetatable') then
|
||||
safe_funcs[getmetatable] = function() end
|
||||
if rawget(_G, "getmetatable") then
|
||||
safe_funcs[getmetatable] = function() end
|
||||
end
|
||||
|
||||
-- Get the current value of string.rep in case other CSMs decide to break
|
||||
do
|
||||
local rep = string.rep
|
||||
safe_funcs[string.rep] = function(str, n)
|
||||
if #str * n > 1048576 then
|
||||
error('string.rep: string length overflow', 2)
|
||||
end
|
||||
return rep(str, n)
|
||||
end
|
||||
local rep = string.rep
|
||||
safe_funcs[string.rep] = function(str, n)
|
||||
if #str * n > 1048576 then
|
||||
error("string.rep: string length overflow", 2)
|
||||
end
|
||||
return rep(str, n)
|
||||
end
|
||||
|
||||
local show_formspec = minetest.show_formspec
|
||||
safe_funcs[show_formspec] = function(formname, ...)
|
||||
if type(formname) == 'string' then
|
||||
return show_formspec('sscsm:_' .. formname, ...)
|
||||
end
|
||||
end
|
||||
local show_formspec = minetest.show_formspec
|
||||
safe_funcs[show_formspec] = function(formname, ...)
|
||||
if type(formname) == "string" then
|
||||
return show_formspec("sscsm:_" .. formname, ...)
|
||||
end
|
||||
end
|
||||
|
||||
local after = minetest.after
|
||||
safe_funcs[after] = function(n, ...)
|
||||
if type(n) == 'number' then return after(n, pcall, ...) end
|
||||
end
|
||||
local after = minetest.after
|
||||
safe_funcs[after] = function(n, ...)
|
||||
if type(n) == "number" then return after(n, pcall, ...) end
|
||||
end
|
||||
|
||||
local on_fs_input = minetest.register_on_formspec_input
|
||||
safe_funcs[on_fs_input] = function(func)
|
||||
on_fs_input(function(formname, fields)
|
||||
if formname:sub(1, 7) == 'sscsm:_' then
|
||||
pcall(func, formname:sub(8), copy(fields))
|
||||
end
|
||||
end)
|
||||
end
|
||||
local on_fs_input = minetest.register_on_formspec_input
|
||||
safe_funcs[on_fs_input] = function(func)
|
||||
on_fs_input(function(formname, fields)
|
||||
if formname:sub(1, 7) == "sscsm:_" then
|
||||
pcall(func, formname:sub(8), copy(fields))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local deserialize = minetest.deserialize
|
||||
safe_funcs[deserialize] = function(str)
|
||||
return deserialize(str, true)
|
||||
end
|
||||
local deserialize = minetest.deserialize
|
||||
safe_funcs[deserialize] = function(str)
|
||||
return deserialize(str, true)
|
||||
end
|
||||
|
||||
if ENABLE_SPECTRE_MITIGATIONS then
|
||||
local get_us_time, floor = minetest.get_us_time, math.floor
|
||||
safe_funcs[get_us_time] = function()
|
||||
return floor(get_us_time() / 100) * 100
|
||||
end
|
||||
end
|
||||
if ENABLE_SPECTRE_MITIGATIONS then
|
||||
local get_us_time, floor = minetest.get_us_time, math.floor
|
||||
safe_funcs[get_us_time] = function()
|
||||
return floor(get_us_time() / 100) * 100
|
||||
end
|
||||
end
|
||||
|
||||
local wrap = function(n)
|
||||
local orig = minetest[n] or minetest[n .. 's']
|
||||
if type(orig) == 'function' then
|
||||
return function(func)
|
||||
orig(function(...)
|
||||
local r = {pcall(func, ...)}
|
||||
if r[1] then
|
||||
table.remove(r, 1)
|
||||
return (table.unpack or unpack)(r)
|
||||
else
|
||||
minetest.log('error', '[SSCSM] ' .. tostring(r[2]))
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
local wrap = function(n)
|
||||
local orig = minetest[n] or minetest[n .. "s"]
|
||||
if type(orig) == "function" then
|
||||
return function(func)
|
||||
orig(function(...)
|
||||
local r = {pcall(func, ...)}
|
||||
if r[1] then
|
||||
table.remove(r, 1)
|
||||
return (table.unpack or unpack)(r)
|
||||
else
|
||||
minetest.log("error", "[SSCSM] " .. tostring(r[2]))
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, k in ipairs({'register_globalstep', 'register_on_death',
|
||||
'register_on_hp_modification', 'register_on_damage_taken',
|
||||
'register_on_dignode', 'register_on_punchnode',
|
||||
'register_on_placenode', 'register_on_item_use',
|
||||
'register_on_modchannel_message', 'register_on_modchannel_signal',
|
||||
'register_on_inventory_open', 'register_on_sending_chat_message',
|
||||
'register_on_receiving_chat_message'}) do
|
||||
safe_funcs[minetest[k]] = wrap(k)
|
||||
end
|
||||
for _, k in ipairs({"register_globalstep", "register_on_death",
|
||||
"register_on_hp_modification", "register_on_damage_taken",
|
||||
"register_on_dignode", "register_on_punchnode",
|
||||
"register_on_placenode", "register_on_item_use",
|
||||
"register_on_modchannel_message", "register_on_modchannel_signal",
|
||||
"register_on_inventory_open", "register_on_sending_chat_message",
|
||||
"register_on_receiving_chat_message"}) do
|
||||
safe_funcs[minetest[k]] = wrap(k)
|
||||
end
|
||||
end
|
||||
|
||||
-- Environment
|
||||
function Env.new_empty()
|
||||
local self = {_raw = {}, _seen = copy(safe_funcs)}
|
||||
self._raw['_G'] = self._raw
|
||||
return setmetatable(self, {__index = Env}) or self
|
||||
local self = {_raw = {}, _seen = copy(safe_funcs)}
|
||||
self._raw["_G"] = self._raw
|
||||
return setmetatable(self, {__index = Env}) or self
|
||||
end
|
||||
-- function Env:get(k) return self._raw[self._seen[k] or k] end
|
||||
function Env:set(k, v) self._raw[copy(k, self._seen)] = copy(v, self._seen) end
|
||||
function Env:set_copy(k, v)
|
||||
self:set(k, v)
|
||||
self._seen[k] = nil
|
||||
self._seen[v] = nil
|
||||
self:set(k, v)
|
||||
self._seen[k] = nil
|
||||
self._seen[v] = nil
|
||||
end
|
||||
function Env:add_globals(...)
|
||||
for i = 1, select('#', ...) do
|
||||
local var = select(i, ...)
|
||||
self:set(var, _G[var])
|
||||
end
|
||||
for i = 1, select("#", ...) do
|
||||
local var = select(i, ...)
|
||||
self:set(var, _G[var])
|
||||
end
|
||||
end
|
||||
function Env:update(data) for k, v in pairs(data) do self:set(k, v) end end
|
||||
function Env:del(k)
|
||||
if self._seen[k] then
|
||||
self._raw[self._seen[k]] = nil
|
||||
self._seen[k] = nil
|
||||
end
|
||||
self._raw[k] = nil
|
||||
if self._seen[k] then
|
||||
self._raw[self._seen[k]] = nil
|
||||
self._seen[k] = nil
|
||||
end
|
||||
self._raw[k] = nil
|
||||
end
|
||||
|
||||
-- function Env:copy()
|
||||
-- local new = {_seen = copy(safe_funcs)}
|
||||
-- new._raw = copy(self._raw, new._seen)
|
||||
-- return setmetatable(new, {__index = Env}) or new
|
||||
-- local new = {_seen = copy(safe_funcs)}
|
||||
-- new._raw = copy(self._raw, new._seen)
|
||||
-- return setmetatable(new, {__index = Env}) or new
|
||||
-- end
|
||||
|
||||
-- Load code into a callable function.
|
||||
function Env:loadstring(code, file)
|
||||
if code:byte(1) == 27 then return nil, 'Invalid code!' end
|
||||
local f, msg = loadstring(code, ('=%q'):format(file))
|
||||
if not f then return nil, msg end
|
||||
setfenv(f, self._raw)
|
||||
return function(...)
|
||||
local good, res = pcall(f, ...)
|
||||
if good then
|
||||
return res
|
||||
else
|
||||
minetest.log('error', '[SSCSM] ' .. tostring(res))
|
||||
end
|
||||
end
|
||||
if code:byte(1) == 27 then return nil, "Invalid code!" end
|
||||
local f, msg = loadstring(code, ("=%q"):format(file))
|
||||
if not f then return nil, msg end
|
||||
setfenv(f, self._raw)
|
||||
return function(...)
|
||||
local good, res = pcall(f, ...)
|
||||
if good then
|
||||
return res
|
||||
else
|
||||
minetest.log("error", "[SSCSM] " .. tostring(res))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Env:exec(code, file)
|
||||
local f, msg = self:loadstring(code, file)
|
||||
if not f then
|
||||
minetest.log('error', '[SSCSM] Syntax error: ' .. tostring(msg))
|
||||
return false
|
||||
end
|
||||
f()
|
||||
return true
|
||||
local f, msg = self:loadstring(code, file)
|
||||
if not f then
|
||||
minetest.log("error", "[SSCSM] Syntax error: " .. tostring(msg))
|
||||
return false
|
||||
end
|
||||
f()
|
||||
return true
|
||||
end
|
||||
|
||||
-- Create the environment
|
||||
local env = Env:new_empty()
|
||||
|
||||
-- Clone everything
|
||||
env:add_globals('assert', 'dump', 'dump2', 'error', 'ipairs', 'math',
|
||||
'next', 'pairs', 'pcall', 'select', 'setmetatable', 'string', 'table',
|
||||
'tonumber', 'tostring', 'type', 'vector', 'xpcall', '_VERSION')
|
||||
env:add_globals("assert", "dump", "dump2", "error", "ipairs", "math",
|
||||
"next", "pairs", "pcall", "select", "setmetatable", "string", "table",
|
||||
"tonumber", "tostring", "type", "vector", "xpcall", "_VERSION")
|
||||
|
||||
env:set_copy('os', {clock = os.clock, difftime = os.difftime, time = os.time})
|
||||
env:set_copy("os", {clock = os.clock, difftime = os.difftime, time = os.time})
|
||||
|
||||
-- Create a slightly locked down "minetest" table
|
||||
do
|
||||
local t = {}
|
||||
for _, k in ipairs({"add_particle", "add_particlespawner", "after",
|
||||
"clear_out_chat_queue", "colorize", "compress", "debug",
|
||||
"decode_base64", "decompress", "delete_particlespawner",
|
||||
"deserialize", "disconnect", "display_chat_message",
|
||||
"encode_base64", "explode_scrollbar_event", "explode_table_event",
|
||||
"explode_textlist_event", "find_node_near", "find_nodes_in_area",
|
||||
"find_nodes_in_area_under_air", "find_nodes_with_meta",
|
||||
"formspec_escape", "get_background_escape_sequence",
|
||||
"get_color_escape_sequence", "get_day_count", "get_item_def",
|
||||
"get_language", "get_meta", "get_node_def", "get_node_level",
|
||||
"get_node_light", "get_node_max_level", "get_node_or_nil",
|
||||
"get_player_names", "get_privilege_list", "get_server_info",
|
||||
"get_timeofday", "get_translator", "get_us_time", "get_version",
|
||||
"get_wielded_item", "gettext", "is_nan", "is_yes", "line_of_sight",
|
||||
"log", "mod_channel_join", "parse_json",
|
||||
"pointed_thing_to_face_pos", "pos_to_string", "privs_to_string",
|
||||
"raycast", "register_globalstep", "register_on_damage_taken",
|
||||
"register_on_death", "register_on_dignode",
|
||||
"register_on_formspec_input", "register_on_hp_modification",
|
||||
"register_on_inventory_open", "register_on_item_use",
|
||||
"register_on_modchannel_message", "register_on_modchannel_signal",
|
||||
"register_on_placenode", "register_on_punchnode",
|
||||
"register_on_receiving_chat_message",
|
||||
"register_on_sending_chat_message", "rgba",
|
||||
"run_server_chatcommand", "send_chat_message", "send_respawn",
|
||||
"serialize", "sha1", "show_formspec", "sound_play", "sound_stop",
|
||||
"string_to_area", "string_to_pos", "string_to_privs",
|
||||
"strip_background_colors", "strip_colors",
|
||||
"strip_foreground_colors", "translate", "wrap_text",
|
||||
"write_json"}) do
|
||||
local func = minetest[k]
|
||||
t[k] = safe_funcs[func] or func
|
||||
end
|
||||
local t = {}
|
||||
for _, k in ipairs({"add_particle", "add_particlespawner", "after",
|
||||
"clear_out_chat_queue", "colorize", "compress", "debug",
|
||||
"decode_base64", "decompress", "delete_particlespawner",
|
||||
"deserialize", "disconnect", "display_chat_message",
|
||||
"encode_base64", "explode_scrollbar_event", "explode_table_event",
|
||||
"explode_textlist_event", "find_node_near", "find_nodes_in_area",
|
||||
"find_nodes_in_area_under_air", "find_nodes_with_meta",
|
||||
"formspec_escape", "get_background_escape_sequence",
|
||||
"get_color_escape_sequence", "get_day_count", "get_item_def",
|
||||
"get_language", "get_meta", "get_node_def", "get_node_level",
|
||||
"get_node_light", "get_node_max_level", "get_node_or_nil",
|
||||
"get_player_names", "get_privilege_list", "get_server_info",
|
||||
"get_timeofday", "get_translator", "get_us_time", "get_version",
|
||||
"get_wielded_item", "gettext", "is_nan", "is_yes", "line_of_sight",
|
||||
"log", "mod_channel_join", "parse_json",
|
||||
"pointed_thing_to_face_pos", "pos_to_string", "privs_to_string",
|
||||
"raycast", "register_globalstep", "register_on_damage_taken",
|
||||
"register_on_death", "register_on_dignode",
|
||||
"register_on_formspec_input", "register_on_hp_modification",
|
||||
"register_on_inventory_open", "register_on_item_use",
|
||||
"register_on_modchannel_message", "register_on_modchannel_signal",
|
||||
"register_on_placenode", "register_on_punchnode",
|
||||
"register_on_receiving_chat_message",
|
||||
"register_on_sending_chat_message", "rgba",
|
||||
"run_server_chatcommand", "send_chat_message", "send_respawn",
|
||||
"serialize", "sha1", "show_formspec", "sound_play", "sound_stop",
|
||||
"string_to_area", "string_to_pos", "string_to_privs",
|
||||
"strip_background_colors", "strip_colors",
|
||||
"strip_foreground_colors", "translate", "wrap_text",
|
||||
"write_json"}) do
|
||||
local func = minetest[k]
|
||||
t[k] = safe_funcs[func] or func
|
||||
end
|
||||
|
||||
env:set_copy('minetest', t)
|
||||
env:set_copy("minetest", t)
|
||||
end
|
||||
|
||||
-- Add table.unpack
|
||||
if not table.unpack then
|
||||
env._raw.table.unpack = unpack
|
||||
env._raw.table.unpack = unpack
|
||||
end
|
||||
|
||||
-- Make sure copy() worked correctly
|
||||
assert(env._raw.minetest.register_on_sending_chat_message ~=
|
||||
minetest.register_on_sending_chat_message, 'Error in copy()!')
|
||||
minetest.register_on_sending_chat_message, "Error in copy()!")
|
||||
|
||||
-- SSCSM functions
|
||||
-- When calling these from an SSCSM, make sure they exist first.
|
||||
local mod_channel
|
||||
local loaded_sscsms = {}
|
||||
env:set('join_mod_channel', function()
|
||||
if not mod_channel then
|
||||
mod_channel = minetest.mod_channel_join('sscsm:exec_pipe')
|
||||
end
|
||||
env:set("join_mod_channel", function()
|
||||
if not mod_channel then
|
||||
mod_channel = minetest.mod_channel_join("sscsm:exec_pipe")
|
||||
end
|
||||
end)
|
||||
|
||||
env:set('leave_mod_channel', function()
|
||||
if mod_channel then
|
||||
mod_channel:leave()
|
||||
mod_channel = false
|
||||
end
|
||||
env:set("leave_mod_channel", function()
|
||||
if mod_channel then
|
||||
mod_channel:leave()
|
||||
mod_channel = false
|
||||
end
|
||||
end)
|
||||
|
||||
-- exec() code sent by the server.
|
||||
minetest.register_on_modchannel_message(function(channel_name, sender, message)
|
||||
if channel_name ~= 'sscsm:exec_pipe' or (sender and sender ~= '') then
|
||||
return
|
||||
end
|
||||
if channel_name ~= "sscsm:exec_pipe" or (sender and sender ~= "") then
|
||||
return
|
||||
end
|
||||
|
||||
-- The first character is currently a version code, currently 0.
|
||||
-- Do not change unless absolutely necessary.
|
||||
local version = message:sub(1, 1)
|
||||
local name, code
|
||||
if version == '0' then
|
||||
local s, e = message:find('\n')
|
||||
if not s or not e then return end
|
||||
local target = message:sub(2, s - 1)
|
||||
if target ~= minetest.localplayer:get_name() then return end
|
||||
message = message:sub(e + 1)
|
||||
s, e = message:find('\n')
|
||||
if not s or not e then return end
|
||||
name = message:sub(1, s - 1)
|
||||
code = message:sub(e + 1)
|
||||
else
|
||||
return
|
||||
end
|
||||
-- The first character is currently a version code, currently 0.
|
||||
-- Do not change unless absolutely necessary.
|
||||
local version = message:sub(1, 1)
|
||||
local name, code
|
||||
if version == "0" then
|
||||
local s, e = message:find("\n")
|
||||
if not s or not e then return end
|
||||
local target = message:sub(2, s - 1)
|
||||
if target ~= minetest.localplayer:get_name() then return end
|
||||
message = message:sub(e + 1)
|
||||
s, e = message:find("\n")
|
||||
if not s or not e then return end
|
||||
name = message:sub(1, s - 1)
|
||||
code = message:sub(e + 1)
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
-- Don't load the same SSCSM twice
|
||||
if not loaded_sscsms[name] then
|
||||
minetest.log('action', '[SSCSM] Loading ' .. name)
|
||||
loaded_sscsms[name] = true
|
||||
env:exec(code, name)
|
||||
end
|
||||
-- Don't load the same SSCSM twice
|
||||
if not loaded_sscsms[name] then
|
||||
minetest.log("action", "[SSCSM] Loading " .. name)
|
||||
loaded_sscsms[name] = true
|
||||
env:exec(code, name)
|
||||
end
|
||||
end)
|
||||
|
||||
-- Send "0" when the "sscsm:exec_pipe" channel is first joined.
|
||||
local sent_request = false
|
||||
minetest.register_on_modchannel_signal(function(channel_name, signal)
|
||||
if sent_request or channel_name ~= 'sscsm:exec_pipe' then
|
||||
return
|
||||
end
|
||||
if sent_request or channel_name ~= "sscsm:exec_pipe" then
|
||||
return
|
||||
end
|
||||
|
||||
if signal == 0 then
|
||||
env._raw.minetest.localplayer = minetest.localplayer
|
||||
env._raw.minetest.camera = minetest.camera
|
||||
env._raw.minetest.ui = copy(minetest.ui)
|
||||
mod_channel:send_all('0')
|
||||
sent_request = true
|
||||
elseif signal == 1 then
|
||||
mod_channel:leave()
|
||||
mod_channel = nil
|
||||
end
|
||||
if signal == 0 then
|
||||
env._raw.minetest.localplayer = minetest.localplayer
|
||||
env._raw.minetest.camera = minetest.camera
|
||||
env._raw.minetest.ui = copy(minetest.ui)
|
||||
mod_channel:send_all("0")
|
||||
sent_request = true
|
||||
elseif signal == 1 then
|
||||
mod_channel:leave()
|
||||
mod_channel = nil
|
||||
end
|
||||
end)
|
||||
|
||||
local function attempt_to_join_mod_channel()
|
||||
-- Wait for minetest.localplayer to become available.
|
||||
if not minetest.localplayer then
|
||||
minetest.after(0.05, attempt_to_join_mod_channel)
|
||||
return
|
||||
end
|
||||
-- Wait for minetest.localplayer to become available.
|
||||
if not minetest.localplayer then
|
||||
minetest.after(0.05, attempt_to_join_mod_channel)
|
||||
return
|
||||
end
|
||||
|
||||
-- Join the mod channel
|
||||
mod_channel = minetest.mod_channel_join('sscsm:exec_pipe')
|
||||
-- Join the mod channel
|
||||
mod_channel = minetest.mod_channel_join("sscsm:exec_pipe")
|
||||
end
|
||||
minetest.after(0, attempt_to_join_mod_channel)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
388
init.lua
388
init.lua
|
@ -20,15 +20,15 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
|
||||
-- If this is running as a CSM (improper installation), load the CSM code.
|
||||
if INIT == 'client' then
|
||||
local modpath
|
||||
if minetest.get_modpath then
|
||||
modpath = minetest.get_modpath(modname)
|
||||
else
|
||||
modpath = modname .. ':'
|
||||
end
|
||||
dofile(modpath .. 'csm/init.lua')
|
||||
return
|
||||
if INIT == "client" then
|
||||
local modpath
|
||||
if minetest.get_modpath then
|
||||
modpath = minetest.get_modpath(modname)
|
||||
else
|
||||
modpath = modname .. ":"
|
||||
end
|
||||
dofile(modpath .. "csm/init.lua")
|
||||
return
|
||||
end
|
||||
|
||||
local sscsm = {minify=true}
|
||||
|
@ -37,19 +37,19 @@ local modpath = minetest.get_modpath(modname)
|
|||
|
||||
-- Remove excess whitespace from code to allow larger files to be sent.
|
||||
if sscsm.minify then
|
||||
local f = loadfile(modpath .. '/minify.lua')
|
||||
if f then
|
||||
sscsm.minify_code = f()
|
||||
else
|
||||
minetest.log('warning', '[SSCSM] Could not load minify.lua!')
|
||||
end
|
||||
local f = loadfile(modpath .. "/minify.lua")
|
||||
if f then
|
||||
sscsm.minify_code = f()
|
||||
else
|
||||
minetest.log("warning", "[SSCSM] Could not load minify.lua!")
|
||||
end
|
||||
end
|
||||
|
||||
if not sscsm.minify_code then
|
||||
function sscsm.minify_code(code)
|
||||
assert(type(code) == 'string')
|
||||
return code
|
||||
end
|
||||
function sscsm.minify_code(code)
|
||||
assert(type(code) == "string")
|
||||
return code
|
||||
end
|
||||
end
|
||||
|
||||
-- Register code
|
||||
|
@ -59,94 +59,94 @@ local csm_order = false
|
|||
-- Recalculate the CSM loading order
|
||||
-- TODO: Make this nicer
|
||||
local function recalc_csm_order()
|
||||
local staging = {}
|
||||
local order = {':init'}
|
||||
local unsatisfied = {}
|
||||
for name, def in pairs(sscsm.registered_csms) do
|
||||
assert(name == def.name)
|
||||
if name:sub(1, 1) ~= ':' then
|
||||
if not def.depends or #def.depends == 0 then
|
||||
table.insert(staging, name)
|
||||
else
|
||||
unsatisfied[name] = {}
|
||||
for _, mod in ipairs(def.depends) do
|
||||
if mod:sub(1, 1) ~= ':' then
|
||||
unsatisfied[name][mod] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
while #staging > 0 do
|
||||
local name = staging[1]
|
||||
for name2, u in pairs(unsatisfied) do
|
||||
if u[name] then
|
||||
u[name] = nil
|
||||
if #u == 0 then
|
||||
table.insert(staging, name2)
|
||||
end
|
||||
end
|
||||
end
|
||||
local staging = {}
|
||||
local order = {":init"}
|
||||
local unsatisfied = {}
|
||||
for name, def in pairs(sscsm.registered_csms) do
|
||||
assert(name == def.name)
|
||||
if name:sub(1, 1) ~= ":" then
|
||||
if not def.depends or #def.depends == 0 then
|
||||
table.insert(staging, name)
|
||||
else
|
||||
unsatisfied[name] = {}
|
||||
for _, mod in ipairs(def.depends) do
|
||||
if mod:sub(1, 1) ~= ":" then
|
||||
unsatisfied[name][mod] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
while #staging > 0 do
|
||||
local name = staging[1]
|
||||
for name2, u in pairs(unsatisfied) do
|
||||
if u[name] then
|
||||
u[name] = nil
|
||||
if #u == 0 then
|
||||
table.insert(staging, name2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(order, name)
|
||||
table.remove(staging, 1)
|
||||
end
|
||||
table.insert(order, name)
|
||||
table.remove(staging, 1)
|
||||
end
|
||||
|
||||
for name, u in pairs(unsatisfied) do
|
||||
if next(u) then
|
||||
local msg = 'SSCSM "' .. name .. '" has unsatisfied dependencies: '
|
||||
local n = false
|
||||
for dep, _ in pairs(u) do
|
||||
if n then msg = msg .. ', ' else n = true end
|
||||
msg = msg .. '"' .. dep .. '"'
|
||||
end
|
||||
minetest.log('error', msg)
|
||||
end
|
||||
end
|
||||
for name, u in pairs(unsatisfied) do
|
||||
if next(u) then
|
||||
local msg = 'SSCSM "' .. name .. '" has unsatisfied dependencies: '
|
||||
local n = false
|
||||
for dep, _ in pairs(u) do
|
||||
if n then msg = msg .. ", " else n = true end
|
||||
msg = msg .. '"' .. dep .. '"'
|
||||
end
|
||||
minetest.log("error", msg)
|
||||
end
|
||||
end
|
||||
|
||||
-- Set csm_order
|
||||
table.insert(order, ':cleanup')
|
||||
csm_order = order
|
||||
-- Set csm_order
|
||||
table.insert(order, ":cleanup")
|
||||
csm_order = order
|
||||
end
|
||||
|
||||
-- Register SSCSMs
|
||||
local block_colon = false
|
||||
sscsm.registered_csms = {}
|
||||
function sscsm.register(def)
|
||||
-- Read files now in case MT decides to block access later.
|
||||
if not def.code and def.file then
|
||||
local f = io.open(def.file, 'rb')
|
||||
if not f then
|
||||
error('Invalid "file" parameter passed to sscsm.register_csm.', 2)
|
||||
end
|
||||
def.code = f:read('*a')
|
||||
f:close()
|
||||
def.file = nil
|
||||
end
|
||||
-- Read files now in case MT decides to block access later.
|
||||
if not def.code and def.file then
|
||||
local f = io.open(def.file, "rb")
|
||||
if not f then
|
||||
error('Invalid "file" parameter passed to sscsm.register_csm.', 2)
|
||||
end
|
||||
def.code = f:read("*a")
|
||||
f:close()
|
||||
def.file = nil
|
||||
end
|
||||
|
||||
if type(def.name) ~= 'string' or def.name:find('\n')
|
||||
or (def.name:sub(1, 1) == ':' and block_colon) then
|
||||
error('Invalid "name" parameter passed to sscsm.register_csm.', 2)
|
||||
end
|
||||
if type(def.name) ~= "string" or def.name:find("\n")
|
||||
or (def.name:sub(1, 1) == ":" and block_colon) then
|
||||
error('Invalid "name" parameter passed to sscsm.register_csm.', 2)
|
||||
end
|
||||
|
||||
if type(def.code) ~= 'string' then
|
||||
error('Invalid "code" parameter passed to sscsm.register_csm.', 2)
|
||||
end
|
||||
if type(def.code) ~= "string" then
|
||||
error('Invalid "code" parameter passed to sscsm.register_csm.', 2)
|
||||
end
|
||||
|
||||
def.code = sscsm.minify_code(def.code)
|
||||
if (#def.name + #def.code) > 65300 then
|
||||
error('The code (or name) passed to sscsm.register_csm is too large.'
|
||||
.. ' Consider refactoring your SSCSM code.', 2)
|
||||
end
|
||||
def.code = sscsm.minify_code(def.code)
|
||||
if (#def.name + #def.code) > 65300 then
|
||||
error("The code (or name) passed to sscsm.register_csm is too large."
|
||||
.. " Consider refactoring your SSCSM code.", 2)
|
||||
end
|
||||
|
||||
-- Copy the table to prevent mods from betraying our trust.
|
||||
sscsm.registered_csms[def.name] = table.copy(def)
|
||||
if csm_order then recalc_csm_order() end
|
||||
-- Copy the table to prevent mods from betraying our trust.
|
||||
sscsm.registered_csms[def.name] = table.copy(def)
|
||||
if csm_order then recalc_csm_order() end
|
||||
end
|
||||
|
||||
function sscsm.unregister(name)
|
||||
sscsm.registered_csms[name] = nil
|
||||
if csm_order then recalc_csm_order() end
|
||||
sscsm.registered_csms[name] = nil
|
||||
if csm_order then recalc_csm_order() end
|
||||
end
|
||||
|
||||
-- Recalculate the CSM order once all other mods are loaded
|
||||
|
@ -154,166 +154,166 @@ minetest.register_on_mods_loaded(recalc_csm_order)
|
|||
|
||||
-- Handle players joining
|
||||
local has_sscsms = {}
|
||||
local mod_channel = minetest.mod_channel_join('sscsm:exec_pipe')
|
||||
local mod_channel = minetest.mod_channel_join("sscsm:exec_pipe")
|
||||
minetest.register_on_modchannel_message(function(channel_name, sender, message)
|
||||
if channel_name ~= 'sscsm:exec_pipe' or not sender or
|
||||
not mod_channel:is_writeable() or message ~= '0' or
|
||||
sender:find('\n') or has_sscsms[sender] then
|
||||
return
|
||||
end
|
||||
minetest.log('action', '[SSCSM] Sending CSMs on request for ' .. sender
|
||||
.. '...')
|
||||
for _, name in ipairs(csm_order) do
|
||||
mod_channel:send_all('0' .. sender .. '\n' .. name
|
||||
.. '\n' .. sscsm.registered_csms[name].code)
|
||||
end
|
||||
if channel_name ~= "sscsm:exec_pipe" or not sender or
|
||||
not mod_channel:is_writeable() or message ~= "0" or
|
||||
sender:find("\n") or has_sscsms[sender] then
|
||||
return
|
||||
end
|
||||
minetest.log("action", "[SSCSM] Sending CSMs on request for " .. sender
|
||||
.. "...")
|
||||
for _, name in ipairs(csm_order) do
|
||||
mod_channel:send_all("0" .. sender .. "\n" .. name
|
||||
.. "\n" .. sscsm.registered_csms[name].code)
|
||||
end
|
||||
end)
|
||||
|
||||
-- Register the SSCSM "builtins"
|
||||
sscsm.register({
|
||||
name = ':init',
|
||||
file = modpath .. '/sscsm_init.lua'
|
||||
name = ":init",
|
||||
file = modpath .. "/sscsm_init.lua"
|
||||
})
|
||||
|
||||
sscsm.register({
|
||||
name = ':cleanup',
|
||||
code = 'sscsm._done_loading_()'
|
||||
name = ":cleanup",
|
||||
code = "sscsm._done_loading_()"
|
||||
})
|
||||
|
||||
block_colon = true
|
||||
|
||||
-- Set the CSM restriction flags
|
||||
local flags = tonumber(minetest.settings:get('csm_restriction_flags'))
|
||||
local flags = tonumber(minetest.settings:get("csm_restriction_flags"))
|
||||
if not flags or flags ~= flags then
|
||||
flags = 62
|
||||
flags = 62
|
||||
end
|
||||
flags = math.floor(math.max(flags, 0)) % 64
|
||||
|
||||
do
|
||||
local def = sscsm.registered_csms[':init']
|
||||
def.code = def.code:gsub('__FLAGS__', tostring(flags))
|
||||
local def = sscsm.registered_csms[":init"]
|
||||
def.code = def.code:gsub("__FLAGS__", tostring(flags))
|
||||
end
|
||||
|
||||
if math.floor(flags / 2) % 2 == 1 then
|
||||
minetest.log('warning', '[SSCSM] SSCSMs enabled, however CSMs cannot '
|
||||
.. 'send chat messages! This will prevent SSCSMs from sending '
|
||||
.. 'messages to the server.')
|
||||
sscsm.com_write_only = true
|
||||
minetest.log("warning", "[SSCSM] SSCSMs enabled, however CSMs cannot "
|
||||
.. "send chat messages! This will prevent SSCSMs from sending "
|
||||
.. "messages to the server.")
|
||||
sscsm.com_write_only = true
|
||||
else
|
||||
sscsm.com_write_only = false
|
||||
sscsm.com_write_only = false
|
||||
end
|
||||
|
||||
-- SSCSM communication
|
||||
local function validate_channel(channel)
|
||||
if type(channel) ~= 'string' then
|
||||
error('SSCSM com channels must be strings!', 3)
|
||||
end
|
||||
if channel:find('\001', nil, true) then
|
||||
error('SSCSM com channels cannot contain U+0001!', 3)
|
||||
end
|
||||
if type(channel) ~= "string" then
|
||||
error("SSCSM com channels must be strings!", 3)
|
||||
end
|
||||
if channel:find("\001", nil, true) then
|
||||
error("SSCSM com channels cannot contain U+0001!", 3)
|
||||
end
|
||||
end
|
||||
|
||||
local msgids = {}
|
||||
function sscsm.com_send(pname, channel, msg)
|
||||
if minetest.is_player(pname) then
|
||||
pname = pname:get_player_name()
|
||||
end
|
||||
validate_channel(channel)
|
||||
if type(msg) == 'string' then
|
||||
msg = '\002' .. msg
|
||||
else
|
||||
msg = assert(minetest.write_json(msg))
|
||||
end
|
||||
if minetest.is_player(pname) then
|
||||
pname = pname:get_player_name()
|
||||
end
|
||||
validate_channel(channel)
|
||||
if type(msg) == "string" then
|
||||
msg = "\002" .. msg
|
||||
else
|
||||
msg = assert(minetest.write_json(msg))
|
||||
end
|
||||
|
||||
-- Short messages can be sent all at once
|
||||
local prefix = '\001SSCSM_COM\001' .. channel .. '\001'
|
||||
if #msg < 65300 then
|
||||
minetest.chat_send_player(pname, prefix .. msg)
|
||||
return
|
||||
end
|
||||
-- Short messages can be sent all at once
|
||||
local prefix = "\001SSCSM_COM\001" .. channel .. "\001"
|
||||
if #msg < 65300 then
|
||||
minetest.chat_send_player(pname, prefix .. msg)
|
||||
return
|
||||
end
|
||||
|
||||
-- You should never send messages over 128MB to clients
|
||||
assert(#msg < 134217728)
|
||||
-- You should never send messages over 128MB to clients
|
||||
assert(#msg < 134217728)
|
||||
|
||||
-- Otherwise split the message into multiple chunks
|
||||
prefix = prefix .. '\001'
|
||||
local id = #msgids + 1
|
||||
local i = 0
|
||||
msgids[id] = true
|
||||
local total_msgs = math.ceil(#msg / 65000)
|
||||
repeat
|
||||
i = i + 1
|
||||
minetest.chat_send_player(pname, prefix .. id .. '\001' .. i ..
|
||||
'\001' .. total_msgs .. '\001' .. msg:sub(1, 65000))
|
||||
msg = msg:sub(65001)
|
||||
until msg == ""
|
||||
-- Otherwise split the message into multiple chunks
|
||||
prefix = prefix .. "\001"
|
||||
local id = #msgids + 1
|
||||
local i = 0
|
||||
msgids[id] = true
|
||||
local total_msgs = math.ceil(#msg / 65000)
|
||||
repeat
|
||||
i = i + 1
|
||||
minetest.chat_send_player(pname, prefix .. id .. "\001" .. i ..
|
||||
"\001" .. total_msgs .. "\001" .. msg:sub(1, 65000))
|
||||
msg = msg:sub(65001)
|
||||
until msg == ""
|
||||
|
||||
-- Allow the ID to be reused on the next globalstep.
|
||||
minetest.after(0, function()
|
||||
msgids[id] = nil
|
||||
end)
|
||||
-- Allow the ID to be reused on the next globalstep.
|
||||
minetest.after(0, function()
|
||||
msgids[id] = nil
|
||||
end)
|
||||
end
|
||||
|
||||
local registered_on_receive = {}
|
||||
function sscsm.register_on_com_receive(channel, func)
|
||||
if not registered_on_receive[channel] then
|
||||
registered_on_receive[channel] = {}
|
||||
end
|
||||
table.insert(registered_on_receive[channel], func)
|
||||
if not registered_on_receive[channel] then
|
||||
registered_on_receive[channel] = {}
|
||||
end
|
||||
table.insert(registered_on_receive[channel], func)
|
||||
end
|
||||
|
||||
local admin_func = minetest.registered_chatcommands['admin'].func
|
||||
minetest.override_chatcommand('admin', {
|
||||
func = function(name, param)
|
||||
local chan, msg = param:match('^\001SSCSM_COM\001([^\001]*)\001(.*)$')
|
||||
if not chan or not msg then
|
||||
return admin_func(name, param)
|
||||
end
|
||||
local admin_func = minetest.registered_chatcommands["admin"].func
|
||||
minetest.override_chatcommand("admin", {
|
||||
func = function(name, param)
|
||||
local chan, msg = param:match("^\001SSCSM_COM\001([^\001]*)\001(.*)$")
|
||||
if not chan or not msg then
|
||||
return admin_func(name, param)
|
||||
end
|
||||
|
||||
-- Get the callbacks
|
||||
local callbacks = registered_on_receive[chan]
|
||||
if not callbacks then return end
|
||||
-- Get the callbacks
|
||||
local callbacks = registered_on_receive[chan]
|
||||
if not callbacks then return end
|
||||
|
||||
-- Load the message
|
||||
if msg:sub(1, 1) == '\002' then
|
||||
msg = msg:sub(2)
|
||||
else
|
||||
msg = minetest.parse_json(msg)
|
||||
end
|
||||
-- Load the message
|
||||
if msg:sub(1, 1) == "\002" then
|
||||
msg = msg:sub(2)
|
||||
else
|
||||
msg = minetest.parse_json(msg)
|
||||
end
|
||||
|
||||
-- Run callbacks
|
||||
for _, func in ipairs(callbacks) do
|
||||
func(name, msg)
|
||||
end
|
||||
end,
|
||||
-- Run callbacks
|
||||
for _, func in ipairs(callbacks) do
|
||||
func(name, msg)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
-- Add a callback for sscsm:com_test
|
||||
local registered_on_loaded = {}
|
||||
function sscsm.register_on_sscsms_loaded(func)
|
||||
table.insert(registered_on_loaded, func)
|
||||
table.insert(registered_on_loaded, func)
|
||||
end
|
||||
|
||||
sscsm.register_on_com_receive('sscsm:com_test', function(name, msg)
|
||||
if type(msg) ~= 'table' or msg.flags ~= flags or has_sscsms[name] then
|
||||
return
|
||||
end
|
||||
has_sscsms[name] = true
|
||||
for _, func in ipairs(registered_on_loaded) do
|
||||
func(name)
|
||||
end
|
||||
sscsm.register_on_com_receive("sscsm:com_test", function(name, msg)
|
||||
if type(msg) ~= "table" or msg.flags ~= flags or has_sscsms[name] then
|
||||
return
|
||||
end
|
||||
has_sscsms[name] = true
|
||||
for _, func in ipairs(registered_on_loaded) do
|
||||
func(name)
|
||||
end
|
||||
end)
|
||||
|
||||
function sscsm.has_sscsms_enabled(name)
|
||||
return has_sscsms[name] or false
|
||||
return has_sscsms[name] or false
|
||||
end
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
has_sscsms[player:get_player_name()] = nil
|
||||
has_sscsms[player:get_player_name()] = nil
|
||||
end)
|
||||
|
||||
function sscsm.com_send_all(channel, msg)
|
||||
for name, _ in pairs(has_sscsms) do
|
||||
sscsm.com_send(name, channel, msg)
|
||||
end
|
||||
for name, _ in pairs(has_sscsms) do
|
||||
sscsm.com_send(name, channel, msg)
|
||||
end
|
||||
end
|
||||
|
|
162
minify.lua
162
minify.lua
|
@ -19,85 +19,85 @@
|
|||
|
||||
-- Find multiple patterns
|
||||
local function find_multiple(text, ...)
|
||||
local n = select('#', ...)
|
||||
local s, e, pattern
|
||||
for i = 1, n do
|
||||
local p = select(i, ...)
|
||||
local s2, e2 = text:find(p)
|
||||
if s2 and (not s or s2 < s) then
|
||||
s, e, pattern = s2, e2 or s2, p
|
||||
end
|
||||
end
|
||||
return s, e, pattern
|
||||
local n = select('#', ...)
|
||||
local s, e, pattern
|
||||
for i = 1, n do
|
||||
local p = select(i, ...)
|
||||
local s2, e2 = text:find(p)
|
||||
if s2 and (not s or s2 < s) then
|
||||
s, e, pattern = s2, e2 or s2, p
|
||||
end
|
||||
end
|
||||
return s, e, pattern
|
||||
end
|
||||
|
||||
-- Matches
|
||||
-- These take 2-3 arguments (code, res, char) and should return code and res.
|
||||
local matches = {
|
||||
-- Handle multi-line strings
|
||||
['%[=*%['] = function(code, res, char)
|
||||
res = res .. char
|
||||
char = char:sub(2, -2)
|
||||
local s, e = code:find(']' .. char .. ']', nil, true)
|
||||
if not s or not e then return code, res end
|
||||
return code:sub(e + 1), res .. code:sub(1, e)
|
||||
end,
|
||||
-- Handle multi-line strings
|
||||
['%[=*%['] = function(code, res, char)
|
||||
res = res .. char
|
||||
char = char:sub(2, -2)
|
||||
local s, e = code:find(']' .. char .. ']', nil, true)
|
||||
if not s or not e then return code, res end
|
||||
return code:sub(e + 1), res .. code:sub(1, e)
|
||||
end,
|
||||
|
||||
-- Handle regular comments
|
||||
['--'] = function(code, res, char)
|
||||
local s, e = code:find('\n', nil, true)
|
||||
if not s or not e then return '', res end
|
||||
-- Handle regular comments
|
||||
['--'] = function(code, res, char)
|
||||
local s, e = code:find('\n', nil, true)
|
||||
if not s or not e then return '', res end
|
||||
|
||||
-- Don't remove copyright or license information.
|
||||
if e >= 7 then
|
||||
local first_word = (code:match('^[ \t]*(%w+)') or ''):lower()
|
||||
if first_word == 'copyright' or first_word == 'license' then
|
||||
return code:sub(s), res .. char .. code:sub(1, s - 1)
|
||||
end
|
||||
end
|
||||
-- Don't remove copyright or license information.
|
||||
if e >= 7 then
|
||||
local first_word = (code:match('^[ \t]*(%w+)') or ''):lower()
|
||||
if first_word == 'copyright' or first_word == 'license' then
|
||||
return code:sub(s), res .. char .. code:sub(1, s - 1)
|
||||
end
|
||||
end
|
||||
|
||||
-- Shift trailing spaces back
|
||||
local spaces = res:match('(%s*)$') or ''
|
||||
return spaces .. code:sub(s), res:sub(1, #res - #spaces)
|
||||
end,
|
||||
-- Shift trailing spaces back
|
||||
local spaces = res:match('(%s*)$') or ''
|
||||
return spaces .. code:sub(s), res:sub(1, #res - #spaces)
|
||||
end,
|
||||
|
||||
-- Handle multi-line comments
|
||||
['%-%-%[=*%['] = function(code, res, char)
|
||||
char = char:sub(4, -2)
|
||||
local s, e = code:find(']' .. char .. ']', nil, true)
|
||||
if not s or not e then return code, res end
|
||||
-- Handle multi-line comments
|
||||
['%-%-%[=*%['] = function(code, res, char)
|
||||
char = char:sub(4, -2)
|
||||
local s, e = code:find(']' .. char .. ']', nil, true)
|
||||
if not s or not e then return code, res end
|
||||
|
||||
-- Shift trailing spaces back
|
||||
local spaces = res:match('(%s*)$') or ''
|
||||
return spaces .. code:sub(e + 1), res:sub(1, #res - #spaces)
|
||||
end,
|
||||
-- Shift trailing spaces back
|
||||
local spaces = res:match('(%s*)$') or ''
|
||||
return spaces .. code:sub(e + 1), res:sub(1, #res - #spaces)
|
||||
end,
|
||||
|
||||
-- Handle quoted text
|
||||
['"'] = function(code, res, char)
|
||||
res = res .. char
|
||||
-- Handle quoted text
|
||||
['"'] = function(code, res, char)
|
||||
res = res .. char
|
||||
|
||||
-- Handle backslashes
|
||||
repeat
|
||||
local _, e, pattern = find_multiple(code, '\\', char)
|
||||
if pattern == char then
|
||||
res = res .. code:sub(1, e)
|
||||
code = code:sub(e + 1)
|
||||
elseif pattern then
|
||||
res = res .. code:sub(1, e + 1)
|
||||
code = code:sub(e + 2)
|
||||
end
|
||||
until not pattern or pattern == char
|
||||
-- Handle backslashes
|
||||
repeat
|
||||
local _, e, pattern = find_multiple(code, '\\', char)
|
||||
if pattern == char then
|
||||
res = res .. code:sub(1, e)
|
||||
code = code:sub(e + 1)
|
||||
elseif pattern then
|
||||
res = res .. code:sub(1, e + 1)
|
||||
code = code:sub(e + 2)
|
||||
end
|
||||
until not pattern or pattern == char
|
||||
|
||||
return code, res
|
||||
end,
|
||||
return code, res
|
||||
end,
|
||||
|
||||
['%s*[\r\n]%s*'] = function(code, res, char)
|
||||
return code, res .. '\n'
|
||||
end,
|
||||
['%s*[\r\n]%s*'] = function(code, res, char)
|
||||
return code, res .. '\n'
|
||||
end,
|
||||
|
||||
['[ \t]+'] = function(code, res, char)
|
||||
return code, res .. ' '
|
||||
end,
|
||||
['[ \t]+'] = function(code, res, char)
|
||||
return code, res .. ' '
|
||||
end,
|
||||
}
|
||||
|
||||
-- Give the functions alternate names
|
||||
|
@ -105,26 +105,26 @@ matches["'"] = matches['"']
|
|||
|
||||
-- The actual transpiler
|
||||
return function(code)
|
||||
assert(type(code) == 'string')
|
||||
assert(type(code) == 'string')
|
||||
|
||||
local res = ''
|
||||
local res = ''
|
||||
|
||||
-- Split the code by "tokens"
|
||||
while true do
|
||||
-- Search for special characters
|
||||
local s, e, pattern = find_multiple(code, '[\'"\\]', '%-%-%[=*%[',
|
||||
'%-%-', '%[=*%[', '%s*[\r\n]%s*', '[ \t]+')
|
||||
if not s then break end
|
||||
-- Split the code by "tokens"
|
||||
while true do
|
||||
-- Search for special characters
|
||||
local s, e, pattern = find_multiple(code, '[\'"\\]', '%-%-%[=*%[',
|
||||
'%-%-', '%[=*%[', '%s*[\r\n]%s*', '[ \t]+')
|
||||
if not s then break end
|
||||
|
||||
-- Add non-matching characters
|
||||
res = res .. code:sub(1, math.max(s - 1, 0))
|
||||
-- Add non-matching characters
|
||||
res = res .. code:sub(1, math.max(s - 1, 0))
|
||||
|
||||
-- Call the correct function
|
||||
local char = code:sub(s, e)
|
||||
local func = matches[char] or matches[pattern]
|
||||
assert(func, 'No function found for pattern!')
|
||||
code, res = func(code:sub(e + 1), res, char)
|
||||
end
|
||||
-- Call the correct function
|
||||
local char = code:sub(s, e)
|
||||
local func = matches[char] or matches[pattern]
|
||||
assert(func, 'No function found for pattern!')
|
||||
code, res = func(code:sub(e + 1), res, char)
|
||||
end
|
||||
|
||||
return (res .. code):trim()
|
||||
return (res .. code):trim()
|
||||
end
|
||||
|
|
356
sscsm_init.lua
356
sscsm_init.lua
|
@ -21,13 +21,13 @@
|
|||
|
||||
-- Make sure both table.unpack and unpack exist.
|
||||
if table.unpack then
|
||||
unpack = table.unpack
|
||||
unpack = table.unpack
|
||||
else
|
||||
table.unpack = unpack
|
||||
table.unpack = unpack
|
||||
end
|
||||
|
||||
-- Make sure a few basic functions exist, these may have been blocked because
|
||||
-- of security or laziness.
|
||||
-- of security or laziness.
|
||||
if not rawget then function rawget(n, name) return n[name] end end
|
||||
if not rawset then function rawset(n, k, v) n[k] = v end end
|
||||
if not rawequal then function rawequal(a, b) return a == b end end
|
||||
|
@ -35,167 +35,167 @@ if not rawequal then function rawequal(a, b) return a == b end end
|
|||
-- Older versions of the CSM don't provide assert(), this function exists for
|
||||
-- compatibility.
|
||||
if not assert then
|
||||
function assert(value, ...)
|
||||
if value then
|
||||
return value, ...
|
||||
else
|
||||
error(... or 'assertion failed!', 2)
|
||||
end
|
||||
end
|
||||
function assert(value, ...)
|
||||
if value then
|
||||
return value, ...
|
||||
else
|
||||
error(... or 'assertion failed!', 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Create the API
|
||||
sscsm = {}
|
||||
function sscsm.global_exists(name)
|
||||
return rawget(_G, name) ~= nil
|
||||
return rawget(_G, name) ~= nil
|
||||
end
|
||||
|
||||
if not sscsm.global_exists('minetest') then
|
||||
minetest = assert(core, 'No "minetest" global found!')
|
||||
minetest = assert(core, 'No "minetest" global found!')
|
||||
end
|
||||
|
||||
minetest.global_exists = sscsm.global_exists
|
||||
|
||||
-- Check if join_mod_channel and leave_mod_channel exist.
|
||||
if sscsm.global_exists('join_mod_channel')
|
||||
and sscsm.global_exists('leave_mod_channel') then
|
||||
sscsm.join_mod_channel = join_mod_channel
|
||||
sscsm.leave_mod_channel = leave_mod_channel
|
||||
join_mod_channel, leave_mod_channel = nil, nil
|
||||
and sscsm.global_exists('leave_mod_channel') then
|
||||
sscsm.join_mod_channel = join_mod_channel
|
||||
sscsm.leave_mod_channel = leave_mod_channel
|
||||
join_mod_channel, leave_mod_channel = nil, nil
|
||||
else
|
||||
local dummy = function() end
|
||||
sscsm.join_mod_channel = dummy
|
||||
sscsm.leave_mod_channel = dummy
|
||||
local dummy = function() end
|
||||
sscsm.join_mod_channel = dummy
|
||||
sscsm.leave_mod_channel = dummy
|
||||
end
|
||||
|
||||
-- Add print()
|
||||
function print(...)
|
||||
local msg = '[SSCSM] '
|
||||
for i = 1, select('#', ...) do
|
||||
if i > 1 then msg = msg .. '\t' end
|
||||
msg = msg .. tostring(select(i, ...))
|
||||
end
|
||||
minetest.log('none', msg)
|
||||
local msg = '[SSCSM] '
|
||||
for i = 1, select('#', ...) do
|
||||
if i > 1 then msg = msg .. '\t' end
|
||||
msg = msg .. tostring(select(i, ...))
|
||||
end
|
||||
minetest.log('none', msg)
|
||||
end
|
||||
|
||||
-- Add register_on_mods_loaded
|
||||
do
|
||||
local funcs = {}
|
||||
function sscsm.register_on_mods_loaded(callback)
|
||||
if funcs then table.insert(funcs, callback) end
|
||||
end
|
||||
local funcs = {}
|
||||
function sscsm.register_on_mods_loaded(callback)
|
||||
if funcs then table.insert(funcs, callback) end
|
||||
end
|
||||
|
||||
function sscsm._done_loading_()
|
||||
sscsm._done_loading_ = nil
|
||||
for _, func in ipairs(funcs) do func() end
|
||||
funcs = nil
|
||||
end
|
||||
function sscsm._done_loading_()
|
||||
sscsm._done_loading_ = nil
|
||||
for _, func in ipairs(funcs) do func() end
|
||||
funcs = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Helper functions
|
||||
if not minetest.get_node then
|
||||
function minetest.get_node(pos)
|
||||
return minetest.get_node_or_nil(pos) or {name = 'ignore', param1 = 0,
|
||||
param2 = 0}
|
||||
end
|
||||
function minetest.get_node(pos)
|
||||
return minetest.get_node_or_nil(pos) or {name = 'ignore', param1 = 0,
|
||||
param2 = 0}
|
||||
end
|
||||
end
|
||||
|
||||
-- Make minetest.run_server_chatcommand allow param to be unspecified.
|
||||
function minetest.run_server_chatcommand(cmd, param)
|
||||
minetest.send_chat_message('/' .. cmd .. ' ' .. (param or ''))
|
||||
minetest.send_chat_message('/' .. cmd .. ' ' .. (param or ''))
|
||||
end
|
||||
|
||||
-- Register "server-side" chatcommands
|
||||
-- Can allow instantaneous responses in some cases.
|
||||
sscsm.registered_chatcommands = {}
|
||||
local function on_chat_message(msg)
|
||||
if msg:sub(1, 1) ~= '/' then return false end
|
||||
if msg:sub(1, 1) ~= '/' then return false end
|
||||
|
||||
local cmd, param = msg:match('^/([^ ]+) *(.*)')
|
||||
if not cmd then
|
||||
minetest.display_chat_message('-!- Empty command')
|
||||
return true
|
||||
end
|
||||
local cmd, param = msg:match('^/([^ ]+) *(.*)')
|
||||
if not cmd then
|
||||
minetest.display_chat_message('-!- Empty command')
|
||||
return true
|
||||
end
|
||||
|
||||
if not sscsm.registered_chatcommands[cmd] then return false end
|
||||
if not sscsm.registered_chatcommands[cmd] then return false end
|
||||
|
||||
local _, res = sscsm.registered_chatcommands[cmd].func(param or '')
|
||||
if res then minetest.display_chat_message(tostring(res)) end
|
||||
local _, res = sscsm.registered_chatcommands[cmd].func(param or '')
|
||||
if res then minetest.display_chat_message(tostring(res)) end
|
||||
|
||||
return true
|
||||
return true
|
||||
end
|
||||
|
||||
function sscsm.register_chatcommand(cmd, def)
|
||||
if type(def) == 'function' then
|
||||
def = {func = def}
|
||||
elseif type(def.func) ~= 'function' then
|
||||
error('Invalid definition passed to sscsm.register_chatcommand.')
|
||||
end
|
||||
if type(def) == 'function' then
|
||||
def = {func = def}
|
||||
elseif type(def.func) ~= 'function' then
|
||||
error('Invalid definition passed to sscsm.register_chatcommand.')
|
||||
end
|
||||
|
||||
sscsm.registered_chatcommands[cmd] = def
|
||||
sscsm.registered_chatcommands[cmd] = def
|
||||
|
||||
if on_chat_message then
|
||||
minetest.register_on_sending_chat_message(on_chat_message)
|
||||
on_chat_message = nil
|
||||
end
|
||||
if on_chat_message then
|
||||
minetest.register_on_sending_chat_message(on_chat_message)
|
||||
on_chat_message = nil
|
||||
end
|
||||
end
|
||||
|
||||
function sscsm.unregister_chatcommand(cmd)
|
||||
sscsm.registered_chatcommands[cmd] = nil
|
||||
sscsm.registered_chatcommands[cmd] = nil
|
||||
end
|
||||
|
||||
-- A proper get_player_control didn't exist before Minetest 5.3.0.
|
||||
if minetest.localplayer.get_control then
|
||||
-- Preserve API compatibility
|
||||
if minetest.localplayer:get_control().LMB == nil then
|
||||
-- MT 5.4+
|
||||
function sscsm.get_player_control()
|
||||
local c = minetest.localplayer:get_control()
|
||||
c.LMB, c.RMB = c.dig, c.place
|
||||
return c
|
||||
end
|
||||
else
|
||||
-- MT 5.3
|
||||
function sscsm.get_player_control()
|
||||
local c = minetest.localplayer:get_control()
|
||||
c.dig, c.place = c.LMB, c.RMB
|
||||
return c
|
||||
end
|
||||
end
|
||||
-- Preserve API compatibility
|
||||
if minetest.localplayer:get_control().LMB == nil then
|
||||
-- MT 5.4+
|
||||
function sscsm.get_player_control()
|
||||
local c = minetest.localplayer:get_control()
|
||||
c.LMB, c.RMB = c.dig, c.place
|
||||
return c
|
||||
end
|
||||
else
|
||||
-- MT 5.3
|
||||
function sscsm.get_player_control()
|
||||
local c = minetest.localplayer:get_control()
|
||||
c.dig, c.place = c.LMB, c.RMB
|
||||
return c
|
||||
end
|
||||
end
|
||||
else
|
||||
-- MT 5.0 to 5.2
|
||||
local floor = math.floor
|
||||
function sscsm.get_player_control()
|
||||
local n = minetest.localplayer:get_key_pressed()
|
||||
return {
|
||||
up = n % 2 == 1,
|
||||
down = floor(n / 2) % 2 == 1,
|
||||
left = floor(n / 4) % 2 == 1,
|
||||
right = floor(n / 8) % 2 == 1,
|
||||
jump = floor(n / 16) % 2 == 1,
|
||||
aux1 = floor(n / 32) % 2 == 1,
|
||||
sneak = floor(n / 64) % 2 == 1,
|
||||
LMB = floor(n / 128) % 2 == 1,
|
||||
RMB = floor(n / 256) % 2 == 1,
|
||||
dig = floor(n / 128) % 2 == 1,
|
||||
place = floor(n / 256) % 2 == 1,
|
||||
}
|
||||
end
|
||||
-- MT 5.0 to 5.2
|
||||
local floor = math.floor
|
||||
function sscsm.get_player_control()
|
||||
local n = minetest.localplayer:get_key_pressed()
|
||||
return {
|
||||
up = n % 2 == 1,
|
||||
down = floor(n / 2) % 2 == 1,
|
||||
left = floor(n / 4) % 2 == 1,
|
||||
right = floor(n / 8) % 2 == 1,
|
||||
jump = floor(n / 16) % 2 == 1,
|
||||
aux1 = floor(n / 32) % 2 == 1,
|
||||
sneak = floor(n / 64) % 2 == 1,
|
||||
LMB = floor(n / 128) % 2 == 1,
|
||||
RMB = floor(n / 256) % 2 == 1,
|
||||
dig = floor(n / 128) % 2 == 1,
|
||||
place = floor(n / 256) % 2 == 1,
|
||||
}
|
||||
end
|
||||
|
||||
-- In Minetest 5.2.0, minetest.get_node_light() segfaults.
|
||||
minetest.get_node_light = nil
|
||||
-- In Minetest 5.2.0, minetest.get_node_light() segfaults.
|
||||
minetest.get_node_light = nil
|
||||
end
|
||||
|
||||
-- Call func(...) every <interval> seconds.
|
||||
local function sscsm_every(interval, func, ...)
|
||||
minetest.after(interval, sscsm_every, interval, func, ...)
|
||||
return func(...)
|
||||
minetest.after(interval, sscsm_every, interval, func, ...)
|
||||
return func(...)
|
||||
end
|
||||
|
||||
function sscsm.every(interval, func, ...)
|
||||
assert(type(interval) == 'number' and type(func) == 'function',
|
||||
'Invalid sscsm.every() invocation.')
|
||||
return sscsm_every(interval, func, ...)
|
||||
assert(type(interval) == 'number' and type(func) == 'function',
|
||||
'Invalid sscsm.every() invocation.')
|
||||
return sscsm_every(interval, func, ...)
|
||||
end
|
||||
|
||||
-- Allow SSCSMs to know about CSM restriction flags.
|
||||
|
@ -203,117 +203,117 @@ end
|
|||
local flags = __FLAGS__
|
||||
sscsm.restriction_flags = assert(flags)
|
||||
sscsm.restrictions = {
|
||||
chat_messages = math.floor(flags / 2) % 2 == 1,
|
||||
read_itemdefs = math.floor(flags / 4) % 2 == 1,
|
||||
read_nodedefs = math.floor(flags / 8) % 2 == 1,
|
||||
lookup_nodes_limit = math.floor(flags / 16) % 2 == 1,
|
||||
read_playerinfo = math.floor(flags / 32) % 2 == 1,
|
||||
chat_messages = math.floor(flags / 2) % 2 == 1,
|
||||
read_itemdefs = math.floor(flags / 4) % 2 == 1,
|
||||
read_nodedefs = math.floor(flags / 8) % 2 == 1,
|
||||
lookup_nodes_limit = math.floor(flags / 16) % 2 == 1,
|
||||
read_playerinfo = math.floor(flags / 32) % 2 == 1,
|
||||
}
|
||||
sscsm.restrictions.lookup_nodes = sscsm.restrictions.lookup_nodes_limit
|
||||
|
||||
-- Add minetest.get_csm_restrictions() if it doesn't exist already.
|
||||
if not minetest.get_csm_restrictions then
|
||||
function minetest.get_csm_restrictions()
|
||||
return table.copy(sscsm.restrictions)
|
||||
end
|
||||
function minetest.get_csm_restrictions()
|
||||
return table.copy(sscsm.restrictions)
|
||||
end
|
||||
end
|
||||
|
||||
-- SSCSM communication
|
||||
-- A lot of this is copied from init.lua.
|
||||
local function validate_channel(channel)
|
||||
if type(channel) ~= 'string' then
|
||||
error('SSCSM com channels must be strings!', 3)
|
||||
end
|
||||
if channel:find('\001', nil, true) then
|
||||
error('SSCSM com channels cannot contain U+0001!', 3)
|
||||
end
|
||||
if type(channel) ~= 'string' then
|
||||
error('SSCSM com channels must be strings!', 3)
|
||||
end
|
||||
if channel:find('\001', nil, true) then
|
||||
error('SSCSM com channels cannot contain U+0001!', 3)
|
||||
end
|
||||
end
|
||||
|
||||
function sscsm.com_send(channel, msg)
|
||||
assert(not sscsm.restrictions.chat_messages, 'Server restrictions ' ..
|
||||
'prevent SSCSM com messages from being sent!')
|
||||
validate_channel(channel)
|
||||
if type(msg) == 'string' then
|
||||
msg = '\002' .. msg
|
||||
else
|
||||
msg = minetest.write_json(msg)
|
||||
end
|
||||
minetest.run_server_chatcommand('admin', '\001SSCSM_COM\001' .. channel ..
|
||||
'\001' .. msg)
|
||||
assert(not sscsm.restrictions.chat_messages, 'Server restrictions ' ..
|
||||
'prevent SSCSM com messages from being sent!')
|
||||
validate_channel(channel)
|
||||
if type(msg) == 'string' then
|
||||
msg = '\002' .. msg
|
||||
else
|
||||
msg = minetest.write_json(msg)
|
||||
end
|
||||
minetest.run_server_chatcommand('admin', '\001SSCSM_COM\001' .. channel ..
|
||||
'\001' .. msg)
|
||||
end
|
||||
|
||||
local registered_on_receive = {}
|
||||
function sscsm.register_on_com_receive(channel, func)
|
||||
if not registered_on_receive[channel] then
|
||||
registered_on_receive[channel] = {}
|
||||
end
|
||||
table.insert(registered_on_receive[channel], func)
|
||||
if not registered_on_receive[channel] then
|
||||
registered_on_receive[channel] = {}
|
||||
end
|
||||
table.insert(registered_on_receive[channel], func)
|
||||
end
|
||||
|
||||
-- Load split messages
|
||||
local incoming_messages = {}
|
||||
local function load_split_message(chan, msg)
|
||||
local id, i, l, pkt = msg:match('^\1([^\1]+)\1([^\1]+)\1([^\1]+)\1(.*)$')
|
||||
id, i, l = tonumber(id), tonumber(i), tonumber(l)
|
||||
local id, i, l, pkt = msg:match('^\1([^\1]+)\1([^\1]+)\1([^\1]+)\1(.*)$')
|
||||
id, i, l = tonumber(id), tonumber(i), tonumber(l)
|
||||
|
||||
if not incoming_messages[id] then
|
||||
incoming_messages[id] = {}
|
||||
end
|
||||
local msgs = incoming_messages[id]
|
||||
msgs[i] = pkt
|
||||
if not incoming_messages[id] then
|
||||
incoming_messages[id] = {}
|
||||
end
|
||||
local msgs = incoming_messages[id]
|
||||
msgs[i] = pkt
|
||||
|
||||
-- Return true if all the messages have been received
|
||||
if #msgs < l then return end
|
||||
for count = 1, l do
|
||||
if not msgs[count] then
|
||||
return
|
||||
end
|
||||
end
|
||||
incoming_messages[id] = nil
|
||||
return table.concat(msgs, '')
|
||||
-- Return true if all the messages have been received
|
||||
if #msgs < l then return end
|
||||
for count = 1, l do
|
||||
if not msgs[count] then
|
||||
return
|
||||
end
|
||||
end
|
||||
incoming_messages[id] = nil
|
||||
return table.concat(msgs, '')
|
||||
end
|
||||
|
||||
-- Detect messages and handle them
|
||||
minetest.register_on_receiving_chat_message(function(message)
|
||||
if type(message) == 'table' then
|
||||
message = message.message
|
||||
end
|
||||
if type(message) == 'table' then
|
||||
message = message.message
|
||||
end
|
||||
|
||||
local chan, msg = message:match('^\001SSCSM_COM\001([^\001]*)\001(.*)$')
|
||||
if not chan or not msg then return end
|
||||
local chan, msg = message:match('^\001SSCSM_COM\001([^\001]*)\001(.*)$')
|
||||
if not chan or not msg then return end
|
||||
|
||||
-- Get the callbacks
|
||||
local callbacks = registered_on_receive[chan]
|
||||
if not callbacks then return true end
|
||||
-- Get the callbacks
|
||||
local callbacks = registered_on_receive[chan]
|
||||
if not callbacks then return true end
|
||||
|
||||
-- Handle split messages
|
||||
local prefix = msg:sub(1, 1)
|
||||
if prefix == '\001' then
|
||||
msg = load_split_message(chan, msg)
|
||||
if not msg then
|
||||
return true
|
||||
end
|
||||
prefix = msg:sub(1, 1)
|
||||
end
|
||||
-- Handle split messages
|
||||
local prefix = msg:sub(1, 1)
|
||||
if prefix == '\001' then
|
||||
msg = load_split_message(chan, msg)
|
||||
if not msg then
|
||||
return true
|
||||
end
|
||||
prefix = msg:sub(1, 1)
|
||||
end
|
||||
|
||||
-- Load the message
|
||||
if prefix == '\002' then
|
||||
msg = msg:sub(2)
|
||||
else
|
||||
msg = minetest.parse_json(msg)
|
||||
end
|
||||
-- Load the message
|
||||
if prefix == '\002' then
|
||||
msg = msg:sub(2)
|
||||
else
|
||||
msg = minetest.parse_json(msg)
|
||||
end
|
||||
|
||||
-- Run callbacks
|
||||
for _, func in ipairs(callbacks) do
|
||||
local ok, text = pcall(func, msg)
|
||||
if not ok then
|
||||
minetest.log('error', '[SSCSM] ' .. tostring(text))
|
||||
end
|
||||
end
|
||||
return true
|
||||
-- Run callbacks
|
||||
for _, func in ipairs(callbacks) do
|
||||
local ok, err = pcall(func, msg)
|
||||
if not ok then
|
||||
minetest.log('error', '[SSCSM] ' .. tostring(err))
|
||||
end
|
||||
end
|
||||
return true
|
||||
end)
|
||||
|
||||
sscsm.register_on_mods_loaded(function()
|
||||
sscsm.leave_mod_channel()
|
||||
sscsm.com_send('sscsm:com_test', {flags = sscsm.restriction_flags})
|
||||
sscsm.leave_mod_channel()
|
||||
sscsm.com_send('sscsm:com_test', {flags = sscsm.restriction_flags})
|
||||
end)
|
||||
|
|
Loading…
Reference in New Issue