These were previously filtered out. Unfortunately now there's some awkward emtpy lines in various places, but, I prefer having empty lines show since that makes typing much simpler.
608 lines
16 KiB
Lua
608 lines
16 KiB
Lua
|
|
--[[
|
|
|
|
ITB (insidethebox) minetest game - Copyright (C) 2017-2018 sofar & nore
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public License
|
|
as published by the Free Software Foundation; either version 2.1
|
|
of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the Free
|
|
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
|
|
MA 02111-1307 USA
|
|
|
|
]]--
|
|
|
|
--[[
|
|
|
|
terminal - an interactive terminal
|
|
|
|
]]--
|
|
|
|
local term = {}
|
|
|
|
local function get_cmd_params(line)
|
|
local cmd = ""
|
|
local params = ""
|
|
for w in line:gmatch("%w+") do
|
|
if cmd == "" then
|
|
cmd = w
|
|
elseif params == "" then
|
|
params = w
|
|
else
|
|
params = params .. " " .. w
|
|
end
|
|
end
|
|
return cmd, params
|
|
end
|
|
|
|
term.help = {
|
|
append = "append text to a file",
|
|
clear = "clear the output",
|
|
echo = "echoes the input back to you",
|
|
help = "display help information for commands",
|
|
list = "list available files",
|
|
lock = "lock the terminal",
|
|
read = "read the content of a file",
|
|
remove = "removes a file",
|
|
unlock = "unlocks the terminal",
|
|
write = "write text to a file",
|
|
edit = "edits a file in an editor",
|
|
telex = "run the telex command - send and receive messages"
|
|
}
|
|
|
|
term.telex_help = {
|
|
help = "display help information for subcommands",
|
|
list = "list received telex messages",
|
|
draft = "create a new, or edit an outgoing telex message",
|
|
discard = "discard the current telex draft message",
|
|
send = "send the current draft telex message to a recipient",
|
|
remove = "remove a received telex message by number",
|
|
read = "read a received telex message by number",
|
|
reply = "create a draft reply to a message by number"
|
|
}
|
|
|
|
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
|
|
if output then
|
|
for part in output:gmatch("([^\r\n]*)\n?") do
|
|
f = f .. minetest.formspec_escape(part) .. ","
|
|
c = c + 1
|
|
end
|
|
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,
|
|
append = function(output, params, c)
|
|
if not c.rw then
|
|
return output .. "\nError: No write access"
|
|
end
|
|
local what, _ = get_cmd_params(params)
|
|
if what == "" then
|
|
return output .. "\nError: Missing file name"
|
|
end
|
|
c.writing = what
|
|
return output .. "\nWriting \"" .. what .. "\". Enter STOP on a line by itself to finish"
|
|
end,
|
|
write = function(output, params, c)
|
|
if not c.rw then
|
|
return output .. "\nError: No write access"
|
|
end
|
|
local what, _ = get_cmd_params(params)
|
|
if what == "" then
|
|
return output .. "\nError: Missing file name"
|
|
end
|
|
c.writing = what
|
|
local meta = minetest.get_meta(c.pos)
|
|
local meta_files = meta:get_string("files")
|
|
if meta_files and meta_files ~= "" then
|
|
local files = minetest.parse_json(meta_files) or {}
|
|
if files and files[what] then
|
|
files[what] = ""
|
|
meta:set_string("files", minetest.write_json(files))
|
|
meta:mark_as_private("files")
|
|
end
|
|
end
|
|
return output .. "\nWriting \"" .. what .. "\". Enter STOP on a line by itself to finish"
|
|
end,
|
|
edit = function(output, params, c)
|
|
if not c.rw then
|
|
return output .. "\nError: No write access"
|
|
end
|
|
local what, _ = get_cmd_params(params)
|
|
if what == "" then
|
|
return output .. "\nError: Missing file name"
|
|
end
|
|
c.what = what
|
|
c.output = output
|
|
|
|
local text = ""
|
|
local meta = minetest.get_meta(c.pos)
|
|
local meta_files = meta:get_string("files")
|
|
if meta_files and meta_files ~= "" then
|
|
local files = minetest.parse_json(meta_files) or {}
|
|
if files and files[what] then
|
|
text = files[what]
|
|
end
|
|
end
|
|
|
|
fsc.show(c.name, "size[12,8]" ..
|
|
"textarea[0.5,0.5;11.5,7.0;text;text;" ..
|
|
minetest.formspec_escape(text) .. "]" ..
|
|
"button[5.2,7.2;1.6,0.5;exit;Save]",
|
|
c,
|
|
term.edit)
|
|
|
|
return false
|
|
end,
|
|
remove = function(output, params, c)
|
|
if not c.rw then
|
|
return output .. "\nError: No write access"
|
|
end
|
|
local meta = minetest.get_meta(c.pos)
|
|
local meta_files = meta:get_string("files")
|
|
if not meta_files or meta_files == "" then
|
|
return output .. "\nError: No such file"
|
|
end
|
|
local files = minetest.parse_json(meta_files) or {}
|
|
local first, _ = get_cmd_params(params)
|
|
if files[first] then
|
|
files[first] = nil
|
|
else
|
|
return output .. "\nError: No such file"
|
|
end
|
|
meta:set_string("files", minetest.write_json(files))
|
|
meta:mark_as_private("files")
|
|
return output .. "\nRemoved \"" .. first .. "\""
|
|
end,
|
|
list = function(output, params, c)
|
|
local meta = minetest.get_meta(c.pos)
|
|
local meta_files = meta:get_string("files")
|
|
local files
|
|
if not meta_files or meta_files == "" then
|
|
return output .. "\nError: No files found"
|
|
end
|
|
files = minetest.parse_json(meta_files) or {}
|
|
if not files then
|
|
return output .. "\nError: No files found"
|
|
end
|
|
for k, _ in pairs(files) do
|
|
output = output .. "\n" .. k
|
|
end
|
|
return output
|
|
end,
|
|
echo = function(output, params, c)
|
|
return output .. "\n" .. params
|
|
end,
|
|
read = function(output, params, c)
|
|
local meta = minetest.get_meta(c.pos)
|
|
local meta_files = meta:get_string("files")
|
|
if not meta_files or meta_files == "" then
|
|
return output .. "\nError: No such file"
|
|
end
|
|
local files = minetest.parse_json(meta_files) or {}
|
|
local first, _ = get_cmd_params(params)
|
|
if files[first] then
|
|
if first == "rules" then
|
|
rules.show(c.name, "player")
|
|
return false
|
|
end
|
|
return output .. "\n" .. files[first]
|
|
else
|
|
return output .. "\nError: No such file"
|
|
end
|
|
end,
|
|
lock = function(output, params, c)
|
|
if not c.rw then
|
|
return output .. "\nError: no write access"
|
|
end
|
|
local meta = minetest.get_meta(c.pos)
|
|
meta:set_int("locked", 1)
|
|
meta:mark_as_private("locked")
|
|
return output .. "\n" .. "Terminal locked"
|
|
end,
|
|
unlock = function(output, params, c)
|
|
return output .. "\n" .. "Error: unable to connect to authentication service"
|
|
end,
|
|
telex = function(output, params, c)
|
|
if params ~= "" then
|
|
local h, p = get_cmd_params(params)
|
|
if h == "help" then
|
|
if p == "" then
|
|
local o = ""
|
|
local ot = {}
|
|
for k, _ in pairs(term.telex_help) do
|
|
ot[#ot + 1] = k
|
|
end
|
|
table.sort(ot)
|
|
for _, v in ipairs(ot) do
|
|
o = o .. " " .. v .. "\n"
|
|
end
|
|
return output .. "\n" ..
|
|
"Available subcommands:\n" ..
|
|
o ..
|
|
"Type `telex help <subcommand>` for more help about that command"
|
|
elseif term.telex_help[p] then
|
|
return output .. "\n" .. term.telex_help[p]
|
|
else
|
|
return output .. "\nError: No help for \"" .. h .. "\""
|
|
end
|
|
elseif h == "list" then
|
|
local player = minetest.get_player_by_name(c.name)
|
|
return output .. "\n" .. table.concat(telex.list(player), "\n")
|
|
elseif h == "draft" then
|
|
local player = minetest.get_player_by_name(c.name)
|
|
local meta = player:get_meta()
|
|
local text = meta:get_string("telex_draft")
|
|
local subject = meta:get_string("telex_subject")
|
|
fsc.show(c.name, "size[12,8]" ..
|
|
"field[0.5,0.5;11.5,1;subject;subject;" ..
|
|
minetest.formspec_escape(subject) .. "]" ..
|
|
"textarea[0.5,1.5;11.5,7.0;text;text;" ..
|
|
minetest.formspec_escape(text) .. "]" ..
|
|
"button[5.2,7.7;1.6,0.5;exit;Save]",
|
|
c,
|
|
term.draft)
|
|
|
|
return false
|
|
elseif h == "discard" then
|
|
local player = minetest.get_player_by_name(c.name)
|
|
local meta = player:get_meta()
|
|
meta:set_string("telex_draft", "")
|
|
meta:set_string("telex_subject", "")
|
|
return output .. "\n" .. "Draft erased."
|
|
elseif h == "send" then
|
|
if p == "" then
|
|
return output .. "\n" .. "Need recipient name to send draft message to."
|
|
end
|
|
|
|
local player = minetest.get_player_by_name(c.name)
|
|
local meta = player:get_meta()
|
|
local text = meta:get_string("telex_draft")
|
|
local subject = meta:get_string("telex_subject")
|
|
|
|
if text == "" then
|
|
return output .. "\n" .. "No draft exists. Cannot send. run `telex draft` to create a draft."
|
|
end
|
|
|
|
if subject == "" then
|
|
return output .. "\n" .. "No draft subject. Edit your draft and enter a subject."
|
|
end
|
|
|
|
local msg = {
|
|
to = p,
|
|
subject = subject,
|
|
from = c.name,
|
|
content = string.split(text, "\n")
|
|
}
|
|
telex.send(msg)
|
|
|
|
return output .. "\n" .. "Mail sent to <" .. p .. ">."
|
|
elseif h == "read" then
|
|
local player = minetest.get_player_by_name(c.name)
|
|
return output .. "\n" .. table.concat(telex.read(player, tonumber(p)), "\n")
|
|
elseif h == "remove" then
|
|
local player = minetest.get_player_by_name(c.name)
|
|
return output .. "\n" .. table.concat(telex.delete(player, tonumber(p)), "\n")
|
|
elseif h == "reply" then
|
|
local player = minetest.get_player_by_name(c.name)
|
|
local msg = telex.get(player, tonumber(p))
|
|
if not msg.subject then
|
|
return output .. "\n" .. table.concat(msg, "\n")
|
|
end
|
|
fsc.show(c.name, "size[12,8]" ..
|
|
"field[0.5,0.5;11.5,1;subject;subject;" ..
|
|
minetest.formspec_escape("Re: " .. msg.subject) .. "]" ..
|
|
"textarea[0.5,1.5;11.5,7.0;text;text;" ..
|
|
"\n\n> " ..
|
|
minetest.formspec_escape(table.concat(msg.content, "\n> ")) .. "]" ..
|
|
"button[5.2,7.7;1.6,0.5;exit;Save]",
|
|
c,
|
|
term.draft)
|
|
|
|
return false
|
|
else
|
|
return output .. "\n" .. "Invalid command passed. Use `telex help` to list available commands."
|
|
end
|
|
else
|
|
return output .. "\n" ..
|
|
"Type `telex help` for more help about the telex command"
|
|
end
|
|
end,
|
|
help = function(output, params, c)
|
|
if params ~= "" then
|
|
local h, _ = get_cmd_params(params)
|
|
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(term.help) do
|
|
ot[#ot + 1] = k
|
|
end
|
|
table.sort(ot)
|
|
for _, v in ipairs(ot) do
|
|
o = o .. " " .. v .. "\n"
|
|
end
|
|
return output .. "\n" ..
|
|
"Available commands:\n" ..
|
|
o ..
|
|
"Type `help <command>` for more help about that command"
|
|
end,
|
|
}
|
|
|
|
function term.recv(player, fields, context)
|
|
-- input validation
|
|
local name = player:get_player_name()
|
|
local c = context
|
|
if not c or not c.pos then
|
|
log.fs_data(player, name, "term.recv/context", fields)
|
|
return true
|
|
end
|
|
|
|
local line = fields.input
|
|
if line and line ~= "" then
|
|
local output = c.output or ""
|
|
minetest.sound_play("terminal_keyboard_clicks", {pos = c.pos})
|
|
|
|
if c.writing then
|
|
-- this shouldn't get reached, but just to be safe, check ro
|
|
if not c.rw then
|
|
c.writing = nil
|
|
output = output .. "\n" .. line
|
|
output = output .. "\nError: no write access"
|
|
c.output = output
|
|
fsc.show(name,
|
|
make_formspec(output, "> "),
|
|
c,
|
|
term.recv)
|
|
return
|
|
end
|
|
-- are we writing a file?
|
|
if line == "STOP" then
|
|
-- done writing a file
|
|
c.writing = nil
|
|
output = output .. "\n" .. line
|
|
c.output = output
|
|
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 = {}
|
|
if not meta_files or meta_files == "" then
|
|
files[c.writing] = line
|
|
else
|
|
files = minetest.parse_json(meta_files) or {}
|
|
if not files[c.writing] then
|
|
files[c.writing] = line
|
|
else
|
|
files[c.writing] = files[c.writing] .. "\n" .. line
|
|
end
|
|
end
|
|
if string.len(files[c.writing]) < 16384 then
|
|
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" .. line
|
|
else
|
|
output = output .. "\n" .. "Error: no space left on device"
|
|
end
|
|
else
|
|
output = output .. "\n" .. "Error: maximum file length exceeded"
|
|
end
|
|
c.output = output
|
|
fsc.show(name,
|
|
make_formspec(output, ""),
|
|
c,
|
|
term.recv)
|
|
return
|
|
else
|
|
-- else parse cmd
|
|
output = output .. "\n> " .. line
|
|
|
|
local meta = minetest.get_meta(c.pos)
|
|
local cmd, params = get_cmd_params(line)
|
|
if meta:get_int("locked") == 1 and cmd ~= "unlock" then
|
|
output = output .. "\nError: Terminal locked, type \"unlock\" to unlock it"
|
|
c.output = output
|
|
fsc.show(name,
|
|
make_formspec(output, "> "),
|
|
c,
|
|
term.recv)
|
|
return
|
|
end
|
|
|
|
local fn = term.commands[cmd]
|
|
if fn then
|
|
output = fn(output, params, c)
|
|
else
|
|
output = output .. "\n" .. "Error: Syntax Error. Try \"help\""
|
|
end
|
|
if output ~= false then
|
|
c.output = output
|
|
fsc.show(name,
|
|
make_formspec(output, "> "),
|
|
c,
|
|
term.recv)
|
|
end
|
|
return
|
|
end
|
|
elseif fields.quit then
|
|
minetest.sound_play("terminal_power_off", {pos = c.pos})
|
|
return true
|
|
elseif fields.output then
|
|
-- CHG events - do not return true
|
|
return
|
|
elseif fields.input then
|
|
-- KEYBOARD events - do not return true
|
|
return
|
|
end
|
|
|
|
log.fs_data(player, name, "term.recv/default", fields)
|
|
return true
|
|
end
|
|
|
|
function term.edit(player, fields, context)
|
|
if not fields.text then
|
|
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, "term.edit/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
|
|
|
|
c.output = output
|
|
|
|
fsc.show(name,
|
|
make_formspec(output, "> "),
|
|
c,
|
|
term.recv)
|
|
return
|
|
end
|
|
|
|
function term.draft(player, fields, context)
|
|
if not fields.text then
|
|
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, "term.draft/terminal", fields)
|
|
return true
|
|
end
|
|
local output = c.output
|
|
|
|
local meta = player:get_meta()
|
|
|
|
-- validate it fits
|
|
if string.len(fields.text) < 49152 then
|
|
meta:set_string("telex_draft", fields.text)
|
|
meta:set_string("telex_subject", fields.subject)
|
|
output = output .. "\n" .. "Draft saved.\n"
|
|
else
|
|
output = output .. "\n" .. "Error: no space left on device\n"
|
|
end
|
|
|
|
c.output = output
|
|
|
|
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()
|
|
local context = {
|
|
pos = pos,
|
|
rw = false,
|
|
output = "",
|
|
name = name,
|
|
}
|
|
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.rw = true
|
|
end
|
|
-- send formspec to player
|
|
fsc.show(name,
|
|
make_formspec(nil, "> "),
|
|
context,
|
|
term.recv)
|
|
minetest.sound_play("terminal_power_on", {pos = pos})
|
|
-- trigger on first use
|
|
local meta = minetest.get_meta(pos)
|
|
if meta:get_int("locked") ~= 1 then
|
|
mech.trigger(pos)
|
|
minetest.after(1.0, mech.untrigger, pos)
|
|
end
|
|
end
|
|
|
|
minetest.register_node("terminal:terminal", {
|
|
description = "Interactive terminal console emulator access interface unit controller",
|
|
drawtype = "mesh",
|
|
mesh = "terminal.obj",
|
|
groups = {mech = 1, trigger = 1},
|
|
tiles = {
|
|
{name = "terminal_base.png"},
|
|
{name = "terminal_idle.png", animation = {type = "vertical_frames", aspect_w = 14, aspect_h = 13, length = 4.0}},
|
|
},
|
|
paramtype = "light",
|
|
paramtype2 = "facedir",
|
|
on_trigger = function(pos)
|
|
local meta = minetest.get_meta(pos)
|
|
minetest.sound_play("terminal_power_on", {pos = pos})
|
|
meta:set_int("locked", 0)
|
|
meta:mark_as_private("locked")
|
|
end,
|
|
on_untrigger = function(pos)
|
|
local meta = minetest.get_meta(pos)
|
|
minetest.sound_play("terminal_power_off", {pos = pos})
|
|
meta:set_int("locked", 1)
|
|
meta:mark_as_private("locked")
|
|
end,
|
|
on_rightclick = terminal_use,
|
|
sounds = sounds.metal,
|
|
})
|