mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-16 16:46:07 +09:00
still wip modularisation, game somehow boots
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
local args = {...}
|
||||
if (#args ~= 2) then
|
||||
print([[usage: cp source_file target_file
|
||||
cp source_file target_directory]])
|
||||
return end
|
||||
fs.cp(os.expandPath(args[1]), os.expandPath(args[2]))
|
||||
@@ -0,0 +1,257 @@
|
||||
local args = {...}
|
||||
|
||||
os.dshenv = {}
|
||||
|
||||
--[[
|
||||
DUMBSHELL: semi sh-compatible language interpreter
|
||||
|
||||
SYNOPSIS
|
||||
dsh [option] [file]
|
||||
sh [option] [file]
|
||||
|
||||
OPTIONS
|
||||
-c string If the -c option is present, then commands are read from
|
||||
string. If there are arguments after the string, they are
|
||||
assigned to the positional parameters, starting with $0.
|
||||
|
||||
|
||||
]]
|
||||
|
||||
-- returns full path. if p starts with "/", only the p is returned
|
||||
local function expandPath(p)
|
||||
return (p:byte(1) == 47) and p or os.expandPath(p)
|
||||
end
|
||||
|
||||
local function startsFromRoot(p)
|
||||
return p:byte(1) == 47
|
||||
end
|
||||
|
||||
local function endsWithSlash(p)
|
||||
return p:byte(#p) == 47
|
||||
end
|
||||
|
||||
--__DSHDEBUG__ = 0x51621D
|
||||
|
||||
local function debug(msg)
|
||||
if __DSHDEBUG__ then print("DEBUG", msg) end
|
||||
end
|
||||
|
||||
local function printErr(msg)
|
||||
print(DLE..msg)
|
||||
end
|
||||
|
||||
local function shallowCopy(t)
|
||||
return {table.unpack(t)}
|
||||
end
|
||||
|
||||
-- BUILTINS -------------------------------------------------------------------
|
||||
|
||||
local function cd(tArgs)
|
||||
local dir = tArgs[1]
|
||||
|
||||
if (dir == nil or #dir < 1) then return end
|
||||
|
||||
local oldWorkingDir = shallowCopy(os.workingDir)
|
||||
|
||||
-- parse dir by delimeter '/'
|
||||
if (dir:byte(1) == 47) then -- if dir begins with '/'
|
||||
os.setWorkingDir(dir)
|
||||
else
|
||||
for word in string.gmatch(dir, "[^/]+") do
|
||||
|
||||
machine.println("CD word: "..word)
|
||||
|
||||
-- 'execute' directory
|
||||
-- Rules: '..' pops os.workingDir
|
||||
-- if dir begins with '/', re-build os.workingDir
|
||||
-- otherwise, push the 'word' to os.workingDir
|
||||
if (word == "..") then
|
||||
os.popWorkingDir()
|
||||
elseif (word == ".") then
|
||||
-- pass
|
||||
else
|
||||
os.pushWorkingDir(word)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- check if the directory exists
|
||||
if not fs.isDir(os.fullWorkPath()) then
|
||||
os.errorNoSuchFileOrDir("cd: "..dir)
|
||||
os.workingDir = shallowCopy(oldWorkingDir)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local function exit(tArgs)
|
||||
exitshell = true
|
||||
end
|
||||
|
||||
local function exec(tArgs)
|
||||
debug("EXECARGS\t"..table.concat(tArgs, ", "))
|
||||
|
||||
if (tArgs[1] == nil or #tArgs[1] < 1) then return end
|
||||
|
||||
local filePath = tArgs[1]
|
||||
local fullFilePath = expandPath(tArgs[1])
|
||||
local execArgs = {}
|
||||
for i, v in ipairs(tArgs) do
|
||||
if (i >= 2) then table.insert(execArgs, v) end
|
||||
end
|
||||
local execByPathFileExists = false
|
||||
local execByPathArg = ""
|
||||
|
||||
-- do some sophisticated file-matching
|
||||
-- step 1: exact file
|
||||
if fs.isFile(fullFilePath) then
|
||||
shell.run(fullFilePath, execArgs)
|
||||
-- step 2: try appending ".lua"
|
||||
elseif fs.isFile(fullFilePath..".lua") then
|
||||
shell.run(fullFilePath..".lua", execArgs)
|
||||
-- step 3: parse os.path (just like $PATH)
|
||||
-- step 3.1: exact file; step 3.2: append ".lua"
|
||||
elseif not startsFromRoot(filePath) then
|
||||
for path in string.gmatch(os.path, "[^;]+") do
|
||||
-- check if 'path' ends with '/'
|
||||
if not endsWithSlash(path) then path = path.."/" end
|
||||
|
||||
debug(path..filePath)
|
||||
|
||||
if fs.isFile(path..filePath) then
|
||||
execByPathArg = path..filePath
|
||||
execByPathFileExists = true
|
||||
break
|
||||
elseif fs.isFile(path..filePath..".lua") then
|
||||
execByPathArg = path..filePath..".lua"
|
||||
execByPathFileExists = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- step else: file not found
|
||||
if execByPathFileExists then
|
||||
shell.run(execByPathArg, execArgs)
|
||||
return EXIT_SUCCESS
|
||||
else
|
||||
if filePath:byte(1) == 46 or filePath:byte(1) == 47 then
|
||||
os.errorNoSuchFile(filePath)
|
||||
else
|
||||
os.errorCmdNotFound(filePath)
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- SYNTAX PARSER --------------------------------------------------------------
|
||||
|
||||
-- tables with functions
|
||||
local builtins = {
|
||||
cd = cd,
|
||||
exit = exit,
|
||||
exec = exec,
|
||||
clear = term.clear
|
||||
}
|
||||
|
||||
local function runcommand(str, recurse)
|
||||
if #str < 1 then return end
|
||||
|
||||
local cmdFound = false
|
||||
|
||||
-- simple cmd parse: WORD ARG1 ARG2 ARG3 ...
|
||||
local args = {}
|
||||
local command = ""
|
||||
for word in string.gmatch(str, "[^ ]+") do
|
||||
if #command < 1 then command = word -- first word will be a name of command
|
||||
else table.insert(args, word) end
|
||||
end
|
||||
|
||||
if builtins[command] then -- try for builtins table
|
||||
builtins[command](args)
|
||||
cmdFound = true
|
||||
return true
|
||||
else
|
||||
|
||||
-- FIXME: 'exec programname args' works, but not 'programname args'
|
||||
|
||||
-- try for os.dshenv.aliases
|
||||
if os.dshenv.aliases[command] then
|
||||
--builtins[os.dshenv.aliases[command]](args)
|
||||
if not recurse then
|
||||
cmdFound = runcommand(os.dshenv.aliases[command], true)
|
||||
end
|
||||
else
|
||||
-- try to launch as program
|
||||
if not recurse then
|
||||
cmdFound = runcommand("exec "..str, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- command not found (really)
|
||||
if not cmdFound then
|
||||
os.errorCmdNotFound(command)
|
||||
end
|
||||
end
|
||||
|
||||
-- END OF SYNTAX PARSER -------------------------------------------------------
|
||||
-- INIT SHELL -----------------------------------------------------------------
|
||||
|
||||
exitshell = false
|
||||
|
||||
-- load up aliases
|
||||
if fs.isFile("/etc/.dshrc") then
|
||||
fs.dofile("/etc/.dshrc")
|
||||
machine.println("[dummix/dsh.lua] Dsh aliases successfully loaded.")
|
||||
end
|
||||
|
||||
|
||||
-- END OF INIT SHELL ----------------------------------------------------------
|
||||
|
||||
-- run interpreter and quit
|
||||
if (args[1]) then
|
||||
local f = fs.open(args[1], "r")
|
||||
local line = ""
|
||||
local s = ""
|
||||
|
||||
-- treat interpreter key (#!) properly
|
||||
-- I'm assuming I was called because I'm the right one
|
||||
-- I have a full trust on "shell.run()" that it rightfully redirected to me
|
||||
--
|
||||
-- NOTE: shell redirection should only apply in interactive mode AND the input
|
||||
-- was like "./filename", or else I'm the right one. Period.
|
||||
-- (and that's how BASH works)
|
||||
repeat
|
||||
line = f.readLine()
|
||||
if line == nil then break end
|
||||
if line:sub(1,2) ~= "#!" then -- ignore line that contains hashbang
|
||||
s = s.." "..line
|
||||
end
|
||||
until line == nil
|
||||
|
||||
f.close()
|
||||
|
||||
runcommand(s)
|
||||
|
||||
exitshell = true
|
||||
end
|
||||
|
||||
|
||||
function getPromptText()
|
||||
--return DC4..os.workingDir[#os.workingDir]..DC3.."# "..DC4 -- we're root! omgwtf
|
||||
return DC4..os.fullWorkPath()..DC3.."# "..DC4 -- we're root! omgwtf
|
||||
end
|
||||
|
||||
-- interactive mode
|
||||
local time = os.date()
|
||||
print(time)
|
||||
|
||||
repeat
|
||||
term.setCursorBlink(true)
|
||||
io.write(getPromptText())
|
||||
local s = input.readLine()
|
||||
runcommand(s)
|
||||
until exitshell
|
||||
|
||||
collectgarbage()
|
||||
return EXIT_SUCCESS
|
||||
@@ -0,0 +1,170 @@
|
||||
--[[
|
||||
LESS IS MORE
|
||||
|
||||
SYNOPSIS:
|
||||
lessismore [filename]
|
||||
less [filename]
|
||||
more [filename]
|
||||
]]
|
||||
local args = {...}
|
||||
|
||||
displayLineNo = true
|
||||
|
||||
local prompt = function()
|
||||
term.setForeCol(3)
|
||||
|
||||
term.emitString(" scroll ", 3, term.height())
|
||||
term.emitString(" quit", 14, term.height())
|
||||
|
||||
term.setForeCol(1)
|
||||
term.emit(18, 1, term.height())
|
||||
term.emit(29, 2, term.height())
|
||||
term.emit(81, 13, term.height())
|
||||
|
||||
term.setForeCol(3)
|
||||
term.setBackCol(0)
|
||||
end
|
||||
|
||||
local function printUsage()
|
||||
print("More: no file specified.")
|
||||
print("Usage: more [filename]")
|
||||
end
|
||||
|
||||
if args[1] == nil or #args[1] <= 0 then printUsage() return end
|
||||
filepath = os.expandPath(args[1])
|
||||
if not fs.isFile(filepath) then os.errorNoSuchFile(filepath) return end
|
||||
|
||||
function log10(n)
|
||||
if n < 1 then return 0
|
||||
elseif n < 10 then return 1
|
||||
elseif n < 100 then return 2
|
||||
elseif n < 1000 then return 3
|
||||
elseif n < 10000 then return 4
|
||||
elseif n < 100000 then return 5
|
||||
elseif n < 1000000 then return 6
|
||||
elseif n < 10000000 then return 7
|
||||
elseif n < 100000000 then return 8
|
||||
elseif n < 1000000000 then return 9
|
||||
else return 10
|
||||
end
|
||||
end
|
||||
|
||||
----------------
|
||||
-- fetch text --
|
||||
----------------
|
||||
lines = {}
|
||||
displayHeight = term.height() - 1 -- bottom one line for prompt
|
||||
|
||||
local file = fs.open(filepath, "r")
|
||||
local line = ""
|
||||
repeat
|
||||
line = file.readLine()
|
||||
table.insert(lines, line)
|
||||
until line == nil
|
||||
|
||||
lineNoLen = log10(#lines)
|
||||
|
||||
-----------
|
||||
-- input --
|
||||
-----------
|
||||
local function scrollDownAction(n)
|
||||
term.clearLine() -- prevent prompt to be scrolled
|
||||
curY = curY + n
|
||||
|
||||
-- prevent overscroll
|
||||
if (curY > #lines - displayHeight) then
|
||||
curY = #lines - displayHeight
|
||||
end
|
||||
|
||||
term.scroll(n)
|
||||
for i = 0, n - 1 do
|
||||
drawString(curY + displayHeight - i, displayHeight - i) -- redraw newline
|
||||
end
|
||||
end
|
||||
|
||||
local function scrollUpAction(n)
|
||||
curY = curY - n
|
||||
|
||||
-- prevent overscroll
|
||||
if (curY < 1) then
|
||||
curY = 1
|
||||
end
|
||||
|
||||
term.scroll(-n)
|
||||
for i = 0, n - 1 do
|
||||
drawString(curY + i, i + 1) -- redraw prev line
|
||||
end
|
||||
term.setCursor(n, term.height())
|
||||
end
|
||||
|
||||
local function processInput()
|
||||
if input.isKeyDown(keys.q) then quit = true end
|
||||
if input.isKeyDown(keys.down) and curY < #lines - displayHeight then
|
||||
scrollDownAction(1)
|
||||
prompt()
|
||||
elseif input.isKeyDown(keys.pageDown) and curY < #lines - displayHeight then
|
||||
scrollDownAction(8)
|
||||
prompt()
|
||||
elseif input.isKeyDown(keys.up) and curY > 1 then
|
||||
scrollUpAction(1)
|
||||
term.clearLine() -- make space for prompt
|
||||
prompt()
|
||||
elseif input.isKeyDown(keys.pageUp) and curY > 1 then
|
||||
scrollUpAction(8)
|
||||
term.clearLine() -- make space for prompt
|
||||
prompt()
|
||||
end
|
||||
|
||||
machine.sleep(50)
|
||||
end
|
||||
|
||||
-------------
|
||||
-- display --
|
||||
-------------
|
||||
displayWidth = term.width() - 1 - (displayLineNo and lineNoLen or 0)
|
||||
|
||||
function drawString(lineNo, y)
|
||||
local string = (lineNo > #lines) and ""
|
||||
or lines[lineNo]:sub(curX, curX + displayWidth)
|
||||
|
||||
if (displayLineNo) then
|
||||
local lineNoStr = DC3..string.format("%"..lineNoLen.."d", curY + y - 1)..DC4
|
||||
string = lineNoStr..string
|
||||
end
|
||||
|
||||
local strDrawX = curX
|
||||
term.emitString(string, strDrawX, y)
|
||||
end
|
||||
|
||||
function redrawText()
|
||||
for i = curY, #lines do
|
||||
if (i >= displayHeight + curY) then break end
|
||||
drawString(i, i - curY + 1)
|
||||
end
|
||||
end
|
||||
|
||||
curY = 1
|
||||
curX = 1
|
||||
quit = false
|
||||
|
||||
if term.isTeletype() then
|
||||
for _, l in ipairs(line) do
|
||||
term.print(l)
|
||||
end
|
||||
quit = true
|
||||
end
|
||||
|
||||
|
||||
term.clear()
|
||||
term.setCursorBlink(false)
|
||||
redrawText()
|
||||
|
||||
repeat
|
||||
prompt()
|
||||
|
||||
term.setCursor(1, term.height())
|
||||
processInput()
|
||||
until quit
|
||||
|
||||
term.clearLine()
|
||||
return
|
||||
@@ -0,0 +1,10 @@
|
||||
local args = {...}
|
||||
|
||||
local dir = os.fullWorkPath()--(#args < 1) and os.fullWorkPath() or args[1]
|
||||
|
||||
local list = fs.list("/"..dir)
|
||||
table.sort(list)
|
||||
|
||||
for _, v in ipairs(list) do
|
||||
print(v)
|
||||
end
|
||||
@@ -0,0 +1,76 @@
|
||||
local args = {...}
|
||||
local _APPVERSION = 0.3
|
||||
--[[
|
||||
MOONSHELL: basically just lua.lua
|
||||
|
||||
SYNOPSIS
|
||||
msh [file]
|
||||
|
||||
msh: Runs shell in interactive mode
|
||||
msh [file]: Try to execute file as Lua script
|
||||
|
||||
]]
|
||||
|
||||
-- run interpreter and quit
|
||||
if (args[1]) then
|
||||
local f = fs.open(args[1], "r")
|
||||
local line = ""
|
||||
local s = ""
|
||||
|
||||
-- treat interpreter key (#!) properly
|
||||
-- I'm assuming I was called because I'm the right one
|
||||
-- I have a full trust on "shell.run()" that it rightfully redirected to me
|
||||
repeat
|
||||
line = f.readLine()
|
||||
if line == nil then break end
|
||||
if line:sub(1,2) ~= "#!" then -- ignore line that contains hashbang
|
||||
s = s.." "..line
|
||||
end
|
||||
until line == nil
|
||||
|
||||
f.close()
|
||||
|
||||
xpcall(
|
||||
function() _G.runscript(s, "="..args[1]) end,
|
||||
function(err) print(DLE..err) end
|
||||
)
|
||||
|
||||
goto terminate
|
||||
end
|
||||
|
||||
-- interactive mode. This is a copy of BOOT.lua
|
||||
run = shell.run
|
||||
|
||||
print("Moonshell "..DC2.._APPVERSION..DC4..", running "..DC2.._VERSION..DC4)
|
||||
print("Lua is copyrighted (C) 1994-2013 Lua.org, PUC-Rio")
|
||||
print("Run run(path) to execute program on 'path'.")
|
||||
print("Run exit() to quit.")
|
||||
|
||||
while not machine.isHalted() do
|
||||
term.setCursorBlink(true)
|
||||
|
||||
io.write(DC3.."lua"..computer.prompt)
|
||||
|
||||
local s = input.readLine()
|
||||
|
||||
if s == "exit()" then break end
|
||||
|
||||
xpcall(
|
||||
function()
|
||||
if s:byte(1) == 61 then -- print out value
|
||||
s1 = string.sub(s, 2)
|
||||
_G.runscript("print(tostring("..s1.."))\n", "=stdin")
|
||||
else
|
||||
_G.runscript(s, "=stdin")
|
||||
end
|
||||
end,
|
||||
function(err) print(DLE..err) end -- it catches logical errors
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
::terminate::
|
||||
collectgarbage()
|
||||
return EXIT_SUCCESS
|
||||
@@ -0,0 +1,6 @@
|
||||
local args = {...}
|
||||
if (#args ~= 2) then
|
||||
print([[usage: mv source target
|
||||
mv source ... directory]])
|
||||
return end
|
||||
fs.mv(os.expandPath(args[1]), os.expandPath(args[2]))
|
||||
@@ -0,0 +1 @@
|
||||
fs.dofile("/etc/_boot.lua")
|
||||
@@ -0,0 +1,8 @@
|
||||
-- dsh aliases
|
||||
os.dshenv.aliases = {
|
||||
lua = "msh",
|
||||
sh = "dsh",
|
||||
shutdown = "exit", -- should be a separate program that actually halts the system
|
||||
less = "lessismore",
|
||||
more = "lessismore"
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
--[[
|
||||
Bootloader for Operation System
|
||||
|
||||
Created by minjaesong on 2016-09-21
|
||||
]]
|
||||
|
||||
-- check directories
|
||||
dirlist = {
|
||||
"/boot",
|
||||
"/bin", -- crucial binaries (e.g. cat, ls, sh(ell), cp, rm, mkdir), it's loosely an UNIX system
|
||||
"/usr",
|
||||
"/usr/bin", -- more utilities and binaries (e.g. less/more, nano)
|
||||
"/home", -- home directory for user
|
||||
"/home/bin", -- user-installed apps
|
||||
"/media" -- auto mounts (e.g. "/media/fd1", "/media/hdb", "/media/sda")
|
||||
}
|
||||
-- just make them if they don't exist
|
||||
for _, dir in ipairs(dirlist) do
|
||||
fs.mkdir(dir)
|
||||
end
|
||||
|
||||
|
||||
if not _G.os then _G.os = {} end
|
||||
os.version = "0.0"
|
||||
os.EXIT_SUCCESS = 0
|
||||
os.workingDir = {"home"}
|
||||
os.path = "home/bin/;/usr/bin/;/bin/" -- infamous $path
|
||||
os.fullWorkPath = function()
|
||||
local ret = table.concat(os.workingDir, "/") -- there's nothing wrong with this.
|
||||
|
||||
if computer.verbose then
|
||||
machine.println("workingDir size: "..#os.workingDir)
|
||||
machine.println("fullWorkPath: "..ret)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
os.setWorkingDir = function(s)
|
||||
if s:byte(#s) == 47 then
|
||||
s = string.sub(s, 1, #s - 1)
|
||||
end
|
||||
if s:byte(1) == 47 then
|
||||
s = string.sub(s, 2, #s)
|
||||
end
|
||||
|
||||
if computer.verbose then
|
||||
machine.println("renew working dir; '"..s.."'")
|
||||
end
|
||||
|
||||
local oldWorkingDir = {table.unpack(os.workingDir)}
|
||||
|
||||
-- renew working directory, EVEN IF s STARTS WITH '/'
|
||||
local t = {}
|
||||
for word in string.gmatch(s, "[^/]+") do
|
||||
table.insert(t, word)
|
||||
end
|
||||
|
||||
os.workingDir = t
|
||||
|
||||
-- check if the directory exists
|
||||
if not fs.isDir(s) then
|
||||
os.errorNoSuchFileOrDir("cd: "..s)
|
||||
os.workingDir = oldWorkingDir
|
||||
return
|
||||
end
|
||||
end
|
||||
os.pushWorkingDir = function(s)
|
||||
if (s == "..") then
|
||||
error("cannot push '..' to working directory.")
|
||||
else
|
||||
table.insert(os.workingDir, s)
|
||||
|
||||
if computer.verbose then
|
||||
machine.println("pushing '"..s.."' to working directory.")
|
||||
end
|
||||
end
|
||||
end
|
||||
os.popWorkingDir = function()
|
||||
if (#os.workingDir > 1) then
|
||||
table.remove(os.workingDir)
|
||||
end
|
||||
end
|
||||
-- @param "path/of/arbitrary"
|
||||
-- @return /working/dir/path/of/arbitrary
|
||||
-- input path's trailing '/' is PRESERVED.
|
||||
os.expandPath = function(p)
|
||||
-- not applicable if the path starts with /
|
||||
if p:byte(1) == 47 or p:byte(1) == 92 then
|
||||
return p
|
||||
end
|
||||
|
||||
return os.fullWorkPath().."/"..p
|
||||
end
|
||||
os.defaultshell = "/bin/dsh.lua"
|
||||
os.clock = function() return machine.milliTime() / 1000 end -- uptime of the computer, in seconds
|
||||
|
||||
|
||||
function os.errorCmdNotFound(cmd)
|
||||
print(cmd..": command not found")
|
||||
end
|
||||
|
||||
function os.errorNoSuchFile(cmd)
|
||||
print(cmd..": No such file")
|
||||
end
|
||||
|
||||
function os.errorNoSuchFileOrDir(cmd)
|
||||
print(cmd..": No such file or directory")
|
||||
end
|
||||
function os.errorIsDir(cmd)
|
||||
print(cmd.." is a directory")
|
||||
end
|
||||
|
||||
|
||||
-- run default shell
|
||||
fs.dofile(os.defaultshell)
|
||||
|
||||
-- quit properly
|
||||
shell.status = shell.halt
|
||||
@@ -0,0 +1,12 @@
|
||||
NAME
|
||||
msh - the Moonshell
|
||||
|
||||
SYNOPSIS
|
||||
msh [file]
|
||||
|
||||
COPYRIGHT
|
||||
See copyright information for the game you are actually playing.
|
||||
|
||||
DESCRIPTION
|
||||
Msh is a Lua prompt that reads lua script from the user, or execute
|
||||
a file user had put as an argument.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,47 @@
|
||||
--[[
|
||||
From https://github.com/prapin/LuaBrainFuck/blob/master/brainfuck.lua
|
||||
|
||||
LuaBrainFuck License
|
||||
--------------------
|
||||
|
||||
LuaBrainFuck is placed under the same license as Lua itself,
|
||||
so licensed under terms of the MIT license reproduced below.
|
||||
This means that the library is free software and can be used for both academic
|
||||
and commercial purposes at absolutely no cost.
|
||||
|
||||
===============================================================================
|
||||
|
||||
Copyright (C) 2012 Patrick Rapin, CH-1543 Grandcour
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
===============================================================================
|
||||
|
||||
(end of COPYRIGHT)
|
||||
|
||||
Example usage: require "brainfuck" "+++>>> your BF code here <<<---"
|
||||
]]
|
||||
return function(s)
|
||||
local subst = {["+"]="v=v+1 ", ["-"]="v=v-1 ", [">"]="i=i+1 ", ["<"]="i=i-1 ",
|
||||
["."] = "w(v)", [","]="v=r()", ["["]="while v~=0 do ", ["]"]="end "}
|
||||
local env = setmetatable({ i=0, t=setmetatable({},{__index=function() return 0 end}),
|
||||
r=function() return io.read(1):byte() end, w=function(c) io.write(string.char(c)) end },
|
||||
{__index=function(t,k) return t.t[t.i] end, __newindex=function(t,k,v) t.t[t.i]=v end })
|
||||
load(s:gsub("[^%+%-<>%.,%[%]]+",""):gsub(".", subst), "brainfuck", "t", env)()
|
||||
end
|
||||
@@ -0,0 +1,159 @@
|
||||
--[[
|
||||
-- ComputerCraft API compatibility layer
|
||||
Usage: require("CCAPI")
|
||||
|
||||
Created by minjaesong on 2016-09-16.
|
||||
--]]
|
||||
|
||||
--------------
|
||||
-- PREAMBLE --
|
||||
--------------
|
||||
|
||||
if term.isTeletype() then error("This is a teletype; cannot use CCAPI layer") end
|
||||
|
||||
|
||||
table.insert(computer.loadedCLayer, "CCAPI")
|
||||
|
||||
|
||||
local function intLog2(i)
|
||||
if i == 0 then return 0 end
|
||||
local log = 0
|
||||
if bit32.band(i, 0xffff0000) ~= 0 then i = bit32.rshift(i, 16) log = 16 end
|
||||
if i >= 256 then i = bit32.rshift(i, 8) log = log + 8 end
|
||||
if i >= 16 then i = bit32.rshift(i, 8) log = log + 4 end
|
||||
if i >= 4 then i = bit32.rshift(i, 8) log = log + 2 end
|
||||
return log + bit32.rshift(i, 1)
|
||||
end
|
||||
|
||||
|
||||
-----------------------
|
||||
-- BIT API Extension --
|
||||
-----------------------
|
||||
|
||||
bit.blshift = bit32.lshift(n, bits)
|
||||
bit.brshift = bit32.arshift(n, bits)
|
||||
bit.blogic_rshift = bit32.rshift(n, bits)
|
||||
|
||||
|
||||
----------------
|
||||
-- COLORS API --
|
||||
----------------
|
||||
|
||||
_G.colors = {}
|
||||
|
||||
colors.white = 0x1
|
||||
colors.orange = 0x2
|
||||
colors.magenta = 0x4
|
||||
colors.lightBlue = 0x8
|
||||
colors.yellow = 0x10
|
||||
colors.lime = 0x20
|
||||
colors.pink = 0x40
|
||||
colors.gray = 0x80
|
||||
colors.grey = 0x80
|
||||
colors.lightGray = 0x100
|
||||
colors.lightGrey = 0x100
|
||||
colors.cyan = 0x200
|
||||
colors.purple = 0x400
|
||||
colors.blue = 0x800
|
||||
colors.brown = 0x1000
|
||||
colors.green = 0x2000
|
||||
colors.red = 0x4000
|
||||
colors.black = 0x8000
|
||||
|
||||
local function normaliseCCcol(cccol)
|
||||
if cccol >= 0x1 and cccol <= 0xFFFF then
|
||||
return intLog2(cccol)
|
||||
else
|
||||
error("invalid CC Colors: "..cccol)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
_G.colours = _G.colors
|
||||
|
||||
|
||||
--------------
|
||||
-- TERM API --
|
||||
--------------
|
||||
|
||||
-- paint_index -> Terminal colour index
|
||||
local ccToGameCol = {--pink
|
||||
1, 5, 7, 10, 4, 11, 15, 2, 3, 10, 8, 9, 14, 12, 6, 0
|
||||
}
|
||||
|
||||
-- "a" -> 10, "3" -> 3
|
||||
local function cHexToInt(c)
|
||||
if type(c) == "number" then -- char
|
||||
if c >= 48 and c <= 57 then
|
||||
return c - 48
|
||||
elseif c >= 65 and c <= 70 then
|
||||
return c - 65 + 10
|
||||
elseif c >= 97 and c <= 102 then
|
||||
return c - 97 + 10
|
||||
else
|
||||
return 0
|
||||
end
|
||||
elseif type(c) == "string" then -- single-letter string
|
||||
if c:byte(1) >= 48 and c:byte(1) <= 57 then
|
||||
return c:byte(1) - 48
|
||||
elseif c:byte(1) >= 65 and c:byte(1) <= 70 then
|
||||
return c:byte(1) - 65 + 10
|
||||
elseif c:byte(1) >= 97 and c:byte(1) <= 102 then
|
||||
return c:byte(1) - 97 + 10
|
||||
else
|
||||
--error("unrepresentable: " .. c)
|
||||
-- return black, as defined in http://www.computercraft.info/wiki/Term.blit
|
||||
return 0
|
||||
end
|
||||
else
|
||||
error("bad argument (string or number expected, got "..type(c)..")")
|
||||
end
|
||||
end
|
||||
|
||||
-- str, str, str
|
||||
term.blit = function(text, foreCol, backCol)
|
||||
assert(
|
||||
type(text) == "string" and type(backCol) == "string" and type(foreCol) == "string",
|
||||
"bad argument: (string, string, string expected, got "..type(text)..", "..type(foreCol)..", "..type(backCol)..")"
|
||||
)
|
||||
if #text ~= #foreCol or #text ~= #backCol or #foreCol ~= #backCol then
|
||||
error("arguments must be the same length")
|
||||
end
|
||||
|
||||
for i = 1, #text do
|
||||
term.setForeCol(ccToGameCol[1 + cHexToInt(foreCol:byte(i))])
|
||||
term.setBackCol(ccToGameCol[1 + cHexToInt(backCol:byte(i))])
|
||||
term.emit(text:byte(i))
|
||||
term.moveCursor(term.getX() + 1, term.getY())
|
||||
end
|
||||
end
|
||||
|
||||
term.getCursorPos = term.getCursor
|
||||
term.setCursorPos = term.moveCursor
|
||||
term.setCursorBlink = term.blink
|
||||
term.isColor = term.isCol
|
||||
term.getSize = term.size
|
||||
term.setTextColor = function(cccol) term.setForeCol(ccToGameCol[normaliseCCcol(cccol)]) end
|
||||
term.getTextColor = term.getForeCol
|
||||
term.setBackgroundColor = function(cccol) term.setBackCol(ccToGameCol[normaliseCCcol(cccol)]) end
|
||||
term.getBackgroundColor = term.getBackCol
|
||||
|
||||
|
||||
--------------------
|
||||
-- FILESYSTEM API --
|
||||
--------------------
|
||||
|
||||
fs.makeDir = fs.mkdir
|
||||
fs.move = fs.mv
|
||||
fs.copy = fs.cp
|
||||
fs.delete = fs.rm
|
||||
fs.combine = fs.concat
|
||||
fs.getDir = fs.parent
|
||||
fs.run = fs.dofile
|
||||
|
||||
|
||||
------------------
|
||||
-- DOWN AND OUT --
|
||||
------------------
|
||||
|
||||
if computer.verbose then print("ComputerCraft compatibility layer successfully loaded.") end
|
||||
@@ -0,0 +1,434 @@
|
||||
--[[
|
||||
Created by minjaesong on 2016-09-15.
|
||||
--]]
|
||||
|
||||
-------------
|
||||
-- ALIASES --
|
||||
-------------
|
||||
|
||||
--_G.io = {} -- we make our own sandbox'd system
|
||||
|
||||
--[[fs.dofile = function(p, ...)
|
||||
local f = fs.open(p, "r")
|
||||
local s = f.readAll()
|
||||
_G.runscript(s, "="..p, ...)
|
||||
end]] -- implementation moved to BOOT.lua
|
||||
|
||||
_G.loadstring = _G.load
|
||||
|
||||
--_G.dofile = function(f) fs.dofile(f) end
|
||||
|
||||
|
||||
fs.fetchText = function(p)
|
||||
local file = fs.open(p, "r")
|
||||
local text = file.readAll()
|
||||
file.close()
|
||||
return text
|
||||
end
|
||||
|
||||
-----------------------------------------
|
||||
-- INPUTSTREAM AND SCANNER (java-like) --
|
||||
-----------------------------------------
|
||||
--[[
|
||||
In whatever code that actually runs everything (computer),
|
||||
there must be:
|
||||
|
||||
override fun keyPressed(key: Int, c: Char) {
|
||||
super.keyPressed(key, c)
|
||||
vt.keyPressed(key, c)
|
||||
|
||||
if (key == Key.RETURN) {
|
||||
val input = vt.closeInputString()
|
||||
}
|
||||
}
|
||||
|
||||
...it basically says to close the input if RETURN is hit,
|
||||
and THIS exact part will close the input for this function.
|
||||
]]
|
||||
_G.__scanforline__ = function(echo) -- pass '1' to not echo; pass nothing to echo
|
||||
machine.closeInputString()
|
||||
machine.openInput(echo or 0)
|
||||
_G.__scanMode__ = "line"
|
||||
local s
|
||||
repeat -- we can do this ONLY IF lua execution process is SEPARATE THREAD
|
||||
s = machine.__readFromStdin()
|
||||
until s
|
||||
-- input is closed when RETURN is hit. See above comments.
|
||||
return s
|
||||
end
|
||||
|
||||
-- use Keys API to identify the keycode
|
||||
--[[_G.__scanforkey__ = function(echo) -- pass '1' to not echo; pass nothing to echo
|
||||
machine.closeInputString()
|
||||
machine.openInput(echo or 0)
|
||||
_G.__scanMode__ = "a_key"
|
||||
local key
|
||||
repeat -- we can do this ONLY IF lua execution process is SEPARATE THREAD
|
||||
key = machine.getLastKeyPress()
|
||||
until key
|
||||
-- input is closed when any key is hit. See above comments.
|
||||
return key
|
||||
end]] -- DELETED: use _G.input.isKeyDown(keycode)
|
||||
|
||||
--- ---
|
||||
-- IO IMPLEMENTATION --
|
||||
--- ---
|
||||
|
||||
|
||||
io.__openfile__ = "stdin"
|
||||
io.stdin = "stdin"
|
||||
io.stdout = "stdout"
|
||||
io.stderr = "stderr"
|
||||
|
||||
io.open = fs.open
|
||||
|
||||
io.input = function(luafile)
|
||||
io.__openfile__ = luafile
|
||||
end
|
||||
|
||||
io.read = function(option)
|
||||
if io.__openfile__ == "stdin" then
|
||||
local input = {}
|
||||
|
||||
-- RETURN not hit
|
||||
while true do
|
||||
local inkey = machine.__readFromStdin()
|
||||
if inkey == 13 or inkey == 10 then
|
||||
break
|
||||
elseif inkey == 8 or inkey == 127 then
|
||||
io.write(string.char(inkey))
|
||||
table.remove(input)
|
||||
elseif inkey > 0 then
|
||||
io.write(string.char(inkey))
|
||||
table.insert(input, string.char(inkey))
|
||||
end
|
||||
end
|
||||
-- RETURN finally hit
|
||||
io.write("\n")
|
||||
|
||||
return table.concat(input)
|
||||
end
|
||||
|
||||
function _readAll()
|
||||
return io.open(io.__openfile__).readAll()
|
||||
end
|
||||
|
||||
function _readLine()
|
||||
return io.open(io.__openfile__).readLine()
|
||||
end
|
||||
|
||||
options = {}
|
||||
options["*n"] = function() error("Read number is not supported, yet!") end--_readNumber
|
||||
options["*a"] = _readAll
|
||||
options["*l"] = _readLine
|
||||
end
|
||||
|
||||
-----------------
|
||||
-- PRINTSTREAM --
|
||||
-----------------
|
||||
|
||||
-- only useful when IO is "opening" stdin
|
||||
--[[io.write = function(...)
|
||||
local args = {...}
|
||||
for _, v in ipairs(args) do
|
||||
local s
|
||||
if v == nil then
|
||||
s = "nil"
|
||||
else
|
||||
s = tostring(v)
|
||||
end
|
||||
term.write(s)
|
||||
end
|
||||
end]]
|
||||
-- for some reason, inputstream above kills 'print' function.
|
||||
-- So we rewrite it.
|
||||
--[[_G.print = function(...) -- dependent on above io.write reimpl
|
||||
local args = {...}
|
||||
|
||||
io.write(args[1])
|
||||
|
||||
if (#args > 1) then
|
||||
for i = 2, #args do
|
||||
io.write("\t")
|
||||
io.write(args[i])
|
||||
end
|
||||
end
|
||||
|
||||
io.write("\n")
|
||||
end]]
|
||||
|
||||
|
||||
---------------
|
||||
-- SHELL API --
|
||||
---------------
|
||||
|
||||
_G.shell = {}
|
||||
shell.status = shell.ok
|
||||
|
||||
-- run a script with path (string) and argstable (table)
|
||||
shell.run = function(path, argstable)
|
||||
-- check for interpreter key "#!"
|
||||
local f = fs.open(path, "r")
|
||||
local s = f.readAll()
|
||||
f.close()
|
||||
|
||||
if s:sub(1,2) == "#!" then
|
||||
local interpreter = s:sub(3)
|
||||
if not argstable then
|
||||
xpcall(function() fs.dofile(interpreter..".lua", path) end, function(err) print(DLE..err) end)
|
||||
else
|
||||
xpcall(function() fs.dofile(interpreter..".lua", path, table.unpack(argstable)) end, function(err) print(DLE..err) end)
|
||||
end
|
||||
else
|
||||
if not argstable then
|
||||
xpcall(function() fs.dofile(path) end, function(err) print(DLE..err) end)
|
||||
else
|
||||
xpcall(function() fs.dofile(path, table.unpack(argstable)) end, function(err) print(DLE..err) end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
shell.ok = 0
|
||||
shell.halt = 127
|
||||
|
||||
|
||||
--------------
|
||||
-- HEXUTILS --
|
||||
--------------
|
||||
|
||||
_G.hexutils = {}
|
||||
|
||||
_G.hexutils.toHexString = function(byteString)
|
||||
assert(type(byteString) == "string", error("Expected string."))
|
||||
|
||||
-- speedup
|
||||
local function iToHex(i)
|
||||
if i == 0 then return "0"
|
||||
elseif i == 1 then return "1"
|
||||
elseif i == 2 then return "2"
|
||||
elseif i == 3 then return "3"
|
||||
elseif i == 4 then return "4"
|
||||
elseif i == 5 then return "5"
|
||||
elseif i == 6 then return "6"
|
||||
elseif i == 7 then return "7"
|
||||
elseif i == 8 then return "8"
|
||||
elseif i == 9 then return "9"
|
||||
elseif i == 10 then return "a"
|
||||
elseif i == 11 then return "b"
|
||||
elseif i == 12 then return "c"
|
||||
elseif i == 13 then return "d"
|
||||
elseif i == 14 then return "e"
|
||||
elseif i == 15 then return "f"
|
||||
else error("unrepresentable: " .. i)
|
||||
end
|
||||
end
|
||||
|
||||
local ret = ""
|
||||
|
||||
for i = 1, #byteString do
|
||||
local c = byteString:byte(i)
|
||||
local msb = iToHex(bit32.rshift(c, 4) % 16)
|
||||
local lsb = iToHex(c % 16)
|
||||
|
||||
ret = ret .. (msb .. lsb)
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
|
||||
--------------
|
||||
-- KEYS API --
|
||||
--------------
|
||||
-- ComputerCraft compliant
|
||||
local keycodeNumToName = {
|
||||
["30"] = "a",
|
||||
["48"] = "b",
|
||||
["46"] = "c",
|
||||
["32"] = "d",
|
||||
["18"] = "e",
|
||||
["33"] = "f",
|
||||
["34"] = "g",
|
||||
["35"] = "h",
|
||||
["23"] = "i",
|
||||
["36"] = "j",
|
||||
["37"] = "k",
|
||||
["38"] = "l",
|
||||
["50"] = "m",
|
||||
["49"] = "n",
|
||||
["24"] = "o",
|
||||
["25"] = "p",
|
||||
["16"] = "q",
|
||||
["19"] = "r",
|
||||
["31"] = "s",
|
||||
["20"] = "t",
|
||||
["22"] = "u",
|
||||
["47"] = "v",
|
||||
["17"] = "w",
|
||||
["45"] = "x",
|
||||
["21"] = "y",
|
||||
["44"] = "z",
|
||||
["2"] = "one",
|
||||
["3"] = "two",
|
||||
["4"] = "three",
|
||||
["5"] = "four",
|
||||
["6"] = "five",
|
||||
["7"] = "six",
|
||||
["8"] = "seven",
|
||||
["9"] = "eight",
|
||||
["10"] = "nine",
|
||||
["11"] = "zero",
|
||||
["12"] = "minus",
|
||||
["13"] = "equals",
|
||||
["14"] = "backspace",
|
||||
["15"] = "tab",
|
||||
["26"] = "leftBracket",
|
||||
["27"] = "rightBracket",
|
||||
["28"] = "enter",
|
||||
["29"] = "leftCtrl",
|
||||
["39"] = "semiColon",
|
||||
["40"] = "apostrophe",
|
||||
["41"] = "grave",
|
||||
["42"] = "leftShift",
|
||||
["43"] = "backslash",
|
||||
["51"] = "comma",
|
||||
["52"] = "period",
|
||||
["53"] = "slash",
|
||||
["54"] = "rightShift",
|
||||
["55"] = "multiply",
|
||||
["56"] = "leftAlt",
|
||||
["57"] = "space",
|
||||
["58"] = "capsLock",
|
||||
["59"] = "f1",
|
||||
["60"] = "f2",
|
||||
["61"] = "f3",
|
||||
["62"] = "f4",
|
||||
["63"] = "f5",
|
||||
["64"] = "f6",
|
||||
["65"] = "f7",
|
||||
["66"] = "f8",
|
||||
["67"] = "f9",
|
||||
["68"] = "f10",
|
||||
["69"] = "numLock",
|
||||
["70"] = "scollLock",
|
||||
["87"] = "f11",
|
||||
["88"] = "f12",
|
||||
["89"] = "f13",
|
||||
["90"] = "f14",
|
||||
["91"] = "f15",
|
||||
["144"] = "cimcumflex",
|
||||
["145"] = "at",
|
||||
["146"] = "colon",
|
||||
["147"] = "underscore",
|
||||
["157"] = "rightCtrl",
|
||||
["184"] = "rightAlt",
|
||||
["197"] = "pause",
|
||||
["199"] = "home",
|
||||
["200"] = "up",
|
||||
["201"] = "pageUp",
|
||||
["203"] = "left",
|
||||
["205"] = "right",
|
||||
["207"] = "end",
|
||||
["208"] = "down",
|
||||
["209"] = "pageDown",
|
||||
["210"] = "insert",
|
||||
["211"] = "delete",
|
||||
["219"] = "leftCommand"
|
||||
}
|
||||
|
||||
_G.keys = {
|
||||
["a"] = 30,
|
||||
["b"] = 48,
|
||||
["c"] = 46,
|
||||
["d"] = 32,
|
||||
["e"] = 18,
|
||||
["f"] = 33,
|
||||
["g"] = 34,
|
||||
["h"] = 35,
|
||||
["i"] = 23,
|
||||
["j"] = 36,
|
||||
["k"] = 37,
|
||||
["l"] = 38,
|
||||
["m"] = 50,
|
||||
["n"] = 49,
|
||||
["o"] = 24,
|
||||
["p"] = 25,
|
||||
["q"] = 16,
|
||||
["r"] = 19,
|
||||
["s"] = 31,
|
||||
["t"] = 20,
|
||||
["u"] = 22,
|
||||
["v"] = 47,
|
||||
["w"] = 17,
|
||||
["x"] = 45,
|
||||
["y"] = 21,
|
||||
["z"] = 44,
|
||||
["one"] = 2,
|
||||
["two"] = 3,
|
||||
["three"] = 4,
|
||||
["four"] = 5,
|
||||
["five"] = 6,
|
||||
["six"] = 7,
|
||||
["seven"] = 8,
|
||||
["eight"] = 9,
|
||||
["nine"] = 10,
|
||||
["zero"] = 11,
|
||||
["minus"] = 12,
|
||||
["equals"] = 13,
|
||||
["backspace"] = 14,
|
||||
["tab"] = 15,
|
||||
["leftBracket"] = 26,
|
||||
["rightBracket"] = 27,
|
||||
["enter"] = 28,
|
||||
["leftCtrl"] = 29,
|
||||
["semiColon"] = 39,
|
||||
["apostrophe"] = 40,
|
||||
["grave"] = 41,
|
||||
["leftShift"] = 42,
|
||||
["backslash"] = 43,
|
||||
["comma"] = 51,
|
||||
["period"] = 52,
|
||||
["slash"] = 53,
|
||||
["rightShift"] = 54,
|
||||
["multiply"] = 55,
|
||||
["leftAlt"] = 56,
|
||||
["space"] = 57,
|
||||
["capsLock"] = 58,
|
||||
["f1"] = 59,
|
||||
["f2"] = 60,
|
||||
["f3"] = 61,
|
||||
["f4"] = 62,
|
||||
["f5"] = 63,
|
||||
["f6"] = 64,
|
||||
["f7"] = 65,
|
||||
["f8"] = 66,
|
||||
["f9"] = 67,
|
||||
["f10"] = 68,
|
||||
["numLock"] = 69,
|
||||
["scollLock"] = 70,
|
||||
["f11"] = 87,
|
||||
["f12"] = 88,
|
||||
["f13"] = 89,
|
||||
["f14"] = 90,
|
||||
["f15"] = 91,
|
||||
["cimcumflex"] = 144,
|
||||
["at"] = 145,
|
||||
["colon"] = 146,
|
||||
["underscore"] = 147,
|
||||
["rightCtrl"] = 157,
|
||||
["rightAlt"] = 184,
|
||||
["pause"] = 197,
|
||||
["home"] = 199,
|
||||
["up"] = 200,
|
||||
["pageUp"] = 201,
|
||||
["left"] = 203,
|
||||
["right"] = 205,
|
||||
["end"] = 207,
|
||||
["down"] = 208,
|
||||
["pageDown"] = 209,
|
||||
["insert"] = 210,
|
||||
["delete"] = 211,
|
||||
["leftCommand"] = 219
|
||||
}
|
||||
_G.keys.getName = function(code) return keycodeNumToName[tostring(code)] end
|
||||
@@ -0,0 +1,497 @@
|
||||
--[[
|
||||
TBASIC: Simple BASIC language based on the Commodore BASIC Version 2.
|
||||
|
||||
(C64 rulz? Nope.)
|
||||
|
||||
|
||||
How to use in your program:
|
||||
|
||||
1. load the script by:
|
||||
if you're using ComputerCraft, use:
|
||||
os.loadAPI "TBASEXEC.lua"
|
||||
else, use:
|
||||
require "TBASEXEC"
|
||||
|
||||
2. run:
|
||||
_TBASIC.EXEC(string of whole command)
|
||||
]]
|
||||
|
||||
if os and os.loadAPI then -- ComputerCraft
|
||||
os.loadAPI "TBASINCL.lua"
|
||||
else
|
||||
require "TBASINCL"
|
||||
end
|
||||
|
||||
table.concat = function(t, delimeter)
|
||||
if #t == 0 then return "" end
|
||||
local outstr = t[1]
|
||||
for i = 2, #t do
|
||||
outstr = outstr..delimeter..tostring(t[i])
|
||||
end
|
||||
|
||||
return outstr
|
||||
end
|
||||
|
||||
-- Copy from TBASINCL; looks like OpenComputers has a bug...
|
||||
function string_hash(str)
|
||||
local hash = 2166136261
|
||||
for i = 1, #str do
|
||||
hash = hash * 16777619
|
||||
hash = bit.bxor(hash, str:byte(i))
|
||||
end
|
||||
return hash
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- INTERPRETER STATUS ---------------------------------------------------------
|
||||
|
||||
local programlist = {}
|
||||
|
||||
-- LEXER ----------------------------------------------------------------------
|
||||
|
||||
local function appendcommand(lineno, statement)
|
||||
if lineno > _TBASIC._INTPRTR.MAXLINES then
|
||||
_TBASIC._ERROR.LINETOOBIG()
|
||||
elseif lineno < 0 then
|
||||
_TBASIC._ERROR.NOLINENUM()
|
||||
else
|
||||
programlist[lineno] = statement
|
||||
end
|
||||
end
|
||||
|
||||
do -- Avoid heap allocs for performance
|
||||
local tokens = {" ", "\t", ",", "(", ")"} -- initial obvious tokens
|
||||
local longest_token_len = 0
|
||||
-- build 'tokens' table from list of operators from the language
|
||||
for _, v in ipairs(_TBASIC._OPERATR) do
|
||||
if not v:match("[A-Za-z]") then -- we want non-alphabetic operators as a token
|
||||
table.insert(tokens, v)
|
||||
-- get longest_token_len, will be used for 'lookahead'
|
||||
local tokenlen = #v
|
||||
if longest_token_len < #v then
|
||||
longest_token_len = #v
|
||||
end
|
||||
end
|
||||
end
|
||||
-- sort them out using ther hash for binary search
|
||||
table.sort(tokens, function(a, b) return string_hash(a) < string_hash(b) end)
|
||||
|
||||
|
||||
function parsewords(line)
|
||||
if line == nil then return end
|
||||
|
||||
-----------------------
|
||||
-- check line sanity --
|
||||
-----------------------
|
||||
|
||||
-- filter for IF statement
|
||||
if line:sub(1, 2):upper() == "IF" then
|
||||
-- no matching THEN
|
||||
if not line:match("[Tt][Hh][Ee][Nn]") then
|
||||
_TBASIC._ERROR.NOMATCHING("IF", "THEN")
|
||||
-- assignment on IF clause
|
||||
elseif line:match("[Ii][Ff][^\n]+[Tt][Hh][Ee][Nn]"):match("[^=+%-*/%%<>!]=[^=<>]") or
|
||||
line:match("[Ii][Ff][^\n]+[Tt][Hh][Ee][Nn]"):match(":=") then
|
||||
_TBASIC._ERROR.ASGONIF()
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------
|
||||
-- automatically infer and insert some commands --
|
||||
--------------------------------------------------
|
||||
-- (This is starting to get dirty...)
|
||||
|
||||
-- unary minus
|
||||
for matchobj in line:gmatch("%-[0-9]+") do
|
||||
local newline = line:gsub(matchobj, "MINUS "..matchobj:sub(2, #matchobj))
|
||||
line = newline
|
||||
end
|
||||
-- conditional for IF
|
||||
-- if IF statement has no appended paren
|
||||
if line:sub(1, 2):upper() == "IF" and not line:match("[Ii][Ff][ ]*%(") then
|
||||
local newline = line:gsub("[Ii][Ff]", "IF ( ", 1):gsub("[Tt][Hh][Ee][Nn]", " ) THEN", 1)
|
||||
line = newline
|
||||
end
|
||||
-- special treatment for FOR
|
||||
if line:sub(1, 3):upper() == "FOR" then
|
||||
if line:match("[0-9]?%.[0-9]") then -- real number used (e.g. "3.14", ".5")
|
||||
_TBASIC._ERROR.ILLEGALARG()
|
||||
else
|
||||
local varnameintm = line:match(" [^\n]+[ =]")
|
||||
|
||||
if varnameintm then
|
||||
local varname = varnameintm:match("[^= ]+")
|
||||
if varname then
|
||||
local newline = line:gsub(" "..varname.."[ =]", " $"..varname.." "..varname.." = ")
|
||||
line = newline:gsub("= =", "=")
|
||||
else
|
||||
_TBASIC._ERROR.SYNTAX()
|
||||
end
|
||||
end
|
||||
-- basically, "FOR x x = 1 TO 10", which converts to "x x 1 10 TO = FOR",
|
||||
-- which is executed (in RPN) in steps of:
|
||||
-- "x x 1 10 TO = FOR"
|
||||
-- "x x (arr) = FOR"
|
||||
-- "x FOR" -- see this part? we need extra 'x' to feed for the FOR statement to function
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
printdbg("parsing line", line)
|
||||
|
||||
|
||||
|
||||
lextable = {}
|
||||
isquote = false
|
||||
quotemode = false
|
||||
wordbuffer = ""
|
||||
local function flush()
|
||||
if (#wordbuffer > 0) then
|
||||
table.insert(lextable, wordbuffer)
|
||||
wordbuffer = ""
|
||||
end
|
||||
end
|
||||
local function append(char)
|
||||
wordbuffer = wordbuffer..char
|
||||
end
|
||||
local function append_no_whitespace(char)
|
||||
if char ~= " " and char ~= "\t" then
|
||||
wordbuffer = wordbuffer..char
|
||||
end
|
||||
end
|
||||
|
||||
-- return: lookless_count on success, nil on failure
|
||||
local function isdelimeter(string)
|
||||
local cmpval = function(table_elem) return string_hash(table_elem) end
|
||||
local lookless_count = #string
|
||||
local ret = nil
|
||||
repeat
|
||||
ret = table.binsearch(tokens, string:sub(1, lookless_count), cmpval)
|
||||
lookless_count = lookless_count - 1
|
||||
until ret or lookless_count < 1
|
||||
return ret and lookless_count + 1 or false
|
||||
end
|
||||
|
||||
local i = 1 -- Lua Protip: variable in 'for' is immutable, and is different from general variable table, even if they have same name
|
||||
while i <= #line do
|
||||
local c = string.char(line:byte(i))
|
||||
|
||||
local lookahead = line:sub(i, i+longest_token_len)
|
||||
|
||||
if isquote then
|
||||
if c == [["]] then
|
||||
flush()
|
||||
isquote = false
|
||||
else
|
||||
append(c)
|
||||
end
|
||||
else
|
||||
if c == [["]] then
|
||||
isquote = true
|
||||
append_no_whitespace("~")
|
||||
else
|
||||
local delimsize = isdelimeter(lookahead) -- returns nil if no matching delimeter found
|
||||
if delimsize then
|
||||
flush() -- flush buffer
|
||||
append_no_whitespace(lookahead:sub(1, delimsize))
|
||||
flush() -- flush this delimeter
|
||||
i = i + delimsize - 1
|
||||
else
|
||||
append_no_whitespace(c)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
i = i + 1
|
||||
end
|
||||
flush() -- don't forget this!
|
||||
|
||||
|
||||
return lextable
|
||||
end
|
||||
end
|
||||
|
||||
local function readprogram(program)
|
||||
for line in program:gmatch("[^\n]+") do
|
||||
lineno = line:match("[0-9]+ ", 1)
|
||||
|
||||
if not lineno then
|
||||
_TBASIC._ERROR.NOLINENUM()
|
||||
end
|
||||
|
||||
statement = line:sub(#lineno + 1)
|
||||
|
||||
appendcommand(tonumber(lineno), statement)
|
||||
end
|
||||
end
|
||||
|
||||
do -- Avoid heap allocs for performance
|
||||
local function stackpush(t, v)
|
||||
t[#t + 1] = v
|
||||
end
|
||||
|
||||
local function stackpop(t)
|
||||
local v = t[#t]
|
||||
t[#t] = nil
|
||||
return v
|
||||
end
|
||||
|
||||
local function stackpeek(t)
|
||||
local v = t[#t]
|
||||
return v
|
||||
end
|
||||
|
||||
local function unmark(word)
|
||||
if type(word) == "table" then return word end
|
||||
return word:sub(2, #word)
|
||||
end
|
||||
|
||||
local function isoperator(word)
|
||||
if word == nil then return false end
|
||||
return word:byte(1) == 35
|
||||
end
|
||||
|
||||
local isvariable = _TBASIC.isvariable
|
||||
local isnumber = _TBASIC.isnumber
|
||||
local isstring = _TBASIC.isstring
|
||||
|
||||
local function isuserfunc(word)
|
||||
if type(word) == "table" then return false end
|
||||
if word == nil then return false end
|
||||
return word:byte(1) == 64
|
||||
end
|
||||
|
||||
local function isbuiltin(word)
|
||||
if type(word) == "table" then return false end
|
||||
if word == nil then return false end
|
||||
return word:byte(1) == 38
|
||||
end
|
||||
|
||||
local function iskeyword(word)
|
||||
if word == nil then return false end
|
||||
return isoperator(word) or isuserfunc(word) or isbuiltin(word)
|
||||
end
|
||||
|
||||
local function isassign(word)
|
||||
if word == nil then return false end
|
||||
return word ~= "==" and word ~= ">=" and word ~= "<=" and word:byte(#word) == 61
|
||||
end
|
||||
|
||||
-- returns truthy value "terminate_loop" upon termination of loop; nil otherwise.
|
||||
local function execword(word, args)
|
||||
if not _TBASIC.__appexit then
|
||||
printdbg("--> execword", word)
|
||||
printdbg("--> execword_args", table.unpack(args))
|
||||
|
||||
if word == "IF" then
|
||||
printdbg("--> branch statement 'IF'")
|
||||
if not _TBASIC.__readvar(args[1]) then -- if condition 'false'
|
||||
printdbg("--> if condition 'false'", table.unpack(args))
|
||||
return "terminate_loop" -- evaluated as 'true' to Lua
|
||||
else
|
||||
printdbg("--> if condition 'true'", table.unpack(args))
|
||||
end
|
||||
end
|
||||
|
||||
printdbg("--> execword_outarg", table.unpack(args))
|
||||
result = _TBASIC.LUAFN[word][1](table.unpack(args))
|
||||
|
||||
printdbg("--> result", result)
|
||||
stackpush(execstack, result)
|
||||
end
|
||||
end
|
||||
|
||||
function printdbg(...)
|
||||
local debug = false
|
||||
if debug then print("DBG", ...) end
|
||||
end
|
||||
|
||||
|
||||
function interpretline(line)
|
||||
if not _TBASIC.__appexit then
|
||||
--[[
|
||||
impl
|
||||
|
||||
1. (normalise expr using parsewords)
|
||||
2. use _TBASIC.RPNPARSR to convert to RPN
|
||||
3. execute RPN op set like FORTH
|
||||
|
||||
* "&" - internal functions
|
||||
* "@" - user-defined functions
|
||||
* "$" - variables (builtin constants and user-defined) -- familiar, eh?
|
||||
* "#" - operators
|
||||
* "~" - strings
|
||||
* none prepended - data (number or string)
|
||||
]]
|
||||
|
||||
lextable = parsewords(line)
|
||||
local vararg = -13 -- magic
|
||||
|
||||
|
||||
if lextable and lextable[1] ~= nil then
|
||||
if lextable[1]:upper() == "REM" then return nil end
|
||||
|
||||
printdbg("lextable", table.concat(lextable, "|"))
|
||||
|
||||
-- execute expression
|
||||
exprlist = _TBASIC.TORPN(lextable) -- 2 2 #+ &PRINT for "PRINT 2+2"
|
||||
|
||||
printdbg("trying to exec", table.concat(exprlist, " "), "\n--------")
|
||||
|
||||
execstack = {}
|
||||
|
||||
for _, word in ipairs(exprlist) do
|
||||
printdbg("stack before", table.concat(execstack, " "))
|
||||
printdbg("word", word)
|
||||
|
||||
if iskeyword(word) then
|
||||
printdbg("is keyword")
|
||||
|
||||
funcname = unmark(word)
|
||||
args = {}
|
||||
argsize = _TBASIC._GETARGS(funcname)
|
||||
|
||||
printdbg("argsize", argsize)
|
||||
|
||||
if not argsize then
|
||||
_TBASIC._ERROR.DEV_UNIMPL(funcname)
|
||||
else
|
||||
if argsize ~= vararg then
|
||||
-- consume 'argsize' elements from the stack
|
||||
for argcnt = argsize, 1, -1 do
|
||||
if #execstack == 0 then
|
||||
_TBASIC._ERROR.ARGMISSING(funcname)
|
||||
end
|
||||
args[argcnt] = stackpop(execstack)
|
||||
end
|
||||
else
|
||||
-- consume entire stack
|
||||
local reversedargs = {}
|
||||
|
||||
while #execstack > 0 and
|
||||
(isvariable(stackpeek(execstack)) or isnumber(stackpeek(execstack)) or
|
||||
isstring(stackpeek(execstack)))
|
||||
do
|
||||
stackpush(reversedargs, stackpop(execstack))
|
||||
end
|
||||
-- reverse 'args'
|
||||
while #reversedargs > 0 do
|
||||
stackpush(args, stackpop(reversedargs))
|
||||
end
|
||||
end
|
||||
|
||||
local terminate_loop = execword(funcname, args)
|
||||
|
||||
if terminate_loop then
|
||||
printdbg("--> termination of loop")
|
||||
printdbg("--------")
|
||||
break
|
||||
end
|
||||
end
|
||||
elseif isvariable(word) then
|
||||
printdbg("is variable")
|
||||
stackpush(execstack, word) -- push raw variable ($ sign retained)
|
||||
else
|
||||
printdbg("is data")
|
||||
stackpush(execstack, word) -- push number or string
|
||||
end
|
||||
|
||||
printdbg("stack after", table.concat(execstack, " "))
|
||||
printdbg("--------")
|
||||
end
|
||||
|
||||
-- if execstack is not empty, something is wrong
|
||||
if #execstack > 0 then
|
||||
_TBASIC._ERROR.SYNTAX() -- cannot reliably pinpoint which statement has error; use generic error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function termination_condition()
|
||||
return terminated or
|
||||
_TBASIC.__appexit or
|
||||
#_TBASIC._INTPRTR.CALLSTCK > _TBASIC._INTPRTR.STACKMAX
|
||||
end
|
||||
|
||||
local function fetchnextcmd()
|
||||
cmd = nil
|
||||
repeat
|
||||
_TBASIC._INTPRTR.PROGCNTR = _TBASIC._INTPRTR.PROGCNTR + 1
|
||||
cmd = programlist[_TBASIC._INTPRTR.PROGCNTR]
|
||||
|
||||
if _TBASIC._INTPRTR.PROGCNTR > _TBASIC._INTPRTR.MAXLINES then
|
||||
terminated = true
|
||||
break
|
||||
end
|
||||
until cmd ~= nil
|
||||
|
||||
if cmd ~= nil then
|
||||
if _TBASIC._INTPRTR.TRACE then
|
||||
print("PC", _TBASIC._INTPRTR.PROGCNTR)
|
||||
end
|
||||
|
||||
return cmd
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function interpretall()
|
||||
|
||||
terminated = false
|
||||
|
||||
repeat
|
||||
interpretline(fetchnextcmd())
|
||||
until termination_condition()
|
||||
end
|
||||
|
||||
-- END OF LEXER ---------------------------------------------------------------
|
||||
|
||||
-- _TBASIC.SHOWLUAERROR = false -- commented; let the shell handle it
|
||||
|
||||
local testprogram = nil
|
||||
|
||||
_G._TBASIC.EXEC = function(cmdstring) -- you can access this interpreter with this global function
|
||||
_TBASIC._INTPRTR.RESET()
|
||||
programlist = {} -- wipe out previous commands from interpreter (do not delete)
|
||||
readprogram(cmdstring)
|
||||
interpretall()
|
||||
end
|
||||
|
||||
|
||||
if testprogram then
|
||||
_TBASIC._INTPRTR.RESET()
|
||||
programlist = {} -- wipe out previous commands from interpreter (do not delete)
|
||||
readprogram(testprogram)
|
||||
interpretall()
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
Terran BASIC (TBASIC)
|
||||
Copyright (c) 2016 Torvald (minjaesong) and the contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the Software), to deal in the
|
||||
Software without restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
]]
|
||||
@@ -0,0 +1,34 @@
|
||||
-- TBASIC extension
|
||||
|
||||
-- these are utilities. Do not touch these lines
|
||||
local __assert = _TBASIC.__assert
|
||||
local __assertlhand = _TBASIC.__assertlhand
|
||||
local __assertrhand = _TBASIC.__assertrhand
|
||||
local __checknumber = _TBASIC.__checknumber
|
||||
local __checkstring = _TBASIC.__checkstring
|
||||
local __readvar = _TBASIC.__readvar
|
||||
local __resolvevararg = _TBASIC.__resolvevarar
|
||||
local vararg = -13 -- magic
|
||||
-- end of utilities
|
||||
|
||||
|
||||
-- these are the sample code for defining your own words
|
||||
--[[
|
||||
-- actual function that does the job
|
||||
local function _fnupgoer(n)
|
||||
print("Up-goer "..__checknumber(n).." goes up!")
|
||||
end
|
||||
|
||||
-- add the word UPGOER to word list
|
||||
table.insert(_TBASIC._FNCTION, "UPGOER")
|
||||
-- add the actual function '_fnupgoer' and its number of arguments (1) to
|
||||
-- '_TBASIC.LUAFN'. 'UPGOER' part should match with the word you just
|
||||
-- inserted to _TBASIC._FNCTION.
|
||||
_TBASIC.LUAFN.UPGOER = {_fnupgoer, 1}
|
||||
]]
|
||||
|
||||
|
||||
-- little debugger's blessing
|
||||
local function _fnenableluatrace() _TBASIC.SHOWLUAERROR = true end
|
||||
table.insert(_TBASIC._FNCTION, "LUATRACEON")
|
||||
_TBASIC.LUAFN.LUATRACEON = {_fnenableluatrace, 0}
|
||||
@@ -0,0 +1,292 @@
|
||||
--[[
|
||||
TBASIC shell
|
||||
|
||||
Synopsis: TBASIC (filename)
|
||||
If no file is specified, interactive mode will be started
|
||||
|
||||
|
||||
To debug EXEC and/or INCL, there's line ```local debug = false``` on each file; change it to ```true``` manually
|
||||
and you are all set.
|
||||
]]
|
||||
|
||||
|
||||
if os and os.loadAPI then -- ComputerCraft
|
||||
os.loadAPI "TBASINCL.lua"
|
||||
os.loadAPI "TBASEXEC.lua"
|
||||
else
|
||||
require "TBASINCL"
|
||||
require "TBASEXEC"
|
||||
end
|
||||
|
||||
args = {...}
|
||||
|
||||
print(_G._TBASIC._HEADER)
|
||||
_TBASIC.PROMPT()
|
||||
_TBASIC.SHOWLUAERROR = false
|
||||
|
||||
|
||||
local function concat_lines(lines, startindex, endindex)
|
||||
local out = ""
|
||||
for i = startindex or 1, endindex or _TBASIC._INTPRTR.MAXLINES do
|
||||
if lines[i] ~= nil then
|
||||
out = out.."\n"..tostring(i).." "..lines[i]
|
||||
end
|
||||
end
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
|
||||
if args[1] then
|
||||
local prog = nil
|
||||
if fs and fs.open then -- ComputerCraft
|
||||
local inp = assert(fs.open(args[1], "r"))
|
||||
prog = inp:readAll()
|
||||
inp:close()
|
||||
else
|
||||
local inp = assert(io.open(args[1], "r"))
|
||||
prog = inp:read("*all")
|
||||
inp:close()
|
||||
end
|
||||
|
||||
_TBASIC.EXEC(prog)
|
||||
else
|
||||
local terminate_app = false
|
||||
|
||||
local ptn_nums = "[0-9]+"
|
||||
local renum_targets = {"GOTO[ ]+"..ptn_nums, "GOSUB[ ]+"..ptn_nums }
|
||||
|
||||
local lines = {}
|
||||
|
||||
local linenum_match = "[0-9]+ "
|
||||
|
||||
local get_linenum = function(line) return line:sub(1,6):match(linenum_match, 1) end -- line:sub(1,6) limits max linumber to be 99999
|
||||
local split_num_and_statements = function(line)
|
||||
local linenum = get_linenum(line)
|
||||
local statements = line:sub(#linenum + 1)
|
||||
return tonumber(linenum), statements
|
||||
end
|
||||
|
||||
while not terminate_app do
|
||||
local __read = false
|
||||
local line = io.read()
|
||||
|
||||
-- tokenise line by " "
|
||||
local args = {} -- shadows system args
|
||||
for word in line:gmatch("[^ ]+") do
|
||||
table.insert(args, word:upper())
|
||||
end
|
||||
|
||||
|
||||
-- TODO more elegant code than IF-ELSEIF-ELSE
|
||||
|
||||
-- massive if-else for running command, cos implementing proper command executor is too expensive here
|
||||
if line:sub(1,6):match(linenum_match) then -- enter new command
|
||||
local linenum, statements = split_num_and_statements(line)
|
||||
lines[tonumber(linenum)] = statements
|
||||
__read = true
|
||||
elseif args[1] == "NEW" then
|
||||
lines = {}
|
||||
elseif args[1] == "RUN" then
|
||||
_TBASIC.EXEC(concat_lines(lines))
|
||||
elseif args[1] == "LIST" then -- LIST, LIST 42, LIST 10-80
|
||||
if not args[2] then
|
||||
print(concat_lines(lines))
|
||||
else
|
||||
if args[2]:match("-") then -- ranged
|
||||
local range = {}
|
||||
for n in args[2]:gmatch("[^-]+") do
|
||||
table.insert(range, n)
|
||||
end
|
||||
local rangestart = tonumber(range[1])
|
||||
local rangeend = tonumber(range[2])
|
||||
|
||||
if not rangestart or not rangeend then
|
||||
_TBASIC._ERROR.ILLEGALARG()
|
||||
else
|
||||
print(concat_lines(lines, rangestart, rangeend))
|
||||
end
|
||||
else
|
||||
local linenum = tonumber(args[2])
|
||||
if not linenum then
|
||||
_TBASIC._ERROR.ILLEGALARG()
|
||||
else
|
||||
print(concat_lines(lines, linenum, linenum))
|
||||
end
|
||||
end
|
||||
end
|
||||
_TBASIC.PROMPT()
|
||||
__read = true
|
||||
elseif args[1] == "DELETE" then -- DELETE 30, DELETE 454-650
|
||||
if not args[2] then
|
||||
_TBASIC._ERROR.ILLEGALARG()
|
||||
else
|
||||
if args[2]:match("-") then -- ranged
|
||||
local range = {}
|
||||
for n in args[2]:gmatch("[^-]+") do
|
||||
table.insert(range, n)
|
||||
end
|
||||
local rangestart = tonumber(range[1])
|
||||
local rangeend = tonumber(range[2])
|
||||
|
||||
if not rangestart or not rangeend then
|
||||
_TBASIC._ERROR.ILLEGALARG()
|
||||
else
|
||||
for i = rangestart, rangeend do
|
||||
lines[i] = nil
|
||||
end
|
||||
end
|
||||
else
|
||||
local linenum = tonumber(args[2])
|
||||
if not linenum then
|
||||
_TBASIC._ERROR.ILLEGALARG()
|
||||
else
|
||||
lines[linenum] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif args[1] == "EXIT" then
|
||||
terminate_app = true
|
||||
break
|
||||
elseif args[1] == "SAVE" then
|
||||
local status, err = pcall(function()
|
||||
if fs and fs.open then -- computercraft
|
||||
local file = fs.open(args[2], "w")
|
||||
file.write(concat_lines(lines))
|
||||
file.close()
|
||||
else
|
||||
local file = assert(io.open(args[2], "w"))
|
||||
file:write(concat_lines(lines))
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
)
|
||||
if err then
|
||||
if _TBASIC.SHOWLUAERROR then
|
||||
print(err)
|
||||
end
|
||||
_TBASIC._ERROR.IOERR()
|
||||
else
|
||||
print("FILE SAVED")
|
||||
end
|
||||
elseif args[1] == "LOAD" then
|
||||
local status, err = pcall(function()
|
||||
lines = {}
|
||||
if fs and fs.open then -- computercraft
|
||||
local file = fs.open(args[2], "r")
|
||||
local data = file.readAll("*all")
|
||||
for dataline in data:gmatch("[^\n]+") do
|
||||
if #dataline > 0 then
|
||||
local linenum, statements = split_num_and_statements(dataline)
|
||||
lines[linenum] = statements
|
||||
end
|
||||
end
|
||||
file.close()
|
||||
else
|
||||
local file = assert(io.open(args[2], "r"))
|
||||
local data = file:read("*all")
|
||||
for dataline in data:gmatch("[^\n]+") do
|
||||
if #dataline > 0 then
|
||||
local linenum, statements = split_num_and_statements(dataline)
|
||||
lines[linenum] = statements
|
||||
end
|
||||
end
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
)
|
||||
if err then
|
||||
if _TBASIC.SHOWLUAERROR then
|
||||
error(err)
|
||||
end
|
||||
_TBASIC._ERROR.IOERR()
|
||||
else
|
||||
print("FILE LOADED")
|
||||
end
|
||||
elseif args[1] == "RENUM" then
|
||||
local statement_table = {}
|
||||
local renumbering_table = {}
|
||||
local new_linenum_counter = 10
|
||||
-- first, get the list of commands, without line number indexing
|
||||
for i = 1, _TBASIC._INTPRTR.MAXLINES do
|
||||
if lines[i] ~= nil then
|
||||
--table.insert(statement_table, lines[i])
|
||||
statement_table[new_linenum_counter] = lines[i]
|
||||
renumbering_table[i] = new_linenum_counter
|
||||
|
||||
-- test
|
||||
--print("old line", i, "new line", new_linenum_counter)
|
||||
|
||||
new_linenum_counter = new_linenum_counter + 10
|
||||
end
|
||||
end
|
||||
-- copy statement_table into lines table
|
||||
lines = statement_table
|
||||
|
||||
-- re-number GOTO and GOSUB line numbers
|
||||
local line_counter = 0 -- loop counter
|
||||
for line_pc = 0, _TBASIC._INTPRTR.MAXLINES do
|
||||
local line = lines[line_pc]
|
||||
if line then
|
||||
line_counter = line_counter + 1
|
||||
|
||||
-- replace
|
||||
-- extract a <- "GOTO 320"
|
||||
-- extract n_from from a (320), make n_to from it
|
||||
-- make new string b <- "GOTO "..n_to
|
||||
for _, match_string in ipairs(renum_targets) do
|
||||
local match = line:match(match_string)
|
||||
if match then
|
||||
local matching_statement = match:gsub("[ ]+"..ptn_nums, "")
|
||||
local target_line_old = tonumber(match:match(ptn_nums))
|
||||
local target_line_new = renumbering_table[target_line_old]
|
||||
|
||||
local gsub_from = match
|
||||
local gsub_to = matching_statement.." "..target_line_new
|
||||
|
||||
-- test
|
||||
--print("matching_statement", matching_statement, "target_line_old", target_line_old, "target_line_new", target_line_new)
|
||||
--print("gsub_from", gsub_from, "gsub_to", gsub_to)
|
||||
|
||||
-- substitute
|
||||
lines[line_pc] = line:gsub(gsub_from, gsub_to)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif #line == 0 and line:byte(1) ~= 10 and line:byte(1) ~= 13 then
|
||||
__read = true
|
||||
else
|
||||
_TBASIC.EXEC("1 "..line) -- execute command right away
|
||||
end
|
||||
|
||||
-- reset
|
||||
if not __read then
|
||||
_TBASIC.PROMPT()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
Terran BASIC (TBASIC)
|
||||
Copyright (c) 2016 Torvald (minjaesong) and the contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the Software), to deal in the
|
||||
Software without restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
]]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
// TODO Fill in from work_files/romapidoc/romapidoc.tex
|
||||
@@ -0,0 +1,585 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.computer
|
||||
|
||||
import org.luaj.vm2.Globals
|
||||
import org.luaj.vm2.LuaError
|
||||
import org.luaj.vm2.LuaTable
|
||||
import org.luaj.vm2.LuaValue
|
||||
import org.luaj.vm2.lib.TwoArgFunction
|
||||
import org.luaj.vm2.lib.ZeroArgFunction
|
||||
import org.luaj.vm2.lib.jse.JsePlatform
|
||||
import net.torvald.terrarum.KVHashMap
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.Second
|
||||
import net.torvald.terrarum.ceilInt
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VDUtil
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VirtualDisk
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.worldobject.ComputerPartsCodex
|
||||
import org.lwjgl.BufferUtils
|
||||
import org.lwjgl.openal.AL10
|
||||
import java.io.*
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
import java.util.logging.Level
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
/**
|
||||
* A part that makes "computer fixture" actually work
|
||||
*
|
||||
* @param avFixtureComputer : actor values for FixtureComputerBase
|
||||
*
|
||||
* @param term : terminal that is connected to the computer fixtures, null if not connected any.
|
||||
* Created by minjaesong on 2016-09-10.
|
||||
*/
|
||||
class TerrarumComputer(peripheralSlots: Int) {
|
||||
|
||||
val DEBUG_UNLIMITED_MEM = false
|
||||
val DEBUG = true
|
||||
|
||||
|
||||
val maxPeripherals: Int = if (DEBUG) 32 else peripheralSlots
|
||||
|
||||
|
||||
lateinit var luaJ_globals: Globals
|
||||
private set
|
||||
|
||||
var stdout: PrintStream? = null
|
||||
private set
|
||||
var stderr: PrintStream? = null
|
||||
private set
|
||||
var stdin: InputStream? = null
|
||||
private set
|
||||
|
||||
val processorCycle: Int // number of Lua statement to process per tick (1/100 s)
|
||||
get() = ComputerPartsCodex.getProcessorCycles(computerValue.getAsInt("processor") ?: 0)
|
||||
val memSize: Int // in bytes; max: 8 GB
|
||||
get() {
|
||||
if (DEBUG_UNLIMITED_MEM) return 16.shl(20)// 16 MB
|
||||
|
||||
var size = 0
|
||||
for (i in 0..3)
|
||||
size += ComputerPartsCodex.getRamSize(computerValue.getAsInt("memSlot$i")!!)
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
val UUID = java.util.UUID.randomUUID().toString()
|
||||
|
||||
val computerValue = KVHashMap()
|
||||
|
||||
var isHalted = false
|
||||
|
||||
lateinit var term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype
|
||||
private set
|
||||
|
||||
val peripheralTable = Array<net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral?>(peripheralSlots, { null }) // index == slot number
|
||||
|
||||
var stdinInput: Int = -1
|
||||
private set
|
||||
|
||||
|
||||
// os-related functions. These are called "machine" library-wise.
|
||||
private val startupTimestamp: Long = System.currentTimeMillis()
|
||||
/** Time elapsed since the power is on. */
|
||||
val milliTime: Int
|
||||
get() = (System.currentTimeMillis() - startupTimestamp).toInt()
|
||||
|
||||
/** String:
|
||||
* if it's UUID, formatted UUID as string, always 36 chars
|
||||
* if not (test purpose only!), just String
|
||||
*/
|
||||
val diskRack = HashMap<String, VirtualDisk>()
|
||||
|
||||
fun attachDisk(slot: String, filename: String) {
|
||||
computerValue[slot] = filename
|
||||
|
||||
// put disk in diskRack
|
||||
if (filename.isNotEmpty() && filename.isNotBlank()) {
|
||||
diskRack[slot] = VDUtil.readDiskArchive(
|
||||
File(Terrarum.currentSaveDir.path + "/computers/$filename").absoluteFile,
|
||||
Level.WARNING,
|
||||
{ },
|
||||
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Filesystem.sysCharset
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
computerValue["memslot0"] = 4864 // -1 indicates mem slot is empty
|
||||
computerValue["memslot1"] = -1 // put index of item here
|
||||
computerValue["memslot2"] = -1 // ditto.
|
||||
computerValue["memslot3"] = -1 // do.
|
||||
|
||||
computerValue["processor"] = -1 // do.
|
||||
|
||||
// as in "dev/hda"; refers hard disk drive (and no partitioning)
|
||||
attachDisk("hda", "uuid_testhda")
|
||||
attachDisk("hdb", "")
|
||||
attachDisk("hdc", "")
|
||||
attachDisk("hdd", "")
|
||||
// as in "dev/fd1"; refers floppy disk drive
|
||||
attachDisk("fd1", "")
|
||||
attachDisk("fd2", "")
|
||||
attachDisk("fd3", "")
|
||||
attachDisk("fd4", "")
|
||||
// SCSI connected optical drive
|
||||
attachDisk("sda", "")
|
||||
|
||||
// boot device
|
||||
computerValue["boot"] = "hda"
|
||||
}
|
||||
|
||||
fun getPeripheral(tableName: String): net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral? {
|
||||
peripheralTable.forEach {
|
||||
if (it?.tableName == tableName)
|
||||
return it
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getPeripheralSlot(tableName: String): Int? {
|
||||
peripheralTable.forEachIndexed { index, peri ->
|
||||
if (peri?.tableName == tableName)
|
||||
return index
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** @return installed slot */
|
||||
fun attachPeripheral(peri: net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral): Int {
|
||||
(0..maxPeripherals - 1).forEach {
|
||||
try {
|
||||
attachPeripheralTo(peri, it)
|
||||
return it
|
||||
}
|
||||
catch (tryNext: RuntimeException) { }
|
||||
}
|
||||
|
||||
throw RuntimeException("No vacant peripheral slot")
|
||||
}
|
||||
|
||||
fun attachPeripheralTo(peri: net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral, slot: Int) {
|
||||
if (peripheralTable[slot] == null) {
|
||||
peripheralTable[slot] = peri
|
||||
peri.loadLib(luaJ_globals)
|
||||
println("[TerrarumComputer] loading peripheral $peri")
|
||||
}
|
||||
else {
|
||||
throw RuntimeException("Peripheral slot is already taken by: ${peripheralTable[slot]?.tableName}")
|
||||
}
|
||||
}
|
||||
|
||||
fun detachPeripheral(peri: net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral) {
|
||||
// search for the peripheral
|
||||
var found = -1
|
||||
for (i in 0..maxPeripherals - 1) {
|
||||
if (peripheralTable[i] == peri) {
|
||||
found = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if (found >= 0) {
|
||||
peripheralTable[found] = null
|
||||
println("[TerrarumComputer] unloading peripheral $peri")
|
||||
}
|
||||
else {
|
||||
throw IllegalArgumentException("Peripheral not exists: $peri")
|
||||
}
|
||||
}
|
||||
|
||||
fun attachTerminal(term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) {
|
||||
this.term = term
|
||||
initSandbox(term)
|
||||
}
|
||||
|
||||
fun initSandbox(term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) {
|
||||
luaJ_globals = JsePlatform.debugGlobals()
|
||||
|
||||
stdout = net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.TerminalPrintStream(this)
|
||||
stderr = net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.TerminalPrintStream(this)
|
||||
stdin = net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.TerminalInputStream(this)
|
||||
|
||||
luaJ_globals.STDOUT = stdout
|
||||
luaJ_globals.STDERR = stderr
|
||||
luaJ_globals.STDIN = stdin
|
||||
|
||||
luaJ_globals["bit"] = luaJ_globals["bit32"]
|
||||
|
||||
// load libraries
|
||||
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Term(luaJ_globals, term)
|
||||
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Security(luaJ_globals)
|
||||
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Filesystem(luaJ_globals, this)
|
||||
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.HostAccessProvider(luaJ_globals, this)
|
||||
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Input(luaJ_globals, this)
|
||||
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.PcSpeakerDriver(luaJ_globals, this)
|
||||
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.WorldInformationProvider(luaJ_globals)
|
||||
|
||||
// secure the sandbox
|
||||
//luaJ_globals["io"] = LuaValue.NIL
|
||||
// dubug should be sandboxed in BOOT.lua (use OpenComputers code)
|
||||
//val sethook = luaJ_globals["debug"]["sethook"]
|
||||
//luaJ_globals["debug"] = LuaValue.NIL
|
||||
|
||||
// ROM BASIC
|
||||
val inputStream = javaClass.getResourceAsStream("/net/torvald/terrarum/modulecomputers/virtualcomputer/virtualcomputer/assets/lua/BOOT.lua")
|
||||
runCommand(InputStreamReader(inputStream), "=boot")
|
||||
|
||||
// computer-related global functions
|
||||
luaJ_globals["totalMemory"] = LuaFunGetTotalMem(this)
|
||||
|
||||
luaJ_globals["computer"] = LuaTable()
|
||||
// rest of the "computer" APIs should be implemented in BOOT.lua
|
||||
|
||||
|
||||
|
||||
// load every peripheral if we're in DEBUG
|
||||
if (DEBUG) {
|
||||
attachPeripheral(net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.PeripheralInternet(this))
|
||||
attachPeripheral(net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.PeripheralPSG(this))
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
fun update(delta: Float) {
|
||||
|
||||
if (currentExecutionThread.state == Thread.State.TERMINATED) {
|
||||
threadRun = false
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (!isHalted) {
|
||||
runBeepQueueManager(delta)
|
||||
}
|
||||
}
|
||||
|
||||
fun keyPressed(key: Int, c: Char) {
|
||||
stdinInput = c.toInt()
|
||||
|
||||
// wake thread
|
||||
runnableRunCommand.resume()
|
||||
|
||||
synchronized(stdin!!) {
|
||||
(stdin as java.lang.Object).notifyAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun openStdin() {
|
||||
stdinInput = -1
|
||||
// sleep the thread
|
||||
runnableRunCommand.pause()
|
||||
}
|
||||
|
||||
lateinit var currentExecutionThread: Thread
|
||||
private set
|
||||
lateinit var runnableRunCommand: ThreadRunCommand
|
||||
private set
|
||||
private var threadRun = false
|
||||
|
||||
fun runCommand(line: String, env: String) {
|
||||
if (!threadRun) {
|
||||
runnableRunCommand = ThreadRunCommand(luaJ_globals, line, env)
|
||||
currentExecutionThread = Thread(null, runnableRunCommand, "LuaJ Separated")
|
||||
currentExecutionThread.start()
|
||||
threadRun = true
|
||||
}
|
||||
}
|
||||
|
||||
fun runCommand(reader: Reader, filename: String) {
|
||||
if (!threadRun) {
|
||||
runnableRunCommand = ThreadRunCommand(luaJ_globals, reader, filename)
|
||||
currentExecutionThread = Thread(null, runnableRunCommand, "LuaJ Separated")
|
||||
currentExecutionThread.start()
|
||||
threadRun = true
|
||||
}
|
||||
}
|
||||
|
||||
class ThreadRunCommand : Runnable {
|
||||
|
||||
private val mode: Int
|
||||
private val arg1: Any
|
||||
private val arg2: String
|
||||
private val lua: Globals
|
||||
|
||||
@Volatile private var running = true
|
||||
@Volatile private var paused = false
|
||||
private val pauseLock = java.lang.Object()
|
||||
|
||||
constructor(luaInstance: Globals, line: String, env: String) {
|
||||
mode = 0
|
||||
arg1 = line
|
||||
arg2 = env
|
||||
lua = luaInstance
|
||||
}
|
||||
|
||||
constructor(luaInstance: Globals, reader: Reader, filename: String) {
|
||||
mode = 1
|
||||
arg1 = reader
|
||||
arg2 = filename
|
||||
lua = luaInstance
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
synchronized(pauseLock) {
|
||||
if (!running) { // may have changed while waiting to
|
||||
// synchronize on pauseLock
|
||||
return
|
||||
}
|
||||
if (paused) {
|
||||
try {
|
||||
pauseLock.wait() // will cause this Thread to block until
|
||||
// another thread calls pauseLock.notifyAll()
|
||||
// Note that calling wait() will
|
||||
// relinquish the synchronized lock that this
|
||||
// thread holds on pauseLock so another thread
|
||||
// can acquire the lock to call notifyAll()
|
||||
// (link with explanation below this code)
|
||||
}
|
||||
catch (ex: InterruptedException) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!running) { // running might have changed since we paused
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
val chunk: LuaValue
|
||||
if (mode == 0)
|
||||
chunk = lua.load(arg1 as String, arg2)
|
||||
else if (mode == 1)
|
||||
chunk = lua.load(arg1 as Reader, arg2)
|
||||
else
|
||||
throw IllegalArgumentException("Unsupported mode: $mode")
|
||||
|
||||
|
||||
chunk.call()
|
||||
}
|
||||
catch (e: LuaError) {
|
||||
e.printStackTrace(System.err)
|
||||
//lua.STDERR.println("${SimpleTextTerminal.ASCII_DLE}${e.message}${SimpleTextTerminal.ASCII_DC4}")
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
running = false
|
||||
// you might also want to do this:
|
||||
//interrupt()
|
||||
}
|
||||
|
||||
fun pause() {
|
||||
// you may want to throw an IllegalStateException if !running
|
||||
paused = true
|
||||
}
|
||||
|
||||
fun resume() {
|
||||
synchronized(pauseLock) {
|
||||
paused = false
|
||||
pauseLock.notifyAll() // Unblocks thread
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LuaFunGetTotalMem(val computer: TerrarumComputer) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return LuaValue.valueOf(computer.memSize)
|
||||
}
|
||||
}
|
||||
|
||||
class ComputerEmitTone(val computer: TerrarumComputer) : TwoArgFunction() {
|
||||
override fun call(millisec: LuaValue, freq: LuaValue): LuaValue {
|
||||
computer.playTone(millisec.checkdouble().toFloat(), freq.checkdouble())
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// BEEPER DRIVER //
|
||||
///////////////////
|
||||
|
||||
private val beepMaxLen = 10f
|
||||
// let's regard it as a tracker...
|
||||
private val beepQueue = ArrayList<Pair<Second, Double>>()
|
||||
private var beepCursor = -1
|
||||
private var beepQueueLineExecTimer: Second = 0f
|
||||
private var beepQueueFired = false
|
||||
|
||||
private fun runBeepQueueManager(delta: Float) {
|
||||
// start emitTone queue
|
||||
if (beepQueue.size > 0 && beepCursor == -1) {
|
||||
beepCursor = 0
|
||||
}
|
||||
|
||||
// advance emitTone queue
|
||||
if (beepCursor >= 0 && beepQueueLineExecTimer >= beepQueueGetLenOfPtn(beepCursor)) {
|
||||
beepQueueLineExecTimer -= beepQueueGetLenOfPtn(beepCursor)
|
||||
beepCursor += 1
|
||||
beepQueueFired = false
|
||||
}
|
||||
|
||||
// complete emitTone queue
|
||||
if (beepCursor >= beepQueue.size) {
|
||||
clearBeepQueue()
|
||||
}
|
||||
|
||||
// actually play queue
|
||||
if (beepCursor >= 0 && beepQueue.size > 0 && !beepQueueFired) {
|
||||
playTone(beepQueue[beepCursor].first, beepQueue[beepCursor].second)
|
||||
beepQueueFired = true
|
||||
|
||||
// delete sources that is finished. AL is limited to 256 sources. If you exceed it,
|
||||
// we won't get any more sounds played.
|
||||
AL10.alSourcei(oldBeepSource, AL10.AL_BUFFER, 0)
|
||||
AL10.alDeleteSources(oldBeepSource)
|
||||
AL10.alDeleteBuffers(oldBeepBuffer)
|
||||
}
|
||||
|
||||
if (beepQueueFired) beepQueueLineExecTimer += delta
|
||||
}
|
||||
|
||||
fun clearBeepQueue() {
|
||||
beepQueue.clear()
|
||||
beepCursor = -1
|
||||
beepQueueLineExecTimer = 0f
|
||||
|
||||
//AL.destroy()
|
||||
|
||||
if (DEBUG) println("[TerrarumComputer] !! Beep queue clear")
|
||||
}
|
||||
|
||||
fun enqueueBeep(duration: Double, freq: Double) {
|
||||
beepQueue.add(Pair(Math.min(duration.toFloat(), beepMaxLen), freq))
|
||||
}
|
||||
|
||||
fun beepQueueGetLenOfPtn(ptnIndex: Int) = beepQueue[ptnIndex].first
|
||||
|
||||
|
||||
////////////////////
|
||||
// TONE GENERATOR //
|
||||
////////////////////
|
||||
|
||||
private val sampleRate = 44100
|
||||
private var beepSource: Int = -1
|
||||
private var beepBuffer: Int = -1
|
||||
private var oldBeepSource: Int = -1
|
||||
private var oldBeepBuffer: Int = -1
|
||||
var audioData: ByteBuffer? = null
|
||||
|
||||
/**
|
||||
* @param duration : milliseconds
|
||||
* @param rampUp
|
||||
* @param rampDown
|
||||
*
|
||||
* ,---. (true, true) ,---- (true, false) ----. (false, true) ----- (false, false)
|
||||
*/
|
||||
private fun makeAudioData(duration: Second, freq: Double,
|
||||
rampUp: Boolean = true, rampDown: Boolean = true): ByteBuffer {
|
||||
|
||||
TODO("with duration as Seconds")
|
||||
|
||||
val audioDataSize = duration.times(sampleRate).ceilInt()
|
||||
val audioData = BufferUtils.createByteBuffer(audioDataSize)
|
||||
|
||||
/*val realDuration = duration * sampleRate / 1000
|
||||
val chopSize = freq / sampleRate
|
||||
|
||||
val amp = Math.max(4600.0 / freq, 1.0)
|
||||
val nHarmonics = if (freq >= 22050.0) 1
|
||||
else if (freq >= 11025.0) 2
|
||||
else if (freq >= 5512.5) 3
|
||||
else if (freq >= 2756.25) 4
|
||||
else if (freq >= 1378.125) 5
|
||||
else if (freq >= 689.0625) 6
|
||||
else 7
|
||||
|
||||
val transitionThre = 974.47218
|
||||
|
||||
// TODO volume ramping?
|
||||
if (freq == 0.0) {
|
||||
for (_ in 0..audioDataSize - 1) {
|
||||
audioData.put(0x00.toByte())
|
||||
}
|
||||
}
|
||||
else if (freq < transitionThre) { // chopper generator (for low freq)
|
||||
for (tsart in 0..audioDataSize - 1) {
|
||||
var sine: Double = amp * Math.cos(Math.PI * 2 * () * chopSize)
|
||||
if (sine > 0.79) sine = 0.79
|
||||
else if (sine < -0.79) sine = -0.79
|
||||
audioData.put(
|
||||
(0.5 + 0.5 * sine).times(0xFF).roundInt().toByte()
|
||||
)
|
||||
}
|
||||
}
|
||||
else { // harmonics generator (for high freq)
|
||||
for (x in 0..realDuration - 1) {
|
||||
var sine: Double = 0.0
|
||||
for (k in 1..nHarmonics) { // mix only odd harmonics in order to make a squarewave
|
||||
sine += Math.sin(Math.PI * 2 * (2*k - 1) * chopSize * x) / (2*k - 1)
|
||||
}
|
||||
audioData.put(
|
||||
(0.5 + 0.5 * sine).times(0xFF).roundInt().toByte()
|
||||
)
|
||||
}
|
||||
}*/
|
||||
|
||||
audioData.rewind()
|
||||
|
||||
return audioData
|
||||
}
|
||||
|
||||
private fun playTone(length: Second, freq: Double) {
|
||||
/*audioData = makeAudioData(leninmilli, freq)
|
||||
|
||||
|
||||
if (!AL.isCreated()) AL.create()
|
||||
|
||||
|
||||
// Clear error stack.
|
||||
AL10.alGetError()
|
||||
|
||||
oldBeepBuffer = beepBuffer
|
||||
beepBuffer = AL10.alGenBuffers()
|
||||
checkALError()
|
||||
|
||||
try {
|
||||
AL10.alBufferData(beepBuffer, AL10.AL_FORMAT_MONO8, audioData, sampleRate)
|
||||
checkALError()
|
||||
|
||||
oldBeepSource = beepSource
|
||||
beepSource = AL10.alGenSources()
|
||||
checkALError()
|
||||
|
||||
try {
|
||||
AL10.alSourceQueueBuffers(beepSource, beepBuffer)
|
||||
checkALError()
|
||||
|
||||
AL10.alSource3f(beepSource, AL10.AL_POSITION, 0f, 0f, 1f)
|
||||
AL10.alSourcef(beepSource, AL10.AL_REFERENCE_DISTANCE, 1f)
|
||||
AL10.alSourcef(beepSource, AL10.AL_MAX_DISTANCE, 1f)
|
||||
AL10.alSourcef(beepSource, AL10.AL_GAIN, 0.3f)
|
||||
checkALError()
|
||||
|
||||
AL10.alSourcePlay(beepSource)
|
||||
checkALError()
|
||||
}
|
||||
catch (e: ALException) {
|
||||
AL10.alDeleteSources(beepSource)
|
||||
}
|
||||
}
|
||||
catch (e: ALException) {
|
||||
AL10.alDeleteSources(beepSource)
|
||||
}*/
|
||||
}
|
||||
|
||||
// Custom implementation of Util.checkALError() that uses our custom exception.
|
||||
private fun checkALError() {
|
||||
val errorCode = AL10.alGetError()
|
||||
if (errorCode != AL10.AL_NO_ERROR) {
|
||||
throw net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.ALException(errorCode)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,521 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
|
||||
|
||||
import org.luaj.vm2.*
|
||||
import org.luaj.vm2.lib.OneArgFunction
|
||||
import org.luaj.vm2.lib.TwoArgFunction
|
||||
import org.luaj.vm2.lib.ZeroArgFunction
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Term.Companion.checkIBM437
|
||||
import java.io.*
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.NoSuchFileException
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* computer directory:
|
||||
* .../computers/
|
||||
* media/hda/ -> .../computers/<uuid for the hda>/
|
||||
*
|
||||
* Created by minjaesong on 2016-09-17.
|
||||
*
|
||||
*
|
||||
* NOTES:
|
||||
* Don't convert '\' to '/'! Rev-slash is used for escape character in sh, and we're sh-compatible!
|
||||
* Use .absoluteFile whenever possible; there's fuckin oddity! (http://bugs.java.com/bugdatabase/view_bug.do;:YfiG?bug_id=4483097)
|
||||
*/
|
||||
@Deprecated("Fuck permission and shit, we go virtual. Use FilesystemTar")
|
||||
internal class FilesystemDir(globals: Globals, computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) {
|
||||
|
||||
init {
|
||||
// load things. WARNING: THIS IS MANUAL!
|
||||
globals["fs"] = LuaValue.tableOf()
|
||||
globals["fs"]["list"] = ListFiles(computer) // CC compliant
|
||||
globals["fs"]["exists"] = FileExists(computer) // CC/OC compliant
|
||||
globals["fs"]["isDir"] = IsDirectory(computer) // CC compliant
|
||||
globals["fs"]["isFile"] = IsFile(computer)
|
||||
globals["fs"]["isReadOnly"] = IsReadOnly(computer) // CC compliant
|
||||
globals["fs"]["getSize"] = GetSize(computer) // CC compliant
|
||||
globals["fs"]["mkdir"] = Mkdir(computer)
|
||||
globals["fs"]["mv"] = Mv(computer)
|
||||
globals["fs"]["cp"] = Cp(computer)
|
||||
globals["fs"]["rm"] = Rm(computer)
|
||||
globals["fs"]["concat"] = ConcatPath(computer) // OC compliant
|
||||
globals["fs"]["open"] = OpenFile(computer) //CC compliant
|
||||
globals["fs"]["parent"] = GetParentDir(computer)
|
||||
// fs.dofile defined in BOOT
|
||||
// fs.fetchText defined in ROMLIB
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun ensurePathSanity(path: LuaValue) {
|
||||
if (path.checkIBM437().contains(Regex("""\.\.""")))
|
||||
throw LuaError("'..' on path is not supported.")
|
||||
if (!isValidFilename(path.checkIBM437()))
|
||||
throw IOException("path contains invalid characters")
|
||||
}
|
||||
|
||||
var isCaseInsensitive: Boolean
|
||||
|
||||
init {
|
||||
try {
|
||||
val uuid = UUID.randomUUID().toString()
|
||||
val lowerCase = File(Terrarum.currentSaveDir, uuid + "oc_rox")
|
||||
val upperCase = File(Terrarum.currentSaveDir, uuid + "OC_ROX")
|
||||
// This should NEVER happen but could also lead to VERY weird bugs, so we
|
||||
// make sure the files don't exist.
|
||||
if (lowerCase.exists()) lowerCase.delete()
|
||||
if (upperCase.exists()) upperCase.delete()
|
||||
|
||||
lowerCase.createNewFile()
|
||||
|
||||
val insensitive = upperCase.exists()
|
||||
lowerCase.delete()
|
||||
|
||||
isCaseInsensitive = insensitive
|
||||
|
||||
println("[Filesystem] Case insensitivity: $isCaseInsensitive")
|
||||
}
|
||||
catch (e: IOException) {
|
||||
System.err.println("[Filesystem] Couldn't determine if the file system is case sensitive, falling back to insensitive.")
|
||||
e.printStackTrace(System.out)
|
||||
isCaseInsensitive = true
|
||||
}
|
||||
}
|
||||
|
||||
// Worst-case: we're on Windows or using a FAT32 partition mounted in *nix.
|
||||
// Note: we allow / as the path separator and expect all \s to be converted
|
||||
// accordingly before the path is passed to the file system.
|
||||
private val invalidChars = Regex("""[<>:"|?*\u0000-\u001F]""") // original OC uses Set(); we use regex
|
||||
|
||||
fun isValidFilename(name: String) = !name.contains(invalidChars)
|
||||
|
||||
fun String.validatePath() : String {
|
||||
if (!isValidFilename(this)) {
|
||||
throw IOException("path contains invalid characters")
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/** actual directory: <appdata>/Saves/<savename>/computers/<drivename>/
|
||||
* directs media/ directory to /<uuid> directory
|
||||
*/
|
||||
fun net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer.getRealPath(luapath: LuaValue) : String {
|
||||
// direct mounted paths to real path
|
||||
val computerDir = Terrarum.currentSaveDir.absolutePath + "/computers/"
|
||||
/* if not begins with "(/?)media/", direct to boot
|
||||
* else, to corresponding drives
|
||||
*
|
||||
* List of device names (these are auto-mounted. why? primitivism :p):
|
||||
* = hda - hdd: hard disks
|
||||
* = fd1 - fd4: floppy drives
|
||||
* = sda: whatever external drives, usually a CD
|
||||
* = boot: current boot device
|
||||
*/
|
||||
|
||||
// remove first '/' in path
|
||||
var path = luapath.checkIBM437().validatePath()
|
||||
if (path.startsWith('/')) path = path.substring(1)
|
||||
|
||||
val finalPath: String
|
||||
|
||||
if (path.startsWith("media/")) {
|
||||
val device = path.substring(6, 9)
|
||||
val subPath = path.substring(9)
|
||||
finalPath = computerDir + this.computerValue.getAsString("device") + subPath
|
||||
}
|
||||
else {
|
||||
finalPath = computerDir + this.computerValue.getAsString("boot") + "/" + path
|
||||
}
|
||||
|
||||
// remove trailing slash
|
||||
return if (finalPath.endsWith("\\") || finalPath.endsWith("/"))
|
||||
finalPath.substring(0, finalPath.length - 1)
|
||||
else
|
||||
finalPath
|
||||
}
|
||||
|
||||
fun combinePath(base: String, local: String) : String {
|
||||
return "$base$local".replace("//", "/")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cname == UUID of the drive
|
||||
*
|
||||
* actual directory: <appdata>/Saves/<savename>/computers/<drivename>/
|
||||
*/
|
||||
class ListFiles(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
FilesystemDir.ensurePathSanity(path)
|
||||
|
||||
println("ListFiles: got path ${path.checkIBM437()}")
|
||||
|
||||
val table = LuaTable()
|
||||
val file = File(computer.getRealPath(path)).absoluteFile
|
||||
try {
|
||||
file.list().forEachIndexed { i, s -> table.insert(i, LuaValue.valueOf(s)) }
|
||||
}
|
||||
catch (e: NullPointerException) {}
|
||||
return table
|
||||
}
|
||||
}
|
||||
|
||||
/** Don't use this. Use isFile */
|
||||
class FileExists(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
FilesystemDir.ensurePathSanity(path)
|
||||
|
||||
return LuaValue.valueOf(Files.exists(Paths.get(computer.getRealPath(path)).toAbsolutePath()))
|
||||
}
|
||||
}
|
||||
|
||||
class IsDirectory(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
FilesystemDir.ensurePathSanity(path)
|
||||
|
||||
val isDir = Files.isDirectory(Paths.get(computer.getRealPath(path)).toAbsolutePath())
|
||||
val exists = Files.exists(Paths.get(computer.getRealPath(path)).toAbsolutePath())
|
||||
|
||||
return LuaValue.valueOf(isDir || exists)
|
||||
}
|
||||
}
|
||||
|
||||
class IsFile(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
FilesystemDir.ensurePathSanity(path)
|
||||
|
||||
// check if the path is file by checking:
|
||||
// 1. isfile
|
||||
// 2. canwrite
|
||||
// 3. length
|
||||
// Why? Our Java simply wants to fuck you.
|
||||
|
||||
val path = Paths.get(computer.getRealPath(path)).toAbsolutePath()
|
||||
var result = false
|
||||
result = Files.isRegularFile(path)
|
||||
|
||||
if (!result) result = Files.isWritable(path)
|
||||
|
||||
if (!result)
|
||||
try { result = Files.size(path) > 0 }
|
||||
catch (e: NoSuchFileException) { result = false }
|
||||
|
||||
return LuaValue.valueOf(result)
|
||||
}
|
||||
}
|
||||
|
||||
class IsReadOnly(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
FilesystemDir.ensurePathSanity(path)
|
||||
|
||||
return LuaValue.valueOf(!Files.isWritable(Paths.get(computer.getRealPath(path)).toAbsolutePath()))
|
||||
}
|
||||
}
|
||||
|
||||
/** we have 4GB file size limit */
|
||||
class GetSize(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
FilesystemDir.ensurePathSanity(path)
|
||||
|
||||
return LuaValue.valueOf(Files.size(Paths.get(computer.getRealPath(path)).toAbsolutePath()).toInt())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO class GetFreeSpace
|
||||
|
||||
/**
|
||||
* difference with ComputerCraft: it returns boolean, true on successful.
|
||||
*/
|
||||
class Mkdir(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
FilesystemDir.ensurePathSanity(path)
|
||||
|
||||
return LuaValue.valueOf(File(computer.getRealPath(path)).absoluteFile.mkdir())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* moves a directory, overwrites the target
|
||||
*/
|
||||
class Mv(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
|
||||
override fun call(from: LuaValue, to: LuaValue) : LuaValue {
|
||||
FilesystemDir.ensurePathSanity(from)
|
||||
FilesystemDir.ensurePathSanity(to)
|
||||
|
||||
val fromFile = File(computer.getRealPath(from)).absoluteFile
|
||||
var success = fromFile.copyRecursively(
|
||||
File(computer.getRealPath(to)).absoluteFile, overwrite = true
|
||||
)
|
||||
if (success) success = fromFile.deleteRecursively()
|
||||
else return LuaValue.valueOf(false)
|
||||
return LuaValue.valueOf(success)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* copies a directory, overwrites the target
|
||||
* difference with ComputerCraft: it returns boolean, true on successful.
|
||||
*/
|
||||
class Cp(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
|
||||
override fun call(from: LuaValue, to: LuaValue) : LuaValue {
|
||||
FilesystemDir.ensurePathSanity(from)
|
||||
FilesystemDir.ensurePathSanity(to)
|
||||
|
||||
return LuaValue.valueOf(
|
||||
File(computer.getRealPath(from)).absoluteFile.copyRecursively(
|
||||
File(computer.getRealPath(to)).absoluteFile, overwrite = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* difference with ComputerCraft: it returns boolean, true on successful.
|
||||
*/
|
||||
class Rm(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
FilesystemDir.ensurePathSanity(path)
|
||||
|
||||
return LuaValue.valueOf(
|
||||
File(computer.getRealPath(path)).absoluteFile.deleteRecursively()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class ConcatPath(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
|
||||
override fun call(base: LuaValue, local: LuaValue) : LuaValue {
|
||||
FilesystemDir.ensurePathSanity(base)
|
||||
FilesystemDir.ensurePathSanity(local)
|
||||
|
||||
val combinedPath = combinePath(base.checkIBM437().validatePath(), local.checkIBM437().validatePath())
|
||||
return LuaValue.valueOf(combinedPath)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mode: r, rb, w, wb, a, ab
|
||||
*
|
||||
* Difference: TEXT MODE assumes CP437 instead of UTF-8!
|
||||
*
|
||||
* When you have opened a file you must always close the file handle, or else data may not be saved.
|
||||
*
|
||||
* FILE class in CC:
|
||||
* (when you look thru them using file = fs.open("./test", "w")
|
||||
*
|
||||
* file = {
|
||||
* close = function()
|
||||
* -- write mode
|
||||
* write = function(string)
|
||||
* flush = function() -- write, keep the handle
|
||||
* writeLine = function(string) -- text mode
|
||||
* -- read mode
|
||||
* readLine = function() -- text mode
|
||||
* readAll = function()
|
||||
* -- binary read mode
|
||||
* read = function() -- read single byte. return: number or nil
|
||||
* -- binary write mode
|
||||
* write = function(byte)
|
||||
* writeBytes = function(string as bytearray)
|
||||
* }
|
||||
*/
|
||||
class OpenFile(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
|
||||
override fun call(path: LuaValue, mode: LuaValue) : LuaValue {
|
||||
FilesystemDir.ensurePathSanity(path)
|
||||
|
||||
|
||||
|
||||
val mode = mode.checkIBM437().toLowerCase()
|
||||
val luaClass = LuaTable()
|
||||
val file = File(computer.getRealPath(path)).absoluteFile
|
||||
|
||||
if (mode.contains(Regex("""[aw]""")) && !file.canWrite())
|
||||
throw LuaError("Cannot open file for " +
|
||||
"${if (mode.startsWith('w')) "read" else "append"} mode" +
|
||||
": is readonly.")
|
||||
|
||||
|
||||
|
||||
|
||||
when (mode) {
|
||||
"r" -> {
|
||||
try {
|
||||
val fr = FileReader(file)
|
||||
luaClass["close"] = FileClassClose(fr)
|
||||
luaClass["readLine"] = FileClassReadLine(fr)
|
||||
luaClass["readAll"] = FileClassReadAll(file.toPath())
|
||||
}
|
||||
catch (e: FileNotFoundException) {
|
||||
e.printStackTrace()
|
||||
throw LuaError(
|
||||
if (e.message != null && e.message!!.contains(Regex("""[Aa]ccess (is )?denied""")))
|
||||
"$path: access denied."
|
||||
else
|
||||
"$path: no such file."
|
||||
)
|
||||
}
|
||||
}
|
||||
"rb" -> {
|
||||
try {
|
||||
val fis = FileInputStream(file)
|
||||
luaClass["close"] = FileClassClose(fis)
|
||||
luaClass["read"] = FileClassReadByte(fis)
|
||||
luaClass["readAll"] = FileClassReadAll(file.toPath())
|
||||
}
|
||||
catch (e: FileNotFoundException) {
|
||||
e.printStackTrace()
|
||||
throw LuaError("$path: no such file.")
|
||||
}
|
||||
}
|
||||
"w", "a" -> {
|
||||
try {
|
||||
val fw = FileWriter(file, (mode.startsWith('a')))
|
||||
luaClass["close"] = FileClassClose(fw)
|
||||
luaClass["write"] = FileClassPrintText(fw)
|
||||
luaClass["writeLine"] = FileClassPrintlnText(fw)
|
||||
luaClass["flush"] = FileClassFlush(fw)
|
||||
}
|
||||
catch (e: FileNotFoundException) {
|
||||
e.printStackTrace()
|
||||
throw LuaError("$path: is a directory.")
|
||||
}
|
||||
}
|
||||
"wb", "ab" -> {
|
||||
try {
|
||||
val fos = FileOutputStream(file, (mode.startsWith('a')))
|
||||
luaClass["close"] = FileClassClose(fos)
|
||||
luaClass["write"] = FileClassWriteByte(fos)
|
||||
luaClass["writeBytes"] = FileClassWriteBytes(fos)
|
||||
luaClass["flush"] = FileClassFlush(fos)
|
||||
}
|
||||
catch (e: FileNotFoundException) {
|
||||
e.printStackTrace()
|
||||
throw LuaError("$path: is a directory.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return luaClass
|
||||
}
|
||||
}
|
||||
|
||||
class GetParentDir(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
FilesystemDir.ensurePathSanity(path)
|
||||
|
||||
var pathSB = StringBuilder(path.checkIBM437())
|
||||
|
||||
// backward travel, drop chars until '/' has encountered
|
||||
while (!pathSB.endsWith('/'))
|
||||
pathSB.deleteCharAt(pathSB.lastIndex - 1)
|
||||
|
||||
// drop trailing '/'
|
||||
if (pathSB.endsWith('/'))
|
||||
pathSB.deleteCharAt(pathSB.lastIndex - 1)
|
||||
|
||||
return LuaValue.valueOf(pathSB.toString())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
// OpenFile implementations //
|
||||
//////////////////////////////
|
||||
|
||||
private class FileClassClose(val fo: Any) : ZeroArgFunction() {
|
||||
override fun call() : LuaValue {
|
||||
if (fo is FileOutputStream)
|
||||
fo.close()
|
||||
else if (fo is FileWriter)
|
||||
fo.close()
|
||||
else if (fo is FileReader)
|
||||
fo.close()
|
||||
else if (fo is FileInputStream)
|
||||
fo.close()
|
||||
else
|
||||
throw IllegalArgumentException("Unacceptable file output: must be either Input/OutputStream or Reader/Writer.")
|
||||
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
private class FileClassWriteByte(val fos: FileOutputStream) : OneArgFunction() {
|
||||
override fun call(byte: LuaValue) : LuaValue {
|
||||
fos.write(byte.checkint())
|
||||
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
private class FileClassWriteBytes(val fos: FileOutputStream) : OneArgFunction() {
|
||||
override fun call(byteString: LuaValue) : LuaValue {
|
||||
val byteString = byteString.checkIBM437()
|
||||
val bytearr = ByteArray(byteString.length, { byteString[it].toByte() })
|
||||
fos.write(bytearr)
|
||||
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
private class FileClassPrintText(val fw: FileWriter) : OneArgFunction() {
|
||||
override fun call(string: LuaValue) : LuaValue {
|
||||
val text = string.checkIBM437()
|
||||
fw.write(text)
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
private class FileClassPrintlnText(val fw: FileWriter) : OneArgFunction() {
|
||||
override fun call(string: LuaValue) : LuaValue {
|
||||
val text = string.checkIBM437() + "\n"
|
||||
fw.write(text)
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
private class FileClassFlush(val fo: Any) : ZeroArgFunction() {
|
||||
override fun call() : LuaValue {
|
||||
if (fo is FileOutputStream)
|
||||
fo.flush()
|
||||
else if (fo is FileWriter)
|
||||
fo.flush()
|
||||
else
|
||||
throw IllegalArgumentException("Unacceptable file output: must be either OutputStream or Writer.")
|
||||
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
private class FileClassReadByte(val fis: FileInputStream) : ZeroArgFunction() {
|
||||
override fun call() : LuaValue {
|
||||
val readByte = fis.read()
|
||||
return if (readByte == -1) LuaValue.NIL else LuaValue.valueOf(readByte)
|
||||
}
|
||||
}
|
||||
|
||||
private class FileClassReadAllBytes(val path: Path) : ZeroArgFunction() {
|
||||
override fun call() : LuaValue {
|
||||
val byteArr = Files.readAllBytes(path)
|
||||
val s: String = java.lang.String(byteArr, "IBM437").toString()
|
||||
return LuaValue.valueOf(s)
|
||||
}
|
||||
}
|
||||
|
||||
private class FileClassReadAll(val path: Path) : ZeroArgFunction() {
|
||||
override fun call() : LuaValue {
|
||||
return FileClassReadAllBytes(path).call()
|
||||
}
|
||||
}
|
||||
|
||||
/** returns NO line separator! */
|
||||
private class FileClassReadLine(val fr: FileReader) : ZeroArgFunction() {
|
||||
val scanner = Scanner(fr.readText()) // no closing; keep the scanner status persistent
|
||||
|
||||
override fun call() : LuaValue {
|
||||
return if (scanner.hasNextLine()) LuaValue.valueOf(scanner.nextLine())
|
||||
else LuaValue.NIL
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-09-17.
|
||||
*/
|
||||
class FilesystemFactory {
|
||||
}
|
||||
@@ -0,0 +1,513 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
|
||||
|
||||
import org.luaj.vm2.*
|
||||
import org.luaj.vm2.lib.OneArgFunction
|
||||
import org.luaj.vm2.lib.TwoArgFunction
|
||||
import org.luaj.vm2.lib.ZeroArgFunction
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Term.Companion.checkIBM437
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.*
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VDUtil
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VDUtil.VDPath
|
||||
|
||||
import java.io.*
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* computer directory:
|
||||
* .../computers/
|
||||
* media/hda/ -> .../computers/<uuid for the hda>/
|
||||
*
|
||||
* Created by minjaesong on 2016-09-17.
|
||||
*
|
||||
*
|
||||
* NOTES:
|
||||
* Don't convert '\' to '/'! Rev-slash is used for escape character in sh, and we're sh-compatible!
|
||||
* Use .absoluteFile whenever possible; there's fuckin oddity! (http://bugs.java.com/bugdatabase/view_bug.do;:YfiG?bug_id=4483097)
|
||||
*/
|
||||
internal class Filesystem(globals: Globals, computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) {
|
||||
|
||||
init {
|
||||
// load things. WARNING: THIS IS MANUAL!
|
||||
globals["fs"] = LuaValue.tableOf()
|
||||
globals["fs"]["list"] = ListFiles(computer) // CC compliant
|
||||
globals["fs"]["exists"] = FileExists(computer) // CC/OC compliant
|
||||
globals["fs"]["isDir"] = IsDirectory(computer) // CC compliant
|
||||
globals["fs"]["isFile"] = IsFile(computer)
|
||||
globals["fs"]["isReadOnly"] = IsReadOnly(computer) // CC compliant
|
||||
globals["fs"]["getSize"] = GetSize(computer) // CC compliant
|
||||
globals["fs"]["mkdir"] = Mkdir(computer)
|
||||
globals["fs"]["mv"] = Mv(computer)
|
||||
globals["fs"]["cp"] = Cp(computer)
|
||||
globals["fs"]["rm"] = Rm(computer)
|
||||
globals["fs"]["concat"] = ConcatPath(computer) // OC compliant
|
||||
globals["fs"]["open"] = OpenFile(computer) //CC compliant
|
||||
globals["fs"]["parent"] = GetParentDir(computer)
|
||||
// fs.dofile defined in BOOT
|
||||
// fs.fetchText defined in ROMLIB
|
||||
}
|
||||
|
||||
companion object {
|
||||
val sysCharset = Charset.forName("CP437")
|
||||
|
||||
fun LuaValue.checkPath(): String {
|
||||
if (this.checkIBM437().contains(Regex("""\.\.""")))
|
||||
throw LuaError("'..' on path is not supported.")
|
||||
return this.checkIBM437().validatePath()
|
||||
}
|
||||
|
||||
// Worst-case: we're on Windows or using a FAT32 partition mounted in *nix.
|
||||
// Note: we allow / as the path separator and expect all \s to be converted
|
||||
// accordingly before the path is passed to the file system.
|
||||
private val invalidChars = Regex("""[<>:"|?*\u0000-\u001F]""") // original OC uses Set(); we use regex
|
||||
|
||||
fun isValidFilename(name: String) = !name.contains(invalidChars)
|
||||
|
||||
fun String.validatePath() : String {
|
||||
if (!isValidFilename(this)) {
|
||||
throw IOException("path contains invalid characters")
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* return value is there for chaining only.
|
||||
*/
|
||||
fun VDPath.dropMount(): VDPath {
|
||||
if (this.hierarchy.size >= 2 && this[0].toCanonicalString(sysCharset) == "media") {
|
||||
this.hierarchy.removeAt(0) // drop "media"
|
||||
this.hierarchy.removeAt(0) // drop whatever mount symbol
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* if path is {media, someUUID, subpath}, redirects to
|
||||
* computer.diskRack[SOMEUUID]->subpath
|
||||
* else, computer.diskRack["hda"]->subpath
|
||||
*/
|
||||
fun net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer.getFile(path: VDPath) : DiskEntry? {
|
||||
val disk = this.getTargetDisk(path)
|
||||
|
||||
if (disk == null) return null
|
||||
|
||||
path.dropMount()
|
||||
|
||||
return VDUtil.getFile(disk, path)
|
||||
}
|
||||
|
||||
/**
|
||||
* if path is like {media, fd1, subpath}, return
|
||||
* computer.diskRack["fd1"]
|
||||
* else, computer.diskRack[<boot device>]
|
||||
*/
|
||||
fun net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer.getTargetDisk(path: VDPath) : VirtualDisk? {
|
||||
if (path.hierarchy.size >= 2 &&
|
||||
Arrays.equals(path[0], "media".toEntryName(DiskEntry.NAME_LENGTH, sysCharset))) {
|
||||
val diskName = path[1].toCanonicalString(sysCharset)
|
||||
val disk = this.diskRack[diskName]
|
||||
|
||||
return disk
|
||||
}
|
||||
else {
|
||||
return this.diskRack[this.computerValue.getAsString("boot")]
|
||||
}
|
||||
}
|
||||
|
||||
fun net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer.getDirectoryEntries(path: VDPath) : Array<DiskEntry>? {
|
||||
val directory = this.getFile(path)
|
||||
|
||||
if (directory == null) return null
|
||||
return VDUtil.getDirectoryEntries(this.getTargetDisk(path)!!, directory)
|
||||
}
|
||||
|
||||
fun combinePath(base: String, local: String) : String {
|
||||
return "$base$local".replace("//", "/")
|
||||
}
|
||||
|
||||
private fun tryBool(action: () -> Unit): LuaValue {
|
||||
try {
|
||||
action()
|
||||
return LuaValue.valueOf(true)
|
||||
}
|
||||
catch (gottaCatchemAll: Exception) {
|
||||
return LuaValue.valueOf(false)
|
||||
}
|
||||
}
|
||||
} // end of Companion Object
|
||||
|
||||
/**
|
||||
* @param cname == UUID of the drive
|
||||
*
|
||||
* actual directory: <appdata>/Saves/<savename>/computers/<drivename>/
|
||||
*/
|
||||
class ListFiles(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
val path = VDPath(path.checkPath(), sysCharset)
|
||||
|
||||
val table = LuaTable()
|
||||
try {
|
||||
val directoryContents = computer.getDirectoryEntries(path)!!
|
||||
println("[Filesystem] directoryContents size: ${directoryContents.size}")
|
||||
directoryContents.forEachIndexed { index, diskEntry ->
|
||||
table.insert(index + 1, LuaValue.valueOf(diskEntry.filename.toCanonicalString(sysCharset)))
|
||||
}
|
||||
}
|
||||
catch (e: KotlinNullPointerException) {}
|
||||
return table
|
||||
}
|
||||
}
|
||||
|
||||
class FileExists(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
val path = VDPath(path.checkPath(), sysCharset)
|
||||
val disk = computer.getTargetDisk(path)
|
||||
|
||||
if (disk == null) return LuaValue.valueOf(false)
|
||||
|
||||
return LuaValue.valueOf(
|
||||
VDUtil.getFile(disk, path.dropMount()) != null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class IsDirectory(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
val path = VDPath(path.checkPath(), sysCharset)
|
||||
return LuaValue.valueOf(computer.getFile(path)?.contents is EntryDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
class IsFile(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
val path = VDPath(path.checkPath(), sysCharset)
|
||||
return LuaValue.valueOf(computer.getFile(path)?.contents is EntryFile)
|
||||
}
|
||||
}
|
||||
|
||||
class IsReadOnly(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
return LuaValue.valueOf(false)
|
||||
}
|
||||
}
|
||||
|
||||
/** we have 2 GB file size limit */
|
||||
class GetSize(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
val path = VDUtil.VDPath(path.checkPath(), sysCharset)
|
||||
val file = computer.getFile(path)
|
||||
try {
|
||||
if (file!!.contents is EntryFile)
|
||||
return LuaValue.valueOf(file.contents.getSizePure().toInt())
|
||||
else if (file.contents is EntryDirectory)
|
||||
return LuaValue.valueOf(file.contents.entryCount)
|
||||
}
|
||||
catch (e: KotlinNullPointerException) {
|
||||
}
|
||||
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
// TODO class GetFreeSpace
|
||||
|
||||
/**
|
||||
* returns true on success
|
||||
*/
|
||||
class Mkdir(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
return tryBool {
|
||||
val path = VDPath(path.checkPath(), sysCharset)
|
||||
val disk = computer.getTargetDisk(path)!!
|
||||
val dirList = computer.getDirectoryEntries(path.getParent())
|
||||
var makeNew = true
|
||||
|
||||
// check dupes
|
||||
if (dirList != null) {
|
||||
for (entry in dirList) {
|
||||
if (Arrays.equals(path.last(), entry.filename)) {
|
||||
makeNew = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (makeNew) {
|
||||
VDUtil.addDir(disk, path.getParent(), path.last())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* moves a directory, overwrites the target
|
||||
*/
|
||||
class Mv(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
|
||||
override fun call(from: LuaValue, to: LuaValue) : LuaValue {
|
||||
return tryBool {
|
||||
val pathFrom = VDPath(from.checkPath(), sysCharset)
|
||||
val disk1 = computer.getTargetDisk(pathFrom)
|
||||
val pathTo = VDPath(to.checkPath(), sysCharset)
|
||||
val disk2 = computer.getTargetDisk(pathTo)
|
||||
|
||||
VDUtil.moveFile(disk1!!, pathFrom, disk2!!, pathTo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* copies a directory, overwrites the target
|
||||
* difference with ComputerCraft: it returns boolean, true on successful.
|
||||
*/
|
||||
class Cp(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
|
||||
override fun call(from: LuaValue, to: LuaValue) : LuaValue {
|
||||
return tryBool {
|
||||
val pathFrom = VDPath(from.checkPath(), sysCharset)
|
||||
val disk1 = computer.getTargetDisk(pathFrom)!!
|
||||
val pathTo = VDPath(to.checkPath(), sysCharset)
|
||||
val disk2 = computer.getTargetDisk(pathTo)!!
|
||||
|
||||
val oldFile = VDUtil.getFile(disk2, pathTo)
|
||||
|
||||
try {
|
||||
VDUtil.deleteFile(disk2, pathTo)
|
||||
}
|
||||
catch (e: FileNotFoundException) {
|
||||
"Nothing to delete beforehand"
|
||||
}
|
||||
|
||||
val file = VDUtil.getFile(disk1, pathFrom)!!
|
||||
try {
|
||||
VDUtil.addFile(disk2, pathTo.getParent(), file)
|
||||
}
|
||||
catch (e: FileNotFoundException) {
|
||||
// roll back delete on disk2
|
||||
if (oldFile != null) {
|
||||
VDUtil.addFile(disk2, oldFile.parentEntryID, oldFile)
|
||||
throw FileNotFoundException("No such destination")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* difference with ComputerCraft: it returns boolean, true on successful.
|
||||
*/
|
||||
class Rm(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
return tryBool {
|
||||
val path = VDPath(path.checkPath(), sysCharset)
|
||||
val disk = computer.getTargetDisk(path)!!
|
||||
|
||||
VDUtil.deleteFile(disk, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ConcatPath(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
|
||||
override fun call(base: LuaValue, local: LuaValue) : LuaValue {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mode: r, rb, w, wb, a, ab
|
||||
*
|
||||
* Difference: TEXT MODE assumes CP437 instead of UTF-8!
|
||||
*
|
||||
* When you have opened a file you must always close the file handle, or else data may not be saved.
|
||||
*
|
||||
* FILE class in CC:
|
||||
* (when you look thru them using file = fs.open("./test", "w")
|
||||
*
|
||||
* file = {
|
||||
* close = function()
|
||||
* -- write mode
|
||||
* write = function(string)
|
||||
* flush = function() -- write, keep the handle
|
||||
* writeLine = function(string) -- text mode
|
||||
* -- read mode
|
||||
* readLine = function() -- text mode
|
||||
* readAll = function()
|
||||
* -- binary read mode
|
||||
* read = function() -- read single byte. return: number or nil
|
||||
* -- binary write mode
|
||||
* write = function(byte)
|
||||
* writeBytes = function(string as bytearray)
|
||||
* }
|
||||
*/
|
||||
class OpenFile(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
|
||||
override fun call(path: LuaValue, mode: LuaValue) : LuaValue {
|
||||
val path = VDPath(path.checkPath(), sysCharset)
|
||||
val disk = computer.getTargetDisk(path)!!
|
||||
|
||||
path.dropMount()
|
||||
|
||||
val mode = mode.checkIBM437().toLowerCase()
|
||||
val luaClass = LuaTable()
|
||||
val fileEntry = computer.getFile(path)!!
|
||||
|
||||
if (fileEntry.contents is EntryDirectory) {
|
||||
throw LuaError("File '${fileEntry.getFilenameString(sysCharset)}' is directory.")
|
||||
}
|
||||
|
||||
val file = fileEntry.contents as EntryFile
|
||||
|
||||
if (mode.contains(Regex("""[aw]""")))
|
||||
throw LuaError("Cannot open file for " +
|
||||
"${if (mode.startsWith('w')) "read" else "append"} mode" +
|
||||
": is readonly.")
|
||||
|
||||
|
||||
when (mode) {
|
||||
"r" -> {
|
||||
try {
|
||||
val fr = StringReader(String(file.bytes.toByteArray(), sysCharset))//FileReader(file)
|
||||
luaClass["close"] = FileClassClose(fr)
|
||||
luaClass["readLine"] = FileClassReadLine(fr)
|
||||
luaClass["readAll"] = FileClassReadAll(file)
|
||||
}
|
||||
catch (e: FileNotFoundException) {
|
||||
e.printStackTrace()
|
||||
throw LuaError(
|
||||
if (e.message != null && e.message!!.contains(Regex("""[Aa]ccess (is )?denied""")))
|
||||
"$path: access denied."
|
||||
else
|
||||
"$path: no such file."
|
||||
)
|
||||
}
|
||||
}
|
||||
"rb" -> {
|
||||
try {
|
||||
val fis = ByteArrayInputStream(file.bytes.toByteArray())
|
||||
luaClass["close"] = FileClassClose(fis)
|
||||
luaClass["read"] = FileClassReadByte(fis)
|
||||
luaClass["readAll"] = FileClassReadAll(file)
|
||||
}
|
||||
catch (e: FileNotFoundException) {
|
||||
e.printStackTrace()
|
||||
throw LuaError("$path: no such file.")
|
||||
}
|
||||
}
|
||||
"w", "a" -> {
|
||||
try {
|
||||
val fw = VDFileWriter(fileEntry, mode.startsWith('a'), sysCharset)
|
||||
luaClass["close"] = FileClassClose(fw)
|
||||
luaClass["write"] = FileClassPrintText(fw)
|
||||
luaClass["writeLine"] = FileClassPrintlnText(fw)
|
||||
luaClass["flush"] = FileClassFlush(fw)
|
||||
}
|
||||
catch (e: FileNotFoundException) {
|
||||
e.printStackTrace()
|
||||
throw LuaError("$path: is a directory.")
|
||||
}
|
||||
}
|
||||
"wb", "ab" -> {
|
||||
try {
|
||||
val fos = VDFileOutputStream(fileEntry, mode.startsWith('a'), sysCharset)
|
||||
luaClass["close"] = FileClassClose(fos)
|
||||
luaClass["write"] = FileClassWriteByte(fos)
|
||||
luaClass["writeBytes"] = FileClassWriteBytes(fos)
|
||||
luaClass["flush"] = FileClassFlush(fos)
|
||||
}
|
||||
catch (e: FileNotFoundException) {
|
||||
e.printStackTrace()
|
||||
throw LuaError("$path: is a directory.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return luaClass
|
||||
}
|
||||
}
|
||||
|
||||
class GetParentDir(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(path: LuaValue) : LuaValue {
|
||||
val path = VDPath(path.checkPath(), sysCharset).getParent()
|
||||
return LuaValue.valueOf(path.toString())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
// OpenFile implementations //
|
||||
//////////////////////////////
|
||||
|
||||
private class FileClassClose(val fo: Closeable) : ZeroArgFunction() {
|
||||
override fun call() : LuaValue {
|
||||
fo.close()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
private class FileClassWriteByte(val fos: VDFileOutputStream) : OneArgFunction() {
|
||||
override fun call(byte: LuaValue) : LuaValue {
|
||||
fos.write(byte.checkint())
|
||||
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
private class FileClassWriteBytes(val fos: VDFileOutputStream) : OneArgFunction() {
|
||||
override fun call(byteString: LuaValue) : LuaValue {
|
||||
val byteString = byteString.checkIBM437()
|
||||
val bytearr = ByteArray(byteString.length, { byteString[it].toByte() })
|
||||
fos.write(bytearr)
|
||||
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
private class FileClassPrintText(val fw: VDFileWriter) : OneArgFunction() {
|
||||
override fun call(string: LuaValue) : LuaValue {
|
||||
val text = string.checkIBM437()
|
||||
fw.write(text)
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
private class FileClassPrintlnText(val fw: VDFileWriter) : OneArgFunction() {
|
||||
override fun call(string: LuaValue) : LuaValue {
|
||||
val text = string.checkIBM437() + "\n"
|
||||
fw.write(text)
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
private class FileClassFlush(val fo: Flushable) : ZeroArgFunction() {
|
||||
override fun call() : LuaValue {
|
||||
fo.flush()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
private class FileClassReadByte(val fis: ByteArrayInputStream) : ZeroArgFunction() {
|
||||
override fun call() : LuaValue {
|
||||
val readByte = fis.read()
|
||||
return if (readByte == -1) LuaValue.NIL else LuaValue.valueOf(readByte)
|
||||
}
|
||||
}
|
||||
|
||||
private class FileClassReadAllBytes(val file: EntryFile) : ZeroArgFunction() {
|
||||
override fun call() : LuaValue {
|
||||
return LuaValue.valueOf(String(file.bytes.toByteArray(), sysCharset))
|
||||
}
|
||||
}
|
||||
|
||||
private class FileClassReadAll(val file: EntryFile) : ZeroArgFunction() {
|
||||
override fun call() : LuaValue {
|
||||
return LuaValue.valueOf(String(file.bytes.toByteArray(), sysCharset))
|
||||
}
|
||||
}
|
||||
|
||||
/** returns NO line separator! */
|
||||
private class FileClassReadLine(fr: Reader) : ZeroArgFunction() {
|
||||
val scanner = Scanner(fr.readText()) // no closing; keep the scanner status persistent
|
||||
|
||||
override fun call() : LuaValue {
|
||||
return if (scanner.hasNextLine()) LuaValue.valueOf(scanner.nextLine())
|
||||
else LuaValue.NIL
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
|
||||
|
||||
import net.torvald.terrarum.gameactors.ai.toLua
|
||||
import org.luaj.vm2.lib.OneArgFunction
|
||||
import org.luaj.vm2.lib.ZeroArgFunction
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Term.Companion.checkIBM437
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype
|
||||
import org.luaj.vm2.*
|
||||
|
||||
/**
|
||||
* Provide Lua an access to computer object that is in Java
|
||||
*
|
||||
* The "machine" refers to the computer fixture itself in the game world.
|
||||
*
|
||||
* Created by minjaesong on 2016-09-19.
|
||||
*/
|
||||
internal class HostAccessProvider(globals: Globals, computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) {
|
||||
|
||||
init {
|
||||
globals["machine"] = LuaTable()
|
||||
globals["machine"]["println"] = PrintLn()
|
||||
globals["machine"]["isHalted"] = IsHalted(computer)
|
||||
|
||||
|
||||
globals["machine"]["__readFromStdin"] = NativeReadStdin(computer)
|
||||
|
||||
globals["machine"]["milliTime"] = NativeGetMilliTime(computer)
|
||||
|
||||
globals["machine"]["sleep"] = NativeThreadSleep(computer)
|
||||
|
||||
globals["__haltsystemexplicit__"] = HaltComputer(computer)
|
||||
}
|
||||
|
||||
class PrintLn(): OneArgFunction() {
|
||||
override fun call(p0: LuaValue): LuaValue {
|
||||
if (p0.isnumber())
|
||||
println(p0.checkdouble())
|
||||
else
|
||||
println(p0.checkIBM437())
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
class IsHalted(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer): ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return LuaValue.valueOf(computer.isHalted)
|
||||
}
|
||||
}
|
||||
|
||||
class NativeReadStdin(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return computer.stdin!!.read().toLua()
|
||||
}
|
||||
}
|
||||
|
||||
class HaltComputer(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : ZeroArgFunction() {
|
||||
override fun call() : LuaValue {
|
||||
computer.isHalted = true
|
||||
computer.luaJ_globals.load("""print(DC4.."system halted")""").call()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
/** Time elapsed since the power is on. */
|
||||
class NativeGetMilliTime(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return LuaValue.valueOf(computer.milliTime)
|
||||
}
|
||||
}
|
||||
|
||||
class NativeThreadSleep(val computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OneArgFunction() {
|
||||
override fun call(mills: LuaValue): LuaValue {
|
||||
computer.currentExecutionThread.join(mills.checklong())
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import org.luaj.vm2.Globals
|
||||
import org.luaj.vm2.LuaTable
|
||||
import org.luaj.vm2.LuaValue
|
||||
import org.luaj.vm2.lib.OneArgFunction
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-09-25.
|
||||
*/
|
||||
class Input(globals: Globals, computer: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) {
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
|
||||
|
||||
import org.luaj.vm2.Globals
|
||||
import org.luaj.vm2.LuaTable
|
||||
import org.luaj.vm2.LuaValue
|
||||
import org.luaj.vm2.lib.TwoArgFunction
|
||||
import org.luaj.vm2.lib.ZeroArgFunction
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer
|
||||
import org.luaj.vm2.LuaFunction
|
||||
import org.luaj.vm2.lib.OneArgFunction
|
||||
|
||||
/**
|
||||
* PC Speaker driver and arpeggiator (MONOTONE-style 4 channels)
|
||||
*
|
||||
* Notes are tuned to A440, equal temperament. This is an ISO standard.
|
||||
*
|
||||
* Created by minjaesong on 2016-09-27.
|
||||
*/
|
||||
class PcSpeakerDriver(val globals: Globals, host: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) {
|
||||
|
||||
init {
|
||||
globals["speaker"] = LuaTable()
|
||||
globals["speaker"]["enqueue"] = EnqueueTone(host)
|
||||
globals["speaker"]["clear"] = ClearQueue(host)
|
||||
globals["speaker"]["retune"] = Retune(globals)
|
||||
globals["speaker"]["resetTune"] = ResetTune(globals)
|
||||
globals["speaker"]["toFreq"] = StringToFrequency(globals)
|
||||
globals["speaker"]["__basefreq__"] = LuaValue.valueOf(BASE_FREQ) // every other PSGs should use this very variable
|
||||
|
||||
// constants
|
||||
// e.g. speaker.A0 returns number 1
|
||||
fun Int.toNote(): String = NOTE_NAMES[this % 12] + this.plus(8).div(12).toString()
|
||||
fun Int.toNoteAlt(): String = NOTE_NAMES_ALT[this % 12] + this.plus(8).div(12).toString()
|
||||
|
||||
for (i in 1..126) {
|
||||
globals["speaker"][i.toNote()] = i // sharps
|
||||
globals["speaker"][i.toNoteAlt()] = i // flats
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val BASE_FREQ = 27.5 // frequency of A0
|
||||
val NOTE_NAMES = arrayOf("GS", "A", "AS", "B", "C", "CS",
|
||||
"D", "DS", "E", "F", "FS", "G")
|
||||
val NOTE_NAMES_ALT = arrayOf("Ab", "A", "Bb", "B", "C", "Db",
|
||||
"D", "Eb", "E", "F", "Gb", "G")
|
||||
|
||||
/** @param basefreq: Frequency of A-0 */
|
||||
fun Int.toFreq(basefreq: Double): Double = basefreq * Math.pow(2.0, (this - 1.0) / 12.0)
|
||||
|
||||
/** @param "A-5", "B4", "C#5", ... */
|
||||
fun String.toNoteIndex(): Int {
|
||||
var notestr = this.replace("-", "")
|
||||
notestr = notestr.replace("#", "S")
|
||||
|
||||
val baseNote = if (notestr.contains("S") || notestr.contains("b"))
|
||||
notestr.substring(0, 2)
|
||||
else
|
||||
notestr.substring(0, 1)
|
||||
|
||||
var note: Int = NOTE_NAMES.indexOf(baseNote) // [0-11]
|
||||
if (note < 0) note = NOTE_NAMES_ALT.indexOf(baseNote) // search again
|
||||
if (note < 0) throw IllegalArgumentException("Unknown note: $this") // failed to search
|
||||
|
||||
val octave: Int = notestr.replace(Regex("""[^0-9]"""), "").toInt()
|
||||
return octave.minus(if (note >= 4) 1 else 0) * 12 + note
|
||||
}
|
||||
}
|
||||
|
||||
class EnqueueTone(val host: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : TwoArgFunction() {
|
||||
/**
|
||||
* @param freq: number (hertz) or string (A-4, A4, B#2, ...)
|
||||
*/
|
||||
override fun call(second: LuaValue, freq: LuaValue): LuaValue {
|
||||
if (freq.isnumber())
|
||||
host.enqueueBeep(second.checkdouble(), freq.checkdouble())
|
||||
else {
|
||||
host.enqueueBeep(second.checkdouble(),
|
||||
freq.checkjstring().toNoteIndex()
|
||||
.toFreq(host.luaJ_globals["speaker"]["__basefreq__"].checkdouble())
|
||||
)
|
||||
}
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
class ClearQueue(val host: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
host.clearBeepQueue()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
class Retune(val globals: Globals) : LuaFunction() {
|
||||
/**
|
||||
* Examples: C256, A440, A#440, ...
|
||||
*/
|
||||
override fun call(arg: LuaValue): LuaValue {
|
||||
val tuneName = arg.checkjstring()
|
||||
|
||||
val baseNote = if (tuneName.contains("#") || tuneName.contains("b")) tuneName.substring(0, 2) else tuneName.substring(0, 1)
|
||||
val freq = tuneName.replace(Regex("""[^0-9]"""), "").toInt()
|
||||
|
||||
// we're assuming the input to be C4, C#4, ... A4, A#4, B4
|
||||
// diffPivot corsp. to G#4, A4, ...
|
||||
val diffPivot = arrayOf(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // 2^(12 / n)
|
||||
var diff = diffPivot[NOTE_NAMES.indexOf(baseNote)]
|
||||
if (diff < 0) diff = diffPivot[NOTE_NAMES_ALT.indexOf(baseNote)] // search again
|
||||
if (diff < 0) throw IllegalArgumentException("Unknown note: $baseNote") // failed to search
|
||||
|
||||
val exp = -diff / 12.0
|
||||
val basefreq = freq * Math.pow(2.0, exp) / if (diff >= 3) 8.0 else 16.0 // converts whatever baseNote to A0
|
||||
|
||||
globals["speaker"]["__basefreq__"] = basefreq
|
||||
return LuaValue.NONE
|
||||
}
|
||||
|
||||
override fun call(): LuaValue {
|
||||
globals["speaker"]["__basefreq__"] = LuaValue.valueOf(BASE_FREQ)
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
class ResetTune(val globals: Globals) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
globals["speaker"]["__basefreq__"] = LuaValue.valueOf(BASE_FREQ)
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* usage = speaker.toFreq(speaker.AS5) --'S' is a substitution for '#'
|
||||
*/
|
||||
class StringToFrequency(val globals: Globals) : OneArgFunction() {
|
||||
/**
|
||||
* @param arg: number (note index) or string (A-4, A4, B#2, ...)
|
||||
*/
|
||||
override fun call(arg: LuaValue): LuaValue {
|
||||
val note = if (arg.isint()) arg.checkint()
|
||||
else {
|
||||
arg.checkjstring().toNoteIndex()
|
||||
}
|
||||
val basefreq = globals["speaker"]["__basefreq__"].checkdouble()
|
||||
|
||||
return LuaValue.valueOf(note.toFreq(basefreq))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
|
||||
|
||||
import org.luaj.vm2.Globals
|
||||
import org.luaj.vm2.LuaValue
|
||||
import org.luaj.vm2.lib.OneArgFunction
|
||||
import net.torvald.terrarum.gameworld.toUint
|
||||
import org.apache.commons.codec.binary.Base64
|
||||
import org.apache.commons.codec.digest.DigestUtils
|
||||
import java.security.SecureRandom
|
||||
|
||||
/**
|
||||
* Hashes, CSPRNG, Base64
|
||||
*
|
||||
* Created by minjaesong on 2016-09-15.
|
||||
*/
|
||||
internal class Security(globals: Globals) {
|
||||
|
||||
init {
|
||||
// load things. WARNING: THIS IS MANUAL!
|
||||
globals["security"] = LuaValue.tableOf()
|
||||
globals["security"]["toSHA256"] = SHA256sum()
|
||||
globals["security"]["toSHA1"] = SHA1sum()
|
||||
globals["security"]["toMD5"] = MD5sum()
|
||||
globals["security"]["randomBytes"] = SecureRandomHex()
|
||||
globals["security"]["decodeBase64"] = DecodeBase64()
|
||||
globals["security"]["encodeBase64"] = EncodeBase64()
|
||||
}
|
||||
|
||||
/** @return byteArray as String */
|
||||
class SHA256sum : OneArgFunction() {
|
||||
override fun call(p0: LuaValue): LuaValue {
|
||||
val hashBytes = DigestUtils.sha256(p0.checkjstring())
|
||||
return LuaValue.valueOf(hashBytes.toStringRepresentation())
|
||||
}
|
||||
}
|
||||
|
||||
/** @return byteArray as String */
|
||||
class SHA1sum: OneArgFunction() {
|
||||
override fun call(p0: LuaValue): LuaValue {
|
||||
val hashBytes = DigestUtils.sha1(p0.checkjstring())
|
||||
return LuaValue.valueOf(hashBytes.toStringRepresentation())
|
||||
}
|
||||
}
|
||||
|
||||
/** @return byteArray as String */
|
||||
class MD5sum: OneArgFunction() {
|
||||
override fun call(p0: LuaValue): LuaValue {
|
||||
val hashBytes = DigestUtils.md5(p0.checkjstring())
|
||||
return LuaValue.valueOf(hashBytes.toStringRepresentation())
|
||||
}
|
||||
}
|
||||
|
||||
/** @return byteArray as String */
|
||||
class SecureRandomHex: OneArgFunction() {
|
||||
override fun call(byteSize: LuaValue): LuaValue {
|
||||
val bytes = ByteArray(byteSize.checkint())
|
||||
SecureRandom().nextBytes(bytes)
|
||||
|
||||
return LuaValue.valueOf(bytes.toStringRepresentation())
|
||||
}
|
||||
}
|
||||
|
||||
/** @return String */
|
||||
class DecodeBase64: OneArgFunction() {
|
||||
override fun call(base64: LuaValue): LuaValue {
|
||||
val decodedBytes = Base64.decodeBase64(base64.checkjstring())
|
||||
return LuaValue.valueOf(decodedBytes.toStringRepresentation())
|
||||
}
|
||||
}
|
||||
|
||||
/** @return byteArray as String */
|
||||
class EncodeBase64: OneArgFunction() {
|
||||
override fun call(inputString: LuaValue): LuaValue {
|
||||
val inputBytes = inputString.checkjstring().toByteArray(charset("UTF-8"))
|
||||
return LuaValue.valueOf(Base64.encodeBase64(inputBytes).toStringRepresentation())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val hexLookup = charArrayOf(
|
||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
|
||||
)
|
||||
|
||||
fun Byte.toHexString(): String {
|
||||
val bInt = this.toUint()
|
||||
return "${hexLookup[bInt.shr(8).and(0xf)]}${hexLookup[bInt.and(0xf)]}"
|
||||
}
|
||||
|
||||
/** essentially, 0xFC to 0xFC.toChar() */
|
||||
fun ByteArray.toStringRepresentation(): String {
|
||||
val sb = StringBuilder()
|
||||
for (b in this)
|
||||
sb.append(b.toChar())
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
|
||||
|
||||
import org.luaj.vm2.*
|
||||
import org.luaj.vm2.lib.*
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal
|
||||
import java.nio.charset.Charset
|
||||
|
||||
/**
|
||||
* Controls terminal as if it was a monitor
|
||||
* (not sending control sequences but just drives it directly)
|
||||
*
|
||||
* Created by minjaesong on 2016-09-12.
|
||||
*/
|
||||
internal class Term(globals: Globals, term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) {
|
||||
|
||||
init {
|
||||
// load things. WARNING: THIS IS MANUAL!
|
||||
globals["term"] = LuaValue.tableOf()
|
||||
globals["term"]["write"] = Term.WriteString(term)
|
||||
globals["term"]["print"] = Term.PrintString(term)
|
||||
globals["term"]["newLine"] = Term.NewLine(term)
|
||||
globals["term"]["moveCursor"] = Term.MoveCursor(term) // TTY function
|
||||
globals["term"]["width"] = Term.GetWidth(term)
|
||||
globals["term"]["scroll"] = Term.Scroll(term)
|
||||
globals["term"]["isTeletype"] = Term.IsTeletype(term)
|
||||
globals["term"]["bell"] = Term.Bell(term)
|
||||
|
||||
if (term is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) {
|
||||
globals["term"]["emitRaw"] = Term.EmitRaw(term)
|
||||
globals["term"]["emit"] = Term.Emit(term)
|
||||
globals["term"]["emitString"] = Term.EmitString(term)
|
||||
globals["term"]["resetColor"] = Term.ResetColour(term)
|
||||
globals["term"]["resetColour"] = Term.ResetColour(term)
|
||||
globals["term"]["clear"] = Term.Clear(term)
|
||||
globals["term"]["clearLine"] = Term.ClearLine(term)
|
||||
globals["term"]["setCursor"] = Term.SetCursor(term)
|
||||
globals["term"]["getCursor"] = Term.GetCursorPos(term)
|
||||
globals["term"]["getX"] = Term.GetCursorX(term)
|
||||
globals["term"]["getY"] = Term.GetCursorY(term)
|
||||
globals["term"]["setX"] = Term.SetCursorX(term)
|
||||
globals["term"]["setY"] = Term.SetCursorY(term)
|
||||
globals["term"]["setCursorBlink"] = Term.SetCursorBlink(term)
|
||||
globals["term"]["size"] = Term.GetSize(term)
|
||||
globals["term"]["height"] = Term.GetHeight(term)
|
||||
globals["term"]["isCol"] = Term.IsColour(term)
|
||||
globals["term"]["setForeCol"] = Term.SetForeColour(term)
|
||||
globals["term"]["setBackCol"] = Term.SetBackColour(term)
|
||||
globals["term"]["foreCol"] = Term.GetForeColour(term)
|
||||
globals["term"]["backCol"] = Term.GetBackColour(term)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun LuaValue.checkIBM437(): String {
|
||||
if (this is LuaString)
|
||||
return m_bytes.copyOfRange(m_offset, m_offset + m_length).toString(Charset.forName("CP437"))
|
||||
// it only works if Charset is ISO-8859, despite of the name "IBM437"
|
||||
// --> then would "CP437" work? -- Torvald at 2017-04-05
|
||||
else
|
||||
throw LuaError("bad argument (string expected, got ${this.typename()})")
|
||||
}
|
||||
}
|
||||
|
||||
class Bell(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : OneArgFunction() {
|
||||
override fun call(pattern: LuaValue): LuaValue {
|
||||
tty.bell(pattern.checkjstring())
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
class WriteString(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : LuaFunction() {
|
||||
override fun call(p0: LuaValue): LuaValue {
|
||||
if (tty is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal)
|
||||
tty.writeString(p0.checkIBM437(), tty.cursorX, tty.cursorY)
|
||||
else
|
||||
tty.writeChars(p0.checkIBM437())
|
||||
return LuaValue.NONE
|
||||
}
|
||||
|
||||
override fun call(s: LuaValue, x: LuaValue, y: LuaValue): LuaValue {
|
||||
if (tty is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal)
|
||||
tty.writeString(s.checkIBM437(), x.checkint(), y.checkint())
|
||||
else
|
||||
throw LuaError("couldn't move cursor; TTY is one-dimensional")
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
class PrintString(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : LuaFunction() {
|
||||
override fun call(p0: LuaValue): LuaValue {
|
||||
if (tty is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal)
|
||||
tty.printString(p0.checkIBM437(), tty.cursorX, tty.cursorY)
|
||||
else
|
||||
tty.printChars(p0.checkIBM437())
|
||||
return LuaValue.NONE
|
||||
}
|
||||
|
||||
override fun call(s: LuaValue, x: LuaValue, y: LuaValue): LuaValue {
|
||||
if (tty is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal)
|
||||
tty.printString(s.checkIBM437(), x.checkint(), y.checkint())
|
||||
else
|
||||
throw LuaError("couldn't move cursor; TTY is one-dimensional")
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
class NewLine(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
tty.newLine()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
class EmitRaw(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ThreeArgFunction() {
|
||||
override fun call(p0: LuaValue, x: LuaValue, y: LuaValue): LuaValue {
|
||||
term.emitChar(p0.checkint(), x.checkint() - 1, y.checkint() - 1)
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
// emitchar
|
||||
class Emit(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ThreeArgFunction() {
|
||||
override fun call(p0: LuaValue, x: LuaValue, y: LuaValue): LuaValue {
|
||||
term.emitChar(p0.checkint().toChar(), x.checkint() - 1, y.checkint() - 1)
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
class EmitString(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ThreeArgFunction() {
|
||||
override fun call(p0: LuaValue, x: LuaValue, y: LuaValue): LuaValue {
|
||||
term.emitString(p0.checkIBM437(), x.checkint() - 1, y.checkint() - 1)
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
class ResetColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
term.resetColour()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
class Clear(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
term.clear()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
class ClearLine(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
term.clearLine()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
/** term.setCursorPos(number x) */
|
||||
class MoveCursor(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : OneArgFunction() {
|
||||
override fun call(p0: LuaValue): LuaValue {
|
||||
for (i in 1..p0.checkint())
|
||||
tty.printChar(' ')
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
class SetCursor(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : TwoArgFunction() {
|
||||
override fun call(x: LuaValue, y: LuaValue): LuaValue {
|
||||
term.setCursor(x.checkint() - 1, y.checkint() - 1)
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
/** One-based */
|
||||
class GetCursorPos(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : VarArgFunction() {
|
||||
override fun invoke(args: Varargs?): Varargs {
|
||||
val ret = arrayOf(LuaValue.valueOf(term.cursorX + 1), LuaValue.valueOf(term.cursorY + 1))
|
||||
return LuaValue.varargsOf(ret)
|
||||
}
|
||||
}
|
||||
|
||||
class GetCursorX(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return LuaValue.valueOf(term.cursorX + 1)
|
||||
}
|
||||
}
|
||||
|
||||
class GetCursorY(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return LuaValue.valueOf(term.cursorY + 1)
|
||||
}
|
||||
}
|
||||
|
||||
class SetCursorX(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : OneArgFunction() {
|
||||
override fun call(p0: LuaValue): LuaValue {
|
||||
term.setCursor(p0.checkint() - 1, term.cursorY)
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
class SetCursorY(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : OneArgFunction() {
|
||||
override fun call(p0: LuaValue): LuaValue {
|
||||
term.setCursor(term.cursorX - 1, p0.checkint())
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
/** term.setCursorBlink(boolean bool) */
|
||||
class SetCursorBlink(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : OneArgFunction() {
|
||||
override fun call(p0: LuaValue): LuaValue {
|
||||
term.cursorBlink = p0.toboolean()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
class GetSize(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : VarArgFunction() {
|
||||
override fun invoke(args: Varargs?): Varargs {
|
||||
val ret = arrayOf(LuaValue.valueOf(term.width), LuaValue.valueOf(term.height))
|
||||
return LuaValue.varargsOf(ret)
|
||||
}
|
||||
}
|
||||
|
||||
class GetWidth(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return LuaValue.valueOf(tty.width)
|
||||
}
|
||||
}
|
||||
|
||||
class GetHeight(val terminal: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return LuaValue.valueOf(terminal.height)
|
||||
}
|
||||
}
|
||||
|
||||
class IsColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return LuaValue.valueOf(term.coloursCount > 4)
|
||||
}
|
||||
}
|
||||
|
||||
/** term.scroll(number n) */
|
||||
class Scroll(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : OneArgFunction() {
|
||||
override fun call(p0: LuaValue): LuaValue {
|
||||
if (tty is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) tty.scroll(p0.checkint())
|
||||
else for (i in 1..p0.checkint()) tty.newLine()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
/** term.setTextColor(number color) */
|
||||
class SetForeColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : OneArgFunction() {
|
||||
override fun call(p0: LuaValue): LuaValue {
|
||||
term.foreColour = p0.checkint()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
/** term.setBackgroundColor(number color) */
|
||||
class SetBackColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : OneArgFunction() {
|
||||
override fun call(p0: LuaValue): LuaValue {
|
||||
term.backColour = p0.checkint()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
class GetForeColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return LuaValue.valueOf(term.foreColour)
|
||||
}
|
||||
}
|
||||
|
||||
class GetBackColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return LuaValue.valueOf(term.backColour)
|
||||
}
|
||||
}
|
||||
|
||||
class IsTeletype(val termInQuestion: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return LuaValue.valueOf(termInQuestion.coloursCount == 0)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi
|
||||
|
||||
import org.luaj.vm2.Globals
|
||||
import org.luaj.vm2.LuaFunction
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.modulebasegame.Ingame
|
||||
import net.torvald.terrarum.modulebasegame.gameworld.WorldTime
|
||||
import org.luaj.vm2.LuaTable
|
||||
import org.luaj.vm2.LuaValue
|
||||
|
||||
/**
|
||||
* Implementation of lua's os.date, to return world info of the game world.
|
||||
*
|
||||
* Created by minjaesong on 2016-09-28.
|
||||
*/
|
||||
class WorldInformationProvider(globals: Globals) {
|
||||
|
||||
init {
|
||||
globals["os"]["time"] = LuaValue.NIL // history is LONG! Our 32-bit Lua's epoch is destined to break down...
|
||||
globals["os"]["date"] = OsDateImpl()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getWorldTimeInLuaFormat() : LuaTable {
|
||||
val t = LuaTable()
|
||||
val time = if (Terrarum.ingame != null) (Terrarum.ingame!! as Ingame).world.time else WorldTime()
|
||||
|
||||
// int Terrarum World Time format
|
||||
t["hour"] = time.hours
|
||||
t["min"] = time.minutes
|
||||
t["wday"] = time.dayOfWeek
|
||||
t["year"] = time.years
|
||||
t["yday"] = time.yearlyDays
|
||||
t["month"] = time.months
|
||||
t["sec"] = time.seconds
|
||||
t["day"] = time.days
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
val defaultDateFormat = "%a %d %B %Y %X"
|
||||
|
||||
/** evaluate single C date format */
|
||||
fun String.evalAsDate(): String {
|
||||
val time = if (Terrarum.ingame != null) (Terrarum.ingame!! as Ingame).world.time else WorldTime()
|
||||
return when (this) {
|
||||
"%a" -> time.getDayNameShort()
|
||||
"%A" -> time.getDayNameFull()
|
||||
"%b" -> time.getMonthNameShort()
|
||||
"%B" -> time.getMonthNameFull()
|
||||
"%c" -> "%x".evalAsDate() + " " + "%X".evalAsDate()
|
||||
"%d" -> time.days.toString()
|
||||
"%H" -> time.hours.toString()
|
||||
"%I" -> throw IllegalArgumentException("%I: AM/PM concept does not exists.")
|
||||
"%M" -> time.minutes.toString()
|
||||
"%m" -> time.months.toString()
|
||||
"%p" -> throw IllegalArgumentException("%p: AM/PM concept does not exists.")
|
||||
"%S" -> time.seconds.toString()
|
||||
"%w" -> time.dayOfWeek.toString()
|
||||
"%x" -> "${String.format("%02d", time.years)}-${String.format("%02d", time.months)}-${String.format("%02d", time.days)}"
|
||||
"%X" -> "${String.format("%02d", time.hours)}:${String.format("%02d", time.minutes)}:${String.format("%02d", time.seconds)}"
|
||||
"%Y" -> time.years.toString()
|
||||
"%y" -> time.years.mod(100).toString()
|
||||
"%%" -> "%"
|
||||
else -> throw IllegalArgumentException("Unknown format string: $this")
|
||||
}
|
||||
}
|
||||
|
||||
val acceptedDateFormats = arrayOf("%a", "%A", "%b", "%B", "%c", "%d", "%H", "%I", "%M", "%m", "%p", "%S", "%w", "%x", "%X", "%Y", "%y", "%%" )
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes: cannot get a representation of arbitrary time.
|
||||
*/
|
||||
class OsDateImpl() : LuaFunction() {
|
||||
// no args
|
||||
override fun call(): LuaValue {
|
||||
return call(defaultDateFormat)
|
||||
}
|
||||
|
||||
override fun call(format: LuaValue): LuaValue {
|
||||
var arg = format.checkjstring()
|
||||
acceptedDateFormats.forEach {
|
||||
if (arg.contains(it))
|
||||
arg = arg.replace(it, it.evalAsDate(), ignoreCase = false)
|
||||
}
|
||||
return LuaValue.valueOf(arg)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral
|
||||
|
||||
import org.luaj.vm2.Globals
|
||||
import org.luaj.vm2.LuaTable
|
||||
import org.luaj.vm2.LuaValue
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-09-29.
|
||||
*/
|
||||
abstract class Peripheral(val tableName: String) {
|
||||
|
||||
abstract fun loadLib(globals: Globals)
|
||||
|
||||
override fun toString(): String = "Peripheral:$tableName"
|
||||
|
||||
abstract val memSize: Int
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.BitmapFont
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import net.torvald.terrarum.ModMgr
|
||||
import org.luaj.vm2.Globals
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2017-05-31.
|
||||
*/
|
||||
class PeripheralCharLCD(val width: Int, val height: Int) : net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral("charLCD") {
|
||||
/*companion object {
|
||||
private val fontSheet = BitmapFont(ModMgr.getPath("dwarventech", "mt-32.tga"), 16, 16)
|
||||
private val font = BitmapFont(fontSheet, 0.toChar())
|
||||
private val fontW = fontSheet.width / fontSheet.horizontalCount
|
||||
private val fontH = fontSheet.height / fontSheet.verticalCount
|
||||
}
|
||||
|
||||
override fun loadLib(globals: Globals) {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return super.toString()
|
||||
}
|
||||
|
||||
override val memSize = width * height
|
||||
|
||||
var cursor: Int = 0 // character LCDs are mostly single long line wrapped
|
||||
|
||||
val memory = ByteArray(memSize) // temporary; replace with proper VMPeripheralWrapper
|
||||
|
||||
/**
|
||||
* @param g Frame Buffer that holds the display of LCD screen
|
||||
*/
|
||||
fun render(batch: SpriteBatch) {
|
||||
memory.forEachIndexed { index, byte ->
|
||||
font.draw(batch, "${byte.toChar()}", (index % width) * fontW.toFloat(), (index / width) * fontH.toFloat())
|
||||
}
|
||||
}*/
|
||||
override fun loadLib(globals: Globals) {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return super.toString()
|
||||
}
|
||||
|
||||
override val memSize: Int
|
||||
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral
|
||||
|
||||
import org.luaj.vm2.Globals
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer
|
||||
import org.luaj.vm2.LuaTable
|
||||
import org.luaj.vm2.LuaValue
|
||||
import org.luaj.vm2.lib.OneArgFunction
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* Provides internet access.
|
||||
*
|
||||
* Created by minjaesong on 2016-09-24.
|
||||
*/
|
||||
internal class PeripheralInternet(val host: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer)
|
||||
: net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral("internet"){
|
||||
|
||||
override val memSize = 1024
|
||||
|
||||
override fun loadLib(globals: Globals) {
|
||||
globals["internet"] = LuaTable()
|
||||
globals["internet"]["fetch"] = FetchWebPage()
|
||||
}
|
||||
|
||||
class FetchWebPage() : OneArgFunction() {
|
||||
override fun call(urlstr: LuaValue): LuaValue {
|
||||
val url = URL(urlstr.checkjstring())
|
||||
val inputstream = BufferedReader(InputStreamReader(url.openStream()))
|
||||
|
||||
var inline = ""
|
||||
var readline = inputstream.readLine()
|
||||
while (readline != null) {
|
||||
inline += readline
|
||||
readline = inputstream.readLine()
|
||||
}
|
||||
inputstream.close()
|
||||
|
||||
return LuaValue.valueOf(inline)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral
|
||||
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer
|
||||
import org.luaj.vm2.Globals
|
||||
import org.luaj.vm2.LuaTable
|
||||
import org.luaj.vm2.LuaValue
|
||||
|
||||
/**
|
||||
* Virtual driver for 4-track squarewave PSG, which has no ability of changing a duty cycle
|
||||
* but has a volume control (you'll need some other tracker than MONOTONE)
|
||||
*
|
||||
* Created by minjaesong on 2016-09-27.
|
||||
*/
|
||||
internal class PeripheralPSG(val host: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer)
|
||||
: net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral("psg") {
|
||||
|
||||
override val memSize = 1024
|
||||
|
||||
override fun loadLib(globals: Globals) {
|
||||
globals["psg"] = LuaTable()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,659 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral
|
||||
|
||||
import org.luaj.vm2.*
|
||||
import org.luaj.vm2.lib.*
|
||||
|
||||
/**
|
||||
* Resolution: 640 x 200, non-square pixels
|
||||
*
|
||||
* Created by minjaesong on 2017-02-08.
|
||||
*/
|
||||
/*class PeripheralVideoCard(val host: TerrarumComputer, val termW: Int = 80, val termH: Int = 25) :
|
||||
Peripheral("ppu") {
|
||||
companion object {
|
||||
val blockW = 8 // MUST BE 8
|
||||
val blockH = 8 // MUST BE 8
|
||||
|
||||
/**
|
||||
* Converts consecutive lua table indexed from 1 as IntArray.
|
||||
* The lua table must not contain any nils in the sequence.
|
||||
*/
|
||||
fun LuaTable.toIntArray(): IntArray {
|
||||
val arr = IntArray(this.keyCount())
|
||||
var k = 1
|
||||
while (true) {
|
||||
if (this[k].isnil()) break
|
||||
|
||||
arr[k - 1] = this[k].checkint()
|
||||
k += 1
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
}
|
||||
|
||||
val width = termW * blockW
|
||||
val height = termH * blockH
|
||||
|
||||
val spritesCount = 256
|
||||
|
||||
val vram = VRAM(width, height, spritesCount)
|
||||
val frameBuffer = ImageBuffer(width, height)
|
||||
val frameBufferImage = frameBuffer.image
|
||||
|
||||
// hard-coded 8x8
|
||||
var fontRom = Array<IntArray>(256, { IntArray(blockH) })
|
||||
|
||||
var showCursor = true
|
||||
|
||||
private var cursorBlinkOn = true
|
||||
private var cursorBlinkTimer = 0
|
||||
private val cursorBlinkTime = 250
|
||||
|
||||
init {
|
||||
// build it for first time
|
||||
resetTextRom()
|
||||
|
||||
frameBufferImage.filter = Image.FILTER_NEAREST
|
||||
}
|
||||
|
||||
val CLUT = VRAM.CLUT
|
||||
val colorsCount = CLUT.size
|
||||
|
||||
val luaSpriteTable = LuaTable()
|
||||
|
||||
var color = 15 // black
|
||||
set(value) {
|
||||
if (value >= colorsCount || value < 0) {
|
||||
throw IllegalArgumentException("Unknown colour: $value")
|
||||
}
|
||||
else {
|
||||
field = value
|
||||
}
|
||||
}
|
||||
|
||||
val cursorSprite = ImageBuffer(blockW, blockH * 2)
|
||||
val cursorImage: Image
|
||||
|
||||
|
||||
override val memSize = 256 * 8 + (width * height * 2) + spritesCount * 16 * 7
|
||||
// fontRom + framebuffers + sprites
|
||||
|
||||
|
||||
init {
|
||||
Arrays.fill(cursorSprite.rgba, 0xFF.toByte())
|
||||
cursorImage = cursorSprite.image
|
||||
|
||||
fun composeSpriteObject(spriteIndex: Int) : LuaValue {
|
||||
val sprite = vram.sprites[spriteIndex]
|
||||
val t = LuaTable()
|
||||
|
||||
t["getColFromPal"] = SpriteGetColFromPal(sprite)
|
||||
t["setPixel"] = SpriteSetPixel(sprite)
|
||||
t["setPalSet"] = SpriteSetPaletteSet(sprite)
|
||||
t["setLine"] = SpriteSetLine(sprite)
|
||||
t["setAll"] = SpriteSetAll(sprite)
|
||||
t["setRotation"] = SpriteSetRotation(sprite)
|
||||
t["setFlipH"] = SpriteSetFlipH(sprite)
|
||||
t["setFlipV"] = SpriteSetFlipV(sprite)
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
(0..spritesCount - 1).forEach { luaSpriteTable[it + 1] = composeSpriteObject(it) }
|
||||
}
|
||||
|
||||
fun buildFontRom(ref: String) {
|
||||
// load font rom out of TGA
|
||||
val imageRef = Image(ref)
|
||||
val image = imageRef.texture.textureData
|
||||
val imageWidth = imageRef.width
|
||||
|
||||
for (i in 0..255) {
|
||||
for (y in 0..blockH - 1) {
|
||||
// letter mirrored horizontally!
|
||||
var scanline = 0
|
||||
for (x in 0..blockW - 1) {
|
||||
val subX = i % 16
|
||||
val subY = i / 16
|
||||
val bit = image[4 * ((subY * blockH + y) * imageWidth + blockW * subX + x) + 3] != 0.toByte()
|
||||
if (bit) scanline = scanline or (1 shl x)
|
||||
}
|
||||
|
||||
fontRom[i][y] = scanline
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadLib(globals: Globals) {
|
||||
globals["ppu"] = LuaTable()
|
||||
globals["ppu"]["setTextForeColor"] = SetTextForeColor(this)
|
||||
globals["ppu"]["getTextForeColor"] = GetTextForeColor(this)
|
||||
globals["ppu"]["setTextBackColor"] = SetTextBackColor(this)
|
||||
globals["ppu"]["getTextBackColor"] = GetTextBackColor(this)
|
||||
globals["ppu"]["setColor"] = SetDrawColor(this)
|
||||
globals["ppu"]["getColor"] = GetDrawColor(this)
|
||||
globals["ppu"]["emitChar"] = EmitChar(this)
|
||||
globals["ppu"]["clearAll"] = ClearAll(this)
|
||||
globals["ppu"]["clearBack"] = ClearBackground(this)
|
||||
globals["ppu"]["clearFore"] = ClearForeground(this)
|
||||
|
||||
globals["ppu"]["getSpritesCount"] = GetSpritesCount(this)
|
||||
globals["ppu"]["width"] = GetWidth(this)
|
||||
globals["ppu"]["height"] = GetHeight(this)
|
||||
|
||||
|
||||
globals["ppu"]["getSprite"] = GetSprite(this)
|
||||
|
||||
globals["ppu"]["drawRectBack"] = DrawRectBack(this)
|
||||
globals["ppu"]["drawRectFore"] = DrawRectFore(this)
|
||||
globals["ppu"]["fillRectBack"] = FillRectBack(this)
|
||||
globals["ppu"]["fillRectFore"] = FillRectFore(this)
|
||||
globals["ppu"]["drawString"] = DrawString(this)
|
||||
}
|
||||
|
||||
private val spriteBuffer = ImageBuffer(VSprite.width * 2, VSprite.height)
|
||||
|
||||
fun render(g: Graphics) {
|
||||
cursorBlinkTimer += Terrarum.deltaTime
|
||||
if (cursorBlinkTimer > cursorBlinkTime) {
|
||||
cursorBlinkTimer -= cursorBlinkTime
|
||||
cursorBlinkOn = !cursorBlinkOn
|
||||
}
|
||||
|
||||
fun VSprite.render() {
|
||||
if (this.isVisible) {
|
||||
val h = VSprite.height
|
||||
val w = VSprite.width
|
||||
if (rotation and 1 == 0) { // deg 0, 180
|
||||
(if (rotation == 0 && !vFlip || rotation == 2 && vFlip) 0..h - 1 else h - 1 downTo 0).forEachIndexed { ordY, y ->
|
||||
(if (rotation == 0 && !hFlip || rotation == 2 && hFlip) 0..w - 1 else w - 1 downTo 0).forEachIndexed { ordX, x ->
|
||||
val pixelData = data[y].ushr(2 * x).and(0b11)
|
||||
val col = getColourFromPalette(pixelData)
|
||||
|
||||
if (this.drawWide) {
|
||||
spriteBuffer.setRGBA(ordX * 2, ordY, col.red, col.green, col.blue, col.alpha)
|
||||
spriteBuffer.setRGBA(ordX * 2 + 1, ordY, col.red, col.green, col.blue, col.alpha)
|
||||
}
|
||||
else {
|
||||
spriteBuffer.setRGBA(ordX, ordY, col.red, col.green, col.blue, col.alpha)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // deg 90, 270
|
||||
(if (rotation == 3 && !hFlip || rotation == 1 && hFlip) 0..w - 1 else w - 1 downTo 0).forEachIndexed { ordY, y ->
|
||||
(if (rotation == 3 && !vFlip || rotation == 1 && vFlip) h - 1 downTo 0 else 0..h - 1).forEachIndexed { ordX, x ->
|
||||
val pixelData = data[y].ushr(2 * x).and(0b11)
|
||||
val col = getColourFromPalette(pixelData)
|
||||
|
||||
if (this.drawWide) {
|
||||
spriteBuffer.setRGBA(ordY * 2, ordX, col.red, col.green, col.blue, col.alpha)
|
||||
spriteBuffer.setRGBA(ordY * 2 + 1, ordX, col.red, col.green, col.blue, col.alpha)
|
||||
}
|
||||
else {
|
||||
spriteBuffer.setRGBA(ordY, ordX, col.red, col.green, col.blue, col.alpha)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
System.arraycopy(vram.background.rgba, 0, frameBuffer.rgba, 0, vram.background.rgba.size)
|
||||
vram.sprites.forEach {
|
||||
if (it.isBackground) {
|
||||
it.render()
|
||||
frameBuffer.softwareRender(spriteBuffer, it.posX, it.posY)
|
||||
}
|
||||
}
|
||||
frameBuffer.softwareRender(vram.foreground, 0, 0)
|
||||
vram.sprites.forEach {
|
||||
if (!it.isBackground) {
|
||||
it.render()
|
||||
frameBuffer.softwareRender(spriteBuffer, it.posX, it.posY)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val img = frameBuffer.image
|
||||
img.filter = Image.FILTER_NEAREST
|
||||
g.drawImage(img.getScaledCopy(blockW * termW, blockH * termH * 2), 0f, 0f)
|
||||
|
||||
|
||||
if (cursorBlinkOn && showCursor) {
|
||||
g.drawImage(
|
||||
cursorImage,
|
||||
host.term.cursorX * blockW.toFloat(),
|
||||
(host.term as Terminal).cursorY * blockH * 2f
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// scanlines
|
||||
g.color = Color(0, 0, 0, 40)
|
||||
g.lineWidth = 1f
|
||||
for (i in 1..blockH * termH * 2 step 2) {
|
||||
g.drawLine(0f, i.toFloat(), blockW * termW - 1f, i.toFloat())
|
||||
}
|
||||
|
||||
img.destroy()
|
||||
}
|
||||
|
||||
private var textColorFore = 49 // white
|
||||
private var textColorBack = 64 // transparent
|
||||
|
||||
fun drawChar(c: Char, x: Int, y: Int, colFore: Int = textColorFore, colBack: Int = textColorBack) {
|
||||
val glyph = fontRom[c.toInt()]
|
||||
|
||||
// software render
|
||||
for (gy in 0..blockH - 1) { for (gx in 0..blockW - 1) {
|
||||
val glyAlpha = glyph[gy] and (1 shl gx)
|
||||
|
||||
if (glyAlpha != 0) {
|
||||
vram.setForegroundPixel(x + gx, y + gy, colFore)
|
||||
}
|
||||
else {
|
||||
vram.setForegroundPixel(x + gx, y + gy, colBack)
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
fun clearBackground() {
|
||||
for (i in 0..width * height - 1) {
|
||||
vram.background.rgba[i] = if (i % 4 == 3) 0xFF.toByte() else 0x00.toByte()
|
||||
}
|
||||
}
|
||||
|
||||
fun clearForeground() {
|
||||
for (i in 0..width * height - 1) {
|
||||
vram.foreground.rgba[i] = 0x00.toByte()
|
||||
}
|
||||
}
|
||||
|
||||
fun clearAll() {
|
||||
for (i in 0..width * height - 1) {
|
||||
vram.background.rgba[i] = if (i % 4 == 3) 0xFF.toByte() else 0x00.toByte()
|
||||
vram.foreground.rgba[i] = 0x00.toByte()
|
||||
}
|
||||
}
|
||||
|
||||
fun drawRectBack(x: Int, y: Int, w: Int, h: Int, c: Int = color) {
|
||||
for (it in 0..w - 1) {
|
||||
vram.setBackgroundPixel(x + it, y, c)
|
||||
vram.setBackgroundPixel(x + it, y + h - 1, c)
|
||||
}
|
||||
for (it in 1..h - 2) {
|
||||
vram.setBackgroundPixel(x, y + it, c)
|
||||
vram.setBackgroundPixel(x + w - 1, y + it, c)
|
||||
}
|
||||
}
|
||||
|
||||
fun fillRectBack(x: Int, y: Int, w: Int, h: Int, c: Int = color) {
|
||||
for (py in 0..h - 1) { for (px in 0..w - 1) {
|
||||
vram.setBackgroundPixel(x + px, y + py, c)
|
||||
}}
|
||||
}
|
||||
|
||||
fun drawRectFore(x: Int, y: Int, w: Int, h: Int, c: Int = color) {
|
||||
for (it in 0..w - 1) {
|
||||
vram.setForegroundPixel(x + it, y, c)
|
||||
vram.setForegroundPixel(x + it, y + h - 1, c)
|
||||
}
|
||||
for (it in 1..h - 2) {
|
||||
vram.setForegroundPixel(x, y + it, c)
|
||||
vram.setForegroundPixel(x + w - 1, y + it, c)
|
||||
}
|
||||
}
|
||||
|
||||
fun fillRectFore(x: Int, y: Int, w: Int, h: Int, c: Int = color) {
|
||||
for (py in 0..h - 1) { for (px in 0..w - 1) {
|
||||
vram.setForegroundPixel(x + px, y + py, c)
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
fun getSprite(index: Int) = vram.sprites[index]
|
||||
|
||||
|
||||
private fun ImageBuffer.softwareRender(other: ImageBuffer, posX: Int, posY: Int) {
|
||||
for (y in 0..other.height - 1) {
|
||||
for (x in 0..other.width - 1) {
|
||||
val ix = posX + x
|
||||
val iy = posY + y
|
||||
if (ix >= 0 && iy >= 0 && ix < this.width && iy < this.height) {
|
||||
if (other.rgba[4 * (y * other.texWidth + x) + 3] != 0.toByte()) { // if not transparent
|
||||
this.rgba[4 * (iy * this.texWidth + ix) + 0] = other.rgba[4 * (y * other.texWidth + x) + 0]
|
||||
this.rgba[4 * (iy * this.texWidth + ix) + 1] = other.rgba[4 * (y * other.texWidth + x) + 1]
|
||||
this.rgba[4 * (iy * this.texWidth + ix) + 2] = other.rgba[4 * (y * other.texWidth + x) + 2]
|
||||
this.rgba[4 * (iy * this.texWidth + ix) + 3] = other.rgba[4 * (y * other.texWidth + x) + 3]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Array be like, in binary; notice that glyphs are flipped horizontally:
|
||||
* ...
|
||||
* 00011000
|
||||
* 00011100
|
||||
* 00011000
|
||||
* 00011000
|
||||
* 00011000
|
||||
* 00011000
|
||||
* 01111111
|
||||
* 00000000
|
||||
* 00111111
|
||||
* 01100011
|
||||
* 01100000
|
||||
* 00111111
|
||||
* 00000011
|
||||
* 00000011
|
||||
* 01111111
|
||||
* 00000000
|
||||
* ...
|
||||
*/
|
||||
fun setTextRom(data: Array<Int>) {
|
||||
for (i in 0..255) {
|
||||
for (y in 0..blockH - 1) {
|
||||
// letter mirrored horizontally!
|
||||
fontRom[i][y] = data[blockH * i + y]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resetTextRom() {
|
||||
buildFontRom("./assets/graphics/fonts/milky.tga")
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// Lua functions //
|
||||
///////////////////
|
||||
|
||||
class SetTextForeColor(val videoCard: PeripheralVideoCard) : OneArgFunction() {
|
||||
override fun call(arg: LuaValue): LuaValue {
|
||||
videoCard.textColorFore = arg.checkint()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
class GetTextForeColor(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return videoCard.textColorFore.toLua()
|
||||
}
|
||||
}
|
||||
class SetTextBackColor(val videoCard: PeripheralVideoCard) : OneArgFunction() {
|
||||
override fun call(arg: LuaValue): LuaValue {
|
||||
videoCard.textColorBack = arg.checkint()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
class GetTextBackColor(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return videoCard.textColorBack.toLua()
|
||||
}
|
||||
}
|
||||
class SetDrawColor(val videoCard: PeripheralVideoCard) : OneArgFunction() {
|
||||
override fun call(arg: LuaValue): LuaValue {
|
||||
videoCard.color = arg.checkint()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
class GetDrawColor(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return videoCard.color.toLua()
|
||||
}
|
||||
}
|
||||
class GetWidth(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return videoCard.width.toLua()
|
||||
}
|
||||
}
|
||||
class GetHeight(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return videoCard.height.toLua()
|
||||
}
|
||||
}
|
||||
class EmitChar(val videoCard: PeripheralVideoCard) : ThreeArgFunction() {
|
||||
/** emitChar(char, x, y) */
|
||||
override fun call(arg1: LuaValue, arg2: LuaValue, arg3: LuaValue): LuaValue {
|
||||
videoCard.drawChar(arg1.checkint().toChar(), arg2.checkint(), arg3.checkint())
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
class ClearAll(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
videoCard.clearAll()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
class ClearBackground(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
videoCard.clearBackground()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
class ClearForeground(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
videoCard.clearForeground()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
class GetSpritesCount(val videoCard: PeripheralVideoCard) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return videoCard.spritesCount.toLua()
|
||||
}
|
||||
}
|
||||
class GetSprite(val videoCard: PeripheralVideoCard) : OneArgFunction() {
|
||||
override fun call(arg: LuaValue): LuaValue {
|
||||
return videoCard.luaSpriteTable[arg.checkint() - 1]
|
||||
}
|
||||
}
|
||||
class DrawRectBack(val videoCard: PeripheralVideoCard) : FourArgFunction() {
|
||||
override fun call(arg1: LuaValue, arg2: LuaValue, arg3: LuaValue, arg4: LuaValue): LuaValue {
|
||||
videoCard.drawRectBack(arg1.checkint(), arg2.checkint(), arg3.checkint(), arg4.checkint())
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
class FillRectBack(val videoCard: PeripheralVideoCard) : FourArgFunction() {
|
||||
override fun call(arg1: LuaValue, arg2: LuaValue, arg3: LuaValue, arg4: LuaValue): LuaValue {
|
||||
videoCard.fillRectBack(arg1.checkint(), arg2.checkint(), arg3.checkint(), arg4.checkint())
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
class DrawRectFore(val videoCard: PeripheralVideoCard) : FourArgFunction() {
|
||||
override fun call(arg1: LuaValue, arg2: LuaValue, arg3: LuaValue, arg4: LuaValue): LuaValue {
|
||||
videoCard.drawRectFore(arg1.checkint(), arg2.checkint(), arg3.checkint(), arg4.checkint())
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
class FillRectFore(val videoCard: PeripheralVideoCard) : FourArgFunction() {
|
||||
override fun call(arg1: LuaValue, arg2: LuaValue, arg3: LuaValue, arg4: LuaValue): LuaValue {
|
||||
videoCard.fillRectFore(arg1.checkint(), arg2.checkint(), arg3.checkint(), arg4.checkint())
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
class DrawString(val videoCard: PeripheralVideoCard) : ThreeArgFunction() {
|
||||
override fun call(arg1: LuaValue, arg2: LuaValue, arg3: LuaValue): LuaValue {
|
||||
val str = arg1.checkjstring()
|
||||
val x = arg2.checkint()
|
||||
val y = arg3.checkint()
|
||||
str.forEachIndexed { i, c ->
|
||||
videoCard.drawChar(c, x + blockW * i, y)
|
||||
}
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
/////////////
|
||||
// Sprites //
|
||||
/////////////
|
||||
|
||||
private class SpriteGetColFromPal(val sprite: VSprite) : OneArgFunction() {
|
||||
override fun call(arg: LuaValue): LuaValue {
|
||||
return when (arg.checkint()) {
|
||||
0 -> sprite.pal0.toLua()
|
||||
1 -> sprite.pal1.toLua()
|
||||
2 -> sprite.pal2.toLua()
|
||||
3 -> sprite.pal3.toLua()
|
||||
else -> throw IndexOutOfBoundsException("Palette size: 4, input: ${arg.checkint()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
private class SpriteSetPixel(val sprite: VSprite) : ThreeArgFunction() {
|
||||
override fun call(arg1: LuaValue, arg2: LuaValue, arg3: LuaValue): LuaValue {
|
||||
sprite.setPixel(arg1.checkint(), arg2.checkint(), arg3.checkint())
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
private class SpriteSetPaletteSet(val sprite: VSprite) : OneArgFunction() {
|
||||
override fun call(arg: LuaValue): LuaValue {
|
||||
sprite.setPaletteSet(arg(1).checkint(), arg(2).checkint(), arg(3).checkint(), arg(4).checkint())
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
private class SpriteSetLine(val sprite: VSprite) : TwoArgFunction() {
|
||||
override fun call(arg1: LuaValue, arg2: LuaValue): LuaValue {
|
||||
sprite.setLine(arg1.checkint(), arg2.checktable().toIntArray())
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
private class SpriteSetAll(val sprite: VSprite) : OneArgFunction() {
|
||||
override fun call(arg: LuaValue): LuaValue {
|
||||
sprite.setAll(arg.checktable().toIntArray())
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
private class SpriteSetRotation(val sprite: VSprite) : OneArgFunction() {
|
||||
override fun call(arg: LuaValue): LuaValue {
|
||||
sprite.rotation = arg.checkint()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
private class SpriteSetFlipH(val sprite: VSprite) : OneArgFunction() {
|
||||
override fun call(arg: LuaValue): LuaValue {
|
||||
sprite.hFlip = arg.checkboolean()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
private class SpriteSetFlipV(val sprite: VSprite) : OneArgFunction() {
|
||||
override fun call(arg: LuaValue): LuaValue {
|
||||
sprite.vFlip = arg.checkboolean()
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VRAM(pxlWidth: Int, pxlHeight: Int, nSprites: Int) {
|
||||
val sprites = Array(nSprites, { VSprite() })
|
||||
|
||||
val background = ImageBuffer(pxlWidth, pxlHeight)
|
||||
val foreground = ImageBuffer(pxlWidth, pxlHeight) // text mode glyphs rendered here
|
||||
|
||||
companion object {
|
||||
val CLUT = DecodeTapestry.colourIndices64 + Color(0, 0, 0, 0)
|
||||
}
|
||||
|
||||
|
||||
fun setBackgroundPixel(x: Int, y: Int, color: Int) {
|
||||
val col = CLUT[color]
|
||||
background.setRGBA(x, y, col.red, col.green, col.blue, 255)
|
||||
}
|
||||
|
||||
fun setForegroundPixel(x: Int, y: Int, color: Int) {
|
||||
val col = CLUT[color]
|
||||
foreground.setRGBA(x, y, col.red, col.green, col.blue, col.alpha)
|
||||
}
|
||||
}
|
||||
|
||||
class VSprite {
|
||||
companion object {
|
||||
val width = 8
|
||||
val height = 8
|
||||
}
|
||||
|
||||
internal val CLUT = VRAM.CLUT
|
||||
internal val data = IntArray(height)
|
||||
|
||||
var pal0 = 64 // transparent
|
||||
var pal1 = 56 // light cyan
|
||||
var pal2 = 19 // magenta
|
||||
var pal3 = 49 // white
|
||||
|
||||
var posX = 0
|
||||
var posY = 0
|
||||
|
||||
var hFlip = false
|
||||
var vFlip = false
|
||||
var rotation = 0
|
||||
set(value) { field = value % 4 }
|
||||
|
||||
var isBackground = false
|
||||
var isVisible = false
|
||||
var drawWide = false
|
||||
|
||||
fun setPaletteSet(col0: Int, col1: Int, col2: Int, col3: Int) {
|
||||
pal0 = col0
|
||||
pal1 = col1
|
||||
pal2 = col2
|
||||
pal3 = col3
|
||||
}
|
||||
|
||||
fun getColourFromPalette(swatchNumber: Int): Color {
|
||||
val clutIndex = when (swatchNumber) {
|
||||
0 -> pal0
|
||||
1 -> pal1
|
||||
2 -> pal2
|
||||
3 -> pal3
|
||||
else -> throw IndexOutOfBoundsException("Palette size: 4, input: $swatchNumber")
|
||||
}
|
||||
return CLUT[clutIndex]
|
||||
}
|
||||
|
||||
fun setPos(x: Int, y: Int) {
|
||||
posX = x
|
||||
posY = y
|
||||
}
|
||||
|
||||
fun setPixel(x: Int, y: Int, color: Int) {
|
||||
data[y] = data[y] xor data[y].and(3 shl (2 * x)) // mask off desired area to 0b00
|
||||
data[y] = data[y] or (color shl (2 * x))
|
||||
}
|
||||
|
||||
fun setLine(y: Int, rowData: IntArray) {
|
||||
for (i in 0..width - 1) {
|
||||
setPixel(i, y, rowData[i])
|
||||
}
|
||||
}
|
||||
|
||||
fun setAll(data: IntArray) {
|
||||
for (i in 0..width * height - 1) {
|
||||
setPixel(i % width, i / width, data[i])
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
abstract class FourArgFunction : LibFunction() {
|
||||
|
||||
abstract override fun call(arg1: LuaValue, arg2: LuaValue, arg3: LuaValue, arg4: LuaValue): LuaValue
|
||||
|
||||
override fun call(): LuaValue {
|
||||
return call(LuaValue.NIL, LuaValue.NIL, LuaValue.NIL, LuaValue.NIL)
|
||||
}
|
||||
|
||||
override fun call(arg: LuaValue): LuaValue {
|
||||
return call(arg, LuaValue.NIL, LuaValue.NIL, LuaValue.NIL)
|
||||
}
|
||||
|
||||
override fun call(arg1: LuaValue, arg2: LuaValue): LuaValue {
|
||||
return call(arg1, arg2, LuaValue.NIL, LuaValue.NIL)
|
||||
}
|
||||
|
||||
override fun invoke(varargs: Varargs): Varargs {
|
||||
return call(varargs.arg1(), varargs.arg(2), varargs.arg(3), varargs.arg(4))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.terminal
|
||||
|
||||
/**
|
||||
* Printing text using Term API triggers 'compatibility' mode, where you are limited to 16 colours.
|
||||
* Use PPU API for full 64 colours!
|
||||
*
|
||||
* Created by minjaesong on 2017-02-08.
|
||||
*/
|
||||
/*class GraphicsTerminal(private val host: TerrarumComputer) : Terminal {
|
||||
lateinit var videoCard: PeripheralVideoCard
|
||||
|
||||
override val width: Int; get() = videoCard.termW
|
||||
override val height: Int; get() = videoCard.termH
|
||||
override val coloursCount: Int; get() = videoCard.colorsCount
|
||||
override var cursorX = 0
|
||||
override var cursorY = 0
|
||||
override var cursorBlink = true
|
||||
|
||||
val backDefault = 0 // black
|
||||
val foreDefault = 1 // white
|
||||
|
||||
override var backColour = backDefault
|
||||
override var foreColour = foreDefault
|
||||
|
||||
private val colourKey: Int
|
||||
get() = backColour.shl(4) or (foreColour).and(0xFF)
|
||||
|
||||
override fun getColor(index: Int) = videoCard.CLUT[index]
|
||||
|
||||
override val displayW: Int; get() = videoCard.width //+ 2 * borderSize
|
||||
override val displayH: Int; get() = videoCard.height //+ 2 * borderSize
|
||||
|
||||
private lateinit var videoScreen: Image
|
||||
|
||||
var TABSIZE = 4
|
||||
|
||||
val errorColour = 6
|
||||
|
||||
override fun printChars(s: String) {
|
||||
printString(s, cursorX, cursorY)
|
||||
}
|
||||
|
||||
override fun update(gc: GameContainer, delta: Int) {
|
||||
wrap()
|
||||
}
|
||||
|
||||
// copied from SimpleTextTerminal
|
||||
private fun wrap() {
|
||||
// wrap cursor
|
||||
if (cursorX < 0 && cursorY <= 0) {
|
||||
setCursor(0, 0)
|
||||
}
|
||||
else if (cursorX >= width) {
|
||||
setCursor(0, cursorY + 1)
|
||||
}
|
||||
else if (cursorX < 0) {
|
||||
setCursor(width - 1, cursorY - 1)
|
||||
}
|
||||
// auto scroll up
|
||||
if (cursorY >= height) {
|
||||
scroll()
|
||||
}
|
||||
}
|
||||
|
||||
override fun render(gc: GameContainer, g: Graphics) {
|
||||
videoCard.render(g)
|
||||
}
|
||||
|
||||
override fun keyPressed(key: Int, c: Char) {
|
||||
host.keyPressed(key, c)
|
||||
}
|
||||
|
||||
override fun writeChars(s: String) {
|
||||
writeString(s, cursorX, cursorY)
|
||||
}
|
||||
|
||||
/** Unlike lua function, this one in Zero-based. */
|
||||
override fun setCursor(x: Int, y: Int) {
|
||||
cursorX = x
|
||||
cursorY = y
|
||||
}
|
||||
|
||||
override fun emitChar(bufferChar: Int, x: Int, y: Int) {
|
||||
videoCard.drawChar(
|
||||
bufferChar.and(0xFF).toChar(),
|
||||
x * PeripheralVideoCard.blockW,
|
||||
y * PeripheralVideoCard.blockH,
|
||||
CLUT16_TO_64[bufferChar.ushr(8).and(0xF)],
|
||||
CLUT16_TO_64[bufferChar.ushr(12).and(0xF)]
|
||||
)
|
||||
}
|
||||
|
||||
override fun emitChar(c: Char, xx: Int, yy: Int) {
|
||||
wrap() // needed
|
||||
var nx = xx
|
||||
var ny = yy
|
||||
// wrap argument cursor
|
||||
if (xx < 0 && yy <= 0) {
|
||||
nx = 0
|
||||
ny = 0
|
||||
}
|
||||
else if (cursorX >= width) {
|
||||
println("arstenioarstoneirastneo")
|
||||
nx = 0
|
||||
ny += 1
|
||||
}
|
||||
else if (cursorX < 0) {
|
||||
nx = width - 1
|
||||
ny -= 1
|
||||
}
|
||||
// auto scroll up
|
||||
if (cursorY >= height) {
|
||||
scroll()
|
||||
ny -= 1
|
||||
}
|
||||
|
||||
println("xx: $xx, yy: $yy")
|
||||
println("nx: $nx, ny: $ny")
|
||||
|
||||
videoCard.drawChar(
|
||||
c,
|
||||
nx * PeripheralVideoCard.blockW,
|
||||
ny * PeripheralVideoCard.blockH,
|
||||
CLUT16_TO_64[foreColour]
|
||||
)
|
||||
}
|
||||
|
||||
override fun printChar(c: Char) {
|
||||
if (c >= ' ' && c.toInt() != 127) {
|
||||
emitChar(c, cursorX, cursorY)
|
||||
cursorX += 1
|
||||
}
|
||||
else {
|
||||
if (BACKSPACE.contains(c)) {
|
||||
cursorX -= 1
|
||||
//wrap()
|
||||
emitChar(0.toChar(), cursorX, cursorY)
|
||||
}
|
||||
else {
|
||||
when (c) {
|
||||
ASCII_BEL -> bell(".")
|
||||
ASCII_TAB -> { cursorX = (cursorX).div(TABSIZE).times(TABSIZE) + TABSIZE }
|
||||
ASCII_LF -> { newLine(); System.err.println("LF ${Random().nextInt(100)}") }
|
||||
ASCII_FF -> clear()
|
||||
ASCII_CR -> { cursorX = 0 }
|
||||
ASCII_DC1, ASCII_DC2, ASCII_DC3,
|
||||
ASCII_DC4 -> { foreColour = c - ASCII_DC1 }
|
||||
ASCII_DLE -> { foreColour = errorColour }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun emitString(s: String, x: Int, y: Int) {
|
||||
setCursor(x, y)
|
||||
|
||||
for (i in 0..s.length - 1) {
|
||||
printChar(s[i])
|
||||
}
|
||||
|
||||
setCursor(x, y)
|
||||
}
|
||||
|
||||
override fun printString(s: String, x: Int, y: Int) {
|
||||
writeString(s, x, y)
|
||||
newLine()
|
||||
}
|
||||
|
||||
override fun writeString(s: String, x: Int, y: Int) {
|
||||
setCursor(x, y)
|
||||
|
||||
for (i in 0..s.length - 1) {
|
||||
printChar(s[i])
|
||||
}
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
videoCard.clearForeground()
|
||||
}
|
||||
|
||||
override fun clearLine() {
|
||||
//TODO("not implemented")
|
||||
}
|
||||
|
||||
override fun newLine() {
|
||||
cursorX = 0; cursorY += 1
|
||||
//wrap()
|
||||
}
|
||||
|
||||
override fun scroll(amount: Int) {
|
||||
val rgba = videoCard.vram.foreground.rgba
|
||||
val displacement = amount.abs() * PeripheralVideoCard.blockH * videoCard.vram.foreground.texWidth * 4
|
||||
if (amount >= 0) {
|
||||
System.arraycopy(
|
||||
rgba, displacement,
|
||||
rgba, 0,
|
||||
rgba.size - displacement
|
||||
)
|
||||
for (it in rgba.size - 1 downTo rgba.size - displacement + 1) { rgba[it] = 0.toByte() }
|
||||
}
|
||||
else {
|
||||
System.arraycopy(
|
||||
rgba, 0,
|
||||
rgba, displacement,
|
||||
rgba.size - displacement
|
||||
)
|
||||
for (it in 0..displacement - 1) { rgba[it] = 0.toByte() }
|
||||
}
|
||||
|
||||
cursorY += -amount
|
||||
}
|
||||
|
||||
/**
|
||||
* does not changes color setting in PPU
|
||||
*/
|
||||
override fun setColour(back: Int, fore: Int) {
|
||||
foreColour = fore
|
||||
backColour = back
|
||||
}
|
||||
|
||||
/**
|
||||
* does not changes color setting in PPU
|
||||
*/
|
||||
override fun resetColour() {
|
||||
foreColour = foreDefault
|
||||
backColour = backDefault
|
||||
}
|
||||
|
||||
/** // copied from SimpleTextTerminal
|
||||
* @param duration: milliseconds
|
||||
* @param freg: Frequency (float)
|
||||
*/
|
||||
override fun emitTone(duration: Int, freq: Double) {
|
||||
host.clearBeepQueue()
|
||||
host.enqueueBeep(duration, freq)
|
||||
}
|
||||
|
||||
// copied from SimpleTextTerminal
|
||||
/** for "emitTone code" on modern BIOS. */
|
||||
override fun bell(pattern: String) {
|
||||
host.clearBeepQueue()
|
||||
|
||||
val freq: Double =
|
||||
if (host.luaJ_globals["computer"]["bellpitch"].isnil())
|
||||
1000.0
|
||||
else
|
||||
host.luaJ_globals["computer"]["bellpitch"].checkdouble()
|
||||
|
||||
for (c in pattern) {
|
||||
when (c) {
|
||||
'.' -> { host.enqueueBeep(80, freq); host.enqueueBeep(50, 0.0) }
|
||||
'-' -> { host.enqueueBeep(200, freq); host.enqueueBeep(50, 0.0) }
|
||||
'=' -> { host.enqueueBeep(500, freq); host.enqueueBeep(50, 0.0) }
|
||||
' ' -> { host.enqueueBeep(200, 0.0) }
|
||||
',' -> { host.enqueueBeep(50, 0.0) }
|
||||
else -> throw IllegalArgumentException("Unacceptable pattern: $c (from '$pattern')")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val WHITE7500 = Color(0xe4eaff)
|
||||
|
||||
val ASCII_NUL = 0.toChar()
|
||||
val ASCII_BEL = 7.toChar() // *BEEP!*
|
||||
val ASCII_TAB = 9.toChar() // move cursor to next (TABSIZE * yy) pos (5 -> 8, 3- > 4, 4 -> 8)
|
||||
val ASCII_LF = 10.toChar() // new line
|
||||
val ASCII_FF = 12.toChar() // new page
|
||||
val ASCII_CR = 13.toChar() // x <- 0
|
||||
val BACKSPACE = arrayOf(127.toChar(), 8.toChar()) // backspace and delete char (8 for WIN, 127 for OSX)
|
||||
val ASCII_DC1 = 17.toChar() // foreground colour 0
|
||||
val ASCII_DC2 = 18.toChar() // foreground colour 1
|
||||
val ASCII_DC3 = 19.toChar() // foreground colour 2
|
||||
val ASCII_DC4 = 20.toChar() // foreground colour 3
|
||||
val ASCII_DLE = 16.toChar() // error message colour
|
||||
|
||||
val asciiControlInUse = charArrayOf(
|
||||
ASCII_NUL,
|
||||
ASCII_BEL,
|
||||
8.toChar(),
|
||||
ASCII_TAB,
|
||||
ASCII_LF,
|
||||
ASCII_FF,
|
||||
ASCII_CR,
|
||||
127.toChar(),
|
||||
ASCII_DC1,
|
||||
ASCII_DC2,
|
||||
ASCII_DC3,
|
||||
ASCII_DC4,
|
||||
ASCII_DLE
|
||||
)
|
||||
|
||||
val CLUT = DecodeTapestry.colourIndices64
|
||||
val CLUT16_TO_64 = intArrayOf(
|
||||
15, 49, 16, 48, 44, 29, 33, 18,
|
||||
5, 22, 39, 26, 25, 10, 31, 13
|
||||
)
|
||||
}
|
||||
|
||||
fun attachVideoCard(videocard: PeripheralVideoCard) {
|
||||
this.videoCard = videocard
|
||||
|
||||
videoScreen = Image(videoCard.width, videoCard.height)
|
||||
}
|
||||
}*/
|
||||
@@ -0,0 +1,381 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.terminal
|
||||
|
||||
|
||||
/**
|
||||
* Default text terminal.
|
||||
*
|
||||
* Created by minjaesong on 2016-09-07.
|
||||
*/
|
||||
/*open class SimpleTextTerminal(
|
||||
phosphorColour: Color, override val width: Int, override val height: Int, private val host: TerrarumComputer,
|
||||
colour: Boolean = false, hires: Boolean = false
|
||||
) : Terminal {
|
||||
|
||||
private val DEBUG = false
|
||||
|
||||
/**
|
||||
* Terminals must support AT LEAST 4 colours.
|
||||
* Color index 0 must be default background, index 3 must be default foreground
|
||||
*/
|
||||
open protected val colours = if (colour) CLUT else CLUT.copyOfRange(0, 4)
|
||||
|
||||
val phosphor = if (colour) WHITE7500 else phosphorColour
|
||||
open val colourScreen = if (colour) Color(8, 8, 8) else Color(19, 19, 19)
|
||||
|
||||
override val coloursCount: Int
|
||||
get() = colours.size
|
||||
|
||||
val errorColour = if (coloursCount > 4) 6 else 1
|
||||
|
||||
open val backDefault = 0 // STANDARD
|
||||
open val foreDefault = 3 // STANDARD
|
||||
|
||||
override var backColour = backDefault
|
||||
override var foreColour = foreDefault
|
||||
private val colourKey: Int
|
||||
get() = backColour.shl(4) or (foreColour).and(0xFF)
|
||||
|
||||
override var cursorX = 0
|
||||
override var cursorY = 0
|
||||
override var cursorBlink = true
|
||||
|
||||
val screenBuffer = AAFrame(width, height, this)
|
||||
|
||||
open protected val fontRef =
|
||||
"./assets/graphics/fonts/${
|
||||
if (hires) "milky.tga"
|
||||
else if (phosphor == GREEN || phosphor == AMBER) "MDA.tga"
|
||||
else "milkymda.tga"
|
||||
}"
|
||||
open protected val fontImg = Image(fontRef)
|
||||
open val fontW = fontImg.width / 16
|
||||
open val fontH = fontImg.height / 16
|
||||
open protected val font = SpriteSheetFont(SpriteSheet(fontRef, fontW, fontH), 0.toChar())
|
||||
|
||||
val borderSize = 20
|
||||
override val displayW = fontW * width + 2 * borderSize
|
||||
override val displayH = fontH * height + 2 * borderSize
|
||||
|
||||
|
||||
var TABSIZE = 4
|
||||
|
||||
private var cursorBlinkTimer = 0
|
||||
private val cursorBlinkLen = 250
|
||||
var cursorBlinkOn = true
|
||||
private set
|
||||
|
||||
|
||||
|
||||
override fun getColor(index: Int): Color = colours[index]
|
||||
|
||||
override fun update(gc: GameContainer, delta: Int) {
|
||||
cursorBlinkTimer = cursorBlinkTimer.plus(delta)
|
||||
if (cursorBlinkTimer > cursorBlinkLen) {
|
||||
cursorBlinkTimer -= cursorBlinkLen
|
||||
cursorBlinkOn = !cursorBlinkOn
|
||||
}
|
||||
|
||||
wrap()
|
||||
}
|
||||
|
||||
private fun wrap() {
|
||||
// wrap cursor
|
||||
if (cursorX < 0 && cursorY <= 0) {
|
||||
setCursor(0, 0)
|
||||
}
|
||||
else if (cursorX >= width) {
|
||||
setCursor(0, cursorY + 1)
|
||||
}
|
||||
else if (cursorX < 0) {
|
||||
setCursor(width - 1, cursorY - 1)
|
||||
}
|
||||
// auto scroll up
|
||||
if (cursorY >= height) {
|
||||
scroll()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* pass UIcanvas to the parameter "g"
|
||||
*/
|
||||
override fun render(gc: GameContainer, g: Graphics) {
|
||||
g.font = font
|
||||
|
||||
|
||||
blendNormal()
|
||||
|
||||
// black background (this is mandatory)
|
||||
g.color = Color.black
|
||||
g.fillRect(0f, 0f, displayW.toFloat(), displayH.toFloat())
|
||||
|
||||
|
||||
// screen buffer
|
||||
for (y in 0..height - 1) {
|
||||
for (x in 0..width - 1) {
|
||||
val ch = screenBuffer.getChar(x, y)
|
||||
|
||||
// background
|
||||
g.color = getColor(screenBuffer.getBackgroundColour(x, y))
|
||||
g.fillRect(fontW * x.toFloat() + borderSize, fontH * y.toFloat() + borderSize,
|
||||
fontW.toFloat(), fontH.toFloat())
|
||||
|
||||
// foreground
|
||||
if (ch.toInt() != 0 && ch.toInt() != 32) {
|
||||
g.color = getColor(screenBuffer.getForegroundColour(x, y))
|
||||
g.drawString(
|
||||
Character.toString(ch),
|
||||
fontW * x.toFloat() + borderSize, fontH * y.toFloat() + borderSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// cursor
|
||||
if (cursorBlinkOn) {
|
||||
g.color = getColor(if (cursorBlink) foreDefault else backDefault)
|
||||
|
||||
g.fillRect(
|
||||
fontW * cursorX.toFloat() + borderSize,
|
||||
fontH * cursorY.toFloat() + borderSize,
|
||||
fontW.toFloat(),
|
||||
fontH.toFloat()
|
||||
)
|
||||
}
|
||||
|
||||
// colour base
|
||||
g.color = colourScreen
|
||||
blendScreen()
|
||||
g.fillRect(0f, 0f, fontW * width.toFloat() + 2 * borderSize, fontH * height.toFloat() + 2 * borderSize)
|
||||
|
||||
// colour overlay
|
||||
g.color = phosphor
|
||||
blendMul()
|
||||
g.fillRect(0f, 0f, fontW * width.toFloat() + 2 * borderSize, fontH * height.toFloat() + 2 * borderSize)
|
||||
|
||||
|
||||
|
||||
blendNormal()
|
||||
|
||||
}
|
||||
|
||||
/** Unlike lua function, this one in Zero-based. */
|
||||
override fun setCursor(x: Int, y: Int) {
|
||||
cursorX = x
|
||||
cursorY = y
|
||||
}
|
||||
|
||||
/** Emits a bufferChar. Does not move cursor
|
||||
* It is also not affected by the control sequences; just print them out as symbol */
|
||||
override fun emitChar(bufferChar: Int, x: Int, y: Int) {
|
||||
try { screenBuffer.drawBuffer(x, y, bufferChar.toChar()) }
|
||||
catch (e: ArrayIndexOutOfBoundsException) { e.printStackTrace() }
|
||||
}
|
||||
|
||||
/** Emits a char. Does not move cursor
|
||||
* It is also not affected by the control sequences; just print them out as symbol */
|
||||
override fun emitChar(c: Char, x: Int, y: Int) {
|
||||
try { screenBuffer.drawBuffer(x, y, c.toInt().and(0xFF).toChar(), colourKey) }
|
||||
catch (e: ArrayIndexOutOfBoundsException) { e.printStackTrace() }
|
||||
}
|
||||
|
||||
/** Prints a char and move cursor accordingly. */
|
||||
override fun printChar(c: Char) {
|
||||
wrap()
|
||||
if (c >= ' ' && c.toInt() != 127) {
|
||||
emitChar(c, cursorX, cursorY)
|
||||
cursorX += 1
|
||||
}
|
||||
else {
|
||||
when (c) {
|
||||
ASCII_BEL -> bell(".")
|
||||
ASCII_BS -> { cursorX -= 1; wrap() }
|
||||
ASCII_TAB -> { cursorX = (cursorX).div(TABSIZE).times(TABSIZE) + TABSIZE }
|
||||
ASCII_LF -> newLine()
|
||||
ASCII_FF -> clear()
|
||||
ASCII_CR -> { cursorX = 0 }
|
||||
ASCII_DEL -> { cursorX -= 1; wrap(); emitChar(colourKey.shl(8), cursorX, cursorY) }
|
||||
ASCII_DC1, ASCII_DC2, ASCII_DC3, ASCII_DC4 -> { foreColour = c - ASCII_DC1 }
|
||||
ASCII_DLE -> { foreColour = errorColour }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** (TTY): Prints a series of chars and move cursor accordingly, then LF
|
||||
* (term): printString() on current cursor pos */
|
||||
override fun printChars(s: String) {
|
||||
printString(s, cursorX, cursorY)
|
||||
}
|
||||
|
||||
/** (TTY): Prints a series of chars and move cursor accordingly
|
||||
* (term): writeString() on current cursor pos */
|
||||
override fun writeChars(s: String) {
|
||||
writeString(s, cursorX, cursorY)
|
||||
}
|
||||
|
||||
/** Emits a string and move cursor accordingly, then do LF */
|
||||
override fun printString(s: String, x: Int, y: Int) {
|
||||
writeString(s, x, y)
|
||||
newLine()
|
||||
}
|
||||
|
||||
/** Emits a string and move cursor accordingly. */
|
||||
override fun writeString(s: String, x: Int, y: Int) {
|
||||
setCursor(x, y)
|
||||
|
||||
for (i in 0..s.length - 1) {
|
||||
printChar(s[i])
|
||||
wrap()
|
||||
}
|
||||
}
|
||||
|
||||
/** Emits a string, does not affected by control sequences. Does not move cursor */
|
||||
override fun emitString(s: String, x: Int, y: Int) {
|
||||
setCursor(x, y)
|
||||
|
||||
for (i in 0..s.length - 1) {
|
||||
printChar(s[i])
|
||||
wrap()
|
||||
}
|
||||
|
||||
setCursor(x, y)
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
screenBuffer.clear(backColour)
|
||||
cursorX = 0
|
||||
cursorY = 0
|
||||
}
|
||||
|
||||
override fun clearLine() {
|
||||
for (i in 0..width - 1)
|
||||
screenBuffer.drawBuffer(i, cursorY, 0.toChar(), colourKey)
|
||||
}
|
||||
|
||||
override fun newLine() {
|
||||
cursorX = 0; cursorY += 1; wrap()
|
||||
}
|
||||
|
||||
override fun scroll(amount: Int) {
|
||||
val offset = amount.times(width).abs()
|
||||
val bsize = screenBuffer.sizeof.ushr(1)
|
||||
|
||||
if (amount == 0) return
|
||||
|
||||
if (amount > 0) {
|
||||
for (i in 0..bsize - 1) {
|
||||
if (i < Math.min(bsize, bsize - offset - 1))
|
||||
// displace contents in buffer
|
||||
screenBuffer.frameBuffer[i] = screenBuffer.frameBuffer[i + offset]
|
||||
else
|
||||
// clean up garbages
|
||||
screenBuffer.frameBuffer[i] = 0.toChar()
|
||||
}
|
||||
cursorY -= amount
|
||||
}
|
||||
else {
|
||||
for (i in bsize - 1 downTo 0) {
|
||||
if (i > Math.max(offset - 1, 0))
|
||||
// displace contents in buffer
|
||||
screenBuffer.frameBuffer[i] = screenBuffer.frameBuffer[i - offset]
|
||||
else
|
||||
screenBuffer.frameBuffer[i] = 0.toChar()
|
||||
}
|
||||
cursorY += amount
|
||||
}
|
||||
}
|
||||
|
||||
override fun setColour(back: Int, fore: Int) {
|
||||
backColour = back
|
||||
foreColour = fore
|
||||
}
|
||||
|
||||
override fun resetColour() {
|
||||
backColour = backDefault
|
||||
foreColour = foreDefault
|
||||
}
|
||||
|
||||
/**
|
||||
* @param duration: milliseconds
|
||||
* @param freg: Frequency (float)
|
||||
*/
|
||||
override fun emitTone(duration: Int, freq: Double) {
|
||||
// println("!! Beep playing row $beepCursor, d ${Math.min(duration, maxDuration)} f $freq")
|
||||
host.clearBeepQueue()
|
||||
host.enqueueBeep(duration, freq)
|
||||
}
|
||||
|
||||
/** for "emitTone code" on modern BIOS. */
|
||||
override fun bell(pattern: String) {
|
||||
host.clearBeepQueue()
|
||||
|
||||
val freq: Double =
|
||||
if (host.luaJ_globals["computer"]["bellpitch"].isnil())
|
||||
1000.0
|
||||
else
|
||||
host.luaJ_globals["computer"]["bellpitch"].checkdouble()
|
||||
|
||||
for (c in pattern) {
|
||||
when (c) {
|
||||
'.' -> { host.enqueueBeep(80, freq); host.enqueueBeep(50, 0.0) }
|
||||
'-' -> { host.enqueueBeep(200, freq); host.enqueueBeep(50, 0.0) }
|
||||
'=' -> { host.enqueueBeep(500, freq); host.enqueueBeep(50, 0.0) }
|
||||
' ' -> { host.enqueueBeep(200, 0.0) }
|
||||
',' -> { host.enqueueBeep(50, 0.0) }
|
||||
else -> throw IllegalArgumentException("Unacceptable pattern: $c (from '$pattern')")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun keyPressed(key: Int, c: Char) {
|
||||
host.keyPressed(key, c)
|
||||
}
|
||||
|
||||
private fun isOOB(x: Int, y: Int) =
|
||||
(x < 0 || y < 0 || x >= width || y >= height)
|
||||
|
||||
companion object {
|
||||
val AMBER = Color(255, 183, 0) // P3, 602 nm
|
||||
val GREEN = Color(74, 255, 0) // P39, 525 nm
|
||||
val WHITE = Color(204, 223, 255) // approximation of white CRT I own
|
||||
private val WHITE7500 = Color(0xe4eaff)
|
||||
val BLUE_NOVELTY = Color(0x27d7ff)
|
||||
val RED = Color(250, 51, 0) // 632 nm
|
||||
val AMETHYST_NOVELTY = Color(0xc095ff)
|
||||
|
||||
val ASCII_NUL = 0.toChar()
|
||||
val ASCII_BEL = 7.toChar() // *BEEP!*
|
||||
val ASCII_BS = 8.toChar() // x = x - 1
|
||||
val ASCII_TAB = 9.toChar() // move cursor to next (TABSIZE * yy) pos (5 -> 8, 3- > 4, 4 -> 8)
|
||||
val ASCII_LF = 10.toChar() // new line
|
||||
val ASCII_FF = 12.toChar() // new page
|
||||
val ASCII_CR = 13.toChar() // x <- 0
|
||||
val ASCII_DEL = 127.toChar() // backspace and delete char
|
||||
val ASCII_DC1 = 17.toChar() // foreground colour 0
|
||||
val ASCII_DC2 = 18.toChar() // foreground colour 1
|
||||
val ASCII_DC3 = 19.toChar() // foreground colour 2
|
||||
val ASCII_DC4 = 20.toChar() // foreground colour 3
|
||||
val ASCII_DLE = 16.toChar() // error message colour
|
||||
|
||||
val asciiControlInUse = charArrayOf(
|
||||
ASCII_NUL,
|
||||
ASCII_BEL,
|
||||
ASCII_BS,
|
||||
ASCII_TAB,
|
||||
ASCII_LF,
|
||||
ASCII_FF,
|
||||
ASCII_CR,
|
||||
ASCII_DEL,
|
||||
ASCII_DC1,
|
||||
ASCII_DC2,
|
||||
ASCII_DC3,
|
||||
ASCII_DC4,
|
||||
ASCII_DLE
|
||||
)
|
||||
|
||||
val CLUT = DecodeTapestry.colourIndices16
|
||||
}
|
||||
}*/
|
||||
|
||||
class ALException(errorCode: Int) : Exception("ALerror: $errorCode") {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.terminal
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-09-14.
|
||||
*/
|
||||
interface Teletype {
|
||||
val width: Int
|
||||
val displayW: Int
|
||||
|
||||
var cursorX: Int
|
||||
|
||||
/**
|
||||
* 0: Teletype
|
||||
* 4: Non-colour terminal (Note that '2' is invalid!)
|
||||
* >4: Colour terminal
|
||||
*/
|
||||
val coloursCount: Int
|
||||
|
||||
fun update(delta: Float)
|
||||
fun render(batch: SpriteBatch)
|
||||
fun keyPressed(key: Int, c: Char)
|
||||
|
||||
/** Prints a char and move cursor accordingly */
|
||||
fun printChar(c: Char)
|
||||
/** (TTY): Prints a series of chars and move cursor accordingly, then LF
|
||||
* (term): printString() on current cursor pos */
|
||||
fun printChars(s: String)
|
||||
/** (TTY): Prints a series of chars and move cursor accordingly
|
||||
* (term): writeString() on current cursor pos */
|
||||
fun writeChars(s: String)
|
||||
fun newLine()
|
||||
fun scroll(amount: Int = 1)
|
||||
|
||||
fun bell(pattern: String = ".")
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.terminal
|
||||
|
||||
import net.torvald.terrarumsansbitmap.slick2d.GameFontBase
|
||||
import net.torvald.terrarum.blendMul
|
||||
import net.torvald.terrarum.blendNormal
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-09-15.
|
||||
*/
|
||||
/*class TeletypeTerminal : Teletype {
|
||||
override val width = 40
|
||||
override val displayW: Int
|
||||
get() = width * font.W
|
||||
/**
|
||||
* 0: Teletype
|
||||
* 4: Non-colour terminal (Note that '2' is invalid!)
|
||||
* >4: Colour terminal
|
||||
*/
|
||||
override val coloursCount = 0
|
||||
|
||||
override var cursorX = 0
|
||||
|
||||
private val font = TTYFont()
|
||||
|
||||
private var lineBuffer = StringBuilder()
|
||||
|
||||
private var currentJob = 0
|
||||
private var currentJobStep = 0
|
||||
private var currentJobLen = 0
|
||||
private var currentJobQueue: Any? = null
|
||||
// make sure certain jobs deliberately take long time by doing them one-by-one, for each frame.
|
||||
private val JOB_IDLE = 0
|
||||
private val JOB_MOVEHEAD = 1
|
||||
private val JOB_PRINTCHAR = 2
|
||||
private val JOB_LINEFEED = 3
|
||||
|
||||
override fun update(gc: GameContainer, delta: Int) {
|
||||
wrap()
|
||||
|
||||
/*when (currentJob) {
|
||||
JOB_PRINTCHAR -> {
|
||||
printChar((currentJobQueue!! as String)[currentJobStep])
|
||||
currentJobStep += 1
|
||||
}
|
||||
JOB_LINEFEED -> {
|
||||
newLine()
|
||||
currentJobStep += 1
|
||||
}
|
||||
}*/
|
||||
|
||||
if (currentJobStep > currentJobLen) {
|
||||
currentJob = JOB_IDLE
|
||||
currentJobLen = 0
|
||||
currentJobStep = 0
|
||||
currentJobQueue = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun render(gc: GameContainer, g: Graphics) {
|
||||
g.font = font
|
||||
g.drawString(lineBuffer.toString(), 0f, 0f)
|
||||
}
|
||||
|
||||
val TABSIZE = 4
|
||||
|
||||
/** Prints a char and move cursor accordingly */
|
||||
override fun printChar(c: Char) {
|
||||
wrap()
|
||||
if (c >= ' ' && c.toInt() != 127) {
|
||||
lineBuffer.append(c)
|
||||
cursorX += 1
|
||||
}
|
||||
else {
|
||||
when (c) {
|
||||
SimpleTextTerminal.ASCII_BEL -> bell()
|
||||
SimpleTextTerminal.ASCII_BS -> { cursorX -= 1; wrap() }
|
||||
SimpleTextTerminal.ASCII_TAB -> { cursorX = (cursorX).div(TABSIZE).times(TABSIZE) + TABSIZE }
|
||||
SimpleTextTerminal.ASCII_LF -> newLine()
|
||||
SimpleTextTerminal.ASCII_FF -> newLine()
|
||||
SimpleTextTerminal.ASCII_CR -> { cursorX = 0 }
|
||||
SimpleTextTerminal.ASCII_DEL -> { } // NOT supported, do nothing
|
||||
SimpleTextTerminal.ASCII_DC1, SimpleTextTerminal.ASCII_DC2, SimpleTextTerminal.ASCII_DC3, SimpleTextTerminal.ASCII_DC4
|
||||
-> { } // NOT supported, do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** (TTY): Prints a series of chars and move cursor accordingly
|
||||
* (term): writeString() on current cursor pos */
|
||||
override fun writeChars(s: String) {
|
||||
/*currentJob = JOB_PRINTCHAR
|
||||
currentJobLen = s.length
|
||||
currentJobQueue = s*/
|
||||
for (i in 0..s.length - 1)
|
||||
printChar(s[i])
|
||||
}
|
||||
|
||||
/** (TTY): Prints a series of chars and move cursor accordingly, then LF
|
||||
* (term): writeString() on current cursor pos */
|
||||
override fun printChars(s: String) {
|
||||
/*currentJob = JOB_PRINTCHAR
|
||||
currentJobLen = s.length + 1
|
||||
currentJobQueue = "$s\n"*/
|
||||
writeChars("$s\n")
|
||||
}
|
||||
|
||||
override fun newLine() {
|
||||
lineBuffer = StringBuilder()
|
||||
}
|
||||
|
||||
override fun scroll(amount: Int) {
|
||||
if (amount < 0) throw IllegalArgumentException("cannot scroll up")
|
||||
if (amount == 1) { newLine(); return }
|
||||
|
||||
currentJob = JOB_LINEFEED
|
||||
currentJobLen = amount
|
||||
}
|
||||
|
||||
override fun bell(pattern: String) {
|
||||
throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
private fun wrap() {
|
||||
if (cursorX < 0) cursorX = 0
|
||||
else if (cursorX >= width) newLine()
|
||||
}
|
||||
|
||||
var sb: StringBuilder = StringBuilder()
|
||||
private var inputOpen = false
|
||||
val DEBUG = true
|
||||
|
||||
override fun keyPressed(key: Int, c: Char) {
|
||||
if (inputOpen) {
|
||||
if (c == SimpleTextTerminal.ASCII_CR)
|
||||
printChar(SimpleTextTerminal.ASCII_LF)
|
||||
else
|
||||
printChar(c)
|
||||
if (!SimpleTextTerminal.asciiControlInUse.contains(c)) sb.append(c)
|
||||
else if (c == SimpleTextTerminal.ASCII_DEL && sb.length > 0) sb.deleteCharAt(sb.length - 1)
|
||||
}
|
||||
}
|
||||
|
||||
class TTYFont : Font {
|
||||
|
||||
internal val fontSheet: SpriteSheet
|
||||
|
||||
internal val W = 9
|
||||
internal val H = 12
|
||||
|
||||
private val chars = arrayOf(
|
||||
'0','1','2','3','4','5','6','7',
|
||||
'8','9','[','#','@',':','>','?',
|
||||
' ','A','B','C','D','E','F','G',
|
||||
'H','I','&','.',']','(','<','\\',
|
||||
'^','J','K','L','M','N','O','P', // ^: escape for capital letter
|
||||
'Q','R','-','¤','*',')',';','\'',
|
||||
'+','/','S','T','U','V','W','X',
|
||||
'Y','Z','_',',','%','=','"','!'
|
||||
)
|
||||
private val mappingTable = HashMap<Int, Int>()
|
||||
|
||||
init {
|
||||
fontSheet = SpriteSheet("./assets/graphics/fonts/teletype_9x12.tga", W, H)
|
||||
chars.forEachIndexed { i, c -> mappingTable[c.toInt()] = i }
|
||||
}
|
||||
|
||||
override fun getHeight(str: String): Int = H
|
||||
|
||||
override fun getWidth(str: String): Int {
|
||||
var ret = 0
|
||||
for (i in 0..str.length - 1)
|
||||
ret += W
|
||||
return ret
|
||||
}
|
||||
|
||||
override fun getLineHeight(): Int = H
|
||||
|
||||
override fun drawString(x: Float, y: Float, text: String) = drawString(x, y, text, Color.white)
|
||||
|
||||
override fun drawString(x: Float, y: Float, text: String, col: Color) {
|
||||
var thisCol = col
|
||||
var textPosOffset = 0
|
||||
|
||||
for (i in 0..text.length - 1) {
|
||||
val index = charToSpriteNum(text.toUpperCase().codePointAt(i))
|
||||
val ch = text[i]
|
||||
|
||||
if (index != null) {
|
||||
// main
|
||||
fontSheet.getSubImage(index % 8, index / 8).draw(
|
||||
x + textPosOffset, y, thisCol
|
||||
)
|
||||
}
|
||||
textPosOffset += W
|
||||
}
|
||||
}
|
||||
|
||||
override fun drawString(x: Float, y: Float, text: String, col: Color, startIndex: Int, endIndex: Int) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
private fun charToSpriteNum(ch: Int): Int? = mappingTable[ch]
|
||||
}
|
||||
}*/
|
||||
@@ -0,0 +1,64 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.terminal
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import net.torvald.terrarum.Second
|
||||
|
||||
/**
|
||||
* A terminal
|
||||
*
|
||||
* Framebuffer: USE net.torvald.aa.AAFrame
|
||||
*
|
||||
* Background color is fixed; text color is variable
|
||||
*
|
||||
* Created by minjaesong on 2016-09-07.
|
||||
*/
|
||||
interface Terminal : net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype {
|
||||
override val width: Int
|
||||
val height: Int
|
||||
override val coloursCount: Int
|
||||
override var cursorX: Int
|
||||
var cursorY: Int
|
||||
var cursorBlink: Boolean
|
||||
var backColour: Int
|
||||
var foreColour: Int
|
||||
|
||||
// to be used in UI
|
||||
override val displayW: Int
|
||||
val displayH: Int
|
||||
|
||||
fun getColor(index: Int): Color
|
||||
override fun update(delta: Float)
|
||||
override fun render(batch: SpriteBatch)
|
||||
override fun keyPressed(key: Int, c: Char)
|
||||
|
||||
// API calls
|
||||
fun setCursor(x: Int, y: Int)
|
||||
/** Emits a bufferChar. Does not move cursor
|
||||
* It is also not affected by the control sequences; just print them out as symbol */
|
||||
fun emitChar(bufferChar: Int, x: Int, y: Int)
|
||||
/** Emits a char. Does not move cursor
|
||||
* It is also not affected by the control sequences; just print them out as symbol */
|
||||
fun emitChar(c: Char, x: Int, y: Int)
|
||||
/** Prints a char and move cursor accordingly. */
|
||||
override fun printChar(c: Char)
|
||||
/** Emits a string, does not affected by control sequences. Does not move cursor */
|
||||
fun emitString(s: String, x: Int, y: Int)
|
||||
/** Emits a string and move cursor accordingly, then do LF */
|
||||
fun printString(s: String, x: Int, y: Int)
|
||||
/** Emits a string and move cursor accordingly. */
|
||||
fun writeString(s: String, x: Int, y: Int)
|
||||
fun clear()
|
||||
fun clearLine()
|
||||
override fun newLine()
|
||||
override fun scroll(amount: Int)
|
||||
fun setColour(back: Int, fore: Int)
|
||||
fun resetColour()
|
||||
/**
|
||||
* @param duration: milliseconds
|
||||
* @param freg: Frequency (float)
|
||||
*/
|
||||
fun emitTone(duration: Second, freq: Double)
|
||||
|
||||
override fun bell(pattern: String)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.terminal
|
||||
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-09-10.
|
||||
*/
|
||||
class TerminalInputStream(val host: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : InputStream() {
|
||||
|
||||
override fun read(): Int {
|
||||
//System.err.println(Thread.currentThread().name)
|
||||
// would display "LuaJ Separated", which means this InputStream will not block main thread
|
||||
|
||||
host.openStdin()
|
||||
synchronized(this) {
|
||||
(this as java.lang.Object).wait()
|
||||
}
|
||||
|
||||
return host.stdinInput
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.terminal
|
||||
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer
|
||||
import java.io.OutputStream
|
||||
import java.io.PrintStream
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-09-10.
|
||||
*/
|
||||
class TerminalPrintStream(val host: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : PrintStream(TerminalOutputStream(host))
|
||||
|
||||
class TerminalOutputStream(val host: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer) : OutputStream() {
|
||||
override fun write(b: Int) = host.term.printChar(b.and(0xFF).toChar())
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd
|
||||
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
|
||||
|
||||
/**
|
||||
* ByteArray that can hold larger than 2 GiB of Data.
|
||||
*
|
||||
* Works kind of like Bank Switching of old game console's cartridges which does same thing.
|
||||
*
|
||||
* Created by Minjaesong on 2017-04-12.
|
||||
*/
|
||||
class ByteArray64(val size: Long) {
|
||||
companion object {
|
||||
val bankSize: Int = 8192
|
||||
}
|
||||
|
||||
private val data: Array<ByteArray>
|
||||
|
||||
init {
|
||||
if (size < 0)
|
||||
throw IllegalArgumentException("Invalid array size!")
|
||||
|
||||
val requiredBanks: Int = 1 + ((size - 1) / bankSize).toInt()
|
||||
|
||||
data = Array<ByteArray>(
|
||||
requiredBanks,
|
||||
{ bankIndex ->
|
||||
kotlin.ByteArray(
|
||||
if (bankIndex == requiredBanks - 1)
|
||||
size.toBankOffset()
|
||||
else
|
||||
bankSize,
|
||||
|
||||
{ 0.toByte() }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun Long.toBankNumber(): Int = (this / bankSize).toInt()
|
||||
private fun Long.toBankOffset(): Int = (this % bankSize).toInt()
|
||||
|
||||
operator fun set(index: Long, value: Byte) {
|
||||
if (index < 0 || index >= size)
|
||||
throw ArrayIndexOutOfBoundsException("size $size, index $index")
|
||||
|
||||
data[index.toBankNumber()][index.toBankOffset()] = value
|
||||
}
|
||||
|
||||
operator fun get(index: Long): Byte {
|
||||
if (index < 0 || index >= size)
|
||||
throw ArrayIndexOutOfBoundsException("size $size, index $index")
|
||||
|
||||
return data[index.toBankNumber()][index.toBankOffset()]
|
||||
}
|
||||
|
||||
operator fun iterator(): ByteIterator {
|
||||
return object : ByteIterator() {
|
||||
var iterationCounter = 0L
|
||||
|
||||
override fun nextByte(): Byte {
|
||||
iterationCounter += 1
|
||||
return this@ByteArray64[iterationCounter - 1]
|
||||
}
|
||||
|
||||
override fun hasNext() = iterationCounter < this@ByteArray64.size
|
||||
}
|
||||
}
|
||||
|
||||
fun iteratorChoppedToInt(): IntIterator {
|
||||
return object : IntIterator() {
|
||||
var iterationCounter = 0L
|
||||
val iteratorSize = 1 + ((this@ByteArray64.size - 1) / 4).toInt()
|
||||
|
||||
override fun nextInt(): Int {
|
||||
var byteCounter = iterationCounter * 4L
|
||||
var int = 0
|
||||
(0..3).forEach {
|
||||
if (byteCounter + it < this@ByteArray64.size) {
|
||||
int += this@ByteArray64[byteCounter + it].toInt() shl (it * 8)
|
||||
}
|
||||
else {
|
||||
int += 0 shl (it * 8)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
iterationCounter += 1
|
||||
return int
|
||||
}
|
||||
|
||||
override fun hasNext() = iterationCounter < iteratorSize
|
||||
}
|
||||
}
|
||||
|
||||
fun forEach(consumer: (Byte) -> Unit) = iterator().forEach { consumer(it) }
|
||||
fun forEachInt32(consumer: (Int) -> Unit) = iteratorChoppedToInt().forEach { consumer(it) }
|
||||
fun forEachBanks(consumer: (ByteArray) -> Unit) = data.forEach(consumer)
|
||||
|
||||
fun sliceArray64(range: LongRange): ByteArray64 {
|
||||
val newarr = ByteArray64(range.last - range.first + 1)
|
||||
range.forEach { index ->
|
||||
newarr[index - range.first] = this[index]
|
||||
}
|
||||
return newarr
|
||||
}
|
||||
|
||||
fun sliceArray(range: IntRange): ByteArray {
|
||||
val newarr = ByteArray(range.last - range.first + 1)
|
||||
range.forEach { index ->
|
||||
newarr[index - range.first] = this[index.toLong()]
|
||||
}
|
||||
return newarr
|
||||
}
|
||||
|
||||
fun toByteArray(): ByteArray {
|
||||
if (this.size > Integer.MAX_VALUE - 8) // according to OpenJDK; the size itself is VM-dependent
|
||||
throw TypeCastException("Impossible cast; too large to fit")
|
||||
|
||||
return ByteArray(this.size.toInt(), { this[it.toLong()] })
|
||||
}
|
||||
|
||||
fun writeToFile(file: File) {
|
||||
var fos = FileOutputStream(file, false)
|
||||
fos.write(data[0])
|
||||
fos.flush()
|
||||
fos.close()
|
||||
|
||||
if (data.size > 1) {
|
||||
fos = FileOutputStream(file, true)
|
||||
for (i in 1..data.lastIndex) {
|
||||
fos.write(data[i])
|
||||
fos.flush()
|
||||
}
|
||||
fos.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ByteArray64InputStream(val byteArray64: ByteArray64): InputStream() {
|
||||
private var readCounter = 0L
|
||||
|
||||
override fun read(): Int {
|
||||
readCounter += 1
|
||||
|
||||
return try {
|
||||
byteArray64[readCounter - 1].toUint()
|
||||
}
|
||||
catch (e: ArrayIndexOutOfBoundsException) {
|
||||
-1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd
|
||||
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
/**
|
||||
* Creates entry-to-offset tables to allow streaming from the disk, without storing whole VD file to the memory.
|
||||
*
|
||||
* Created by minjaesong on 2017-11-17.
|
||||
*/
|
||||
class DiskSkimmer(diskFile: File) {
|
||||
|
||||
class EntryOffsetPair(val entryID: Int, val offset: Long)
|
||||
|
||||
val entryToOffsetTable = ArrayList<EntryOffsetPair>()
|
||||
|
||||
|
||||
init {
|
||||
val fis = FileInputStream(diskFile)
|
||||
var currentPosition = fis.skip(47) // skip disk header
|
||||
|
||||
|
||||
fun skipRead(bytes: Long) {
|
||||
currentPosition += fis.skip(bytes)
|
||||
}
|
||||
/**
|
||||
* Reads a byte and adds up the position var
|
||||
*/
|
||||
fun readByte(): Byte {
|
||||
currentPosition++
|
||||
val read = fis.read()
|
||||
|
||||
if (read < 0) throw InternalError("Unexpectedly reached EOF")
|
||||
return read.toByte()
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads specific bytes to the buffer and adds up the position var
|
||||
*/
|
||||
fun readBytes(buffer: ByteArray): Int {
|
||||
val readStatus = fis.read(buffer)
|
||||
currentPosition += readStatus
|
||||
return readStatus
|
||||
}
|
||||
fun readIntBig(): Int {
|
||||
val buffer = ByteArray(4)
|
||||
val readStatus = readBytes(buffer)
|
||||
if (readStatus != 4) throw InternalError("Unexpected error -- EOF reached? (expected 4, got $readStatus)")
|
||||
return buffer.toIntBig()
|
||||
}
|
||||
fun readInt48(): Long {
|
||||
val buffer = ByteArray(6)
|
||||
val readStatus = readBytes(buffer)
|
||||
if (readStatus != 6) throw InternalError("Unexpected error -- EOF reached? (expected 6, got $readStatus)")
|
||||
return buffer.toInt48()
|
||||
}
|
||||
|
||||
|
||||
while (true) {
|
||||
val entryID = readIntBig()
|
||||
|
||||
// footer
|
||||
if (entryID == 0xFEFEFEFE.toInt()) break
|
||||
|
||||
|
||||
// fill up table
|
||||
entryToOffsetTable.add(EntryOffsetPair(entryID, currentPosition))
|
||||
|
||||
skipRead(4) // skip entryID of parent
|
||||
|
||||
val entryType = readByte()
|
||||
|
||||
skipRead(256 + 6 + 6 + 4) // skips rest of the header
|
||||
|
||||
|
||||
// figure out the entry size so that we can skip
|
||||
val entrySize: Long = when(entryType) {
|
||||
0x01.toByte() -> readInt48()
|
||||
0x11.toByte() -> readInt48() + 6 // size of compressed payload + 6 (header elem for uncompressed size)
|
||||
0x02.toByte() -> readIntBig().shl(16).toLong() * 4 - 2 // #entris is 2 bytes, we read 4 bytes, so we subtract 2
|
||||
0x03.toByte() -> 4 // symlink
|
||||
else -> throw InternalError("Unknown entry type: ${entryType.toUint()}")
|
||||
}
|
||||
|
||||
|
||||
skipRead(entrySize) // skips rest of the entry's actual contents
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun ByteArray.toIntBig(): Int {
|
||||
return this[0].toUint().shl(24) or this[1].toUint().shl(16) or
|
||||
this[2].toUint().shl(8) or this[2].toUint()
|
||||
}
|
||||
|
||||
private fun ByteArray.toInt48(): Long {
|
||||
return this[0].toUlong().shl(56) or this[1].toUlong().shl(48) or
|
||||
this[2].toUlong().shl(40) or this[3].toUlong().shl(32) or
|
||||
this[4].toUlong().shl(24) or this[5].toUlong().shl(16) or
|
||||
this[6].toUlong().shl(8) or this[7].toUlong()
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,307 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd
|
||||
|
||||
import java.io.IOException
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
import java.util.zip.CRC32
|
||||
import kotlin.experimental.and
|
||||
import kotlin.experimental.or
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2017-03-31.
|
||||
*/
|
||||
|
||||
typealias EntryID = Int
|
||||
|
||||
val specversion = 0x03.toByte()
|
||||
|
||||
class VirtualDisk(
|
||||
/** capacity of 0 makes the disk read-only */
|
||||
var capacity: Long,
|
||||
var diskName: ByteArray = ByteArray(NAME_LENGTH),
|
||||
footer: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 = net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(8) // default to mandatory 8-byte footer
|
||||
) {
|
||||
var footerBytes: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 = footer
|
||||
private set
|
||||
val entries = HashMap<EntryID, DiskEntry>()
|
||||
var isReadOnly: Boolean
|
||||
set(value) { footerBytes[0] = (footerBytes[0] and 0xFE.toByte()) or value.toBit() }
|
||||
get() = capacity == 0L || (footerBytes.size > 0 && footerBytes[0].and(1) == 1.toByte())
|
||||
fun getDiskNameString(charset: Charset) = String(diskName, charset)
|
||||
val root: DiskEntry
|
||||
get() = entries[0]!!
|
||||
|
||||
|
||||
private fun Boolean.toBit() = if (this) 1.toByte() else 0.toByte()
|
||||
|
||||
internal fun __internalSetFooter__(footer: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64) {
|
||||
footerBytes = footer
|
||||
}
|
||||
|
||||
private fun serializeEntriesOnly(): net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 {
|
||||
val bufferList = ArrayList<Byte>() // FIXME this part would take up excessive memory for large files
|
||||
entries.forEach {
|
||||
val serialised = it.value.serialize()
|
||||
serialised.forEach { bufferList.add(it) }
|
||||
}
|
||||
|
||||
val byteArray = net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(bufferList.size.toLong())
|
||||
bufferList.forEachIndexed { index, byte -> byteArray[index.toLong()] = byte }
|
||||
return byteArray
|
||||
}
|
||||
|
||||
fun serialize(): AppendableByteBuffer {
|
||||
val entriesBuffer = serializeEntriesOnly()
|
||||
val buffer = AppendableByteBuffer(HEADER_SIZE + entriesBuffer.size + FOOTER_SIZE + footerBytes.size)
|
||||
val crc = hashCode().toBigEndian()
|
||||
|
||||
buffer.put(MAGIC)
|
||||
buffer.put(capacity.toInt48())
|
||||
buffer.put(diskName.forceSize(NAME_LENGTH))
|
||||
buffer.put(crc)
|
||||
buffer.put(specversion)
|
||||
buffer.put(entriesBuffer)
|
||||
buffer.put(FOOTER_START_MARK)
|
||||
buffer.put(footerBytes)
|
||||
buffer.put(EOF_MARK)
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
val crcList = IntArray(entries.size)
|
||||
var crcListAppendCursor = 0
|
||||
entries.forEach { _, u ->
|
||||
crcList[crcListAppendCursor] = u.hashCode()
|
||||
crcListAppendCursor++
|
||||
}
|
||||
crcList.sort()
|
||||
val crc = CRC32()
|
||||
crcList.forEach { crc.update(it) }
|
||||
|
||||
return crc.value.toInt()
|
||||
}
|
||||
|
||||
/** Expected size of the virtual disk */
|
||||
val usedBytes: Long
|
||||
get() = entries.map { it.value.serialisedSize }.sum() + HEADER_SIZE + FOOTER_SIZE
|
||||
|
||||
fun generateUniqueID(): Int {
|
||||
var id: Int
|
||||
do {
|
||||
id = Random().nextInt()
|
||||
} while (null != entries[id] || id == FOOTER_MARKER)
|
||||
return id
|
||||
}
|
||||
|
||||
override fun equals(other: Any?) = if (other == null) false else this.hashCode() == other.hashCode()
|
||||
override fun toString() = "VirtualDisk(name: ${getDiskNameString(Charsets.UTF_8)}, capacity: $capacity bytes, crc: ${hashCode().toHex()})"
|
||||
|
||||
companion object {
|
||||
val HEADER_SIZE = 47L // according to the spec
|
||||
val FOOTER_SIZE = 6L // footer mark + EOF
|
||||
val NAME_LENGTH = 32
|
||||
|
||||
val MAGIC = "TEVd".toByteArray()
|
||||
val FOOTER_MARKER = 0xFEFEFEFE.toInt()
|
||||
val FOOTER_START_MARK = FOOTER_MARKER.toBigEndian()
|
||||
val EOF_MARK = byteArrayOf(0xFF.toByte(), 0x19.toByte())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class DiskEntry(
|
||||
// header
|
||||
var entryID: EntryID,
|
||||
var parentEntryID: EntryID,
|
||||
var filename: ByteArray = ByteArray(NAME_LENGTH),
|
||||
var creationDate: Long,
|
||||
var modificationDate: Long,
|
||||
|
||||
// content
|
||||
val contents: DiskEntryContent
|
||||
) {
|
||||
fun getFilenameString(charset: Charset) = if (entryID == 0) ROOTNAME else filename.toCanonicalString(charset)
|
||||
|
||||
val serialisedSize: Long
|
||||
get() = contents.getSizeEntry() + HEADER_SIZE
|
||||
|
||||
companion object {
|
||||
val HEADER_SIZE = 281L // according to the spec
|
||||
val ROOTNAME = "(root)"
|
||||
val NAME_LENGTH = 256
|
||||
|
||||
val NORMAL_FILE = 1.toByte()
|
||||
val DIRECTORY = 2.toByte()
|
||||
val SYMLINK = 3.toByte()
|
||||
val COMPRESSED_FILE = 0x11.toByte()
|
||||
|
||||
private fun DiskEntryContent.getTypeFlag() =
|
||||
if (this is EntryFile) NORMAL_FILE
|
||||
else if (this is EntryDirectory) DIRECTORY
|
||||
else if (this is EntrySymlink) SYMLINK
|
||||
else 0 // NULL
|
||||
|
||||
fun getTypeString(entry: DiskEntryContent) = when(entry.getTypeFlag()) {
|
||||
NORMAL_FILE -> "File"
|
||||
DIRECTORY -> "Directory"
|
||||
SYMLINK -> "Symbolic Link"
|
||||
else -> "(unknown type)"
|
||||
}
|
||||
}
|
||||
|
||||
fun serialize(): AppendableByteBuffer {
|
||||
val serialisedContents = contents.serialize()
|
||||
val buffer = AppendableByteBuffer(HEADER_SIZE + serialisedContents.size)
|
||||
|
||||
buffer.put(entryID.toBigEndian())
|
||||
buffer.put(parentEntryID.toBigEndian())
|
||||
buffer.put(contents.getTypeFlag())
|
||||
buffer.put(filename.forceSize(NAME_LENGTH))
|
||||
buffer.put(creationDate.toInt48())
|
||||
buffer.put(modificationDate.toInt48())
|
||||
buffer.put(this.hashCode().toBigEndian())
|
||||
buffer.put(serialisedContents.array)
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
override fun hashCode() = contents.serialize().getCRC32()
|
||||
|
||||
override fun equals(other: Any?) = if (other == null) false else this.hashCode() == other.hashCode()
|
||||
|
||||
override fun toString() = "DiskEntry(name: ${getFilenameString(Charsets.UTF_8)}, index: $entryID, type: ${contents.getTypeFlag()}, crc: ${hashCode().toHex()})"
|
||||
}
|
||||
|
||||
|
||||
fun ByteArray.forceSize(size: Int): ByteArray {
|
||||
return ByteArray(size, { if (it < this.size) this[it] else 0.toByte() })
|
||||
}
|
||||
interface DiskEntryContent {
|
||||
fun serialize(): AppendableByteBuffer
|
||||
fun getSizePure(): Long
|
||||
fun getSizeEntry(): Long
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not retrieve bytes directly from this! Use VDUtil.retrieveFile(DiskEntry)
|
||||
* And besides, the bytes could be compressed.
|
||||
*/
|
||||
open class EntryFile(internal var bytes: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64) : DiskEntryContent {
|
||||
|
||||
override fun getSizePure() = bytes.size
|
||||
override fun getSizeEntry() = getSizePure() + 6
|
||||
|
||||
/** Create new blank file */
|
||||
constructor(size: Long): this(net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(size))
|
||||
|
||||
override fun serialize(): AppendableByteBuffer {
|
||||
val buffer = AppendableByteBuffer(getSizeEntry())
|
||||
buffer.put(getSizePure().toInt48())
|
||||
buffer.put(bytes)
|
||||
return buffer
|
||||
}
|
||||
}
|
||||
class EntryFileCompressed(internal var uncompressedSize: Long, bytes: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64) : EntryFile(bytes) {
|
||||
|
||||
override fun getSizePure() = bytes.size
|
||||
override fun getSizeEntry() = getSizePure() + 12
|
||||
|
||||
/* No new blank file for the compressed */
|
||||
|
||||
override fun serialize(): AppendableByteBuffer {
|
||||
val buffer = AppendableByteBuffer(getSizeEntry())
|
||||
buffer.put(getSizePure().toInt48())
|
||||
buffer.put(uncompressedSize.toInt48())
|
||||
buffer.put(bytes)
|
||||
return buffer
|
||||
}
|
||||
}
|
||||
class EntryDirectory(private val entries: ArrayList<EntryID> = ArrayList<EntryID>()) : DiskEntryContent {
|
||||
|
||||
override fun getSizePure() = entries.size * 4L
|
||||
override fun getSizeEntry() = getSizePure() + 2
|
||||
private fun checkCapacity(toAdd: Int = 1) {
|
||||
if (entries.size + toAdd > 65535)
|
||||
throw IOException("Directory entries limit exceeded.")
|
||||
}
|
||||
|
||||
fun add(entryID: EntryID) {
|
||||
checkCapacity()
|
||||
entries.add(entryID)
|
||||
}
|
||||
|
||||
fun remove(entryID: EntryID) {
|
||||
entries.remove(entryID)
|
||||
}
|
||||
|
||||
fun contains(entryID: EntryID) = entries.contains(entryID)
|
||||
|
||||
fun forEach(consumer: (EntryID) -> Unit) = entries.forEach(consumer)
|
||||
|
||||
val entryCount: Int
|
||||
get() = entries.size
|
||||
|
||||
override fun serialize(): AppendableByteBuffer {
|
||||
val buffer = AppendableByteBuffer(getSizeEntry())
|
||||
buffer.put(entries.size.toShort().toBigEndian())
|
||||
entries.forEach { indexNumber -> buffer.put(indexNumber.toBigEndian()) }
|
||||
return buffer
|
||||
}
|
||||
|
||||
companion object {
|
||||
val NEW_ENTRY_SIZE = DiskEntry.HEADER_SIZE + 4L
|
||||
}
|
||||
}
|
||||
class EntrySymlink(val target: EntryID) : DiskEntryContent {
|
||||
|
||||
override fun getSizePure() = 4L
|
||||
override fun getSizeEntry() = 4L
|
||||
|
||||
override fun serialize(): AppendableByteBuffer {
|
||||
val buffer = AppendableByteBuffer(4)
|
||||
return buffer.put(target.toBigEndian())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Int.toHex() = this.toLong().and(0xFFFFFFFF).toString(16).padStart(8, '0').toUpperCase()
|
||||
fun Int.toBigEndian(): ByteArray {
|
||||
return ByteArray(4, { this.ushr(24 - (8 * it)).toByte() })
|
||||
}
|
||||
fun Long.toInt48(): ByteArray {
|
||||
return ByteArray(6, { this.ushr(40 - (8 * it)).toByte() })
|
||||
}
|
||||
fun Short.toBigEndian(): ByteArray {
|
||||
return byteArrayOf(
|
||||
this.div(256).toByte(),
|
||||
this.toByte()
|
||||
)
|
||||
}
|
||||
|
||||
fun AppendableByteBuffer.getCRC32(): Int {
|
||||
val crc = CRC32()
|
||||
this.array.forEachInt32 { crc.update(it) }
|
||||
return crc.value.toInt()
|
||||
}
|
||||
class AppendableByteBuffer(val size: Long) {
|
||||
val array = net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64(size)
|
||||
private var offset = 0L
|
||||
|
||||
fun put(byteArray64: net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64): AppendableByteBuffer {
|
||||
// it's slow but works
|
||||
// can't do system.arrayCopy directly
|
||||
byteArray64.forEach { put(it) }
|
||||
return this
|
||||
}
|
||||
fun put(byteArray: ByteArray): AppendableByteBuffer {
|
||||
byteArray.forEach { put(it) }
|
||||
return this
|
||||
}
|
||||
fun put(byte: Byte): AppendableByteBuffer {
|
||||
array[offset] = byte
|
||||
offset += 1
|
||||
return this
|
||||
}
|
||||
fun forEach(consumer: (Byte) -> Unit) = array.forEach(consumer)
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,56 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.worldobject
|
||||
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-09-08.
|
||||
*/
|
||||
object ComputerPartsCodex {
|
||||
val rams = HashMap<Int, Int>() // id, capacity in bytes (0 bytes - 8 GBytes)
|
||||
val processors = HashMap<Int, Int>() // id, cycles
|
||||
val harddisks = HashMap<Int, Int>() // id, capacity in bytes
|
||||
val diskettes = HashMap<Int, Int>() // id, capacity in bytes
|
||||
val opticaldiscs = HashMap<Int, Int>() // id, capacity in bytes
|
||||
|
||||
init {
|
||||
// in kilobytes
|
||||
rams.put(4864, 128.KiB())
|
||||
rams.put(4865, 192.KiB())
|
||||
rams.put(4866, 256.KiB())
|
||||
rams.put(4867, 384.KiB())
|
||||
rams.put(4868, 512.KiB())
|
||||
rams.put(4869, 768.KiB())
|
||||
rams.put(4870, 1024.KiB())
|
||||
rams.put(4871, 2048.KiB())
|
||||
|
||||
processors.put(4872, 1000)
|
||||
processors.put(4873, 2000)
|
||||
processors.put(4874, 4000)
|
||||
processors.put(4875, 8000) // this is totally OP
|
||||
|
||||
harddisks.put(4876, 1.MB())
|
||||
harddisks.put(4877, 2.MB())
|
||||
harddisks.put(4878, 5.MB())
|
||||
harddisks.put(4879, 10.MB())
|
||||
|
||||
// Floppy disk: your primitive and only choice of removable storage
|
||||
diskettes.put(4880, 360.kB()) // single-sided
|
||||
diskettes.put(4881, 720.kB()) // double-sided
|
||||
diskettes.put(4882, 1440.kB()) // 3.5" HD
|
||||
diskettes.put(4883, 2880.kB()) // 3.5" ED
|
||||
|
||||
// CD-Rs
|
||||
opticaldiscs.put(4884, 8.MB()) // arbitrary size
|
||||
}
|
||||
|
||||
fun getRamSize(itemIndex: Int): Int = rams[itemIndex] ?: 0
|
||||
fun getProcessorCycles(itemIndex: Int): Int = processors[itemIndex] ?: 0
|
||||
fun getHDDSize(itemIndex: Int): Int = harddisks[itemIndex] ?: 0
|
||||
fun getFDDSize(itemIndex: Int): Int = diskettes[itemIndex] ?: 0
|
||||
fun getODDSize(itemIndex: Int): Int = opticaldiscs[itemIndex] ?: 0
|
||||
|
||||
private fun Int.MB() = this * 1000000 // 1 MB == 1 000 000 bytes, bitches!
|
||||
private fun Int.kB() = this * 1000
|
||||
private fun Int.KiB() = this.shl(10)
|
||||
private fun Int.MiB() = this.shl(20)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.worldobject
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import net.torvald.terrarum.modulebasegame.gameactors.FixtureBase
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-09-08.
|
||||
*/
|
||||
class FixtureBasicTerminal(world: GameWorld, phosphor: Color) : FixtureBase(world) {
|
||||
|
||||
/*val computer = TerrarumComputer(8)
|
||||
val vt: Terminal = SimpleTextTerminal(phosphor, 80, 25, computer)
|
||||
val ui = UITextTerminal(vt)
|
||||
|
||||
init {
|
||||
computer.attachTerminal(vt)
|
||||
|
||||
collisionFlag = COLLISION_PLATFORM
|
||||
|
||||
actorValue[AVKey.UUID] = UUID.randomUUID().toString()
|
||||
}*/
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.worldobject
|
||||
|
||||
import net.torvald.terrarum.modulebasegame.gameactors.FixtureBase
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-09-08.
|
||||
*/
|
||||
open class FixtureComputerBase(world: GameWorld) : FixtureBase(world) {
|
||||
|
||||
/** Connected terminal */
|
||||
var terminal: FixtureBasicTerminal? = null
|
||||
|
||||
var computerInside: net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer? = null
|
||||
|
||||
init {
|
||||
// UUID of the "brain"
|
||||
actorValue["computerid"] = "none"
|
||||
|
||||
|
||||
collisionFlag = COLLISION_PLATFORM
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
// get the computer actually work //
|
||||
////////////////////////////////////
|
||||
|
||||
fun attachTerminal(uuid: String) {
|
||||
val fetchedTerminal = getTerminalByUUID(uuid)
|
||||
computerInside = net.torvald.terrarum.modulecomputers.virtualcomputer.computer.TerrarumComputer(8)
|
||||
computerInside!!.attachTerminal(fetchedTerminal!!)
|
||||
actorValue["computerid"] = computerInside!!.UUID
|
||||
}
|
||||
|
||||
fun detatchTerminal() {
|
||||
terminal = null
|
||||
}
|
||||
|
||||
private fun getTerminalByUUID(uuid: String): net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal? {
|
||||
TODO("get terminal by UUID. Return null if not found")
|
||||
}
|
||||
|
||||
|
||||
|
||||
////////////////
|
||||
// game codes //
|
||||
////////////////
|
||||
|
||||
override fun update(delta: Float) {
|
||||
super.update(delta)
|
||||
if (terminal != null) terminal!!.update(delta)
|
||||
}
|
||||
|
||||
fun keyPressed(key: Int, c: Char) {
|
||||
/*if (terminal != null) {
|
||||
terminal!!.vt.keyPressed(key, c)
|
||||
computerInside!!.keyPressed(key, c)
|
||||
}*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.worldobject.ui
|
||||
|
||||
import com.badlogic.gdx.graphics.Camera
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import net.torvald.terrarum.Second
|
||||
import net.torvald.terrarum.ui.*
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-09-08.
|
||||
*/
|
||||
class UITextTerminal(val terminal: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : UICanvas() {
|
||||
|
||||
|
||||
override var width: Int = terminal.displayW// + some
|
||||
override var height: Int = terminal.displayH// + frame
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* In milliseconds
|
||||
*
|
||||
* Timer itself is implemented in the handler.
|
||||
*/
|
||||
override var openCloseTime: Second = OPENCLOSE_GENERIC
|
||||
|
||||
override fun updateUI(delta: Float) {
|
||||
terminal.update(delta)
|
||||
}
|
||||
|
||||
override fun renderUI(batch: SpriteBatch, camera: Camera) {
|
||||
//terminal.render(gc, terminalDisplay.graphics)
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not modify handler.openCloseCounter here.
|
||||
*/
|
||||
override fun doOpening(delta: Float) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not modify handler.openCloseCounter here.
|
||||
*/
|
||||
override fun doClosing(delta: Float) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not modify handler.openCloseCounter here.
|
||||
*/
|
||||
override fun endOpening(delta: Float) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not modify handler.openCloseCounter here.
|
||||
*/
|
||||
override fun endClosing(delta: Float) {
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user