daily pastebin goal
28%
SHARE
TWEET

Hologram Editor 0.7.0 (English)

MoonlightOwl Feb 2nd, 2017 316 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. --       Hologram Editor v0.70
  2. -- by NEO, Totoro (aka MoonlightOwl)
  3. -- 10/14/2014, all right reserved =)
  4.  
  5. local unicode = require('unicode')
  6. local event = require('event')
  7. local term = require('term')
  8. local fs = require('filesystem')
  9. local com = require('component')
  10. local gpu = com.gpu
  11.  
  12. --     Colors     --
  13. local color = {
  14.   back = 0x000000,
  15.   fore = 0xFFFFFF,
  16.   info = 0x335555,
  17.   error = 0xFF3333,
  18.   help = 0x336600,
  19.   gold = 0xFFCC33,
  20.   gray = 0x080808,
  21.   lightgray = 0x333333
  22. }
  23.  
  24. --  Localization  --
  25. local loc = {
  26.   FILE_REQUEST = 'Enter file name',
  27.   ERROR_CAPTION = 'Error',
  28.   WARNING_CAPTION = 'Warning',
  29.   DONE_CAPTION = 'Done',
  30.   PROJECTOR_UNAVAILABLE_MESSAGE = 'Projector not found!',
  31.   SAVING_MESSAGE = 'Saving...',
  32.   SAVED_MESSAGE = 'The file was saved!',
  33.   LOADING_MESSAGE = 'Loading...',
  34.   LOADED_MESSAGE = 'Loaded successfully!',
  35.   TOO_LOW_RESOLUTION_ERROR = '[ERROR] Your display/GPU does not support 80×25+ resolution.',
  36.   TOO_LOW_SCREEN_TIER_ERROR = '[ERROR] You can use the Tier 2 GPU, but you need Tier 3 display anyway.',
  37.   FORMAT_READING_ERROR = 'Invalid file format!',
  38.   FILE_NOT_FOUND_ERROR = 'File not found!',
  39.   CANNOT_OPEN_ERROR = 'Cannot open the file!',
  40.   CANNOT_SAVE_ERROR = 'Cannot write to the file!',
  41.   PALETTE_FRAME = 'Palette',
  42.   VIEWPORT_FRAME = 'Projection',
  43.   UTILS_FRAME = 'Management',
  44.   LAYER_LABEL = 'Hologram layer:',
  45.   GHOST_LAYER_LABEL = '\'Ghost\' layer:',
  46.   PROGRAMMERS_LABEL = 'Developers:',
  47.   CONTACT_LABEL = 'Contact:',
  48.   EXIT_LABEL = "Quit: 'Q' or ",
  49.   EXIT_BUTTON = 'Exit',
  50.   REFRESH_BUTTON = 'Refresh',
  51.   TOP_BUTTON = 'Top',
  52.   FRONT_BUTTON = 'Front',
  53.   SIDE_BUTTON = 'Side',
  54.   BELOW_BUTTON = 'Below',
  55.   ABOVE_BUTTON = 'Above',
  56.   CLEAR_BUTTON = 'Clear',
  57.   FILL_BUTTON = 'Fill',
  58.   TO_PROJECTOR = 'To Projector',
  59.   SAVE_BUTTON = 'Save',
  60.   LOAD_BUTTON = 'Load',
  61.   NEW_FILE_BUTTON = 'New file'
  62. }
  63. --      ****      --
  64.  
  65.  
  66. -- Load a component safely
  67. local function trytofind(name)
  68.   if com.isAvailable(name) then
  69.     return com.getPrimary(name)
  70.   else
  71.     return nil
  72.   end
  73. end
  74.  
  75. -- Constants --
  76. local OLDWIDTH, OLDHEIGHT = gpu.getResolution()
  77. local WIDTH, HEIGHT = gpu.maxResolution()
  78. local FULLSIZE = true
  79. local HOLOW, HOLOH = 48, 32        -- hologram size
  80. local TOP, FRONT, SIDE = 0, 1, 2   -- projections
  81. local MENUX = HOLOW*2+5            -- right panel offset
  82. local BUTTONW = 12                 -- buttons width
  83. local GRIDX, GRIDY = 3, 2
  84.  
  85. -- Interface variables --
  86. local buttons = {}
  87. local textboxes = {}
  88. local repaint = false
  89.  
  90. -- App state --
  91. local colortable = {}
  92. local hexcolortable = {}
  93. local darkhexcolors = {}
  94. local brush = {color = 1, x = 8, cx = 8, moving = false}
  95. local ghost_layer = 1
  96. local ghost_layer_below = true
  97. local layer = 1
  98. local view = TOP
  99. local running = true
  100.  
  101. -- Auxiliary functions --
  102. local function rgb2hex(r,g,b)
  103.   return r*65536+g*256+b
  104. end
  105. local function setHexColor(n, r, g, b)
  106.   local hexcolor = rgb2hex(r,g,b)
  107.   hexcolortable[n] = hexcolor
  108.   darkhexcolors[n] = bit32.rshift(bit32.band(hexcolor, 0xfefefe), 1)
  109. end
  110.  
  111. -- ========================================= H O L O G R A P H I C S ========================================= --
  112. local holo = {}
  113. local function set(x, y, z, value)
  114.   if holo[x] == nil then holo[x] = {} end
  115.   if holo[x][y] == nil then holo[x][y] = {} end
  116.   holo[x][y][z] = value
  117. end
  118. local function get(x, y, z)
  119.   if holo[x] ~= nil and holo[x][y] ~= nil and holo[x][y][z] ~= nil then
  120.     return holo[x][y][z]
  121.   else
  122.     return 0
  123.   end
  124. end
  125.  
  126. local writer = {}
  127. function writer:init(file)
  128.   self.buffer = {}
  129.   self.file = file
  130. end
  131. function writer:write(sym)
  132.   table.insert(self.buffer, sym)
  133.   if #self.buffer >= 4 then self:finalize() end
  134. end
  135. function writer:finalize()
  136.   if #self.buffer > 0 then
  137.     local byte = 0
  138.     for i=4, 1, -1 do
  139.       local x = self.buffer[i] or 0
  140.       byte = byte * 4 + x
  141.     end
  142.     self.file:write(string.char(byte))
  143.     self.buffer = {}
  144.   end
  145. end
  146.  
  147. local function toBinary(x)
  148.   local data = {}
  149.   while x > 0 do
  150.     table.insert(data, x % 2)
  151.     x = math.floor(x / 2)
  152.   end
  153.   return data
  154. end
  155.  
  156. local function save(filename, compressed)
  157.   -- save the palette
  158.   local file = io.open(filename, 'wb')
  159.   if file ~= nil then
  160.     for i=1, 3 do
  161.       for c=1, 3 do
  162.         file:write(string.char(colortable[i][c]))
  163.       end
  164.     end
  165.     writer:init(file)
  166.     if compressed then
  167.       local function put(symbol, length)
  168.         if length > 0 then
  169.           writer:write(symbol)
  170.           local l = toBinary(length + 1)
  171.           l[#l] = nil
  172.           l[1] = l[1] + 2
  173.           for i=#l, 1, -1 do writer:write(l[i]) end
  174.         end
  175.       end
  176.       local len = 0
  177.       local sym = -1
  178.       -- save compressed data
  179.       for x=1, HOLOW do
  180.         for y=1, HOLOH do
  181.           for z=1, HOLOW do
  182.             local a = get(x, y, z)
  183.             if sym == a then  -- next sequence symbol
  184.               len = len + 1
  185.             else              -- first symbol of new sequence
  186.               put(sym, len)
  187.               len = 1
  188.               sym = a
  189.             end
  190.           end
  191.         end
  192.       end
  193.       put(sym, len)  -- the last sequence
  194.     else
  195.       -- save without compression
  196.       for x=1, HOLOW do
  197.         for y=1, HOLOH do
  198.           for z=1, HOLOW do
  199.             writer:write(get(x, y, z))
  200.           end
  201.         end
  202.       end
  203.     end
  204.     writer:finalize()
  205.     file:close()
  206.     return true
  207.   else
  208.     return false, filename..": "..loc.CANNOT_SAVE_ERROR
  209.   end
  210. end
  211.  
  212. local reader = {}
  213. function reader:init(file)
  214.   self.buffer = {}
  215.   self.file = file
  216. end
  217. function reader:read()
  218.   if #self.buffer == 0 then
  219.     if not self:fetch() then return nil end
  220.   end
  221.   -- get the last symbol from buffer
  222.   local sym = self.buffer[#self.buffer]
  223.   self.buffer[#self.buffer] = nil
  224.   return sym
  225. end
  226. function reader:fetch()
  227.   self.buffer = {}
  228.   local char = file:read(1)
  229.   if char == nil then return false
  230.   else
  231.     local byte = string.byte(char)
  232.     for i=0, 3 do
  233.       local a = byte % 4
  234.       byte = math.floor(byte / 4)
  235.       self.buffer[4-i] = a   -- write the bytes in reversed order
  236.     end
  237.     return true
  238.   end
  239. end
  240.  
  241. local function load(filename, compressed)
  242.   if fs.exists(filename) then
  243.     file = io.open(filename, 'rb')
  244.     if file ~= nil then
  245.       -- load palette
  246.       for i=1, 3 do
  247.         for c=1, 3 do
  248.           colortable[i][c] = string.byte(file:read(1))
  249.         end
  250.         setHexColor(i,colortable[i][1],
  251.                       colortable[i][2],
  252.                       colortable[i][3])
  253.       end
  254.       -- load data array
  255.       holo = {}
  256.       reader:init(file)
  257.       if compressed then          -- read compressed data
  258.         local x, y, z = 1, 1, 1
  259.         while true do
  260.           local a = reader:read() -- read one symbol
  261.           if a == nil then file:close(); return true end
  262.           local len = 1
  263.           while true do           -- get binary length value
  264.             local b = reader:read()
  265.             if b == nil then
  266.               file:close()
  267.               if a == 0 then return true
  268.               else return false, filename..": "..loc.FORMAT_READING_ERROR end
  269.             end
  270.             local fin = (b > 1)
  271.             if fin then b = b-2 end
  272.             len = bit32.lshift(len, 1)
  273.             len = len + b
  274.             if fin then break end
  275.           end
  276.           len = len - 1
  277.           -- write the sequence
  278.           for i=1, len do
  279.             -- write one voxel
  280.             if a ~= 0 then set(x,y,z, a) end
  281.             -- move the coordinates
  282.             z = z+1
  283.             if z > HOLOW then
  284.               y = y+1
  285.               if y > HOLOH then
  286.                 x = x+1
  287.                 if x > HOLOW then file:close(); return true end
  288.                 y = 1
  289.               end
  290.               z = 1
  291.             end  
  292.           end
  293.         end
  294.       else                        -- read uncompressed data
  295.         for x=1, HOLOW do
  296.           for y=1, HOLOH do
  297.             for z=1, HOLOW do
  298.               local a = reader:read()
  299.               if a ~= 0 and a ~= nil then
  300.                 set(x,y,z, a)
  301.               end
  302.             end
  303.           end
  304.         end
  305.       end
  306.       file:close()
  307.       return true
  308.     else
  309.       return false, filename..": "..loc.CANNOT_OPEN_ERROR
  310.     end
  311.   else
  312.     return false, filename..": "..loc.FILE_NOT_FOUND_ERROR
  313.   end
  314. end
  315.  
  316.  
  317. -- ============================================== B U T T O N S ============================================== --
  318. local Button = {}
  319. Button.__index = Button
  320. function Button.new(func, x, y, text, fore, back, width, nu)
  321.   self = setmetatable({}, Button)
  322.  
  323.   self.form = '[ '
  324.   if width == nil then width = 0
  325.     else width = (width - unicode.len(text))-4 end
  326.   for i=1, math.floor(width/2) do
  327.     self.form = self.form.. ' '
  328.   end
  329.   self.form = self.form..text
  330.   for i=1, math.ceil(width/2) do
  331.     self.form = self.form.. ' '
  332.   end
  333.   self.form = self.form..' ]'
  334.  
  335.   self.func = func
  336.  
  337.   self.x = math.floor(x); self.y = math.floor(y)
  338.   self.fore = fore
  339.   self.back = back
  340.   self.visible = true
  341.  
  342.   self.notupdate = nu or false
  343.  
  344.   return self
  345. end
  346. function Button:draw(fore, back)
  347.   if self.visible then
  348.     local fore = fore or self.fore
  349.     local back = back or self.back
  350.     gpu.setForeground(fore)
  351.     gpu.setBackground(back)
  352.     gpu.set(self.x, self.y, self.form)
  353.   end
  354. end
  355. function Button:click(x, y)
  356.   if self.visible then
  357.     if y == self.y then
  358.       if x >= self.x and x < self.x+unicode.len(self.form) then
  359.         self:draw(self.back, self.fore)
  360.         local data = self.func()
  361.         if not self.notupdate then self:draw() end
  362.         return true, data
  363.       end
  364.     end
  365.   end
  366.   return false
  367. end
  368.  
  369. local function buttonNew(buttons, func, x, y, text, fore, back, width, notupdate)
  370.   local button = Button.new(func, x, y, text, fore, back, width, notupdate)
  371.   table.insert(buttons, button)
  372.   return button
  373. end
  374. local function buttonsDraw(buttons)
  375.   for i=1, #buttons do
  376.     buttons[i]:draw()
  377.   end
  378. end
  379. local function buttonsClick(buttons, x, y)
  380.   for i=1, #buttons do
  381.     local ok, data = buttons[i]:click(x, y)
  382.     if ok then return data end
  383.   end
  384.   return nil
  385. end
  386.  
  387.  
  388. -- ============================================ T E X T B O X E S ============================================ --
  389. local Textbox = {}
  390. Textbox.__index = Textbox
  391. function Textbox.new(check, func, x, y, value, width)
  392.   self = setmetatable({}, Textbox)
  393.  
  394.   self.form = '>'
  395.   if width == nil then width = 10 end
  396.   for i=1, width-1 do
  397.     self.form = self.form..' '
  398.   end
  399.  
  400.   self.check = check
  401.   self.func = func
  402.   self.value = tostring(value)
  403.  
  404.   self.x = math.floor(x); self.y = math.floor(y)
  405.   self.width = width
  406.   self.visible = true
  407.  
  408.   return self
  409. end
  410. function Textbox:draw(content)
  411.   if self.visible then
  412.     gpu.setBackground(color.lightgray)
  413.     gpu.setForeground(color.fore)
  414.     gpu.set(self.x, self.y, self.form)
  415.     if content then gpu.set(self.x+2, self.y, self.value) end
  416.   end
  417. end
  418. function Textbox:click(x, y)
  419.   if self.visible then
  420.     if y == self.y then
  421.       if x >= self.x and x < self.x+self.width then
  422.         self:draw(false)
  423.         term.setCursor(self.x+2, self.y)
  424.         term.setCursorBlink(true)
  425.         local value = self.value
  426.         term.write(value)
  427.         -- read data
  428.         while true do
  429.           name, a, char, code = event.pull()
  430.           if name == 'key_down' then
  431.             if char > 30 then
  432.               if unicode.len(value) < (self.width-3) then
  433.                 local letter = unicode.char(char)
  434.                 value = value .. letter
  435.                 term.write(letter)
  436.               end
  437.             else
  438.               -- enter
  439.               if code == 28 then
  440.                 -- validate
  441.                 if self.check(value) then
  442.                   -- call a function
  443.                   self.value = value
  444.                   self.func(value)
  445.                 end
  446.                 break
  447.               -- backspace
  448.               elseif code == 14 then
  449.                 if unicode.len(value) > 0 then
  450.                   local x, y = term.getCursor()
  451.                   gpu.set(x-1, y, ' ')
  452.                   term.setCursor(x-1, y)
  453.                   value = unicode.sub(value, 1, -2)
  454.                 end
  455.               end
  456.             end
  457.           elseif name == 'touch' then
  458.             break
  459.           end
  460.         end
  461.         --
  462.         term.setCursorBlink(false)
  463.         self:draw(true)
  464.         gpu.setBackground(color.back)
  465.         return true
  466.       end
  467.     end
  468.   end
  469.   return false
  470. end
  471. function Textbox:setValue(value)
  472.   self.value = tostring(value)
  473. end
  474. function Textbox:getValue()
  475.   return self.value
  476. end
  477. function Textbox:setVisible(flag)
  478.   self.visible = flag
  479. end
  480. function Textbox:isVisible()
  481.   return self.visible
  482. end
  483.  
  484. local function textboxNew(textboxes, check, func, x, y, value, width)
  485.   textbox = Textbox.new(check, func, x, y, value, width)
  486.   table.insert(textboxes, textbox)
  487.   return textbox
  488. end
  489. local function textboxesDraw(textboxes)
  490.   for i=1, #textboxes do
  491.     textboxes[i]:draw(true)
  492.   end
  493. end
  494. local function textboxesClick(textboxes, x, y)
  495.   for i=1, #textboxes do
  496.     textboxes[i]:click(x, y)
  497.   end
  498. end
  499.  
  500.  
  501. -- ============================================= G R A P H I C S ============================================= --
  502. local gridLine1, gridLine2, gridLine1s, gridLine2s = nil, nil, nil, nil
  503. local strLine = "+"
  504. local colorCursorY, colorCursorWidth = 8, 8
  505. local function initGraphics()
  506.   -- grid prefabs
  507.   if FULLSIZE then gridLine1 = string.rep("██  ", HOLOW/2)
  508.   else
  509.     gridLine1 = string.rep("▀", HOLOW/2)
  510.     gridLine2 = string.rep("▄", HOLOW/2)
  511.     gridLine1s = string.rep("▀", HOLOH/2)
  512.     gridLine2s = string.rep("▄", HOLOH/2)
  513.   end
  514.   -- line prefab
  515.   for i=1, WIDTH do
  516.     strLine = strLine..'-'
  517.   end
  518.   -- palette cursor parameters
  519.   if not FULLSIZE then
  520.     colorCursorY, colorCursorWidth = 1, 7
  521.   end
  522. end
  523.  
  524. -- draw a line
  525. local function line(x1, x2, y)
  526.   gpu.set(x1,y,string.sub(strLine, 1, x2-x1))
  527.   gpu.set(x2,y,'+')
  528. end
  529.  
  530. -- draw a frame
  531. local function frame(x1, y1, x2, y2, caption, nobottom)
  532.   line(x1, x2, y1)
  533.   if not nobottom then line(x1, x2, y2) end
  534.   if caption ~= nil then
  535.     gpu.set(x1 + math.ceil((x2-x1)/2) - math.ceil(unicode.len(caption)/2), y1, caption)
  536.   end
  537. end
  538.  
  539. -- draw a grid
  540. local function drawGrid(x, y)
  541.   gpu.setBackground(color.back)
  542.   gpu.setForeground(color.gray)
  543.   gpu.fill(0, y, MENUX, HOLOW, ' ')
  544.   if FULLSIZE then
  545.     for i=0, HOLOW-1 do
  546.       if view ~= TOP and i == HOLOH then
  547.         gpu.setForeground(color.fore)
  548.         line(1, MENUX-1, y+HOLOH)
  549.         break
  550.       end
  551.       gpu.set(x + (i%2)*2, y + i, gridLine1)
  552.     end
  553.   else
  554.     for i=0, HOLOW-1 do
  555.       if view == TOP then
  556.         if i%2==0 then gpu.set(x+i, y, gridLine1, true)
  557.         else gpu.set(x+i, y, gridLine2, true) end
  558.       else
  559.         if i%2==0 then gpu.set(x+i, y, gridLine1s, true)
  560.         else gpu.set(x+i, y, gridLine2s, true) end
  561.       end
  562.     end
  563.   end
  564. end
  565.  
  566. -- draw a colored rectangle
  567. local function drawRect(x, y, fill)
  568.   gpu.setForeground(color.fore)
  569.   gpu.setBackground(color.gray)
  570.   gpu.set(x, y,   "╓──────╖")
  571.   gpu.set(x, y+1, "║      ║")
  572.   gpu.set(x, y+2, "╙──────╜")
  573.   gpu.setForeground(fill)
  574.   gpu.set(x+2, y+1, "████")
  575. end
  576. local function drawSmallRect(x, y, fill)
  577.   gpu.setForeground(color.fore)
  578.   gpu.set(x, y,   "╓─────╖")
  579.   gpu.set(x, y+1, "║     ║")
  580.   gpu.set(x, y+2, "╙─────╜")
  581.   gpu.setForeground(fill)
  582.   gpu.set(x+2, y+1, "███")
  583. end
  584.  
  585. -- draw brush selection menu
  586. local function drawPaletteFrame()
  587.   gpu.setForeground(color.fore)
  588.   gpu.setBackground(color.back)
  589.   if FULLSIZE then
  590.     frame(MENUX, 3, WIDTH-2, 16, "[ "..loc.PALETTE_FRAME.." ]", true)
  591.     for i=0, 3 do
  592.       drawRect(MENUX+1+i*colorCursorWidth, 5, hexcolortable[i])
  593.     end
  594.     gpu.setForeground(0xFF0000); gpu.set(MENUX+1, 10, "R:")
  595.     gpu.setForeground(0x00FF00); gpu.set(MENUX+1, 11, "G:")
  596.     gpu.setForeground(0x0000FF); gpu.set(MENUX+1, 12, "B:")
  597.   else
  598.     for i=0, 3 do
  599.       drawSmallRect(MENUX+1+i*colorCursorWidth, 2, hexcolortable[i])
  600.     end
  601.     gpu.setForeground(0xFF0000); gpu.set(MENUX+1, 5, "R:")
  602.     gpu.setForeground(0x00FF00); gpu.set(MENUX+11, 5, "G:")
  603.     gpu.setForeground(0x0000FF); gpu.set(MENUX+21, 5, "B:")
  604.   end
  605. end
  606. -- draw and move brush selector
  607. local function drawColorCursor(force)
  608.   if force or brush.moving then
  609.     gpu.setBackground(color.back)
  610.     gpu.setForeground(color.fore)
  611.     if FULLSIZE then gpu.set(MENUX+2+brush.cx, colorCursorY, "      ")
  612.     else gpu.set(MENUX+2+brush.cx, colorCursorY, "-----") end
  613.    
  614.     if brush.moving then
  615.       if brush.x ~= brush.color * colorCursorWidth then brush.x = brush.color*colorCursorWidth end
  616.       if brush.cx < brush.x then brush.cx = brush.cx + 1
  617.       elseif brush.cx > brush.x then brush.cx = brush.cx - 1
  618.       else brush.moving = false end
  619.     end
  620.    
  621.     if FULLSIZE then
  622.       gpu.setBackground(color.lightgray)
  623.       gpu.set(MENUX+2+brush.cx, colorCursorY, ":^^^^:")
  624.     else gpu.set(MENUX+2+brush.cx, colorCursorY, ":vvv:") end
  625.   end
  626. end
  627. local function drawLayerFrame()
  628.   gpu.setForeground(color.fore)
  629.   gpu.setBackground(color.back)
  630.   if FULLSIZE then
  631.     frame(MENUX, 16, WIDTH-2, 28, "[ "..loc.VIEWPORT_FRAME.." ]", true)
  632.     gpu.set(MENUX+13, 18, loc.LAYER_LABEL)
  633.     gpu.set(MENUX+1, 23, loc.GHOST_LAYER_LABEL)
  634.   else
  635.     gpu.set(MENUX+1, 8, loc.LAYER_LABEL)
  636.   end
  637. end
  638. local function drawUtilsFrame()
  639.   gpu.setForeground(color.fore)
  640.   gpu.setBackground(color.back)
  641.   frame(MENUX, 28, WIDTH-2, 36, "[ "..loc.UTILS_FRAME.." ]")
  642. end
  643.  
  644. local function mainScreen()
  645.   gpu.setForeground(color.fore)
  646.   gpu.setBackground(color.back)
  647.   term.clear()
  648.   frame(1,1, WIDTH, HEIGHT, "{ Hologram Editor }", not FULLSIZE)
  649.   -- "canvas"
  650.   drawGrid(GRIDX, GRIDY)
  651.  
  652.   drawPaletteFrame()
  653.   drawLayerFrame()
  654.   drawUtilsFrame()
  655.  
  656.   drawColorCursor(true)
  657.   buttonsDraw(buttons)
  658.   textboxesDraw(textboxes)
  659.  
  660.   -- "about us" box
  661.   if FULLSIZE then
  662.     gpu.setForeground(color.info)
  663.     gpu.setBackground(color.gray)
  664.     gpu.set(MENUX+3, HEIGHT-11, " Hologram Editor v0.70 Beta  ")
  665.     gpu.setForeground(color.fore)
  666.     gpu.set(MENUX+3, HEIGHT-10, "            * * *            ")
  667.     gpu.set(MENUX+3, HEIGHT-9,  " "..loc.PROGRAMMERS_LABEL..string.rep(' ', 28-unicode.len(loc.PROGRAMMERS_LABEL)))
  668.     gpu.set(MENUX+3, HEIGHT-8,  "         NEO, Totoro         ")
  669.     gpu.set(MENUX+3, HEIGHT-7,  "            * * *            ")
  670.     gpu.set(MENUX+3, HEIGHT-6,  " "..loc.CONTACT_LABEL..string.rep(' ', 28-unicode.len(loc.CONTACT_LABEL)))
  671.     gpu.set(MENUX+3, HEIGHT-5,  "       computercraft.ru      ")
  672.     gpu.setForeground(color.fore)
  673.     gpu.setBackground(color.back)
  674.     gpu.set(MENUX+1, HEIGHT-2, loc.EXIT_LABEL)
  675.   else
  676.     gpu.setForeground(color.info)
  677.     gpu.setBackground(color.gray)
  678.     gpu.set(MENUX+1, HEIGHT-2,  "by Totoro © computercraft.ru")
  679.     gpu.setForeground(color.fore)
  680.     gpu.setBackground(color.back)
  681.     gpu.set(MENUX+1, HEIGHT, loc.EXIT_LABEL)
  682.   end
  683. end
  684.  
  685.  
  686. -- ============================================= M E S S A G E S ============================================= --
  687. local function showMessage(text, caption, textcolor)
  688.   local caption = '[ '..caption..' ]'
  689.   local x = MENUX/2 - unicode.len(text)/2 - 4
  690.   local y = HEIGHT/2 - 2
  691.   gpu.setBackground(color.back)
  692.   gpu.setForeground(color.fore)
  693.   gpu.fill(x, y, unicode.len(text)+9, 5, ' ')
  694.   frame(x, y, x+unicode.len(text)+8, y+4, caption)
  695.   gpu.setForeground(textcolor)
  696.   gpu.set(x+4,y+2, text)
  697.   -- "canvas" must be rerendered
  698.   repaint = true
  699. end
  700.  
  701.  
  702. -- =============================================== L A Y E R S =============================================== --
  703. local function project(x, y, layer, view)
  704.   if view == TOP then
  705.     return x, layer, y
  706.   elseif view == FRONT then
  707.     return x, HOLOH-y+1, layer
  708.   else
  709.     return layer, HOLOH-y+1, x
  710.   end
  711. end
  712. local function getVoxelColor(x, y, z, grid)
  713.   local voxel = get(x, y, z)
  714.   if voxel ~= 0 then return hexcolortable[voxel]
  715.   elseif grid then return color.gray
  716.   else return color.back end
  717. end
  718. local function drawVoxel(sx, sy, nogrid)
  719.   if FULLSIZE then
  720.     local voxel = get(project(sx, sy, layer, view))
  721.     local dx = (GRIDX-2) + sx*2
  722.     local dy = (GRIDY-1) + sy
  723.     if voxel ~= 0 then
  724.       gpu.setForeground(hexcolortable[voxel])
  725.       gpu.set(dx, dy, "██")
  726.     else  
  727.       local ghost = get(gx, gy, gz)
  728.       if ghost ~= 0 then
  729.         gpu.setForeground(darkhexcolors[ghost])
  730.         gpu.set(dx, dy, "░░")
  731.       elseif not nogrid then
  732.         if (sx+sy)%2 == 0 then gpu.setForeground(color.gray)
  733.         else gpu.setForeground(color.back) end
  734.         gpu.set(dx, dy, "██")
  735.       end
  736.     end
  737.   else
  738.     local sxUp, syUp = sx, sy
  739.     if syUp%2 == 0 then syUp = syUp-1 end
  740.     local sxDown, syDown = sxUp, syUp + 1
  741.     local dx, dy = (GRIDX-1) + sxUp, (GRIDY-1) + math.ceil(syUp/2)
  742.     local a, b, c = project(sxUp, syUp, layer, view)
  743.     gpu.setForeground(getVoxelColor(a, b, c, ((sxUp+syUp)%2 == 0)))
  744.     a, b, c = project(sxDown, syDown, layer, view)
  745.     gpu.setBackground(getVoxelColor(a, b, c, ((sxDown+syDown)%2 == 0)))
  746.     gpu.set(dx, dy, "▀")
  747.   end
  748. end
  749.  
  750. function drawLayer()
  751.   drawGrid(GRIDX, GRIDY)
  752.   local step, limit
  753.   if FULLSIZE then step = 1 else step = 2 end
  754.   if view == TOP then limit = HOLOW else limit = HOLOH end
  755.   for x=1, HOLOW do
  756.     for y=1, limit, step do drawVoxel(x, y, true) end
  757.   end
  758.   -- no need to refresh a screen
  759.   repaint = false
  760. end
  761. local function fillLayer()
  762.   for x=1, HOLOW do
  763.     for z=1, HOLOW do
  764.       set(x, layer, z, brush.color)
  765.     end
  766.   end
  767.   drawLayer()
  768. end
  769. local function clearLayer()
  770.   for x=1, HOLOW do
  771.     if holo[x] ~= nil then holo[x][layer] = nil end
  772.   end
  773.   drawLayer()
  774. end
  775.  
  776.  
  777. -- ==================================== G U I   F U N C T I O N A L I T Y ==================================== --
  778. local function exit() running = false end
  779.  
  780. local function nextGhost()
  781.   local limit = HOLOH
  782.   if view ~= TOP then limit = HOLOW end
  783.  
  784.   if ghost_layer_below then
  785.     ghost_layer_below = false
  786.     if ghost_layer < limit then
  787.       ghost_layer = layer + 1
  788.     else ghost_layer = limit end
  789.     drawLayer()
  790.   else  
  791.     if ghost_layer < limit then
  792.       ghost_layer = ghost_layer + 1
  793.       drawLayer()
  794.     end
  795.   end
  796.   tb_ghostlayer:setValue(''); tb_ghostlayer:draw()
  797. end
  798. local function prevGhost()
  799.   if not ghost_layer_below then
  800.     ghost_layer_below = true
  801.     if layer > 1 then
  802.       ghost_layer = layer - 1
  803.     else ghost_layer = 1 end
  804.     drawLayer()
  805.   else
  806.     if ghost_layer > 1 then
  807.       ghost_layer = ghost_layer - 1
  808.       drawLayer()
  809.     end
  810.   end
  811.   tb_ghostlayer:setValue(''); tb_ghostlayer:draw()
  812. end
  813. local function setGhostLayer(value)
  814.   local n = tonumber(value)
  815.   local limit = HOLOH
  816.   if view ~= TOP then limit = HOLOW end
  817.   if n == nil or n < 1 or n > limit then return false end
  818.   ghost_layer = n
  819.   drawLayer()
  820.   return true
  821. end
  822. local function moveGhost()
  823.   if ghost_layer_below then
  824.     if layer > 1 then ghost_layer = layer - 1
  825.     else ghost_layer = 1 end
  826.   else
  827.     local limit = HOLOH
  828.     if view ~= TOP then limit = HOLOW end
  829.     if layer < limit then ghost_layer = layer + 1
  830.     else ghost_layer = limit end
  831.   end
  832. end
  833.  
  834. local function nextLayer()
  835.   -- projections limits
  836.   local limit = HOLOH
  837.   if view ~= TOP then limit = HOLOW end
  838.  
  839.   if layer < limit then
  840.     layer = layer + 1
  841.     tb_layer:setValue(layer)
  842.     tb_layer:draw(true)
  843.     moveGhost()
  844.     drawLayer()
  845.   end
  846. end
  847. local function prevLayer()
  848.   if layer > 1 then
  849.     layer = layer - 1
  850.     tb_layer:setValue(layer)
  851.     tb_layer:draw(true)
  852.     moveGhost()
  853.     drawLayer()
  854.   end
  855. end
  856. local function setLayer(value)
  857.   local n = tonumber(value)
  858.   local limit = HOLOH
  859.   if view ~= TOP then limit = HOLOW end
  860.   if n == nil or n < 1 or n > limit then return false end
  861.   layer = n
  862.   moveGhost()
  863.   drawLayer()
  864.   tb_layer:setValue(layer)
  865.   tb_layer:draw(true)
  866.   return true
  867. end
  868.  
  869. local function setFilename(str)
  870.   if str ~= nil and str ~= '' and unicode.len(str)<30 then
  871.     return true
  872.   else
  873.     return false
  874.   end
  875. end
  876.  
  877. local function changeColor(rgb, value)
  878.   if value == nil then return false end
  879.   n = tonumber(value)
  880.   if n == nil or n < 0 or n > 255 then return false end
  881.   -- save data to table
  882.   colortable[brush.color][rgb] = n
  883.   setHexColor(brush.color, colortable[brush.color][1],
  884.                            colortable[brush.color][2],
  885.                            colortable[brush.color][3])
  886.   -- draw new palette colors
  887.   drawPaletteFrame()
  888.   return true
  889. end
  890. local function changeRed(value) return changeColor(1, value) end
  891. local function changeGreen(value) return changeColor(2, value) end
  892. local function changeBlue(value) return changeColor(3, value) end
  893.  
  894. local function moveSelector(num)
  895.   if num == 0 and brush.color ~= 0 then
  896.     tb_red:setVisible(false)
  897.     tb_green:setVisible(false)
  898.     tb_blue:setVisible(false)
  899.     gpu.setBackground(color.back)
  900.     if FULLSIZE then
  901.       gpu.fill(MENUX+3, 10, 45, 3, ' ')
  902.     else
  903.       gpu.set(MENUX+3, 5, '      ')
  904.       gpu.set(MENUX+13, 5, '      ')
  905.       gpu.set(MENUX+23, 5, '      ')
  906.     end
  907.   elseif num ~= 0 and brush.color == 0 then
  908.     tb_red:setVisible(true); tb_red:draw(true)
  909.     tb_green:setVisible(true); tb_green:draw(true)
  910.     tb_blue:setVisible(true); tb_blue:draw(true)
  911.   end
  912.   brush.color = num
  913.   brush.moving = true
  914.   tb_red:setValue(colortable[num][1]); tb_red:draw(true)
  915.   tb_green:setValue(colortable[num][2]); tb_green:draw(true)
  916.   tb_blue:setValue(colortable[num][3]); tb_blue:draw(true)
  917. end
  918.  
  919. local function setTopView(norefresh)
  920.   view = TOP
  921.   -- top projection has less layers
  922.   if layer > HOLOH then layer = HOLOH end
  923.   if not norefresh then drawLayer() end
  924. end
  925. local function setFrontView() view = FRONT; drawLayer() end
  926. local function setSideView() view = SIDE; drawLayer() end
  927.  
  928. local function drawHologram()
  929.   -- check projector availability
  930.   local projector = trytofind('hologram')
  931.   if projector ~= nil then
  932.     local depth = projector.maxDepth()
  933.     -- clean it up
  934.     projector.clear()
  935.     -- send the palette
  936.     if depth == 2 then
  937.       for i=1, 3 do
  938.         projector.setPaletteColor(i, hexcolortable[i])
  939.       end
  940.     else
  941.       projector.setPaletteColor(1, hexcolortable[1])
  942.     end
  943.     -- send data array
  944.     for x=1, HOLOW do
  945.       for y=1, HOLOH do
  946.         for z=1, HOLOW do
  947.           n = get(x,y,z)
  948.           if n ~= 0 then
  949.             if depth == 2 then
  950.               projector.set(x,y,z,n)
  951.             else
  952.               projector.set(x,y,z,1)
  953.             end
  954.           end
  955.         end
  956.       end      
  957.     end
  958.   else
  959.     showMessage(loc.PROJECTOR_UNAVAILABLE_MESSAGE, loc.ERROR_CAPTION, color.error)
  960.   end
  961. end
  962.  
  963. local function newHologram()
  964.   holo = {}
  965.   drawLayer()
  966. end
  967.  
  968. local function saveHologram()
  969.   local filename = tb_file:getValue()
  970.   if filename ~= loc.FILE_REQUEST then
  971.     -- show a warning
  972.     showMessage(loc.SAVING_MESSAGE, loc.WARNING_CAPTION, color.gold)
  973.     local compressed = true
  974.     -- add our 'brand' extension =)
  975.     if string.sub(filename, -3) == '.3d' then compressed = false
  976.     elseif string.sub(filename, -4) ~= '.3dx' then
  977.       filename = filename..'.3dx'
  978.     end
  979.     -- save
  980.     local ok, message = save(filename, compressed)
  981.     if ok then
  982.       showMessage(loc.SAVED_MESSAGE, loc.DONE_CAPTION, color.gold)
  983.     else
  984.       showMessage(message, loc.ERROR_CAPTION, color.error)
  985.     end
  986.   end
  987. end
  988.  
  989. local function loadHologram()
  990.   local filename = tb_file:getValue()
  991.   if filename ~= loc.FILE_REQUEST then
  992.     -- show a warning
  993.     showMessage(loc.LOADING_MESSAGE, loc.WARNING_CAPTION, color.gold)
  994.     local compressed = nil
  995.     -- add our 'brand' extension =)
  996.     if string.sub(filename, -3) == '.3d' then compressed = false
  997.     elseif string.sub(filename, -4) == '.3dx' then compressed = true end
  998.     -- load
  999.     local ok, message = nil, nil
  1000.     if compressed ~= nil then
  1001.       ok, message = load(filename, compressed)
  1002.     else
  1003.       -- if no file extension vas specified, try both alternately
  1004.       ok, message = load(filename..'.3dx', true)
  1005.       if not ok then
  1006.         ok, message = load(filename..'.3d', false)
  1007.       end
  1008.     end
  1009.     if ok then
  1010.       -- refresh textboxes
  1011.       tb_red:setValue(colortable[brush.color][1]); tb_red:draw(true)
  1012.       tb_green:setValue(colortable[brush.color][2]); tb_green:draw(true)
  1013.       tb_blue:setValue(colortable[brush.color][3]); tb_blue:draw(true)
  1014.       -- refresh the palette
  1015.       drawPaletteFrame()
  1016.       -- reset viewport
  1017.       setTopView(true)
  1018.       setLayer(1)
  1019.     else
  1020.       showMessage(message, loc.ERROR_CAPTION, color.error)
  1021.     end
  1022.   end
  1023. end
  1024.  
  1025.  
  1026. -- =========================================== M A I N   C Y C L E =========================================== --
  1027. -- initialization
  1028. -- chech screen resolution; you must have tier 2/3 GPU and tier 3 monitor to work
  1029. if HEIGHT < HOLOW/2 then
  1030.   error(loc.TOO_LOW_RESOLUTION_ERROR)
  1031. elseif HEIGHT < HOLOW+2 then
  1032.   com.screen.setPrecise(true)
  1033.   if not com.screen.isPrecise() then error(loc.TOO_LOW_SCREEN_TIER) end
  1034.   FULLSIZE = false
  1035.   MENUX = HOLOW + 2
  1036.   color.gray = color.lightgray
  1037.   GRIDX = 1
  1038.   GRIDY = 2
  1039.   BUTTONW = 9
  1040. else
  1041.   com.screen.setPrecise(false)
  1042.   WIDTH = HOLOW*2 + 40
  1043.   HEIGHT = HOLOW + 2
  1044. end
  1045. gpu.setResolution(WIDTH, HEIGHT)
  1046. gpu.setForeground(color.fore)
  1047. gpu.setBackground(color.back)
  1048.  
  1049. -- set default palette
  1050. colortable = {{255, 0, 0}, {0, 255, 0}, {0, 102, 255}}
  1051. colortable[0] = {0, 0, 0}  -- eraser
  1052. for i=0, 3 do setHexColor(i, colortable[i][1], colortable[i][2], colortable[i][3]) end
  1053.  
  1054. initGraphics()
  1055.  
  1056. -- generate interface
  1057. if FULLSIZE then
  1058.   buttonNew(buttons, exit, WIDTH-BUTTONW-2, HEIGHT-2, loc.EXIT_BUTTON, color.back, color.error, BUTTONW)
  1059.   buttonNew(buttons, drawLayer, MENUX+11, 14, loc.REFRESH_BUTTON, color.back, color.gold, BUTTONW)
  1060.   buttonNew(buttons, prevLayer, MENUX+1, 19, '-', color.fore, color.info, 5)
  1061.   buttonNew(buttons, nextLayer, MENUX+7, 19, '+', color.fore, color.info, 5)
  1062.   buttonNew(buttons, setTopView, MENUX+1, 21, loc.TOP_BUTTON, color.fore, color.info, 10)
  1063.   buttonNew(buttons, setFrontView, MENUX+12, 21, loc.FRONT_BUTTON, color.fore, color.info, 10)
  1064.   buttonNew(buttons, setSideView, MENUX+24, 21, loc.SIDE_BUTTON, color.fore, color.info, 9)
  1065.  
  1066.   buttonNew(buttons, prevGhost, MENUX+1, 24, loc.BELOW_BUTTON, color.fore, color.info, 6)
  1067.   buttonNew(buttons, nextGhost, MENUX+10, 24, loc.ABOVE_BUTTON, color.fore, color.info, 6)
  1068.  
  1069.   buttonNew(buttons, clearLayer, MENUX+1, 26, loc.CLEAR_BUTTON, color.fore, color.info, BUTTONW)
  1070.   buttonNew(buttons, fillLayer, MENUX+2+BUTTONW, 26, loc.FILL_BUTTON, color.fore, color.info, BUTTONW)
  1071.  
  1072.   buttonNew(buttons, drawHologram, MENUX+9, 30, loc.TO_PROJECTOR, color.back, color.gold, 16)
  1073.   buttonNew(buttons, saveHologram, MENUX+1, 33, loc.SAVE_BUTTON, color.fore, color.help, BUTTONW)
  1074.   buttonNew(buttons, loadHologram, MENUX+8+BUTTONW, 33, loc.LOAD_BUTTON, color.fore, color.info, BUTTONW)
  1075.   buttonNew(buttons, newHologram, MENUX+1, 35, loc.NEW_FILE_BUTTON, color.fore, color.info, BUTTONW)
  1076. else
  1077.   buttonNew(buttons, exit, WIDTH-BUTTONW-1, HEIGHT, loc.EXIT_BUTTON, color.back, color.error, BUTTONW)
  1078.   buttonNew(buttons, drawLayer, MENUX+9, 6, loc.REFRESH_BUTTON, color.back, color.gold, BUTTONW)
  1079.   buttonNew(buttons, prevLayer, MENUX+1, 9, '-', color.fore, color.info, 5)
  1080.   buttonNew(buttons, nextLayer, MENUX+7, 9, '+', color.fore, color.info, 5)
  1081.   buttonNew(buttons, setTopView, MENUX+1, 11, loc.TOP_BUTTON, color.fore, color.info, 8)
  1082.   buttonNew(buttons, setFrontView, MENUX+10, 12, loc.FRONT_BUTTON, color.fore, color.info, 8)
  1083.   buttonNew(buttons, setSideView, MENUX+20, 13, loc.SIDE_BUTTON, color.fore, color.info, 8)
  1084.  
  1085.   buttonNew(buttons, clearLayer, MENUX+1, 15, loc.CLEAR_BUTTON, color.fore, color.info, BUTTONW)
  1086.   buttonNew(buttons, fillLayer, MENUX+14, 15, loc.FILL_BUTTON, color.fore, color.info, BUTTONW)
  1087.  
  1088.   buttonNew(buttons, drawHologram, MENUX+7, 17, loc.TO_PROJECTOR, color.back, color.gold, 16)
  1089.   buttonNew(buttons, saveHologram, MENUX+1, 20, loc.SAVE_BUTTON, color.fore, color.help, BUTTONW)
  1090.   buttonNew(buttons, loadHologram, MENUX+16, 20, loc.LOAD_BUTTON, color.fore, color.info, BUTTONW)
  1091.   buttonNew(buttons, newHologram, MENUX+1, 21, loc.NEW_FILE_BUTTON, color.fore, color.info, BUTTONW)
  1092. end
  1093.  
  1094. local function isNumber(value) if tonumber(value) ~= nil then return true else return false end end
  1095. local function correctLayer(value)
  1096.   local n = tonumber(value)
  1097.   if n~= nil then
  1098.     if view == TOP then
  1099.       if n > 0 and n <= HOLOH then return true end
  1100.     else
  1101.       if n > 0 and n <= HOLOW then return true end
  1102.     end
  1103.   end
  1104.   return false
  1105. end
  1106.  
  1107. tb_red, tb_green, tb_blue, tb_layer, tb_ghostlayer, tb_file = nil, nil, nil, nil, nil, nil
  1108. if FULLSIZE then
  1109.   tb_red = textboxNew(textboxes, isNumber, changeRed, MENUX+5, 10, '255', WIDTH-MENUX-7)
  1110.   tb_green = textboxNew(textboxes, isNumber, changeGreen, MENUX+5, 11, '0', WIDTH-MENUX-7)
  1111.   tb_blue = textboxNew(textboxes, isNumber, changeBlue, MENUX+5, 12, '0', WIDTH-MENUX-7)
  1112.   tb_layer = textboxNew(textboxes, correctLayer, setLayer, MENUX+13, 19, '1', WIDTH-MENUX-15)
  1113.   tb_ghostlayer = textboxNew(textboxes, correctLayer, setGhostLayer, MENUX+19, 24, '', WIDTH-MENUX-21)
  1114.   tb_file = textboxNew(textboxes, function() return true end, setFilename, MENUX+1, 32, loc.FILE_REQUEST, WIDTH-MENUX-3)
  1115. else
  1116.   tb_red = textboxNew(textboxes, isNumber, changeRed, MENUX+3, 5, '255', 6)
  1117.   tb_green = textboxNew(textboxes, isNumber, changeGreen, MENUX+13, 5, '0', 6)
  1118.   tb_blue = textboxNew(textboxes, isNumber, changeBlue, MENUX+23, 5, '0', 6)
  1119.   tb_layer = textboxNew(textboxes, correctLayer, setLayer, MENUX+13, 9, '1', WIDTH-MENUX-14)
  1120.   tb_file = textboxNew(textboxes, function() return true end, setFilename, MENUX+1, 19, loc.FILE_REQUEST, WIDTH-MENUX-2)
  1121. end
  1122.  
  1123. mainScreen()
  1124. moveSelector(1)
  1125.  
  1126. local function delay(active) if active then return 0.02 else return 2.0 end end
  1127.  
  1128. while running do
  1129.   local name, add, x, y, button = event.pull(delay(brush.moving))
  1130.  
  1131.   if name == 'key_down' then
  1132.     -- if 'Q' was pressed - the quit app
  1133.     if y == 16 then break
  1134.     elseif y == 41 then
  1135.       moveSelector(0)
  1136.     elseif y>=2 and y<=4 then
  1137.       moveSelector(y-1)
  1138.     elseif y == 211 then
  1139.       clearLayer()
  1140.     end
  1141.   elseif name == 'touch' or name == 'drag' then
  1142.   -- rerender the screen after message box
  1143.     if repaint then drawLayer()
  1144.     else
  1145.       if name == 'touch' then
  1146.         -- check the GUI
  1147.         buttonsClick(buttons, math.ceil(x), math.ceil(y))
  1148.         textboxesClick(textboxes, math.ceil(x), math.ceil(y))
  1149.         -- color selection
  1150.         if x > MENUX+1 and x < MENUX+37 then
  1151.           if FULLSIZE then
  1152.             if y > 4 and y < 8 then
  1153.               moveSelector(math.floor((x-MENUX-1)/colorCursorWidth))
  1154.             end
  1155.           else
  1156.             if y > 1 and y < 4 and x < WIDTH-2 then
  1157.               moveSelector(math.floor((x-MENUX-1)/colorCursorWidth))
  1158.             end
  1159.           end
  1160.         end
  1161.       end
  1162.      
  1163.       -- "render"
  1164.       local limit
  1165.       if view == TOP then limit = HOLOW else limit = HOLOH end
  1166.      
  1167.       local dx, dy = nil, nil
  1168.       if FULLSIZE then
  1169.         if x >= GRIDX and x < GRIDX+HOLOW*2 then
  1170.           if y >= GRIDY and y < GRIDY+limit then
  1171.             dx, dy = math.floor((x-GRIDX)/2)+1, math.floor(y-GRIDY+1)
  1172.           end
  1173.         end
  1174.       else
  1175.         if x >= (GRIDX-1) and x <= GRIDX+HOLOW then
  1176.           if y >= (GRIDY-1) and y <= GRIDY+limit/2 then
  1177.             dx, dy = math.floor(x - GRIDX + 2), math.floor((y-GRIDY+1)*2)+1
  1178.           end
  1179.         end
  1180.       end
  1181.       if dx ~= nil then
  1182.         local a, b, c = project(dx, dy, layer, view)
  1183.         if button == 0 then set(a, b, c, brush.color)
  1184.         else set(a, b, c, 0) end
  1185.         drawVoxel(dx, dy)
  1186.       end
  1187.     end
  1188.   end
  1189.  
  1190.   drawColorCursor()
  1191. end
  1192.  
  1193. -- finalize
  1194. gpu.setResolution(OLDWIDTH, OLDHEIGHT)
  1195. gpu.setForeground(0xFFFFFF)
  1196. gpu.setBackground(0x000000)
  1197. term.clear()
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top