Alyssa

Tenon

Jun 9th, 2021 (edited)
595
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 160.71 KB | None | 0 0
  1. -- vim: syntax=lua
  2. -- luacheck: globals loadRemote getRemote fs loadstring peripheral
  3.  
  4.  
  5. local versionTag = "v2.0.5"
  6.  
  7. local args = {...}
  8. local layoutMode = args[1] == "--layout" or args[1] == "-l"
  9.  
  10. local successTools = {}
  11.  
  12. local function xenon()
  13. local util = (function()
  14.   if util then return util end
  15. local util = {}
  16.  
  17. function util.toListName(modid, nbtHash, damage, pred)
  18.   return modid .. "::" .. nbtHash .. "::" .. damage .. "::" .. pred
  19. end
  20.  
  21. function util.fromListName(lName)
  22.   return lName:match("^(.-)::")
  23. end
  24.  
  25. function util.wrappedWrite(surf, text, x, y, width, color, align, lineHeight)
  26.   lineHeight = lineHeight or 1
  27.  
  28.   local lines = {""}
  29.  
  30.   text = tostring(text)
  31.  
  32.   local stX, stY = x, y + math.floor((lineHeight - 1) / 2)
  33.   for word in text:gmatch("%S+") do
  34.     if x + #word > stX + width and x ~= stX then
  35.       x = stX
  36.       y = y + lineHeight
  37.       lines[#lines] = lines[#lines]:sub(1, -2)
  38.       lines[#lines + 1] = ""
  39.     end
  40.  
  41.     lines[#lines] = lines[#lines] .. word .. " "
  42.     x = x + #word + 1
  43.   end
  44.  
  45.   lines[#lines] = lines[#lines]:sub(1, -2)
  46.  
  47.   local colors = {}
  48.   if type(color) == "string" and color:match("^rainbow") then
  49.     for c in color:gmatch("(%d+);") do
  50.       colors[#colors + 1] = tonumber(c)
  51.     end
  52.   end
  53.  
  54.   local rainboxIndex = 1
  55.   function commitString(text, x, y)
  56.     if #colors > 0 then
  57.       for i = 1, #text do
  58.         local char = text:sub(i, i)
  59.         surf:drawString(char, x + i - 1, y, nil, colors[rainboxIndex])
  60.         if char ~= " " then
  61.           rainboxIndex = 1 + (rainboxIndex % #colors)
  62.         end
  63.       end
  64.     else
  65.       surf:drawString(text, x, y, nil, color)
  66.     end
  67.   end
  68.  
  69.   if surf then
  70.     for i = 1, #lines do
  71.       if align == "right" then
  72.         commitString(lines[i], stX + width - #lines[i], stY + (i - 1)*lineHeight, color)
  73.       elseif align == "center" then
  74.         commitString(lines[i], stX + math.floor((width - #lines[i]) / 2), stY + (i - 1)*lineHeight, color)
  75.       elseif align == "justify" and i ~= #lines then
  76.         local lineStr = lines[i]
  77.         local requiredExtra = width - #(lineStr:gsub("%s", ""))
  78.  
  79.         local finalStr = ""
  80.         local _, wordCount = lineStr:gsub("%S+", "")
  81.  
  82.         if wordCount == 1 then
  83.           finalStr = lineStr:gsub("%s", "")
  84.         else
  85.           local spacePerInstance = math.floor(requiredExtra / (wordCount - 1))
  86.           local overflowAmount = requiredExtra - (spacePerInstance * (wordCount - 1))
  87.  
  88.           local wordI = 0
  89.           for word in lineStr:gmatch("%S+") do
  90.             wordI = wordI + 1
  91.  
  92.             local padding = spacePerInstance
  93.             if wordI == wordCount then
  94.               padding = 0
  95.             elseif overflowAmount > 0 then
  96.               padding = padding + 1
  97.               overflowAmount = overflowAmount - 1
  98.             end
  99.  
  100.             finalStr = finalStr .. word .. (" "):rep(padding)
  101.           end
  102.         end
  103.  
  104.         commitString(finalStr, stX, stY + (i - 1)*lineHeight, color)
  105.       else -- left
  106.         commitString(lines[i], stX, stY + (i - 1)*lineHeight, color)
  107.       end
  108.     end
  109.   end
  110.  
  111.   return y + math.ceil((lineHeight - 1) / 2) + 1
  112. end
  113.  
  114. function util.parseOrdinalStyle(resolver, styles, styleName)
  115.   local ordinals = {}
  116.   for ordinal in (styles[styleName] or "0"):gmatch("%S+") do
  117.     ordinals[#ordinals + 1] = ordinal
  118.   end
  119.  
  120.   if styles[styleName .. "-top"]    then ordinals[1] = styles[styleName .. "-top"] end
  121.   if styles[styleName .. "-right"]  then ordinals[2] = styles[styleName .. "-right"] end
  122.   if styles[styleName .. "-bottom"] then ordinals[3] = styles[styleName .. "-bottom"] end
  123.   if styles[styleName .. "-left"]   then ordinals[4] = styles[styleName .. "-left"] end
  124.  
  125.   local top = resolver({}, "number", ordinals[1])
  126.   local right = resolver({}, "number", ordinals[2] or ordinals[1])
  127.   local bottom = resolver({}, "number", ordinals[3] or ordinals[1])
  128.   local left = resolver({}, "number", ordinals[4] or ordinals[2] or ordinals[1])
  129.  
  130.   return top, right, bottom, left
  131. end
  132.  
  133. function util.deepClone(table, cache)
  134.   cache = cache or {}
  135.   local t = {}
  136.  
  137.   cache[table] = t
  138.  
  139.   for k, v in pairs(table) do
  140.     if type(v) == "table" then
  141.       if cache[v] then
  142.         t[k] = cache[v]
  143.       else
  144.         t[k] = util.deepClone(v, cache)
  145.       end
  146.     else
  147.       t[k] = v
  148.     end
  149.   end
  150.  
  151.   return t
  152. end
  153.  
  154. function util.round(num, numDecimalPlaces)
  155.   local mult = 10^(numDecimalPlaces or 0)
  156.   return math.floor(num * mult + 0.5) / mult
  157. end
  158.  
  159. function util.matchPredicate(predicate, tab)
  160.   if not tab then
  161.     return false
  162.   end
  163.  
  164.   for k, v in pairs(predicate) do
  165.     local kType = type(k)
  166.     if kType ~= "number" then
  167.       if not tab[k] then
  168.         return false
  169.       end
  170.     end
  171.  
  172.     if type(v) == "table" then
  173.       return util.matchPredicate(v, tab[k])
  174.     else
  175.       if kType == "number" then
  176.         local found = false
  177.         for i = 1, #tab do
  178.           if tab[k] == v then
  179.             found = true
  180.             break
  181.           end
  182.         end
  183.  
  184.         return found
  185.       else
  186.         if tab[k] ~= v then
  187.           return false
  188.         end
  189.       end
  190.     end
  191.   end
  192.  
  193.   return true
  194. end
  195.  
  196. function util.equals(val1, val2)
  197.   local typeV = type(val1)
  198.  
  199.   if typeV ~= type(val2) then
  200.     return false
  201.   end
  202.  
  203.   if typeV ~= "table" then
  204.     return val1 == val2
  205.   end
  206.  
  207.   local lengthV1 = 0
  208.   for k, v in pairs(val1) do
  209.     lengthV1 = lengthV1 + 1
  210.  
  211.     if not util.equals(v, val2[k]) then
  212.       return false
  213.     end
  214.   end
  215.  
  216.   local lengthV2 = 0
  217.   for _ in pairs(val2) do
  218.     lengthV2 = lengthV2 + 1
  219.   end
  220.  
  221.   return lengthV1 == lengthV2
  222. end
  223.  
  224. return util end)()
  225.  
  226.   -- Load local config
  227.   local configHandle = fs.open("config.lua", "r")
  228.   if not configHandle then
  229.     configHandle = fs.open(".config", "r")
  230.  
  231.     if not configHandle then
  232.       error("No config file found at '.config', please create one")
  233.     end
  234.   end
  235.  
  236.   local config
  237.   local configData = configHandle.readAll()
  238.   if not configData:match("^return") then
  239.     configData = "return " .. configData
  240.   end
  241.   local configFunc, err = loadstring(configData)
  242.   if not configFunc then
  243.     error("Invalid config: Line " .. (err:match(":(%d+:.+)") or err))
  244.   else
  245.     config = configFunc()
  246.   end
  247.  
  248.   configHandle.close()
  249.  
  250.   if not (turtle or layoutMode or config.outChest) then
  251.     error("Xenon must run on a turtle")
  252.   end
  253.  
  254.  
  255. local transformedItems = {}
  256.  
  257. local predicateCache = {}
  258. local predicateIDCounter = 0
  259. for i = 1, #config.items do local item = config.items[i] -- do
  260.   if item.predicate then
  261.     for predicateID = 1, #predicateCache do
  262.       local predicate = predicateCache[predicateID]
  263.       if util.equals(predicate, item.predicate) then
  264.         item.predicateID = predicateID
  265.       end
  266.     end
  267.  
  268.     if not item.predicateID then
  269.       predicateIDCounter = predicateIDCounter + 1
  270.  
  271.       item.predicateID = predicateIDCounter
  272.       predicateCache[predicateIDCounter] = item.predicate
  273.     end
  274.   end
  275.  
  276.   transformedItems[util.toListName(item.modid, item.nbtHash or "", item.damage or 0, item.predicateID or 0)] = item
  277. end
  278.  
  279.  
  280. --== Load required libs / files ==--
  281.  
  282.  
  283. local surface = (function()
  284.   if surface then return surface end
  285. local surface = { } do
  286.     --[[
  287.     Surface 2
  288.  
  289.     The MIT License (MIT)
  290.     Copyright (c) 2017 CrazedProgrammer
  291.  
  292.     Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
  293.     associated documentation files (the "Software"), to deal in the Software without restriction,
  294.     including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
  295.     and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
  296.     so, subject to the following conditions:
  297.  
  298.     The above copyright notice and this permission notice shall be included in all copies or
  299.     substantial portions of the Software.
  300.  
  301.     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  302.     INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  303.     PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  304.     COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
  305.     AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  306.     WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  307.     ]]
  308.     local surf = { }
  309.     surface.surf = surf
  310.  
  311.     local table_concat, math_floor, math_atan2 = table.concat, math.floor, math.atan2
  312.  
  313.     local _cc_color_to_hex, _cc_hex_to_color = { }, { }
  314.     for i = 0, 15 do
  315.         _cc_color_to_hex[2 ^ i] = string.format("%01x", i)
  316.         _cc_hex_to_color[string.format("%01x", i)] = 2 ^ i
  317.     end
  318.  
  319.     local _chars = { }
  320.     for i = 0, 255 do
  321.         _chars[i] = string.char(i)
  322.     end
  323.     local _numstr = { }
  324.     for i = 0, 1023 do
  325.         _numstr[i] = tostring(i)
  326.     end
  327.  
  328.     local _eprc, _esin, _ecos = 20, { }, { }
  329.     for i = 0, _eprc - 1 do
  330.         _esin[i + 1] = (1 - math.sin(i / _eprc * math.pi * 2)) / 2
  331.         _ecos[i + 1] = (1 + math.cos(i / _eprc * math.pi * 2)) / 2
  332.     end
  333.  
  334.     local _steps, _palette, _rgbpal, _palr, _palg, _palb = 16
  335.  
  336.     local function calcStack(stack, width, height)
  337.         local ox, oy, cx, cy, cwidth, cheight = 0, 0, 0, 0, width, height
  338.         for i = 1, #stack do
  339.             ox = ox + stack[i].ox
  340.             oy = oy + stack[i].oy
  341.             cx = cx + stack[i].x
  342.             cy = cy + stack[i].y
  343.             cwidth = stack[i].width
  344.             cheight = stack[i].height
  345.         end
  346.         return ox, oy, cx, cy, cwidth, cheight
  347.     end
  348.  
  349.     local function clipRect(x, y, width, height, cx, cy, cwidth, cheight)
  350.         if x < cx then
  351.             width = width + x - cx
  352.             x = cx
  353.         end
  354.         if y < cy then
  355.             height = height + y - cy
  356.             y = cy
  357.         end
  358.         if x + width > cx + cwidth then
  359.             width = cwidth + cx - x
  360.         end
  361.         if y + height > cy + cheight then
  362.             height = cheight + cy - y
  363.         end
  364.         return x, y, width, height
  365.     end
  366.  
  367.  
  368.  
  369.     function surface.create(width, height, b, t, c)
  370.         local surface = setmetatable({ }, {__index = surface.surf})
  371.         surface.width = width
  372.         surface.height = height
  373.         surface.buffer = { }
  374.         surface.overwrite = false
  375.         surface.stack = { }
  376.         surface.ox, surface.oy, surface.cx, surface.cy, surface.cwidth, surface.cheight = calcStack(surface.stack, width, height)
  377.         -- force array indeces instead of hashed indices
  378.  
  379.         local buffer = surface.buffer
  380.         for i = 1, width * height * 3, 3 do
  381.             buffer[i] = b or false
  382.             buffer[i + 1] = t or false
  383.             buffer[i + 2] = c or false
  384.         end
  385.         buffer[width * height * 3 + 1] = false
  386.         if not b then
  387.             for i = 1, width * height * 3, 3 do
  388.                 buffer[i] = b
  389.             end
  390.         end
  391.         if not t then
  392.             for i = 2, width * height * 3, 3 do
  393.                 buffer[i] = t
  394.             end
  395.         end
  396.         if not c then
  397.             for i = 3, width * height * 3, 3 do
  398.                 buffer[i] = c
  399.             end
  400.         end
  401.  
  402.         return surface
  403.     end
  404.  
  405.     function surface.getPlatformOutput(output)
  406.         output = output or (term or gpu or (love and love.graphics) or io)
  407.  
  408.         if output.blit and output.setCursorPos then
  409.             return "cc", output, output.getSize()
  410.         elseif output.write and output.setCursorPos and output.setTextColor and output.setBackgroundColor then
  411.             return "cc-old", output, output.getSize()
  412.         elseif output.blitPixels then
  413.             return "riko-4", output, 320, 200
  414.         elseif output.points and output.setColor then
  415.             return "love2d", output, output.getWidth(), output.getHeight()
  416.         elseif output.drawPixel then
  417.             return "redirection", output, 64, 64
  418.         elseif output.setForeground and output.setBackground and output.set then
  419.             return "oc", output, output.getResolution()
  420.         elseif output.write then
  421.             return "ansi", output, (os.getenv and (os.getenv("COLUMNS"))) or 80, (os.getenv and (os.getenv("LINES"))) or 43
  422.         else
  423.             error("unsupported platform/output object")
  424.         end
  425.     end
  426.  
  427.     function surf:output(output, x, y, sx, sy, swidth, sheight)
  428.         local platform, output, owidth, oheight = surface.getPlatformOutput(output)
  429.  
  430.         x = x or 0
  431.         y = y or 0
  432.         sx = sx or 0
  433.         sy = sy or 0
  434.         swidth = swidth or self.width
  435.         sheight = sheight or self.height
  436.         sx, sy, swidth, sheight = clipRect(sx, sy, swidth, sheight, 0, 0, self.width, self.height)
  437.  
  438.         local buffer = self.buffer
  439.         local bwidth = self.width
  440.         local xoffset, yoffset, idx
  441.  
  442.         if platform == "cc" then
  443.             -- CC
  444.             local str, text, back = { }, { }, { }
  445.             for j = 0, sheight - 1 do
  446.                 yoffset = (j + sy) * bwidth + sx
  447.                 for i = 0, swidth - 1 do
  448.                     xoffset = (yoffset + i) * 3
  449.                     idx = i + 1
  450.                     str[idx] = buffer[xoffset + 3] or " "
  451.                     text[idx] = _cc_color_to_hex[buffer[xoffset + 2] or 1]
  452.                     back[idx] = _cc_color_to_hex[buffer[xoffset + 1] or 32768]
  453.                 end
  454.                 output.setCursorPos(x + 1, y + j + 1)
  455.                 output.blit(table_concat(str), table_concat(text), table_concat(back))
  456.             end
  457.  
  458.         elseif platform == "cc-old" then
  459.             -- CC pre-1.76
  460.             local str, b, t, pb, pt = { }
  461.             for j = 0, sheight - 1 do
  462.                 output.setCursorPos(x + 1, y + j + 1)
  463.                 yoffset = (j + sy) * bwidth + sx
  464.                 for i = 0, swidth - 1 do
  465.                     xoffset = (yoffset + i) * 3
  466.                     pb = buffer[xoffset + 1] or 32768
  467.                     pt = buffer[xoffset + 2] or 1
  468.                     if pb ~= b then
  469.                         if #str ~= 0 then
  470.                             output.write(table_concat(str))
  471.                             str = { }
  472.                         end
  473.                         b = pb
  474.                         output.setBackgroundColor(b)
  475.                     end
  476.                     if pt ~= t then
  477.                         if #str ~= 0 then
  478.                             output.write(table_concat(str))
  479.                             str = { }
  480.                         end
  481.                         t = pt
  482.                         output.setTextColor(t)
  483.                     end
  484.                     str[#str + 1] = buffer[xoffset + 3] or " "
  485.                 end
  486.                 output.write(table_concat(str))
  487.                 str = { }
  488.             end
  489.  
  490.         elseif platform == "riko-4" then
  491.             -- Riko 4
  492.             local pixels = { }
  493.             for j = 0, sheight - 1 do
  494.                 yoffset = (j + sy) * bwidth + sx
  495.                 for i = 0, swidth - 1 do
  496.                     pixels[j * swidth + i + 1] = buffer[(yoffset + i) * 3 + 1] or 0
  497.                 end
  498.             end
  499.             output.blitPixels(x, y, swidth, sheight, pixels)
  500.  
  501.         elseif platform == "love2d" then
  502.             -- Love2D
  503.             local pos, r, g, b, pr, pg, pb = { }
  504.             for j = 0, sheight - 1 do
  505.                 yoffset = (j + sy) * bwidth + sx
  506.                 for i = 0, swidth - 1 do
  507.                     xoffset = (yoffset + i) * 3
  508.                     pr = buffer[xoffset + 1]
  509.                     pg = buffer[xoffset + 2]
  510.                     pb = buffer[xoffset + 3]
  511.                     if pr ~= r or pg ~= g or pb ~= b then
  512.                         if #pos ~= 0 then
  513.                             output.setColor((r or 0) * 255, (g or 0) * 255, (b or 0) * 255, (r or g or b) and 255 or 0)
  514.                             output.points(pos)
  515.                         end
  516.                         r, g, b = pr, pg, pb
  517.                         pos = { }
  518.                     end
  519.                     pos[#pos + 1] = i + x + 1
  520.                     pos[#pos + 1] = j + y + 1
  521.                 end
  522.             end
  523.             output.setColor((r or 0) * 255, (g or 0) * 255, (b or 0) * 255, (r or g or b) and 255 or 0)
  524.             output.points(pos)
  525.  
  526.         elseif platform == "redirection" then
  527.             -- Redirection arcade (gpu)
  528.             -- todo: add image:write support for extra performance
  529.             local px = output.drawPixel
  530.             for j = 0, sheight - 1 do
  531.                 for i = 0, swidth - 1 do
  532.                     px(x + i, y + j, buffer[((j + sy) * bwidth + (i + sx)) * 3 + 1] or 0)
  533.                 end
  534.             end
  535.  
  536.         elseif platform == "oc" then
  537.             -- OpenComputers
  538.             local str, lx, b, t, pb, pt = { }
  539.             for j = 0, sheight - 1 do
  540.                 lx = x
  541.                 yoffset = (j + sy) * bwidth + sx
  542.                 for i = 0, swidth - 1 do
  543.                     xoffset = (yoffset + i) * 3
  544.                     pb = buffer[xoffset + 1] or 0x000000
  545.                     pt = buffer[xoffset + 2] or 0xFFFFFF
  546.                     if pb ~= b then
  547.                         if #str ~= 0 then
  548.                             output.set(lx + 1, j + y + 1, table_concat(str))
  549.                             lx = i + x
  550.                             str = { }
  551.                         end
  552.                         b = pb
  553.                         output.setBackground(b)
  554.                     end
  555.                     if pt ~= t then
  556.                         if #str ~= 0 then
  557.                             output.set(lx + 1, j + y + 1, table_concat(str))
  558.                             lx = i + x
  559.                             str = { }
  560.                         end
  561.                         t = pt
  562.                         output.setForeground(t)
  563.                     end
  564.                     str[#str + 1] = buffer[xoffset + 3] or " "
  565.                 end
  566.                 output.set(lx + 1, j + y + 1, table_concat(str))
  567.                 str = { }
  568.             end
  569.  
  570.         elseif platform == "ansi" then
  571.             -- ANSI terminal
  572.             local str, b, t, pb, pt = { }
  573.             for j = 0, sheight - 1 do
  574.                 str[#str + 1] = "\x1b[".._numstr[y + j + 1]..";".._numstr[x + 1].."H"
  575.                 yoffset = (j + sy) * bwidth + sx
  576.                 for i = 0, swidth - 1 do
  577.                     xoffset = (yoffset + i) * 3
  578.                     pb = buffer[xoffset + 1] or 0
  579.                     pt = buffer[xoffset + 2] or 7
  580.                     if pb ~= b then
  581.                         b = pb
  582.                         if b < 8 then
  583.                             str[#str + 1] = "\x1b[".._numstr[40 + b].."m"
  584.                         elseif b < 16 then
  585.                             str[#str + 1] = "\x1b[".._numstr[92 + b].."m"
  586.                         elseif b < 232 then
  587.                             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"
  588.                         else
  589.                             local gr = _numstr[b * 10 - 2312]
  590.                             str[#str + 1] = "\x1b[48;2;"..gr..";"..gr..";"..gr.."m"
  591.                         end
  592.                     end
  593.                     if pt ~= t then
  594.                         t = pt
  595.                         if t < 8 then
  596.                             str[#str + 1] = "\x1b[".._numstr[30 + t].."m"
  597.                         elseif t < 16 then
  598.                             str[#str + 1] = "\x1b[".._numstr[82 + t].."m"
  599.                         elseif t < 232 then
  600.                             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"
  601.                         else
  602.                             local gr = _numstr[t * 10 - 2312]
  603.                             str[#str + 1] = "\x1b[38;2;"..gr..";"..gr..";"..gr.."m"
  604.                         end
  605.                     end
  606.                     str[#str + 1] = buffer[xoffset + 3] or " "
  607.                 end
  608.             end
  609.             output.write(table_concat(str))
  610.         end
  611.     end
  612.  
  613.     function surf:push(x, y, width, height, nooffset)
  614.         x, y = x + self.ox, y + self.oy
  615.  
  616.         local ox, oy = nooffset and self.ox or x, nooffset and self.oy or y
  617.         x, y, width, height = clipRect(x, y, width, height, self.cx, self.cy, self.cwidth, self.cheight)
  618.         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}
  619.  
  620.         self.ox, self.oy, self.cx, self.cy, self.cwidth, self.cheight = calcStack(self.stack, self.width, self.height)
  621.     end
  622.  
  623.     function surf:pop()
  624.         if #self.stack == 0 then
  625.             error("no stencil to pop")
  626.         end
  627.         self.stack[#self.stack] = nil
  628.         self.ox, self.oy, self.cx, self.cy, self.cwidth, self.cheight = calcStack(self.stack, self.width, self.height)
  629.     end
  630.  
  631.     function surf:copy()
  632.         local surface = setmetatable({ }, {__index = surface.surf})
  633.  
  634.         for k, v in pairs(self) do
  635.             surface[k] = v
  636.         end
  637.  
  638.         surface.buffer = { }
  639.         for i = 1, self.width * self.height * 3 + 1 do
  640.             surface.buffer[i] = false
  641.         end
  642.         for i = 1, self.width * self.height * 3 do
  643.             surface.buffer[i] = self.buffer[i]
  644.         end
  645.  
  646.         surface.stack = { }
  647.         for i = 1, #self.stack do
  648.             surface.stack[i] = self.stack[i]
  649.         end
  650.  
  651.         return surface
  652.     end
  653.  
  654.     function surf:clear(b, t, c)
  655.         local xoffset, yoffset
  656.  
  657.         for j = 0, self.cheight - 1 do
  658.             yoffset = (j + self.cy) * self.width + self.cx
  659.             for i = 0, self.cwidth - 1 do
  660.                 xoffset = (yoffset + i) * 3
  661.                 self.buffer[xoffset + 1] = b
  662.                 self.buffer[xoffset + 2] = t
  663.                 self.buffer[xoffset + 3] = c
  664.             end
  665.         end
  666.     end
  667.  
  668.     function surf:drawPixel(x, y, b, t, c)
  669.         x, y = x + self.ox, y + self.oy
  670.  
  671.         local idx
  672.         if x >= self.cx and x < self.cx + self.cwidth and y >= self.cy and y < self.cy + self.cheight then
  673.             idx = (y * self.width + x) * 3
  674.             if b or self.overwrite then
  675.                 self.buffer[idx + 1] = b
  676.             end
  677.             if t or self.overwrite then
  678.                 self.buffer[idx + 2] = t
  679.             end
  680.             if c or self.overwrite then
  681.                 self.buffer[idx + 3] = c
  682.             end
  683.         end
  684.     end
  685.  
  686.     function surf:drawString(str, x, y, b, t)
  687.         x, y = x + self.ox, y + self.oy
  688.  
  689.         local sx = x
  690.         local insidey = y >= self.cy and y < self.cy + self.cheight
  691.         local idx
  692.         local lowerxlim = self.cx
  693.         local upperxlim = self.cx + self.cwidth
  694.         local writeb = b or self.overwrite
  695.         local writet = t or self.overwrite
  696.  
  697.         for i = 1, #str do
  698.             local c = str:sub(i, i)
  699.             if c == "\n" then
  700.                 x = sx
  701.                 y = y + 1
  702.                 if insidey then
  703.                     if y >= self.cy + self.cheight then
  704.                         return
  705.                     end
  706.                 else
  707.                     insidey = y >= self.cy
  708.                 end
  709.             else
  710.                 idx = (y * self.width + x) * 3
  711.                 if x >= lowerxlim and x < upperxlim and insidey then
  712.                     if writeb then
  713.                         self.buffer[idx + 1] = b
  714.                     end
  715.                     if writet then
  716.                         self.buffer[idx + 2] = t
  717.                     end
  718.                     self.buffer[idx + 3] = c
  719.                 end
  720.                 x = x + 1
  721.             end
  722.         end
  723.     end
  724.  
  725.     -- You can remove any of these components
  726.     function surface.load(strpath, isstr)
  727.         local data = strpath
  728.         if not isstr then
  729.             local handle = io.open(strpath, "rb")
  730.             if not handle then return end
  731.             local chars = { }
  732.             local byte = handle:read(1)
  733.             if type(byte) == "number" then -- cc doesn't conform to standards
  734.                 while byte do
  735.                     chars[#chars + 1] = _chars[byte]
  736.                     byte = handle:read(1)
  737.                 end
  738.             else
  739.                 while byte do
  740.                     chars[#chars + 1] = byte
  741.                     byte = handle:read(1)
  742.                 end
  743.             end
  744.             handle:close()
  745.             data = table_concat(chars)
  746.         end
  747.  
  748.         if data:sub(1, 3) == "RIF" then
  749.             -- Riko 4 image format
  750.             local width, height = data:byte(4) * 256 + data:byte(5), data:byte(6) * 256 + data:byte(7)
  751.             local surf = surface.create(width, height)
  752.             local buffer = surf.buffer
  753.             local upper, byte = 8, false
  754.             local byte = data:byte(index)
  755.  
  756.             for j = 0, height - 1 do
  757.                 for i = 0, height - 1 do
  758.                     if not upper then
  759.                         buffer[(j * width + i) * 3 + 1] = math_floor(byte / 16)
  760.                     else
  761.                         buffer[(j * width + i) * 3 + 1] = byte % 16
  762.                         index = index + 1
  763.                         data = data:byte(index)
  764.                     end
  765.                     upper = not upper
  766.                 end
  767.             end
  768.             return surf
  769.  
  770.         elseif data:sub(1, 2) == "BM" then
  771.             -- BMP format
  772.             local width = data:byte(0x13) + data:byte(0x14) * 256
  773.             local height = data:byte(0x17) + data:byte(0x18) * 256
  774.             if data:byte(0xF) ~= 0x28 or data:byte(0x1B) ~= 1 or data:byte(0x1D) ~= 0x18 then
  775.                 error("unsupported bmp format, only uncompressed 24-bit rgb is supported.")
  776.             end
  777.             local offset, linesize = 0x36, math.ceil((width * 3) / 4) * 4
  778.  
  779.             local surf = surface.create(width, height)
  780.             local buffer = surf.buffer
  781.             for j = 0, height - 1 do
  782.                 for i = 0, width - 1 do
  783.                     buffer[(j * width + i) * 3 + 1] = data:byte((height - j - 1) * linesize + i * 3 + offset + 3) / 255
  784.                     buffer[(j * width + i) * 3 + 2] = data:byte((height - j - 1) * linesize + i * 3 + offset + 2) / 255
  785.                     buffer[(j * width + i) * 3 + 3] = data:byte((height - j - 1) * linesize + i * 3 + offset + 1) / 255
  786.                 end
  787.             end
  788.             return surf
  789.  
  790.         elseif data:find("\30") then
  791.             -- NFT format
  792.             local width, height, lwidth = 0, 1, 0
  793.             for i = 1, #data do
  794.                 if data:byte(i) == 10 then -- newline
  795.                     height = height + 1
  796.                     if lwidth > width then
  797.                         width = lwidth
  798.                     end
  799.                     lwidth = 0
  800.                 elseif data:byte(i) == 30 or data:byte(i) == 31 then -- color control
  801.                     lwidth = lwidth - 1
  802.                 elseif data:byte(i) ~= 13 then -- not carriage return
  803.                     lwidth = lwidth + 1
  804.                 end
  805.             end
  806.             if data:byte(#data) == 10 then
  807.                 height = height - 1
  808.             end
  809.  
  810.             local surf = surface.create(width, height)
  811.             local buffer = surf.buffer
  812.             local index, x, y, b, t = 1, 0, 0
  813.  
  814.             while index <= #data do
  815.                 if data:byte(index) == 10 then
  816.                     x, y = 0, y + 1
  817.                 elseif data:byte(index) == 30 then
  818.                     index = index + 1
  819.                     b = _cc_hex_to_color[data:sub(index, index)]
  820.                 elseif data:byte(index) == 31 then
  821.                     index = index + 1
  822.                     t = _cc_hex_to_color[data:sub(index, index)]
  823.                 elseif data:byte(index) ~= 13 then
  824.                     buffer[(y * width + x) * 3 + 1] = b
  825.                     buffer[(y * width + x) * 3 + 2] = t
  826.                     if b or t then
  827.                         buffer[(y * width + x) * 3 + 3] = data:sub(index, index)
  828.                     elseif data:sub(index, index) ~= " " then
  829.                         buffer[(y * width + x) * 3 + 3] = data:sub(index, index)
  830.                     end
  831.                     x = x + 1
  832.                 end
  833.                 index = index + 1
  834.             end
  835.  
  836.             return surf
  837.         else
  838.             -- NFP format
  839.             local width, height, lwidth = 0, 1, 0
  840.             for i = 1, #data do
  841.                 if data:byte(i) == 10 then -- newline
  842.                     height = height + 1
  843.                     if lwidth > width then
  844.                         width = lwidth
  845.                     end
  846.                     lwidth = 0
  847.                 elseif data:byte(i) ~= 13 then -- not carriage return
  848.                     lwidth = lwidth + 1
  849.                 end
  850.             end
  851.             if data:byte(#data) == 10 then
  852.                 height = height - 1
  853.             end
  854.  
  855.             local surf = surface.create(width, height)
  856.             local buffer = surf.buffer
  857.             local x, y = 0, 0
  858.             for i = 1, #data do
  859.                 if data:byte(i) == 10 then
  860.                     x, y = 0, y + 1
  861.                 elseif data:byte(i) ~= 13 then
  862.                     buffer[(y * width + x) * 3 + 1] = _cc_hex_to_color[data:sub(i, i)]
  863.                     x = x + 1
  864.                 end
  865.             end
  866.  
  867.             return surf
  868.         end
  869.     end
  870.  
  871.     function surf:save(file, format)
  872.         format = format or "nfp"
  873.         local data = { }
  874.         if format == "nfp" then
  875.             for j = 0, self.height - 1 do
  876.                 for i = 0, self.width - 1 do
  877.                     data[#data + 1] = _cc_color_to_hex[self.buffer[(j * self.width + i) * 3 + 1]] or " "
  878.                 end
  879.                 data[#data + 1] = "\n"
  880.             end
  881.  
  882.         elseif format == "nft" then
  883.             for j = 0, self.height - 1 do
  884.                 local b, t, pb, pt
  885.                 for i = 0, self.width - 1 do
  886.                     pb = self.buffer[(j * self.width + i) * 3 + 1]
  887.                     pt = self.buffer[(j * self.width + i) * 3 + 2]
  888.                     if pb ~= b then
  889.                         data[#data + 1] = "\30"..(_cc_color_to_hex[pb] or " ")
  890.                         b = pb
  891.                     end
  892.                     if pt ~= t then
  893.                         data[#data + 1] = "\31"..(_cc_color_to_hex[pt] or " ")
  894.                         t = pt
  895.                     end
  896.                     data[#data + 1] = self.buffer[(j * self.width + i) * 3 + 3] or " "
  897.                 end
  898.                 data[#data + 1] = "\n"
  899.             end
  900.  
  901.         elseif format == "rif" then
  902.             data[1] = "RIF"
  903.             data[2] = string.char(math_floor(self.width / 256), self.width % 256)
  904.             data[3] = string.char(math_floor(self.height / 256), self.height % 256)
  905.             local byte, upper, c = 0, false
  906.             for j = 0, self.width - 1 do
  907.                 for i = 0, self.height - 1 do
  908.                     c = self.buffer[(j * self.width + i) * 3 + 1] or 0
  909.                     if not upper then
  910.                         byte = c * 16
  911.                     else
  912.                         byte = byte + c
  913.                         data[#data + 1] = string.char(byte)
  914.                     end
  915.                     upper = not upper
  916.                 end
  917.             end
  918.             if upper then
  919.                 data[#data + 1] = string.char(byte)
  920.             end
  921.  
  922.         elseif format == "bmp" then
  923.             data[1] = "BM"
  924.             data[2] = string.char(0, 0, 0, 0) -- file size, change later
  925.             data[3] = string.char(0, 0, 0, 0, 0x36, 0, 0, 0, 0x28, 0, 0, 0)
  926.             data[4] = string.char(self.width % 256, math_floor(self.width / 256), 0, 0)
  927.             data[5] = string.char(self.height % 256, math_floor(self.height / 256), 0, 0)
  928.             data[6] = string.char(1, 0, 0x18, 0, 0, 0, 0, 0)
  929.             data[7] = string.char(0, 0, 0, 0) -- pixel data size, change later
  930.             data[8] = string.char(0x13, 0x0B, 0, 0, 0x13, 0x0B, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
  931.  
  932.             local padchars = math.ceil((self.width * 3) / 4) * 4 - self.width * 3
  933.             for j = self.height - 1, 0, -1 do
  934.                 for i = 0, self.width - 1 do
  935.                     data[#data + 1] = string.char((self.buffer[(j * self.width + i) * 3 + 1] or 0) * 255)
  936.                     data[#data + 1] = string.char((self.buffer[(j * self.width + i) * 3 + 2] or 0) * 255)
  937.                     data[#data + 1] = string.char((self.buffer[(j * self.width + i) * 3 + 3] or 0) * 255)
  938.                 end
  939.                 data[#data + 1] = ("\0"):rep(padchars)
  940.             end
  941.             local size = #table_concat(data)
  942.             data[2] = string.char(size % 256, math_floor(size / 256) % 256, math_floor(size / 65536), 0)
  943.             size = size - 54
  944.             data[7] = string.char(size % 256, math_floor(size / 256) % 256, math_floor(size / 65536), 0)
  945.  
  946.         else
  947.             error("format not supported")
  948.         end
  949.  
  950.         data = table_concat(data)
  951.         if file then
  952.             local handle = io.open(file, "wb")
  953.             for i = 1, #data do
  954.                 handle:write(data:byte(i))
  955.             end
  956.             handle:close()
  957.         end
  958.         return data
  959.     end
  960.     function surf:drawLine(x1, y1, x2, y2, b, t, c)
  961.         if x1 == x2 then
  962.             x1, y1, x2, y2 = x1 + self.ox, y1 + self.oy, x2 + self.ox, y2 + self.oy
  963.             if x1 < self.cx or x1 >= self.cx + self.cwidth then return end
  964.             if y2 < y1 then
  965.                 local temp = y1
  966.                 y1 = y2
  967.                 y2 = temp
  968.             end
  969.             if y1 < self.cy then y1 = self.cy end
  970.             if y2 >= self.cy + self.cheight then y2 = self.cy + self.cheight - 1 end
  971.             if b or self.overwrite then
  972.                 for j = y1, y2 do
  973.                     self.buffer[(j * self.width + x1) * 3 + 1] = b
  974.                 end
  975.             end
  976.             if t or self.overwrite then
  977.                 for j = y1, y2 do
  978.                     self.buffer[(j * self.width + x1) * 3 + 2] = t
  979.                 end
  980.             end
  981.             if c or self.overwrite then
  982.                 for j = y1, y2 do
  983.                     self.buffer[(j * self.width + x1) * 3 + 3] = c
  984.                 end
  985.             end
  986.         elseif y1 == y2 then
  987.             x1, y1, x2, y2 = x1 + self.ox, y1 + self.oy, x2 + self.ox, y2 + self.oy
  988.             if y1 < self.cy or y1 >= self.cy + self.cheight then return end
  989.             if x2 < x1 then
  990.                 local temp = x1
  991.                 x1 = x2
  992.                 x2 = temp
  993.             end
  994.             if x1 < self.cx then x1 = self.cx end
  995.             if x2 >= self.cx + self.cwidth then x2 = self.cx + self.cwidth - 1 end
  996.             if b or self.overwrite then
  997.                 for i = x1, x2 do
  998.                     self.buffer[(y1 * self.width + i) * 3 + 1] = b
  999.                 end
  1000.             end
  1001.             if t or self.overwrite then
  1002.                 for i = x1, x2 do
  1003.                     self.buffer[(y1 * self.width + i) * 3 + 2] = t
  1004.                 end
  1005.             end
  1006.             if c or self.overwrite then
  1007.                 for i = x1, x2 do
  1008.                     self.buffer[(y1 * self.width + i) * 3 + 3] = c
  1009.                 end
  1010.             end
  1011.         else
  1012.             local delta_x = x2 - x1
  1013.             local ix = delta_x > 0 and 1 or -1
  1014.             delta_x = 2 * math.abs(delta_x)
  1015.             local delta_y = y2 - y1
  1016.             local iy = delta_y > 0 and 1 or -1
  1017.             delta_y = 2 * math.abs(delta_y)
  1018.             self:drawPixel(x1, y1, b, t, c)
  1019.             if delta_x >= delta_y then
  1020.                 local error = delta_y - delta_x / 2
  1021.                 while x1 ~= x2 do
  1022.                     if (error >= 0) and ((error ~= 0) or (ix > 0)) then
  1023.                         error = error - delta_x
  1024.                         y1 = y1 + iy
  1025.                     end
  1026.                     error = error + delta_y
  1027.                     x1 = x1 + ix
  1028.                     self:drawPixel(x1, y1, b, t, c)
  1029.                 end
  1030.             else
  1031.                 local error = delta_x - delta_y / 2
  1032.                 while y1 ~= y2 do
  1033.                     if (error >= 0) and ((error ~= 0) or (iy > 0)) then
  1034.                         error = error - delta_y
  1035.                         x1 = x1 + ix
  1036.                     end
  1037.                     error = error + delta_x
  1038.                     y1 = y1 + iy
  1039.                     self:drawPixel(x1, y1, b, t, c)
  1040.                 end
  1041.             end
  1042.         end
  1043.     end
  1044.  
  1045.     function surf:drawRect(x, y, width, height, b, t, c)
  1046.         self:drawLine(x, y, x + width - 1, y, b, t, c)
  1047.         self:drawLine(x, y, x, y + height - 1, b, t, c)
  1048.         self:drawLine(x + width - 1, y, x + width - 1, y + height - 1, b, t, c)
  1049.         self:drawLine(x, y + height - 1, x + width - 1, y + height - 1, b, t, c)
  1050.     end
  1051.  
  1052.     function surf:fillRect(x, y, width, height, b, t, c)
  1053.         x, y, width, height = clipRect(x + self.ox, y + self.oy, width, height, self.cx, self.cy, self.cwidth, self.cheight)
  1054.  
  1055.         if b or self.overwrite then
  1056.             for j = 0, height - 1 do
  1057.                 for i = 0, width - 1 do
  1058.                     self.buffer[((j + y) * self.width + i + x) * 3 + 1] = b
  1059.                 end
  1060.             end
  1061.         end
  1062.         if t or self.overwrite then
  1063.             for j = 0, height - 1 do
  1064.                 for i = 0, width - 1 do
  1065.                     self.buffer[((j + y) * self.width + i + x) * 3 + 2] = t
  1066.                 end
  1067.             end
  1068.         end
  1069.         if c or self.overwrite then
  1070.             for j = 0, height - 1 do
  1071.                 for i = 0, width - 1 do
  1072.                     self.buffer[((j + y) * self.width + i + x) * 3 + 3] = c
  1073.                 end
  1074.             end
  1075.         end
  1076.     end
  1077.  
  1078.     function surf:drawTriangle(x1, y1, x2, y2, x3, y3, b, t, c)
  1079.         self:drawLine(x1, y1, x2, y2, b, t, c)
  1080.         self:drawLine(x2, y2, x3, y3, b, t, c)
  1081.         self:drawLine(x3, y3, x1, y1, b, t, c)
  1082.     end
  1083.  
  1084.     function surf:fillTriangle(x1, y1, x2, y2, x3, y3, b, t, c)
  1085.         if y1 > y2 then
  1086.             local tempx, tempy = x1, y1
  1087.             x1, y1 = x2, y2
  1088.             x2, y2 = tempx, tempy
  1089.         end
  1090.         if y1 > y3 then
  1091.             local tempx, tempy = x1, y1
  1092.             x1, y1 = x3, y3
  1093.             x3, y3 = tempx, tempy
  1094.         end
  1095.         if y2 > y3 then
  1096.             local tempx, tempy = x2, y2
  1097.             x2, y2 = x3, y3
  1098.             x3, y3 = tempx, tempy
  1099.         end
  1100.         if y1 == y2 and x1 > x2 then
  1101.             local temp = x1
  1102.             x1 = x2
  1103.             x2 = temp
  1104.         end
  1105.         if y2 == y3 and x2 > x3 then
  1106.             local temp = x2
  1107.             x2 = x3
  1108.             x3 = temp
  1109.         end
  1110.  
  1111.         local x4, y4
  1112.         if x1 <= x2 then
  1113.             x4 = x1 + (y2 - y1) / (y3 - y1) * (x3 - x1)
  1114.             y4 = y2
  1115.             local tempx, tempy = x2, y2
  1116.             x2, y2 = x4, y4
  1117.             x4, y4 = tempx, tempy
  1118.         else
  1119.             x4 = x1 + (y2 - y1) / (y3 - y1) * (x3 - x1)
  1120.             y4 = y2
  1121.         end
  1122.  
  1123.         local finvslope1 = (x2 - x1) / (y2 - y1)
  1124.         local finvslope2 = (x4 - x1) / (y4 - y1)
  1125.         local linvslope1 = (x3 - x2) / (y3 - y2)
  1126.         local linvslope2 = (x3 - x4) / (y3 - y4)
  1127.  
  1128.         local xstart, xend, dxstart, dxend
  1129.         for y = math.ceil(y1 + 0.5) - 0.5, math.floor(y3 - 0.5) + 0.5, 1 do
  1130.             if y <= y2 then -- first half
  1131.                 xstart = x1 + finvslope1 * (y - y1)
  1132.                 xend = x1 + finvslope2 * (y - y1)
  1133.             else -- second half
  1134.                 xstart = x3 - linvslope1 * (y3 - y)
  1135.                 xend = x3 - linvslope2 * (y3 - y)
  1136.             end
  1137.  
  1138.             dxstart, dxend = math.ceil(xstart - 0.5), math.floor(xend - 0.5)
  1139.             if dxstart <= dxend then
  1140.                 self:drawLine(dxstart, y - 0.5, dxend, y - 0.5, b, t, c)
  1141.             end
  1142.         end
  1143.     end
  1144.  
  1145.     function surf:drawEllipse(x, y, width, height, b, t, c)
  1146.         for i = 0, _eprc - 1 do
  1147.             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)
  1148.         end
  1149.     end
  1150.  
  1151.     function surf:fillEllipse(x, y, width, height, b, t, c)
  1152.         x, y = x + self.ox, y + self.oy
  1153.  
  1154.         local sx, sy
  1155.         for j = 0, height - 1 do
  1156.             for i = 0, width - 1 do
  1157.                 sx, sy = i + x, j + y
  1158.                 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
  1159.                     if b or self.overwrite then
  1160.                         self.buffer[(sy * self.width + sx) * 3 + 1] = b
  1161.                     end
  1162.                     if t or self.overwrite then
  1163.                         self.buffer[(sy * self.width + sx) * 3 + 2] = t
  1164.                     end
  1165.                     if c or self.overwrite then
  1166.                         self.buffer[(sy * self.width + sx) * 3 + 3] = c
  1167.                     end
  1168.                 end
  1169.             end
  1170.         end
  1171.     end
  1172.  
  1173.     function surf:drawArc(x, y, width, height, fromangle, toangle, b, t, c)
  1174.         if fromangle > toangle then
  1175.             local temp = fromangle
  1176.             fromangle = toangle
  1177.             temp = toangle
  1178.         end
  1179.         fromangle = math_floor(fromangle / math.pi / 2 * _eprc + 0.5)
  1180.         toangle = math_floor(toangle / math.pi / 2 * _eprc + 0.5) - 1
  1181.  
  1182.         for j = fromangle, toangle do
  1183.             local i = j % _eprc
  1184.             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)
  1185.         end
  1186.     end
  1187.  
  1188.     function surf:fillArc(x, y, width, height, fromangle, toangle, b, t, c)
  1189.         x, y = x + self.ox, y + self.oy
  1190.  
  1191.         if fromangle > toangle then
  1192.             local temp = fromangle
  1193.             fromangle = toangle
  1194.             temp = toangle
  1195.         end
  1196.         local diff = toangle - fromangle
  1197.         fromangle = fromangle % (math.pi * 2)
  1198.  
  1199.         local fx, fy, sx, sy, dir
  1200.         for j = 0, height - 1 do
  1201.             for i = 0, width - 1 do
  1202.                 fx, fy = (i + 0.5) / width * 2 - 1, (j + 0.5) / height * 2 - 1
  1203.                 sx, sy = i + x, j + y
  1204.                 dir = math_atan2(-fy, fx) % (math.pi * 2)
  1205.                 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
  1206.                     if b or self.overwrite then
  1207.                         self.buffer[(sy * self.width + sx) * 3 + 1] = b
  1208.                     end
  1209.                     if t or self.overwrite then
  1210.                         self.buffer[(sy * self.width + sx) * 3 + 2] = t
  1211.                     end
  1212.                     if c or self.overwrite then
  1213.                         self.buffer[(sy * self.width + sx) * 3 + 3] = c
  1214.                     end
  1215.                 end
  1216.             end
  1217.         end
  1218.     end
  1219.     function surf:drawSurface(surf2, x, y, width, height, sx, sy, swidth, sheight)
  1220.         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
  1221.  
  1222.         if width == swidth and height == sheight then
  1223.             local nx, ny
  1224.             nx, ny, width, height = clipRect(x, y, width, height, self.cx, self.cy, self.cwidth, self.cheight)
  1225.             swidth, sheight = width, height
  1226.             if nx > x then
  1227.                 sx = sx + nx - x
  1228.                 x = nx
  1229.             end
  1230.             if ny > y then
  1231.                 sy = sy + ny - y
  1232.                 y = ny
  1233.             end
  1234.             nx, ny, swidth, sheight = clipRect(sx, sy, swidth, sheight, 0, 0, surf2.width, surf2.height)
  1235.             width, height = swidth, sheight
  1236.             if nx > sx then
  1237.                 x = x + nx - sx
  1238.                 sx = nx
  1239.             end
  1240.             if ny > sy then
  1241.                 y = y + ny - sy
  1242.                 sy = ny
  1243.             end
  1244.  
  1245.             local b, t, c
  1246.             for j = 0, height - 1 do
  1247.                 for i = 0, width - 1 do
  1248.                     b = surf2.buffer[((j + sy) * surf2.width + i + sx) * 3 + 1]
  1249.                     t = surf2.buffer[((j + sy) * surf2.width + i + sx) * 3 + 2]
  1250.                     c = surf2.buffer[((j + sy) * surf2.width + i + sx) * 3 + 3]
  1251.                     if b or self.overwrite then
  1252.                         self.buffer[((j + y) * self.width + i + x) * 3 + 1] = b
  1253.                     end
  1254.                     if t or self.overwrite then
  1255.                         self.buffer[((j + y) * self.width + i + x) * 3 + 2] = t
  1256.                     end
  1257.                     if c or self.overwrite then
  1258.                         self.buffer[((j + y) * self.width + i + x) * 3 + 3] = c
  1259.                     end
  1260.                 end
  1261.             end
  1262.         else
  1263.             local hmirror, vmirror = false, false
  1264.             if width < 0 then
  1265.                 hmirror = true
  1266.                 x = x + width
  1267.             end
  1268.             if height < 0 then
  1269.                 vmirror = true
  1270.                 y = y + height
  1271.             end
  1272.             if swidth < 0 then
  1273.                 hmirror = not hmirror
  1274.                 sx = sx + swidth
  1275.             end
  1276.             if sheight < 0 then
  1277.                 vmirror = not vmirror
  1278.                 sy = sy + sheight
  1279.             end
  1280.             width, height, swidth, sheight = math.abs(width), math.abs(height), math.abs(swidth), math.abs(sheight)
  1281.  
  1282.             local xscale, yscale, px, py, ssx, ssy, b, t, c = swidth / width, sheight / height
  1283.             for j = 0, height - 1 do
  1284.                 for i = 0, width - 1 do
  1285.                     px, py = math_floor((i + 0.5) * xscale), math_floor((j + 0.5) * yscale)
  1286.                     if hmirror then
  1287.                         ssx = x + width - i - 1
  1288.                     else
  1289.                         ssx = i + x
  1290.                     end
  1291.                     if vmirror then
  1292.                         ssy = y + height - j - 1
  1293.                     else
  1294.                         ssy = j + y
  1295.                     end
  1296.  
  1297.                     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
  1298.                         b = surf2.buffer[(py * surf2.width + px) * 3 + 1]
  1299.                         t = surf2.buffer[(py * surf2.width + px) * 3 + 2]
  1300.                         c = surf2.buffer[(py * surf2.width + px) * 3 + 3]
  1301.                         if b or self.overwrite then
  1302.                             self.buffer[(ssy * self.width + ssx) * 3 + 1] = b
  1303.                         end
  1304.                         if t or self.overwrite then
  1305.                             self.buffer[(ssy * self.width + ssx) * 3 + 2] = t
  1306.                         end
  1307.                         if c or self.overwrite then
  1308.                             self.buffer[(ssy * self.width + ssx) * 3 + 3] = c
  1309.                         end
  1310.                     end
  1311.                 end
  1312.             end
  1313.         end
  1314.     end
  1315.  
  1316.     function surf:drawSurfaceRotated(surf2, x, y, ox, oy, angle)
  1317.         local sin, cos, sx, sy, px, py = math.sin(angle), math.cos(angle)
  1318.         for j = math.floor(-surf2.height * 0.75), math.ceil(surf2.height * 0.75) do
  1319.             for i = math.floor(-surf2.width * 0.75), math.ceil(surf2.width * 0.75) do
  1320.                 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)
  1321.                 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
  1322.                     b = surf2.buffer[(py * surf2.width + px) * 3 + 1]
  1323.                     t = surf2.buffer[(py * surf2.width + px) * 3 + 2]
  1324.                     c = surf2.buffer[(py * surf2.width + px) * 3 + 3]
  1325.                     if b or self.overwrite then
  1326.                         self.buffer[(sy * self.width + sx) * 3 + 1] = b
  1327.                     end
  1328.                     if t or self.overwrite then
  1329.                         self.buffer[(sy * self.width + sx) * 3 + 2] = t
  1330.                     end
  1331.                     if c or self.overwrite then
  1332.                         self.buffer[(sy * self.width + sx) * 3 + 3] = c
  1333.                     end
  1334.                 end
  1335.             end
  1336.         end
  1337.     end
  1338.  
  1339.     function surf:drawSurfacesInterlaced(surfs, x, y, step)
  1340.         x, y, step = x + self.ox, y + self.oy, step or 0
  1341.         local width, height = surfs[1].width, surfs[1].height
  1342.         for i = 2, #surfs do
  1343.             if surfs[i].width ~= width or surfs[i].height ~= height then
  1344.                 error("surfaces should be the same size")
  1345.             end
  1346.         end
  1347.  
  1348.         local sx, sy, swidth, sheight, index, b, t, c = clipRect(x, y, width, height, self.cx, self.cy, self.cwidth, self.cheight)
  1349.         for j = sy, sy + sheight - 1 do
  1350.             for i = sx, sx + swidth - 1 do
  1351.                 index = (i + j + step) % #surfs + 1
  1352.                 b = surfs[index].buffer[((j - sy) * surfs[index].width + i - sx) * 3 + 1]
  1353.                 t = surfs[index].buffer[((j - sy) * surfs[index].width + i - sx) * 3 + 2]
  1354.                 c = surfs[index].buffer[((j - sy) * surfs[index].width + i - sx) * 3 + 3]
  1355.                 if b or self.overwrite then
  1356.                     self.buffer[(j * self.width + i) * 3 + 1] = b
  1357.                 end
  1358.                 if t or self.overwrite then
  1359.                     self.buffer[(j * self.width + i) * 3 + 2] = t
  1360.                 end
  1361.                 if c or self.overwrite then
  1362.                     self.buffer[(j * self.width + i) * 3 + 3] = c
  1363.                 end
  1364.             end
  1365.         end
  1366.     end
  1367.  
  1368.     function surf:drawSurfaceSmall(surf2, x, y)
  1369.         x, y = x + self.ox, y + self.oy
  1370.         if surf2.width % 2 ~= 0 or surf2.height % 3 ~= 0 then
  1371.             error("surface width must be a multiple of 2 and surface height a multiple of 3")
  1372.         end
  1373.  
  1374.         local sub, char, c1, c2, c3, c4, c5, c6 = 32768
  1375.         for j = 0, surf2.height / 3 - 1 do
  1376.             for i = 0, surf2.width / 2 - 1 do
  1377.                 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
  1378.                     char, c1, c2, c3, c4, c5, c6 = 0,
  1379.                     surf2.buffer[((j * 3) * surf2.width + i * 2) * 3 + 1],
  1380.                     surf2.buffer[((j * 3) * surf2.width + i * 2 + 1) * 3 + 1],
  1381.                     surf2.buffer[((j * 3 + 1) * surf2.width + i * 2) * 3 + 1],
  1382.                     surf2.buffer[((j * 3 + 1) * surf2.width + i * 2 + 1) * 3 + 1],
  1383.                     surf2.buffer[((j * 3 + 2) * surf2.width + i * 2) * 3 + 1],
  1384.                     surf2.buffer[((j * 3 + 2) * surf2.width + i * 2 + 1) * 3 + 1]
  1385.                     if c1 ~= c6 then
  1386.                         sub = c1
  1387.                         char = 1
  1388.                     end
  1389.                     if c2 ~= c6 then
  1390.                         sub = c2
  1391.                         char = char + 2
  1392.                     end
  1393.                     if c3 ~= c6 then
  1394.                         sub = c3
  1395.                         char = char + 4
  1396.                     end
  1397.                     if c4 ~= c6 then
  1398.                         sub = c4
  1399.                         char = char + 8
  1400.                     end
  1401.                     if c5 ~= c6 then
  1402.                         sub = c5
  1403.                         char = char + 16
  1404.                     end
  1405.                     self.buffer[((j + y) * self.width + i + x) * 3 + 1] = c6
  1406.                     self.buffer[((j + y) * self.width + i + x) * 3 + 2] = sub
  1407.                     self.buffer[((j + y) * self.width + i + x) * 3 + 3] = _chars[128 + char]
  1408.                 end
  1409.             end
  1410.         end
  1411.     end
  1412.     function surf:flip(horizontal, vertical)
  1413.         local ox, oy, nx, ny, tb, tt, tc
  1414.         if horizontal then
  1415.             for i = 0, math.ceil(self.cwidth / 2) - 1 do
  1416.                 for j = 0, self.cheight - 1 do
  1417.                     ox, oy, nx, ny = i + self.cx, j + self.cy, self.cx + self.cwidth - i - 1, j + self.cy
  1418.                     tb = self.buffer[(oy * self.width + ox) * 3 + 1]
  1419.                     tt = self.buffer[(oy * self.width + ox) * 3 + 2]
  1420.                     tc = self.buffer[(oy * self.width + ox) * 3 + 3]
  1421.                     self.buffer[(oy * self.width + ox) * 3 + 1] = self.buffer[(ny * self.width + nx) * 3 + 1]
  1422.                     self.buffer[(oy * self.width + ox) * 3 + 2] = self.buffer[(ny * self.width + nx) * 3 + 2]
  1423.                     self.buffer[(oy * self.width + ox) * 3 + 3] = self.buffer[(ny * self.width + nx) * 3 + 3]
  1424.                     self.buffer[(ny * self.width + nx) * 3 + 1] = tb
  1425.                     self.buffer[(ny * self.width + nx) * 3 + 2] = tt
  1426.                     self.buffer[(ny * self.width + nx) * 3 + 3] = tc
  1427.                 end
  1428.             end
  1429.         end
  1430.         if vertical then
  1431.             for j = 0, math.ceil(self.cheight / 2) - 1 do
  1432.                 for i = 0, self.cwidth - 1 do
  1433.                     ox, oy, nx, ny = i + self.cx, j + self.cy, i + self.cx, self.cy + self.cheight - j - 1
  1434.                     tb = self.buffer[(oy * self.width + ox) * 3 + 1]
  1435.                     tt = self.buffer[(oy * self.width + ox) * 3 + 2]
  1436.                     tc = self.buffer[(oy * self.width + ox) * 3 + 3]
  1437.                     self.buffer[(oy * self.width + ox) * 3 + 1] = self.buffer[(ny * self.width + nx) * 3 + 1]
  1438.                     self.buffer[(oy * self.width + ox) * 3 + 2] = self.buffer[(ny * self.width + nx) * 3 + 2]
  1439.                     self.buffer[(oy * self.width + ox) * 3 + 3] = self.buffer[(ny * self.width + nx) * 3 + 3]
  1440.                     self.buffer[(ny * self.width + nx) * 3 + 1] = tb
  1441.                     self.buffer[(ny * self.width + nx) * 3 + 2] = tt
  1442.                     self.buffer[(ny * self.width + nx) * 3 + 3] = tc
  1443.                 end
  1444.             end
  1445.         end
  1446.     end
  1447.  
  1448.     function surf:shift(x, y, b, t, c)
  1449.         local hdir, vdir = x < 0, y < 0
  1450.         local xstart, xend = self.cx, self.cx + self.cwidth - 1
  1451.         local ystart, yend = self.cy, self.cy + self.cheight - 1
  1452.         local nx, ny
  1453.         for j = vdir and ystart or yend, vdir and yend or ystart, vdir and 1 or -1 do
  1454.             for i = hdir and xstart or xend, hdir and xend or xstart, hdir and 1 or -1 do
  1455.                 nx, ny = i - x, j - y
  1456.                 if nx >= 0 and nx < self.width and ny >= 0 and ny < self.height then
  1457.                     self.buffer[(j * self.width + i) * 3 + 1] = self.buffer[(ny * self.width + nx) * 3 + 1]
  1458.                     self.buffer[(j * self.width + i) * 3 + 2] = self.buffer[(ny * self.width + nx) * 3 + 2]
  1459.                     self.buffer[(j * self.width + i) * 3 + 3] = self.buffer[(ny * self.width + nx) * 3 + 3]
  1460.                 else
  1461.                     self.buffer[(j * self.width + i) * 3 + 1] = b
  1462.                     self.buffer[(j * self.width + i) * 3 + 2] = t
  1463.                     self.buffer[(j * self.width + i) * 3 + 3] = c
  1464.                 end
  1465.             end
  1466.         end
  1467.     end
  1468.  
  1469.     function surf:map(colors)
  1470.         local c
  1471.         for j = self.cy, self.cy + self.cheight - 1 do
  1472.             for i = self.cx, self.cx + self.cwidth - 1 do
  1473.                 c = colors[self.buffer[(j * self.width + i) * 3 + 1]]
  1474.                 if c or self.overwrite then
  1475.                     self.buffer[(j * self.width + i) * 3 + 1] = c
  1476.                 end
  1477.             end
  1478.         end
  1479.     end
  1480.     surface.palette = { }
  1481.     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"}
  1482.     surface.palette.riko4 = {"181818","1D2B52","7E2553","008651","AB5136","5F564F","7D7F82","FF004C","FFA300","FFF023","00E755","29ADFF","82769C","FF77A9","FECCA9","ECECEC"}
  1483.     surface.palette.redirection = {[0]="040404",[1]="FFFFFF"}
  1484.  
  1485.     local function setPalette(palette)
  1486.         if palette == _palette then return end
  1487.         _palette = palette
  1488.         _rgbpal, _palr, _palg, _palb = { }, { }, { }, { }
  1489.  
  1490.         local indices = { }
  1491.         for k, v in pairs(_palette) do
  1492.             if type(v) == "string" then
  1493.                 _palr[k] = tonumber(v:sub(1, 2), 16) / 255
  1494.                 _palg[k] = tonumber(v:sub(3, 4), 16) / 255
  1495.                 _palb[k] = tonumber(v:sub(5, 6), 16) / 255
  1496.             elseif type(v) == "number" then
  1497.                 _palr[k] = math.floor(v / 65536) / 255
  1498.                 _palg[k] = (math.floor(v / 256) % 256) / 255
  1499.                 _palb[k] = (v % 256) / 255
  1500.             end
  1501.             indices[#indices + 1] = k
  1502.         end
  1503.  
  1504.         local pr, pg, pb, dist, d, id
  1505.         for i = 0, _steps - 1 do
  1506.             for j = 0, _steps - 1 do
  1507.                 for k = 0, _steps - 1 do
  1508.                     pr = (i + 0.5) / _steps
  1509.                     pg = (j + 0.5) / _steps
  1510.                     pb = (k + 0.5) / _steps
  1511.  
  1512.                     dist = 1e10
  1513.                     for l = 1, #indices do
  1514.                         d = (pr - _palr[indices[l]]) ^ 2 + (pg - _palg[indices[l]]) ^ 2 + (pb - _palb[indices[l]]) ^ 2
  1515.                         if d < dist then
  1516.                             dist = d
  1517.                             id = l
  1518.                         end
  1519.                     end
  1520.                     _rgbpal[i * _steps * _steps + j * _steps + k + 1] = indices[id]
  1521.                 end
  1522.             end
  1523.         end
  1524.     end
  1525.  
  1526.  
  1527.  
  1528.     function surf:toRGB(palette)
  1529.         setPalette(palette)
  1530.         local c
  1531.         for j = 0, self.height - 1 do
  1532.             for i = 0, self.width - 1 do
  1533.                 c = self.buffer[(j * self.width + i) * 3 + 1]
  1534.                 self.buffer[(j * self.width + i) * 3 + 1] = _palr[c]
  1535.                 self.buffer[(j * self.width + i) * 3 + 2] = _palg[c]
  1536.                 self.buffer[(j * self.width + i) * 3 + 3] = _palb[c]
  1537.             end
  1538.         end
  1539.     end
  1540.  
  1541.     function surf:toPalette(palette, dither)
  1542.         setPalette(palette)
  1543.         local scale, r, g, b, nr, ng, nb, c, dr, dg, db = _steps - 1
  1544.         for j = 0, self.height - 1 do
  1545.             for i = 0, self.width - 1 do
  1546.                 r = self.buffer[(j * self.width + i) * 3 + 1]
  1547.                 g = self.buffer[(j * self.width + i) * 3 + 2]
  1548.                 b = self.buffer[(j * self.width + i) * 3 + 3]
  1549.                 r = (r > 1) and 1 or r
  1550.                 r = (r < 0) and 0 or r
  1551.                 g = (g > 1) and 1 or g
  1552.                 g = (g < 0) and 0 or g
  1553.                 b = (b > 1) and 1 or b
  1554.                 b = (b < 0) and 0 or b
  1555.  
  1556.                 nr = (r == 1) and scale or math_floor(r * _steps)
  1557.                 ng = (g == 1) and scale or math_floor(g * _steps)
  1558.                 nb = (b == 1) and scale or math_floor(b * _steps)
  1559.                 c = _rgbpal[nr * _steps * _steps + ng * _steps + nb + 1]
  1560.                 if dither then
  1561.                     dr = (r - _palr[c]) / 16
  1562.                     dg = (g - _palg[c]) / 16
  1563.                     db = (b - _palb[c]) / 16
  1564.  
  1565.                     if i < self.width - 1 then
  1566.                         self.buffer[(j * self.width + i + 1) * 3 + 1] = self.buffer[(j * self.width + i + 1) * 3 + 1] + dr * 7
  1567.                         self.buffer[(j * self.width + i + 1) * 3 + 2] = self.buffer[(j * self.width + i + 1) * 3 + 2] + dg * 7
  1568.                         self.buffer[(j * self.width + i + 1) * 3 + 3] = self.buffer[(j * self.width + i + 1) * 3 + 3] + db * 7
  1569.                     end
  1570.                     if j < self.height - 1 then
  1571.                         if i > 0 then
  1572.                             self.buffer[((j + 1) * self.width + i - 1) * 3 + 1] = self.buffer[((j + 1) * self.width + i - 1) * 3 + 1] + dr * 3
  1573.                             self.buffer[((j + 1) * self.width + i - 1) * 3 + 2] = self.buffer[((j + 1) * self.width + i - 1) * 3 + 2] + dg * 3
  1574.                             self.buffer[((j + 1) * self.width + i - 1) * 3 + 3] = self.buffer[((j + 1) * self.width + i - 1) * 3 + 3] + db * 3
  1575.                         end
  1576.                         self.buffer[((j + 1) * self.width + i) * 3 + 1] = self.buffer[((j + 1) * self.width + i) * 3 + 1] + dr * 5
  1577.                         self.buffer[((j + 1) * self.width + i) * 3 + 2] = self.buffer[((j + 1) * self.width + i) * 3 + 2] + dg * 5
  1578.                         self.buffer[((j + 1) * self.width + i) * 3 + 3] = self.buffer[((j + 1) * self.width + i) * 3 + 3] + db * 5
  1579.                         if i < self.width - 1 then
  1580.                             self.buffer[((j + 1) * self.width + i + 1) * 3 + 1] = self.buffer[((j + 1) * self.width + i + 1) * 3 + 1] + dr * 1
  1581.                             self.buffer[((j + 1) * self.width + i + 1) * 3 + 2] = self.buffer[((j + 1) * self.width + i + 1) * 3 + 2] + dg * 1
  1582.                             self.buffer[((j + 1) * self.width + i + 1) * 3 + 3] = self.buffer[((j + 1) * self.width + i + 1) * 3 + 3] + db * 1
  1583.                         end
  1584.                     end
  1585.                 end
  1586.                 self.buffer[(j * self.width + i) * 3 + 1] = c
  1587.                 self.buffer[(j * self.width + i) * 3 + 2] = nil
  1588.                 self.buffer[(j * self.width + i) * 3 + 3] = nil
  1589.             end
  1590.         end
  1591.     end
  1592.     function surface.loadFont(surf)
  1593.         local font = {width = surf.width, height = surf.height - 1}
  1594.         font.buffer =  { }
  1595.         font.indices = {0}
  1596.         font.widths = { }
  1597.  
  1598.         local startc, hitc, curc = surf.buffer[((surf.height - 1) * surf.width) * 3 + 1]
  1599.         for i = 0, surf.width - 1 do
  1600.             curc = surf.buffer[((surf.height - 1) * surf.width + i) * 3 + 1]
  1601.             if curc ~= startc then
  1602.                 hitc = curc
  1603.                 break
  1604.             end
  1605.         end
  1606.  
  1607.         for j = 0, surf.height - 2 do
  1608.             for i = 0, surf.width - 1 do
  1609.                 font.buffer[j * font.width + i + 1] = surf.buffer[(j * surf.width + i) * 3 + 1] == hitc
  1610.             end
  1611.         end
  1612.  
  1613.         local curchar = 1
  1614.         for i = 0, surf.width - 1 do
  1615.             if surf.buffer[((surf.height - 1) * surf.width + i) * 3 + 1] == hitc then
  1616.                 font.widths[curchar] = i - font.indices[curchar]
  1617.                 curchar = curchar + 1
  1618.                 font.indices[curchar] = i + 1
  1619.             end
  1620.         end
  1621.         font.widths[curchar] = font.width - font.indices[curchar]
  1622.  
  1623.         return font
  1624.     end
  1625.  
  1626.     function surface.getTextSize(str, font)
  1627.         local cx, cy, maxx = 0, 0, 0
  1628.         local ox, char = cx
  1629.  
  1630.         for i = 1, #str do
  1631.             char = str:byte(i) - 31
  1632.  
  1633.             if char + 31 == 10 then -- newline
  1634.                 cx = ox
  1635.                 cy = cy + font.height + 1
  1636.             elseif font.indices[char] then
  1637.                 cx = cx + font.widths[char] + 1
  1638.             else
  1639.                 cx = cx + font.widths[1]
  1640.             end
  1641.             if cx > maxx then
  1642.                 maxx = cx
  1643.             end
  1644.         end
  1645.  
  1646.         return maxx - 1, cy + font.height
  1647.     end
  1648.  
  1649.     function surf:drawText(str, font, x, y, b, t, c)
  1650.         local cx, cy = x + self.ox, y + self.oy
  1651.         local ox, char, idx = cx
  1652.  
  1653.         for i = 1, #str do
  1654.             char = str:byte(i) - 31
  1655.  
  1656.             if char + 31 == 10 then -- newline
  1657.                 cx = ox
  1658.                 cy = cy + font.height + 1
  1659.             elseif font.indices[char] then
  1660.                 for i = 0, font.widths[char] - 1 do
  1661.                     for j = 0, font.height - 1 do
  1662.                         x, y = cx + i, cy + j
  1663.                         if font.buffer[j * font.width + i + font.indices[char] + 1] then
  1664.                             if x >= self.cx and x < self.cx + self.cwidth and y >= self.cy and y < self.cy + self.cheight then
  1665.                                 idx = (y * self.width + x) * 3
  1666.                                 if b or self.overwrite then
  1667.                                     self.buffer[idx + 1] = b
  1668.                                 end
  1669.                                 if t or self.overwrite then
  1670.                                     self.buffer[idx + 2] = t
  1671.                                 end
  1672.                                 if c or self.overwrite then
  1673.                                     self.buffer[idx + 3] = c
  1674.                                 end
  1675.                             end
  1676.                         end
  1677.                     end
  1678.                 end
  1679.                 cx = cx + font.widths[char] + 1
  1680.             else
  1681.                 cx = cx + font.widths[1]
  1682.             end
  1683.         end
  1684.     end
  1685.     local smap = { }
  1686.     surface.smap = smap
  1687.  
  1688.     function surface.loadSpriteMap(surf, spwidth, spheight, sprites)
  1689.         if surf.width % spwidth ~= 0 or surf.height % spheight ~= 0 then
  1690.             error("sprite width/height does not match smap width/height")
  1691.         end
  1692.  
  1693.         local smap = setmetatable({ }, {__index = surface.smap})
  1694.         smap.surf = surf
  1695.         smap.spwidth = spwidth
  1696.         smap.spheight = spheight
  1697.         smap.sprites = sprites or ((surf.width / spwidth) * (surf.height / spheight))
  1698.         smap.perline = surf.width / spwidth
  1699.  
  1700.         return smap
  1701.     end
  1702.  
  1703.     function smap:pos(index, scale)
  1704.         if index < 0 or index >= self.sprites then
  1705.             error("sprite index out of bounds")
  1706.         end
  1707.  
  1708.         return (index % self.perline) * self.spwidth, math.floor(index / self.perline) * self.spheight
  1709.     end
  1710.  
  1711.     function smap:sprite(index, x, y, width, height)
  1712.         local sx, sy = self:pos(index)
  1713.         return self.surf, x, y, width or self.spwidth, height or self.spheight, sx, sy, self.spwidth, self.spheight
  1714.     end
  1715. end return surface
  1716.  end)()
  1717. local fontData = (function()
  1718.   if fontData then return fontData end
  1719. return [[
  1720.     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
  1721.     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
  1722.     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
  1723.           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
  1724.     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
  1725.                                                   0                                                                                                                                                                                                                                                                                 000         00                           0       0                                    0
  1726.    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)()
  1727.  
  1728. local font = surface.loadFont(surface.load(fontData, true))
  1729.  
  1730.  
  1731. local wapi = (function()
  1732.   if wapi then return wapi end
  1733. local jua = nil
  1734. local idPatt = "#R%d+"
  1735.  
  1736. if not ((socket and socket.websocket) or http.websocketAsync) then
  1737.     error("You do not have CC:Tweaked/CCTweaks installed or you are not on the latest version.")
  1738. end
  1739.  
  1740. local newws = socket and socket.websocket or http.websocketAsync
  1741. local async
  1742. if socket and socket.websocket then
  1743.     async = false
  1744. else
  1745.     async = true
  1746. end
  1747.  
  1748. local callbackRegistry = {}
  1749. wsRegistry = {}
  1750.  
  1751. local function gfind(str, patt)
  1752.     local t = {}
  1753.     for found in str:gmatch(patt) do
  1754.         table.insert(t, found)
  1755.     end
  1756.  
  1757.     if #t > 0 then
  1758.         return t
  1759.     else
  1760.         return nil
  1761.     end
  1762. end
  1763.  
  1764. local function findID(url)
  1765.     local found = gfind(url, idPatt)
  1766.     return tonumber(found[#found]:sub(found[#found]:find("%d+")))
  1767. end
  1768.  
  1769. local function newID()
  1770.     return #callbackRegistry + 1
  1771. end
  1772.  
  1773. local function trimID(url)
  1774.     local found = gfind(url, idPatt)
  1775.     local s, e = url:find(found[#found])
  1776.     return url:sub(1, s-1)
  1777. end
  1778.  
  1779. function open(callback, url, headers)
  1780.     local id
  1781.     if async then
  1782.         id = newID()
  1783.     end
  1784.     local newUrl
  1785.     if async then
  1786.         newUrl = url .. "#R" .. id
  1787.         newws(newUrl, headers)
  1788.     else
  1789.         if headers then
  1790.             error("Websocket headers not supported under CCTweaks")
  1791.         end
  1792.         local ws = newws(url)
  1793.         ws.send = ws.write
  1794.         id = ws.id()
  1795.         wsRegistry[id] = ws
  1796.     end
  1797.     callbackRegistry[id] = callback
  1798.     return id
  1799. end
  1800.  
  1801. function init(jua)
  1802.     jua = jua
  1803.     if async then
  1804.         jua.on("websocket_success", function(event, url, handle)
  1805.             local id = findID(url)
  1806.             if type(callbackRegistry[id]) == "table" and callbackRegistry[id].success then
  1807.                 callbackRegistry[id].success(findID(url), handle)
  1808.             end
  1809.         end)
  1810.  
  1811.         jua.on("websocket_failure", function(event, url)
  1812.             local id = findID(url)
  1813.             if type(callbackRegistry[id]) == "table" and callbackRegistry[id].failure then
  1814.                 callbackRegistry[id].failure(findID(url))
  1815.             end
  1816.             table.remove(callbackRegistry, id)
  1817.         end)
  1818.  
  1819.         jua.on("websocket_message", function(event, url, data)
  1820.             local id = findID(url)
  1821.             if type(callbackRegistry[id]) == "table" and callbackRegistry[id].message then
  1822.                 callbackRegistry[id].message(findID(url), data)
  1823.             end
  1824.         end)
  1825.  
  1826.         jua.on("websocket_closed", function(event, url)
  1827.             local id = findID(url)
  1828.             if type(callbackRegistry[id]) == "table" and callbackRegistry[id].closed then
  1829.                 callbackRegistry[id].closed(findID(url))
  1830.             end
  1831.             table.remove(callbackRegistry, id)
  1832.         end)
  1833.     else
  1834.         jua.on("socket_connect", function(event, id)
  1835.             if type(callbackRegistry[id]) == "table" and callbackRegistry[id].success then
  1836.                 callbackRegistry[id].success(id, wsRegistry[id])
  1837.             end
  1838.         end)
  1839.  
  1840.         jua.on("socket_error", function(event, id, msg)
  1841.             if type(callbackRegistry[id]) == "table" and callbackRegistry[id].failure then
  1842.                 callbackRegistry[id].failure(id, msg)
  1843.             end
  1844.             table.remove(callbackRegistry, id)
  1845.         end)
  1846.  
  1847.         jua.on("socket_message", function(event, id)
  1848.             if type(callbackRegistry[id]) == "table" and callbackRegistry[id].message then
  1849.                 local data = wsRegistry[id].read()
  1850.                 callbackRegistry[id].message(id, data)
  1851.             end
  1852.         end)
  1853.  
  1854.         jua.on("socket_closed", function(event, id)
  1855.             if type(callbackRegistry[id]) == "table" and callbackRegistry[id].closed then
  1856.                 callbackRegistry[id].closed(id)
  1857.             end
  1858.             table.remove(callbackRegistry, id)
  1859.         end)
  1860.     end
  1861. end
  1862.  
  1863. return {
  1864.     open = open,
  1865.     init = init
  1866. }
  1867.  end)()
  1868. local rapi = (function()
  1869.   if rapi then return rapi end
  1870. local jua = nil
  1871. local idPatt = "#R%d+"
  1872.  
  1873. local callbackRegistry = {}
  1874.  
  1875. local function gfind(str, patt)
  1876.     local t = {}
  1877.     for found in str:gmatch(patt) do
  1878.         table.insert(t, found)
  1879.     end
  1880.  
  1881.     if #t > 0 then
  1882.         return t
  1883.     else
  1884.         return nil
  1885.     end
  1886. end
  1887.  
  1888. local function findID(url)
  1889.     local found = gfind(url, idPatt)
  1890.     if not found then
  1891.         return -1
  1892.     end
  1893.     return tonumber(found[#found]:sub(found[#found]:find("%d+")))
  1894. end
  1895.  
  1896. local function newID()
  1897.     for i = 1, math.huge do
  1898.         if not callbackRegistry[i] then
  1899.             return i
  1900.         end
  1901.     end
  1902. end
  1903.  
  1904. local function trimID(url)
  1905.     local found = gfind(url, idPatt)
  1906.     local s, e = url:find(found[#found])
  1907.     return url:sub(1, s-1)
  1908. end
  1909.  
  1910. function request(callback, url, headers, postData)
  1911.     local id = newID()
  1912.     local newUrl = url .. "#R" .. id
  1913.     callbackRegistry[id] = callback
  1914.     http.request(newUrl, postData, headers)
  1915. end
  1916.  
  1917. function init(jua)
  1918.     jua = jua
  1919.     jua.on("http_success", function(event, url, handle)
  1920.         local id = findID(url)
  1921.         if callbackRegistry[id] then
  1922.             if type(callbackRegistry[id]) == "table" and callbackRegistry[id].success then
  1923.                 callbackRegistry[id].success(true, trimID(url), handle)
  1924.             else
  1925.                 callbackRegistry[id](true, trimID(url), handle)
  1926.             end
  1927.             callbackRegistry[id] = nil
  1928.         end
  1929.     end)
  1930.  
  1931.     jua.on("http_failure", function(event, url, handle)
  1932.         local id = findID(url)
  1933.         if callbackRegistry[id] then
  1934.             if type(callbackRegistry[id]) == "table" and callbackRegistry[id].failure then
  1935.                 callbackRegistry[id].failure(false, trimID(url), handle)
  1936.             else
  1937.                 callbackRegistry[id](false, trimID(url), handle)
  1938.             end
  1939.             callbackRegistry[id] = nil
  1940.         end
  1941.     end)
  1942. end
  1943.  
  1944. return {
  1945.     request = request,
  1946.     init = init
  1947. }
  1948.  end)()
  1949. local tapi = (function()
  1950.   if tapi then return tapi end
  1951. local w
  1952. local r
  1953. local jua
  1954. local json
  1955. local await
  1956.  
  1957. local endpoint = "tenebra.lil.gay"
  1958. local wsEndpoint = "wss://"..endpoint
  1959. local httpEndpoint = "https://"..endpoint
  1960.  
  1961. local function asserttype(var, name, vartype, optional)
  1962.   if not (type(var) == vartype or optional and type(var) == "nil") then
  1963.     error(name..": expected "..vartype.." got "..type(var), 3)
  1964.   end
  1965. end
  1966.  
  1967. function init(juai, jsoni, wi, ri)
  1968.   asserttype(juai, "jua", "table")
  1969.   asserttype(jsoni, "json", "table")
  1970.   asserttype(wi, "w", "table", true)
  1971.   asserttype(ri, "r", "table")
  1972.  
  1973.   jua = juai
  1974.   await = juai.await
  1975.   json = jsoni
  1976.   w = wi
  1977.   r = ri
  1978. end
  1979.  
  1980. local function prints(...)
  1981.   local objs = {...}
  1982.   for i, obj in ipairs(objs) do
  1983.     print(textutils.serialize(obj))
  1984.   end
  1985. end
  1986.  
  1987. local function url(call)
  1988.   return httpEndpoint..call
  1989. end
  1990.  
  1991. local function api_request(cb, api, data)
  1992.   local success, _url, handle = await(r.request, url(api) .. (api:find("%%?") and "?cc" or "&cc"), {["Content-Type"]="application/json"}, data and json.encode(data))
  1993.   if success then
  1994.     cb(success, json.decode(handle.readAll()))
  1995.     handle.close()
  1996.   else
  1997.     cb(success)
  1998.   end
  1999. end
  2000.  
  2001. local function authorize_websocket(cb, privatekey)
  2002.   asserttype(cb, "callback", "function")
  2003.   asserttype(privatekey, "privatekey", "string", true)
  2004.  
  2005.   api_request(function(success, data)
  2006.     cb(success and data and data.ok, data and data.url and data.url:gsub("wss:", "wss:") or data)
  2007.   end, "/ws/start", {
  2008.     privatekey = privatekey
  2009.   })
  2010. end
  2011.  
  2012. function address(cb, address)
  2013.   asserttype(cb, "callback", "function")
  2014.   asserttype(address, "address", "string")
  2015.  
  2016.   api_request(function(success, data)
  2017.     if data.address then
  2018.       data.address.address = address
  2019.     end
  2020.     cb(success and data and data.ok, data and data.address or data)
  2021.   end, "/addresses/"..address)
  2022. end
  2023.  
  2024. function addressTransactions(cb, address, limit, offset)
  2025.   asserttype(cb, "callback", "function")
  2026.   asserttype(address, "address", "string")
  2027.   asserttype(limit, "limit", "number", true)
  2028.   asserttype(offset, "offset", "number", true)
  2029.  
  2030.   api_request(function(success, data)
  2031.     cb(success and data and data.ok, data and data.transactions or data)
  2032.   end, "/addresses/"..address.."/transactions?limit="..(limit or 50).."&offset="..(offset or 0))
  2033. end
  2034.  
  2035. function addressNames(cb, address)
  2036.   asserttype(cb, "callback", "function")
  2037.   asserttype(address, "address", "string")
  2038.  
  2039.   api_request(function(success, data)
  2040.     cb(success and data and data.ok, data and data.names or data)
  2041.   end, "/addresses/"..address.."/names")
  2042. end
  2043.  
  2044. function addresses(cb, limit, offset)
  2045.   asserttype(cb, "callback", "function")
  2046.   asserttype(limit, "limit", "number", true)
  2047.   asserttype(offset, "offset", "number", true)
  2048.  
  2049.   api_request(function(success, data)
  2050.     cb(success and data and data.ok, data and data.addresses or data)
  2051.   end, "/addresses?limit="..(limit or 50).."&offset="..(offset or 0))
  2052. end
  2053.  
  2054. function name(cb, name)
  2055.   asserttype(cb, "callback", "function")
  2056.   asserttype(name, "name", "string")
  2057.  
  2058.   api_request(function(success, data)
  2059.     cb(success and data and data.ok, data and data.name or data)
  2060.   end, "/names/"..name)
  2061. end
  2062.  
  2063. function rich(cb, limit, offset)
  2064.   asserttype(cb, "callback", "function")
  2065.   asserttype(limit, "limit", "number", true)
  2066.   asserttype(offset, "offset", "number", true)
  2067.  
  2068.   api_request(function(success, data)
  2069.     cb(success and data and data.ok, data and data.addresses or data)
  2070.   end, "/addresses/rich?limit="..(limit or 50).."&offset="..(offset or 0))
  2071. end
  2072.  
  2073. function transactions(cb, limit, offset)
  2074.   asserttype(cb, "callback", "function")
  2075.   asserttype(limit, "limit", "number", true)
  2076.   asserttype(offset, "offset", "number", true)
  2077.  
  2078.   api_request(function(success, data)
  2079.     cb(success and data and data.ok, data and data.transactions or data)
  2080.   end, "/transactions?limit="..(limit or 50).."&offset="..(offset or 0))
  2081. end
  2082.  
  2083. function latestTransactions(cb, limit, offset)
  2084.   asserttype(cb, "callback", "function")
  2085.   asserttype(limit, "limit", "number", true)
  2086.   asserttype(offset, "offset", "number", true)
  2087.  
  2088.   api_request(function(success, data)
  2089.     cb(success and data and data.ok, data and data.transactions or data)
  2090.   end, "/transactions/latest?limit="..(limit or 50).."&offset="..(offset or 0))
  2091. end
  2092.  
  2093. function transaction(cb, txid)
  2094.   asserttype(cb, "callback", "function")
  2095.   asserttype(txid, "txid", "number")
  2096.  
  2097.   api_request(function(success, data)
  2098.     cb(success and data and data.ok, data and data.transaction or data)
  2099.   end, "/transactions/"..txid)
  2100. end
  2101.  
  2102. function makeTransaction(cb, privatekey, to, amount, metadata)
  2103.   asserttype(cb, "callback", "function")
  2104.   asserttype(privatekey, "privatekey", "string")
  2105.   asserttype(to, "to", "string")
  2106.   asserttype(amount, "amount", "number")
  2107.   asserttype(metadata, "metadata", "string", true)
  2108.  
  2109.   api_request(function(success, data)
  2110.     cb(success and data and data.ok, data and data.transaction or data)
  2111.   end, "/transactions", {
  2112.     privatekey = privatekey,
  2113.     to = to,
  2114.     amount = amount,
  2115.     metadata = metadata
  2116.   })
  2117. end
  2118.  
  2119. local wsEventNameLookup = {
  2120.   blocks = "block",
  2121.   ownBlocks = "block",
  2122.   transactions = "transaction",
  2123.   ownTransactions = "transaction",
  2124.   names = "name",
  2125.   ownNames = "name",
  2126.   ownWebhooks = "webhook",
  2127.   motd = "motd"
  2128. }
  2129.  
  2130. local wsEvents = {}
  2131.  
  2132. local wsReqID = 0
  2133. local wsReqRegistry = {}
  2134. local wsEvtRegistry = {}
  2135. local wsHandleRegistry = {}
  2136.  
  2137. local function newWsID()
  2138.   local id = wsReqID
  2139.   wsReqID = wsReqID + 1
  2140.   return id
  2141. end
  2142.  
  2143. local function registerEvent(id, event, callback)
  2144.   if wsEvtRegistry[id] == nil then
  2145.     wsEvtRegistry[id] = {}
  2146.   end
  2147.  
  2148.   if wsEvtRegistry[id][event] == nil then
  2149.     wsEvtRegistry[id][event] = {}
  2150.   end
  2151.  
  2152.   table.insert(wsEvtRegistry[id][event], callback)
  2153. end
  2154.  
  2155. local function registerRequest(id, reqid, callback)
  2156.   if wsReqRegistry[id] == nil then
  2157.     wsReqRegistry[id] = {}
  2158.   end
  2159.  
  2160.   wsReqRegistry[id][reqid] = callback
  2161. end
  2162.  
  2163. local function discoverEvents(id, event)
  2164.     local evs = {}
  2165.     for k,v in pairs(wsEvtRegistry[id]) do
  2166.         if k == event or string.match(k, event) or event == "*" then
  2167.             for i,v2 in ipairs(v) do
  2168.                 table.insert(evs, v2)
  2169.             end
  2170.         end
  2171.     end
  2172.  
  2173.     return evs
  2174. end
  2175.  
  2176. wsEvents.success = function(id, handle)
  2177.   -- fire success event
  2178.   wsHandleRegistry[id] = handle
  2179.   if wsEvtRegistry[id] then
  2180.     local evs = discoverEvents(id, "success")
  2181.     for i, v in ipairs(evs) do
  2182.       v(id, handle)
  2183.     end
  2184.   end
  2185. end
  2186.  
  2187. wsEvents.failure = function(id)
  2188.   -- fire failure event
  2189.   if wsEvtRegistry[id] then
  2190.     local evs = discoverEvents(id, "failure")
  2191.     for i, v in ipairs(evs) do
  2192.       v(id)
  2193.     end
  2194.   end
  2195. end
  2196.  
  2197. wsEvents.message = function(id, data)
  2198.   data = json.decode(data)
  2199.   --print("msg:"..tostring(data.ok)..":"..tostring(data.type)..":"..tostring(data.id))
  2200.   --prints(data)
  2201.   -- handle events and responses
  2202.   if wsReqRegistry[id] and wsReqRegistry[id][tonumber(data.id)] then
  2203.     wsReqRegistry[id][tonumber(data.id)](data)
  2204.   elseif wsEvtRegistry[id] then
  2205.     local evs = discoverEvents(id, data.type)
  2206.     for i, v in ipairs(evs) do
  2207.       v(data)
  2208.     end
  2209.  
  2210.     if data.event then
  2211.       evs = discoverEvents(id, data.event)
  2212.       for i, v in ipairs(evs) do
  2213.         v(data)
  2214.       end
  2215.     end
  2216.  
  2217.     local evs2 = discoverEvents(id, "message")
  2218.     for i, v in ipairs(evs2) do
  2219.       v(id, data)
  2220.     end
  2221.   end
  2222. end
  2223.  
  2224. wsEvents.closed = function(id)
  2225.   -- fire closed event
  2226.   if wsEvtRegistry[id] then
  2227.     local evs = discoverEvents(id, "closed")
  2228.     for i, v in ipairs(evs) do
  2229.       v(id)
  2230.     end
  2231.   end
  2232. end
  2233.  
  2234. local function wsRequest(cb, id, type, data)
  2235.   local reqID = newWsID()
  2236.   registerRequest(id, reqID, function(data)
  2237.     cb(data)
  2238.   end)
  2239.   data.id = tostring(reqID)
  2240.   data.type = type
  2241.   wsHandleRegistry[id].send(json.encode(data))
  2242. end
  2243.  
  2244. local function barebonesMixinHandle(id, handle)
  2245.   handle.on = function(event, cb)
  2246.     registerEvent(id, event, cb)
  2247.   end
  2248.  
  2249.   return handle
  2250. end
  2251.  
  2252. local function mixinHandle(id, handle)
  2253.   handle.subscribe = function(cb, event, eventcb)
  2254.     local data = await(wsRequest, id, "subscribe", {
  2255.       event = event
  2256.     })
  2257.     registerEvent(id, wsEventNameLookup[event], eventcb)
  2258.     cb(data.ok, data)
  2259.   end
  2260.  
  2261.   return barebonesMixinHandle(id, handle)
  2262. end
  2263.  
  2264. function connect(cb, privatekey, preconnect)
  2265.   asserttype(cb, "callback", "function")
  2266.   asserttype(privatekey, "privatekey", "string", true)
  2267.   asserttype(preconnect, "preconnect", "function", true)
  2268.   local wsurl
  2269.   if privatekey then
  2270.     local success, auth = await(authorize_websocket, privatekey)
  2271.     wsurl = success and auth or wsEndpoint
  2272.   end
  2273.   local id = w.open(wsEvents, wsurl)
  2274.   if preconnect then
  2275.     preconnect(id, barebonesMixinHandle(id, {}))
  2276.   end
  2277.   registerEvent(id, "success", function(id, handle)
  2278.     cb(true, mixinHandle(id, handle))
  2279.   end)
  2280.   registerEvent(id, "failure", function(id)
  2281.     cb(false)
  2282.   end)
  2283. end
  2284.  
  2285. local domainMatch = "^([%l%d-_]*)@?([%l%d-]+).tst$"
  2286. local commonMetaMatch = "^(.+)=(.+)$"
  2287.  
  2288. function parseMeta(meta)
  2289.   asserttype(meta, "meta", "string")
  2290.   local tbl = {meta={}}
  2291.  
  2292.   for m in meta:gmatch("[^;]+") do
  2293.     if m:match(domainMatch) then
  2294.       -- print("Matched domain")
  2295.  
  2296.       local p1, p2 = m:match("([%l%d-_]*)@"), m:match("@?([%l%d-]+).tst")
  2297.       tbl.name = p1
  2298.       tbl.domain = p2
  2299.  
  2300.     elseif m:match(commonMetaMatch) then
  2301.       -- print("Matched common meta")
  2302.  
  2303.       local p1, p2 = m:match(commonMetaMatch)
  2304.  
  2305.       tbl.meta[p1] = p2
  2306.  
  2307.     else
  2308.       -- print("Unmatched standard meta")
  2309.  
  2310.       table.insert(tbl.meta, m)
  2311.     end
  2312.     -- print(m)
  2313.   end
  2314.   -- print(textutils.serialize(tbl))
  2315.   return tbl
  2316. end
  2317.  
  2318. local g = string.gsub
  2319. 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 "))()
  2320.  
  2321. function makeaddressbyte(byte)
  2322.   byte = 48 + math.floor(byte / 7)
  2323.   return string.char(byte + 39 > 122 and 101 or byte > 57 and byte + 39 or byte)
  2324. end
  2325.  
  2326. function makev2address(key)
  2327.   local protein = {}
  2328.   local stick = sha256(sha256(key))
  2329.   local n = 0
  2330.   local link = 0
  2331.   local v2 = "t"
  2332.   repeat
  2333.     if n < 9 then protein[n] = string.sub(stick,0,2)
  2334.     stick = sha256(sha256(stick)) end
  2335.     n = n + 1
  2336.   until n == 9
  2337.   n = 0
  2338.   repeat
  2339.     link = tonumber(string.sub(stick,1+(2*n),2+(2*n)),16) % 9
  2340.     if string.len(protein[link]) ~= 0 then
  2341.       v2 = v2 .. makeaddressbyte(tonumber(protein[link],16))
  2342.       protein[link] = ''
  2343.       n = n + 1
  2344.     else
  2345.       stick = sha256(stick)
  2346.     end
  2347.   until n == 9
  2348.   return v2
  2349. end
  2350.  
  2351. function toTenebraWalletFormat(passphrase)
  2352.   return sha256("TENEBRAWALLET"..passphrase).."-000"
  2353. end
  2354.  
  2355. return {
  2356.   init = init,
  2357.   address = address,
  2358.   addressTransactions = addressTransactions,
  2359.   addressNames = addressNames,
  2360.   addresses = addresses,
  2361.   name = name,
  2362.   rich = rich,
  2363.   transactions = transactions,
  2364.   latestTransactions = latestTransactions,
  2365.   transaction = transaction,
  2366.   makeTransaction = makeTransaction,
  2367.   connect = connect,
  2368.   parseMeta = parseMeta,
  2369.   sha256 = sha256,
  2370.   makeaddressbyte = makeaddressbyte,
  2371.   makev2address = makev2address,
  2372.   toTenebraWalletFormat = toTenebraWalletFormat
  2373. }
  2374.  end)()
  2375. local jua = (function()
  2376.   if jua then return jua end
  2377. local juaVersion = "0.0"
  2378.  
  2379. juaRunning = false
  2380. eventRegistry = {}
  2381. timedRegistry = {}
  2382.  
  2383. local function registerEvent(event, callback)
  2384.     if eventRegistry[event] == nil then
  2385.         eventRegistry[event] = {}
  2386.     end
  2387.  
  2388.     table.insert(eventRegistry[event], callback)
  2389. end
  2390.  
  2391. local function registerTimed(time, repeating, callback)
  2392.     if repeating then
  2393.         callback(true)
  2394.     end
  2395.  
  2396.     table.insert(timedRegistry, {
  2397.         time = time,
  2398.         repeating = repeating,
  2399.         callback = callback,
  2400.         timer = os.startTimer(time)
  2401.     })
  2402. end
  2403.  
  2404. local function discoverEvents(event)
  2405.     local evs = {}
  2406.     for k,v in pairs(eventRegistry) do
  2407.         if k == event or string.match(k, event) or event == "*" then
  2408.             for i,v2 in ipairs(v) do
  2409.                 table.insert(evs, v2)
  2410.             end
  2411.         end
  2412.     end
  2413.  
  2414.     return evs
  2415. end
  2416.  
  2417. function on(event, callback)
  2418.     registerEvent(event, callback)
  2419. end
  2420.  
  2421. function setInterval(callback, time)
  2422.     registerTimed(time, true, callback)
  2423. end
  2424.  
  2425. function setTimeout(callback, time)
  2426.     registerTimed(time, false, callback)
  2427. end
  2428.  
  2429. function tick()
  2430.     local eargs = {os.pullEventRaw()}
  2431.     local event = eargs[1]
  2432.  
  2433.     if eventRegistry[event] == nil then
  2434.         eventRegistry[event] = {}
  2435.     else
  2436.         local evs = discoverEvents(event)
  2437.         for i, v in ipairs(evs) do
  2438.             v(unpack(eargs))
  2439.         end
  2440.     end
  2441.  
  2442.     if event == "timer" then
  2443.         local timer = eargs[2]
  2444.  
  2445.         for i = #timedRegistry, 1, -1 do
  2446.             local v = timedRegistry[i]
  2447.             if v.timer == timer then
  2448.                 v.callback(not v.repeating or nil)
  2449.  
  2450.                 if v.repeating then
  2451.                     v.timer = os.startTimer(v.time)
  2452.                 else
  2453.                     table.remove(timedRegistry, i)
  2454.                 end
  2455.             end
  2456.         end
  2457.     end
  2458. end
  2459.  
  2460. function run()
  2461.     os.queueEvent("init")
  2462.     juaRunning = true
  2463.     while juaRunning do
  2464.         tick()
  2465.     end
  2466. end
  2467.  
  2468. function go(func)
  2469.     on("init", func)
  2470.     run()
  2471. end
  2472.  
  2473. function stop()
  2474.     juaRunning = false
  2475. end
  2476.  
  2477. function await(func, ...)
  2478.     local args = {...}
  2479.     local out
  2480.     local finished
  2481.     func(function(...)
  2482.         out = {...}
  2483.         finished = true
  2484.     end, unpack(args))
  2485.     while not finished do tick() end
  2486.     return unpack(out)
  2487. end
  2488.  
  2489. return {
  2490.     on = on,
  2491.     setInterval = setInterval,
  2492.     setTimeout = setTimeout,
  2493.     tick = tick,
  2494.     run = run,
  2495.     go = go,
  2496.     stop = stop,
  2497.     await = await
  2498. }
  2499.  end)()
  2500.  
  2501. local logger = (function()
  2502.   if logger then return logger end
  2503.  
  2504. local logger = {}
  2505. local slackURL = config.slackURL
  2506. local discordURL = config.discordURL
  2507. local slackName = config.slackName
  2508. local discordName = config.discordName
  2509. local externName
  2510.  
  2511. local webhookHeaders = {["Content-Type"] = "application/json", ["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0"}
  2512.  
  2513. local function time()
  2514.   return os.epoch("utc")
  2515. end
  2516.  
  2517. function logger.init(prints, tExternName, noColor)
  2518.   logger.printf = prints and print or function() end
  2519.   logger.handle = fs.open("/log", "a")
  2520.   logger.color = not noColor
  2521.  
  2522.   externName = tExternName or os.getComputerLabel() or "Computer - " .. os.getComputerID()
  2523. end
  2524.  
  2525. function logger.log(text)
  2526.   if logger.color then
  2527.     term.setTextColor(colors.white)
  2528.   end
  2529.   logger.printf(text)
  2530.   logger.handle.write(text .. "\n")
  2531.   logger.handle.flush()
  2532. end
  2533.  
  2534. function logger.info(text, externRelay, quiet)
  2535.   if logger.color then
  2536.     term.setTextColor(colors.gray)
  2537.   end
  2538.   logger.printf("[" .. time() .. "] [INFO] " .. text)
  2539.  
  2540.   if not quiet then
  2541.     logger.handle.write("[" .. time() .. "] [INFO] " .. text .. "\n")
  2542.     logger.handle.flush()
  2543.   end
  2544.  
  2545.   if externRelay == "important" then
  2546.     logger.externMention(text)
  2547.   elseif externRelay then
  2548.     logger.externInfo(text)
  2549.   end
  2550. end
  2551.  
  2552. function logger.warn(text, externRelay, quiet)
  2553.   if logger.color then
  2554.     term.setTextColor(colors.yellow)
  2555.   end
  2556.   logger.printf("[" .. time() .. "] [WARN] " .. text)
  2557.  
  2558.   if not quiet then
  2559.     logger.handle.write("[" .. time() .. "] [WARN] " .. text .. "\n")
  2560.     logger.handle.flush()
  2561.   end
  2562.  
  2563.   if externRelay then
  2564.     logger.externMention(text)
  2565.   end
  2566. end
  2567.  
  2568. function logger.error(text, externRelay, quiet)
  2569.   if logger.color then
  2570.     term.setTextColor(colors.red)
  2571.   end
  2572.   logger.printf("[" .. time() .. "] [ERROR] " .. text)
  2573.  
  2574.   if not quiet then
  2575.     logger.handle.write("[" .. time() .. "] [ERROR] " .. text .. "\n")
  2576.     logger.handle.flush()
  2577.   end
  2578.  
  2579.   if externRelay then
  2580.     logger.externMention(text)
  2581.   end
  2582. end
  2583.  
  2584. function logger.externInfo(text)
  2585.   if slackURL then
  2586.     http.post(slackURL,  textutils.serializeJSON({username = externName, text = text}), webhookHeaders)
  2587.   end
  2588.  
  2589.   if discordURL then
  2590.     http.post(discordURL, textutils.serializeJSON({username = externName, content = text}), webhookHeaders)
  2591.   end
  2592. end
  2593.  
  2594. function logger.externMention(text)
  2595.   if slackURL then
  2596.     if slackName then
  2597.       http.post(slackURL, textutils.serializeJSON({username = externName, text = "<@" .. slackName .. "> " .. text}), webhookHeaders)
  2598.     else
  2599.       http.post(slackURL, textutils.serializeJSON({username = externName, text = "<@" .. slackName .. "> " .. text}), webhookHeaders)
  2600.     end
  2601.   end
  2602.  
  2603.   if discordURL then
  2604.     if discordName then
  2605.       http.post(discordURL, textutils.serializeJSON({username = externName, content = "<@" .. discordName .. "> " .. text}), webhookHeaders)
  2606.     else
  2607.       http.post(discordURL, textutils.serializeJSON({username = externName, content = text}), webhookHeaders)
  2608.     end
  2609.   end
  2610. end
  2611.  
  2612. function logger.close()
  2613.   logger.handle.close()
  2614. end
  2615.  
  2616. return logger
  2617.  end)()
  2618. logger.init(true, config.title, not term.isColor())
  2619. successTools.logger = logger
  2620.  
  2621. local json = (function()
  2622.   if json then return json end
  2623. local json = {}
  2624.  
  2625. ------------------------------------------------------------------ utils
  2626. local controls = {["\n"]="\\n", ["\r"]="\\r", ["\t"]="\\t", ["\b"]="\\b", ["\f"]="\\f", ["\""]="\\\"", ["\\"]="\\\\"}
  2627.  
  2628. local function isArray(t)
  2629.     local max = 0
  2630.     for k,v in pairs(t) do
  2631.         if type(k) ~= "number" then
  2632.             return false
  2633.         elseif k > max then
  2634.             max = k
  2635.         end
  2636.     end
  2637.     return max == #t
  2638. end
  2639.  
  2640. local whites = {['\n']=true; ['\r']=true; ['\t']=true; [' ']=true; [',']=true; [':']=true}
  2641. local function removeWhite(str)
  2642.     while whites[str:sub(1, 1)] do
  2643.         str = str:sub(2)
  2644.     end
  2645.     return str
  2646. end
  2647.  
  2648. ------------------------------------------------------------------ encoding
  2649.  
  2650. local function encodeCommon(val, pretty, tabLevel, tTracking)
  2651.     local str = ""
  2652.  
  2653.     -- Tabbing util
  2654.     local function tab(s)
  2655.         str = str .. ("\t"):rep(tabLevel) .. s
  2656.     end
  2657.  
  2658.     local function arrEncoding(val, bracket, closeBracket, iterator, loopFunc)
  2659.         str = str .. bracket
  2660.         if pretty then
  2661.             str = str .. "\n"
  2662.             tabLevel = tabLevel + 1
  2663.         end
  2664.         for k,v in iterator(val) do
  2665.             tab("")
  2666.             loopFunc(k,v)
  2667.             str = str .. ","
  2668.             if pretty then str = str .. "\n" end
  2669.         end
  2670.         if pretty then
  2671.             tabLevel = tabLevel - 1
  2672.         end
  2673.         if str:sub(-2) == ",\n" then
  2674.             str = str:sub(1, -3) .. "\n"
  2675.         elseif str:sub(-1) == "," then
  2676.             str = str:sub(1, -2)
  2677.         end
  2678.         tab(closeBracket)
  2679.     end
  2680.  
  2681.     -- Table encoding
  2682.     if type(val) == "table" then
  2683.         assert(not tTracking[val], "Cannot encode a table holding itself recursively")
  2684.         tTracking[val] = true
  2685.         if isArray(val) then
  2686.             arrEncoding(val, "[", "]", ipairs, function(k,v)
  2687.                 str = str .. encodeCommon(v, pretty, tabLevel, tTracking)
  2688.             end)
  2689.         else
  2690.             arrEncoding(val, "{", "}", pairs, function(k,v)
  2691.                 assert(type(k) == "string", "JSON object keys must be strings", 2)
  2692.                 str = str .. encodeCommon(k, pretty, tabLevel, tTracking)
  2693.                 str = str .. (pretty and ": " or ":") .. encodeCommon(v, pretty, tabLevel, tTracking)
  2694.             end)
  2695.         end
  2696.         -- String encoding
  2697.     elseif type(val) == "string" then
  2698.         str = '"' .. val:gsub("[%c\"\\]", controls) .. '"'
  2699.         -- Number encoding
  2700.     elseif type(val) == "number" or type(val) == "boolean" then
  2701.         str = tostring(val)
  2702.     else
  2703.         error("JSON only supports arrays, objects, numbers, booleans, and strings", 2)
  2704.     end
  2705.     return str
  2706. end
  2707.  
  2708. function json.encode(val)
  2709.     return encodeCommon(val, false, 0, {})
  2710. end
  2711.  
  2712. function json.encodePretty(val)
  2713.     return encodeCommon(val, true, 0, {})
  2714. end
  2715.  
  2716. ------------------------------------------------------------------ decoding
  2717.  
  2718. local decodeControls = {}
  2719. for k,v in pairs(controls) do
  2720.     decodeControls[v] = k
  2721. end
  2722.  
  2723. function json.parseBoolean(str)
  2724.     if str:sub(1, 4) == "true" then
  2725.         return true, removeWhite(str:sub(5))
  2726.     else
  2727.         return false, removeWhite(str:sub(6))
  2728.     end
  2729. end
  2730.  
  2731. function json.parseNull(str)
  2732.     return nil, removeWhite(str:sub(5))
  2733. end
  2734.  
  2735. local numChars = {['e']=true; ['E']=true; ['+']=true; ['-']=true; ['.']=true}
  2736. function json.parseNumber(str)
  2737.     local i = 1
  2738.     while numChars[str:sub(i, i)] or tonumber(str:sub(i, i)) do
  2739.         i = i + 1
  2740.     end
  2741.     local val = tonumber(str:sub(1, i - 1))
  2742.     str = removeWhite(str:sub(i))
  2743.     return val, str
  2744. end
  2745.  
  2746. function json.parseString(str)
  2747.     str = str:sub(2)
  2748.     local s = ""
  2749.     while str:sub(1,1) ~= "\"" do
  2750.         local next = str:sub(1,1)
  2751.         str = str:sub(2)
  2752.         assert(next ~= "\n", "Unclosed string")
  2753.  
  2754.         if next == "\\" then
  2755.             local escape = str:sub(1,1)
  2756.             str = str:sub(2)
  2757.  
  2758.             next = assert(decodeControls[next..escape], "Invalid escape character")
  2759.         end
  2760.  
  2761.         s = s .. next
  2762.     end
  2763.     return s, removeWhite(str:sub(2))
  2764. end
  2765.  
  2766. function json.parseArray(str)
  2767.     str = removeWhite(str:sub(2))
  2768.  
  2769.     local val = {}
  2770.     local i = 1
  2771.     while str:sub(1, 1) ~= "]" do
  2772.         local v = nil
  2773.         v, str = json.parseValue(str)
  2774.         val[i] = v
  2775.         i = i + 1
  2776.         str = removeWhite(str)
  2777.     end
  2778.     str = removeWhite(str:sub(2))
  2779.     return val, str
  2780. end
  2781.  
  2782. function json.parseObject(str)
  2783.     str = removeWhite(str:sub(2))
  2784.  
  2785.     local val = {}
  2786.     while str:sub(1, 1) ~= "}" do
  2787.         local k, v = nil, nil
  2788.         k, v, str = json.parseMember(str)
  2789.         val[k] = v
  2790.         str = removeWhite(str)
  2791.     end
  2792.     str = removeWhite(str:sub(2))
  2793.     return val, str
  2794. end
  2795.  
  2796. function json.parseMember(str)
  2797.     local k = nil
  2798.     k, str = json.parseValue(str)
  2799.     local val = nil
  2800.     val, str = json.parseValue(str)
  2801.     return k, val, str
  2802. end
  2803.  
  2804. function json.parseValue(str)
  2805.     local fchar = str:sub(1, 1)
  2806.     if fchar == "{" then
  2807.         return json.parseObject(str)
  2808.     elseif fchar == "[" then
  2809.         return json.parseArray(str)
  2810.     elseif tonumber(fchar) ~= nil or numChars[fchar] then
  2811.         return json.parseNumber(str)
  2812.     elseif str:sub(1, 4) == "true" or str:sub(1, 5) == "false" then
  2813.         return json.parseBoolean(str)
  2814.     elseif fchar == "\"" then
  2815.         return json.parseString(str)
  2816.     elseif str:sub(1, 4) == "null" then
  2817.         return json.parseNull(str)
  2818.     end
  2819.     return nil
  2820. end
  2821.  
  2822. function json.decode(str)
  2823.     str = removeWhite(str)
  2824.     t = json.parseValue(str)
  2825.     return t
  2826. end
  2827.  
  2828. function json.decodeFromFile(path)
  2829.     local file = assert(fs.open(path, "r"))
  2830.     local decoded = json.decode(file.readAll())
  2831.     file.close()
  2832.     return decoded
  2833. end
  2834.  
  2835. return json
  2836.  end)()
  2837.  
  2838.  
  2839. local versionURL = "http://xenon.its-em.ma/version"
  2840.  
  2841. if config.checkForUpdates ~= false then
  2842.   rapi.request(function(success, url, handle)
  2843.     if success then
  2844.       if url == versionURL then
  2845.         local release = handle.readAll()
  2846.         handle.close()
  2847.  
  2848.         if release ~= versionTag then
  2849.           logger.warn("Version mismatch, latest release is "
  2850.             .. release .. ", but running version is " .. versionTag)
  2851.  
  2852.           if release:match("v(%d+)") ~= versionTag:match("v(%d+)") then
  2853.             logger.warn("Latest version has a major version seperation gap, it may not be safe to update. Review the changelog for more details.")
  2854.           end
  2855.         end
  2856.       end
  2857.     else
  2858.       if url == versionURL then
  2859.         logger.warn("Unable to fetch release data")
  2860.       end
  2861.     end
  2862.   end, versionURL)
  2863. end
  2864.  
  2865.  
  2866. --== Initialize Renderer ==--
  2867.  
  2868. local defaultLayout =
  2869. [[
  2870. <body>
  2871.     <header>My Shop</header>
  2872.     <aside>Welcome! To make a purchase, use /pay to send the exact amount
  2873.         of Tenebra to the respective address. Excess Tenebra will be refunded.</aside>
  2874.     <table class="stock-table">
  2875.         <thead>
  2876.             <tr>
  2877.                 <th class="stock">Stock</th>
  2878.                 <th class="name">Item Name</th>
  2879.                 <th class="price">Price</th>
  2880.                 <th class="addy">Address</th>
  2881.             </tr>
  2882.         </thead>
  2883.         <tbody>
  2884.             <tr id="row-template">
  2885.                 <td id="stock"></td>
  2886.                 <td id="name"></td>
  2887.                 <td class="price-container"><span id="price"></span>tst/i</td>
  2888.            <!-- <td id="price-per-stack"></td> -->
  2889.            <!-- <td id="addy"></td> -->
  2890.                 <td id="addy-full"></td>
  2891.             </tr>
  2892.         </tbody>
  2893.     </table>
  2894.     <details>By @Emma</details>
  2895. </body>
  2896. ]]
  2897. local defaultStyles =
  2898. [[
  2899. /* Think of this file as your reference:
  2900.     Everything that can be customized is in here
  2901.     And everything that is not in here, cannot be customized */
  2902.  
  2903. colors {
  2904.     --white:     #F0F0F0;
  2905.     --orange:    #F2B233;
  2906.     --magenta:   #E57FD8;
  2907.     --lightBlue: #99B2F2;
  2908.     --yellow:    #DEDE6C;
  2909.     --lime:      #7FCC19;
  2910.     --pink:      #F2B2CC;
  2911.     --gray:      #4C4C4C;
  2912.     --lightGray: #999999;
  2913.     --cyan:      #4C99B2;
  2914.     --purple:    #B266E5;
  2915.     --blue:      #3366CC;
  2916.     --brown:     #7F664C;
  2917.     --green:     #57A64E;
  2918.     --red:       #CC4C4C;
  2919.     --black:     #191919;
  2920. }
  2921.  
  2922. * {
  2923.     display: block;
  2924. }
  2925.  
  2926. body {
  2927.     background-color: lightBlue;
  2928. }
  2929.  
  2930. header {
  2931.     position: relative;
  2932.  
  2933.     /* content: "My Shop"; */
  2934.     /* background: url(myLogo.nfp); */
  2935.     /* background-position: center; */
  2936.     /* Content and Background (Images) are mutually exclusive, with content taking precedence if both are present */
  2937.  
  2938.     background-color: blue;
  2939.  
  2940.     width: 100%;
  2941.     padding: 1px;
  2942.  
  2943.     color: white;
  2944.     text-align: left;
  2945.  
  2946.     font-size: 2em; /* Either 1em or 2em, no other sizes are currently supported */
  2947. }
  2948.  
  2949. aside {
  2950.     position: absolute;
  2951.     right: 0;
  2952.  
  2953.     /* Since `top` (and bottom) are omitted here, it will be positioned relative to the last positioned element,
  2954.         which is exactly where we want it to be.. */
  2955.  
  2956.     width: 30px;
  2957.     height: 100rem; /* In Xenon CSS, rem doesn't stand for Root Em, it stands for Remaining space, so this is 100% of the remaining space */
  2958.  
  2959.    text-align: left;
  2960.    padding: 1px;
  2961.    
  2962.    background-color: cyan;
  2963.    color: white;
  2964. }
  2965.  
  2966. table {
  2967.    position: relative;
  2968.  
  2969.    background-color: lightBlue;
  2970.  
  2971.    width: calc(100% - 30px);
  2972.    height: calc(100rem - 2px); /* See note under aside height */
  2973. }
  2974.  
  2975. td {
  2976.    color: white;
  2977.    padding: 0 1px 0 0;
  2978. }
  2979.  
  2980. .stock {
  2981.    color: white;
  2982.    text-align: right;
  2983.    width: 7px;
  2984. }
  2985.  
  2986. .stock.low {
  2987.    color: yellow;
  2988. }
  2989.  
  2990. .stock.critical {
  2991.    color: red;
  2992. }
  2993.  
  2994. .name {
  2995.    flex: 1; /* tr elements implicitly have flex-box like behavior, it is the only element that (currently) supports this feature */
  2996. }
  2997.  
  2998. .price-container, .price {
  2999.    text-align: right;
  3000.    width: 10px;
  3001. }
  3002.  
  3003. .addy-full, .addy {
  3004.    color: white;
  3005.    width: 15px;
  3006. }
  3007.  
  3008. th {
  3009.    color: blue;
  3010.    padding: 1px 1px 1px 0;
  3011. }
  3012.  
  3013. /* This is a pretty unreliable rule, just saves a few chars */
  3014. /* If you plan on changing the structure, you will most likely need to change this */
  3015. th:nth-child(2n + 1) {
  3016.    text-align: right;
  3017. }
  3018.  
  3019. details {
  3020.    position: absolute;
  3021.    left: 0;
  3022.    bottom: 0;
  3023.  
  3024.    background-color: transparent;
  3025.    color: white;
  3026.  
  3027.    width: calc(100% - 30px);
  3028.    height: 1px;
  3029. }
  3030. ]]
  3031. local userLayout = fs.open(config.layout or "layout.html", "r")
  3032. local userStyles = fs.open(config.styles or "styles.css", "r")
  3033.  
  3034. local layout, styles = defaultLayout, defaultStyles
  3035. if userLayout then
  3036.  layout = userLayout.readAll()
  3037.  userLayout.close()
  3038. end
  3039.  
  3040. if userStyles then
  3041.  styles = userStyles.readAll()
  3042.  userStyles.close()
  3043. end
  3044.  
  3045. local renderer = (function()
  3046.  if renderer then return renderer end
  3047. local renderer = {}
  3048. renderer.model = {}
  3049. renderer.styles = { {}, {} }
  3050.  
  3051.  
  3052. local xmlutils = (function()
  3053.  if xmlutils then return xmlutils end
  3054. local xmlutils = {}
  3055.  
  3056. local INVERSE_ESCAPE_MAP = {
  3057.  ["\\a"] = "\a", ["\\b"] = "\b", ["\\f"] = "\f", ["\\n"] = "\n", ["\\r"] = "\r",
  3058.  ["\\t"] = "\t", ["\\v"] = "\v", ["\\\\"] = "\\",
  3059. }
  3060.  
  3061. local specialEscapes = {
  3062.  nbsp = " ", amp = "&", tenebra = "\164"
  3063. }
  3064.  
  3065. local function consumeWhitespace(wBuffer)
  3066.  local nPos = wBuffer:find("%S")
  3067.  return wBuffer:sub(nPos or #wBuffer + 1)
  3068. end
  3069.  
  3070. function xmlutils.parse(buffer)
  3071.  local tagStack = {children = {}}
  3072.  
  3073.  local parsePoint = tagStack
  3074.  
  3075.  local next = buffer:find("%<%!%-%-")
  3076.  while next do
  3077.    local endComment = buffer:find("%-%-%>", next + 4)
  3078.    buffer = buffer:sub(1, next - 1) .. buffer:sub(endComment + 3)
  3079.  
  3080.    next = buffer:find("%<%!%-%-")
  3081.  end
  3082.  
  3083.  local ntWhite = buffer:find("%S")
  3084.  
  3085.  while ntWhite do
  3086.    buffer = buffer:sub(ntWhite)
  3087.  
  3088.    local nxtLoc, _, capt = buffer:find("(%<%/?)%s*[a-zA-Z0-9_%:]+")
  3089.    if nxtLoc ~= 1 and buffer:sub(1,3) ~= "<![" then
  3090.      --Text node probably
  3091.      if nxtLoc ~= buffer:find("%<") then
  3092.        -- Syntax error
  3093.        error("Unexpected character")
  3094.      end
  3095.  
  3096.      local cnt = buffer:sub(1, nxtLoc - 1)
  3097.  
  3098.      local replaceSearch = 1
  3099.      while true do
  3100.        local esBegin, esEnd, code = cnt:find("%&([%w#]%w-)%;", replaceSearch)
  3101.        if not esBegin then break end
  3102.  
  3103.        local replacement = specialEscapes[code]
  3104.        if not replacement then
  3105.          if code:match("^#%d+$") then
  3106.            replacement = string.char(tonumber(code:sub(2)))
  3107.          else
  3108.            error("Unknown replacement '" .. code .. "' in xml")
  3109.          end
  3110.        end
  3111.  
  3112.        cnt = cnt:sub(1, esBegin - 1) .. replacement .. cnt:sub(esEnd + 1)
  3113.        replaceSearch = esBegin + 1
  3114.      end
  3115.  
  3116.      parsePoint.children[#parsePoint.children + 1] = {type = "text", content = cnt, parent = parsePoint}
  3117.      buffer = buffer:sub(nxtLoc)
  3118.    elseif nxtLoc == 1 and capt == "</" then
  3119.      -- Closing tag
  3120.      local _, endC, closingName = buffer:find("%<%/%s*([a-zA-Z0-9%_%-%:]+)")
  3121.      if closingName == parsePoint.name then
  3122.        -- All good!
  3123.        parsePoint = parsePoint.parent
  3124.  
  3125.        local _, endTagPos = buffer:find("%s*>")
  3126.        if not endTagPos then
  3127.          -- Improperly terminated terminating tag... how?
  3128.          error("Improperly terminated terminating tag...")
  3129.        end
  3130.  
  3131.        buffer = buffer:sub(endTagPos + 1)
  3132.      else
  3133.        -- BAD! Someone forgot to close their tag, gonna be strict and throw
  3134.        -- TODO?: Add stack unwind to attempt to still parse?
  3135.        error("Unterminated '" .. tostring(parsePoint.name) .. "' tag")
  3136.      end
  3137.    else
  3138.      -- Proper node
  3139.  
  3140.      if buffer:sub(1, 9) == "<![CDATA[" then
  3141.        parsePoint.children[#parsePoint.children + 1] = {type = "cdata", parent = parsePoint}
  3142.  
  3143.        local ctepos = buffer:find("%]%]%>")
  3144.        if not ctepos then
  3145.          -- Syntax error
  3146.          error("Unterminated CDATA")
  3147.        end
  3148.  
  3149.        parsePoint.children[#parsePoint.children].content = buffer:sub(10, ctepos - 1)
  3150.  
  3151.        buffer = buffer:sub(ctepos + 3)
  3152.      else
  3153.  
  3154.        parsePoint.children[#parsePoint.children + 1] = {type = "normal", children = {}, properties = {}, parent = parsePoint}
  3155.        parsePoint = parsePoint.children[#parsePoint.children]
  3156.  
  3157.        local _, eTp, tagName = buffer:find("%<%s*([a-zA-Z0-9%_%-%:]+)")
  3158.        parsePoint.name = tagName
  3159.  
  3160.        buffer = buffer:sub(eTp + 1)
  3161.  
  3162.        local sp, ep
  3163.        repeat
  3164.          buffer = consumeWhitespace(buffer)
  3165.  
  3166.          local nChar, eChar, propName = buffer:find("([a-zA-Z0-9%_%-%:]+)")
  3167.          if nChar == 1 then
  3168.            local nextNtWhite, propMatch = (buffer:find("%S", eChar + 1))
  3169.            if not nextNtWhite then
  3170.              error("Unexpected EOF")
  3171.            end
  3172.            buffer = buffer:sub(nextNtWhite)
  3173.  
  3174.            buffer = consumeWhitespace(buffer)
  3175.  
  3176.            local eqP = buffer:find("%=")
  3177.            if eqP ~= 1 then
  3178.              error("Expected '='")
  3179.            end
  3180.  
  3181.            buffer = buffer:sub(eqP + 1)
  3182.  
  3183.            nextNtWhite, _, propMatch = buffer:find("(%S)")
  3184.  
  3185.            if tonumber(propMatch) then
  3186.              -- Gon be a num
  3187.              local _, endNP, wholeNum = buffer:find("([0-9%.]+)")
  3188.  
  3189.              if tonumber(wholeNum) then
  3190.                parsePoint.properties[propName] = tonumber(wholeNum)
  3191.              else
  3192.                error("Unfinished number")
  3193.              end
  3194.  
  3195.              buffer = buffer:sub(endNP + 1)
  3196.            elseif propMatch == "\"" or propMatch == "'" then
  3197.              -- Gon be a string
  3198.  
  3199.              buffer = buffer:sub(nextNtWhite)
  3200.  
  3201.              local terminationPt = buffer:find("[^%\\]%" .. propMatch) + 1
  3202.  
  3203.              local buildStr = buffer:sub(2, terminationPt - 1)
  3204.  
  3205.              local repPl, _, repMatch = buildStr:find("(%\\.)")
  3206.              while repMatch do
  3207.                local replS = INVERSE_ESCAPE_MAP[repMatch] or repMatch:sub(2)
  3208.                buildStr = buildStr:sub(1, repPl - 1) .. replS .. buildStr:sub(repPl + 2)
  3209.                repPl, _, repMatch = buildStr:find("(%\\.)")
  3210.              end
  3211.  
  3212.              parsePoint.properties[propName] = buildStr
  3213.  
  3214.              buffer = buffer:sub(terminationPt + 1)
  3215.            else
  3216.              error("Unexpected property, expected number or string")
  3217.            end
  3218.          end
  3219.  
  3220.          sp, ep = buffer:find("%s*%/?>")
  3221.          if not sp then
  3222.            error("Unterminated tag")
  3223.          end
  3224.        until sp == 1
  3225.  
  3226.        local selfTerm = buffer:sub(ep - 1, ep - 1)
  3227.        if selfTerm == "/" then
  3228.          -- Self terminating tag
  3229.          parsePoint = parsePoint.parent
  3230.        end
  3231.  
  3232.        buffer = buffer:sub(ep + 1)
  3233.      end
  3234.    end
  3235.  
  3236.    ntWhite = buffer:find("%S")
  3237.  end
  3238.  
  3239.  return tagStack
  3240. end
  3241.  
  3242. local prettyXML
  3243. do
  3244.  local ESCAPE_MAP = {
  3245.    ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", ["\r"] = "\\r",
  3246.    ["\t"] = "\\t", ["\v"] = "\\v", ["\\"] = "\\\\",
  3247.  }
  3248.  
  3249.  local function escape(s)
  3250.    s = s:gsub("([%c\\])", ESCAPE_MAP)
  3251.    local dq = s:find("\"")
  3252.     if dq then
  3253.       return s:gsub("\"", "\\\"")
  3254.     else
  3255.       return s
  3256.     end
  3257.   end
  3258.  
  3259.   local root = false
  3260.   prettyXML = function(parsedXML, spPos)
  3261.     spPos = spPos or 0
  3262.  
  3263.     local amRoot
  3264.     if root then
  3265.       amRoot = false
  3266.     else
  3267.       amRoot = true
  3268.       root = true
  3269.     end
  3270.  
  3271.     local str = ""
  3272.     local newFlag = false
  3273.     for i = 1, #parsedXML.children do
  3274.       local elm = parsedXML.children[i]
  3275.  
  3276.       if elm.type == "normal" then
  3277.         str = str .. (" "):rep(spPos) .. "<" .. elm.name
  3278.  
  3279.         for k, v in pairs(elm.properties) do
  3280.           str = str .. " " .. k .. "="
  3281.           if type(v) == "number" then
  3282.             str = str .. v
  3283.           else
  3284.             str = str .. "\"" .. escape(v) .. "\""
  3285.           end
  3286.         end
  3287.  
  3288.         if elm.children and #elm.children ~= 0 then
  3289.           str = str .. ">\n"
  3290.  
  3291.           local ret, fl = prettyXML(elm, spPos + 2)
  3292.           if fl then
  3293.             str = str:sub(1, #str - 1) .. ret
  3294.           else
  3295.             str = str .. ret
  3296.           end
  3297.  
  3298.           str = str .. (fl and "" or (" "):rep(spPos)) .. "</" .. elm.name .. ">\n"
  3299.         else
  3300.           str = str .. "></" .. elm.name .. ">\n"
  3301.         end
  3302.       elseif elm.type == "cdata" then
  3303.         str = str .. (" "):rep(spPos) .. "<![CDATA[" .. elm.content .. "]]>\n"
  3304.       elseif elm.type == "text" then
  3305.         if #parsedXML.children == 1 then
  3306.           str = elm.content
  3307.           newFlag = true
  3308.         else
  3309.           str = str .. (" "):rep(spPos) .. elm.content .. "\n"
  3310.         end
  3311.       end
  3312.     end
  3313.  
  3314.     if amRoot then
  3315.       root = false
  3316.       return str
  3317.     else
  3318.       return str, newFlag
  3319.     end
  3320.   end
  3321. end
  3322.  
  3323. xmlutils.pretty = prettyXML
  3324.  
  3325. return xmlutils
  3326.  end)()
  3327. local css = (function()
  3328.   if css then return css end
  3329. -- CSS Parser
  3330.  
  3331. local function trim(str)
  3332.   return str:match("%s*(.+)"):reverse():match("%s*(.+)"):reverse()
  3333. end
  3334.  
  3335. return function(toParse)
  3336.   local ruleset = {}
  3337.   local order = {}
  3338.  
  3339.   local next = toParse:find("%/%*")
  3340.   while next do
  3341.     local endComment = toParse:find("%*%/", next + 2)
  3342.     toParse = toParse:sub(1, next - 1) .. toParse:sub(endComment + 2)
  3343.  
  3344.     next = toParse:find("%/%*")
  3345.   end
  3346.  
  3347.   for IRules in toParse:gmatch("%s*([^{}]+%s-%b{})") do
  3348.     local applicatorStr = IRules:match("^[^{}]+")
  3349.     local applicators = {}
  3350.  
  3351.     for applicator in applicatorStr:gmatch("[^,]+") do
  3352.       applicators[#applicators + 1] = #ruleset + 1
  3353.       ruleset[#ruleset + 1] = {trim(applicator), {}}
  3354.     end
  3355.  
  3356.     local contents = IRules:match("%b{}"):sub(2, -2)
  3357.  
  3358.     for rule in contents:gmatch("[^%;]+") do
  3359.       local name = rule:match("^%s-([^%s%:]+)")
  3360.       if name then
  3361.         local rest = rule:match("%:%s*(.+)"):reverse():match("%s*(.+)"):reverse()
  3362.  
  3363.         for i = 1, #applicators do local applicator = applicators[i] -- do
  3364.           ruleset[applicator][2][#ruleset[applicator][2] + 1] = {name, rest}
  3365.         end
  3366.       end
  3367.     end
  3368.   end
  3369.  
  3370.   return ruleset
  3371. end
  3372.  end)()
  3373.  
  3374. -- Components
  3375. local components = (function()
  3376.   if components then return components end
  3377.  
  3378. local tableComponent = (function()
  3379.   if tableComponent then return tableComponent end
  3380. local tableComponent = {}
  3381.  
  3382. local function makeTextEl(content, parent)
  3383.   return {
  3384.     type = "text",
  3385.     content = (parent.properties.prepend or "")
  3386.               .. content ..
  3387.               (parent.properties.append or ""),
  3388.     parent = parent
  3389.   }
  3390. end
  3391.  
  3392. local function addClass(node, class)
  3393.   local prop = node.properties
  3394.   local cc = prop.class or ""
  3395.  
  3396.   if #cc > 0 then
  3397.     local stM, enM = cc:find(class)
  3398.     if (not stM) or cc:sub(stM - 1, enM + 1):match("%S+") ~= class then
  3399.       prop.class = cc .. " " .. class
  3400.     end
  3401.   else
  3402.     prop.class = class
  3403.   end
  3404. end
  3405.  
  3406. function tableComponent.new(node, renderer)
  3407.   local t = { node = node, renderer = renderer }
  3408.  
  3409.   local rtemp = renderer.querySelector("#row-template")
  3410.   if #rtemp > 0 then
  3411.     local row = rtemp[1]
  3412.  
  3413.     for i = 1, #row.parent.children do
  3414.       if row.parent.children[i] == row then
  3415.         row.parent.children[i] = nil
  3416.       end
  3417.     end
  3418.  
  3419.     row.properties.id = nil
  3420.     t.rowTemplate = row
  3421.   end
  3422.  
  3423.   local tel = renderer.querySelector("th", node)
  3424.   for i = 1, #tel do local th = tel[i] -- do
  3425.     th.adapter = renderer.components.text.new(th)
  3426.   end
  3427.  
  3428.   return setmetatable(t, { __index = tableComponent })
  3429. end
  3430.  
  3431. function tableComponent:render(surf, position, styles, resolver)
  3432.   if styles["background-color"] then
  3433.     local c = resolver({}, "color", styles["background-color"])
  3434.     if c > 0 then
  3435.       surf:fillRect(position.left, position.top, position.width, position.height, c)
  3436.     end
  3437.   end
  3438.  
  3439.   local rows = self.renderer.querySelector("tr", self.node)
  3440.  
  3441.   local flowY = position.top
  3442.   for i = 1, #rows do local row = rows[i] -- do
  3443.     local flowX = position.left
  3444.     local maxH = 0
  3445.  
  3446.     local flexTot = 0
  3447.     local remWidth = position.width
  3448.     local widths = {}
  3449.  
  3450.     local topRowMargin,
  3451.           rightRowMargin,
  3452.           bottomRowMargin,
  3453.           leftRowMargin = util.parseOrdinalStyle(resolver, row.styles, "margin")
  3454.  
  3455.     flowX = flowX + leftRowMargin
  3456.     remWidth = remWidth - rightRowMargin
  3457.  
  3458.     flowY = flowY + topRowMargin
  3459.  
  3460.     for j = 1, #row.children do
  3461.       local td = row.children[j]
  3462.       if td.styles.width then
  3463.         local w = resolver({width = position.width, flowW = remWidth}, "width", td.styles.width)
  3464.         remWidth = remWidth - w
  3465.         widths[j] = w
  3466.       else
  3467.         flexTot = flexTot + (tonumber(td.styles.flex) or 1)
  3468.       end
  3469.     end
  3470.  
  3471.     for j = 1, #row.children do
  3472.       local td = row.children[j]
  3473.       if row.styles["line-height"] and not td.styles["line-height"] then
  3474.         td.styles["line-height"] = row.styles["line-height"]
  3475.       end
  3476.  
  3477.       local height = tonumber(td.adapter:resolveHeight(td.styles, { width = 10 }, resolver):sub(1, -3))
  3478.  
  3479.       local width
  3480.       if widths[j] then
  3481.         width = math.floor(widths[j])
  3482.       else
  3483.         width = math.floor(remWidth * ((tonumber(td.styles.flex) or 1) / flexTot))
  3484.       end
  3485.  
  3486.       local topMargin,
  3487.             rightMargin,
  3488.             bottomMargin,
  3489.             leftMargin = util.parseOrdinalStyle(resolver, td.styles, "margin")
  3490.  
  3491.       flowX = flowX + leftMargin
  3492.       -- If we mutate flowY here it accumulates
  3493.       -- However, we want flowX to accumulate, because we're rendering inline
  3494.       -- flowY = flowY + topMargin
  3495.  
  3496.       td.adapter:render(surf, {
  3497.         left = flowX,
  3498.         top = flowY + topMargin,
  3499.         width = width,
  3500.         height = height
  3501.       }, td.styles, resolver)
  3502.  
  3503.       maxH = math.max(maxH, height + bottomMargin)
  3504.  
  3505.       flowX = flowX + width + rightMargin
  3506.     end
  3507.  
  3508.     if row.styles["background-color"] then
  3509.       local c = resolver({}, "color", row.styles["background-color"])
  3510.       if c > 0 then
  3511.         surf:fillRect(position.left, flowY, position.width, maxH, c)
  3512.       end
  3513.     end
  3514.  
  3515.     flowY = flowY + maxH + bottomRowMargin
  3516.   end
  3517. end
  3518.  
  3519. function tableComponent:updateData(data)
  3520.   self.data = data
  3521.  
  3522.   -- New data so create and restyle it
  3523.   local body = self.renderer.querySelector("tbody", self.node)[1]
  3524.   if self.rowTemplate then
  3525.     local newChildren = {}
  3526.  
  3527.     local sortedList = {}
  3528.     for k, _ in pairs(data) do
  3529.       sortedList[#sortedList + 1] = k
  3530.     end
  3531.  
  3532.     table.sort(sortedList, function(str1, str2)
  3533.       local cOrder1 = transformedItems[str1].order
  3534.       local cOrder2 = transformedItems[str2].order
  3535.  
  3536.       if (cOrder1 or cOrder2) and (cOrder1 ~= cOrder2) then
  3537.         return (cOrder1 or math.huge) < (cOrder2 or math.huge)
  3538.       end
  3539.  
  3540.       str1 = transformedItems[str1].disp
  3541.       str2 = transformedItems[str2].disp
  3542.  
  3543.       local i = 0
  3544.       local c1, c2
  3545.       repeat
  3546.         i = i + 1
  3547.         c1 = str1:sub(i, i):lower()
  3548.         c2 = str2:sub(i, i):lower()
  3549.       until i == #str1 or i == #str2 or c1 ~= c2
  3550.  
  3551.       return c1:byte() < c2:byte()
  3552.     end)
  3553.  
  3554.     for sI = 1, #sortedList do
  3555.       local k = sortedList[sI]
  3556.       local v = tostring(data[sortedList[sI]])
  3557.  
  3558.       local skeleton = util.deepClone(self.rowTemplate)
  3559.       skeleton.parent = body
  3560.  
  3561.       local tel = self.renderer.querySelector("td", skeleton)
  3562.       for i = 1, #tel do local td = tel[i] -- do
  3563.         td.adapter = self.renderer.components.text.new(td)
  3564.       end
  3565.  
  3566.       local stock = self.renderer.querySelector("#stock", skeleton)[1]
  3567.       local name = self.renderer.querySelector("#name", skeleton)[1]
  3568.       local price = self.renderer.querySelector("#price", skeleton)[1]
  3569.       local pricePerStack = self.renderer.querySelector("#price-per-stack", skeleton)[1]
  3570.       local addy = self.renderer.querySelector("#addy", skeleton)[1]
  3571.       local addyFull = self.renderer.querySelector("#addy-full", skeleton)[1]
  3572.  
  3573.       if stock then
  3574.         stock.children = { makeTextEl(v, stock) }
  3575.         addClass(stock, "stock")
  3576.  
  3577.         v = tonumber(v)
  3578.         if v < (transformedItems[k].critical or config.criticalStock or 10) then
  3579.           addClass(stock, "critical")
  3580.         elseif v < (transformedItems[k].low or config.lowStock or 50) then
  3581.           addClass(stock, "low")
  3582.         end
  3583.       end
  3584.  
  3585.       if name then
  3586.         name.children = { makeTextEl(transformedItems[k].disp or k, name) }
  3587.         addClass(name, "name")
  3588.       end
  3589.  
  3590.       if price then
  3591.         price.children = { makeTextEl(transformedItems[k].price, price) }
  3592.         addClass(price, "price")
  3593.       end
  3594.  
  3595.       if pricePerStack then
  3596.         pricePerStack.children = { makeTextEl(util.round(60 / transformedItems[k].price, 2), pricePerStack) }
  3597.         addClass(pricePerStack, "price-per-stack")
  3598.       end
  3599.  
  3600.       if addy then
  3601.         addy.children = { makeTextEl(transformedItems[k].addy, addy) }
  3602.         addClass(addy, "addy")
  3603.       end
  3604.  
  3605.       if addyFull then
  3606.         addyFull.children = { makeTextEl(transformedItems[k].addy .. "@" .. config.name .. ".tst", addyFull) }
  3607.         addClass(addyFull, "addy-full")
  3608.       end
  3609.  
  3610.       newChildren[#newChildren + 1] = skeleton
  3611.     end
  3612.  
  3613.     body.children = newChildren
  3614.   end
  3615.  
  3616.   self.renderer.processStyles()
  3617. end
  3618.  
  3619. return tableComponent end)()
  3620. local basicComponent = (function()
  3621.   if basicComponent then return basicComponent end
  3622. local basicTextComponent = {}
  3623.  
  3624. local function calcWidth(text)
  3625.   if #text == 0 then return 0 end
  3626.  
  3627.   local w = -1
  3628.   for i = 1, #text do
  3629.     w = w + font.widths[string.byte(text:sub(i, i)) - 31] + 1
  3630.   end
  3631.  
  3632.   return w
  3633. end
  3634.  
  3635. local function calcSizeBig(text)
  3636.   return math.ceil(calcWidth(text) / 2) * 2, math.ceil(font.height / 3) * 3
  3637. end
  3638.  
  3639. local function writeBig(surf, text, x, y, col, bg, align, width)
  3640.   local sw, sh = calcSizeBig(text)
  3641.   local tempSurf = surface.create(sw, sh, bg)
  3642.  
  3643.   tempSurf:drawText(text, font, 0, 0, col, bg, bg)
  3644.   if align == "left" then
  3645.     surf:drawSurfaceSmall(tempSurf, x, y)
  3646.   elseif align == "center" then
  3647.     surf:drawSurfaceSmall(tempSurf, math.floor(x + (width - sw / 2) / 2), y)
  3648.   else
  3649.     surf:drawSurfaceSmall(tempSurf, width + x - sw / 2, y)
  3650.   end
  3651. end
  3652.  
  3653. local function transformText(text, styles)
  3654.   local style = styles["text-transform"]
  3655.   if style == "uppercase" then
  3656.     return text:upper()
  3657.   elseif style == "lowercase" then
  3658.     return text:lower()
  3659.   elseif style == "capitalize" then
  3660.     return text:gsub("%f[%a]%w", function(c) return c:upper() end)
  3661.   end
  3662.  
  3663.   return text
  3664. end
  3665.  
  3666. function basicTextComponent.new(node)
  3667.   return setmetatable({ node = node }, { __index = basicTextComponent })
  3668. end
  3669.  
  3670. function basicTextComponent:render(surf, position, styles, resolver)
  3671.   local bgc
  3672.   if styles["background-color"] then
  3673.     bgc = resolver({}, "color", styles["background-color"])
  3674.     if bgc > 0 then
  3675.       surf:fillRect(position.left, position.top, position.width, position.height, bgc)
  3676.     end
  3677.   end
  3678.  
  3679.   local topPad,
  3680.         rightPad,
  3681.         _, -- bottomPad is unused
  3682.         leftPad = util.parseOrdinalStyle(resolver, styles, "padding")
  3683.  
  3684.   local lineHeight = 1
  3685.   if styles["line-height"] then
  3686.     lineHeight = resolver({}, "number", styles["line-height"])
  3687.   end
  3688.  
  3689.   local cY = position.top + topPad
  3690.  
  3691.   if styles["background"] then
  3692.     local path = styles["background"]:match("url(%b())"):sub(2, -2)
  3693.     local img = surface.load(path)
  3694.  
  3695.     local mw, mh = math.ceil(img.width / 2) * 2, math.ceil(img.height / 3) * 3
  3696.     if img.width ~= mw or img.height ~= mh then
  3697.       if bgc <= 0 then
  3698.         -- Gotta guess
  3699.         bgc = 0
  3700.       end
  3701.  
  3702.       local temp = surface.create(mw, mh, bgc)
  3703.       temp:drawSurface(img, 0, 0)
  3704.  
  3705.       img = temp
  3706.     end
  3707.  
  3708.     local pos = styles["background-position"] or "center"
  3709.  
  3710.     if pos == "left" then
  3711.       surf:drawSurfaceSmall(img, position.left + leftPad, cY)
  3712.     elseif pos == "right" then
  3713.       surf:drawSurfaceSmall(img, position.left + position.width - rightPad - img.width / 2, cY)
  3714.     elseif pos == "center" then
  3715.       surf:drawSurfaceSmall(img, position.left + math.floor((position.width - rightPad - img.width / 2) / 2), cY)
  3716.     end
  3717.   elseif styles.content then
  3718.     local text = resolver({}, "string", styles.content)
  3719.     text = transformText(text, styles)
  3720.  
  3721.     if styles["font-size"] == "2em" then
  3722.       if bgc <= 0 then
  3723.         error("'font-size: 2em' requires 'background-color' to be present")
  3724.       end
  3725.  
  3726.       writeBig(surf, text,
  3727.         position.left + leftPad, cY,
  3728.         resolver({}, "color", styles.color), bgc,
  3729.         styles["text-align"] or "left", position.width - leftPad - rightPad)
  3730.     else
  3731.       util.wrappedWrite(surf, text,
  3732.         position.left + leftPad, cY, position.width - leftPad - rightPad,
  3733.         resolver({}, "color", styles.color), styles["text-align"] or "left", lineHeight)
  3734.     end
  3735.   else
  3736.     if styles["font-size"] == "2em" then
  3737.       if bgc <= 0 then
  3738.         error("'font-size: 2em' requires 'background-color' to be present")
  3739.       end
  3740.  
  3741.       -- TODO Wrapping support?
  3742.       local text = self.node.children[1].content or ""
  3743.       text = transformText(text, styles)
  3744.       writeBig(surf, text,
  3745.         position.left + leftPad, cY,
  3746.         resolver({}, "color", styles.color), bgc,
  3747.         styles["text-align"] or "left", position.width - leftPad - rightPad)
  3748.     else
  3749.       local children = self.node.children
  3750.       local acc = ""
  3751.  
  3752.       for i = 1, #children do local child = children[i] -- do
  3753.         if child.type == "text" then
  3754.           acc = acc .. child.content
  3755.         elseif child.name == "br" then
  3756.           acc = transformText(acc, styles)
  3757.  
  3758.           cY = util.wrappedWrite(surf, acc,
  3759.             position.left + leftPad, cY, position.width - leftPad - rightPad,
  3760.             resolver({}, "color", styles.color), styles["text-align"] or "left", lineHeight)
  3761.           acc = ""
  3762.         elseif child.name == "span" then
  3763.           acc = acc .. child.children[1].content
  3764.         end
  3765.       end
  3766.       if #acc > 0 then
  3767.         acc = transformText(acc, styles)
  3768.  
  3769.         util.wrappedWrite(surf, acc,
  3770.           position.left + leftPad, cY, position.width - leftPad - rightPad,
  3771.           resolver({}, "color", styles.color), styles["text-align"] or "left", lineHeight)
  3772.       end
  3773.     end
  3774.   end
  3775. end
  3776.  
  3777. function basicTextComponent:resolveHeight(styles, context, resolver)
  3778.   local topPad,
  3779.         rightPad,
  3780.         bottomPad,
  3781.         leftPad = util.parseOrdinalStyle(resolver, styles, "padding")
  3782.  
  3783.   local cY = 0
  3784.  
  3785.   if styles["background"] then
  3786.     local path = styles["background"]:match("url(%b())"):sub(2, -2)
  3787.     local img = surface.load(path)
  3788.  
  3789.     cY = math.ceil(img.height / 3)
  3790.   elseif styles["font-size"] == "2em" then
  3791.     cY = math.ceil(font.height / 3)
  3792.   elseif styles.content then
  3793.     cY = util.wrappedWrite(nil, resolver({}, "string", styles.content),
  3794.       0, cY, context.width - leftPad - rightPad)
  3795.   else
  3796.     local children = self.node.children
  3797.     local acc = ""
  3798.     for i = 1, #children do local child = children[i] -- do
  3799.       if child.type == "text" then
  3800.         acc = acc .. child.content
  3801.       elseif child.name == "br" then
  3802.         cY = util.wrappedWrite(nil, acc,
  3803.           position.left + leftPad, cY, position.width - leftPad - rightPad)
  3804.         acc = ""
  3805.         cY = cY + 1
  3806.       elseif child.name == "span" then
  3807.         acc = acc .. child.children[1].content
  3808.       end
  3809.     end
  3810.     cY = cY + 1
  3811.   end
  3812.  
  3813.   if styles["line-height"] then
  3814.     cY = cY * resolver({}, "number", styles["line-height"])
  3815.   end
  3816.  
  3817.   return (topPad + bottomPad + cY) .. "px"
  3818. end
  3819.  
  3820. return basicTextComponent
  3821.  end)()
  3822.  
  3823. return {
  3824.   table = tableComponent,
  3825.   header = basicComponent,
  3826.   aside = basicComponent,
  3827.   details = basicComponent,
  3828.   div = basicComponent,
  3829.   text = basicComponent
  3830. }
  3831.  end)()
  3832.  
  3833. renderer.components = components
  3834.  
  3835. local function deepMap(set, func, level)
  3836.   level = level or 1
  3837.  
  3838.   for i = 1, #set.children do local child = set.children[i] -- do
  3839.     func(child, level)
  3840.     if child.children then
  3841.       deepMap(child, func, level + 1)
  3842.     end
  3843.   end
  3844. end
  3845.  
  3846. local function queryMatch(el, selector)
  3847.   if el.type ~= "normal" then return false end
  3848.  
  3849.   if selector == "*" then
  3850.     return true
  3851.   else
  3852.     local namesToMatch = selector:match("^([^:]+):?")
  3853.     local psuedoSelector = selector:match(":(.+)")
  3854.  
  3855.     for nameToMatch in namesToMatch:gmatch("[%.%#]?[^%.%#]+") do
  3856.       if nameToMatch:match("^%#") then -- Matching an id
  3857.         if el.properties.id ~= nameToMatch:match("^%#(.+)") then
  3858.           return false
  3859.         end
  3860.       elseif nameToMatch:match("^%.") then -- Matching a class
  3861.         if el.properties.class then
  3862.           local good = false
  3863.           for class in el.properties.class:gmatch("%S+") do
  3864.             if class == nameToMatch:match("^%.(.+)") then
  3865.               good = true
  3866.               break
  3867.             end
  3868.           end
  3869.  
  3870.           if not good then
  3871.             return false
  3872.           end
  3873.         else
  3874.           return false
  3875.         end
  3876.       elseif el.name ~= nameToMatch then -- Matching an element
  3877.         return false
  3878.       end
  3879.     end
  3880.  
  3881.     if psuedoSelector then
  3882.       local pfunc = psuedoSelector:match("[^%(%)]+")
  3883.       local args = psuedoSelector:match("%b()"):sub(2, -2)
  3884.  
  3885.       if pfunc == "nth-child" then
  3886.         local nf = -1
  3887.         local op = "+"
  3888.         local ofs = 0
  3889.         for actor in args:gmatch("%S+") do
  3890.           local nn = actor:match("(%d+)n")
  3891.           if nn then nf = tonumber(nn) else
  3892.             local nop = actor:match("[%+%-]")
  3893.             if nop then op = nop else
  3894.               ofs = tonumber(actor)
  3895.             end
  3896.           end
  3897.         end
  3898.  
  3899.         local acn = 0
  3900.         for i = 1, #el.parent.children do
  3901.           if el.parent.children[i] == el then
  3902.             acn = i
  3903.             break
  3904.           end
  3905.         end
  3906.  
  3907.         local acndebug = acn -- TODO REMOVE ME
  3908.  
  3909.         if op == "+" then
  3910.           acn = acn - ofs
  3911.         else
  3912.           acn = acn + ofs
  3913.         end
  3914.  
  3915.         if nf ~= -1 then
  3916.           if acn / nf % 1 ~= 0 then
  3917.             return false
  3918.           end
  3919.         else
  3920.           if acn ~= 0 then
  3921.             return false
  3922.           end
  3923.         end
  3924.       end
  3925.     end
  3926.  
  3927.     return true
  3928.   end
  3929. end
  3930.  
  3931. local function querySelector(selector, startingNode)
  3932.   local steps = {}
  3933.   local step = ""
  3934.   local brace = 0
  3935.   for c in selector:gmatch(".") do
  3936.     if c:match("%s") and brace == 0 then
  3937.       steps[#steps + 1] = step
  3938.       step = ""
  3939.     else
  3940.       step = step .. c
  3941.       if c:match("[%(%{]") then
  3942.         brace = brace + 1
  3943.       elseif c:match("[%)%}]") then
  3944.         brace = brace - 1
  3945.       end
  3946.     end
  3947.   end
  3948.   steps[#steps + 1] = step
  3949.  
  3950.   local matches = {}
  3951.   deepMap(startingNode or renderer.model, function(el, level)
  3952.     if #steps > level then return end -- Cannot possibly match the selector so optimize a bit
  3953.  
  3954.     local stillMatches = true
  3955.     local activeEl = el
  3956.     for outLev = #steps, 1, -1 do
  3957.       if not queryMatch(activeEl, steps[outLev]) then
  3958.         stillMatches = false
  3959.         break
  3960.       end
  3961.  
  3962.       activeEl = el.parent
  3963.     end
  3964.  
  3965.     if stillMatches then
  3966.       matches[#matches + 1] = el
  3967.     end
  3968.   end)
  3969.  
  3970.   return matches
  3971. end
  3972.  
  3973. local function parseHex(hexStr)
  3974.   if hexStr:sub(1, 1) ~= "#" then
  3975.     error("'" .. hexStr .. "' is not a hex string")
  3976.   end
  3977.   hexStr = hexStr:sub(2)
  3978.  
  3979.   local len = #hexStr
  3980.   local finalNums = {}
  3981.  
  3982.   if len == 3 then
  3983.     for c in hexStr:gmatch(".") do
  3984.       finalNums[#finalNums + 1] = tonumber(c, 16) / 15
  3985.     end
  3986.   elseif len % 2 == 0 then
  3987.     for c in hexStr:gmatch("..") do
  3988.       finalNums[#finalNums + 1] = tonumber(c, 16) / 255
  3989.     end
  3990.   else
  3991.     error("'#" .. hexStr .. "' is of invalid length")
  3992.   end
  3993.  
  3994.   return finalNums
  3995. end
  3996.  
  3997. local function parseOffset(numStr)
  3998.   if numStr == "0" then
  3999.     return { "pixel", 0 }
  4000.   elseif numStr:match("%d+px") then
  4001.     return { "pixel", tonumber(numStr:match("%d+")) }
  4002.   elseif numStr:match("%d+rem") then
  4003.     return { "remain", tonumber(numStr:match("%d+")) }
  4004.   elseif numStr:match("%d+%%") then
  4005.     return { "percent", tonumber(numStr:match("%d+")) }
  4006.   end
  4007. end
  4008.  
  4009. local function matchCalc(str)
  4010.   local op = str:match("[%+%-]")
  4011.   local v1 = str:match("%(%s*([^%+%-%s]+)")
  4012.   local v2 = str:match("([^%+%-%s]+)%s*%)")
  4013.  
  4014.   return op, v1, v2
  4015. end
  4016.  
  4017. local function resolveVal(context, extra, valStr)
  4018.   if valStr == "unset" then
  4019.     return nil
  4020.   end
  4021.  
  4022.   local type = type(extra) == "table" and extra.type or extra
  4023.  
  4024.   if type == "string" then
  4025.     local dq = valStr:match("\"([^\"]+)\"")
  4026.     if dq then return dq end
  4027.  
  4028.     local sq = valStr:match("'([^']+)'")
  4029.     if sq then return sq end
  4030.  
  4031.     return valStr
  4032.   end
  4033.  
  4034.   if type == "number" then
  4035.     local val = parseOffset(valStr)
  4036.     if val[1] == "pixel" then
  4037.       return val[2]
  4038.     else
  4039.       return 0
  4040.     end
  4041.   end
  4042.  
  4043.   if type == "left" then
  4044.     if valStr:match("^calc") then
  4045.       local op, v1, v2 = matchCalc(valStr)
  4046.       if op == "+" then
  4047.         return resolveVal(context, extra, v1) + resolveVal(context, extra, v2) - context.flowX
  4048.       else
  4049.         return resolveVal(context, extra, v1) - resolveVal(context, extra, v2) + context.flowX
  4050.       end
  4051.     end
  4052.  
  4053.     local val = parseOffset(valStr)
  4054.     if val[1] == "pixel" then
  4055.       return context.flowX + val[2]
  4056.     elseif val[1] == "percent" then
  4057.       return math.floor(context.width * (val[2] / 100) + context.flowX)
  4058.     elseif val[1] == "remain" then
  4059.       return math.floor(context.flowW * (val[2] / 100) + context.flowX)
  4060.     end
  4061.   elseif type == "right" then
  4062.     if valStr:match("^calc") then
  4063.       local op, v1, v2 = matchCalc(valStr)
  4064.       if op == "+" then
  4065.         return resolveVal(context, extra, v1) - parseOffset(v2)[2] -- TODO Will not work with types other than pixel
  4066.       else
  4067.         return resolveVal(context, extra, v1) + parseOffset(v2)[2] -- TODO Same here ^^^
  4068.       end
  4069.     end
  4070.  
  4071.     local val = parseOffset(valStr)
  4072.     if val[1] == "pixel" then
  4073.       return context.flowX + context.flowW
  4074.           - val[2]
  4075.           - extra.width
  4076.     else
  4077.       return context.flowX
  4078.     end
  4079.     --  TODO Implement other methods
  4080.   end
  4081.  
  4082.   if type == "top" then
  4083.     if valStr:match("^calc") then
  4084.       local op, v1, v2 = matchCalc(valStr)
  4085.       if op == "+" then
  4086.         return resolveVal(context, extra, v1) + resolveVal(context, extra, v2) - context.flowY
  4087.       else
  4088.         return resolveVal(context, extra, v1) - resolveVal(context, extra, v2) + context.flowY
  4089.       end
  4090.     end
  4091.  
  4092.     local val = parseOffset(valStr)
  4093.     if val[1] == "pixel" then
  4094.       return context.flowY + val[2]
  4095.     elseif val[1] == "percent" then
  4096.       return math.floor(context.height * (val[2] / 100) + context.flowY)
  4097.     elseif val[1] == "remain" then
  4098.       return math.floor(context.flowY * (val[2] / 100) + context.flowY)
  4099.     end
  4100.   elseif type == "bottom" then
  4101.     if valStr:match("^calc") then
  4102.       local op, v1, v2 = matchCalc(valStr)
  4103.       if op == "+" then
  4104.         return resolveVal(context, extra, v1) - parseOffset(v2)[2] -- TODO Will not work with types other than pixel
  4105.       else
  4106.         return resolveVal(context, extra, v1) + parseOffset(v2)[2] -- TODO Same here ^^^
  4107.       end
  4108.     end
  4109.  
  4110.     local val = parseOffset(valStr)
  4111.     if val[1] == "pixel" then
  4112.       return context.flowY + context.flowH
  4113.           - val[2]
  4114.           - extra.height
  4115.     else
  4116.       return context.flowY
  4117.     end
  4118.     --  TODO Implement other methods
  4119.   end
  4120.  
  4121.   if type == "width" then
  4122.     if valStr:match("^calc") then
  4123.       local op, v1, v2 = matchCalc(valStr)
  4124.       if op == "+" then
  4125.         return resolveVal(context, extra, v1) + resolveVal(context, extra, v2)
  4126.       else
  4127.         return resolveVal(context, extra, v1) - resolveVal(context, extra, v2)
  4128.       end
  4129.     end
  4130.  
  4131.     local val = parseOffset(valStr)
  4132.     if val[1] == "pixel" then
  4133.       return val[2]
  4134.     elseif val[1] == "percent" then
  4135.       return context.width * (val[2] / 100)
  4136.     elseif val[1] == "remain" then
  4137.       return context.flowW * (val[2] / 100)
  4138.     end
  4139.   elseif type == "height" then
  4140.     if valStr:match("^calc") then
  4141.       local op, v1, v2 = matchCalc(valStr)
  4142.       if op == "+" then
  4143.         return resolveVal(context, extra, v1) + resolveVal(context, extra, v2)
  4144.       else
  4145.         return resolveVal(context, extra, v1) - resolveVal(context, extra, v2)
  4146.       end
  4147.     end
  4148.  
  4149.     local val = parseOffset(valStr)
  4150.     if val[1] == "pixel" then
  4151.       return val[2]
  4152.     elseif val[1] == "percent" then
  4153.       return context.height * (val[2] / 100)
  4154.     elseif val[1] == "remain" then
  4155.       return context.flowH * (val[2] / 100)
  4156.     end
  4157.   end
  4158.  
  4159.   if type == "color" then
  4160.     if valStr == "transparent" then
  4161.       return -1
  4162.     elseif valStr:match("^rainbow") then
  4163.       local colors = {}
  4164.       for c in valStr:gmatch("([^%(%)]-)[,%)]%s*") do
  4165.         if renderer.colorReference[c] then
  4166.           colors[#colors + 1] = 2 ^ renderer.colorReference[c][1]
  4167.         else
  4168.           error("Color '" .. c .. "' was never defined")
  4169.         end
  4170.       end
  4171.  
  4172.       return "rainbow " .. table.concat(colors, ";") .. ";"
  4173.     elseif renderer.colorReference[valStr] then
  4174.       return 2 ^ renderer.colorReference[valStr][1]
  4175.     elseif not valStr then
  4176.       return 0
  4177.     else
  4178.       error("Color '" .. valStr .. "' was never defined")
  4179.     end
  4180.   end
  4181. end
  4182.  
  4183. function renderer.processStyles(styles)
  4184.   local rulesets
  4185.  
  4186.   if styles then
  4187.     rulesets = css(styles)
  4188.     renderer.styles = rulesets
  4189.  
  4190.     local colorI
  4191.     for i = 1, #rulesets do
  4192.       if rulesets[i][1] == "colors" then
  4193.         colorI = i
  4194.         break
  4195.       end
  4196.     end
  4197.  
  4198.     local colorSet
  4199.     if colorI then
  4200.       colorSet = rulesets[colorI][2]
  4201.     else
  4202.       -- ComputerCraft Default Palette
  4203.       colorSet = {
  4204.         { "white", "#F0F0F0" },
  4205.         { "orange", "#F2B233" },
  4206.         { "magenta", "#E57FD8" },
  4207.         { "lightBlue", "#99B2F2" },
  4208.         { "yellow", "#DEDE6C" },
  4209.         { "lime", "#7FCC19" },
  4210.         { "pink", "#F2B2CC" },
  4211.         { "gray", "#4C4C4C" },
  4212.         { "lightGray", "#999999" },
  4213.         { "cyan", "#4C99B2" },
  4214.         { "purple", "#B266E5" },
  4215.         { "blue", "#3366CC" },
  4216.         { "brown", "#7F664C" },
  4217.         { "green", "#57A64E" },
  4218.         { "red", "#CC4C4C" },
  4219.         { "black", "#191919" }
  4220.       }
  4221.     end
  4222.  
  4223.     local toTab = {}
  4224.  
  4225.     local ci = 0
  4226.     for i = 1, #colorSet do
  4227.       if ci == 16 then
  4228.         error("Too many colors")
  4229.       end
  4230.  
  4231.       local color, hex = colorSet[i][1], colorSet[i][2]
  4232.  
  4233.       toTab[color:match("^%-?%-?([^%-]+)$")] = { ci, hex:match("#(.+)") }
  4234.       ci = ci + 1
  4235.     end
  4236.  
  4237.     colorSet = toTab
  4238.  
  4239.     renderer.colorReference = colorSet
  4240.   else
  4241.     rulesets = renderer.styles
  4242.   end
  4243.  
  4244.   for rulesetI = 1, #rulesets do
  4245.     local k = rulesets[rulesetI][1]
  4246.     local v = rulesets[rulesetI][2]
  4247.     local matches = querySelector(k)
  4248.  
  4249.     for i = 1, #matches do local matchedEl = matches[i] -- do
  4250.       matchedEl.styles = matchedEl.styles or {}
  4251.  
  4252.       for j = 1, #v do
  4253.         local prop = v[j][1]
  4254.         local val = v[j][2]
  4255.         matchedEl.styles[prop] = val
  4256.       end
  4257.     end
  4258.   end
  4259. end
  4260.  
  4261. function renderer.inflateXML(xml)
  4262.   renderer.model = xmlutils.parse(xml)
  4263.   local model = renderer.model
  4264.  
  4265.   if model.children and model.children[1] and model.children[1].name ~= "body" then
  4266.     error("Bad Layout Structure (No Body)")
  4267.   end
  4268.  
  4269.   local body = model.children[1]
  4270.   for i = 1, #body.children do local el = body.children[i] -- do
  4271.     if components[el.name] then
  4272.       el.adapter = components[el.name].new(el, renderer, resolveVal)
  4273.     else
  4274.       error("Unknown element " .. el.name)
  4275.     end
  4276.   end
  4277. end
  4278.  
  4279. function renderer.renderToSurface(surf, node, context)
  4280.   node = node or renderer.model.children[1]
  4281.  
  4282.   context = context or {
  4283.     flowX = 0,
  4284.     flowY = 0,
  4285.     flowW = surf.width,
  4286.     flowH = surf.height,
  4287.     width = surf.width,
  4288.     height = surf.height
  4289.   }
  4290.  
  4291.   if node.styles and node.styles["background-color"] then
  4292.     local c = resolveVal({}, "color", node.styles["background-color"])
  4293.     surf:clear(c)
  4294.   end
  4295.  
  4296.   for i = 1, #node.children do local el = node.children[i] -- do
  4297.     if not el.styles then el.styles = {} end
  4298.     local s = el.styles
  4299.  
  4300.     if s.display ~= "none" then
  4301.       local px, py, pw, ph =
  4302.       context.flowX, context.flowY,
  4303.       context.flowW, context.flowH
  4304.  
  4305.       if s.position == "absolute" then
  4306.         context = {
  4307.           flowX = context.flowX,
  4308.           flowY = context.flowY,
  4309.           flowW = surf.width,
  4310.           flowH = surf.height,
  4311.           width = surf.width,
  4312.           height = surf.height
  4313.         }
  4314.  
  4315.         if s.left or s.right then
  4316.           context.flowX = 0
  4317.         end
  4318.  
  4319.         if s.top or s.bottom then
  4320.           context.flowY = 0
  4321.         end
  4322.       end
  4323.  
  4324.       local width, height
  4325.       width = resolveVal(context, "width", s.width or "100rem")
  4326.  
  4327.       if not s.height and el.adapter and el.adapter.resolveHeight then
  4328.         s.height = el.adapter:resolveHeight(s, { flow = context, width = width }, resolveVal)
  4329.       end
  4330.       height = resolveVal(context, "height", s.height or "100rem")
  4331.  
  4332.       local left
  4333.       if s.right then
  4334.         left = resolveVal(context, { type = "right", width = width }, s.right)
  4335.       else
  4336.         left = resolveVal(context, "left", s.left or "0")
  4337.       end
  4338.  
  4339.       local top
  4340.       if s.bottom then
  4341.         top = resolveVal(context, { type = "bottom", height = height }, s.bottom)
  4342.       else
  4343.         top = resolveVal(context, "top", s.top or "0")
  4344.       end
  4345.  
  4346.       local topMargin,
  4347.             _, -- rightMargin currently unused as there is no way (currently) to have inline elements
  4348.             bottomMargin,
  4349.             leftMargin = util.parseOrdinalStyle(resolveVal, s, "margin")
  4350.  
  4351.       left = left + leftMargin
  4352.       top = top + topMargin
  4353.  
  4354.       if el.adapter then
  4355.         el.adapter:render(surf, {
  4356.           left = left,
  4357.           top = top,
  4358.           width = width,
  4359.           height = height
  4360.         }, s, resolveVal)
  4361.  
  4362.         context.flowY = context.flowY + height + bottomMargin
  4363.         context.flowH = context.flowH - height - bottomMargin
  4364.       end
  4365.  
  4366.       if s.position == "absolute" then
  4367.         context = {
  4368.           flowX = px,
  4369.           flowY = py,
  4370.           flowW = pw,
  4371.           flowH = ph,
  4372.           width = surf.width,
  4373.           height = surf.height
  4374.         }
  4375.       end
  4376.     end
  4377.   end
  4378. end
  4379.  
  4380. renderer.querySelector = querySelector
  4381.  
  4382. return renderer
  4383.  end)()
  4384. renderer.inflateXML(layout)
  4385. renderer.processStyles(styles)
  4386.  
  4387.  
  4388.   if layoutMode then
  4389.     local exampleData = config.example or {
  4390.       ["minecraft:gold_ingot::0::0"] = 412,
  4391.       ["minecraft:iron_ingot::0::0"] = 4,
  4392.       ["minecraft:diamond::0::0"] = 27
  4393.     }
  4394.  
  4395.     local rmList = {}
  4396.     for item in pairs(exampleData) do
  4397.       if not transformedItems[item] then
  4398.         rmList[#rmList + 1] = item
  4399.       end
  4400.     end
  4401.  
  4402.     for i = 1, #rmList do local item = rmList[i] -- do
  4403.       exampleData[item] = nil
  4404.     end
  4405.  
  4406.     local els = renderer.querySelector("table.stock-table")
  4407.     for i = 1, #els do
  4408.       els[i].adapter:updateData(exampleData)
  4409.     end
  4410.  
  4411.     for _, v in pairs(renderer.colorReference) do
  4412.       term.setPaletteColor(2^v[1], tonumber(v[2], 16))
  4413.     end
  4414.  
  4415.     local testSurf = surface.create(term.getSize())
  4416.  
  4417.     renderer.renderToSurface(testSurf)
  4418.     testSurf:output()
  4419.  
  4420.     os.pullEvent("mouse_click")
  4421.   else
  4422.     local repaintMonitor -- Forward declaration
  4423.  
  4424. --== Chests ==--
  4425.  
  4426. if config.chest then
  4427.   config.chests = { config.chest }
  4428. end
  4429.  
  4430. -- Wrap the peripherals
  4431. if not config.chests then
  4432.   local periphs = peripheral.getNames()
  4433.   local chest
  4434.   for i = 1, #periphs do local periph = periphs[i] -- do
  4435.     if periph:match("chest") or periph:match("shulker_box") then
  4436.       chest = periph
  4437.     end
  4438.   end
  4439.  
  4440.   if not chest then
  4441.     error("No configured chest(s), and none could be found")
  4442.   else
  4443.     config.chests = { chest }
  4444.   end
  4445. end
  4446.  
  4447. local chestPeriphs = {}
  4448. for i = 1, #config.chests do local chest = config.chests[i] -- do
  4449.   chestPeriphs[#chestPeriphs + 1] = peripheral.wrap(chest)
  4450.  
  4451.   if not chestPeriphs[#chestPeriphs] then
  4452.     chestPeriphs[#chestPeriphs] = nil
  4453.     logger.error("No chest by name '" .. chest .. "'")
  4454.   end
  4455. end
  4456.  
  4457. if #chestPeriphs == 0 then
  4458.   error("No valid chest(s) could be found")
  4459. end
  4460.  
  4461. if not config.self and not config.outChest then
  4462.   -- Attempt to find by chestPeriph reverse search
  4463.   local cp = chestPeriphs[1]
  4464.   local list = cp.getTransferLocations()
  4465.   for i = 1, #list do local loc = list[i] -- do
  4466.     if loc:match("^turtle") then
  4467.       config.self = loc
  4468.       logger.warn("config.self not specified, assuming turtle connection '" .. config.self .. "'")
  4469.  
  4470.       break
  4471.     end
  4472.   end
  4473.  
  4474.   if not config.self then
  4475.     error("config.self not specified, and was unable to infer self, please add to config")
  4476.   end
  4477. end
  4478.  
  4479. -- Wrap the output chest
  4480. local outChest = nil
  4481. if config.outChest then
  4482.   outChest = peripheral.wrap(config.outChest)
  4483. end
  4484.  
  4485. --== Monitors ==--
  4486.  
  4487. local monPeriph
  4488. if not config.monitor then
  4489.   local mon = peripheral.find("monitor")
  4490.  
  4491.   if mon then
  4492.     monPeriph = mon
  4493.   else
  4494.     error("No configured monitor(s), and none could be found")
  4495.   end
  4496. else
  4497.   monPeriph = peripheral.wrap(config.monitor)
  4498.  
  4499.   if not (monPeriph and monPeriph.setPaletteColor) then
  4500.     error("No monitor by name '" .. monPeriph .. "' could be found")
  4501.   end
  4502. end
  4503.  
  4504. --== RS Integrators ==--
  4505.  
  4506. local rsIntegrators = {}
  4507. if config.redstoneIntegrator then
  4508.   local toWrap = {}
  4509.   if type(config.redstoneIntegrator[1]) == "table" then
  4510.     for i = 1, #config.redstoneIntegrator do local integrator = config.redstoneIntegrator[i] -- do
  4511.       toWrap[#toWrap + 1] = integrator
  4512.     end
  4513.   else
  4514.     toWrap = {config.redstoneIntegrator}
  4515.   end
  4516.  
  4517.   for i = 1, #toWrap do local integrator = toWrap[i] -- do
  4518.     local pHandle = peripheral.wrap(integrator[1])
  4519.     rsIntegrators[#rsIntegrators + 1] = {pHandle, integrator[2]}
  4520.   end
  4521. end
  4522.  
  4523. monPeriph.setTextScale(config.textScale or 0.5)
  4524. successTools.monitor = monPeriph
  4525.  
  4526. --== Various Helper Functions ==--
  4527.  
  4528. local function anyFree()
  4529.   local c = 0
  4530.   for i = 1, 16 do
  4531.     c = c + turtle.getItemSpace(i)
  4532.   end
  4533.  
  4534.   return c > 0
  4535. end
  4536.  
  4537. local function getFreeSlot()
  4538.   for i = 1, 16 do
  4539.     if turtle.getItemCount(i) == 0 then
  4540.       return i
  4541.     end
  4542.   end
  4543. end
  4544.  
  4545. --== Inventory Management Functions ==--
  4546.  
  4547. local drawRefresh
  4548.  
  4549. local function processChest(chestPeriph, list, slotList, hasPredCache)
  4550.   local cTable = chestPeriph.list()
  4551.   if not cTable then
  4552.     logger.error("Unable to list chest '" .. chestPeriph .. "'")
  4553.   else
  4554.     for k, v in pairs(cTable) do -- For each item..
  4555.       local bName = util.toListName(v.name, v.nbtHash or "", v.damage, 0) -- Simplified name to check if deep predicate matching is required
  4556.  
  4557.       local predicateID = 0
  4558.       if hasPredCache[bName] then
  4559.         -- This item has known predicates, find which one
  4560.  
  4561.         -- First see if we can match the predicate without making expensive meta calls
  4562.         for chkPredicateID = 1, #predicateCache do
  4563.           if util.matchPredicate(predicateCache[chkPredicateID], v) then
  4564.             predicateID = chkPredicateID
  4565.             break
  4566.           end
  4567.         end
  4568.  
  4569.         -- Check detailed metadata
  4570.         if predicateID == 0 then
  4571.           -- This may take a while, so make sure to alert potential customers while shop is unavaliable
  4572.           -- TODO: ^^^^^ but only when sleep is required
  4573.  
  4574.           local cachedMeta = chestPeriph.getItemMeta(k)
  4575.           for chkPredicateID = 1, #predicateCache do
  4576.             if util.matchPredicate(predicateCache[chkPredicateID], cachedMeta) then
  4577.               predicateID = chkPredicateID
  4578.               break
  4579.             end
  4580.           end
  4581.         end
  4582.       end
  4583.  
  4584.  
  4585.       local lName = util.toListName(v.name, v.nbtHash or "", v.damage, predicateID)
  4586.  
  4587.       if transformedItems[lName] then
  4588.         if not list[lName] then
  4589.           list[lName] = v.count
  4590.           slotList[lName] = { { k, v.count, chestPeriph } }
  4591.         else
  4592.           list[lName] = list[lName] + v.count
  4593.           slotList[lName][#slotList[lName] + 1] = { k, v.count, chestPeriph }
  4594.         end
  4595.       end
  4596.     end
  4597.   end
  4598. end
  4599.  
  4600. local list -- Item count list
  4601. local slotList -- Keep track of which slots (in chests) items are located
  4602. local hasPredCache -- Keep track of which items have predicates
  4603. local function countItems()
  4604.   local hasDrawnRefresh = false
  4605.  
  4606.   local lastList = slotList
  4607.  
  4608.   list = {}
  4609.   hasPredCache = {}
  4610.   slotList = {}
  4611.  
  4612.   -- Perform some initial transformations on the data
  4613.   for i = 1, #config.items do local item = config.items[i] -- do
  4614.     local bName = util.toListName(item.modid, item.nbtHash or "", item.damage or 0, 0)
  4615.     if not hasPredCache[bName] then
  4616.       hasPredCache[bName] = item.predicateID ~= nil
  4617.     end
  4618.  
  4619.     if config.showBlanks then
  4620.       local lName = util.toListName(item.modid, item.nbtHash or "", item.damage or 0, item.predicateID or 0)
  4621.       list[lName] = 0
  4622.       slotList[lName] = {}
  4623.     end
  4624.   end
  4625.  
  4626.   -- Iterate over all known chests
  4627.   for ck = 1, #chestPeriphs do
  4628.     local chestPeriph = chestPeriphs[ck]
  4629.     processChest(chestPeriph, list, slotList, hasPredCache)
  4630.   end
  4631.  
  4632.   if not util.equals(lastList, slotList) then
  4633.     local els = renderer.querySelector("table.stock-table")
  4634.     for i = 1, #els do
  4635.       els[i].adapter:updateData(list)
  4636.     end
  4637.  
  4638.     repaintMonitor()
  4639.   end
  4640. end
  4641.  
  4642. local function dispense(mcname, count)
  4643.   local toMoveCount = count
  4644.   while toMoveCount > 0 do
  4645.     -- We don't need to check for item availability here because
  4646.     -- we already did that in processPayment()
  4647.  
  4648.     for i = #slotList[mcname], 1, -1 do
  4649.       local chestPeriph = slotList[mcname][i][3]
  4650.       local amountPushed = 0
  4651.       if config.outChest then
  4652.         local tempSlot = getFreeSlot()
  4653.         amountPushed = chestPeriph.pushItems(config.self, slotList[mcname][i][1], toMoveCount, tempSlot)
  4654.         outChest.pullItems(config.self, tempSlot)
  4655.       else
  4656.         amountPushed = chestPeriph.pushItems(config.self, slotList[mcname][i][1], toMoveCount)
  4657.       end
  4658.  
  4659.       toMoveCount = toMoveCount - amountPushed
  4660.  
  4661.       if toMoveCount <= 0 then
  4662.         break
  4663.       end
  4664.  
  4665.       if not anyFree() and not config.outChest then
  4666.         for j = 1, 16 do
  4667.           if turtle.getItemCount(j) > 0 then
  4668.             turtle.select(j)
  4669.             turtle.drop()
  4670.           end
  4671.         end
  4672.       end
  4673.     end
  4674.   end
  4675.  
  4676.   if config.outChest then
  4677.     local toBeDispensed = count
  4678.     local iList, iSlotList = {}, {}
  4679.     processChest(outChest, iList, iSlotList, hasPredCache)
  4680.     for i = #iSlotList[mcname], 1, -1 do
  4681.       toBeDispensed = toBeDispensed -
  4682.         outChest.drop(
  4683.           iSlotList[mcname][i][1],
  4684.           math.min(iSlotList[mcname][i][2], toBeDispensed),
  4685.           config.outChestDir or "up")
  4686.  
  4687.       if toBeDispensed <= 0 then
  4688.         break
  4689.       end
  4690.     end
  4691.   else
  4692.     for i = 1, 16 do
  4693.       if turtle.getItemCount(i) > 0 then
  4694.         turtle.select(i)
  4695.         turtle.drop()
  4696.       end
  4697.     end
  4698.   end
  4699.  
  4700.   countItems()
  4701. end
  4702.  
  4703. local function findItem(name)
  4704.   for k, item in pairs(config.items) do
  4705.     if item.addy == name then
  4706.       return item, util.toListName(item.modid, item.nbtHash or "", item.damage or 0, item.predicateID or 0)
  4707.     end
  4708.   end
  4709.  
  4710.   return false
  4711. end
  4712.  
  4713. --== Payment Processing Functions ==--
  4714.  
  4715. local messages = {
  4716.   overpaid = "message=You paid {amount} TST more than you should have, here is your change, {buyer}.",
  4717.   underpaid = "error=You must pay at least {price} TST for {item}(s), you have been refunded, {buyer}.",
  4718.   outOfStock = "error=We do not have any {item}(s) at the moment, sorry for any inconvenience, {buyer}.",
  4719.   unknownItem = "error=We do not currently sell {item}(s), sorry for any inconvenience, {buyer}."
  4720. }
  4721.  
  4722. if config.messages then
  4723.   for k, v in pairs(config.messages) do
  4724.     messages[k] = v
  4725.   end
  4726. end
  4727.  
  4728. local function escapeSemi(txt)
  4729.   return txt:gsub("[%;%=]", "")
  4730. end
  4731.  
  4732. local function template(str, context)
  4733.   for k, v in pairs(context) do
  4734.     str = str:gsub("{" .. k .. "}", v)
  4735.   end
  4736.  
  4737.   return str
  4738. end
  4739.  
  4740. local function processPayment(tx, meta)
  4741.   local item, mcname = findItem(meta.name)
  4742.  
  4743.   if item then
  4744.     local count = math.floor(tonumber(tx.value) / item.price)
  4745.  
  4746.     local ac = math.min(count, list[mcname] or 0)
  4747.     if ac > 0 then
  4748.       logger.info("Dispensing " .. count .. " " .. item.disp .. "(s)")
  4749.       logger.info("Xenon (" .. (config.title or "Shop") .. "): " ..
  4750.           (meta.meta and meta.meta["username"] or "Someone") .. " bought " .. ac .. " " .. item.disp .. "(s) (" .. (ac * item.price) .. " TST)!",
  4751.         (config.logger or {}).purchase or false)
  4752.     end
  4753.  
  4754.     if (list[mcname] or 0) < count then
  4755.       logger.warn("More items were requested than available, refunding..")
  4756.  
  4757.       if (list[mcname] ~= 0) then
  4758.         logger.warn("Xenon (" .. (config.title or "Shop") .. "): " ..
  4759.             (meta.meta and meta.meta["username"] or "Someone") .. " bought all remaining " .. item.disp .. "(s), they are now out of stock.",
  4760.           (config.logger or {}).outOfStock or false)
  4761.       end
  4762.  
  4763.       if meta.meta and meta.meta["return"] then
  4764.         local refundAmt = math.floor(tx.value - (list[mcname] * item.price))
  4765.  
  4766.         if ac == 0 then
  4767.           await(tapi.makeTransaction, config.pkey, meta.meta["return"], refundAmt,
  4768.             template(messages.outOfStock, { item = item.disp, price = item.price, amount = refundAmt, buyer = (meta.meta and meta.meta["username"] or "Someone") }))
  4769.         else
  4770.           await(tapi.makeTransaction, config.pkey, meta.meta["return"], refundAmt,
  4771.             template(messages.overpaid, { item = item.disp, price = item.price, amount = refundAmt, buyer = (meta.meta and meta.meta["username"] or "Someone") }))
  4772.         end
  4773.       end
  4774.       count = list[mcname]
  4775.       tx.value = math.ceil(list[mcname] * item.price)
  4776.     end
  4777.  
  4778.     if tx.value < item.price then
  4779.       local refundAmt = tx.value
  4780.  
  4781.       await(tapi.makeTransaction, config.pkey, meta.meta["return"], refundAmt,
  4782.         template(messages.underpaid, { item = item.disp, amount = refundAmt, price = item.price, buyer = (meta.meta and meta.meta["username"] or "Someone") }))
  4783.     elseif tx.value > count * item.price then
  4784.       if meta.meta and meta.meta["return"] then
  4785.         local refundAmt = tx.value - (count * item.price)
  4786.  
  4787.         if refundAmt >= 1 then
  4788.           await(tapi.makeTransaction, config.pkey, meta.meta["return"], refundAmt,
  4789.             template(messages.overpaid, { item = item.disp, amount = refundAmt, price = item.price, buyer = (meta.meta and meta.meta["username"] or "Someone") }))
  4790.         end
  4791.       end
  4792.     end
  4793.  
  4794.     if list[mcname] and list[mcname] ~= 0 then
  4795.       dispense(mcname, count)
  4796.     end
  4797.   else
  4798.     logger.warn("Payment was sent for an invalid item (" .. meta.name .. "), aborting..")
  4799.     if meta.meta and meta.meta["return"] then
  4800.       await(tapi.makeTransaction, config.pkey, meta.meta["return"], tx.value,
  4801.         template(messages.unknownItem, { item = escapeSemi(meta.name), amount = refundAmt, buyer = (meta.meta and meta.meta["username"] or "Someone") }))
  4802.     end
  4803.   end
  4804. end
  4805.  
  4806.  
  4807. --== Monitor Rendering Endpoints ==--
  4808.  
  4809. local monW, monH = monPeriph.getSize()
  4810. local displaySurf = surface.create(monW, monH)
  4811.  
  4812. function repaintMonitor()
  4813.   for _, v in pairs(renderer.colorReference) do
  4814.     monPeriph.setPaletteColor(2^v[1], tonumber(v[2], 16))
  4815.   end
  4816.  
  4817.   renderer.renderToSurface(displaySurf)
  4818.   displaySurf:output(monPeriph)
  4819. end
  4820.  
  4821. local function drawStartup()
  4822.   monPeriph.setPaletteColor(2^0, 0x2F3542)
  4823.   monPeriph.setPaletteColor(2^1, 0x747D8C)
  4824.  
  4825.   monPeriph.setBackgroundColor(2^0)
  4826.   monPeriph.setTextColor(2^1)
  4827.   monPeriph.clear()
  4828.  
  4829.   local str = "Xenon is initializing..."
  4830.   monPeriph.setCursorPos(math.ceil((monW - #str) / 2), math.ceil(monH / 2))
  4831.   monPeriph.write(str)
  4832. end
  4833.  
  4834. -- Not local because of forward declaration
  4835. function drawRefresh()
  4836.   monPeriph.setPaletteColor(2^0, 0x2F3542)
  4837.   monPeriph.setPaletteColor(2^1, 0x747D8C)
  4838.  
  4839.   monPeriph.setBackgroundColor(2^0)
  4840.   monPeriph.setTextColor(2^1)
  4841.   monPeriph.clear()
  4842.  
  4843.   local str = "Refreshing stock..."
  4844.   monPeriph.setCursorPos(math.ceil((monW - #str) / 2), math.ceil(monH / 2))
  4845.   monPeriph.write(str)
  4846. end
  4847.  
  4848.  
  4849.     -- Initialize Item List
  4850.     countItems()
  4851.  
  4852.     drawStartup()
  4853.  
  4854. --== Tenebra Interface Setup ==--
  4855.  
  4856. local ws -- Tenebra Websocket forward declaration
  4857.  
  4858. rapi.init(jua)
  4859. wapi.init(jua)
  4860. tapi.init(jua, json, wapi, rapi)
  4861.  
  4862. jua.on("terminate", function()
  4863.   if ws then ws.close() end
  4864.   jua.stop()
  4865.   logger.error("Terminated")
  4866.   logger.close()
  4867. end)
  4868.  
  4869. -- Double check that the config is self-consistent (pkey matches address, address owns name)
  4870. if not config.pkey then
  4871.   error("No private-key (config.pkey)")
  4872. end
  4873.  
  4874. if config.pkeyFormat == "kwallet" then
  4875.   config.pkey = tapi.toTenebraWalletFormat(config.pkey)
  4876. end
  4877.  
  4878. do
  4879.   local pkeyAddress = tapi.makev2address(config.pkey)
  4880.   if config.host ~= pkeyAddress then
  4881.     error("Generated host (" .. pkeyAddress .. ") does not match config.host (" .. config.host .. ")")
  4882.   end
  4883.  
  4884.   local success, nameInfo = jua.await(tapi.name, config.name)
  4885.  
  4886.   if not success then
  4887.     if nameInfo.error then
  4888.       if nameInfo.error == "name_not_found" then
  4889.         error("Error validating name, name '" .. config.name .. "' does not exist/has not been purchased.")
  4890.       end
  4891.     end
  4892.  
  4893.     error("Error validating name, could not retrieve name info from Tenebra server.")
  4894.   end
  4895.  
  4896.   if nameInfo.owner ~= config.host then
  4897.     --error("Host (" .. config.host ..") does not own name (" .. config.name .. ")")
  4898.   end
  4899. end
  4900.  
  4901. --== Misc Jua Hooks ==--
  4902.  
  4903. local await = jua.await
  4904.  
  4905. local lightVal = false
  4906. local redstoneTimer = 0
  4907. local updateTimer = 0
  4908. local intervalInc = math.min(config.redstoneInterval or 5, config.updateInterval or 30)
  4909. jua.setInterval(function()
  4910.   redstoneTimer = redstoneTimer + intervalInc
  4911.   updateTimer = updateTimer + intervalInc
  4912.  
  4913.   if redstoneTimer >= (config.redstoneInterval or 5) then
  4914.     lightVal = not lightVal
  4915.  
  4916.     if type(config.redstoneSide) == "table" then
  4917.       for i = 1, #config.redstoneSide do local side = config.redstoneSide[i] -- do
  4918.         rs.setOutput(side, lightVal)
  4919.       end
  4920.     elseif type(config.redstoneSide) == "string" then
  4921.       rs.setOutput(config.redstoneSide, lightVal)
  4922.     end
  4923.    
  4924.     for i = 1, #rsIntegrators do local integrator = rsIntegrators[i] -- do
  4925.       integrator[1].setOutput(integrator[2], lightVal)
  4926.     end
  4927.  
  4928.     redstoneTimer = 0
  4929.   end
  4930.  
  4931.   if updateTimer >= (config.updateInterval or 30) then
  4932.     countItems()
  4933.  
  4934.     updateTimer = 0
  4935.   end
  4936. end, intervalInc)
  4937.  
  4938. --== Handlers ==--
  4939. local function handleTransaction(data)
  4940.   local tx = data.transaction
  4941.  
  4942.   if tx.to == config.host then
  4943.     if tx.metadata then
  4944.       local meta = tx.metadata
  4945.       if type(meta) == "string" then
  4946.         meta = tapi.parseMeta(meta)
  4947.       end
  4948.  
  4949.       if meta.domain == config.name then
  4950.         logger.info("Received " .. tx.value .. "tst from " .. tx.from .. " (Meta: " .. tostring(tx.metadata) .. ")")
  4951.  
  4952.         processPayment(tx, meta)
  4953.       end
  4954.     end
  4955.   end
  4956. end
  4957.  
  4958. --== Main Loop ==--
  4959.  
  4960. jua.go(function()
  4961.   logger.info("Startup!", false, true)
  4962.  
  4963.   local success
  4964.   success, ws = await(tapi.connect, config.pkey or "no-pkey")
  4965.  
  4966.   if success then
  4967.     logger.info("Connected to websocket.", false, true)
  4968.     ws.on("hello", function(helloData)
  4969.       logger.info("MOTD: " .. helloData.motd, false, true)
  4970.       local subscribeSuccess = await(ws.subscribe, "transactions", handleTransaction)
  4971.  
  4972.       if subscribeSuccess then
  4973.         logger.info("Subscribed successfully", false, true)
  4974.         repaintMonitor()
  4975.       else
  4976.         logger.error("Failed to subscribe")
  4977.         jua.stop()
  4978.  
  4979.         error("Failed to subscribe to Tenebra transactions")
  4980.       end
  4981.     end)
  4982.  
  4983.     ws.on("closed", function()
  4984.       os.reboot()
  4985.     end)
  4986.   else
  4987.     logger.error("Failed to request a websocket url")
  4988.     jua.stop()
  4989.  
  4990.     error("Failed to request a websocket url")
  4991.   end
  4992. end)
  4993.  
  4994.   end
  4995. end
  4996.  
  4997. local success, error = pcall(xenon)
  4998.  
  4999. if not success then
  5000.   local isColor = term.isColor()
  5001.   local setBG = isColor and term.setBackgroundColor or function() end
  5002.   local setFG = isColor and term.setTextColor or function() end
  5003.  
  5004.   setBG(colors.black)
  5005.   setFG(colors.red)
  5006.  
  5007.   print("[ERROR] Xenon terminated with error: '" .. error .. "'")
  5008.  
  5009.   setFG(colors.blue)
  5010.   print("This computer will reboot in 10 seconds..")
  5011.  
  5012.   if successTools.monitor then
  5013.     local mon = successTools.monitor
  5014.     local monW, monH = mon.getSize()
  5015.     local isMonColor = mon.isColor()
  5016.  
  5017.     if isMonColor then
  5018.       mon.setPaletteColor(2^0, 0xFFA502)
  5019.       mon.setPaletteColor(2^1, 0xFFFFFF)
  5020.       mon.setPaletteColor(2^2, 0xFF4757)
  5021.  
  5022.       mon.setBackgroundColor(2^0)
  5023.       mon.setTextColor(2^1)
  5024.     end
  5025.  
  5026.     mon.clear()
  5027.  
  5028.     if isMonColor then
  5029.       mon.setBackgroundColor(2^2)
  5030.     end
  5031.  
  5032.     for i = 2, 4 do
  5033.       mon.setCursorPos(1, i)
  5034.       mon.write((" "):rep(monW))
  5035.     end
  5036.  
  5037.     mon.setCursorPos(2, 3)
  5038.     mon.write("Xenon ran into an error!")
  5039.  
  5040.     if isMonColor then
  5041.       mon.setBackgroundColor(2^0)
  5042.     end
  5043.  
  5044.     mon.setCursorPos(2, 6)
  5045.     mon.write("Error Details:")
  5046.     mon.setCursorPos(2, 7)
  5047.     mon.write(error)
  5048.  
  5049.     local str = "Xenon will reboot in 10 seconds.."
  5050.     mon.setCursorPos(math.ceil((monW - #str) / 2), monH - 1)
  5051.     mon.write(str)
  5052.   end
  5053.  
  5054.   if successTools.logger then
  5055.     successTools.logger.error("Xenon (" .. ((config or {}).title or "Shop") .. "): Terminated with error: '" .. error .. "'",
  5056.       ((config or {}).logger or {}).crash or false)
  5057.   end
  5058.  
  5059.   sleep(10)
  5060.   os.reboot()
  5061. else
  5062.   if successTools.monitor then
  5063.     local mon = successTools.monitor
  5064.     local monW, monH = mon.getSize()
  5065.     local isMonColor = mon.isColor()
  5066.  
  5067.     if isMonColor then
  5068.       mon.setPaletteColor(2^0, 0x2F3542)
  5069.       mon.setPaletteColor(2^1, 0x747D8C)
  5070.  
  5071.       mon.setBackgroundColor(2^0)
  5072.       mon.setTextColor(2^1)
  5073.     end
  5074.  
  5075.     mon.clear()
  5076.  
  5077.     local str = "Xenon was terminated..."
  5078.     mon.setCursorPos(math.ceil((monW - #str) / 2), math.ceil(monH / 2))
  5079.     mon.write(str)
  5080.   end
  5081. end
  5082.  
  5083.  
Add Comment
Please, Sign In to add comment