fsc: secure formspec code.
Implemented for terminal, and signs, rules, inspector and creator interface code. Also, fix a bazillion usability bugs in here.
This commit is contained in:
parent
43bebe3a7f
commit
44e0ef1647
@ -7,6 +7,7 @@ read_globals = {
|
||||
"dump",
|
||||
"VoxelManip", "VoxelArea",
|
||||
"PseudoRandom", "ItemStack",
|
||||
"SecureRandom",
|
||||
table = { fields = { "copy" } },
|
||||
"unpack",
|
||||
"AreaStore",
|
||||
|
@ -1,4 +1,6 @@
|
||||
|
||||
local callback = {}
|
||||
|
||||
local function register_teleport(name, def)
|
||||
local function teleport_update_particles(pos, pname)
|
||||
local tm = math.random(10, 50) / 10
|
||||
@ -51,17 +53,14 @@ local function register_teleport(name, def)
|
||||
minetest.register_node(name, def)
|
||||
end
|
||||
|
||||
local context = {}
|
||||
|
||||
local function series_progress_reset(player, id)
|
||||
-- show a formspec to the user where they can explicitly reset
|
||||
-- series progress, and explain why they might not want to do so
|
||||
|
||||
local name = player:get_player_name()
|
||||
context[name] = id
|
||||
|
||||
--FIXME change this to a bunch of labels.
|
||||
minetest.show_formspec(name, "series:reset",
|
||||
fsc.show(name,
|
||||
"size[10,7]" ..
|
||||
"textlist[0.5,0.5;8.7,4.5;restettext;You have already completed this series.,," ..
|
||||
"If you want\\, you can reset your progress for this series," ..
|
||||
@ -70,43 +69,31 @@ local function series_progress_reset(player, id)
|
||||
"series later\\, and come back regularly to check if that," ..
|
||||
"has happened.;0;0]" ..
|
||||
"button[0.6,5.5;4.4,1.5;btn_cancel;I'll come back later]" ..
|
||||
"button[5.0,5.5;4.4,1.5;btn_reset;Reset progress]"
|
||||
"button[5.0,5.5;4.4,1.5;btn_reset;Reset progress]",
|
||||
{id = id},
|
||||
callback.progress_reset
|
||||
)
|
||||
end
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "series:reset" then
|
||||
return false
|
||||
end
|
||||
|
||||
function callback.progress_reset(player, fields, context)
|
||||
local name = player:get_player_name()
|
||||
minetest.close_formspec(name, "")
|
||||
|
||||
if not context[name] then
|
||||
log.fs_data(player, name, formname, fields)
|
||||
return true
|
||||
end
|
||||
|
||||
if not fields.btn_reset then
|
||||
context[name] = nil
|
||||
return true
|
||||
end
|
||||
|
||||
-- reset series progress
|
||||
local id = context[name]
|
||||
local pmeta = db.player_get_meta(name)
|
||||
pmeta.series_progress[id] = nil
|
||||
pmeta.series_progress[context] = nil
|
||||
db.player_set_meta(name, pmeta)
|
||||
|
||||
minetest.chat_send_player(player:get_player_name(),
|
||||
"Reset progress for series " .. id)
|
||||
context[name] = nil
|
||||
"Reset progress for series " .. context)
|
||||
|
||||
return true
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
local function series_enter_choice(player, id)
|
||||
local function series_enter_choice(player, id, b_sel)
|
||||
-- Show a formspec allowing the user to choose what box they want to enter
|
||||
local name = player:get_player_name()
|
||||
|
||||
@ -136,47 +123,34 @@ local function series_enter_choice(player, id)
|
||||
f = f .. ";]"
|
||||
f = f .. "button[1.4,8.1;3.4,1;enter;Play]"
|
||||
|
||||
minetest.show_formspec(name, "series:enter_choice", f)
|
||||
fsc.show(name, f, {id = id, boxes = boxes, b_sel = b_sel}, callback.enter_choice)
|
||||
end
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "series:enter_choice" then
|
||||
return false
|
||||
end
|
||||
|
||||
function callback.enter_choice(player, fields, context)
|
||||
local name = player:get_player_name()
|
||||
|
||||
if not context[name] then
|
||||
log.fs_data(player, name, formname, fields)
|
||||
return true
|
||||
end
|
||||
|
||||
if fields.in_series then
|
||||
local i = tonumber(string.match(fields.in_series, ":(%d+)", 1))
|
||||
context[name].b_sel = i
|
||||
series_enter_choice(player, context[name].id)
|
||||
return true
|
||||
if i > #context.boxes or i < 1 then
|
||||
return true
|
||||
end
|
||||
series_enter_choice(player, context.id, i)
|
||||
return
|
||||
end
|
||||
|
||||
if fields.enter then
|
||||
if not context[name].id or not context[name].b_sel then
|
||||
if not context.id or not context.b_sel then
|
||||
return true
|
||||
end
|
||||
minetest.close_formspec(name, "")
|
||||
local i = context[name].b_sel
|
||||
local id = context[name].id
|
||||
local i = context.b_sel
|
||||
local id = context.id
|
||||
local pmeta = db.player_get_meta(name)
|
||||
pmeta.series_progress[id] = i
|
||||
db.player_set_meta(name, pmeta)
|
||||
boxes.next_series(player, id, true)
|
||||
context[name] = nil
|
||||
return true
|
||||
end
|
||||
context[name] = nil
|
||||
|
||||
return true
|
||||
end)
|
||||
|
||||
end
|
||||
|
||||
register_teleport("boxes:enter_teleport", {
|
||||
description = "Enter teleport",
|
||||
@ -195,7 +169,6 @@ register_teleport("boxes:enter_teleport", {
|
||||
return true
|
||||
end
|
||||
elseif smeta.meta.type == db.RANDOM_ACCESS_TYPE then
|
||||
context[player:get_player_name()] = {id = id}
|
||||
series_enter_choice(player, id)
|
||||
return true
|
||||
end
|
||||
@ -340,21 +313,20 @@ local get_sizes = function(player)
|
||||
return {20, 25, 30, 35, 40}
|
||||
end
|
||||
|
||||
local do_series_if = function(player)
|
||||
local do_series_if = function(player, context)
|
||||
local name = player:get_player_name()
|
||||
assert(context[name])
|
||||
assert(context)
|
||||
|
||||
local f =
|
||||
"size[12,9]"
|
||||
local f = "size[12,9]"
|
||||
|
||||
if context[name].exc_sel then
|
||||
if context.exc_sel then
|
||||
f = f .. "button[5.6,3;1,1;add;<<]"
|
||||
end
|
||||
if context[name].inc_sel then
|
||||
if context.inc_sel then
|
||||
f = f .. "button[5.6,4;1,1;remove;>>]"
|
||||
end
|
||||
|
||||
local showall = context[name].showall == "true"
|
||||
local showall = context.showall == "true"
|
||||
if showall then
|
||||
f = f .. "checkbox[6.5,1;showall;Show all boxes;true]"
|
||||
else
|
||||
@ -363,19 +335,25 @@ local do_series_if = function(player)
|
||||
|
||||
f = f .. "dropdown[0.5,1;5.45;series;"
|
||||
local series = db.series_get_series()
|
||||
local s_sel = ""
|
||||
local s_sel
|
||||
local i = 0
|
||||
for k, v in pairs(series) do
|
||||
i = i + 1
|
||||
f = f .. "{" .. v.id .. "} " .. minetest.formspec_escape(v.name)
|
||||
if k ~= #series then
|
||||
f = f .. ","
|
||||
end
|
||||
if context[name].series_id and context[name].series_id == v then
|
||||
s_sel = k
|
||||
if context.series_id and context.series_id == v.id then
|
||||
s_sel = i
|
||||
end
|
||||
end
|
||||
f = f .. ";" .. s_sel .. "]"
|
||||
if s_sel then
|
||||
f = f .. ";" .. s_sel .. "]"
|
||||
else
|
||||
f = f .. ";]"
|
||||
end
|
||||
|
||||
local series_id = context[name].series_id
|
||||
local series_id = context.series_id
|
||||
if series_id then
|
||||
local smeta = db.series_get_meta(series_id)
|
||||
|
||||
@ -390,7 +368,7 @@ local do_series_if = function(player)
|
||||
f = f .. "textlist[0.5,3;5,6;list_included;"
|
||||
|
||||
local boxes = db.series_get_boxes(series_id)
|
||||
context[name].inc = boxes
|
||||
context.inc = boxes
|
||||
for k, box_id in pairs(boxes) do
|
||||
local bmeta = db.box_get_meta(box_id)
|
||||
local bname = bmeta.meta.box_name or "[unnamed]"
|
||||
@ -428,7 +406,7 @@ local do_series_if = function(player)
|
||||
b_size = b_size + 1
|
||||
end
|
||||
end
|
||||
context[name].exc = b
|
||||
context.exc = b
|
||||
for k, box_id in ipairs(b) do
|
||||
local bmeta = db.box_get_meta(box_id)
|
||||
if bmeta.meta.status == db.STATUS_SUBMITTED then
|
||||
@ -447,117 +425,109 @@ local do_series_if = function(player)
|
||||
|
||||
f = f .. ";]"
|
||||
end
|
||||
|
||||
minetest.show_formspec(player:get_player_name(), "boxes:series", f)
|
||||
fsc.show(name, f, context, callback.series)
|
||||
end
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "boxes:series" then
|
||||
return false
|
||||
end
|
||||
function callback.series(player, fields, context)
|
||||
local name = player:get_player_name()
|
||||
|
||||
if fields.quit then
|
||||
context[name] = nil
|
||||
return true
|
||||
end
|
||||
|
||||
-- minimum required permissions
|
||||
if not minetest.check_player_privs(name, "server") then
|
||||
log.fs_data(player, name, formname, fields)
|
||||
context[name] = nil
|
||||
return true
|
||||
end
|
||||
|
||||
if not context[name] then
|
||||
log.fs_data(player, name, formname, fields)
|
||||
context[name] = nil
|
||||
return true
|
||||
end
|
||||
|
||||
if fields.showall then
|
||||
context[name].showall = fields.showall
|
||||
do_series_if(player)
|
||||
return true
|
||||
context.showall = fields.showall
|
||||
do_series_if(player, context)
|
||||
return
|
||||
end
|
||||
if fields.list_included then
|
||||
local i = tonumber(string.match(fields.list_included, ":(%d+)", 1))
|
||||
context[name].inc_sel = context[name].inc[i]
|
||||
do_series_if(player)
|
||||
return true
|
||||
context.inc_sel = context.inc[i]
|
||||
do_series_if(player, context)
|
||||
return
|
||||
end
|
||||
if fields.list_excluded then
|
||||
local i = tonumber(string.match(fields.list_excluded, ":(%d+)", 1))
|
||||
context[name].exc_sel = context[name].exc[i]
|
||||
do_series_if(player)
|
||||
return true
|
||||
context.exc_sel = context.exc[i]
|
||||
do_series_if(player, context)
|
||||
return
|
||||
end
|
||||
if fields.add then
|
||||
if context[name].exc_sel and context[name].series_id then
|
||||
db.series_add_at_end(context[name].series_id, context[name].exc_sel)
|
||||
minetest.log("action", name .. " added box " .. context[name].exc_sel ..
|
||||
" to series " .. context[name].series_id)
|
||||
minetest.chat_send_player(name, " Added box " .. context[name].exc_sel ..
|
||||
" to series " .. context[name].series_id)
|
||||
context[name].exc_sel = nil
|
||||
if context.exc_sel and context.series_id then
|
||||
db.series_add_at_end(context.series_id, context.exc_sel)
|
||||
minetest.log("action", name .. " added box " .. context.exc_sel ..
|
||||
" to series " .. context.series_id)
|
||||
minetest.chat_send_player(name, " Added box " .. context.exc_sel ..
|
||||
" to series " .. context.series_id)
|
||||
context.exc_sel = nil
|
||||
end
|
||||
do_series_if(player)
|
||||
return true
|
||||
do_series_if(player, context)
|
||||
return
|
||||
end
|
||||
if fields.remove then
|
||||
if context[name].inc_sel and context[name].series_id then
|
||||
db.series_delete_box(context[name].series_id, context[name].inc_sel)
|
||||
minetest.log("action", name .. " removed box " .. context[name].inc_sel ..
|
||||
" from series " .. context[name].series_id)
|
||||
minetest.chat_send_player(name, " Removed box " .. context[name].inc_sel ..
|
||||
" from series " .. context[name].series_id)
|
||||
context[name].inc_sel = nil
|
||||
if context.inc_sel and context.series_id then
|
||||
db.series_delete_box(context.series_id, context.inc_sel)
|
||||
minetest.log("action", name .. " removed box " .. context.inc_sel ..
|
||||
" from series " .. context.series_id)
|
||||
minetest.chat_send_player(name, " Removed box " .. context.inc_sel ..
|
||||
" from series " .. context.series_id)
|
||||
context.inc_sel = nil
|
||||
end
|
||||
do_series_if(player)
|
||||
return true
|
||||
do_series_if(player, context)
|
||||
return
|
||||
end
|
||||
|
||||
-- These three fields are dropdowns or text fields, and sent every time
|
||||
-- We need to handle every one of them
|
||||
if fields.series then
|
||||
context[name].series_id = tonumber(string.match(fields.series, "{(%d+)}", 1))
|
||||
context.series_id = tonumber(string.match(fields.series, "{(%d+)}", 1))
|
||||
end
|
||||
if fields.series_type then
|
||||
if context[name].series_id then
|
||||
local smeta = db.series_get_meta(context[name].series_id)
|
||||
if fields.series_type and not fields.key_enter_field then
|
||||
if context.series_id then
|
||||
local smeta = db.series_get_meta(context.series_id)
|
||||
local ntype = smeta.meta.type
|
||||
if fields.series_type == "Sequential" then
|
||||
minetest.chat_send_player(name, "Changing series " .. context.series_id ..
|
||||
" to type Sequential")
|
||||
minetest.log("action", name .. " changes series " .. context.series_id ..
|
||||
" to type Sequential")
|
||||
ntype = db.SEQUENTIAL_TYPE
|
||||
elseif fields.series_type == "Random access" then
|
||||
minetest.chat_send_player(name, "Changing series " .. context.series_id ..
|
||||
" to type Random")
|
||||
minetest.log("action", name .. " changes series " .. context.series_id ..
|
||||
" to type Random")
|
||||
ntype = db.RANDOM_ACCESS_TYPE
|
||||
end
|
||||
smeta.meta.type = ntype
|
||||
db.series_set_meta(context[name].series_id, smeta)
|
||||
db.series_set_meta(context.series_id, smeta)
|
||||
end
|
||||
end
|
||||
if fields.series_name then
|
||||
if context[name].series_id then
|
||||
local smeta = db.series_get_meta(context[name].series_id)
|
||||
if fields.series_name and fields.key_enter_field == "series_name" then
|
||||
if context.series_id then
|
||||
local smeta = db.series_get_meta(context.series_id)
|
||||
smeta.name = fields.series_name
|
||||
db.series_set_meta(context[name].series_id, smeta)
|
||||
db.series_set_meta(context.series_id, smeta)
|
||||
minetest.chat_send_player(name, "Renamed series " .. context.series_id ..
|
||||
" to \"" .. fields.series_name .. "\"")
|
||||
minetest.log("action", name .. " renamed series " .. context.series_id ..
|
||||
" to \"" .. fields.series_name .. "\"")
|
||||
end
|
||||
end
|
||||
if fields.series or fields.series_type or fields.series_name then
|
||||
do_series_if(player)
|
||||
return true
|
||||
do_series_if(player, context)
|
||||
return
|
||||
end
|
||||
|
||||
log.fs_data(player, name, formname, fields)
|
||||
context[name] = nil
|
||||
return true
|
||||
end)
|
||||
end
|
||||
|
||||
do_creator_if = function(player)
|
||||
do_creator_if = function(player, context)
|
||||
local name = player:get_player_name()
|
||||
if not context[name] then
|
||||
context[name] = {}
|
||||
context[name].boxes = get_boxes(name)
|
||||
context[name].sizes = get_sizes(name)
|
||||
if not context.boxes or not context.sizes then
|
||||
context.boxes = get_boxes(name)
|
||||
context.sizes = get_sizes(name)
|
||||
end
|
||||
|
||||
local counts = {
|
||||
@ -571,7 +541,7 @@ do_creator_if = function(player)
|
||||
"field_close_on_enter[input;false]" ..
|
||||
"textlist[0.4,0.5;7.2,7.3;boxes;"
|
||||
--list of boxes owned, or all in case of admin
|
||||
for i, v in ipairs(context[name].boxes) do
|
||||
for i, v in ipairs(context.boxes) do
|
||||
if i > 1 then f = f .. "," end
|
||||
local text = ""
|
||||
if v.status == db.STATUS_SUBMITTED then
|
||||
@ -602,47 +572,46 @@ do_creator_if = function(player)
|
||||
|
||||
if (minetest.check_player_privs(name, "create") and counts.editing + counts.submitted <= limit) or
|
||||
minetest.check_player_privs(name, "server") then
|
||||
context[name].cancreate = true
|
||||
context.cancreate = true
|
||||
f = f .. "button[8.4,1;3.4,1;new;Create new]"
|
||||
elseif not context[name].limitreached then
|
||||
context[name].limitreached = true
|
||||
elseif not context.limitreached then
|
||||
context.limitreached = true
|
||||
minetest.chat_send_player(name, "You have too many unfinished boxes. Box creation will " ..
|
||||
"become available again once your boxes get accepted.")
|
||||
end
|
||||
|
||||
if context[name].box and context[name].box.status == db.STATUS_EDITING then
|
||||
if context.box and context.box.status == db.STATUS_EDITING then
|
||||
f = f .. "button[8.4,3;3.4,1;submit;Submit]"
|
||||
elseif minetest.check_player_privs(name, "server") and context[name].box and
|
||||
context[name].box.status == db.STATUS_SUBMITTED then
|
||||
elseif minetest.check_player_privs(name, "server") and context.box and
|
||||
context.box.status == db.STATUS_SUBMITTED then
|
||||
f = f .. "button[8.4,3;3.4,1;reject;Reject]"
|
||||
f = f .. "button[8.4,4;3.4,1;accept;Accept]"
|
||||
elseif context[name].box and context[name].box.status == db.STATUS_SUBMITTED then
|
||||
elseif context.box and context.box.status == db.STATUS_SUBMITTED then
|
||||
f = f .. "button[8.4,3;3.4,1;retract;Retract]"
|
||||
end
|
||||
|
||||
if not context[name].box or context[name].box.status == db.STATUS_EDITING then
|
||||
if not context.box or context.box.status == db.STATUS_EDITING then
|
||||
f = f .. "button[8.4,7;3.4,1;edit;Edit]"
|
||||
elseif minetest.check_player_privs(name, "server") then
|
||||
f = f .. "button[8.4,7;3.4,1;edit;Force edit]"
|
||||
end
|
||||
|
||||
if context[name].box then
|
||||
if context.box then
|
||||
f = f .. "button[8.4,6;3.4,1;play;Play]"
|
||||
end
|
||||
|
||||
minetest.show_formspec(player:get_player_name(), "boxes:create", f)
|
||||
fsc.show(name, f, context, callback.boxes_create)
|
||||
end
|
||||
|
||||
local do_creator_size_if = function(player)
|
||||
local do_creator_size_if = function(player, context)
|
||||
local name = player:get_player_name()
|
||||
assert(context[name])
|
||||
|
||||
local f =
|
||||
"size[6.2, 6]" ..
|
||||
"field_close_on_enter[input;false]" ..
|
||||
"textlist[0.4,0.5;5.2,4.3;sizes;"
|
||||
|
||||
for i, v in ipairs(context[name].sizes) do
|
||||
for i, v in ipairs(context.sizes) do
|
||||
if i > 1 then f = f .. "," end
|
||||
f = f .. tostring(v)
|
||||
end
|
||||
@ -650,30 +619,15 @@ local do_creator_size_if = function(player)
|
||||
"]" ..
|
||||
"button[0.4,5;5.4,1;create;Choose selected size]"
|
||||
|
||||
minetest.show_formspec(player:get_player_name(), "boxes:create", f)
|
||||
|
||||
fsc.show(name, f, context, callback.boxes_create)
|
||||
end
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "boxes:create" then
|
||||
return false
|
||||
end
|
||||
function callback.boxes_create(player, fields, context)
|
||||
local name = player:get_player_name()
|
||||
|
||||
if fields.quit then
|
||||
return true
|
||||
end
|
||||
|
||||
-- minimum required permissions
|
||||
if not minetest.check_player_privs(name, "create") and
|
||||
not minetest.check_player_privs(name, "server") then
|
||||
log.fs_data(player, name, formname, fields)
|
||||
context[name] = nil
|
||||
return true
|
||||
end
|
||||
|
||||
if not context[name] then
|
||||
log.fs_data(player, name, formname, fields)
|
||||
return true
|
||||
end
|
||||
|
||||
@ -711,38 +665,32 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if fields.boxes and fields.boxes ~= "INV" then
|
||||
local s, _ = fields.boxes:gsub(".*:(.*)", "%1")
|
||||
if not s then
|
||||
log.fs_data(player, name, formname, fields)
|
||||
return true
|
||||
end
|
||||
local n = tonumber(s, 10)
|
||||
if n and context[name].boxes[n] then
|
||||
context[name].box = context[name].boxes[n]
|
||||
do_creator_if(player)
|
||||
if n and context.boxes[n] then
|
||||
context.box = context.boxes[n]
|
||||
do_creator_if(player, context)
|
||||
return
|
||||
else
|
||||
log.fs_data(player, name, formname, fields)
|
||||
return true
|
||||
end
|
||||
elseif fields.play and context[name].box then
|
||||
local id = context[name].box.id
|
||||
elseif fields.play and context.box then
|
||||
local id = context.box.id
|
||||
if is_admin or box_get_status(id) == db.STATUS_ACCEPTED or is_builder(id) then
|
||||
minetest.chat_send_player(name, "Playing box " .. tostring(id))
|
||||
minetest.registered_chatcommands["enter"].func(name, tostring(id))
|
||||
else
|
||||
log.fs_data(player, name, formname, fields)
|
||||
end
|
||||
minetest.close_formspec(name, "")
|
||||
context[name] = nil
|
||||
elseif fields.edit and context[name].box then
|
||||
local id = context[name].box.id
|
||||
return true
|
||||
elseif fields.edit and context.box then
|
||||
local id = context.box.id
|
||||
if is_admin or (box_get_status(id) == db.STATUS_EDITING and is_builder(id)) then
|
||||
minetest.chat_send_player(name, "Editing box " .. tostring(id))
|
||||
minetest.registered_chatcommands["edite"].func(name, tostring(id))
|
||||
else
|
||||
log.fs_data(player, name, formname, fields)
|
||||
end
|
||||
minetest.close_formspec(name, "")
|
||||
context[name] = nil
|
||||
elseif fields.submit and context[name].box then
|
||||
local id = context[name].box.id
|
||||
return true
|
||||
elseif fields.submit and context.box then
|
||||
local id = context.box.id
|
||||
if is_admin or (box_get_status(id) == db.STATUS_EDITING and is_builder(id)) then
|
||||
minetest.chat_send_player(name, "Submitting box " .. tostring(id))
|
||||
minetest.log("action", name .. " submits box " .. tostring(id))
|
||||
@ -750,71 +698,57 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
irc.say(name .. " has submitted a box for review")
|
||||
end
|
||||
box_set_status(id, db.STATUS_SUBMITTED)
|
||||
else
|
||||
log.fs_data(player, name, formname, fields)
|
||||
end
|
||||
minetest.close_formspec(name, "")
|
||||
context[name] = nil
|
||||
elseif fields.accept and context[name].box then
|
||||
local id = context[name].box.id
|
||||
return true
|
||||
elseif fields.accept and context.box then
|
||||
local id = context.box.id
|
||||
if is_admin and box_get_status(id) == db.STATUS_SUBMITTED then
|
||||
minetest.chat_send_player(name, "Accepting box " .. tostring(id))
|
||||
minetest.log("action", name .. " accepts box " .. tostring(id))
|
||||
box_set_status(id, db.STATUS_ACCEPTED)
|
||||
else
|
||||
log.fs_data(player, name, formname, fields)
|
||||
end
|
||||
minetest.close_formspec(name, "")
|
||||
context[name] = nil
|
||||
elseif fields.retract and context[name].box then
|
||||
local id = context[name].box.id
|
||||
return true
|
||||
elseif fields.retract and context.box then
|
||||
local id = context.box.id
|
||||
if is_builder(id) and box_get_status(id) == db.STATUS_SUBMITTED then
|
||||
minetest.chat_send_player(name, "Retracted box " .. tostring(id))
|
||||
minetest.log("action", name .. " retracts box " .. tostring(id))
|
||||
box_set_status(id, db.STATUS_EDITING)
|
||||
else
|
||||
log.fs_data(player, name, formname, fields)
|
||||
end
|
||||
minetest.close_formspec(name, "")
|
||||
context[name] = nil
|
||||
elseif fields.reject and context[name].box then
|
||||
local id = context[name].box.id
|
||||
return true
|
||||
elseif fields.reject and context.box then
|
||||
local id = context.box.id
|
||||
if is_admin and box_get_status(id) == db.STATUS_SUBMITTED then
|
||||
minetest.chat_send_player(name, "Rejecting box " .. tostring(id))
|
||||
minetest.log("action", name .. " rejects box " .. tostring(id))
|
||||
box_set_status(id, db.STATUS_EDITING)
|
||||
else
|
||||
log.fs_data(player, name, formname, fields)
|
||||
end
|
||||
minetest.close_formspec(name, "")
|
||||
context[name] = nil
|
||||
elseif fields.new and context[name].cancreate then
|
||||
do_creator_size_if(player)
|
||||
return true
|
||||
elseif fields.new and context.cancreate then
|
||||
do_creator_size_if(player, context)
|
||||
return
|
||||
elseif fields.series and is_admin then
|
||||
context[name] = {}
|
||||
do_series_if(player)
|
||||
do_series_if(player, {})
|
||||
return
|
||||
elseif fields.sizes then
|
||||
local s, _ = fields.sizes:gsub(".*:(.*)", "%1")
|
||||
context[name].size = context[name].sizes[tonumber(s, 10)]
|
||||
elseif fields.create and context[name] and context[name].size and context[name].cancreate then
|
||||
context.size = context.sizes[tonumber(s, 10)]
|
||||
return
|
||||
elseif fields.create and context.size and context.cancreate then
|
||||
minetest.close_formspec(name, "")
|
||||
minetest.chat_send_player(name, "Creating a new box with size " .. context[name].size)
|
||||
minetest.log("action", name .. " creates a new box with size " .. context[name].size)
|
||||
boxes.make_new(player, context[name].size)
|
||||
context[name] = nil
|
||||
else
|
||||
log.fs_data(player, name, formname, fields)
|
||||
context[name] = nil
|
||||
minetest.chat_send_player(name, "Creating a new box with size " .. context.size)
|
||||
minetest.log("action", name .. " creates a new box with size " .. context.size)
|
||||
boxes.make_new(player, context.size)
|
||||
return true
|
||||
end
|
||||
return true
|
||||
end)
|
||||
end
|
||||
|
||||
register_teleport("boxes:creator_teleport", {
|
||||
description = "Creator teleport",
|
||||
tiles = {"blocks_tiles.png^[sheet:8x8:2,2"},
|
||||
on_teleport = function(pos, node, player)
|
||||
context[player:get_player_name()] = nil
|
||||
do_creator_if(player)
|
||||
do_creator_if(player, {})
|
||||
return true
|
||||
end,
|
||||
})
|
||||
|
8
mods/fsc/.luacheckrc
Normal file
8
mods/fsc/.luacheckrc
Normal file
@ -0,0 +1,8 @@
|
||||
unused_args = false
|
||||
allow_defined_top = true
|
||||
|
||||
read_globals = {
|
||||
"minetest",
|
||||
"SecureRandom",
|
||||
}
|
||||
|
1
mods/fsc/description.txt
Normal file
1
mods/fsc/description.txt
Normal file
@ -0,0 +1 @@
|
||||
Easier method for creating better and more secure formspecs.
|
92
mods/fsc/init.lua
Normal file
92
mods/fsc/init.lua
Normal file
@ -0,0 +1,92 @@
|
||||
|
||||
--[[
|
||||
|
||||
FormSpec Context ('fsc') mod for minetest
|
||||
|
||||
Copyright (C) 2017 Auke Kok <sofar@foo-projects.org>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for
|
||||
any purpose with or without fee is hereby granted, provided that the
|
||||
above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY
|
||||
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
]]--
|
||||
|
||||
fsc = {}
|
||||
|
||||
local _data = {}
|
||||
|
||||
local SRNG = SecureRandom()
|
||||
assert(SRNG)
|
||||
|
||||
local function make_new_random_id()
|
||||
local s = SRNG:next_bytes(16)
|
||||
return s:gsub(".", function(c) return string.format("%02x", string.byte(c)) end)
|
||||
end
|
||||
|
||||
function fsc.show(name, formspec, context, callback)
|
||||
assert(name)
|
||||
assert(formspec)
|
||||
assert(callback)
|
||||
|
||||
if not context then
|
||||
context = {}
|
||||
end
|
||||
|
||||
-- erase old context!
|
||||
local id = "fsc:" .. make_new_random_id()
|
||||
_data[name] = {
|
||||
id = id,
|
||||
name = name,
|
||||
context = context,
|
||||
callback = callback,
|
||||
}
|
||||
|
||||
minetest.show_formspec(name, id, formspec)
|
||||
end
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if not formname:match("fsc:") then
|
||||
return false
|
||||
end
|
||||
|
||||
local name = player:get_player_name()
|
||||
local data = _data[name]
|
||||
if not data then
|
||||
minetest.log("warning", "fsc: no data for formspec sent by " .. name)
|
||||
minetest.close_formspec(name, formname)
|
||||
return
|
||||
end
|
||||
if data.id ~= formname then
|
||||
minetest.log("warning", "fsc: invalid id for formspec sent by " .. name)
|
||||
minetest.close_formspec(name, formname)
|
||||
_data[name] = nil
|
||||
return
|
||||
end
|
||||
if data.name ~= name then
|
||||
minetest.log("error", "fsc: possible hash collision or exploit (name mismatch)")
|
||||
minetest.close_formspec(name, formname)
|
||||
_data[name] = nil
|
||||
return
|
||||
end
|
||||
if data then
|
||||
if data.callback(player, fields, data.context) then
|
||||
minetest.close_formspec(name, formname)
|
||||
_data[name] = nil
|
||||
elseif fields.quit then
|
||||
_data[name] = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
_data[player:get_player_name()] = nil
|
||||
end)
|
||||
|
1
mods/fsc/mod.conf
Normal file
1
mods/fsc/mod.conf
Normal file
@ -0,0 +1 @@
|
||||
name = fsc
|
123
mods/fsc/readme.md
Normal file
123
mods/fsc/readme.md
Normal file
@ -0,0 +1,123 @@
|
||||
|
||||
## fsc
|
||||
|
||||
This mod is designed to help write more secure formspec handling
|
||||
code. It achieves this by throwing out the concept of "formspec
|
||||
names" entirely and giving each formspec shown to the player a unique,
|
||||
random ID. The player can only then submit form data using this unique
|
||||
ID, and, the handling code can invalidate the ID during processing
|
||||
automatically.
|
||||
|
||||
This reduces the risk that an attacker can forge formspec data and
|
||||
send uninvited packets to the server. The server will discard any
|
||||
form data that appears to come from a client that is attempting to
|
||||
use old or incorrect fsc-created forms and will note this event in
|
||||
the minetest log.
|
||||
|
||||
Because of the simplicity of the approach, mods will no longer need
|
||||
to focus on basic formspec handling code and can instead spent their
|
||||
time verifying the proper permissions and input data correctness.
|
||||
|
||||
This mod also provides a much more simple way to maintain a formspec
|
||||
"context" and pass it along to subsequent formspecs. This makes
|
||||
writing formspec code simpler as the context does not need to be
|
||||
maintained outside the formspec handling or creation code, and no
|
||||
memory leakage needs to be worried about.
|
||||
|
||||
A player can also only ever obtain one context, and attempting to
|
||||
use an invalid or outdated context will result in all current valid
|
||||
formspec contexts being revoked. Combined together, all these features
|
||||
make formspecs a lot safer to work with.
|
||||
|
||||
## Usage
|
||||
|
||||
The basic workflow of `fsc` contains of a single function call. Outside
|
||||
of this function call, there are no other API functions or data.
|
||||
|
||||
```
|
||||
function fsc.show(name, formspec, context, callback)
|
||||
-- `name`: a playername,
|
||||
-- `formspec`: a valid formspec string,
|
||||
-- `context`: any data, may be `nil`
|
||||
-- `callback`: function(player, fields, context).
|
||||
```
|
||||
|
||||
The return value of `fsc.show()` is always `nil` - it returns nothing.
|
||||
|
||||
The callback function will only be called if basic sanity checks on the data
|
||||
pass requirements. You can implement it simply as follows:
|
||||
|
||||
```
|
||||
local function callback(player, fields, context)
|
||||
-- `player`: player object,
|
||||
-- `fields`: table containing formspec data returned by the player client,
|
||||
-- `context`: any data, will never be `nil`. If no context was passed
|
||||
-- to `fsc.show()`, it will contain `{}`
|
||||
return true
|
||||
end
|
||||
```
|
||||
|
||||
The return value of the callback may be `nil` or `true`. If you return
|
||||
`nil`, the context is not invalidated, and the player may submit the
|
||||
formspec using the same ID again. This is useful if the player merely
|
||||
selects a list item or otherwise performs an action in the form that
|
||||
does not cause the form to be closed on the client, and you wish to
|
||||
keep the form open.
|
||||
|
||||
If you return `true`, or if you return `nil` and `fields.quit` is set,
|
||||
then the fsc code will invalidate the ID and close the formspec. You
|
||||
should return `true` unless you want to keep the form open to the
|
||||
player.
|
||||
|
||||
Making a simple callback handler that shows a new form is therefore
|
||||
relatively straightforward. The below example passes the current
|
||||
context data through to the new form. The old form will close, and
|
||||
the new form will appear to the player with the new content.
|
||||
|
||||
```
|
||||
local function callback(player, fields, context)
|
||||
local name = player:get_player_name()
|
||||
if fields.rename then
|
||||
fsc.show(name,
|
||||
"field[new_name;What is the new name?;" .. minetest.formspec_escape(context.old_name) .. "]",
|
||||
context,
|
||||
callback)
|
||||
return
|
||||
else
|
||||
-- do something else
|
||||
return true
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
In some cases, you may wish to show a form without having the
|
||||
need for a callback, in case the content is just informational and
|
||||
non-interactive. In that case, you can omit a callback handler by
|
||||
just inserting an empty callback handler, as follows:
|
||||
|
||||
`fsc.show(name, formspec, {}, function() end)`
|
||||
|
||||
## Node Formspecs
|
||||
|
||||
Node formspecs are not handled. Due to the nature of node/inventory
|
||||
formspecs, it is inherently impossible to perform the same checks on
|
||||
node/inventory formspecs as `fsc` can do for (normal) formspecs.
|
||||
|
||||
## License
|
||||
|
||||
FormSpec Context ('fsc') mod for minetest, licensed under the `ISC` license:
|
||||
|
||||
Copyright (C) 2017 Auke Kok <sofar@foo-projects.org>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for
|
||||
any purpose with or without fee is hereby granted, provided that the
|
||||
above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY
|
||||
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
@ -1 +1,2 @@
|
||||
boxes
|
||||
fsc
|
||||
|
@ -131,7 +131,7 @@ minetest.register_tool("inspector:inspector", {
|
||||
minetest.formspec_escape(desc).."]"..
|
||||
"button_exit[2.5,7.5;3,1;close;Close]"
|
||||
|
||||
minetest.show_formspec(user:get_player_name(), "inspector:inspector", formspec)
|
||||
fsc.show(user:get_player_name(), formspec, {}, function() end)
|
||||
end,
|
||||
})
|
||||
|
||||
@ -152,7 +152,7 @@ minetest.register_chatcommand("inspect", {
|
||||
minetest.formspec_escape(desc).."]"..
|
||||
"button_exit[2.5,7.5;3,1;close;Close]"
|
||||
|
||||
minetest.show_formspec(name, "inspector:inspector", formspec)
|
||||
fsc.show(name, formspec, {}, function() end)
|
||||
return true
|
||||
end,
|
||||
})
|
||||
|
@ -3,3 +3,4 @@ music
|
||||
skybox
|
||||
sfinv
|
||||
conf
|
||||
fsc
|
||||
|
@ -5,8 +5,6 @@
|
||||
|
||||
--]]
|
||||
|
||||
local menucontext = {}
|
||||
|
||||
local function toggle_music(player)
|
||||
local m = player:get_attribute("music")
|
||||
if not m or m == "1" then
|
||||
@ -41,6 +39,131 @@ sfinv.register_page("menu:lobby", {
|
||||
end,
|
||||
})
|
||||
|
||||
local function callback_erase(player, fields, context)
|
||||
local name = player:get_player_name()
|
||||
if fields.btn_cancel or fields.quit then
|
||||
return true
|
||||
end
|
||||
if not fields.btn_erase or
|
||||
context ~= tonumber(fields.confirm) or
|
||||
not boxes.players_editing_boxes[name] then
|
||||
return true
|
||||
end
|
||||
|
||||
-- wipe the box
|
||||
local box = boxes.players_editing_boxes[name]
|
||||
minetest.log("action", name .. " erases all content of box " .. box.box_id)
|
||||
|
||||
local minp = vector.add(box.minp, 1)
|
||||
local maxp = vector.add(box.maxp, -1)
|
||||
|
||||
-- wipe nodes with meta separately
|
||||
for _, p in ipairs(minetest.find_nodes_with_meta(minp, maxp)) do
|
||||
minetest.remove_node(p)
|
||||
end
|
||||
|
||||
local cid_air = minetest.get_content_id("air")
|
||||
|
||||
local vm = minetest.get_voxel_manip(minp, maxp)
|
||||
local emin, emax = vm:get_emerged_area()
|
||||
local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax}
|
||||
local vmdata = vm:get_data()
|
||||
local param2 = vm:get_param2_data()
|
||||
|
||||
for z = minp.z, maxp.z do
|
||||
for y = minp.y, maxp.y do
|
||||
local index = va:index(minp.x, y, z)
|
||||
for x = minp.x, maxp.x do
|
||||
vmdata[index] = cid_air
|
||||
param2[index] = 0
|
||||
index = index + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
vm:set_data(vmdata)
|
||||
vm:set_param2_data(param2)
|
||||
vm:update_liquids()
|
||||
vm:write_to_map()
|
||||
vm:update_map()
|
||||
minetest.after(0.1, minetest.fix_light, minp, maxp)
|
||||
|
||||
-- reset pedestal number, build time
|
||||
box.start_edit_time = minetest.get_gametime()
|
||||
local bmeta = db.box_get_meta(box.box_id)
|
||||
bmeta.meta.num_items = 0
|
||||
bmeta.meta.build_time = 0
|
||||
db.box_set_meta(box.box_id, bmeta)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function callback_landscape(player, fields, context)
|
||||
local name = player:get_player_name()
|
||||
if fields.btn_cancel or fields.quit then
|
||||
return true
|
||||
end
|
||||
if not fields.btn_landscape or
|
||||
context ~= tonumber(fields.confirm) or
|
||||
not boxes.players_editing_boxes[name] then
|
||||
return true
|
||||
end
|
||||
|
||||
-- landscape the box
|
||||
local box = boxes.players_editing_boxes[name]
|
||||
minetest.log("action", name .. " landscapes box " .. box.box_id)
|
||||
|
||||
local minp = vector.add(box.minp, 1)
|
||||
local maxp = vector.add(box.maxp, -1)
|
||||
|
||||
local vm = minetest.get_voxel_manip(minp, maxp)
|
||||
local emin, emax = vm:get_emerged_area()
|
||||
local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax}
|
||||
local vmdata = vm:get_data()
|
||||
local param2 = vm:get_param2_data()
|
||||
|
||||
local cursor = 8
|
||||
local stack
|
||||
|
||||
for y = minp.y, maxp.y do
|
||||
local inv = player:get_inventory()
|
||||
while not stack or stack:is_empty() and cursor > 1 do
|
||||
stack = inv:get_stack("landscape", cursor)
|
||||
cursor = cursor - 1
|
||||
end
|
||||
if not stack:is_empty() then
|
||||
local item = stack:take_item()
|
||||
local iname = item:get_name()
|
||||
if iname ~= "torches:torch" and minetest.registered_nodes[iname] then
|
||||
local cid = minetest.get_content_id(iname)
|
||||
for z = minp.z, maxp.z do
|
||||
local index = va:index(minp.x, y, z)
|
||||
for x = minp.x, maxp.x do
|
||||
vmdata[index] = cid
|
||||
param2[index] = 0
|
||||
index = index + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vm:set_data(vmdata)
|
||||
vm:set_param2_data(param2)
|
||||
vm:update_liquids()
|
||||
vm:write_to_map()
|
||||
vm:update_map()
|
||||
minetest.after(0.1, minetest.fix_light, minp, maxp)
|
||||
|
||||
-- reset pedestal number, build time
|
||||
box.start_edit_time = minetest.get_gametime()
|
||||
local bmeta = db.box_get_meta(box.box_id)
|
||||
bmeta.meta.num_items = 0
|
||||
bmeta.meta.build_time = 0
|
||||
db.box_set_meta(box.box_id, bmeta)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
sfinv.register_page("menu:edit", {
|
||||
title = "Game",
|
||||
is_in_nav = function(self, player, context)
|
||||
@ -107,8 +230,7 @@ sfinv.register_page("menu:edit", {
|
||||
if fields.erase then
|
||||
local name = player:get_player_name()
|
||||
local token = math.random(10000, 99999)
|
||||
menucontext[name] = {token = token}
|
||||
minetest.show_formspec(player:get_player_name(), "menu:erase",
|
||||
fsc.show(name,
|
||||
"size[10,7]" ..
|
||||
"textlist[0.5,0.5;8.7,2.0;restettext;" ..
|
||||
"Erasing the content is irreversible\\, and can not," ..
|
||||
@ -117,16 +239,17 @@ sfinv.register_page("menu:edit", {
|
||||
"text box\\,and press \"Erase Everything\".;0;0]" ..
|
||||
"field[4.0,3.5;3.0,1.5;confirm;;]" ..
|
||||
"button[0.6,5.5;4.4,1.5;btn_cancel;Cancel]" ..
|
||||
"button[5.0,5.5;4.4,1.5;btn_erase;Erase everything]"
|
||||
"button[5.0,5.5;4.4,1.5;btn_erase;Erase everything]",
|
||||
token,
|
||||
callback_erase
|
||||
)
|
||||
end
|
||||
if fields.landscape then
|
||||
local name = player:get_player_name()
|
||||
local token = math.random(10000, 99999)
|
||||
menucontext[name] = {token = token}
|
||||
local inv = minetest.get_inventory({type="player", name = name})
|
||||
inv:set_size("landscape", 10)
|
||||
minetest.show_formspec(player:get_player_name(), "menu:landscape",
|
||||
fsc.show(name,
|
||||
"size[10,10]" ..
|
||||
"list[current_player:landscape;landscape;0.2,0;1,8;]" ..
|
||||
"list[current_player;main;1.6,0;8,1;]" ..
|
||||
@ -139,7 +262,9 @@ sfinv.register_page("menu:edit", {
|
||||
"label[1.6,7.6;Put the code \\\"" .. token .. "\\\" in the box here:]" ..
|
||||
"field[6.4,7.4;3.0,1.5;confirm;;]" ..
|
||||
"button[1.4,8.8;4,1.5;btn_cancel;Cancel]" ..
|
||||
"button[5.4,8.8;4,1.5;btn_landscape;Landscape this box]"
|
||||
"button[5.4,8.8;4,1.5;btn_landscape;Landscape this box]",
|
||||
token,
|
||||
callback_landscape
|
||||
)
|
||||
end
|
||||
if fields.music then
|
||||
@ -148,155 +273,6 @@ sfinv.register_page("menu:edit", {
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "menu:erase" then
|
||||
return false
|
||||
end
|
||||
local name = player:get_player_name()
|
||||
if fields.btn_cancel or fields.quit then
|
||||
menucontext[name] = nil
|
||||
minetest.close_formspec(player:get_player_name(), "")
|
||||
return true
|
||||
end
|
||||
if not fields.btn_erase or
|
||||
not menucontext[name] or
|
||||
not menucontext[name].token or
|
||||
menucontext[name].token ~= tonumber(fields.confirm) or
|
||||
not boxes.players_editing_boxes[name] then
|
||||
--FIXME log formspec data?
|
||||
menucontext[name] = nil
|
||||
minetest.close_formspec(player:get_player_name(), "")
|
||||
return true
|
||||
end
|
||||
|
||||
-- wipe the box
|
||||
local box = boxes.players_editing_boxes[name]
|
||||
minetest.log("action", name .. " erases all content of box " .. box.box_id)
|
||||
|
||||
local minp = vector.add(box.minp, 1)
|
||||
local maxp = vector.add(box.maxp, -1)
|
||||
|
||||
-- wipe nodes with meta separately
|
||||
for _, p in ipairs(minetest.find_nodes_with_meta(minp, maxp)) do
|
||||
minetest.remove_node(p)
|
||||
end
|
||||
|
||||
local cid_air = minetest.get_content_id("air")
|
||||
|
||||
local vm = minetest.get_voxel_manip(minp, maxp)
|
||||
local emin, emax = vm:get_emerged_area()
|
||||
local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax}
|
||||
local vmdata = vm:get_data()
|
||||
local param2 = vm:get_param2_data()
|
||||
|
||||
for z = minp.z, maxp.z do
|
||||
for y = minp.y, maxp.y do
|
||||
local index = va:index(minp.x, y, z)
|
||||
for x = minp.x, maxp.x do
|
||||
vmdata[index] = cid_air
|
||||
param2[index] = 0
|
||||
index = index + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
vm:set_data(vmdata)
|
||||
vm:set_param2_data(param2)
|
||||
vm:update_liquids()
|
||||
vm:write_to_map()
|
||||
vm:update_map()
|
||||
minetest.after(0.1, minetest.fix_light, minp, maxp)
|
||||
|
||||
-- reset pedestal number, build time
|
||||
box.start_edit_time = minetest.get_gametime()
|
||||
local bmeta = db.box_get_meta(box.box_id)
|
||||
bmeta.meta.num_items = 0
|
||||
bmeta.meta.build_time = 0
|
||||
db.box_set_meta(box.box_id, bmeta)
|
||||
|
||||
menucontext[name] = nil
|
||||
minetest.close_formspec(player:get_player_name(), "")
|
||||
return true
|
||||
end)
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "menu:landscape" then
|
||||
return false
|
||||
end
|
||||
local name = player:get_player_name()
|
||||
if fields.btn_cancel or fields.quit then
|
||||
menucontext[name] = nil
|
||||
minetest.close_formspec(player:get_player_name(), "")
|
||||
return true
|
||||
end
|
||||
if not fields.btn_landscape or
|
||||
not menucontext[name] or
|
||||
not menucontext[name].token or
|
||||
menucontext[name].token ~= tonumber(fields.confirm) or
|
||||
not boxes.players_editing_boxes[name] then
|
||||
--FIXME log formspec data?
|
||||
menucontext[name] = nil
|
||||
minetest.close_formspec(player:get_player_name(), "")
|
||||
return true
|
||||
end
|
||||
|
||||
-- landscape the box
|
||||
local box = boxes.players_editing_boxes[name]
|
||||
minetest.log("action", name .. " landscapes box " .. box.box_id)
|
||||
|
||||
local minp = vector.add(box.minp, 1)
|
||||
local maxp = vector.add(box.maxp, -1)
|
||||
|
||||
local vm = minetest.get_voxel_manip(minp, maxp)
|
||||
local emin, emax = vm:get_emerged_area()
|
||||
local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax}
|
||||
local vmdata = vm:get_data()
|
||||
local param2 = vm:get_param2_data()
|
||||
|
||||
local cursor = 8
|
||||
local stack
|
||||
|
||||
for y = minp.y, maxp.y do
|
||||
local inv = player:get_inventory()
|
||||
while not stack or stack:is_empty() and cursor > 1 do
|
||||
stack = inv:get_stack("landscape", cursor)
|
||||
cursor = cursor - 1
|
||||
end
|
||||
if not stack:is_empty() then
|
||||
local item = stack:take_item()
|
||||
local iname = item:get_name()
|
||||
if iname ~= "torches:torch" and minetest.registered_nodes[iname] then
|
||||
local cid = minetest.get_content_id(iname)
|
||||
for z = minp.z, maxp.z do
|
||||
local index = va:index(minp.x, y, z)
|
||||
for x = minp.x, maxp.x do
|
||||
vmdata[index] = cid
|
||||
param2[index] = 0
|
||||
index = index + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vm:set_data(vmdata)
|
||||
vm:set_param2_data(param2)
|
||||
vm:update_liquids()
|
||||
vm:write_to_map()
|
||||
vm:update_map()
|
||||
minetest.after(0.1, minetest.fix_light, minp, maxp)
|
||||
|
||||
-- reset pedestal number, build time
|
||||
box.start_edit_time = minetest.get_gametime()
|
||||
local bmeta = db.box_get_meta(box.box_id)
|
||||
bmeta.meta.num_items = 0
|
||||
bmeta.meta.build_time = 0
|
||||
db.box_set_meta(box.box_id, bmeta)
|
||||
|
||||
menucontext[name] = nil
|
||||
minetest.close_formspec(player:get_player_name(), "")
|
||||
return true
|
||||
end)
|
||||
|
||||
|
||||
sfinv.register_page("menu:play", {
|
||||
title = "Game",
|
||||
|
1
mods/rules/depends.txt
Normal file
1
mods/rules/depends.txt
Normal file
@ -0,0 +1 @@
|
||||
fsc
|
@ -246,7 +246,7 @@ function rules.show(name, t)
|
||||
"]" ..
|
||||
"button_exit[0.7,7;11.2,1;close;Close]"
|
||||
|
||||
minetest.show_formspec(name, "rules:", f)
|
||||
fsc.show(name, f, {}, function() end)
|
||||
end
|
||||
|
||||
minetest.register_chatcommand("rules", {
|
||||
|
@ -1,3 +1,4 @@
|
||||
boxes
|
||||
log
|
||||
ranks?
|
||||
fsc
|
||||
|
@ -1,8 +1,6 @@
|
||||
|
||||
signs = {}
|
||||
|
||||
local context = {}
|
||||
|
||||
local function clean_sign_entities(pos)
|
||||
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 0.5)) do
|
||||
if not obj:is_player() and obj:get_luaentity().name == "signs:sign" then
|
||||
@ -113,6 +111,64 @@ local function sign_refresh(pos)
|
||||
return (count > 0)
|
||||
end
|
||||
|
||||
local function text_callback(player, fields, context)
|
||||
local name = player:get_player_name()
|
||||
|
||||
--validate user was allowed to edit signs
|
||||
if not boxes.can_edit(player) then
|
||||
log.fs_data(player, name, "signs:text", fields)
|
||||
return true
|
||||
end
|
||||
|
||||
if not fields.text then
|
||||
if fields.quit then
|
||||
return true
|
||||
end
|
||||
fields.text = ""
|
||||
end
|
||||
|
||||
-- validate length of sign text does not exceed max
|
||||
if string.len(fields.text) > 1000 then
|
||||
fields.text = fields.text:sub(1, 1000)
|
||||
end
|
||||
|
||||
-- check: validate composed texture string does not exceed max
|
||||
-- max 1000 characters, each character can take up to 17 chars to encode
|
||||
-- in tex. So the total size is 17000 + "[combine", which easily fits
|
||||
-- 64k max texture string.
|
||||
|
||||
--validate sign pos is actually within the box that player is in
|
||||
local pos = context
|
||||
if not boxes.validate_pos(player, pos) then
|
||||
return true
|
||||
end
|
||||
|
||||
-- verify node is still a sign
|
||||
local node = minetest.get_node(pos)
|
||||
if node.name ~= "signs:sign" and node.name ~= "signs:sign_wall" then
|
||||
-- sign no longer exists. Could be lag.
|
||||
return true
|
||||
end
|
||||
|
||||
-- erase sign
|
||||
clean_sign_entities(pos)
|
||||
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("text", fields.text)
|
||||
meta:mark_as_private("text")
|
||||
|
||||
-- for lobby signs that need a refresh
|
||||
if minetest.get_node_timer(pos):is_started() then
|
||||
sign_refresh(pos)
|
||||
end
|
||||
|
||||
if fields.text ~= "" then
|
||||
minetest.add_entity(pos, "signs:sign")
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
minetest.register_node("signs:sign", {
|
||||
description = "sign",
|
||||
drawtype = "nodebox",
|
||||
@ -148,13 +204,14 @@ minetest.register_node("signs:sign", {
|
||||
return itemstack
|
||||
end
|
||||
-- show text input form
|
||||
context[name] = pos
|
||||
local text = minetest.get_meta(pos):get_string("text")
|
||||
minetest.show_formspec(clicker:get_player_name(), "signs:text",
|
||||
fsc.show(name,
|
||||
"size[8,8]" ..
|
||||
"textarea[0.5,0.5;7.5,6.5;text;text;" ..
|
||||
minetest.formspec_escape(text) .. "]" ..
|
||||
"button_exit[3.5,7;1,0.5;exit;exit]")
|
||||
"button_exit[3.5,7;1,0.5;exit;exit]",
|
||||
pos,
|
||||
text_callback)
|
||||
end,
|
||||
on_destruct = function(pos)
|
||||
clean_sign_entities(pos)
|
||||
@ -225,13 +282,14 @@ minetest.register_node("signs:sign_wall", {
|
||||
return itemstack
|
||||
end
|
||||
-- show text input form
|
||||
context[name] = pos
|
||||
local text = minetest.get_meta(pos):get_string("text")
|
||||
minetest.show_formspec(clicker:get_player_name(), "signs:text",
|
||||
fsc.show(name,
|
||||
"size[8,8]" ..
|
||||
"textarea[0.5,0.5;7.5,6.5;text;text;" ..
|
||||
minetest.formspec_escape(text) .. "]" ..
|
||||
"button_exit[3.5,7;1,0.5;exit;exit]")
|
||||
"button_exit[3.5,7;1,0.5;exit;exit]",
|
||||
pos,
|
||||
text_callback)
|
||||
end,
|
||||
on_destruct = function(pos)
|
||||
clean_sign_entities(pos)
|
||||
@ -248,69 +306,6 @@ minetest.register_node("signs:sign_wall", {
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "signs:text" then
|
||||
return false
|
||||
end
|
||||
|
||||
local name = player:get_player_name()
|
||||
|
||||
--validate user was allowed to edit signs
|
||||
if not boxes.can_edit(player) or not context[name] then
|
||||
log.fs_data(player, name, formname, fields)
|
||||
return true
|
||||
end
|
||||
|
||||
if not fields.text then
|
||||
if fields.quit then
|
||||
return true
|
||||
end
|
||||
fields.text = ""
|
||||
end
|
||||
|
||||
-- validate length of sign text does not exceed max
|
||||
if string.len(fields.text) > 1000 then
|
||||
fields.text = fields.text:sub(1, 1000)
|
||||
end
|
||||
|
||||
-- check: validate composed texture string does not exceed max
|
||||
-- max 1000 characters, each character can take up to 17 chars to encode
|
||||
-- in tex. So the total size is 17000 + "[combine", which easily fits
|
||||
-- 64k max texture string.
|
||||
|
||||
--validate sign pos is actually within the box that player is in
|
||||
local pos = context[name]
|
||||
if not boxes.validate_pos(player, pos) then
|
||||
return true
|
||||
end
|
||||
|
||||
-- verify node is still a sign
|
||||
local node = minetest.get_node(pos)
|
||||
if node.name ~= "signs:sign" and node.name ~= "signs:sign_wall" then
|
||||
-- sign no longer exists. Could be lag.
|
||||
return true
|
||||
end
|
||||
|
||||
-- erase sign
|
||||
context[name] = nil
|
||||
clean_sign_entities(pos)
|
||||
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("text", fields.text)
|
||||
meta:mark_as_private("text")
|
||||
|
||||
-- for lobby signs that need a refresh
|
||||
if minetest.get_node_timer(pos):is_started() then
|
||||
sign_refresh(pos)
|
||||
end
|
||||
|
||||
if fields.text ~= "" then
|
||||
minetest.add_entity(pos, "signs:sign")
|
||||
end
|
||||
|
||||
return true
|
||||
end)
|
||||
|
||||
local function make_tex(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local text = meta:get_string("dtext")
|
||||
@ -510,6 +505,24 @@ local icons = {
|
||||
[18] = "winksmile",
|
||||
[19] = "xmouth",
|
||||
}
|
||||
|
||||
local function icon_callback(player, fields, context)
|
||||
if not fields.quit then
|
||||
local k, _ = next(fields)
|
||||
local i = tonumber(k) or 0
|
||||
if not icons[i] then
|
||||
return true
|
||||
end
|
||||
|
||||
local pos = context.pos
|
||||
local node = minetest.get_node(pos)
|
||||
node.name = "signs:icon_" .. icons[i]
|
||||
minetest.swap_node(pos, node)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
for k, v in ipairs(icons) do
|
||||
minetest.register_node("signs:icon_".. v, {
|
||||
description = "sign (" .. v .. ")",
|
||||
@ -542,7 +555,6 @@ for k, v in ipairs(icons) do
|
||||
return
|
||||
end
|
||||
local name = box.name -- player name
|
||||
context[name] = pos
|
||||
local form = "size[9,8]\n"
|
||||
for kk, vv in ipairs(icons) do
|
||||
form = form ..
|
||||
@ -558,10 +570,7 @@ for k, v in ipairs(icons) do
|
||||
2.0 * math.floor((kk-1)/5) + 1.5 ..
|
||||
";" .. vv .. "]\n"
|
||||
end
|
||||
minetest.show_formspec(name, "signs:icons", form)
|
||||
--local node = minetest.get_node(pos)
|
||||
--node.name = "signs:icon_" .. icons[i]
|
||||
--minetest.swap_node(pos, node)
|
||||
fsc.show(name, form, {pos = pos}, icon_callback)
|
||||
end,
|
||||
groups = {icon = 1}, -- find icons using group:icon
|
||||
icon_name = v,
|
||||
@ -569,32 +578,6 @@ for k, v in ipairs(icons) do
|
||||
})
|
||||
end
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "signs:icons" then
|
||||
return false
|
||||
end
|
||||
|
||||
local name = player:get_player_name()
|
||||
|
||||
if not fields.quit then
|
||||
local k, _ = next(fields)
|
||||
local i = tonumber(k) or 0
|
||||
if not icons[i] or not context[name] then
|
||||
--FIXME formspec data log
|
||||
minetest.close_formspec(name, "")
|
||||
return true
|
||||
end
|
||||
|
||||
local pos = context[name]
|
||||
local node = minetest.get_node(pos)
|
||||
node.name = "signs:icon_" .. icons[i]
|
||||
minetest.swap_node(pos, node)
|
||||
end
|
||||
|
||||
context[name] = nil
|
||||
minetest.close_formspec(name, "")
|
||||
return true
|
||||
end)
|
||||
|
||||
local stardir = {
|
||||
[0] = {x = 1, y = 0, z = 0},
|
||||
|
@ -1,3 +1,4 @@
|
||||
mech
|
||||
rules
|
||||
log
|
||||
fsc
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
]]--
|
||||
|
||||
local context = {}
|
||||
local term = {}
|
||||
|
||||
local function get_cmd_params(line)
|
||||
local cmd = ""
|
||||
@ -22,7 +22,7 @@ local function get_cmd_params(line)
|
||||
return cmd, params
|
||||
end
|
||||
|
||||
local help = {
|
||||
term.help = {
|
||||
append = "append text to a file",
|
||||
clear = "clear the output",
|
||||
echo = "echoes the input back to you",
|
||||
@ -36,7 +36,24 @@ local help = {
|
||||
edit = "edits a file in an editor",
|
||||
}
|
||||
|
||||
local commands = {
|
||||
local function make_formspec(output, prompt)
|
||||
local f =
|
||||
"size[12,8]" ..
|
||||
"field_close_on_enter[input;false]" ..
|
||||
"textlist[0.4,0.5;11,6;output;"
|
||||
|
||||
local c = 1
|
||||
for part in output:gmatch("[^\r\n]+") do
|
||||
f = f .. minetest.formspec_escape(part) .. ","
|
||||
c = c + 1
|
||||
end
|
||||
f = f .. minetest.formspec_escape(prompt) .. ";" .. c .. ";false]"
|
||||
|
||||
f = f .. "field[0.7,7;11.2,1;input;;]"
|
||||
return f
|
||||
end
|
||||
|
||||
term.commands = {
|
||||
clear = function(output, params, c)
|
||||
return ""
|
||||
end,
|
||||
@ -93,11 +110,12 @@ local commands = {
|
||||
end
|
||||
end
|
||||
|
||||
minetest.show_formspec(c.name, "terminal:edit",
|
||||
"size[12,8]" ..
|
||||
fsc.show(c.name, "size[12,8]" ..
|
||||
"textarea[0.5,0.5;11.5,7.0;text;text;" ..
|
||||
minetest.formspec_escape(text) .. "]" ..
|
||||
"button_exit[5.2,7.2;1.6,0.5;exit;Save]")
|
||||
"button_exit[5.2,7.2;1.6,0.5;exit;Save]",
|
||||
c,
|
||||
term.edit)
|
||||
|
||||
return false
|
||||
end,
|
||||
@ -173,15 +191,15 @@ local commands = {
|
||||
help = function(output, params, c)
|
||||
if params ~= "" then
|
||||
local h, _ = get_cmd_params(params)
|
||||
if help[h] then
|
||||
return output .. "\n" .. help[h]
|
||||
if term.help[h] then
|
||||
return output .. "\n" .. term.help[h]
|
||||
else
|
||||
return output .. "\nError: No help for \"" .. h .. "\""
|
||||
end
|
||||
end
|
||||
local o = ""
|
||||
local ot = {}
|
||||
for k, _ in pairs(help) do
|
||||
for k, _ in pairs(term.help) do
|
||||
ot[#ot + 1] = k
|
||||
end
|
||||
table.sort(ot)
|
||||
@ -195,76 +213,12 @@ local commands = {
|
||||
end,
|
||||
}
|
||||
|
||||
local function make_formspec(output, prompt)
|
||||
local f =
|
||||
"size[12,8]" ..
|
||||
"field_close_on_enter[input;false]" ..
|
||||
"textlist[0.4,0.5;11,6;output;"
|
||||
|
||||
local c = 1
|
||||
for part in output:gmatch("[^\r\n]+") do
|
||||
f = f .. minetest.formspec_escape(part) .. ","
|
||||
c = c + 1
|
||||
end
|
||||
f = f .. minetest.formspec_escape(prompt) .. ";" .. c .. ";false]"
|
||||
|
||||
f = f .. "field[0.7,7;11.2,1;input;;]"
|
||||
return f
|
||||
end
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "terminal:edit" then
|
||||
return false
|
||||
end
|
||||
|
||||
if not fields.text then
|
||||
return true
|
||||
end
|
||||
|
||||
local name = player:get_player_name()
|
||||
local c = context[name]
|
||||
if not c or not c.pos or not c.output then
|
||||
log.fs_data(player, name, formname, fields)
|
||||
return true
|
||||
end
|
||||
local output = c.output
|
||||
|
||||
if not c.what then
|
||||
output = output .. "\n" .. "Error: no such file\n"
|
||||
minetest.show_formspec(name, "terminal:", make_formspec(output, "> "))
|
||||
return true
|
||||
end
|
||||
|
||||
local meta = minetest.get_meta(c.pos)
|
||||
local meta_files = meta:get_string("files")
|
||||
local files
|
||||
files = minetest.parse_json(meta_files) or {}
|
||||
files[c.what] = fields.text
|
||||
|
||||
-- validate it fits
|
||||
local json = minetest.write_json(files)
|
||||
if string.len(json) < 49152 then
|
||||
meta:set_string("files", json)
|
||||
meta:mark_as_private("files")
|
||||
output = output .. "\n" .. "Wrote: " .. c.what .. "\n"
|
||||
else
|
||||
output = output .. "\n" .. "Error: no space left on device\n"
|
||||
end
|
||||
|
||||
minetest.show_formspec(name, "terminal:", make_formspec(output, "> "))
|
||||
return true
|
||||
end)
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "terminal:" then
|
||||
return false
|
||||
end
|
||||
|
||||
function term.recv(player, fields, context)
|
||||
-- input validation
|
||||
local name = player:get_player_name()
|
||||
local c = context[name]
|
||||
local c = context
|
||||
if not c or not c.pos then
|
||||
log.fs_data(player, name, formname, fields)
|
||||
log.fs_data(player, name, "terminal:", fields)
|
||||
return true
|
||||
end
|
||||
|
||||
@ -280,8 +234,11 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
output = output .. "\n" .. line
|
||||
output = output .. "\nError: no write access"
|
||||
c.output = output
|
||||
minetest.show_formspec(name, "terminal:", make_formspec(output, "> "))
|
||||
return true
|
||||
fsc.show(name,
|
||||
make_formspec(output, "> "),
|
||||
c,
|
||||
term.recv)
|
||||
return
|
||||
end
|
||||
-- are we writing a file?
|
||||
if line == "STOP" then
|
||||
@ -289,8 +246,11 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
c.writing = nil
|
||||
output = output .. "\n" .. line
|
||||
c.output = output
|
||||
minetest.show_formspec(name, "terminal:", make_formspec(output, "> "))
|
||||
return true
|
||||
fsc.show(name,
|
||||
make_formspec(output, "> "),
|
||||
c,
|
||||
term.recv)
|
||||
return
|
||||
end
|
||||
local meta = minetest.get_meta(c.pos)
|
||||
local meta_files = meta:get_string("files")
|
||||
@ -318,7 +278,11 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
output = output .. "\n" .. "Error: maximum file length exceeded"
|
||||
end
|
||||
c.output = output
|
||||
minetest.show_formspec(name, "terminal:", make_formspec(output, ""))
|
||||
fsc.show(name,
|
||||
make_formspec(output, ""),
|
||||
c,
|
||||
term.recv)
|
||||
return
|
||||
else
|
||||
-- else parse cmd
|
||||
output = output .. "\n> " .. line
|
||||
@ -328,11 +292,14 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if meta:get_int("locked") == 1 and cmd ~= "unlock" then
|
||||
output = output .. "\nError: Terminal locked, type \"unlock\" to unlock it"
|
||||
c.output = output
|
||||
minetest.show_formspec(name, "terminal:", make_formspec(output, "> "))
|
||||
return true
|
||||
fsc.show(name,
|
||||
make_formspec(output, "> "),
|
||||
c,
|
||||
term.recv)
|
||||
return
|
||||
end
|
||||
|
||||
local fn = commands[cmd]
|
||||
local fn = term.commands[cmd]
|
||||
if fn then
|
||||
output = fn(output, params, c)
|
||||
else
|
||||
@ -340,25 +307,76 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
end
|
||||
if output ~= false then
|
||||
c.output = output
|
||||
minetest.show_formspec(name, "terminal:", make_formspec(output, "> "))
|
||||
fsc.show(name,
|
||||
make_formspec(output, "> "),
|
||||
c,
|
||||
term.recv)
|
||||
end
|
||||
return
|
||||
end
|
||||
elseif fields.quit then
|
||||
context[name] = nil
|
||||
minetest.sound_play("terminal_power_off", {pos = c.pos})
|
||||
else
|
||||
log.fs_data(player, name, formname, fields)
|
||||
return true
|
||||
elseif fields.output then
|
||||
-- CHG events - do not return true
|
||||
return
|
||||
end
|
||||
|
||||
log.fs_data(player, name, "terminal:", fields)
|
||||
return true
|
||||
end
|
||||
|
||||
function term.edit(player, fields, context)
|
||||
if not fields.text then
|
||||
return true
|
||||
end
|
||||
return true
|
||||
end)
|
||||
|
||||
local name = player:get_player_name()
|
||||
local c = context
|
||||
if not c or not c.pos or not c.output then
|
||||
log.fs_data(player, name, "terminal:", fields)
|
||||
return true
|
||||
end
|
||||
local output = c.output
|
||||
|
||||
if not c.what then
|
||||
output = output .. "\n" .. "Error: no such file\n"
|
||||
fsc.show(name,
|
||||
make_formspec(output, "> "),
|
||||
c,
|
||||
term.recv)
|
||||
return
|
||||
end
|
||||
|
||||
local meta = minetest.get_meta(c.pos)
|
||||
local meta_files = meta:get_string("files")
|
||||
local files
|
||||
files = minetest.parse_json(meta_files) or {}
|
||||
files[c.what] = fields.text
|
||||
|
||||
-- validate it fits
|
||||
local json = minetest.write_json(files)
|
||||
if string.len(json) < 49152 then
|
||||
meta:set_string("files", json)
|
||||
meta:mark_as_private("files")
|
||||
output = output .. "\n" .. "Wrote: " .. c.what .. "\n"
|
||||
else
|
||||
output = output .. "\n" .. "Error: no space left on device\n"
|
||||
end
|
||||
|
||||
fsc.show(name,
|
||||
make_formspec(output, "> "),
|
||||
c,
|
||||
term.recv)
|
||||
return
|
||||
end
|
||||
|
||||
local terminal_use = function(pos, node, clicker, itemstack, pointed_thing)
|
||||
if not clicker then
|
||||
return
|
||||
end
|
||||
local name = clicker:get_player_name()
|
||||
context[name] = {
|
||||
local context = {
|
||||
pos = pos,
|
||||
rw = false,
|
||||
output = "",
|
||||
@ -367,10 +385,13 @@ local terminal_use = function(pos, node, clicker, itemstack, pointed_thing)
|
||||
if boxes.players_editing_boxes[name] or
|
||||
(not boxes.players_in_boxes[name] and minetest.check_player_privs(clicker, "server")) then
|
||||
-- allow rw access
|
||||
context[name].rw = true
|
||||
context.rw = true
|
||||
end
|
||||
-- send formspec to player
|
||||
minetest.show_formspec(name, "terminal:", make_formspec("", "> "))
|
||||
fsc.show(name,
|
||||
make_formspec("", "> "),
|
||||
context,
|
||||
term.recv)
|
||||
minetest.sound_play("terminal_power_on", {pos = pos})
|
||||
-- trigger on first use
|
||||
local meta = minetest.get_meta(pos)
|
||||
@ -380,7 +401,6 @@ local terminal_use = function(pos, node, clicker, itemstack, pointed_thing)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
minetest.register_node("terminal:terminal", {
|
||||
description = "An interactive terminal console emulator access interface unit controller",
|
||||
drawtype = "mesh",
|
||||
|
Loading…
x
Reference in New Issue
Block a user