Spaces → tabs, double quotes (in some files)

This makes the code style more consistent with the Lua code built into 
the engine.
master
luk3yx 2021-12-05 17:04:23 +13:00
parent cd954713ab
commit 05e500857c
5 changed files with 1096 additions and 1096 deletions

View File

@ -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
View File

@ -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

View File

@ -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

View File

@ -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)