znepb

Xenon

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