Skip to content

Instantly share code, notes, and snippets.

@incinirate
Last active May 13, 2018 23:38
Show Gist options
  • Save incinirate/54bfe3f7d20823fcc6a730c3f0bd9feb to your computer and use it in GitHub Desktop.
Save incinirate/54bfe3f7d20823fcc6a730c3f0bd9feb to your computer and use it in GitHub Desktop.
Don't read me, I'm a squashed and bundled nightmare
-- vim: syntax=lua
-- luacheck: globals loadRemote getRemote fs loadstring peripheral
-- Load required libs / files
local surface = (function()
local surface = { } do
--[[
Surface 2
The MIT License (MIT)
Copyright (c) 2017 CrazedProgrammer
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.
]]
local surf = { }
surface.surf = surf
local table_concat, math_floor, math_atan2 = table.concat, math.floor, math.atan2
local _cc_color_to_hex, _cc_hex_to_color = { }, { }
for i = 0, 15 do
_cc_color_to_hex[2 ^ i] = string.format("%01x", i)
_cc_hex_to_color[string.format("%01x", i)] = 2 ^ i
end
local _chars = { }
for i = 0, 255 do
_chars[i] = string.char(i)
end
local _numstr = { }
for i = 0, 1023 do
_numstr[i] = tostring(i)
end
local _eprc, _esin, _ecos = 20, { }, { }
for i = 0, _eprc - 1 do
_esin[i + 1] = (1 - math.sin(i / _eprc * math.pi * 2)) / 2
_ecos[i + 1] = (1 + math.cos(i / _eprc * math.pi * 2)) / 2
end
local _steps, _palette, _rgbpal, _palr, _palg, _palb = 16
local function calcStack(stack, width, height)
local ox, oy, cx, cy, cwidth, cheight = 0, 0, 0, 0, width, height
for i = 1, #stack do
ox = ox + stack[i].ox
oy = oy + stack[i].oy
cx = cx + stack[i].x
cy = cy + stack[i].y
cwidth = stack[i].width
cheight = stack[i].height
end
return ox, oy, cx, cy, cwidth, cheight
end
local function clipRect(x, y, width, height, cx, cy, cwidth, cheight)
if x < cx then
width = width + x - cx
x = cx
end
if y < cy then
height = height + y - cy
y = cy
end
if x + width > cx + cwidth then
width = cwidth + cx - x
end
if y + height > cy + cheight then
height = cheight + cy - y
end
return x, y, width, height
end
function surface.create(width, height, b, t, c)
local surface = setmetatable({ }, {__index = surface.surf})
surface.width = width
surface.height = height
surface.buffer = { }
surface.overwrite = false
surface.stack = { }
surface.ox, surface.oy, surface.cx, surface.cy, surface.cwidth, surface.cheight = calcStack(surface.stack, width, height)
-- force array indeces instead of hashed indices
local buffer = surface.buffer
for i = 1, width * height * 3, 3 do
buffer[i] = b or false
buffer[i + 1] = t or false
buffer[i + 2] = c or false
end
buffer[width * height * 3 + 1] = false
if not b then
for i = 1, width * height * 3, 3 do
buffer[i] = b
end
end
if not t then
for i = 2, width * height * 3, 3 do
buffer[i] = t
end
end
if not c then
for i = 3, width * height * 3, 3 do
buffer[i] = c
end
end
return surface
end
function surface.getPlatformOutput(output)
output = output or (term or gpu or (love and love.graphics) or io)
if output.blit and output.setCursorPos then
return "cc", output, output.getSize()
elseif output.write and output.setCursorPos and output.setTextColor and output.setBackgroundColor then
return "cc-old", output, output.getSize()
elseif output.blitPixels then
return "riko-4", output, 320, 200
elseif output.points and output.setColor then
return "love2d", output, output.getWidth(), output.getHeight()
elseif output.drawPixel then
return "redirection", output, 64, 64
elseif output.setForeground and output.setBackground and output.set then
return "oc", output, output.getResolution()
elseif output.write then
return "ansi", output, (os.getenv and (os.getenv("COLUMNS"))) or 80, (os.getenv and (os.getenv("LINES"))) or 43
else
error("unsupported platform/output object")
end
end
function surf:output(output, x, y, sx, sy, swidth, sheight)
local platform, output, owidth, oheight = surface.getPlatformOutput(output)
x = x or 0
y = y or 0
sx = sx or 0
sy = sy or 0
swidth = swidth or self.width
sheight = sheight or self.height
sx, sy, swidth, sheight = clipRect(sx, sy, swidth, sheight, 0, 0, self.width, self.height)
local buffer = self.buffer
local bwidth = self.width
local xoffset, yoffset, idx
if platform == "cc" then
-- CC
local str, text, back = { }, { }, { }
for j = 0, sheight - 1 do
yoffset = (j + sy) * bwidth + sx
for i = 0, swidth - 1 do
xoffset = (yoffset + i) * 3
idx = i + 1
str[idx] = buffer[xoffset + 3] or " "
text[idx] = _cc_color_to_hex[buffer[xoffset + 2] or 1]
back[idx] = _cc_color_to_hex[buffer[xoffset + 1] or 32768]
end
output.setCursorPos(x + 1, y + j + 1)
output.blit(table_concat(str), table_concat(text), table_concat(back))
end
elseif platform == "cc-old" then
-- CC pre-1.76
local str, b, t, pb, pt = { }
for j = 0, sheight - 1 do
output.setCursorPos(x + 1, y + j + 1)
yoffset = (j + sy) * bwidth + sx
for i = 0, swidth - 1 do
xoffset = (yoffset + i) * 3
pb = buffer[xoffset + 1] or 32768
pt = buffer[xoffset + 2] or 1
if pb ~= b then
if #str ~= 0 then
output.write(table_concat(str))
str = { }
end
b = pb
output.setBackgroundColor(b)
end
if pt ~= t then
if #str ~= 0 then
output.write(table_concat(str))
str = { }
end
t = pt
output.setTextColor(t)
end
str[#str + 1] = buffer[xoffset + 3] or " "
end
output.write(table_concat(str))
str = { }
end
elseif platform == "riko-4" then
-- Riko 4
local pixels = { }
for j = 0, sheight - 1 do
yoffset = (j + sy) * bwidth + sx
for i = 0, swidth - 1 do
pixels[j * swidth + i + 1] = buffer[(yoffset + i) * 3 + 1] or 0
end
end
output.blitPixels(x, y, swidth, sheight, pixels)
elseif platform == "love2d" then
-- Love2D
local pos, r, g, b, pr, pg, pb = { }
for j = 0, sheight - 1 do
yoffset = (j + sy) * bwidth + sx
for i = 0, swidth - 1 do
xoffset = (yoffset + i) * 3
pr = buffer[xoffset + 1]
pg = buffer[xoffset + 2]
pb = buffer[xoffset + 3]
if pr ~= r or pg ~= g or pb ~= b then
if #pos ~= 0 then
output.setColor((r or 0) * 255, (g or 0) * 255, (b or 0) * 255, (r or g or b) and 255 or 0)
output.points(pos)
end
r, g, b = pr, pg, pb
pos = { }
end
pos[#pos + 1] = i + x + 1
pos[#pos + 1] = j + y + 1
end
end
output.setColor((r or 0) * 255, (g or 0) * 255, (b or 0) * 255, (r or g or b) and 255 or 0)
output.points(pos)
elseif platform == "redirection" then
-- Redirection arcade (gpu)
-- todo: add image:write support for extra performance
local px = output.drawPixel
for j = 0, sheight - 1 do
for i = 0, swidth - 1 do
px(x + i, y + j, buffer[((j + sy) * bwidth + (i + sx)) * 3 + 1] or 0)
end
end
elseif platform == "oc" then
-- OpenComputers
local str, lx, b, t, pb, pt = { }
for j = 0, sheight - 1 do
lx = x
yoffset = (j + sy) * bwidth + sx
for i = 0, swidth - 1 do
xoffset = (yoffset + i) * 3
pb = buffer[xoffset + 1] or 0x000000
pt = buffer[xoffset + 2] or 0xFFFFFF
if pb ~= b then
if #str ~= 0 then
output.set(lx + 1, j + y + 1, table_concat(str))
lx = i + x
str = { }
end
b = pb
output.setBackground(b)
end
if pt ~= t then
if #str ~= 0 then
output.set(lx + 1, j + y + 1, table_concat(str))
lx = i + x
str = { }
end
t = pt
output.setForeground(t)
end
str[#str + 1] = buffer[xoffset + 3] or " "
end
output.set(lx + 1, j + y + 1, table_concat(str))
str = { }
end
elseif platform == "ansi" then
-- ANSI terminal
local str, b, t, pb, pt = { }
for j = 0, sheight - 1 do
str[#str + 1] = "\x1b[".._numstr[y + j + 1]..";".._numstr[x + 1].."H"
yoffset = (j + sy) * bwidth + sx
for i = 0, swidth - 1 do
xoffset = (yoffset + i) * 3
pb = buffer[xoffset + 1] or 0
pt = buffer[xoffset + 2] or 7
if pb ~= b then
b = pb
if b < 8 then
str[#str + 1] = "\x1b[".._numstr[40 + b].."m"
elseif b < 16 then
str[#str + 1] = "\x1b[".._numstr[92 + b].."m"
elseif b < 232 then
str[#str + 1] = "\x1b[48;2;".._numstr[math_floor((b - 16) / 36 * 85 / 2)]..";".._numstr[math_floor((b - 16) / 6 % 6 * 85 / 2)]..";".._numstr[math_floor((b - 16) % 6 * 85 / 2)].."m"
else
local gr = _numstr[b * 10 - 2312]
str[#str + 1] = "\x1b[48;2;"..gr..";"..gr..";"..gr.."m"
end
end
if pt ~= t then
t = pt
if t < 8 then
str[#str + 1] = "\x1b[".._numstr[30 + t].."m"
elseif t < 16 then
str[#str + 1] = "\x1b[".._numstr[82 + t].."m"
elseif t < 232 then
str[#str + 1] = "\x1b[38;2;".._numstr[math_floor((t - 16) / 36 * 85 / 2)]..";".._numstr[math_floor((t - 16) / 6 % 6 * 85 / 2)]..";".._numstr[math_floor((t - 16) % 6 * 85 / 2)].."m"
else
local gr = _numstr[t * 10 - 2312]
str[#str + 1] = "\x1b[38;2;"..gr..";"..gr..";"..gr.."m"
end
end
str[#str + 1] = buffer[xoffset + 3] or " "
end
end
output.write(table_concat(str))
end
end
function surf:push(x, y, width, height, nooffset)
x, y = x + self.ox, y + self.oy
local ox, oy = nooffset and self.ox or x, nooffset and self.oy or y
x, y, width, height = clipRect(x, y, width, height, self.cx, self.cy, self.cwidth, self.cheight)
self.stack[#self.stack + 1] = {ox = ox - self.ox, oy = oy - self.oy, x = x - self.cx, y = y - self.cy, width = width, height = height}
self.ox, self.oy, self.cx, self.cy, self.cwidth, self.cheight = calcStack(self.stack, self.width, self.height)
end
function surf:pop()
if #self.stack == 0 then
error("no stencil to pop")
end
self.stack[#self.stack] = nil
self.ox, self.oy, self.cx, self.cy, self.cwidth, self.cheight = calcStack(self.stack, self.width, self.height)
end
function surf:copy()
local surface = setmetatable({ }, {__index = surface.surf})
for k, v in pairs(self) do
surface[k] = v
end
surface.buffer = { }
for i = 1, self.width * self.height * 3 + 1 do
surface.buffer[i] = false
end
for i = 1, self.width * self.height * 3 do
surface.buffer[i] = self.buffer[i]
end
surface.stack = { }
for i = 1, #self.stack do
surface.stack[i] = self.stack[i]
end
return surface
end
function surf:clear(b, t, c)
local xoffset, yoffset
for j = 0, self.cheight - 1 do
yoffset = (j + self.cy) * self.width + self.cx
for i = 0, self.cwidth - 1 do
xoffset = (yoffset + i) * 3
self.buffer[xoffset + 1] = b
self.buffer[xoffset + 2] = t
self.buffer[xoffset + 3] = c
end
end
end
function surf:drawPixel(x, y, b, t, c)
x, y = x + self.ox, y + self.oy
local idx
if x >= self.cx and x < self.cx + self.cwidth and y >= self.cy and y < self.cy + self.cheight then
idx = (y * self.width + x) * 3
if b or self.overwrite then
self.buffer[idx + 1] = b
end
if t or self.overwrite then
self.buffer[idx + 2] = t
end
if c or self.overwrite then
self.buffer[idx + 3] = c
end
end
end
function surf:drawString(str, x, y, b, t)
x, y = x + self.ox, y + self.oy
local sx = x
local insidey = y >= self.cy and y < self.cy + self.cheight
local idx
local lowerxlim = self.cx
local upperxlim = self.cx + self.cwidth
local writeb = b or self.overwrite
local writet = t or self.overwrite
for i = 1, #str do
local c = str:sub(i, i)
if c == "\n" then
x = sx
y = y + 1
if insidey then
if y >= self.cy + self.cheight then
return
end
else
insidey = y >= self.cy
end
else
idx = (y * self.width + x) * 3
if x >= lowerxlim and x < upperxlim and insidey then
if writeb then
self.buffer[idx + 1] = b
end
if writet then
self.buffer[idx + 2] = t
end
self.buffer[idx + 3] = c
end
x = x + 1
end
end
end
-- You can remove any of these components
function surface.load(strpath, isstr)
local data = strpath
if not isstr then
local handle = io.open(strpath, "rb")
if not handle then return end
local chars = { }
local byte = handle:read(1)
if type(byte) == "number" then -- cc doesn't conform to standards
while byte do
chars[#chars + 1] = _chars[byte]
byte = handle:read(1)
end
else
while byte do
chars[#chars + 1] = byte
byte = handle:read(1)
end
end
handle:close()
data = table_concat(chars)
end
if data:sub(1, 3) == "RIF" then
-- Riko 4 image format
local width, height = data:byte(4) * 256 + data:byte(5), data:byte(6) * 256 + data:byte(7)
local surf = surface.create(width, height)
local buffer = surf.buffer
local upper, byte = 8, false
local byte = data:byte(index)
for j = 0, height - 1 do
for i = 0, height - 1 do
if not upper then
buffer[(j * width + i) * 3 + 1] = math_floor(byte / 16)
else
buffer[(j * width + i) * 3 + 1] = byte % 16
index = index + 1
data = data:byte(index)
end
upper = not upper
end
end
return surf
elseif data:sub(1, 2) == "BM" then
-- BMP format
local width = data:byte(0x13) + data:byte(0x14) * 256
local height = data:byte(0x17) + data:byte(0x18) * 256
if data:byte(0xF) ~= 0x28 or data:byte(0x1B) ~= 1 or data:byte(0x1D) ~= 0x18 then
error("unsupported bmp format, only uncompressed 24-bit rgb is supported.")
end
local offset, linesize = 0x36, math.ceil((width * 3) / 4) * 4
local surf = surface.create(width, height)
local buffer = surf.buffer
for j = 0, height - 1 do
for i = 0, width - 1 do
buffer[(j * width + i) * 3 + 1] = data:byte((height - j - 1) * linesize + i * 3 + offset + 3) / 255
buffer[(j * width + i) * 3 + 2] = data:byte((height - j - 1) * linesize + i * 3 + offset + 2) / 255
buffer[(j * width + i) * 3 + 3] = data:byte((height - j - 1) * linesize + i * 3 + offset + 1) / 255
end
end
return surf
elseif data:find("\30") then
-- NFT format
local width, height, lwidth = 0, 1, 0
for i = 1, #data do
if data:byte(i) == 10 then -- newline
height = height + 1
if lwidth > width then
width = lwidth
end
lwidth = 0
elseif data:byte(i) == 30 or data:byte(i) == 31 then -- color control
lwidth = lwidth - 1
elseif data:byte(i) ~= 13 then -- not carriage return
lwidth = lwidth + 1
end
end
if data:byte(#data) == 10 then
height = height - 1
end
local surf = surface.create(width, height)
local buffer = surf.buffer
local index, x, y, b, t = 1, 0, 0
while index <= #data do
if data:byte(index) == 10 then
x, y = 0, y + 1
elseif data:byte(index) == 30 then
index = index + 1
b = _cc_hex_to_color[data:sub(index, index)]
elseif data:byte(index) == 31 then
index = index + 1
t = _cc_hex_to_color[data:sub(index, index)]
elseif data:byte(index) ~= 13 then
buffer[(y * width + x) * 3 + 1] = b
buffer[(y * width + x) * 3 + 2] = t
if b or t then
buffer[(y * width + x) * 3 + 3] = data:sub(index, index)
elseif data:sub(index, index) ~= " " then
buffer[(y * width + x) * 3 + 3] = data:sub(index, index)
end
x = x + 1
end
index = index + 1
end
return surf
else
-- NFP format
local width, height, lwidth = 0, 1, 0
for i = 1, #data do
if data:byte(i) == 10 then -- newline
height = height + 1
if lwidth > width then
width = lwidth
end
lwidth = 0
elseif data:byte(i) ~= 13 then -- not carriage return
lwidth = lwidth + 1
end
end
if data:byte(#data) == 10 then
height = height - 1
end
local surf = surface.create(width, height)
local buffer = surf.buffer
local x, y = 0, 0
for i = 1, #data do
if data:byte(i) == 10 then
x, y = 0, y + 1
elseif data:byte(i) ~= 13 then
buffer[(y * width + x) * 3 + 1] = _cc_hex_to_color[data:sub(i, i)]
x = x + 1
end
end
return surf
end
end
function surf:save(file, format)
format = format or "nfp"
local data = { }
if format == "nfp" then
for j = 0, self.height - 1 do
for i = 0, self.width - 1 do
data[#data + 1] = _cc_color_to_hex[self.buffer[(j * self.width + i) * 3 + 1]] or " "
end
data[#data + 1] = "\n"
end
elseif format == "nft" then
for j = 0, self.height - 1 do
local b, t, pb, pt
for i = 0, self.width - 1 do
pb = self.buffer[(j * self.width + i) * 3 + 1]
pt = self.buffer[(j * self.width + i) * 3 + 2]
if pb ~= b then
data[#data + 1] = "\30"..(_cc_color_to_hex[pb] or " ")
b = pb
end
if pt ~= t then
data[#data + 1] = "\31"..(_cc_color_to_hex[pt] or " ")
t = pt
end
data[#data + 1] = self.buffer[(j * self.width + i) * 3 + 3] or " "
end
data[#data + 1] = "\n"
end
elseif format == "rif" then
data[1] = "RIF"
data[2] = string.char(math_floor(self.width / 256), self.width % 256)
data[3] = string.char(math_floor(self.height / 256), self.height % 256)
local byte, upper, c = 0, false
for j = 0, self.width - 1 do
for i = 0, self.height - 1 do
c = self.buffer[(j * self.width + i) * 3 + 1] or 0
if not upper then
byte = c * 16
else
byte = byte + c
data[#data + 1] = string.char(byte)
end
upper = not upper
end
end
if upper then
data[#data + 1] = string.char(byte)
end
elseif format == "bmp" then
data[1] = "BM"
data[2] = string.char(0, 0, 0, 0) -- file size, change later
data[3] = string.char(0, 0, 0, 0, 0x36, 0, 0, 0, 0x28, 0, 0, 0)
data[4] = string.char(self.width % 256, math_floor(self.width / 256), 0, 0)
data[5] = string.char(self.height % 256, math_floor(self.height / 256), 0, 0)
data[6] = string.char(1, 0, 0x18, 0, 0, 0, 0, 0)
data[7] = string.char(0, 0, 0, 0) -- pixel data size, change later
data[8] = string.char(0x13, 0x0B, 0, 0, 0x13, 0x0B, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
local padchars = math.ceil((self.width * 3) / 4) * 4 - self.width * 3
for j = self.height - 1, 0, -1 do
for i = 0, self.width - 1 do
data[#data + 1] = string.char((self.buffer[(j * self.width + i) * 3 + 1] or 0) * 255)
data[#data + 1] = string.char((self.buffer[(j * self.width + i) * 3 + 2] or 0) * 255)
data[#data + 1] = string.char((self.buffer[(j * self.width + i) * 3 + 3] or 0) * 255)
end
data[#data + 1] = ("\0"):rep(padchars)
end
local size = #table_concat(data)
data[2] = string.char(size % 256, math_floor(size / 256) % 256, math_floor(size / 65536), 0)
size = size - 54
data[7] = string.char(size % 256, math_floor(size / 256) % 256, math_floor(size / 65536), 0)
else
error("format not supported")
end
data = table_concat(data)
if file then
local handle = io.open(file, "wb")
for i = 1, #data do
handle:write(data:byte(i))
end
handle:close()
end
return data
end
function surf:drawLine(x1, y1, x2, y2, b, t, c)
if x1 == x2 then
x1, y1, x2, y2 = x1 + self.ox, y1 + self.oy, x2 + self.ox, y2 + self.oy
if x1 < self.cx or x1 >= self.cx + self.cwidth then return end
if y2 < y1 then
local temp = y1
y1 = y2
y2 = temp
end
if y1 < self.cy then y1 = self.cy end
if y2 >= self.cy + self.cheight then y2 = self.cy + self.cheight - 1 end
if b or self.overwrite then
for j = y1, y2 do
self.buffer[(j * self.width + x1) * 3 + 1] = b
end
end
if t or self.overwrite then
for j = y1, y2 do
self.buffer[(j * self.width + x1) * 3 + 2] = t
end
end
if c or self.overwrite then
for j = y1, y2 do
self.buffer[(j * self.width + x1) * 3 + 3] = c
end
end
elseif y1 == y2 then
x1, y1, x2, y2 = x1 + self.ox, y1 + self.oy, x2 + self.ox, y2 + self.oy
if y1 < self.cy or y1 >= self.cy + self.cheight then return end
if x2 < x1 then
local temp = x1
x1 = x2
x2 = temp
end
if x1 < self.cx then x1 = self.cx end
if x2 >= self.cx + self.cwidth then x2 = self.cx + self.cwidth - 1 end
if b or self.overwrite then
for i = x1, x2 do
self.buffer[(y1 * self.width + i) * 3 + 1] = b
end
end
if t or self.overwrite then
for i = x1, x2 do
self.buffer[(y1 * self.width + i) * 3 + 2] = t
end
end
if c or self.overwrite then
for i = x1, x2 do
self.buffer[(y1 * self.width + i) * 3 + 3] = c
end
end
else
local delta_x = x2 - x1
local ix = delta_x > 0 and 1 or -1
delta_x = 2 * math.abs(delta_x)
local delta_y = y2 - y1
local iy = delta_y > 0 and 1 or -1
delta_y = 2 * math.abs(delta_y)
self:drawPixel(x1, y1, b, t, c)
if delta_x >= delta_y then
local error = delta_y - delta_x / 2
while x1 ~= x2 do
if (error >= 0) and ((error ~= 0) or (ix > 0)) then
error = error - delta_x
y1 = y1 + iy
end
error = error + delta_y
x1 = x1 + ix
self:drawPixel(x1, y1, b, t, c)
end
else
local error = delta_x - delta_y / 2
while y1 ~= y2 do
if (error >= 0) and ((error ~= 0) or (iy > 0)) then
error = error - delta_y
x1 = x1 + ix
end
error = error + delta_x
y1 = y1 + iy
self:drawPixel(x1, y1, b, t, c)
end
end
end
end
function surf:drawRect(x, y, width, height, b, t, c)
self:drawLine(x, y, x + width - 1, y, b, t, c)
self:drawLine(x, y, x, y + height - 1, b, t, c)
self:drawLine(x + width - 1, y, x + width - 1, y + height - 1, b, t, c)
self:drawLine(x, y + height - 1, x + width - 1, y + height - 1, b, t, c)
end
function surf:fillRect(x, y, width, height, b, t, c)
x, y, width, height = clipRect(x + self.ox, y + self.oy, width, height, self.cx, self.cy, self.cwidth, self.cheight)
if b or self.overwrite then
for j = 0, height - 1 do
for i = 0, width - 1 do
self.buffer[((j + y) * self.width + i + x) * 3 + 1] = b
end
end
end
if t or self.overwrite then
for j = 0, height - 1 do
for i = 0, width - 1 do
self.buffer[((j + y) * self.width + i + x) * 3 + 2] = t
end
end
end
if c or self.overwrite then
for j = 0, height - 1 do
for i = 0, width - 1 do
self.buffer[((j + y) * self.width + i + x) * 3 + 3] = c
end
end
end
end
function surf:drawTriangle(x1, y1, x2, y2, x3, y3, b, t, c)
self:drawLine(x1, y1, x2, y2, b, t, c)
self:drawLine(x2, y2, x3, y3, b, t, c)
self:drawLine(x3, y3, x1, y1, b, t, c)
end
function surf:fillTriangle(x1, y1, x2, y2, x3, y3, b, t, c)
if y1 > y2 then
local tempx, tempy = x1, y1
x1, y1 = x2, y2
x2, y2 = tempx, tempy
end
if y1 > y3 then
local tempx, tempy = x1, y1
x1, y1 = x3, y3
x3, y3 = tempx, tempy
end
if y2 > y3 then
local tempx, tempy = x2, y2
x2, y2 = x3, y3
x3, y3 = tempx, tempy
end
if y1 == y2 and x1 > x2 then
local temp = x1
x1 = x2
x2 = temp
end
if y2 == y3 and x2 > x3 then
local temp = x2
x2 = x3
x3 = temp
end
local x4, y4
if x1 <= x2 then
x4 = x1 + (y2 - y1) / (y3 - y1) * (x3 - x1)
y4 = y2
local tempx, tempy = x2, y2
x2, y2 = x4, y4
x4, y4 = tempx, tempy
else
x4 = x1 + (y2 - y1) / (y3 - y1) * (x3 - x1)
y4 = y2
end
local finvslope1 = (x2 - x1) / (y2 - y1)
local finvslope2 = (x4 - x1) / (y4 - y1)
local linvslope1 = (x3 - x2) / (y3 - y2)
local linvslope2 = (x3 - x4) / (y3 - y4)
local xstart, xend, dxstart, dxend
for y = math.ceil(y1 + 0.5) - 0.5, math.floor(y3 - 0.5) + 0.5, 1 do
if y <= y2 then -- first half
xstart = x1 + finvslope1 * (y - y1)
xend = x1 + finvslope2 * (y - y1)
else -- second half
xstart = x3 - linvslope1 * (y3 - y)
xend = x3 - linvslope2 * (y3 - y)
end
dxstart, dxend = math.ceil(xstart - 0.5), math.floor(xend - 0.5)
if dxstart <= dxend then
self:drawLine(dxstart, y - 0.5, dxend, y - 0.5, b, t, c)
end
end
end
function surf:drawEllipse(x, y, width, height, b, t, c)
for i = 0, _eprc - 1 do
self:drawLine(math_floor(x + _ecos[i + 1] * (width - 1) + 0.5), math_floor(y + _esin[i + 1] * (height - 1) + 0.5), math_floor(x + _ecos[(i + 1) % _eprc + 1] * (width - 1) + 0.5), math_floor(y + _esin[(i + 1) % _eprc + 1] * (height - 1) + 0.5), b, t, c)
end
end
function surf:fillEllipse(x, y, width, height, b, t, c)
x, y = x + self.ox, y + self.oy
local sx, sy
for j = 0, height - 1 do
for i = 0, width - 1 do
sx, sy = i + x, j + y
if ((i + 0.5) / width * 2 - 1) ^ 2 + ((j + 0.5) / height * 2 - 1) ^ 2 <= 1 and sx >= self.cx and sx < self.cx + self.cwidth and sy >= self.cy and sy < self.cy + self.cheight then
if b or self.overwrite then
self.buffer[(sy * self.width + sx) * 3 + 1] = b
end
if t or self.overwrite then
self.buffer[(sy * self.width + sx) * 3 + 2] = t
end
if c or self.overwrite then
self.buffer[(sy * self.width + sx) * 3 + 3] = c
end
end
end
end
end
function surf:drawArc(x, y, width, height, fromangle, toangle, b, t, c)
if fromangle > toangle then
local temp = fromangle
fromangle = toangle
temp = toangle
end
fromangle = math_floor(fromangle / math.pi / 2 * _eprc + 0.5)
toangle = math_floor(toangle / math.pi / 2 * _eprc + 0.5) - 1
for j = fromangle, toangle do
local i = j % _eprc
self:drawLine(math_floor(x + _ecos[i + 1] * (width - 1) + 0.5), math_floor(y + _esin[i + 1] * (height - 1) + 0.5), math_floor(x + _ecos[(i + 1) % _eprc + 1] * (width - 1) + 0.5), math_floor(y + _esin[(i + 1) % _eprc + 1] * (height - 1) + 0.5), b, t, c)
end
end
function surf:fillArc(x, y, width, height, fromangle, toangle, b, t, c)
x, y = x + self.ox, y + self.oy
if fromangle > toangle then
local temp = fromangle
fromangle = toangle
temp = toangle
end
local diff = toangle - fromangle
fromangle = fromangle % (math.pi * 2)
local fx, fy, sx, sy, dir
for j = 0, height - 1 do
for i = 0, width - 1 do
fx, fy = (i + 0.5) / width * 2 - 1, (j + 0.5) / height * 2 - 1
sx, sy = i + x, j + y
dir = math_atan2(-fy, fx) % (math.pi * 2)
if fx ^ 2 + fy ^ 2 <= 1 and ((dir >= fromangle and dir - fromangle <= diff) or (dir <= (fromangle + diff) % (math.pi * 2))) and sx >= self.cx and sx < self.cx + self.cwidth and sy >= self.cy and sy < self.cy + self.cheight then
if b or self.overwrite then
self.buffer[(sy * self.width + sx) * 3 + 1] = b
end
if t or self.overwrite then
self.buffer[(sy * self.width + sx) * 3 + 2] = t
end
if c or self.overwrite then
self.buffer[(sy * self.width + sx) * 3 + 3] = c
end
end
end
end
end
function surf:drawSurface(surf2, x, y, width, height, sx, sy, swidth, sheight)
x, y, width, height, sx, sy, swidth, sheight = x + self.ox, y + self.oy, width or surf2.width, height or surf2.height, sx or 0, sy or 0, swidth or surf2.width, sheight or surf2.height
if width == swidth and height == sheight then
local nx, ny
nx, ny, width, height = clipRect(x, y, width, height, self.cx, self.cy, self.cwidth, self.cheight)
swidth, sheight = width, height
if nx > x then
sx = sx + nx - x
x = nx
end
if ny > y then
sy = sy + ny - y
y = ny
end
nx, ny, swidth, sheight = clipRect(sx, sy, swidth, sheight, 0, 0, surf2.width, surf2.height)
width, height = swidth, sheight
if nx > sx then
x = x + nx - sx
sx = nx
end
if ny > sy then
y = y + ny - sy
sy = ny
end
local b, t, c
for j = 0, height - 1 do
for i = 0, width - 1 do
b = surf2.buffer[((j + sy) * surf2.width + i + sx) * 3 + 1]
t = surf2.buffer[((j + sy) * surf2.width + i + sx) * 3 + 2]
c = surf2.buffer[((j + sy) * surf2.width + i + sx) * 3 + 3]
if b or self.overwrite then
self.buffer[((j + y) * self.width + i + x) * 3 + 1] = b
end
if t or self.overwrite then
self.buffer[((j + y) * self.width + i + x) * 3 + 2] = t
end
if c or self.overwrite then
self.buffer[((j + y) * self.width + i + x) * 3 + 3] = c
end
end
end
else
local hmirror, vmirror = false, false
if width < 0 then
hmirror = true
x = x + width
end
if height < 0 then
vmirror = true
y = y + height
end
if swidth < 0 then
hmirror = not hmirror
sx = sx + swidth
end
if sheight < 0 then
vmirror = not vmirror
sy = sy + sheight
end
width, height, swidth, sheight = math.abs(width), math.abs(height), math.abs(swidth), math.abs(sheight)
local xscale, yscale, px, py, ssx, ssy, b, t, c = swidth / width, sheight / height
for j = 0, height - 1 do
for i = 0, width - 1 do
px, py = math_floor((i + 0.5) * xscale), math_floor((j + 0.5) * yscale)
if hmirror then
ssx = x + width - i - 1
else
ssx = i + x
end
if vmirror then
ssy = y + height - j - 1
else
ssy = j + y
end
if ssx >= self.cx and ssx < self.cx + self.cwidth and ssy >= self.cy and ssy < self.cy + self.cheight and px >= 0 and px < surf2.width and py >= 0 and py < surf2.height then
b = surf2.buffer[(py * surf2.width + px) * 3 + 1]
t = surf2.buffer[(py * surf2.width + px) * 3 + 2]
c = surf2.buffer[(py * surf2.width + px) * 3 + 3]
if b or self.overwrite then
self.buffer[(ssy * self.width + ssx) * 3 + 1] = b
end
if t or self.overwrite then
self.buffer[(ssy * self.width + ssx) * 3 + 2] = t
end
if c or self.overwrite then
self.buffer[(ssy * self.width + ssx) * 3 + 3] = c
end
end
end
end
end
end
function surf:drawSurfaceRotated(surf2, x, y, ox, oy, angle)
local sin, cos, sx, sy, px, py = math.sin(angle), math.cos(angle)
for j = math.floor(-surf2.height * 0.75), math.ceil(surf2.height * 0.75) do
for i = math.floor(-surf2.width * 0.75), math.ceil(surf2.width * 0.75) do
sx, sy, px, py = x + i, y + j, math_floor(cos * (i + 0.5) - sin * (j + 0.5) + ox), math_floor(sin * (i + 0.5) + cos * (j + 0.5) + oy)
if sx >= self.cx and sx < self.cx + self.cwidth and sy >= self.cy and sy < self.cy + self.cheight and px >= 0 and px < surf2.width and py >= 0 and py < surf2.height then
b = surf2.buffer[(py * surf2.width + px) * 3 + 1]
t = surf2.buffer[(py * surf2.width + px) * 3 + 2]
c = surf2.buffer[(py * surf2.width + px) * 3 + 3]
if b or self.overwrite then
self.buffer[(sy * self.width + sx) * 3 + 1] = b
end
if t or self.overwrite then
self.buffer[(sy * self.width + sx) * 3 + 2] = t
end
if c or self.overwrite then
self.buffer[(sy * self.width + sx) * 3 + 3] = c
end
end
end
end
end
function surf:drawSurfacesInterlaced(surfs, x, y, step)
x, y, step = x + self.ox, y + self.oy, step or 0
local width, height = surfs[1].width, surfs[1].height
for i = 2, #surfs do
if surfs[i].width ~= width or surfs[i].height ~= height then
error("surfaces should be the same size")
end
end
local sx, sy, swidth, sheight, index, b, t, c = clipRect(x, y, width, height, self.cx, self.cy, self.cwidth, self.cheight)
for j = sy, sy + sheight - 1 do
for i = sx, sx + swidth - 1 do
index = (i + j + step) % #surfs + 1
b = surfs[index].buffer[((j - sy) * surfs[index].width + i - sx) * 3 + 1]
t = surfs[index].buffer[((j - sy) * surfs[index].width + i - sx) * 3 + 2]
c = surfs[index].buffer[((j - sy) * surfs[index].width + i - sx) * 3 + 3]
if b or self.overwrite then
self.buffer[(j * self.width + i) * 3 + 1] = b
end
if t or self.overwrite then
self.buffer[(j * self.width + i) * 3 + 2] = t
end
if c or self.overwrite then
self.buffer[(j * self.width + i) * 3 + 3] = c
end
end
end
end
function surf:drawSurfaceSmall(surf2, x, y)
x, y = x + self.ox, y + self.oy
if surf2.width % 2 ~= 0 or surf2.height % 3 ~= 0 then
error("surface width must be a multiple of 2 and surface height a multiple of 3")
end
local sub, char, c1, c2, c3, c4, c5, c6 = 32768
for j = 0, surf2.height / 3 - 1 do
for i = 0, surf2.width / 2 - 1 do
if i + x >= self.cx and i + x < self.cx + self.cwidth and j + y >= self.cy and j + y < self.cy + self.cheight then
char, c1, c2, c3, c4, c5, c6 = 0,
surf2.buffer[((j * 3) * surf2.width + i * 2) * 3 + 1],
surf2.buffer[((j * 3) * surf2.width + i * 2 + 1) * 3 + 1],
surf2.buffer[((j * 3 + 1) * surf2.width + i * 2) * 3 + 1],
surf2.buffer[((j * 3 + 1) * surf2.width + i * 2 + 1) * 3 + 1],
surf2.buffer[((j * 3 + 2) * surf2.width + i * 2) * 3 + 1],
surf2.buffer[((j * 3 + 2) * surf2.width + i * 2 + 1) * 3 + 1]
if c1 ~= c6 then
sub = c1
char = 1
end
if c2 ~= c6 then
sub = c2
char = char + 2
end
if c3 ~= c6 then
sub = c3
char = char + 4
end
if c4 ~= c6 then
sub = c4
char = char + 8
end
if c5 ~= c6 then
sub = c5
char = char + 16
end
self.buffer[((j + y) * self.width + i + x) * 3 + 1] = c6
self.buffer[((j + y) * self.width + i + x) * 3 + 2] = sub
self.buffer[((j + y) * self.width + i + x) * 3 + 3] = _chars[128 + char]
end
end
end
end
function surf:flip(horizontal, vertical)
local ox, oy, nx, ny, tb, tt, tc
if horizontal then
for i = 0, math.ceil(self.cwidth / 2) - 1 do
for j = 0, self.cheight - 1 do
ox, oy, nx, ny = i + self.cx, j + self.cy, self.cx + self.cwidth - i - 1, j + self.cy
tb = self.buffer[(oy * self.width + ox) * 3 + 1]
tt = self.buffer[(oy * self.width + ox) * 3 + 2]
tc = self.buffer[(oy * self.width + ox) * 3 + 3]
self.buffer[(oy * self.width + ox) * 3 + 1] = self.buffer[(ny * self.width + nx) * 3 + 1]
self.buffer[(oy * self.width + ox) * 3 + 2] = self.buffer[(ny * self.width + nx) * 3 + 2]
self.buffer[(oy * self.width + ox) * 3 + 3] = self.buffer[(ny * self.width + nx) * 3 + 3]
self.buffer[(ny * self.width + nx) * 3 + 1] = tb
self.buffer[(ny * self.width + nx) * 3 + 2] = tt
self.buffer[(ny * self.width + nx) * 3 + 3] = tc
end
end
end
if vertical then
for j = 0, math.ceil(self.cheight / 2) - 1 do
for i = 0, self.cwidth - 1 do
ox, oy, nx, ny = i + self.cx, j + self.cy, i + self.cx, self.cy + self.cheight - j - 1
tb = self.buffer[(oy * self.width + ox) * 3 + 1]
tt = self.buffer[(oy * self.width + ox) * 3 + 2]
tc = self.buffer[(oy * self.width + ox) * 3 + 3]
self.buffer[(oy * self.width + ox) * 3 + 1] = self.buffer[(ny * self.width + nx) * 3 + 1]
self.buffer[(oy * self.width + ox) * 3 + 2] = self.buffer[(ny * self.width + nx) * 3 + 2]
self.buffer[(oy * self.width + ox) * 3 + 3] = self.buffer[(ny * self.width + nx) * 3 + 3]
self.buffer[(ny * self.width + nx) * 3 + 1] = tb
self.buffer[(ny * self.width + nx) * 3 + 2] = tt
self.buffer[(ny * self.width + nx) * 3 + 3] = tc
end
end
end
end
function surf:shift(x, y, b, t, c)
local hdir, vdir = x < 0, y < 0
local xstart, xend = self.cx, self.cx + self.cwidth - 1
local ystart, yend = self.cy, self.cy + self.cheight - 1
local nx, ny
for j = vdir and ystart or yend, vdir and yend or ystart, vdir and 1 or -1 do
for i = hdir and xstart or xend, hdir and xend or xstart, hdir and 1 or -1 do
nx, ny = i - x, j - y
if nx >= 0 and nx < self.width and ny >= 0 and ny < self.height then
self.buffer[(j * self.width + i) * 3 + 1] = self.buffer[(ny * self.width + nx) * 3 + 1]
self.buffer[(j * self.width + i) * 3 + 2] = self.buffer[(ny * self.width + nx) * 3 + 2]
self.buffer[(j * self.width + i) * 3 + 3] = self.buffer[(ny * self.width + nx) * 3 + 3]
else
self.buffer[(j * self.width + i) * 3 + 1] = b
self.buffer[(j * self.width + i) * 3 + 2] = t
self.buffer[(j * self.width + i) * 3 + 3] = c
end
end
end
end
function surf:map(colors)
local c
for j = self.cy, self.cy + self.cheight - 1 do
for i = self.cx, self.cx + self.cwidth - 1 do
c = colors[self.buffer[(j * self.width + i) * 3 + 1]]
if c or self.overwrite then
self.buffer[(j * self.width + i) * 3 + 1] = c
end
end
end
end
surface.palette = { }
surface.palette.cc = {[1]="F0F0F0",[2]="F2B233",[4]="E57FD8",[8]="99B2F2",[16]="DEDE6C",[32]="7FCC19",[64]="F2B2CC",[128]="4C4C4C",[256]="999999",[512]="4C99B2",[1024]="B266E5",[2048]="3366CC",[4096]="7F664C",[8192]="57A64E",[16384]="CC4C4C",[32768]="191919"}
surface.palette.riko4 = {"181818","1D2B52","7E2553","008651","AB5136","5F564F","7D7F82","FF004C","FFA300","FFF023","00E755","29ADFF","82769C","FF77A9","FECCA9","ECECEC"}
surface.palette.redirection = {[0]="040404",[1]="FFFFFF"}
local function setPalette(palette)
if palette == _palette then return end
_palette = palette
_rgbpal, _palr, _palg, _palb = { }, { }, { }, { }
local indices = { }
for k, v in pairs(_palette) do
if type(v) == "string" then
_palr[k] = tonumber(v:sub(1, 2), 16) / 255
_palg[k] = tonumber(v:sub(3, 4), 16) / 255
_palb[k] = tonumber(v:sub(5, 6), 16) / 255
elseif type(v) == "number" then
_palr[k] = math.floor(v / 65536) / 255
_palg[k] = (math.floor(v / 256) % 256) / 255
_palb[k] = (v % 256) / 255
end
indices[#indices + 1] = k
end
local pr, pg, pb, dist, d, id
for i = 0, _steps - 1 do
for j = 0, _steps - 1 do
for k = 0, _steps - 1 do
pr = (i + 0.5) / _steps
pg = (j + 0.5) / _steps
pb = (k + 0.5) / _steps
dist = 1e10
for l = 1, #indices do
d = (pr - _palr[indices[l]]) ^ 2 + (pg - _palg[indices[l]]) ^ 2 + (pb - _palb[indices[l]]) ^ 2
if d < dist then
dist = d
id = l
end
end
_rgbpal[i * _steps * _steps + j * _steps + k + 1] = indices[id]
end
end
end
end
function surf:toRGB(palette)
setPalette(palette)
local c
for j = 0, self.height - 1 do
for i = 0, self.width - 1 do
c = self.buffer[(j * self.width + i) * 3 + 1]
self.buffer[(j * self.width + i) * 3 + 1] = _palr[c]
self.buffer[(j * self.width + i) * 3 + 2] = _palg[c]
self.buffer[(j * self.width + i) * 3 + 3] = _palb[c]
end
end
end
function surf:toPalette(palette, dither)
setPalette(palette)
local scale, r, g, b, nr, ng, nb, c, dr, dg, db = _steps - 1
for j = 0, self.height - 1 do
for i = 0, self.width - 1 do
r = self.buffer[(j * self.width + i) * 3 + 1]
g = self.buffer[(j * self.width + i) * 3 + 2]
b = self.buffer[(j * self.width + i) * 3 + 3]
r = (r > 1) and 1 or r
r = (r < 0) and 0 or r
g = (g > 1) and 1 or g
g = (g < 0) and 0 or g
b = (b > 1) and 1 or b
b = (b < 0) and 0 or b
nr = (r == 1) and scale or math_floor(r * _steps)
ng = (g == 1) and scale or math_floor(g * _steps)
nb = (b == 1) and scale or math_floor(b * _steps)
c = _rgbpal[nr * _steps * _steps + ng * _steps + nb + 1]
if dither then
dr = (r - _palr[c]) / 16
dg = (g - _palg[c]) / 16
db = (b - _palb[c]) / 16
if i < self.width - 1 then
self.buffer[(j * self.width + i + 1) * 3 + 1] = self.buffer[(j * self.width + i + 1) * 3 + 1] + dr * 7
self.buffer[(j * self.width + i + 1) * 3 + 2] = self.buffer[(j * self.width + i + 1) * 3 + 2] + dg * 7
self.buffer[(j * self.width + i + 1) * 3 + 3] = self.buffer[(j * self.width + i + 1) * 3 + 3] + db * 7
end
if j < self.height - 1 then
if i > 0 then
self.buffer[((j + 1) * self.width + i - 1) * 3 + 1] = self.buffer[((j + 1) * self.width + i - 1) * 3 + 1] + dr * 3
self.buffer[((j + 1) * self.width + i - 1) * 3 + 2] = self.buffer[((j + 1) * self.width + i - 1) * 3 + 2] + dg * 3
self.buffer[((j + 1) * self.width + i - 1) * 3 + 3] = self.buffer[((j + 1) * self.width + i - 1) * 3 + 3] + db * 3
end
self.buffer[((j + 1) * self.width + i) * 3 + 1] = self.buffer[((j + 1) * self.width + i) * 3 + 1] + dr * 5
self.buffer[((j + 1) * self.width + i) * 3 + 2] = self.buffer[((j + 1) * self.width + i) * 3 + 2] + dg * 5
self.buffer[((j + 1) * self.width + i) * 3 + 3] = self.buffer[((j + 1) * self.width + i) * 3 + 3] + db * 5
if i < self.width - 1 then
self.buffer[((j + 1) * self.width + i + 1) * 3 + 1] = self.buffer[((j + 1) * self.width + i + 1) * 3 + 1] + dr * 1
self.buffer[((j + 1) * self.width + i + 1) * 3 + 2] = self.buffer[((j + 1) * self.width + i + 1) * 3 + 2] + dg * 1
self.buffer[((j + 1) * self.width + i + 1) * 3 + 3] = self.buffer[((j + 1) * self.width + i + 1) * 3 + 3] + db * 1
end
end
end
self.buffer[(j * self.width + i) * 3 + 1] = c
self.buffer[(j * self.width + i) * 3 + 2] = nil
self.buffer[(j * self.width + i) * 3 + 3] = nil
end
end
end
function surface.loadFont(surf)
local font = {width = surf.width, height = surf.height - 1}
font.buffer = { }
font.indices = {0}
font.widths = { }
local startc, hitc, curc = surf.buffer[((surf.height - 1) * surf.width) * 3 + 1]
for i = 0, surf.width - 1 do
curc = surf.buffer[((surf.height - 1) * surf.width + i) * 3 + 1]
if curc ~= startc then
hitc = curc
break
end
end
for j = 0, surf.height - 2 do
for i = 0, surf.width - 1 do
font.buffer[j * font.width + i + 1] = surf.buffer[(j * surf.width + i) * 3 + 1] == hitc
end
end
local curchar = 1
for i = 0, surf.width - 1 do
if surf.buffer[((surf.height - 1) * surf.width + i) * 3 + 1] == hitc then
font.widths[curchar] = i - font.indices[curchar]
curchar = curchar + 1
font.indices[curchar] = i + 1
end
end
font.widths[curchar] = font.width - font.indices[curchar]
return font
end
function surface.getTextSize(str, font)
local cx, cy, maxx = 0, 0, 0
local ox, char = cx
for i = 1, #str do
char = str:byte(i) - 31
if char + 31 == 10 then -- newline
cx = ox
cy = cy + font.height + 1
elseif font.indices[char] then
cx = cx + font.widths[char] + 1
else
cx = cx + font.widths[1]
end
if cx > maxx then
maxx = cx
end
end
return maxx - 1, cy + font.height
end
function surf:drawText(str, font, x, y, b, t, c)
local cx, cy = x + self.ox, y + self.oy
local ox, char, idx = cx
for i = 1, #str do
char = str:byte(i) - 31
if char + 31 == 10 then -- newline
cx = ox
cy = cy + font.height + 1
elseif font.indices[char] then
for i = 0, font.widths[char] - 1 do
for j = 0, font.height - 1 do
x, y = cx + i, cy + j
if font.buffer[j * font.width + i + font.indices[char] + 1] then
if x >= self.cx and x < self.cx + self.cwidth and y >= self.cy and y < self.cy + self.cheight then
idx = (y * self.width + x) * 3
if b or self.overwrite then
self.buffer[idx + 1] = b
end
if t or self.overwrite then
self.buffer[idx + 2] = t
end
if c or self.overwrite then
self.buffer[idx + 3] = c
end
end
end
end
end
cx = cx + font.widths[char] + 1
else
cx = cx + font.widths[1]
end
end
end
local smap = { }
surface.smap = smap
function surface.loadSpriteMap(surf, spwidth, spheight, sprites)
if surf.width % spwidth ~= 0 or surf.height % spheight ~= 0 then
error("sprite width/height does not match smap width/height")
end
local smap = setmetatable({ }, {__index = surface.smap})
smap.surf = surf
smap.spwidth = spwidth
smap.spheight = spheight
smap.sprites = sprites or ((surf.width / spwidth) * (surf.height / spheight))
smap.perline = surf.width / spwidth
return smap
end
function smap:pos(index, scale)
if index < 0 or index >= self.sprites then
error("sprite index out of bounds")
end
return (index % self.perline) * self.spwidth, math.floor(index / self.perline) * self.spheight
end
function smap:sprite(index, x, y, width, height)
local sx, sy = self:pos(index)
return self.surf, x, y, width or self.spwidth, height or self.spheight, sx, sy, self.spwidth, self.spheight
end
end return surface
end)()
local fontData = (function()
return [[
0 0 0 0 0 0000 00 0 00 0 0 0 0 0 0 00 0 00 00 0 0000 000 0000 00 00 00 000 00 000 00 000 0000 0000 000 0 0 000 000 0 0 0 00 0 0 0 00 000 00 000 000 00000 0 0 0 0 0 0 0 0 0 0 0000 000 0 000 0 0 0 0 00 0 0 0 0 00 0 00 0 00
0 0 0 00000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0 00 0 0 0 0 0 0 0 0 0 00 000 00 0 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 000 000 000 00 0 00 0 0 00 0 00 0 000 00 000 000 0 00 000 000 0 0 0 0 0 0 0 0 0 0 0000 0 0 0 0 0
0 0 0 000 0 00 0 0 0 0 0 000 000 0 0 0 0 0 0 0 0 000 000 0 00 000 0 0 0 00 0 0000 000 0 0 0 000 000 0 00 0000 0 0 00 0 0 0 0 0 00 0 0 000 0 0 000 00 0 0 0 0 0 0 0 0 00 000 00 0 0 0 0 0 0 0 0 0 0 0 0 000 0 0 000 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 00 00 0 0 0 0 0 0 0 0 00 0 0 0 0 0 0 0 0
00000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0000 0 0 0 0 0 0 0 0 0 00 000 00 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 000 0 000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0000 0 0 0 00 00 0 0 0 0
0 0 0 0000 0 00 00 0 0 0 0 0 0 00 000 0000 00 0 000 00 0 00 000 0 0 00 0 0 000 00 000 0000 0 00 0 0 000 00 0 0 0000 0 0 0 0 00 0 0 0 0 0 000 0 00 00 0 0 0 0 000 0000 000 0 000 0000 000 000 000 000 000 0 0 0 0 0 0 0 0 0 0 0 0 0 00 000 000 0 000 0 000 00 0 0 0 0 0 0000 00 0 00
0 000 00 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]] end)()
local font = surface.loadFont(surface.load(fontData, true))
local wapi = (function()
local jua = nil
local idPatt = "#R%d+"
if not ((socket and socket.websocket) or http.websocketAsync) then
error("You do not have CC:Tweaked/CCTweaks installed or you are not on the latest version.")
end
local newws = socket and socket.websocket or http.websocketAsync
local async
if socket and socket.websocket then
async = false
else
async = true
end
callbackRegistry = {}
wsRegistry = {}
local function gfind(str, patt)
local t = {}
for found in str:gmatch(patt) do
table.insert(t, found)
end
if #t > 0 then
return t
else
return nil
end
end
local function findID(url)
local found = gfind(url, idPatt)
return tonumber(found[#found]:sub(found[#found]:find("%d+")))
end
local function newID()
return #callbackRegistry + 1
end
local function trimID(url)
local found = gfind(url, idPatt)
local s, e = url:find(found[#found])
return url:sub(1, s-1)
end
function open(callback, url, headers)
local id
if async then
id = newID()
end
local newUrl
if async then
newUrl = url .. "#R" .. id
newws(newUrl, headers)
else
if headers then
error("Websocket headers not supported under CCTweaks")
end
local ws = newws(url)
ws.send = ws.write
id = ws.id()
wsRegistry[id] = ws
end
callbackRegistry[id] = callback
return id
end
function init(jua)
jua = jua
if async then
jua.on("websocket_success", function(event, url, handle)
local id = findID(url)
if type(callbackRegistry[id]) == "table" and callbackRegistry[id].success then
callbackRegistry[id].success(findID(url), handle)
end
end)
jua.on("websocket_failure", function(event, url)
local id = findID(url)
if type(callbackRegistry[id]) == "table" and callbackRegistry[id].failure then
callbackRegistry[id].failure(findID(url))
end
table.remove(callbackRegistry, id)
end)
jua.on("websocket_message", function(event, url, data)
local id = findID(url)
if type(callbackRegistry[id]) == "table" and callbackRegistry[id].message then
callbackRegistry[id].message(findID(url), data)
end
end)
jua.on("websocket_closed", function(event, url)
local id = findID(url)
if type(callbackRegistry[id]) == "table" and callbackRegistry[id].closed then
callbackRegistry[id].closed(findID(url))
end
table.remove(callbackRegistry, id)
end)
else
jua.on("socket_connect", function(event, id)
if type(callbackRegistry[id]) == "table" and callbackRegistry[id].success then
callbackRegistry[id].success(id, wsRegistry[id])
end
end)
jua.on("socket_error", function(event, id, msg)
if type(callbackRegistry[id]) == "table" and callbackRegistry[id].failure then
callbackRegistry[id].failure(id, msg)
end
table.remove(callbackRegistry, id)
end)
jua.on("socket_message", function(event, id)
if type(callbackRegistry[id]) == "table" and callbackRegistry[id].message then
local data = wsRegistry[id].read()
callbackRegistry[id].message(id, data)
end
end)
jua.on("socket_closed", function(event, id)
if type(callbackRegistry[id]) == "table" and callbackRegistry[id].closed then
callbackRegistry[id].closed(id)
end
table.remove(callbackRegistry, id)
end)
end
end
return {
open = open,
init = init
}
end)()
local rapi = (function()
local jua = nil
local idPatt = "#R%d+"
callbackRegistry = {}
local function gfind(str, patt)
local t = {}
for found in str:gmatch(patt) do
table.insert(t, found)
end
if #t > 0 then
return t
else
return nil
end
end
local function findID(url)
local found = gfind(url, idPatt)
return tonumber(found[#found]:sub(found[#found]:find("%d+")))
end
local function newID()
return #callbackRegistry + 1
end
local function trimID(url)
local found = gfind(url, idPatt)
local s, e = url:find(found[#found])
return url:sub(1, s-1)
end
function request(callback, url, headers, postData)
local id = newID()
local newUrl = url .. "#R" .. id
http.request(newUrl, postData, headers)
callbackRegistry[id] = callback
end
function init(jua)
jua = jua
jua.on("http_success", function(event, url, handle)
local id = findID(url)
if callbackRegistry[id] then
if type(callbackRegistry[id]) == "table" and callbackRegistry[id].success then
callbackRegistry[id].success(true, trimID(url), handle)
else
callbackRegistry[id](true, trimID(url), handle)
end
table.remove(callbackRegistry, id)
end
end)
jua.on("http_failure", function(event, url, handle)
local id = findID(url)
if callbackRegistry[id] then
if type(callbackRegistry[id]) == "table" and callbackRegistry[id].failure then
callbackRegistry[id].failure(false, trimID(url), handle)
else
callbackRegistry[id](false, trimID(url), handle)
end
table.remove(callbackRegistry, id)
end
end)
end
return {
request = request,
init = init
}
end)()
local kapi = (function()
local w
local r
local jua
local json
local await
local endpoint = "krist.ceriat.net"
local wsEndpoint = "ws://"..endpoint
local httpEndpoint = "http://"..endpoint
local function asserttype(var, name, vartype, optional)
if not (type(var) == vartype or optional and type(var) == "nil") then
error(name..": expected "..vartype.." got "..type(var), 3)
end
end
function init(juai, jsoni, wi, ri)
asserttype(juai, "jua", "table")
asserttype(jsoni, "json", "table")
asserttype(wi, "w", "table", true)
asserttype(ri, "r", "table")
jua = juai
await = juai.await
json = jsoni
w = wi
r = ri
end
local function prints(...)
local objs = {...}
for i, obj in ipairs(objs) do
print(textutils.serialize(obj))
end
end
local function url(call)
return httpEndpoint..call
end
local function api_request(cb, api, data)
local success, url, handle = await(r.request, url(api), {["Content-Type"]="application/json"}, data and json.encode(data))
if success then
cb(success, json.decode(handle.readAll()))
handle.close()
else
cb(success)
end
end
local function authorize_websocket(cb, privatekey)
asserttype(cb, "callback", "function")
asserttype(privatekey, "privatekey", "string", true)
api_request(function(success, data)
cb(success, data and data.url:gsub("wss:", "ws:"))
end, "/ws/start", {
privatekey = privatekey
})
end
function address(cb, address)
asserttype(cb, "callback", "function")
asserttype(address, "address", "string")
api_request(function(success, data)
data.address.address = address
cb(success, data.address)
end, "/addresses/"..address)
end
function addressTransactions(cb, address, limit, offset)
asserttype(cb, "callback", "function")
asserttype(address, "address", "string")
asserttype(limit, "limit", "number", true)
asserttype(offset, "offset", "number", true)
api_request(function(success, data)
cb(success, data.transactions)
end, "/addresses/"..address.."/transactions?limit="..(limit or 50).."&offset="..(offset or 0))
end
function addressNames(cb, address)
asserttype(cb, "callback", "function")
asserttype(address, "address", "string")
api_request(function(success, data)
cb(success, data.names)
end, "/addresses/"..address.."/names")
end
function addresses(cb, limit, offset)
asserttype(cb, "callback", "function")
asserttype(limit, "limit", "number", true)
asserttype(offset, "offset", "number", true)
api_request(function(success, data)
cb(success, data.addresses)
end, "/addresses?limit="..(limit or 50).."&offset="..(offset or 0))
end
function rich(cb, limit, offset)
asserttype(cb, "callback", "function")
asserttype(limit, "limit", "number", true)
asserttype(offset, "offset", "number", true)
api_request(function(success, data)
cb(success, data.addresses)
end, "/addresses/rich?limit="..(limit or 50).."&offset="..(offset or 0))
end
function transactions(cb, limit, offset)
asserttype(cb, "callback", "function")
asserttype(limit, "limit", "number", true)
asserttype(offset, "offset", "number", true)
api_request(function(success, data)
cb(success, data.transactions)
end, "/transactions?limit="..(limit or 50).."&offset="..(offset or 0))
end
function latestTransactions(cb, limit, offset)
asserttype(cb, "callback", "function")
asserttype(limit, "limit", "number", true)
asserttype(offset, "offset", "number", true)
api_request(function(success, data)
cb(success, data.transactions)
end, "/transactions/latest?limit="..(limit or 50).."&offset="..(offset or 0))
end
function transaction(cb, txid)
asserttype(cb, "callback", "function")
asserttype(txid, "txid", "number")
api_request(function(success, data)
cb(success, data.transaction)
end, "/transactions/"..txid)
end
function makeTransaction(cb, privatekey, to, amount, metadata)
asserttype(cb, "callback", "function")
asserttype(privatekey, "privatekey", "string")
asserttype(to, "to", "string")
asserttype(amount, "amount", "number")
asserttype(metadata, "metadata", "string", true)
api_request(function(success, data)
cb(success, data.transaction)
end, "/transactions", {
privatekey = privatekey,
to = to,
amount = amount,
metadata = metadata
})
end
local wsEventNameLookup = {
blocks = "block",
ownBlocks = "block",
transactions = "transaction",
ownTransactions = "transaction",
names = "name",
ownNames = "name",
ownWebhooks = "webhook",
motd = "motd"
}
local wsEvents = {}
local wsReqID = 0
local wsReqRegistry = {}
local wsEvtRegistry = {}
local wsHandleRegistry = {}
local function newWsID()
local id = wsReqID
wsReqID = wsReqID + 1
return id
end
local function registerEvent(id, event, callback)
if wsEvtRegistry[id] == nil then
wsEvtRegistry[id] = {}
end
if wsEvtRegistry[id][event] == nil then
wsEvtRegistry[id][event] = {}
end
table.insert(wsEvtRegistry[id][event], callback)
end
local function registerRequest(id, reqid, callback)
if wsReqRegistry[id] == nil then
wsReqRegistry[id] = {}
end
wsReqRegistry[id][reqid] = callback
end
local function discoverEvents(id, event)
local evs = {}
for k,v in pairs(wsEvtRegistry[id]) do
if k == event or string.match(k, event) or event == "*" then
for i,v2 in ipairs(v) do
table.insert(evs, v2)
end
end
end
return evs
end
wsEvents.success = function(id, handle)
-- fire success event
wsHandleRegistry[id] = handle
if wsEvtRegistry[id] then
local evs = discoverEvents(id, "success")
for i, v in ipairs(evs) do
v(id, handle)
end
end
end
wsEvents.failure = function(id)
-- fire failure event
if wsEvtRegistry[id] then
local evs = discoverEvents(id, "failure")
for i, v in ipairs(evs) do
v(id)
end
end
end
wsEvents.message = function(id, data)
local data = json.decode(data)
--print("msg:"..tostring(data.ok)..":"..tostring(data.type)..":"..tostring(data.id))
--prints(data)
-- handle events and responses
if wsReqRegistry[id] and wsReqRegistry[id][tonumber(data.id)] then
wsReqRegistry[id][tonumber(data.id)](data)
elseif wsEvtRegistry[id] then
local evs = discoverEvents(id, data.type)
for i, v in ipairs(evs) do
v(data)
end
if data.event then
local evs = discoverEvents(id, data.event)
for i, v in ipairs(evs) do
v(data)
end
end
local evs2 = discoverEvents(id, "message")
for i, v in ipairs(evs2) do
v(id, data)
end
end
end
wsEvents.closed = function(id)
-- fire closed event
if wsEvtRegistry[id] then
local evs = discoverEvents(id, "closed")
for i, v in ipairs(evs) do
v(id)
end
end
end
local function wsRequest(cb, id, type, data)
local reqID = newWsID()
registerRequest(id, reqID, function(data)
cb(data)
end)
data.id = tostring(reqID)
data.type = type
wsHandleRegistry[id].send(json.encode(data))
end
local function barebonesMixinHandle(id, handle)
handle.on = function(event, cb)
registerEvent(id, event, cb)
end
return handle
end
local function mixinHandle(id, handle)
handle.subscribe = function(cb, event, eventcb)
local data = await(wsRequest, id, "subscribe", {
event = event
})
registerEvent(id, wsEventNameLookup[event], eventcb)
cb(data.ok, data)
end
return barebonesMixinHandle(id, handle)
end
function connect(cb, privatekey, preconnect)
asserttype(cb, "callback", "function")
asserttype(privatekey, "privatekey", "string", true)
asserttype(preconnect, "preconnect", "function", true)
local url
if privatekey then
local success, auth = await(authorize_websocket, privatekey)
url = success and auth or wsEndpoint
end
local id = w.open(wsEvents, url)
if preconnect then
preconnect(id, barebonesMixinHandle(id, {}))
end
registerEvent(id, "success", function(id, handle)
cb(true, mixinHandle(id, handle))
end)
registerEvent(id, "failure", function(id)
cb(false)
end)
end
local domainMatch = "^([%l%d%-%_]*)@?([%l%d%-]+).kst$"
local commonMetaMatch = "^(.+)=(.+)$"
function parseMeta(meta)
asserttype(meta, "meta", "string")
local tbl = {meta={}}
for m in meta:gmatch("[^;]+") do
if m:match(domainMatch) then
-- print("Matched domain")
local p1, p2 = m:match("([%l%d-_]*)@"), m:match("@?([%l%d-]+).kst")
tbl.name = p1
tbl.domain = p2
elseif m:match(commonMetaMatch) then
-- print("Matched common meta")
local p1, p2 = m:match(commonMetaMatch)
tbl.meta[p1] = p2
else
-- print("Unmatched standard meta")
table.insert(tbl.meta, m)
end
-- print(m)
end
-- print(textutils.serialize(tbl))
return tbl
end
local g = string.gsub
sha256 = loadstring(g(g(g(g(g(g(g(g('Sa=XbandSb=XbxWSc=XlshiftSd=unpackSe=2^32SYf(g,h)Si=g/2^hSj=i%1Ui-j+j*eVSYk(l,m)Sn=l/2^mUn-n%1VSo={0x6a09e667Tbb67ae85T3c6ef372Ta54ff53aT510e527fT9b05688cT1f83d9abT5be0cd19}Sp={0x428a2f98T71374491Tb5c0fbcfTe9b5dba5T3956c25bT59f111f1T923f82a4Tab1c5ed5Td807aa98T12835b01T243185beT550c7dc3T72be5d74T80deb1feT9bdc06a7Tc19bf174Te49b69c1Tefbe4786T0fc19dc6T240ca1ccT2de92c6fT4a7484aaT5cb0a9dcT76f988daT983e5152Ta831c66dTb00327c8Tbf597fc7Tc6e00bf3Td5a79147T06ca6351T14292967T27b70a85T2e1b2138T4d2c6dfcT53380d13T650a7354T766a0abbT81c2c92eT92722c85Ta2bfe8a1Ta81a664bTc24b8b70Tc76c51a3Td192e819Td6990624Tf40e3585T106aa070T19a4c116T1e376c08T2748774cT34b0bcb5T391c0cb3T4ed8aa4aT5b9cca4fT682e6ff3T748f82eeT78a5636fT84c87814T8cc70208T90befffaTa4506cebTbef9a3f7Tc67178f2}SYq(r,q)if e-1-r[1]<q then r[2]=r[2]+1;r[1]=q-(e-1-r[1])-1 else r[1]=r[1]+qVUrVSYs(t)Su=#t;t[#t+1]=0x80;while#t%64~=56Zt[#t+1]=0VSv=q({0,0},u*8)fWw=2,1,-1Zt[#t+1]=a(k(a(v[w]TFF000000),24)TFF)t[#t+1]=a(k(a(v[w]TFF0000),16)TFF)t[#t+1]=a(k(a(v[w]TFF00),8)TFF)t[#t+1]=a(v[w]TFF)VUtVSYx(y,w)Uc(y[w]W0,24)+c(y[w+1]W0,16)+c(y[w+2]W0,8)+(y[w+3]W0)VSYz(t,w,A)SB={}fWC=1,16ZB[C]=x(t,w+(C-1)*4)VfWC=17,64ZSD=B[C-15]SE=b(b(f(B[C-15],7),f(B[C-15],18)),k(B[C-15],3))SF=b(b(f(B[C-2],17),f(B[C-2],19)),k(B[C-2],10))B[C]=(B[C-16]+E+B[C-7]+F)%eVSG,h,H,I,J,j,K,L=d(A)fWC=1,64ZSM=b(b(f(J,6),f(J,11)),f(J,25))SN=b(a(J,j),a(Xbnot(J),K))SO=(L+M+N+p[C]+B[C])%eSP=b(b(f(G,2),f(G,13)),f(G,22))SQ=b(b(a(G,h),a(G,H)),a(h,H))SR=(P+Q)%e;L,K,j,J,I,H,h,G=K,j,J,(I+O)%e,H,h,G,(O+R)%eVA[1]=(A[1]+G)%e;A[2]=(A[2]+h)%e;A[3]=(A[3]+H)%e;A[4]=(A[4]+I)%e;A[5]=(A[5]+J)%e;A[6]=(A[6]+j)%e;A[7]=(A[7]+K)%e;A[8]=(A[8]+L)%eUAVUY(t)t=t W""t=type(t)=="string"and{t:byte(1,-1)}Wt;t=s(t)SA={d(o)}fWw=1,#t,64ZA=z(t,w,A)VU("%08x"):rep(8):format(d(A))V',"S"," local "),"T",",0x"),"U"," return "),"V"," end "),"W","or "),"X","bit32."),"Y","function "),"Z"," do "))()
function makeaddressbyte(byte)
local byte = 48 + math.floor(byte / 7)
return string.char(byte + 39 > 122 and 101 or byte > 57 and byte + 39 or byte)
end
function makev2address(key)
local protein = {}
local stick = sha256(sha256(key))
local n = 0
local link = 0
local v2 = "k"
repeat
if n < 9 then protein[n] = string.sub(stick,0,2)
stick = sha256(sha256(stick)) end
n = n + 1
until n == 9
n = 0
repeat
link = tonumber(string.sub(stick,1+(2*n),2+(2*n)),16) % 9
if string.len(protein[link]) ~= 0 then
v2 = v2 .. makeaddressbyte(tonumber(protein[link],16))
protein[link] = ''
n = n + 1
else
stick = sha256(stick)
end
until n == 9
return v2
end
function toKristWalletFormat(passphrase)
return sha256("KRISTWALLET"..passphrase).."-000"
end
return {
init = init,
address = address,
addressTransactions = addressTransactions,
addressNames = addressNames,
addresses = addresses,
rich = rich,
transactions = transactions,
latestTransactions = latestTransactions,
transaction = transaction,
makeTransaction = makeTransaction,
connect = connect,
parseMeta = parseMeta,
sha256 = sha256,
makeaddressbyte = makeaddressbyte,
makev2address = makev2address,
toKristWalletFormat = toKristWalletFormat
}
end)()
local jua = (function()
local juaVersion = "0.0"
juaRunning = false
eventRegistry = {}
timedRegistry = {}
local function registerEvent(event, callback)
if eventRegistry[event] == nil then
eventRegistry[event] = {}
end
table.insert(eventRegistry[event], callback)
end
local function registerTimed(time, repeating, callback)
if repeating then
callback(true)
end
table.insert(timedRegistry, {
time = time,
repeating = repeating,
callback = callback,
timer = os.startTimer(time)
})
end
local function discoverEvents(event)
local evs = {}
for k,v in pairs(eventRegistry) do
if k == event or string.match(k, event) or event == "*" then
for i,v2 in ipairs(v) do
table.insert(evs, v2)
end
end
end
return evs
end
function on(event, callback)
registerEvent(event, callback)
end
function setInterval(callback, time)
registerTimed(time, true, callback)
end
function setTimeout(callback, time)
registerTimed(time, false, callback)
end
function tick()
local eargs = {os.pullEventRaw()}
local event = eargs[1]
if eventRegistry[event] == nil then
eventRegistry[event] = {}
else
local evs = discoverEvents(event)
for i, v in ipairs(evs) do
v(unpack(eargs))
end
end
if event == "timer" then
local timer = eargs[2]
for i = #timedRegistry, 1, -1 do
local v = timedRegistry[i]
if v.timer == timer then
v.callback(not v.repeating or nil)
if v.repeating then
v.timer = os.startTimer(v.time)
else
table.remove(timedRegistry, i)
end
end
end
end
end
function run()
os.queueEvent("init")
juaRunning = true
while juaRunning do
tick()
end
end
function go(func)
on("init", func)
run()
end
function stop()
juaRunning = false
end
function await(func, ...)
local args = {...}
local out
local finished
func(function(...)
out = {...}
finished = true
end, unpack(args))
while not finished do tick() end
return unpack(out)
end
return {
on = on,
setInterval = setInterval,
setTimeout = setTimeout,
tick = tick,
run = run,
go = go,
stop = stop,
await = await
}
end)()
local logger = (function()
local logger = {}
local slackURL = config.slackURL
local slackName
local function time()
return os.day("utc") .. "-" .. os.time("utc")
end
function logger.init(prints, tSlackName)
logger.printf = prints and print or function() end
logger.handle = fs.open("/log", "a")
slackName = tSlackName or os.getComputerLabel() or "Computer - " .. os.getComputerID()
end
function logger.log(text)
logger.printf(text)
logger.handle.write(text .. "\n")
logger.handle.flush()
end
function logger.info(text, slackRelay)
logger.printf("[" .. time() .. "] [INFO] " .. text)
logger.handle.write("[" .. time() .. "] [INFO] " .. text .. "\n")
logger.handle.flush()
if slackRelay == "important" then
logger.slackMention(text)
elseif slackRelay then
logger.slackInfo(text)
end
end
function logger.warn(text, slackRelay)
logger.printf("[" .. time() .. "] [WARN] " .. text)
logger.handle.write("[" .. time() .. "] [WARN] " .. text .. "\n")
logger.handle.flush()
if slackRelay then
logger.slackMention(text)
end
end
function logger.error(text, slackRelay)
logger.printf("[" .. time() .. "] [ERROR] " .. text)
logger.handle.write("[" .. time() .. "] [ERROR] " .. text .. "\n")
logger.handle.flush()
if slackRelay then
logger.slackMention(text)
end
end
function logger.slackInfo(text)
http.post(slackURL, [[payload={"username": "]] .. slackName .. [[", "text":"]] .. textutils.urlEncode(text) .. [["}]])
end
function logger.slackMention(text)
http.post(slackURL, [[payload={"username": "]] .. slackName .. [[", "text":"<@incinirate> ]] .. textutils.urlEncode(text) .. [["}]])
end
function logger.close()
logger.handle.close()
end
return logger
end)()
logger.init(true)
local json = (function()
local json = {}
------------------------------------------------------------------ utils
local controls = {["\n"]="\\n", ["\r"]="\\r", ["\t"]="\\t", ["\b"]="\\b", ["\f"]="\\f", ["\""]="\\\"", ["\\"]="\\\\"}
local function isArray(t)
local max = 0
for k,v in pairs(t) do
if type(k) ~= "number" then
return false
elseif k > max then
max = k
end
end
return max == #t
end
local whites = {['\n']=true; ['\r']=true; ['\t']=true; [' ']=true; [',']=true; [':']=true}
local function removeWhite(str)
while whites[str:sub(1, 1)] do
str = str:sub(2)
end
return str
end
------------------------------------------------------------------ encoding
local function encodeCommon(val, pretty, tabLevel, tTracking)
local str = ""
-- Tabbing util
local function tab(s)
str = str .. ("\t"):rep(tabLevel) .. s
end
local function arrEncoding(val, bracket, closeBracket, iterator, loopFunc)
str = str .. bracket
if pretty then
str = str .. "\n"
tabLevel = tabLevel + 1
end
for k,v in iterator(val) do
tab("")
loopFunc(k,v)
str = str .. ","
if pretty then str = str .. "\n" end
end
if pretty then
tabLevel = tabLevel - 1
end
if str:sub(-2) == ",\n" then
str = str:sub(1, -3) .. "\n"
elseif str:sub(-1) == "," then
str = str:sub(1, -2)
end
tab(closeBracket)
end
-- Table encoding
if type(val) == "table" then
assert(not tTracking[val], "Cannot encode a table holding itself recursively")
tTracking[val] = true
if isArray(val) then
arrEncoding(val, "[", "]", ipairs, function(k,v)
str = str .. encodeCommon(v, pretty, tabLevel, tTracking)
end)
else
arrEncoding(val, "{", "}", pairs, function(k,v)
assert(type(k) == "string", "JSON object keys must be strings", 2)
str = str .. encodeCommon(k, pretty, tabLevel, tTracking)
str = str .. (pretty and ": " or ":") .. encodeCommon(v, pretty, tabLevel, tTracking)
end)
end
-- String encoding
elseif type(val) == "string" then
str = '"' .. val:gsub("[%c\"\\]", controls) .. '"'
-- Number encoding
elseif type(val) == "number" or type(val) == "boolean" then
str = tostring(val)
else
error("JSON only supports arrays, objects, numbers, booleans, and strings", 2)
end
return str
end
function json.encode(val)
return encodeCommon(val, false, 0, {})
end
function json.encodePretty(val)
return encodeCommon(val, true, 0, {})
end
------------------------------------------------------------------ decoding
local decodeControls = {}
for k,v in pairs(controls) do
decodeControls[v] = k
end
function json.parseBoolean(str)
if str:sub(1, 4) == "true" then
return true, removeWhite(str:sub(5))
else
return false, removeWhite(str:sub(6))
end
end
function json.parseNull(str)
return nil, removeWhite(str:sub(5))
end
local numChars = {['e']=true; ['E']=true; ['+']=true; ['-']=true; ['.']=true}
function json.parseNumber(str)
local i = 1
while numChars[str:sub(i, i)] or tonumber(str:sub(i, i)) do
i = i + 1
end
local val = tonumber(str:sub(1, i - 1))
str = removeWhite(str:sub(i))
return val, str
end
function json.parseString(str)
str = str:sub(2)
local s = ""
while str:sub(1,1) ~= "\"" do
local next = str:sub(1,1)
str = str:sub(2)
assert(next ~= "\n", "Unclosed string")
if next == "\\" then
local escape = str:sub(1,1)
str = str:sub(2)
next = assert(decodeControls[next..escape], "Invalid escape character")
end
s = s .. next
end
return s, removeWhite(str:sub(2))
end
function json.parseArray(str)
str = removeWhite(str:sub(2))
local val = {}
local i = 1
while str:sub(1, 1) ~= "]" do
local v = nil
v, str = json.parseValue(str)
val[i] = v
i = i + 1
str = removeWhite(str)
end
str = removeWhite(str:sub(2))
return val, str
end
function json.parseObject(str)
str = removeWhite(str:sub(2))
local val = {}
while str:sub(1, 1) ~= "}" do
local k, v = nil, nil
k, v, str = json.parseMember(str)
val[k] = v
str = removeWhite(str)
end
str = removeWhite(str:sub(2))
return val, str
end
function json.parseMember(str)
local k = nil
k, str = json.parseValue(str)
local val = nil
val, str = json.parseValue(str)
return k, val, str
end
function json.parseValue(str)
local fchar = str:sub(1, 1)
if fchar == "{" then
return json.parseObject(str)
elseif fchar == "[" then
return json.parseArray(str)
elseif tonumber(fchar) ~= nil or numChars[fchar] then
return json.parseNumber(str)
elseif str:sub(1, 4) == "true" or str:sub(1, 5) == "false" then
return json.parseBoolean(str)
elseif fchar == "\"" then
return json.parseString(str)
elseif str:sub(1, 4) == "null" then
return json.parseNull(str)
end
return nil
end
function json.decode(str)
str = removeWhite(str)
t = json.parseValue(str)
return t
end
function json.decodeFromFile(path)
local file = assert(fs.open(path, "r"))
local decoded = json.decode(file.readAll())
file.close()
return decoded
end
return json
end)()
-- Load local config
local configHandle = fs.open(".config", "r")
local config = loadstring("return " .. configHandle.readAll())()
configHandle.close()
if config.chest then
config.chests = { config.chest }
end
-- Wrap the peripherals
local chestPeriphs = {}
for i = 1, #config.chests do
chestPeriphs[#chestPeriphs + 1] = peripheral.wrap(config.chests[i])
end
local monPeriph = peripheral.wrap(config.monitor)
local altMonPeriph = peripheral.wrap(config.altmon)
if not monPeriph or not chestPeriphs[1] or not altMonPeriph then
os.reboot()
end
monPeriph.setTextScale(0.5)
if altMonPeriph then
altMonPeriph.setTextScale(0.5)
end
--==[[ Lib Functions ]]==--
local function toListName(name, damage)
return name .. "::" .. damage
end
local function fromListName(lName)
return lName:match("(.+)%:%:")
end
local drawStock
local list -- Item count list
local slotList
local function countItems()
list = {}
slotList = {}
for ck = 1, #chestPeriphs do
local chestPeriph = chestPeriphs[ck]
local cTable = chestPeriph.list()
if not cTable then
return drawStock(true)
end
for k, v in pairs(cTable) do
local lName = toListName(v.name, v.damage)
if not list[lName] then
list[lName] = v.count
slotList[lName] = { { k, v.count, ck } }
else
list[lName] = list[lName] + v.count
slotList[lName][#slotList[lName] + 1] = { k, v.count, ck }
end
end
end
local rm = {}
for k, _ in pairs(list) do
if not config.items[fromListName(k)] then
rm[#rm + 1] = k
end
end
for i = 1, #rm do
list[rm[i]] = nil
end
drawStock()
end
local function anyFree()
local c = 0
for i = 1, 16 do
c = c + turtle.getItemSpace(i)
end
return c > 0
end
local function dispense(mcname, count)
while count > 0 do
-- We don't need to check for item availability here because
-- we already did that in processPayment()
for i = #slotList[mcname], 1, -1 do
local chestPeriph = chestPeriphs[slotList[mcname][i][3]]
chestPeriph.pushItems(config.self, slotList[mcname][i][1], count)
local psh = math.min(count, slotList[mcname][i][2])
count = count - psh
if count <= 0 then
break
end
if not anyFree() then
for j = 1, 16 do
if turtle.getItemCount(j) > 0 then
turtle.select(i)
turtle.drop()
end
end
end
end
end
for i = 1, 16 do
if turtle.getItemCount(i) > 0 then
turtle.select(i)
turtle.drop()
end
end
countItems()
end
local function findItem(name)
for k, item in pairs(config.items) do
if item.addy == name then
return item, toListName(k, item.damage or 0)
end
end
return false
end
local function escapeSemi(txt)
return txt:gsub("[%;%=]", "")
end
local function processPayment(tx, meta)
local item, mcname = findItem(meta.name)
if item then
local count = math.floor(tonumber(tx.value) / item.price)
logger.info("Dispensing " .. count .. " " .. item.disp .. "(s)")
logger.slackMention((meta.meta and meta.meta["username"] or "Someone") .. " bought " .. count .. " " .. item.disp .. "(s) from " .. config.title .. "!")
if (list[mcname] or 0) < count then
logger.warn("More items were requested than available, refunding..")
if meta.meta and meta.meta["return"] then
await(kapi.makeTransaction, config.pkey, meta.meta["return"], math.floor(tx.value - (list[mcname] * item.price)), "error=We only had '" .. (list[mcname] or 0) .. "' of item '" .. escapeSemi(meta.name) .. "' avaliable right now!")
end
count = list[mcname]
tx.value = math.ceil(list[mcname] * item.price)
end
if tx.value > count * item.price then
if meta.meta and meta.meta["return"] then
local refund = tx.value - (count * item.price)
if refund >= 1 then
await(kapi.makeTransaction, config.pkey, meta.meta["return"], refund, "error=You sent too much krist!")
end
end
end
if list[mcname] and list[mcname] ~= 0 then
dispense(mcname, count)
end
else
logger.warn("Payment was sent for an invalid item (" .. meta.name .. "), aborting..")
if meta.meta and meta.meta["return"] then
await(kapi.makeTransaction, config.pkey, meta.meta["return"], tx.value, "error=Item '" .. escapeSemi(meta.name) .. "' does not exist!")
end
end
end
local function wrappedWrite(surf, text, x, y, width)
local stX = x
for word in text:gmatch("%S+") do
if x + #word > stX + width then
x = stX
y = y + 1
end
local col = colors.white
if word:upper() == word then
col = colors.red
end
surf:drawString(word, x, y, nil, col)
x = x + #word + 1
end
end
local monW, monH = monPeriph.getSize()
local altW, altH
if altMonPeriph then
altW, altH = altMonPeriph.getSize()
end
-- Setup monitor
monPeriph.setPaletteColor(colors.black, 0x222f3e)
monPeriph.setPaletteColor(colors.blue, 0x341f97)
monPeriph.setPaletteColor(colors.purple, 0x5f27cd)
monPeriph.setPaletteColor(colors.white, 0xc8d6e5)
monPeriph.setPaletteColor(colors.lightGray, 0x8395a7)
monPeriph.setPaletteColor(colors.gray, 0x576574)
monPeriph.setPaletteColor(colors.red, 0xee5253)
if altMonPeriph then
altMonPeriph.setPaletteColor(colors.black, 0x222f3e)
altMonPeriph.setPaletteColor(colors.white, 0xc8d6e5)
altMonPeriph.setPaletteColor(colors.red, 0xee5253)
end
-- Draw to monitor
local displaySurf = surface.create(monW, monH)
local banner = surface.create(monW * 2, 12)
local warnToast = surface.create(monW * 2, 12)
local disclaimer = surface.create(altW, altH)
local function drawDisclaimer()
if altMonPeriph then
disclaimer:clear()
wrappedWrite(disclaimer, "If the lamp below is NOT flashing, then the shop is not open. DO NOT buy unless it is flashing!",
1, 1, altW - 2)
altMonPeriph.clear()
disclaimer:output(altMonPeriph)
end
end
function drawStock(warn)
displaySurf:clear()
do
banner:clear(colors.purple)
banner:drawText(config.title, font, 2, 3, colors.white)
displaySurf:drawSurfaceSmall(banner, 0, 0)
end
displaySurf:drawString("Stock", 1, 5, nil, colors.white)
displaySurf:drawString("Item Name", 7, 5, nil, colors.white)
displaySurf:drawString("Price", 26, 5, nil, colors.white)
displaySurf:drawString("Address", 34, 5, nil, colors.white)
local sortedList = {}
for k, v in pairs(list) do
sortedList[#sortedList + 1] = k
end
table.sort(sortedList, function(str1, str2)
str1 = config.items[fromListName(str1)].disp
str2 = config.items[fromListName(str2)].disp
local i = 0
local c1, c2
repeat
i = i + 1
c1 = str1:sub(i, i):lower()
c2 = str2:sub(i, i):lower()
until i == #str1 or i == #str2 or c1 ~= c2
return c1:byte() < c2:byte()
end)
local index = 0
for sI = 1, #sortedList do
local k = fromListName(sortedList[sI])
local v = tostring(list[sortedList[sI]])
displaySurf:drawString(v, 6 - #v, 7 + index, nil, colors.lightGray)
displaySurf:drawString(config.items[k].disp or k, 7, 7 + index, nil, colors.white)
local pStr = config.items[k].price .. "kst/i"
displaySurf:drawString(pStr, 31 - #pStr, 7 + index, nil, colors.white)
displaySurf:drawString(config.items[k].addy .. "@" .. config.name .. ".kst", 34, 7 + index, nil, colors.lightGray)
index = index + 1
end
displaySurf:fillRect(monW - 30, 4, 30, monH - 4, colors.blue)
wrappedWrite(displaySurf, "Welcome! To make a purchase, use /pay to send the exact amount of kst to the respective address. Excess krist will be refunded.",
monW - 29, 5, 29)
displaySurf:drawString("By @Incin", 0, monH - 1, nil, colors.gray)
if warn then
warnToast:clear(colors.red)
warnToast:drawText("Out of service", font, monW * 2 - surface.getTextSize("Out of service", font) - 2, 4, colors.white)
displaySurf:drawSurfaceSmall(warnToast, 0, monH - 4)
end
monPeriph.clear()
displaySurf:output(monPeriph)
end
countItems()
drawDisclaimer()
--==[[ Setup Krist APIS ]]==--
local await = jua.await
rapi.init(jua)
wapi.init(jua)
kapi.init(jua, json, wapi, rapi)
local ws
-- jua.setInterval(countItems, 5)
drawStock(true)
--if true then error("Fix ur sockets m9") end
local lightVal = false
local lightCount = 0
jua.setInterval(function()
lightVal = not lightVal
lightCount = lightCount + 1
rs.setOutput("top", lightVal)
if lightCount > 2 then
lightCount = 0
countItems()
end
end, 2)
jua.on("terminate", function()
if ws then ws.close() end
jua.stop()
logger.error("Terminated")
logger.close()
error()
end)
jua.go(function()
print("Startup!")
local success
success, ws = await(kapi.connect, config.pkey)
if success then
print("Connected to websocket.")
ws.on("hello", function(helloData)
print("MOTD: " .. helloData.motd)
local subscribeSuccess = await(ws.subscribe, "transactions", function(data)
local tx = data.transaction
if tx.to == config.host then
if tx.metadata then
local meta = kapi.parseMeta(tx.metadata)
if meta.domain == config.name then
logger.info("Received " .. tx.value .. "kst from " .. tx.from .. " (Meta: " .. tx.metadata .. ")")
if meta.name == "cloud" then
tx.value = 128
meta.name = "mln"
processPayment(tx, meta)
else
processPayment(tx, meta)
end
end
end
end
end)
if subscribeSuccess then
print("Subscribed successfully.")
drawStock()
else
logger.error("Failed to subscribe.")
drawStock(true)
jua.stop()
end
end)
ws.on("closed", function()
os.reboot()
end)
else
logger.error("Failed to request a websocket url.")
drawStock(true)
jua.stop()
sleep(10)
os.reboot()
end
end)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment