Add server-side SSCSM support.

master
luk3yx 2020-07-30 19:46:46 +12:00 committed by MoNTE48
parent 7b37f2c1d8
commit 90211350a5
8 changed files with 929 additions and 35 deletions

View File

@ -34,5 +34,6 @@ dofile(gamepath .. "voxelarea.lua")
dofile(gamepath .. "forceloading.lua")
dofile(gamepath .. "statbars.lua")
dofile(gamepath .. "knockback.lua")
dofile(gamepath .. "sscsm" .. DIR_DELIM .. "init.lua")
profiler = nil

View File

@ -0,0 +1,343 @@
--
-- SSCSM: Server-Sent Client-Side Mods
-- Initial code sent to the client
--
-- Copyright © 2019-2021 by luk3yx
-- Copyright © 2020-2021 MultiCraft Development Team
-- License: GNU LGPL 3.0+
--
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU Lesser General Public License as published by
-- the Free Software Foundation; either version 3.0 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public License
-- along with this program; if not, write to the Free Software Foundation,
-- Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--
-- Make sure both table.unpack and unpack exist.
if table.unpack then
unpack = table.unpack
else
table.unpack = unpack -- luacheck: ignore
end
-- Make sure a few basic functions exist, these may have been blocked because
-- 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
-- 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
end
-- Create the API
sscsm = {}
function sscsm.global_exists(name)
return rawget(_G, name) ~= nil
end
if sscsm.global_exists('minetest') then
core = minetest
else
minetest = assert(core, 'No "minetest" global found!')
end
core.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
else
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
core.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
function sscsm._done_loading_()
sscsm._done_loading_ = nil
for _, func in ipairs(funcs) do func() end
funcs = nil
end
end
-- Helper functions
if not core.get_node then
function core.get_node(pos)
return core.get_node_or_nil(pos) or {name = 'ignore', param1 = 0,
param2 = 0}
end
end
-- Make core.run_server_chatcommand allow param to be unspecified.
function core.run_server_chatcommand(cmd, param)
core.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
local cmd, param = msg:match('^/([^ ]+) *(.*)')
if not cmd then
core.display_chat_message('-!- Empty command')
return true
end
if not sscsm.registered_chatcommands[cmd] then return false end
local _, res = sscsm.registered_chatcommands[cmd].func(param or '')
if res then core.display_chat_message(tostring(res)) end
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
sscsm.registered_chatcommands[cmd] = def
if on_chat_message then
core.register_on_sending_chat_message(on_chat_message)
on_chat_message = nil
end
end
function sscsm.unregister_chatcommand(cmd)
sscsm.registered_chatcommands[cmd] = nil
end
-- A proper get_player_control didn't exist before Minetest 5.3.0.
if core.localplayer.get_control then
-- Preserve API compatibility
if core.localplayer:get_control().LMB == nil then
-- MT 5.4+
function sscsm.get_player_control()
local c = core.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 = core.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 = core.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, core.get_node_light() segfaults.
core.get_node_light = nil
end
-- Call func(...) every <interval> seconds.
local function sscsm_every(interval, func, ...)
core.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, ...)
end
-- Allow SSCSMs to know about CSM restriction flags.
-- "__FLAGS__" is replaced with the actual value in init.lua.
-- luacheck: globals __FLAGS__ __MAPGEN_LIMIT__
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,
}
sscsm.restrictions.lookup_nodes = sscsm.restrictions.lookup_nodes_limit
-- Add core.get_csm_restrictions() if it doesn't exist already.
if not core.get_csm_restrictions then
function core.get_csm_restrictions()
return table.copy(sscsm.restrictions)
end
end
local mapgen_limit = __MAPGEN_LIMIT__
function core.is_valid_pos(pos)
if not pos or type(pos) ~= "table" then
return false
end
for _, v in ipairs({"x", "y", "z"}) do
if not pos[v] or pos[v] ~= pos[v] or
pos[v] < -mapgen_limit or pos[v] > mapgen_limit then
return false
end
end
return true
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
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 = core.write_json(msg)
end
core.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)
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)
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 j = 1, l do
if not msgs[j] then
return
end
end
incoming_messages[id] = nil
return table.concat(msgs, '')
end
-- Detect messages and handle them
core.register_on_receiving_chat_message(function(message)
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
-- 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
-- Load the message
if prefix == '\002' then
msg = msg:sub(2)
else
msg = core.parse_json(msg)
end
-- Run callbacks
for _, func in ipairs(callbacks) do
local ok, err = pcall(func, msg)
if not ok then
core.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})
end)
if not core.global_exists("utf8") or not utf8.lower then
utf8 = string
end

315
builtin/game/sscsm/init.lua Normal file
View File

@ -0,0 +1,315 @@
--
-- SSCSM: Server-Sent Client-Side Mods
--
-- Copyright © 2019-2021 by luk3yx
-- Copyright © 2020-2021 MultiCraft Development Team
--
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU Lesser General Public License as published by
-- the Free Software Foundation; either version 3.0 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public License
-- along with this program; if not, write to the Free Software Foundation,
-- Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--
sscsm = {minify=true}
local modpath = core.get_builtin_path() .. "game" .. DIR_DELIM ..
"sscsm" .. DIR_DELIM
-- 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
core.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
end
-- Register code
sscsm.registered_csms = {}
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
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
core.log("error", msg)
end
end
-- 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
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
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
end
function sscsm.unregister(name)
sscsm.registered_csms[name] = nil
if csm_order then recalc_csm_order() end
end
-- Recalculate the CSM order once all other mods are loaded
core.register_on_mods_loaded(recalc_csm_order)
-- Handle players joining
local has_sscsms = {}
local mod_channel = core.mod_channel_join("sscsm:exec_pipe")
if not core.is_singleplayer() then
core.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
core.log("action", "[SSCSM] Sending CSMs on request for " .. sender
.. "...")
for _, name in ipairs(csm_order) do
local def = sscsm.registered_csms[name]
if not def.is_enabled_for or def.is_enabled_for(sender) then
mod_channel:send_all("0" .. sender .. "\n" .. name
.. "\n" .. sscsm.registered_csms[name].code)
end
end
end)
end
-- Register the SSCSM "builtins"
sscsm.register({
name = ":init",
file = modpath .. "client.lua"
})
sscsm.register({
name = ":cleanup",
code = "sscsm._done_loading_()"
})
block_colon = true
-- Set the CSM restriction flags
local flags = tonumber(core.settings:get("csm_restriction_flags"))
if not flags or flags ~= flags then
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 mapgen_limit = tonumber(core.settings:get("mapgen_limit"))
def.code = def.code:gsub("__MAPGEN_LIMIT__", tostring(mapgen_limit))
end
if math.floor(flags / 2) % 2 == 1 then
core.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
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
end
local msgids = {}
function sscsm.com_send(pname, channel, msg)
if core.is_player(pname) then
pname = pname:get_player_name()
end
validate_channel(channel)
if type(msg) == "string" then
msg = "\002" .. msg
else
msg = assert(core.write_json(msg))
end
-- Short messages can be sent all at once
local prefix = "\001SSCSM_COM\001" .. channel .. "\001"
if #msg < 65300 then
core.chat_send_player(pname, prefix .. msg)
return
end
-- 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
core.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.
core.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)
end
local admin_func = core.registered_chatcommands["admin"].func
core.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
-- Load the message
if msg:sub(1, 1) == "\002" then
msg = msg:sub(2)
else
msg = core.parse_json(msg)
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)
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
end
core.register_on_leaveplayer(function(player)
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
end

View File

@ -0,0 +1,132 @@
--
-- A primitive code minifier
--
-- Copyright © 2019-2021 by luk3yx
-- Copyright © 2020-2021 MultiCraft Development Team
--
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU Lesser General Public License as published by
-- the Free Software Foundation; either version 3.0 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public License
-- along with this program; if not, write to the Free Software Foundation,
-- Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--
-- 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
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 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
-- 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
-- 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 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,
['%s*[\r\n]%s*'] = function(code, res, char)
return code, res .. '\n'
end,
['[ \t]+'] = function(code, res, char)
return code, res .. ' '
end,
}
-- Give the functions alternate names
matches["'"] = matches['"']
-- The actual transpiler
return function(code)
assert(type(code) == 'string')
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
-- 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
return (res .. code):trim()
end

View File

@ -1124,7 +1124,7 @@ player_transfer_distance (Player transfer distance) int 0
enable_pvp (Player versus player) bool true
# Enable mod channels support.
enable_mod_channels (Mod channels) bool false
enable_mod_channels (Mod channels) bool true
# If this is set, players will always (re)spawn at the given position.
static_spawnpoint (Static spawnpoint) string
@ -1315,7 +1315,7 @@ server_side_occlusion_culling (Server side occlusion culling) bool true
# LOOKUP_NODES_LIMIT: 16 (limits get_node call client-side to
# csm_restriction_noderange)
# READ_PLAYERINFO: 32 (disable get_player_names call client-side)
csm_restriction_flags (Client side modding restrictions) int 62
csm_restriction_flags (Client side modding restrictions) int 60
# If the CSM restriction for node range is enabled, get_node calls are limited
# to this distance from the player to the node.

View File

@ -3033,6 +3033,110 @@ angles in radians.
SSCSM
=====
MultiCraft has a built-in SSCSM implementation that will load SSCSMs onto
MultiCraft clients, as well as non-MultiCraft clients if they have the correct
CSM installed.
Server-side API
---------------
* `sscsm.register(SSCSM definition table)`:
* Registers a server-provided CSM with an [SSCSM definition table].
* `sscsm.register_on_sscsms_loaded(function(name))`:
* Registers a callback that will be called when SSCSMs have been loaded
on a client.
* `sscsm.has_sscsms_enabled(name)`:
* Returns a boolean.
* Returns `true` if the client supports SSCSMs and SSCSMs have been loaded.
* Note that this will not return `true` immediately after joining.
* `sscsm.com_send(player_or_name, channel, msg)`:
* Sends a com message to a specific client.
* `msg` can be any object that can be serialized with JSON.
* `sscsm.com_send_all(channel, msg)`:
* Sends a com message to all clients.
* `sscsm.register_on_com_receive(channel, function(name, msg))`:
* Registers a function to be called when a message on channel is received
from the client.
* Note that `msg` may be any JSON-compatible type, so checking the type of
this object is strongly recommended.
Client-side API
---------------
SSCSMs have access most of the API functions mentioned in client_lua_api.txt,
along with an extra `sscsm` namespace:
* `sscsm.register_on_mods_loaded(function())`:
* Runs the provided callback once all SSCSMs are loaded.
* `sscsm.register_chatcommand(command, function(param))`:
* Similar to `minetest.register_chatcommand`, however overrides commands
starting in `/` instead. This can be used to make some commands have
instantaneous responses.
* `sscsm.unregister_chatcommand(command)`:
* Unregisters a chatcommand.
* `sscsm.get_player_control()`:
* Returns a table similar to the server-side `player:get_player_control()`.
* `sscsm.every(interval, func, ...)`:
* Calls `func` every `interval` seconds with any extra parameters specified.
* Use `minetest.register_globalstep` instead if `interval` is zero.
* `sscsm.com_send(channel, msg)`:
* Sends `msg` (a JSON-compatible object) to the server.
* Note that client-to-server messages cannot be long, for plain strings
the channel and message combined must be at most 492 characters.
* `sscsm.register_on_com_receive(channel, function(msg))`:
* Registers a function to be called when a message on `channel` is received
from the server.
This namespace has the following constants:
* `sscsm.restriction_flags`:
* The `csm_restriction_flags` setting set in the server's `multicraft.conf`.
* `sscsm.restrictions`: A table based on `csm_restriction_flags`:
* `chat_messages`: When `true`, SSCSMs can't send chat messages or run
server chatcommands.
* `read_itemdefs`: When `true`, SSCSMs can't read item definitions.
* `read_nodedefs`: When `true`, SSCSMs can't read node definitions.
* `lookup_nodes_limit`: When `true`, any get_node calls are restricted.
* `read_playerinfo`: When `true`, `minetest.get_player_names()` will return
`nil`.
SSCSM definition table
----------------------
```lua
sscsm.register({
-- The name of the server-provided CSM. Using `modname` or
-- `modname:sscsmname` is strongly recommended. This name cannot start with
-- a colon or contain newlines.
name = "mymod",
-- The code to be sent to clients.
-- code = "print('Hello world!')",
-- The file to read code from.
-- This should be used instead of `code`.
file = minetest.get_modpath("mymod") .. DIR_DELIM .. "sscsm.lua",
-- An optional list of SSCSMs that this one depends on.
-- depends = {"othermod"},
})
```
Security considerations
-----------------------
Do not trust any input sent to the server via SSCSMs (and do not store
sensitive data in SSCSM code), as malicious users can and will inspect code and
modify the output from SSCSMs.
I repeat, **do not trust the client** and/or SSCSMs with any sensitive
information and do not trust any output from the client and/or SSCSMs. Make
sure you rerun any privilege checks on the server.
Helper functions
================

View File

@ -1351,7 +1351,7 @@
# Enable mod channels support.
# type: bool
# enable_mod_channels = false
# enable_mod_channels = true
# If this is set, players will always (re)spawn at the given position.
# type: string
@ -1389,7 +1389,7 @@
# ask_reconnect_on_crash = false
# From how far clients know about objects, stated in mapblocks (16 nodes).
#
#
# Setting this larger than active_block_range will also cause the server
# to maintain active objects up to this distance in the direction the
# player is looking. (This can avoid mobs suddenly disappearing from view)
@ -1589,7 +1589,7 @@
# csm_restriction_noderange)
# READ_PLAYERINFO: 32 (disable get_player_names call client-side)
# type: int
# csm_restriction_flags = 62
# csm_restriction_flags = 60
# If the CSM restriction for node range is enabled, get_node calls are limited
# to this distance from the player to the node.
@ -1942,7 +1942,7 @@
# octaves = 3,
# persistence = 0.5,
# lacunarity = 2.0,
# flags =
# flags =
# }
# Second of two 3D noises that together define tunnels.
@ -1955,7 +1955,7 @@
# octaves = 3,
# persistence = 0.5,
# lacunarity = 2.0,
# flags =
# flags =
# }
# 3D noise defining giant caverns.
@ -1968,7 +1968,7 @@
# octaves = 5,
# persistence = 0.63,
# lacunarity = 2.0,
# flags =
# flags =
# }
# 3D noise defining terrain.
@ -1994,7 +1994,7 @@
# octaves = 2,
# persistence = 0.8,
# lacunarity = 2.0,
# flags =
# flags =
# }
## Mapgen V6
@ -2381,7 +2381,7 @@
# octaves = 5,
# persistence = 0.63,
# lacunarity = 2.0,
# flags =
# flags =
# }
# 3D noise defining structure of river canyon walls.
@ -2394,7 +2394,7 @@
# octaves = 4,
# persistence = 0.75,
# lacunarity = 2.0,
# flags =
# flags =
# }
# 3D noise defining structure of floatlands.
@ -2410,7 +2410,7 @@
# octaves = 4,
# persistence = 0.75,
# lacunarity = 1.618,
# flags =
# flags =
# }
# 3D noise defining giant caverns.
@ -2423,7 +2423,7 @@
# octaves = 5,
# persistence = 0.63,
# lacunarity = 2.0,
# flags =
# flags =
# }
# First of two 3D noises that together define tunnels.
@ -2436,7 +2436,7 @@
# octaves = 3,
# persistence = 0.5,
# lacunarity = 2.0,
# flags =
# flags =
# }
# Second of two 3D noises that together define tunnels.
@ -2449,7 +2449,7 @@
# octaves = 3,
# persistence = 0.5,
# lacunarity = 2.0,
# flags =
# flags =
# }
# 3D noise that determines number of dungeons per mapchunk.
@ -2462,7 +2462,7 @@
# octaves = 2,
# persistence = 0.8,
# lacunarity = 2.0,
# flags =
# flags =
# }
## Mapgen Carpathian
@ -2705,7 +2705,7 @@
# octaves = 5,
# persistence = 0.55,
# lacunarity = 2.0,
# flags =
# flags =
# }
# First of two 3D noises that together define tunnels.
@ -2718,7 +2718,7 @@
# octaves = 3,
# persistence = 0.5,
# lacunarity = 2.0,
# flags =
# flags =
# }
# Second of two 3D noises that together define tunnels.
@ -2731,7 +2731,7 @@
# octaves = 3,
# persistence = 0.5,
# lacunarity = 2.0,
# flags =
# flags =
# }
# 3D noise defining giant caverns.
@ -2744,7 +2744,7 @@
# octaves = 5,
# persistence = 0.63,
# lacunarity = 2.0,
# flags =
# flags =
# }
# 3D noise that determines number of dungeons per mapchunk.
@ -2757,7 +2757,7 @@
# octaves = 2,
# persistence = 0.8,
# lacunarity = 2.0,
# flags =
# flags =
# }
## Mapgen Flat
@ -2867,7 +2867,7 @@
# octaves = 3,
# persistence = 0.5,
# lacunarity = 2.0,
# flags =
# flags =
# }
# Second of two 3D noises that together define tunnels.
@ -2880,7 +2880,7 @@
# octaves = 3,
# persistence = 0.5,
# lacunarity = 2.0,
# flags =
# flags =
# }
# 3D noise that determines number of dungeons per mapchunk.
@ -2893,7 +2893,7 @@
# octaves = 2,
# persistence = 0.8,
# lacunarity = 2.0,
# flags =
# flags =
# }
## Mapgen Fractal
@ -3067,7 +3067,7 @@
# octaves = 3,
# persistence = 0.5,
# lacunarity = 2.0,
# flags =
# flags =
# }
# Second of two 3D noises that together define tunnels.
@ -3080,7 +3080,7 @@
# octaves = 3,
# persistence = 0.5,
# lacunarity = 2.0,
# flags =
# flags =
# }
# 3D noise that determines number of dungeons per mapchunk.
@ -3093,7 +3093,7 @@
# octaves = 2,
# persistence = 0.8,
# lacunarity = 2.0,
# flags =
# flags =
# }
## Mapgen Valleys
@ -3183,7 +3183,7 @@
# octaves = 3,
# persistence = 0.5,
# lacunarity = 2.0,
# flags =
# flags =
# }
# Second of two 3D noises that together define tunnels.
@ -3196,7 +3196,7 @@
# octaves = 3,
# persistence = 0.5,
# lacunarity = 2.0,
# flags =
# flags =
# }
# The depth of dirt or other biome filler node.
@ -3222,7 +3222,7 @@
# octaves = 6,
# persistence = 0.63,
# lacunarity = 2.0,
# flags =
# flags =
# }
# Defines large-scale river channel structure.
@ -3274,7 +3274,7 @@
# octaves = 6,
# persistence = 0.8,
# lacunarity = 2.0,
# flags =
# flags =
# }
# Amplifies the valleys.
@ -3313,7 +3313,7 @@
# octaves = 2,
# persistence = 0.8,
# lacunarity = 2.0,
# flags =
# flags =
# }
## Advanced
@ -3374,4 +3374,3 @@
# so see a full list at https://content.minetest.net/help/content_flags/
# type: string
# contentdb_flag_blacklist = nonfree, desktop_default

View File

@ -360,7 +360,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("default_password", "");
settings->setDefault("default_privs", "interact, shout");
settings->setDefault("enable_pvp", "true");
settings->setDefault("enable_mod_channels", "false");
settings->setDefault("enable_mod_channels", "true");
settings->setDefault("disallow_empty_password", "false");
settings->setDefault("disable_anticheat", "false");
settings->setDefault("enable_rollback_recording", "false");
@ -383,7 +383,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("max_block_send_distance", "10");
settings->setDefault("block_send_optimize_distance", "4");
settings->setDefault("server_side_occlusion_culling", "true");
settings->setDefault("csm_restriction_flags", "62");
settings->setDefault("csm_restriction_flags", "60");
settings->setDefault("csm_restriction_noderange", "0");
settings->setDefault("max_clearobjects_extra_loaded_blocks", "4096");
settings->setDefault("time_speed", "72");