Rewrite entire mod

Now far more efficient, and prepared for the addition of other computer-related items and computers.
This commit is contained in:
octacian 2017-01-29 15:18:49 -08:00
parent 78b1b836a7
commit de79747243
14 changed files with 1212 additions and 294 deletions

View File

@ -1,8 +0,0 @@
-- bios os config
clear = "clear" -- clear command
off = "shutdown" -- shutdown command
reboot = "reboot" -- reboot command
digiline = false -- do not support digilines
network = false -- do not support network
on = "rightclick" -- on command (rightclick)
clear_on_close = false -- do not clear output on close

View File

@ -1,2 +1,12 @@
set_output(get_output()..get_field("input").."\n"..get_attr("name")..":~$ ") -- print input
refresh() -- refresh
local input = get_attr("input")
if input ~= "" then
print(input, false) -- print input
local ok, res = run(input)
if res then print(res) end
print(get_os("prefix")) -- Print prefix
set_input("")
refresh() -- refresh
end

View File

@ -1 +1,11 @@
set_output("Welcome to BiosOS version 0.1.\n\n"..get_attr("name")..":~$ ") -- print welcome
-- Set OS values
set_os("clear", "clear")
set_os("off", "shutdown")
set_os("reboot", "shutdown -r")
set_os("prefix", get_attr("name")..":~$ ")
-- Set initial output value
set_output("Welcome to BiosOS version 0.1.\n\n"..get_os("prefix")) -- print welcome
-- Refresh view
refresh()

123
builtin.lua Normal file
View File

@ -0,0 +1,123 @@
-- digicompute/builtin.lua
digicompute.builtin = {}
local builtin = digicompute.builtin
-- [function] check if file exists
function builtin.exists(path)
local f = io.open(path, "r") -- open file
if f ~= nil then f:close() return true end
end
-- [function] list contents
function builtin.list(path)
local files = minetest.get_dir_list(path, false)
local subdirs = minetest.get_dir_list(path, true)
return {
files = files or nil,
subdirs = subdirs or nil,
}
end
-- [function] create file
function builtin.create(path)
local f = io.open(path, "w") -- create file
f:close() -- close file
return true
end
-- [function] write to file
function builtin.write(path, data, mode)
if mode ~= "w" and mode ~= "a" then
mode = "w"
end
local f = io.open(path, mode) -- open file for writing
f:write(data) -- write data
f:close() -- close file
return true
end
-- [function] read file
function builtin.read(path)
local f = io.open(path, "r") -- open file for reading
if f then
local data = f:read("*all") -- read and store all data
return data -- return file contents
end
end
-- [function] copy file
function builtin.copy(original, new)
local original = builtin.read(original) -- read
if original then
builtin.write(new, original) -- write
return true
end
end
-- [function] create directory
function builtin.mkdir(path)
if not io.open(path) then
if minetest.mkdir then
minetest.mkdir(path) -- create directory if minetest.mkdir is available
else
os.execute('mkdir "'..path..'"') -- create directory with os mkdir command
end
return true
end
end
-- [function] remove directory
function builtin.rmdir(path)
if io.open(path) then
-- [local function] remove files
local function rm_files(ppath, files)
for _, f in ipairs(files) do
os.remove(ppath.."/"..f)
end
end
-- [local function] check and rm dir
local function rm_dir(dpath)
local files = minetest.get_dir_list(dpath, false)
local subdirs = minetest.get_dir_list(dpath, true)
rm_files(dpath, files)
if subdirs then
for _, d in ipairs(subdirs) do
rm_dir(dpath.."/"..d)
end
end
os.remove(dpath)
end
rm_dir(path)
return true
end
end
-- [function] copy directory
function builtin.cpdir(original, new)
if io.open(original) then
-- [local function] copy files
local function copy_files(opath, npath, files)
for _, f in ipairs(files) do
builtin.copy(opath.."/"..f, npath.."/"..f)
end
end
-- [local function] check and copy dir
local function copy_dir(opath, npath)
builtin.mkdir(npath)
local files = minetest.get_dir_list(opath, false)
local subdirs = minetest.get_dir_list(opath, true)
copy_files(opath, npath, files)
for _, d in ipairs(subdirs) do
copy_dir(opath.."/"..d, npath.."/"..d)
end
end
copy_dir(original, new)
return true
end
end

690
c_api.lua
View File

@ -1,136 +1,606 @@
-- digicompute/c_api.lua
--[[
API for registering computer nodes. Documentation in progress.
]]
local modpath = digicompute.modpath
local path = digicompute.path
function digicompute.register_computer(termstring, desc)
digicompute.c = {}
local modpath = digicompute.modpath
local path = digicompute.path
local main_path = path.."computers/"
-- Make computer directory
digicompute.builtin.mkdir(main_path)
-------------------
-- ID MANAGEMENT --
-------------------
local computers = {}
-- [function] load computers
function digicompute.load_computers()
local data = minetest.deserialize(digicompute.builtin.read(path.."/computers.txt"))
if type(data) == "table" then
computers = data
end
end
-- Load all computers
digicompute.load_computers()
-- [function] save computers
function digicompute.save_computers()
digicompute.builtin.write(path.."/computers.txt", minetest.serialize(computers))
end
-- [function] generate new computer ID
function digicompute.c:new_id(pos)
assert(type(pos) == "table", "digicompute.c:new_id requires a valid position")
local meta = minetest.get_meta(pos)
local function count()
local count = 1
for _, i in pairs(computers) do
count = count + 1
end
return count
end
local id = "c_"..count()
computers[id] = {
pos = pos,
}
meta:set_string("id", id)
end
-- [event] save computers on shutdown
minetest.register_on_shutdown(digicompute.save_computers)
-------------------
---- FORMSPECS ----
-------------------
local computer_contexts = {}
local tabs = {
"main",
"settings",
}
-- [function] handle tabs
function digicompute.c:handle_tabs(pos, player, fields)
if fields.tabs then
if digicompute.c:open(pos, player, tabs[tonumber(fields.tabs)]) then
return true
end
end
end
digicompute.c.forms = {
naming = {
cache_formname = false,
get = function(pos)
local meta = minetest.get_meta(pos)
return
"size[6,1.7]"..
default.gui_bg_img..
"field[.25,0.50;6,1;name;Computer Name:;"..minetest.formspec_escape(meta:get_string("name")).."]"..
"button[4.95,1;1,1;submit_name;Set]"
end,
handle = function(pos, player, fields)
local meta = minetest.get_meta(pos)
local name = player:get_player_name()
local owner = meta:get_string("owner")
if owner == name then
if fields.name or fields.key_enter_field == "name" and fields.name ~= "" then
meta:set_string("name", fields.name)
meta:set_string("setup", "true")
meta:set_string("path", main_path..meta:get_string("owner").."/"..meta:get_string("id").."/")
digicompute.c:init(pos)
digicompute.c:open(pos, player)
else
minetest.chat_send_player(name, "Name cannot be empty.")
end
else
minetest.chat_send_player(name, "Only the owner can set this computer. ("..owner..")")
end
end,
},
main = {
get = function(pos)
local meta = minetest.get_meta(pos)
local input = minetest.formspec_escape(meta:get_string("input"))
local help = minetest.formspec_escape(meta:get_string("help"))
local output = meta:get_string("output"):split("\n", true)
for i, line in ipairs(output) do
output[i] = minetest.formspec_escape(line)
end
return
"size[10,11]"..
"tabheader[0,0;tabs;Command Line,Settings;1]"..
"bgcolor[#000000FF;]"..
"tableoptions[background=#000000FF;highlight=#00000000;border=false]"..
"table[-0.25,-0.38;10.38,11.17;list_credits;"..table.concat(output, ",")..";"..#output.."]"..
"button[9.56,10.22;0.8,2;help;?]"..
"tooltip[help;"..help.."]"..
"field[-0.02,10.99;10.1,1;input;;"..input.."]"..
"field_close_on_enter[input;false]"
end,
handle = function(pos, player, fields)
if digicompute.c:handle_tabs(pos, player, fields) then return end
local meta = minetest.get_meta(pos) -- get meta
local os = minetest.deserialize(meta:get_string("os")) or {}
local prefix = os.prefix or ""
if fields.input or fields.key_enter_field == "name" then
if fields.input == os.clear then
meta:set_string("output", prefix)
meta:set_string("input", "")
digicompute.c:open(pos, player)
elseif fields.input == os.off then digicompute.c:off(pos, player)
elseif fields.input == os.reboot then digicompute.c:reboot(pos, player)
else -- else, turn over to os
-- Set meta value(s)
meta:set_string("input", fields.input)
-- Run main.lua
digicompute.c:run_file(pos, player, "os/main.lua") -- Run main
end
end
end,
},
settings = {
get = function(pos)
local meta = minetest.get_meta(pos)
return
"size[10,11]"..
"tabheader[0,0;tabs;Command Line,Settings;2]"..
default.gui_bg_img..
"button[0.5,0.25;9,1;reset;Reset Filesystem]"..
"tooltip[reset;Wipes all files and OS data replacing it with the basic BiosOS.]"..
"label[0.5,10.35;digicompute Version: "..tostring(digicompute.VERSION)..", "..
digicompute.RELEASE_TYPE.."]"..
"label[0.5,10.75;(c) Copywrite "..tostring(os.date("%Y")).." "..
"Elijah Duffy <theoctacian@gmail.com>]"
end,
handle = function(pos, player, fields)
if digicompute.c:handle_tabs(pos, player, fields) then return end
local meta = minetest.get_meta(pos)
if fields.reset then
-- Clear buffers
meta:set_string("output", "")
meta:set_string("input", "")
-- Reset Filesystem
digicompute.c:reinit(pos)
end
end,
},
}
-- [function] open formspec
function digicompute.c:open(pos, player, formname)
local meta = minetest.get_meta(pos)
if meta:get_string("setup") == "true" then
local meta_formname = meta:get_string("formname")
if not formname and meta_formname and meta_formname ~= "" then
formname = meta_formname
end
else
formname = "naming"
end
formname = formname or "main"
local form = digicompute.c.forms[formname]
if form then
local name = player:get_player_name()
if form.cache_formname ~= false then
meta:set_string("formname", formname)
end
computer_contexts[name] = minetest.get_meta(pos):get_string("id")
minetest.show_formspec(name, "digicompute:"..formname, form.get(pos, player))
return true
end
end
-- [event] on receive fields
minetest.register_on_player_receive_fields(function(player, formname, fields)
local formname = formname:split(":")
if formname[1] == "digicompute" and digicompute.c.forms[formname[2]] then
local computer = computers[computer_contexts[player:get_player_name()]]
if computer then
local pos = computer.pos
minetest.get_meta(pos):set_string("current_user", player:get_player_name())
digicompute.c.forms[formname[2]].handle(pos, player, fields)
else
minetest.chat_send_player(player:get_player_name(), "Computer could not be found!")
end
end
end)
----------------------
-- HELPER FUNCTIONS --
----------------------
-- [function] update infotext
function digicompute.c:infotext(pos)
local meta = minetest.get_meta(pos)
local state = minetest.registered_nodes[minetest.get_node(pos).name].digicompute.state
if meta:get_string("setup") == "true" then
meta:set_string("infotext", meta:get_string("name").." - "..state.."\n(owned by "
..meta:get_string("owner")..")")
else
meta:set_string("infotext", "Unconfigured Computer - "..state.."\n(owned by "
..meta:get_string("owner")..")")
end
end
-- [function] initialize computer
function digicompute.c:init(pos)
local meta = minetest.get_meta(pos)
local path = meta:get_string("path")
if path and path ~= "" then
digicompute.builtin.mkdir(main_path..meta:get_string("owner"))
digicompute.builtin.mkdir(path)
digicompute.builtin.cpdir(digicompute.modpath.."/bios/", path.."os")
digicompute.c:run_file(pos, meta:get_string("owner"), "os/start.lua")
digicompute.log("Initialized computer "..meta:get_string("id").." owned by "..
meta:get_string("owner").." at "..minetest.pos_to_string(pos))
digicompute.c:infotext(pos)
end
end
-- [function] deinitialize computer
function digicompute.c:deinit(pos, clear_entry)
local meta = minetest.get_meta(pos)
local path = meta:get_string("path")
local owner = meta:get_string("owner")
if path and path ~= "" then
digicompute.builtin.rmdir(path)
digicompute.log("Deinitialized computer "..meta:get_string("id").." owned by "..
meta:get_string("owner").." at "..minetest.pos_to_string(pos))
if digicompute.builtin.list(main_path..owner).subdirs then
os.remove(main_path..owner)
end
end
if clear_entry ~= false then
local id = meta:get_string("id")
computers[id] = nil
end
end
-- [function] reinitialize computer (reset)
function digicompute.c:reinit(pos)
digicompute.c:deinit(pos, false)
digicompute.c:init(pos)
end
-- [function] turn computer on
function digicompute.c:on(pos, player)
local temp = minetest.get_node(pos)
local ddef = minetest.registered_nodes[temp.name].digicompute
if ddef.state == "off" then
local name, param2 = temp.name, temp.param2
-- Swap to Bios
minetest.swap_node({x = pos.x, y = pos.y, z = pos.z}, {name = name.."_bios", param2 = param2}) -- set node to bios
-- Update infotext
digicompute.c:infotext(pos)
-- Swap to on node after 2 seconds
minetest.after(2, function(pos_)
minetest.swap_node({x = pos_.x, y = pos_.y, z = pos_.z}, {name = name.."_on", param2 = param2})
-- Update infotext
digicompute.c:infotext(pos)
-- Run start if setup
if minetest.get_meta(pos):get_string("setup") == "true" then
digicompute.c:run_file(pos, player, "os/start.lua")
end
end, vector.new(pos))
end
end
-- [function] turn computer off
function digicompute.c:off(pos, player)
local temp = minetest.get_node(pos) -- Get basic node information
local offname = "digicompute:"..minetest.registered_nodes[temp.name].digicompute.base
-- Swap node to off
minetest.swap_node({x = pos.x, y = pos.y, z = pos.z}, {name = offname, param2 = temp.param2})
-- Update infotext
digicompute.c:infotext(pos)
-- Update Formspec
minetest.close_formspec(player:get_player_name(), "")
-- Clear update buffer
minetest.get_meta(pos):set_string("output", "")
end
-- [function] reboot computer
function digicompute.c:reboot(pos, player)
digicompute.c:off(pos, player)
digicompute.c:on(pos, player)
end
-----------------------
----- ENVIRONMENT -----
-----------------------
-- [function] Make environment
function digicompute.c:make_env(pos, player)
assert(pos, "digicompute.c:make_env missing position")
local meta = minetest.get_meta(pos)
-- Main Environment Functions
local main = {}
-- [local function] print
function main.print(contents, newline)
if type(contents) ~= "string" then
contents = dump(contents)
end
if newline == false then
newline = ""
else
newline = "\n"
end
meta:set_string("output", meta:get_string("output")..newline..contents)
end
-- [local function] set help
function main.set_help(value)
if not value or type(value) ~= "string" then
value = "Type a command and press enter."
end
return meta:set_string("help", value)
end
-- [local function] get attribute
function main.get_attr(key)
return meta:get_string(key) or nil
end
-- [local function] get output
function main.get_output()
return meta:get_string("output") or nil
end
-- [local function] set output
function main.set_output(value)
return meta:set_string("output", value)
end
-- [local function] get input
function main.get_input()
return meta:get_string("input") or nil
end
-- [local function] set input
function main.set_input(value)
return meta:set_string("input", value)
end
-- [local function] get os value
function main.get_os(key)
return minetest.deserialize(meta:get_string("os"))[key] or nil
end
-- [local function] set os value
function main.set_os(key, value)
local allowed_keys = {
clear = true,
off = true,
reboot = true,
prefix = true,
}
if allowed_keys[key] == true then
local table = minetest.deserialize(meta:get_string("os")) or {}
table[key] = value
return meta:set_string("os", minetest.serialize(table))
else
return false
end
end
-- [local function] get userdata value
function main.get_userdata(key)
return minetest.deserialize(meta:get_string("userdata"))[key] or nil
end
-- [local function] set userdata value
function main.set_userdata(key, value)
local table = minetest.deserialize(meta:get_string("userdata")) or {}
table[key] = value
return meta:set_string("userdata", minetest.serialize(table))
end
-- [local function] refresh
function main.refresh()
return digicompute.c:open(pos, minetest.get_player_by_name(meta:get_string("current_user")))
end
-- [local function] run code
function main.run(code)
return digicompute.c:run_code(pos, player, code)
end
-- Filesystem Environment Functions
local fs = {}
local cpath = meta:get_string("path")
-- [local function] exists
function fs.exists(path)
return digicompute.builtin.exists(cpath..path)
end
-- [local function] create file
function fs.create(path)
return digicompute.builtin.create(cpath..path)
end
-- [local function] remove file
function fs.remove(path)
return os.remove(cpath..path)
end
-- [local function] write to file
function fs.write(path, data, mode)
if type(data) ~= "string" then
data = dump(data)
end
return digicompute.builtin.write(cpath..path, data, mode)
end
-- [local function] read file
function fs.read(path)
return digicompute.builtin.read(cpath..path)
end
-- [local function] list directory contents
function fs.list(path)
return digicompute.builtin.list(cpath..path)
end
-- [local function] copy file
function fs.copy(original, new)
return digicompute.builtin.copy(cpath..original, cpath..new)
end
-- [local function] create directory
function fs.mkdir(path)
return digicompute.builtin.mkdir(cpath..path)
end
-- [local function] remove directory
function fs.rmdir(path)
return digicompute.builtin.rmdir(cpath..path)
end
-- [local function] copy directory
function fs.cpdir(original, new)
return digicompute.builtin.cpdir(cpath..original, cpath..new)
end
-- [local function] run file
function fs.run(path)
return digicompute.c:run_file(pos, player, path)
end
-- Get default env table
local env = digicompute.env()
env.fs = fs
for k, v in pairs(main) do
env[k] = v
end
return env
end
-- [function] run code
function digicompute.c:run_code(pos, player, code)
local env = digicompute.c:make_env(pos, player)
local ok, res = digicompute.run_code(code, env)
return ok, res
end
-- [function] run file
function digicompute.c:run_file(pos, player, path)
local path = minetest.get_meta(pos):get_string("path")..path
local env = digicompute.c:make_env(pos, player)
local ok, res = digicompute.run_file(path, env)
return ok, res
end
----------------------
-- NODE DEFINITIONS --
----------------------
function digicompute.register_computer(itemstring, def)
-- off
minetest.register_node("digicompute:"..termstring, {
minetest.register_node("digicompute:"..itemstring, {
digicompute = {
state = "off",
base = itemstring,
},
drawtype = "nodebox",
description = desc.description,
tiles = desc.off_tiles,
description = def.description,
tiles = def.off_tiles,
paramtype = "light",
paramtype2 = "facedir",
groups = {cracky = 2},
drop = "digicompute:"..termstring,
drop = "digicompute:"..itemstring,
sounds = default.node_sound_stone_defaults(),
node_box = desc.node_box,
digiline = {
receptor = {},
effector = {
action = function(pos, node, channel, msg)
if digicompute.digiline ~= false then
local meta = minetest.get_meta(pos) -- get meta
-- if channel is correct, turn on
if channel == meta:get_string("channel") then
if msg.system == digicompute.digiline_on then
digicompute.on(pos, termstring)
end
end
end
end
},
},
after_place_node = function(pos, placer)
node_box = def.node_box,
after_place_node = function(pos, player)
local meta = minetest.get_meta(pos)
meta:set_string("owner", placer:get_player_name())
meta:set_string("owner", player:get_player_name())
meta:set_string("input", "") -- Initialize input buffer
meta:set_string("output", "") -- Initialize output buffer
meta:set_string("os", "") -- Initialize OS table
meta:set_string("userspace", "") -- Initialize userspace table
meta:set_string("help", "Type a command and press enter.") -- Initialize help
digicompute.c:new_id(pos) -- Set up ID
-- Update infotext
digicompute.c:infotext(pos)
end,
on_rightclick = function(pos)
digicompute.on(pos, termstring)
on_rightclick = function(pos, node, player)
digicompute.c:on(pos, player)
end,
on_destruct = function(pos)
local meta = minetest.get_meta(pos) -- get meta
if meta:get_string("name") then digicompute.fs.deinit(pos) end
if minetest.get_meta(pos):get_string("name") then
digicompute.c:deinit(pos)
end
end,
})
-- bios
minetest.register_node("digicompute:"..termstring.."_bios", {
minetest.register_node("digicompute:"..itemstring.."_bios", {
digicompute = {
state = "bios",
base = itemstring,
},
drawtype = "nodebox",
description = desc.description,
tiles = desc.bios_tiles,
paramtype = "light",
paramtype2 = "facedir",
drop = "",
groups = {unbreakable = 1, not_in_creative_inventory = 1},
sounds = default.node_sound_stone_defaults(),
node_box = desc.node_box,
})
-- on
minetest.register_node("digicompute:"..termstring.."_on", {
drawtype = "nodebox",
description = desc.description,
tiles = desc.on_tiles,
defription = def.defription,
tiles = def.bios_tiles,
paramtype = "light",
paramtype2 = "facedir",
groups = {cracky = 2, not_in_creative_inventory = 1},
drop = "digicompute:"..termstring,
drop = "digicompute:"..itemstring,
sounds = default.node_sound_stone_defaults(),
node_box = desc.node_box,
digiline = {
receptor = {},
effector = {
action = function(pos, node, channel, msg)
-- if os supports digilines and digiline on, listen for signal
if digicompute.digiline ~= false and digicompute.on:find("digiline") then
local meta = minetest.get_meta(pos) -- get meta
if channel ~= meta:get_string("channel") then return end -- ignore if not proper channel
if msg.system then
if msg.system == digicompute.clear then digicompute.clear("output", pos) -- clear output
elseif msg.system == digicompute.off then digicompute.off(pos, termstring) -- turn off
elseif msg.system == digicompute.reboot then digicompute.reboot(pos, termstring) -- reboot
else digicompute.proc_digiline({x = pos.x, y = pos.y, z = pos.z}, fields.input) end -- else, hand over to OS
end
end
end
},
},
on_construct = function(pos) -- set meta and formspec
local meta = minetest.get_meta(pos) -- get meta
meta:set_string("output", "") -- output buffer
meta:set_string("input", "") -- input buffer
local name = meta:get_string("name") -- get computer name
if not name then name = "" end -- if name nil, set to blank
meta:set_string("formspec", digicompute.formspec_name(name)) -- computer name formspec
end,
on_rightclick = function(pos)
-- if clear_on_close is true, clear
if digicompute.clear_on_close == true then
local meta = minetest.get_meta(pos) -- get meta
meta:set_string("formspec", digicompute.formspec("", "")) -- clear formspec
node_box = def.node_box,
on_destruct = function(pos)
if minetest.get_meta(pos):get_string("name") then
digicompute.c:deinit(pos)
end
end,
})
-- on
minetest.register_node("digicompute:"..itemstring.."_on", {
digicompute = {
state = "on",
base = itemstring,
},
drawtype = "nodebox",
description = def.defription,
tiles = def.on_tiles,
paramtype = "light",
paramtype2 = "facedir",
groups = {cracky = 2, not_in_creative_inventory = 1},
drop = "digicompute:"..itemstring,
sounds = default.node_sound_stone_defaults(),
node_box = def.node_box,
on_rightclick = function(pos, node, player)
digicompute.c:open(pos, player)
end,
on_destruct = function(pos)
local meta = minetest.get_meta(pos) -- get meta
if meta:get_string("name") then digicompute.fs.deinit(pos) end
end,
on_receive_fields = function(pos, formname, fields, sender) -- process formdata
local meta = minetest.get_meta(pos) -- get meta
-- if name, set
if fields.name then
meta:set_string("name", fields.name) -- set name
meta:set_string("setup", "true") -- set computer to configured
digicompute.fs.init(pos, fields.name) -- initialize filesystem
-- try to run when_on
if not digicompute.fs.run_file(pos, "os/start.lua", fields, "start.lua") then
meta:set_string("formspec", digicompute.formspec("", "")) -- set formspec
end
digicompute.refresh(pos) -- refresh
end
local name = meta:get_string("name") -- get name
local c = loadfile(path.."/"..meta:get_string("owner").."/"..name.."/os/conf.lua")
local e, msg = pcall(c)
-- if submitted, process basic commands, pass on to os
if fields.input then
if fields.input == clear then meta:set_string("formspec", digicompute.formspec("",""))
elseif fields.input == off then digicompute.off(pos, termstring) -- set off
elseif fields.input == reboot then digicompute.reboot(pos, termstring) -- reboot
else -- else, turn over to os
digicompute.fs.run_file(pos, "os/main.lua", fields, "main.lua") -- run main.lua
end
if minetest.get_meta(pos):get_string("name") then
digicompute.c:deinit(pos)
end
end,
})

View File

@ -1,4 +1,4 @@
-- digicompute/nodes.lua
-- digicompute/computers.lua
digicompute.register_computer("default", {
description = "digicomputer",

View File

@ -1,3 +1 @@
default
datalib
digilines?

14
doc/README.md Normal file
View File

@ -0,0 +1,14 @@
# Documentation
The digicompute API is divided among several different files. Unless otherwise mentioned, the `.md` documentation file is labeled the same as the Lua file containin the code. For example, documentation of all the API functions introduced by `builtin.lua` can be found in `doc/builtin.md`. Below, an overview of each documentation can be found.
## `builtin.md`
This documents the API introduced by `builtin.lua`. Mostly containing file interaction APIs, __builtin__ contains functions that don't fit elsewhere.
## `env.md`
This covers the simplistic API created in `env.lua` for running code under an environment.
## `c_api.md`
This documents the entire API used by computers. This API is only for use by modders who want to register or customize a computer.
## `c_os.md`
This documents the API accessible under a secure environment to the computer OS. Through this API, the player can interact with the computer itself to make their own programs.

64
doc/builtin.md Normal file
View File

@ -0,0 +1,64 @@
# Builtin
Builtin contains many independent functions that don't seem to fit elsewhere. Currently, it is mainly an API for easy interaction with the filesystem. All functions provided by builtin are packed under `digicompute.builtin.*`, but have been shortened in this documentation.
## `exists`
__Usage:__ `digicompute.builtin.exists(<string: path>)`
* `true`: file exists
* `nil`: file does not exist
Checks to see if a file exists by opening it with `io.open()` and returning `true` if `io.open()` does not return a `nil` value.
## `create`
__Usage:__ `digicompute.builtin.create(<string: path>)`
* `true`: file successfully created
Creates a file with `io.open()` and returns `true`. If you want to write to the file, use `write` directly, as it will automatically create the file if it doesn't already exist.
## `write`
__Usage:__ `digicompute.builtin.write(<string: path>, <any: data>, <string: mode>`
* `true`: successfully wrote to file
Writes any data to the file specified by `path` and returns `true` if successful. If the file does not exist, it will be created and written to. You can specify if you would like to overwrite or append to a file using the optional `mode` parameter (`w`: overwrite/create, `a`: append). Do directly write a table, but rather serialize it first with `minetest.serialize`. Doing so will cause a crash.
## `read`
__Usage:__ `digicompute.builtin.read(<string: path>)`
* `not-nil`: data read from file
* `nil`: file does not exist
Attempts to read entire file with `io.open():read()`. If `nil` is returned, the file does not exist, otherwise, the file contents will be returned. If your data was serialized before being written, be sure to run `minetest.deserialize` after reading the file.
## `copy`
__Usage:__ `digicompute.builtin.copy(<string: original path>, <string: new path>`
* `true`: successfully copied
* `nil`: original file does not exist
Reads from one path then creates and writes its contents to the new path. If the function returns `nil`, the file doesn't exist or some other error has occurred.
## `mkdir`
__Usage:__ `digicompute.builtin.mkdir(<string: path>)`
* `true`: successfully created directory
* `nil`: directory already exists
Attempts to create directory with `minetest.mkdir()` if available, resorting to `os.execute("mkdir")` if unavailable. Will return `true` if successful, and `nil` if the directory already exists or another error occurs.
## `rmdir`
__Usage:__ `digicompute.builtin.rmdir(<string: path>)`
* `true`: successfully removed directory
* `nil`: directory does not exist
Recursively removes a directory if it exists. __Note:__ this is destructive and will remove all of the sub-directories and files inside of a directory.
## `cpdir`
__Usage:__ `digicompute.builtin.cpdir(<string: original path>, <string: new path>`
* `true`: successfully copied directory
* `nil`: original directory does not exist
Recursively copies a directory and all it's sub-directories and files. __Note:__ depending on the size of the original directory, this may take some time.

205
doc/c_api.md Normal file
View File

@ -0,0 +1,205 @@
# Computer API
This API is used for registering new computers. The API can also be used for modders to make advanced interactions with pre-existing computers or customize their own.
## Computer Registration
It's actually quite easy to register a computer, the end result looking similar to the definition of a normal node.
Register a computer with `digicompute.register_computer`.
```lua
digicompute.register_computer("<computer_string>", {
description = "<description>",
off_tiles = {},
bios_tiles = {},
on_tiles = {},
node_box = {},
})
```
The definition is formed just like that of a normal node definition, except digicompute uses it to do a lot of groundwork rather than requiring you to do it manually. **Note:** do not put a modname in the computer string, `digicompute:` is automatically inserted.
**Example:**
```lua
digicompute.register_computer("default", {
description = "digicomputer",
off_tiles = {
"top.png",
"bottom.png",
"right.png",
"left.png",
"back_off.png",
"front_off.png",
},
bios_tiles = {
"top.png",
"bottom.png",
"right.png",
"left.png",
"back_off.png",
"front_off.png^bios.png",
},
on_tiles = {
"top.png",
"bottom.png",
"right.png",
"left.png",
"back.png",
"front.png"
},
node_box = {
type = "fixed",
fixed = {
{-0.5, -0.5, -0.125, 0.5, 0.5, 0.5},
}
},
})
```
Above is example code from the default computer.
## Advanced API
This API is more of a documentation of pre-existing API functions for developers new to this mod who would like to get started. The Advanced API documentation is sectioned out as it is in the code.
### ID Management
This section manages loading, saving, and assigning new IDs to computers.
#### `load_computers()`
**Usage:** `digicompute.load_computers()`
Loads the IDs of all computers for later use. This should only be called after the variable `computer` (type: `table`) is defined. **Note:** the Computer API automatically loads the computers IDs when the server starts.
#### `save_computers()`
**Usage:** `digicompute.save_computers()`
Saves computer IDs as stored in the `computer` table. Be sure that this table exists before attempting to save. **Note:** the Computer API automatically saves the computers IDs before the server shuts down.
#### `c:new_id(pos)`
**Usage:** `digicompute.c:new_id(<computer position (table)>)`
Generate a new computer ID, store it in the `computers` table, and save it in the node meta. Make sure this table exists before attempting to generate a new ID.
### Formspecs
This section uses tables to store information about the formspec(s) and their tabs. It also introduces functions to show and handle received fields from formspecs.
#### `c:handle_tabs(pos, player, fields)`
**Usage:** `digicompute.c:handle_tabs(<computer position (table)>, <player (userdata value)>, <form fields (table)>`
Handles tab switching. Should be called in the handle function of any tab for the main form. Valid tabs should be added (in proper order) to the tabs table (defined above function). **TODO:** improve API to support tab handling for other forms.
#### Forms Table
**Name:** `digicompute.c.forms`
This is a slightly more complex topic, as this table handles all of the forms (and tabs) used by digicomputers. Each form/tab has it's own entry, defining a table of informatin about it.
**Basic Parameters:**
```lua
digicompute.c.forms = {
newformname = {
cache_formname = true/false,
get = function(pos, player) ... end,
handle = function(pos, player, fields) ... end,
},
...
}
```
The `cache_formname` field is used in `digicompute.c:open` to choose whether or not to cache the formname in meta. If the formname is cached in meta, it will automatically be opened the next time the computer is right-clicked. Unless this is `false`, the formname will be cached.
`get` is a required item which is used by `digicompute.c:open` to obtain the actual formspec information. All that really matters is that you return a valid formspec string at the end of the function.
`handle` is called `on_receive_fields` to handle player input. It is a required item, but there are no direct requirements past that.
**Example (naming form):**
```lua
naming = {
cache_formname = false,
get = function(pos)
local meta = minetest.get_meta(pos)
return
"size[6,1.7]"..
default.gui_bg_img..
"field[.25,0.50;6,1;name;Computer Name:;"..minetest.formspec_escape(meta:get_string("name")).."]"..
"button[4.95,1;1,1;submit_name;Set]"
end,
handle = function(pos, player, fields)
local meta = minetest.get_meta(pos)
local name = player:get_player_name()
local owner = meta:get_string("owner")
if owner == name then
if fields.name or fields.key_enter_field == "name" and fields.name ~= "" then
meta:set_string("name", fields.name)
meta:set_string("setup", "true")
meta:set_string("path", main_path..meta:get_string("owner").."/"..meta:get_string("id").."/")
digicompute.c:init(pos)
digicompute.c:open(pos, player)
else
minetest.chat_send_player(name, "Name cannot be empty.")
end
else
minetest.chat_send_player(name, "Only the owner can set this computer. ("..owner..")")
end
end,
},
```
#### `c:open(pos, player, formname)`
**Usage:** `digicompute.c:open(<computer position (table)>, <player (userdata value)>, <form name (string)>)`
Shows a form defined in the forms table. If the formname is not provided, it will be set to the formname cached in meta (if any), and default to `main`. Fields are automatically sent to the `handle` function defined in the forms table. **Note:** `player` should not be the a plaintext string containing the player name, but a userdata value.
### Helper Functions
This section defines several helper functions used in the formspecs, environment, and node definition.
#### `c:infotext(pos)`
**Usage:** `digicompute.c:infotext(<computer position (table)>)`
Updates the infotext of the computer. This is called after the computer is named or when its state changes (off/bios/on).
#### `c:init(pos)`
**Usage:** `digicompute.c:init(<computer position (table)>)`
Initializes the computers filesystem, runs `main.lua`, and updates the infotext. **Note:** path must already be defined in meta, otherwise the initialization process will not complete (this is defined in the handling function of the naming form).
#### `c:deinit(pos, true/false)`
**Usage:** `digicompute.c:deinit(<computer position (table)>, <clear computer ID entry (boolean)>)`
Deinitializes a computers filesystem. The entry in the computers table is also cleared unless the final parameter is `false` (used when a computer reset is requested as the ID should not be cleared).
#### `c:reinit(pos)`
**Usage:** `digicompute.c:reinit(<computer position (table)>)`
Reinitializes the filesystem of a computer by calling `c:deinit` followed by `c:init`. **Note:** this is destructive and will wipe any files created or changed by the player.
#### `c:on(pos, player)`
**Usage:** `digicompute.c:on(<computer position (table)>, <player (userdata value)>`
Turns a computer on (will not execute if computer is not off). `start.lua` is automatically run, hence the player userdata is required.
#### `c:off(pos, player)`
**Usage:** `digicompute.c:off(<computer position (table)>, <player (userdata value)>`
Turns a computer off. The formspec is automatically closed using `minetest.close_formspec` (requires Minetest 0.4.15 or later), hence the player userdata is required.
#### `c:reboot(pos, player)`
**Usage:** `digicompute.c:reboot(<computer position (table)>, <player (userdata value)>`
Reboots a computer by calling `c:off` followed by `c:on`.
### Environment
This section introduces functions to initialize the environment per-computer and execute a string or file under the environment.
#### `c:make_env(pos, player)`
**Usage:** `digicompute.c:make_env(<computer position (table)>, <player (userdata value)>`
Returns a table of functions allowed under the environment. The table is made up of a wide array of functions for interacting with the computer and its file system. These are joined with the table returned by `digicompute.env()`, explaned in `env.md`. The player userdata parameter is required for later callbacks to functions such as `c:open`.
#### `c:run_code(pos, player, code)`
**Usage:** `digicompute.c:run_code(<computer position (table)>, <player (userdata value)>, <code (string)>)`
Generates an environment table using `c:make_env` and runs the code (provided as third parameter) with `digicompute.run_code` (see `env.md`).
#### `c:run_file(pos, player, path)`
**Usage:** `digicompute.c:run_file(<computer position (table)>, <player (userdata value)>, <path (string)>)`
Generates an environment table using `c:make_env` and runs the code found in the file specified by `path` with `digicompute.run_file` (see `env.md`). **Note:** the path is relative to the computer, meaning that `main.lua` could be run with `digicompute.c:run_file(pos, player, "os/main.lua")`.

128
doc/c_os.md Normal file
View File

@ -0,0 +1,128 @@
# Computer OS API
This API can be used by players to interact with the computer under a safe and secure environment. The documentation is divided into two sections as is the code, main (for general functions), and filesystem (for filesystem access).
## Main
This contains a set of functions mainly for the purpose of interacting with the computer's displays.
#### `print(string, false)`
**Usage:** `print(<contents>, <newline (true/false)>`
Prints to the output buffer. If contents is not a string, it will be converted to a string with `dump`. The second parameter, if false, prevents print from inserting a newline before printing the provided contents.
#### `set_help(value)`
**Usage:** `set_help(<value (string)>)`
Sets the text to be shown when hovering over the help button. A `nil` value will revert the text to default.
#### `get_attr(key)`
**Usage:** `get_attr(<attribute name (string)>)`
Gets a piece of global information from the node meta (storage). Several common attributes are below. **Note:** none of these attributes can be directly set, with the purpose of being read-only. However, there are methods to set several.
* `owner`: username of the player who owns the computer.
* `input`: input field.
* `output`: output buffer.
* `name`: computer name.
#### `get_output()`
**Usage:** `get_output()`
Returns the value of the output buffer. Shorthand for `get_attr("output")`.
#### `set_output(value)`
**Usage:** `set_output(<value (string)>)`
Set the output buffer to any string. This is the write method for the output attribute.
#### `get_input()`
**Usage:** `get_input()`
Returns the value of the input field. Shorthand for `get_attr("input")`.
#### `set_input(value)`
**Usage:** `set_input(<value (string)>)`
Set the input field to any string. This is the write method for the input attribute.
#### `get_os(key)`
**Usage:** `get_os(<data name (string)>)`
Gets a piece of information from the OS table. See next function for further information on this table.
#### `set_os(key, value)`
**Usage:** `set_os(<data name (string)>, <value>`
Sets a piece of information stored in the OS table. This table stores basic values containing information global to the operating system. However, it is quite limitted, only being capable of storing a few pieces of information as listed below.
* `clear`: command to clear the output and input.
* `off`: command to turn the computer off.
* `reboot`: command to reboot the computer.
* `prefix`: prefix printed at the beginning of a new line.
#### `get_userdata(key)`
**Usage:** `get_userdata(<data name (string)>)`
Gets a piece of information from the userdata table. This table is like RAM, as information will be reset when the computer is turned off.
#### `set_userdata(key, value)`
**Usage:** `set_userdata(<data name (string)>, <value>`
Stores any piece of information in the non-persistant userdata table. (Table is cleared when computer is turned off, therefore non-persistant.)
#### `refresh()`
**Usage:** `refresh()`
Refresh the computer display, typically after making changes to a buffer, field, or other element.
#### `run(code)`
**Usage:** `run(<code (string)>)`
Run code under the environment (e.g. run data in the input field whenever it is submitted).
## Filesystem
This API section introduces function to interact with the computer's physical filesystem.
#### `exists(path)`
__Usage:__ `fs.exists(<path (string)>)`
Checks to see if a file exists by opening it with `io.open()` and returns `true` if exists, and `nil` if it does not.
#### `create(path)`
__Usage:__ `fs.create(<path (string)>)`
Creates a file with and returns `true` unless unsuccessful. If you want to write to the file, use `write` directly, as it will automatically create the file if it doesn't already exist.
#### `remove(path)`
__Usage:__ `fs.remove(<path (string)>`
Removes a file and returns `true` if successful. If the return value is `nil`, the file either does not exist or is a directory. Directories must be removed with `rmdir`.
#### `write(path, data, mode)`
__Usage:__ `fs.write(<path (string)>, <data>, <mode (string)>`
Writes any data to the file specified by `path` and returns `true` if successful. If the file does not exist, it will be created and written to. You can specify if you would like to overwrite or append to a file using the optional `mode` parameter (`w`: overwrite/create, `a`: append).
#### `read(path)`
__Usage:__ `fs.read(<path (string)>)`
Attempts to read entire file. If `nil` is returned, the file does not exist, otherwise, the file contents will be returned.
#### `copy(original_path, new_path)`
__Usage:__ `fs.copy(<original path (string)>, <new path> (string)`
Reads from one path then creates and writes its contents to the new path. If the function returns `nil`, the file doesn't exist or some other error has occurred. Otherwise, a return value of `true` indicates a success.
#### `mkdir(path)`
__Usage:__ `fs.mkdir(<path (string)>)`
Creates a directory. Will return `true` if successful, and `nil` if the directory already exists or another error occurs.
#### `rmdir(path)`
__Usage:__ `fs.rmdir(<path (string)>)`
Recursively removes a directory if it exists. Returns `true` if successful. __Note:__ this is destructive and will remove all of the sub-directories and files inside of a directory.
#### `cpdir(original_path, new_path)`
__Usage:__ `fs.cpdir(<original path (string)>, <new path (string)>`
Recursively copies a directory and all it's sub-directories and files. Returns `true` if successful. __Note:__ depending on the size of the original directory, this may take some time.

20
doc/env.md Normal file
View File

@ -0,0 +1,20 @@
# Environment API
This API provides a simple set of functions to easily run code under a secure environment.
## `env()`
**Usage:** `digicompute.env()`
Returns a table of safe functions for use when executing code under a secure environment. It is not recommended that you attempt to use this table to manually execute code, rather use `digicompute.run_code`.
## `run_code(code, env)`
**Usage:** `digicompute.run_code(<code (string)>, <environment (table)>)`
* `false`, `msg`: code failed to execute.
* `true`, `msg`: code executed successfully.
Attempts to run provided code under a safe environment (environment table can be generated with `digicompute.env()`). The function returns two variables, the first being a boolean telling whether the operation was successful, and the second an error message/return value from the executed code.
## `run_file(path, env)`
**Usage:** `digicompute.run_file(<path (string)>, <environment (table)>)`
Loads the contents of a file using `builtin` and provides the resulting code and environment table (provided as second parameter) to `run_code`.

172
env.lua
View File

@ -1,16 +1,7 @@
-- digicompute/env.lua --
-- ENVIRONMENT --
-----------------
-- [function] create environment
function digicompute.create_env(pos, fields)
local meta = minetest.get_meta(pos) -- get meta
-- CUSTOM SAFE FUNCTIONS --
local function safe_print(param)
print(dump(param))
end
function digicompute.env()
local function safe_date()
return(os.date("*t",os.time()))
end
@ -37,133 +28,7 @@ function digicompute.create_env(pos, fields)
return string.find(...)
end
-- [function] get attr (from meta)
local function get_attr(key)
return meta:get_string(key) or nil
end
-- [function] get userdata
local function get_userdata(key)
local t = minetest.deserialize(meta:get_string("userspace"))
return t[key] or nil
end
-- [function] set userdata
local function set_userdata(key, value)
local t = minetest.deserialize(meta:get_string("userspace"))
t[key] = value
return meta:set_string("userspace", minetest.serialize(t))
end
-- [function] get input
local function get_input()
return meta:get_string("input") or nil
end
-- [function] set input
local function set_input(value)
return meta:set_string("input", value) or nil
end
-- [function] get output
local function get_output()
return meta:get_string("output") or nil
end
-- [function] set output
local function set_output(value)
return meta:set_string("output", value) or nil
end
-- [function] get field
local function get_field(key)
return fields[key] or nil
end
-- [function] refresh
local function refresh()
meta:set_string("formspec", digicompute.formspec(meta:get_string("input"), meta:get_string("output")))
return true
end
-- filesystem API
-- [function] get file (read)
local function get_file(path)
local res = digicompute.fs.get_file(pos, path)
if res then return res end
end
-- [function] get directory contents
local function get_dir(path)
local res = digicompute.fs.get_dir(pos, path)
if res then return res end
end
-- [function] exists
local function exists(path)
local res = digicompute.fs.exists(pos, path)
if res then return res end
end
-- [function] mkdir
local function mkdir(path)
local res = digicompute.fs.exists(pos, path)
if res then return res end
end
-- [function] rmdir
local function rmdir(path)
local res = digicompute.fs.rmdir(pos, path)
if res then return res end
end
-- [function] mkdir
local function mkdir(path)
local res = digicompute.fs.exists(pos, path)
if res then return res end
end
-- [function] create file
local function create(path)
local res = digicompute.fs.create(pos, path)
if res then return res end
end
-- [function] write
local function write(path, data)
local res = digicompute.fs.write(pos, path, data)
if res then return res end
end
-- [function] append
local function append(path, data)
local res = digicompute.fs.append(pos, path, data)
if res then return res end
end
-- [function] copy
local function copy(path, npath)
local res = digicompute.fs.copy(pos, path, npath)
if res then return res end
end
-- ENVIRONMENT TABLE --
local env = {
run = digicompute.run,
get_attr = get_attr,
get_userdata = get_userdata,
set_userdata = set_userdata,
get_input = get_input,
set_input = set_input,
get_output = get_output,
set_output = set_output,
get_field = get_field,
refresh = refresh,
fs = {
read = get_file,
list = get_dir,
check = exists,
mkdir = mkdir,
rmdir = rmdir,
touch = create,
write = write,
copy = copy,
cp = copy,
},
string = {
byte = string.byte,
char = string.char,
@ -221,5 +86,38 @@ function digicompute.create_env(pos, fields)
datetable = safe_date,
},
}
return env -- return table
return env
end
-- [function] run code
function digicompute.run_code(code, env)
if code:byte(1) == 27 then
return nil, "Binary code prohibited."
end
local f, msg = loadstring(code)
if not f then return false, msg end
setfenv(f, env)
-- Turn off JIT optimization for user code so that count
-- events are generated when adding debug hooks
if rawget(_G, "jit") then
jit.off(f, true)
end
-- Use instruction counter to stop execution
-- after 10000 events
debug.sethook(function()
return false, "Code timed out!"
end, "", 10000)
local ok, ret = pcall(f)
debug.sethook() -- Clear hook
if not ok then return false, ret end
return true, ret
end
-- [function] run file
function digicompute.run_file(path, env)
local code = digicompute.builtin.read(path)
local ok, res = digicompute.run_code(code, env)
return ok, res
end

View File

@ -1,45 +1,31 @@
-- digicompute/init.lua
digicompute = {}
-- variables
digicompute.VERSION = 0.1
digicompute.RELEASE_TYPE = "beta"
digicompute.path = minetest.get_worldpath().."/digicompute/" -- digicompute directory
digicompute.modpath = minetest.get_modpath("digicompute") -- modpath
local modpath = digicompute.modpath -- modpath pointer
-- logger
-- Load builtin
dofile(modpath.."/builtin.lua")
-- Logger
function digicompute.log(content, log_type)
assert(content, "digicompute.log content nil")
if log_type == nil then log_type = "action" end
minetest.log(log_type, "[digicompute] "..content)
end
-- create mod world dir
datalib.mkdir(datalib.worldpath.."/digicompute")
digicompute.path = datalib.worldpath.."/digicompute"
-- Create mod directory inside world directory
digicompute.builtin.mkdir(digicompute.path)
-- FORMSPECS
-- normal
function digicompute.formspec(input, output)
if not output then local output = "" end
if not input then local input = "" end
-- formspec
local formspec =
"size[10,11]"..
default.gui_bg_img..
"textarea[.25,.25;10,11.5;output;Output:;"..output.."]"..
"field[.25,10.75;10,1;input;;"..input.."]"..
"field_close_on_enter[input;false]"
return formspec -- return formspec text
end
-- set name
function digicompute.formspec_name(computer)
if not computer then local computer = "" end -- use blank channel is none specified
local formspec =
"size[6,1.7]"..
default.gui_bg_img..
"field[.25,0.50;6,1;name;Computer Name:;"..computer.."]"..
"button[4.95,1;1,1;submit_name;Set]"
return formspec
end
-- /FORMSPECS
-- Load environment utilities
dofile(modpath.."/env.lua")
-- load resources
dofile(modpath.."/api.lua") -- load api
dofile(modpath.."/nodes.lua") -- load nodes
-- Load API-like resources
dofile(modpath.."/c_api.lua") -- Computer API
-- Load registration code
dofile(modpath.."/computers.lua") -- Computers