add lua sandbox, tabs, stuff
This commit is contained in:
parent
2593c55ee3
commit
fa1f4894af
|
@ -0,0 +1,133 @@
|
||||||
|
--[[
|
||||||
|
computers.sandbox code is from https://github.com/kikito/lua-sandbox
|
||||||
|
License MIT 2013 Enrique García Cota
|
||||||
|
additionally modified by wsor to work with lua 5.1/luajit
|
||||||
|
modifications include:
|
||||||
|
* additional protections
|
||||||
|
* tweaking of structure
|
||||||
|
* modifications to the default env
|
||||||
|
]]
|
||||||
|
computers.sandbox = {}
|
||||||
|
computers.api = {}
|
||||||
|
|
||||||
|
--default sandbox enviroment used if none is provided
|
||||||
|
computers.sandbox.default_env = {}
|
||||||
|
|
||||||
|
([[
|
||||||
|
_VERSION assert error ipairs next pairs
|
||||||
|
select tonumber tostring type unpack
|
||||||
|
|
||||||
|
math.abs math.acos math.asin math.atan math.atan2 math.ceil
|
||||||
|
math.cos math.cosh math.deg math.exp math.fmod math.floor
|
||||||
|
math.frexp math.huge math.ldexp math.log math.log10 math.max
|
||||||
|
math.min math.modf math.pi math.pow math.rad math.random
|
||||||
|
math.sin math.sinh math.sqrt math.tan math.tanh
|
||||||
|
|
||||||
|
os.clock os.difftime os.time
|
||||||
|
|
||||||
|
string.byte string.char string.format
|
||||||
|
string.len string.lower string.reverse
|
||||||
|
string.sub string.upper
|
||||||
|
|
||||||
|
table.insert table.maxn table.remove table.sort table.concat
|
||||||
|
]]):gsub('%S+', function(id)
|
||||||
|
local module, method = id:match('([^%.]+)%.([^%.]+)')
|
||||||
|
if module then
|
||||||
|
computers.sandbox.default_env[module] = computers.sandbox.default_env[module] or {}
|
||||||
|
computers.sandbox.default_env[module][method] = _G[module][method]
|
||||||
|
else
|
||||||
|
computers.sandbox.default_env[id] = _G[id]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
--takes a code string, returns a sandboxed function to exicute
|
||||||
|
function computers.sandbox.loadstring(code, options)
|
||||||
|
local defaults = {
|
||||||
|
env = table.copy(computers.sandbox.default_env),
|
||||||
|
quota = 5000
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options or {}
|
||||||
|
for k, v in pairs(defaults) do
|
||||||
|
if not options[k] then options[k] = v end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(type(code) == "string", "[computers.sandbox]: passed is not a string")
|
||||||
|
|
||||||
|
local env = options.env
|
||||||
|
env._G = env
|
||||||
|
|
||||||
|
if code:byte(1) == 27 then
|
||||||
|
minetest.log("warning", "[computers.sandbox]: attempted bytecode execution termminated")
|
||||||
|
return nil, "bytecode disallowed"
|
||||||
|
end
|
||||||
|
|
||||||
|
local f, err = loadstring(code)
|
||||||
|
if not f then return nil, err end
|
||||||
|
setfenv(f, env)
|
||||||
|
|
||||||
|
if jit then jit.off(f, true) end
|
||||||
|
|
||||||
|
local function timeout()
|
||||||
|
debug.sethook()
|
||||||
|
error("quota exceeded: " .. tostring(options.quota))
|
||||||
|
end
|
||||||
|
|
||||||
|
return function(...)
|
||||||
|
debug.sethook(timeout, "", options.quota)
|
||||||
|
local string_metatable = getmetatable("")
|
||||||
|
assert(string_metatable.__index == string, "[computers.sandbox]: error with the string metatable")
|
||||||
|
string_metatable.__index = env.string
|
||||||
|
|
||||||
|
local status, ret = pcall(f, ...)
|
||||||
|
|
||||||
|
debug.sethook()
|
||||||
|
string_metatable.__index = string
|
||||||
|
|
||||||
|
if not status then error(ret) end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
--supports string input or table env input with a default env of the base
|
||||||
|
function computers.sandbox.merge_env(nenv, denv)
|
||||||
|
local base_env = table.copy(denv or computers.sandbox.default_env)
|
||||||
|
|
||||||
|
if type(nenv) == "table" then
|
||||||
|
for k, v in pairs(nenv) do
|
||||||
|
base_env[k] = base_env[k] or {}
|
||||||
|
for key, value in pairs(v) do
|
||||||
|
base_env[k][key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif type(nenv) == "string" then
|
||||||
|
nenv:gsub('%S+', function(id)
|
||||||
|
local module, method = id:match('([^%.]+)%.([^%.]+)')
|
||||||
|
if module then
|
||||||
|
base_env[module] = base_env[module] or {}
|
||||||
|
base_env[module][method] = _G[module][method]
|
||||||
|
else
|
||||||
|
base_env[id] = _G[id]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return base_env
|
||||||
|
end
|
||||||
|
|
||||||
|
function computers.api.get_dir_keyed_list(path, is_dir)
|
||||||
|
local files = minetest.get_dir_list(path, is_dir)
|
||||||
|
local keyed = {}
|
||||||
|
for _, file in pairs(files) do
|
||||||
|
keyed[file] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return keyed
|
||||||
|
end
|
||||||
|
|
||||||
|
function computers.api.chat_send_player(player, msg)
|
||||||
|
local name = player
|
||||||
|
if type(name) == "userdata" then name = player:get_player_name() end
|
||||||
|
minetest.chat_send_player(name, msg)
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "exit",
|
||||||
|
"license": "MIT",
|
||||||
|
"version": 0.1,
|
||||||
|
"engine": 0.42,
|
||||||
|
"help": "exits the computer"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
function exit(pos, input, data)
|
||||||
|
minetest.close_formspec(data.player:get_player_name(), "")
|
||||||
|
|
||||||
|
return "you shouldnt see this"
|
||||||
|
end
|
||||||
|
|
||||||
|
return exit
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "expr",
|
||||||
|
"license": "MIT",
|
||||||
|
"version": 0.1,
|
||||||
|
"engine": 0.42,
|
||||||
|
"help": "exicutes mathmatical expresions"
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
function expr(pos, input, data)
|
||||||
|
local output
|
||||||
|
local func = computers.sandbox.loadstring("return " .. input, {env={}})
|
||||||
|
|
||||||
|
if func then output = func() end
|
||||||
|
|
||||||
|
if type(output) ~= "number" or output == nil then
|
||||||
|
return "Error: invalid or missing input"
|
||||||
|
end
|
||||||
|
|
||||||
|
return tostring(output)
|
||||||
|
end
|
||||||
|
|
||||||
|
return expr
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "nano",
|
||||||
|
"license": "MIT",
|
||||||
|
"version": 0.1,
|
||||||
|
"engine": 0.42,
|
||||||
|
"help": "allows editing of files"
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
function nano(pos, input, data)
|
||||||
|
local path = computers.devicepath .. "/" .. minetest.hash_node_position(pos) .. data.element.pwd
|
||||||
|
local files = computers.api.get_dir_keyed_list(path, false)
|
||||||
|
|
||||||
|
if not input then return "no file designated" end
|
||||||
|
if not files[input:split(" ")[1]] then return "file " .. input:split(" ")[1] .. " not found" end
|
||||||
|
if computers.formspec.get_index_by_name(
|
||||||
|
computers.formspec.registered_kast[data.player:get_player_name()],
|
||||||
|
"nano_btn"
|
||||||
|
) then return "nano is already open, please close it" end
|
||||||
|
|
||||||
|
local f = io.open(path .. "/" .. input:split(" ")[1])
|
||||||
|
if not f then return "error reading file" end
|
||||||
|
local file = f:read("*all")
|
||||||
|
f:close()
|
||||||
|
|
||||||
|
--clean up if there exists a legacy nano_ctn
|
||||||
|
local nctn = computers.formspec.get_index_by_name(
|
||||||
|
computers.formspec.registered_kast[data.player:get_player_name()],
|
||||||
|
"nano_ctn"
|
||||||
|
)
|
||||||
|
if nctn then
|
||||||
|
local fs = computers.formspec.registered_kast[data.player:get_player_name()]
|
||||||
|
table.remove(fs, nctn)
|
||||||
|
computers.formspec.registered_kast[data.player:get_player_name()] = fs
|
||||||
|
minetest.chat_send_all(dump(fs))
|
||||||
|
end
|
||||||
|
|
||||||
|
local form = computers.gui.add_tab(data.player, "Nano", {
|
||||||
|
type = "container",
|
||||||
|
name = "nano_ctn",
|
||||||
|
state = 1,
|
||||||
|
x = 0,
|
||||||
|
y = 1,
|
||||||
|
{
|
||||||
|
type = "background",
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
w = 10,
|
||||||
|
h = 11,
|
||||||
|
texture_name = "[combine:16x16^[noalpha"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type = "textarea",
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
w = 10,
|
||||||
|
h = 10,
|
||||||
|
name = "nano_editor",
|
||||||
|
--read_only = 1,
|
||||||
|
--label = "test",
|
||||||
|
default = file,
|
||||||
|
close_on_enter = false,
|
||||||
|
--this breaks the scrollbar
|
||||||
|
--[[ props = {
|
||||||
|
border = false,
|
||||||
|
} ]]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type = "box",
|
||||||
|
x = 0,
|
||||||
|
y = 10,
|
||||||
|
w = 10,
|
||||||
|
h = 1,
|
||||||
|
color = "#ffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type = "field",
|
||||||
|
x = 0,
|
||||||
|
y = 10,
|
||||||
|
w = 10,
|
||||||
|
h = 1,
|
||||||
|
name = "nano_commands",
|
||||||
|
close_on_enter = false,
|
||||||
|
pwd = "",
|
||||||
|
props = {
|
||||||
|
border = false,
|
||||||
|
},
|
||||||
|
on_event = function(form, player, element, value, fields)
|
||||||
|
if value == "save" and fields.nano_editor then
|
||||||
|
--minetest.chat_send_all(fields.nano_editor)
|
||||||
|
if #fields.nano_editor < 12000 then --12000 is luacheck line length of 120 * 100 lines
|
||||||
|
minetest.safe_file_write(path .. "/" .. input:split(" ")[1], fields.nano_editor)
|
||||||
|
else
|
||||||
|
computers.api.chat_send_player(player, minetest.colorize("red", "[Nano]: file is to long"))
|
||||||
|
end
|
||||||
|
elseif value == "exit" then
|
||||||
|
local btn = computers.formspec.get_index_by_name(form, "nano_btn")
|
||||||
|
local ctn = computers.formspec.get_index_by_name(form, "nano_ctn")
|
||||||
|
form[ctn].state = 1
|
||||||
|
table.remove(form, btn)
|
||||||
|
--table.remove(form, ctn) cant actually remove it from within itself, therefore we hide it
|
||||||
|
local tindex = 0
|
||||||
|
for index, tab in pairs(form.tabs) do
|
||||||
|
if tab == "nano" then tindex = index end
|
||||||
|
end
|
||||||
|
table.remove(form.tabs, tindex)
|
||||||
|
tindex = computers.formspec.get_index_by_name(form, "terminal_ctn")
|
||||||
|
|
||||||
|
form[tindex].state = 0
|
||||||
|
else
|
||||||
|
computers.api.chat_send_player(player, minetest.colorize("red", "[Nano]: invalid command"))
|
||||||
|
end
|
||||||
|
|
||||||
|
return form
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return form
|
||||||
|
end
|
||||||
|
|
||||||
|
return nano
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "touch",
|
"name": "touch",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": 0.2,
|
"version": 0.3,
|
||||||
"engine": 0.4,
|
"engine": 0.42,
|
||||||
"help": "creates a file in the current path"
|
"help": "creates a file in the current path"
|
||||||
}
|
}
|
|
@ -3,11 +3,14 @@ function touch(pos, input, data)
|
||||||
--make dir to be safe
|
--make dir to be safe
|
||||||
minetest.mkdir(path)
|
minetest.mkdir(path)
|
||||||
|
|
||||||
|
if #minetest.get_dir_list(path, false) > 10
|
||||||
|
and not minetest.check_player_privs(data.player, "computers_filesystem") then
|
||||||
|
return "ERROR: you have reached your max file limit"
|
||||||
|
end
|
||||||
|
|
||||||
if input and input ~= "" and not input:find("/") then
|
if input and input ~= "" and not input:find("/") then
|
||||||
for _, item in pairs(minetest.get_dir_list(path, nil)) do
|
if computers.api.get_dir_keyed_list(path, nil)[input] then
|
||||||
if item == input then
|
return "ERROR: trying to create already existing file/folder"
|
||||||
return "ERROR: trying to create already existing file/folder"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
minetest.safe_file_write(path .. "/" .. input, "")
|
minetest.safe_file_write(path .. "/" .. input, "")
|
||||||
return "file " .. input .. " created"
|
return "file " .. input .. " created"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
local registered_astk = {}
|
|
||||||
computers.formspec = {}
|
computers.formspec = {}
|
||||||
|
computers.formspec.registered_kast = {}
|
||||||
|
|
||||||
computers.formspec.get_element_by_name = formspec_ast.get_element_by_name
|
computers.formspec.get_element_by_name = formspec_ast.get_element_by_name
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ function computers.formspec.show_formspec(player, formname, fs)
|
||||||
playername = player:get_player_name()
|
playername = player:get_player_name()
|
||||||
end
|
end
|
||||||
if type(fs) == "table" then
|
if type(fs) == "table" then
|
||||||
registered_astk[playername] = table.copy(fs)
|
computers.formspec.registered_kast[playername] = table.copy(fs)
|
||||||
formspec = formspec_ast.unparse(computers.formspec.convert_to_ast(fs))
|
formspec = formspec_ast.unparse(computers.formspec.convert_to_ast(fs))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -94,16 +94,38 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||||
if formname ~= "computers:gui" then return end
|
if formname ~= "computers:gui" then return end
|
||||||
local pname = player:get_player_name()
|
local pname = player:get_player_name()
|
||||||
|
|
||||||
if fields.quit then registered_astk[pname] = nil return end
|
if fields.quit then computers.formspec.registered_kast[pname] = nil return end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
trying to figure out what a player actually did can be a mess
|
||||||
|
input can validly be nil for a text field sometimes, othertimes it can come in first
|
||||||
|
when a user has actual selected a button. buttons only come in when selected,
|
||||||
|
so we override whatever the first key is if there a button since we are sure it was pressed
|
||||||
|
since we cant get types from fields, we rely on the button being named name_btn with _btn being a suffix
|
||||||
|
]]
|
||||||
|
|
||||||
local keys = {}
|
local keys = {}
|
||||||
for key, val in pairs(fields) do table.insert(keys, key) end
|
local btn_override
|
||||||
|
for key, val in pairs(fields) do
|
||||||
|
table.insert(keys, key)
|
||||||
|
local split = key:split("_")
|
||||||
|
if #split >= 2 and split[2] == "btn" then btn_override = key end
|
||||||
|
end
|
||||||
|
|
||||||
local element = computers.formspec.get_element_by_name(registered_astk[pname], keys[1])
|
local element = computers.formspec.get_element_by_name(
|
||||||
|
computers.formspec.registered_kast[pname],
|
||||||
|
btn_override or keys[1]
|
||||||
|
)
|
||||||
|
|
||||||
if element and element.on_event then
|
if element and element.on_event then
|
||||||
--on_event(form, player, element)
|
--on_event(form, player, element)
|
||||||
local form = element.on_event(registered_astk[pname], player, element, fields[keys[1]])
|
local form = element.on_event(
|
||||||
|
computers.formspec.registered_kast[pname],
|
||||||
|
player,
|
||||||
|
element,
|
||||||
|
fields[btn_override or keys[1]],
|
||||||
|
fields
|
||||||
|
)
|
||||||
|
|
||||||
if form then computers.formspec.show_formspec(player, formname, form) end
|
if form then computers.formspec.show_formspec(player, formname, form) end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,29 +1,31 @@
|
||||||
--local usage
|
computers.gui = {}
|
||||||
|
|
||||||
local futil = computers.formspec
|
local futil = computers.formspec
|
||||||
|
|
||||||
function computers.load_gui(pos, node, clicker)
|
local function select_btn(form, btn)
|
||||||
--minetest.chat_send_all("test")
|
--to hardcoded
|
||||||
local function select_btn(form, btn)
|
for _, obtn in pairs(form.tabs) do
|
||||||
--to hardcoded
|
local cindex = futil.get_index_by_name(form, obtn .. "_btn")
|
||||||
for _, obtn in pairs({"terminal", "browser"}) do
|
form[cindex].props.bgimg = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff70"
|
||||||
local cindex = futil.get_index_by_name(form, obtn .. "_btn")
|
form[cindex].props.bgimg_hovered = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff90"
|
||||||
form[cindex].props.bgimg = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff70"
|
|
||||||
form[cindex].props.bgimg_hovered = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff90"
|
|
||||||
|
|
||||||
cindex = futil.get_index_by_name(form, obtn .. "_ctn")
|
cindex = futil.get_index_by_name(form, obtn .. "_ctn")
|
||||||
form[cindex].state = 1
|
form[cindex].state = 1
|
||||||
end
|
|
||||||
|
|
||||||
local cindex = futil.get_index_by_name(form, btn .. "_btn")
|
|
||||||
form[cindex].props.bgimg = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff20"
|
|
||||||
form[cindex].props.bgimg_hovered = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff40"
|
|
||||||
|
|
||||||
local aindex = futil.get_index_by_name(form, btn .. "_ctn")
|
|
||||||
form[aindex].state = 0
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local cindex = futil.get_index_by_name(form, btn .. "_btn")
|
||||||
|
form[cindex].props.bgimg = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff20"
|
||||||
|
form[cindex].props.bgimg_hovered = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff40"
|
||||||
|
|
||||||
|
local aindex = futil.get_index_by_name(form, btn .. "_ctn")
|
||||||
|
form[aindex].state = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function computers.gui.load(pos, node, clicker)
|
||||||
|
|
||||||
local formspec = {
|
local formspec = {
|
||||||
formspec_version = 4,
|
formspec_version = 4,
|
||||||
|
tabs = {"terminal", "browser"},
|
||||||
{
|
{
|
||||||
type = "size",
|
type = "size",
|
||||||
w = 10,
|
w = 10,
|
||||||
|
@ -150,6 +152,8 @@ function computers.load_gui(pos, node, clicker)
|
||||||
local output = computers.registered_commands[cdata[1]](pos, cdata[2], pass_table)
|
local output = computers.registered_commands[cdata[1]](pos, cdata[2], pass_table)
|
||||||
if output and type(output) == "string" then
|
if output and type(output) == "string" then
|
||||||
form[cindex][eindex].default = text .. output .. "\n" .. "user:~" .. element.pwd .."$" .." "
|
form[cindex][eindex].default = text .. output .. "\n" .. "user:~" .. element.pwd .."$" .." "
|
||||||
|
elseif output and type(output) == "table" then
|
||||||
|
form = output
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
form[cindex][eindex].default = text .. value ..
|
form[cindex][eindex].default = text .. value ..
|
||||||
|
@ -188,10 +192,31 @@ function computers.load_gui(pos, node, clicker)
|
||||||
h = 2,
|
h = 2,
|
||||||
name = "test_btn",
|
name = "test_btn",
|
||||||
label = "test btn",
|
label = "test btn",
|
||||||
on_event = function(form, player, element)
|
on_event = function(_, player, element)
|
||||||
local cindex = futil.get_index_by_name(form, "browser_ctn")
|
--local cindex = futil.get_index_by_name(form, "browser_ctn")
|
||||||
local eindex = futil.get_index_by_name(form[cindex], "test_btn")
|
--local eindex = futil.get_index_by_name(form[cindex], "test_btn")
|
||||||
form[cindex][eindex] = {type = "label", x=1, y=3, label = "test button label"}
|
--form[cindex][eindex] = {type = "label", x=1, y=3, label = "test button label"}
|
||||||
|
local form = computers.gui.add_tab(player, "Tname", {
|
||||||
|
type = "container",
|
||||||
|
name = "tname_ctn",
|
||||||
|
state = 1,
|
||||||
|
x = 0,
|
||||||
|
y = 1,
|
||||||
|
{
|
||||||
|
type = "background",
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
w = 10,
|
||||||
|
h = 11,
|
||||||
|
texture_name = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff70"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type = "label",
|
||||||
|
x = 1,
|
||||||
|
y = 1.5,
|
||||||
|
label = "Tname pane",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return form
|
return form
|
||||||
end,
|
end,
|
||||||
|
@ -206,4 +231,65 @@ function computers.load_gui(pos, node, clicker)
|
||||||
}
|
}
|
||||||
|
|
||||||
futil.show_formspec(clicker, "computers:gui", formspec)
|
futil.show_formspec(clicker, "computers:gui", formspec)
|
||||||
|
end
|
||||||
|
|
||||||
|
--legacy compat
|
||||||
|
computers.load_gui = computers.gui.load
|
||||||
|
|
||||||
|
--note you can create to many pages thuse overflowing the formspec, need to be fixed
|
||||||
|
function computers.gui.add_tab(player, tname, tab)
|
||||||
|
local name = player
|
||||||
|
if type(player) == "userdata" then name = player:get_player_name() end
|
||||||
|
|
||||||
|
assert(tab, "[computers.sandbox]: new tab for " .. name .. " not found")
|
||||||
|
assert(tab.type == "container", "[computers.sandbox]: invalid new tab format for " .. name)
|
||||||
|
assert(tab.name:split("_")[2] == "ctn", "[computers.sandbox]: invalid tab name for " .. name)
|
||||||
|
assert(tab.x == 0 and tab.y == 1, "[computers.sandbox]: invalid tab name for " .. name)
|
||||||
|
|
||||||
|
|
||||||
|
local formspec = table.copy(computers.formspec.registered_kast[name])
|
||||||
|
local fs = {}
|
||||||
|
local btn
|
||||||
|
|
||||||
|
for key, val in pairs(formspec) do
|
||||||
|
if type(key) == "string" then
|
||||||
|
fs[key] = val
|
||||||
|
elseif type(val) == "table" then
|
||||||
|
if val.name and #val.name:split("_") >= 2 and val.name:split("_")[2] == "btn" then
|
||||||
|
btn = 1
|
||||||
|
elseif btn and btn == 1 then
|
||||||
|
table.insert(fs, {
|
||||||
|
type = "button",
|
||||||
|
x = #formspec.tabs*2,
|
||||||
|
y = 0,
|
||||||
|
w = 2,
|
||||||
|
h = 1,
|
||||||
|
name = tname:lower() .. "_btn",
|
||||||
|
label = tname,
|
||||||
|
on_event = function(form, _, element)
|
||||||
|
select_btn(form, tname:lower())
|
||||||
|
|
||||||
|
return form
|
||||||
|
end,
|
||||||
|
props = {
|
||||||
|
border = false,
|
||||||
|
bgimg = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff70",
|
||||||
|
bgimg_hovered = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff90",
|
||||||
|
bgimg_middle = "4,4",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
btn = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(fs, val)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(fs, tab)
|
||||||
|
table.insert(fs.tabs, tname:lower())
|
||||||
|
|
||||||
|
return fs
|
||||||
end
|
end
|
|
@ -8,7 +8,7 @@ minetest.mkdir(computers.devicepath) --make sure it exists
|
||||||
minetest.mkdir(computers.networkpath) --make sure it exists
|
minetest.mkdir(computers.networkpath) --make sure it exists
|
||||||
|
|
||||||
computers.os = {
|
computers.os = {
|
||||||
version = 0.41,
|
version = 0.42,
|
||||||
name = "kuto",
|
name = "kuto",
|
||||||
authors = {"wsor", "luk3yx"},
|
authors = {"wsor", "luk3yx"},
|
||||||
license = "MIT",
|
license = "MIT",
|
||||||
|
@ -16,6 +16,7 @@ computers.os = {
|
||||||
|
|
||||||
dofile(computers.modpath .. "/old_stuff/init.lua")
|
dofile(computers.modpath .. "/old_stuff/init.lua")
|
||||||
|
|
||||||
|
dofile(computers.modpath .. "/api.lua")
|
||||||
dofile(computers.modpath .. "/formspec.lua")
|
dofile(computers.modpath .. "/formspec.lua")
|
||||||
dofile(computers.modpath .. "/commands.lua")
|
dofile(computers.modpath .. "/commands.lua")
|
||||||
dofile(computers.modpath .. "/gui.lua")
|
dofile(computers.modpath .. "/gui.lua")
|
||||||
|
|
Loading…
Reference in New Issue