-- pain2 local scr_x, scr_y = term.getSize() local mx, my = scr_x/2, scr_y/2 -- midpoint of screen local keysDown = {} -- list of all pushed keys local miceDown = {} -- list of all clicked mice buttons local dragPoses = {{{},{}}, {{},{}}, {{},{}}} -- records initial and current mouse position per button while scrolling local TICKNO = 0 -- iterates every time main() loops local flashPaletteOnBar = false -- whether or not to flash the dot palette numbers on the bottom bar -- debug renderer is slower, but the normal one isn't functional yet local useDebugRenderer = false local canvas = { {{},{},{}} } local frame = 1 local dot = 1 local pain = { screenWidth = scr_x, screenHeight = scr_y, scrollX = 0, scrollY = 0, brushSize = 2, barmsg = "Started PAIN.", barlife = 12, showBar = true, doRender = true, size = { x = 1, y = 1, width = scr_x, height = scr_y }, dots = { [0] = { " ", " ", " " }, [1] = { " ", "f", "0" }, [2] = { " ", "f", "e" }, }, tool = "pencil" } local setBarMsg = function(message) pain.barmsg = message pain.barlife = 16 pain.doRender = true end local controlHoldCheck = {} -- used to prevent repeated inputs on non-repeating controls local control = { quit = { key = keys.q, holdDown = false, modifiers = { [keys.leftCtrl] = true }, }, scrollUp = { -- decrease scrollY key = keys.up, holdDown = true, modifiers = {}, }, scrollDown = { key = keys.down, holdDown = true, modifiers = {}, }, scrollLeft = { key = keys.left, holdDown = true, modifiers = {}, }, scrollRight = { key = keys.right, holdDown = true, modifiers = {}, }, resetScroll = { key = keys.a, holdDown = false, modifiers = {}, }, switchNextFrame = { key = keys.rightBracket, holdDown = false, modifiers = {}, }, switchPrevFrame = { key = keys.leftBracket, holdDown = false, modifiers = {}, }, increaseBrushSize = { key = keys.equals, holdDown = false, modifiers = {}, }, increaseBrushSize_Alt = { key = keys.numPadAdd, holdDown = false, modifiers = {}, }, decreaseBrushSize = { key = keys.minus, holdDown = false, modifiers = {}, }, decreaseBrushSize_Alt = { key = keys.numPadSubtract, holdDown = false, modifiers = {}, }, moveMod = { key = keys.leftShift, holdDown = true, modifiers = { [keys.leftShift] = true }, }, creepMod = { key = keys.leftAlt, holdDown = true, modifiers = { [keys.leftAlt] = true }, }, toolMod = { key = keys.leftShift, holdDown = true, modifiers = { [keys.leftShift] = true }, }, pencilTool = { key = keys.p, holdDown = false, modifiers = { [keys.leftShift] = true }, }, brushTool = { key = keys.b, holdDown = false, modifiers = { [keys.leftShift] = true }, }, textTool = { key = keys.t, holdDown = false, modifiers = { [keys.leftShift] = true }, }, lineTool = { key = keys.l, holdDown = false, modifiers = { [keys.leftShift] = true }, }, selectPalette_0 = { key = keys.zero, holdDown = false, modifiers = { [keys.leftShift] = true }, }, selectPalette_1 = { key = keys.one, holdDown = false, modifiers = { [keys.leftShift] = true }, }, selectPalette_2 = { key = keys.two, holdDown = false, modifiers = { [keys.leftShift] = true }, }, selectPalette_3 = { key = keys.three, holdDown = false, modifiers = { [keys.leftShift] = true }, }, selectPalette_4 = { key = keys.four, holdDown = false, modifiers = { [keys.leftShift] = true }, }, selectPalette_5 = { key = keys.five, holdDown = false, modifiers = { [keys.leftShift] = true }, }, selectPalette_6 = { key = keys.six, holdDown = false, modifiers = { [keys.leftShift] = true }, }, selectPalette_7 = { key = keys.seven, holdDown = false, modifiers = { [keys.leftShift] = true }, }, selectPalette_8 = { key = keys.eight, holdDown = false, modifiers = { [keys.leftShift] = true }, }, selectPalette_9 = { key = keys.nine, holdDown = false, modifiers = { [keys.leftShift] = true }, }, } local checkControl = function(name) local modlist = { keys.leftCtrl, keys.rightCtrl, keys.leftShift, keys.rightShift, keys.leftAlt, keys.rightAlt, } for i = 1, #modlist do if control[name].modifiers[modlist[i]] then if not keysDown[modlist[i]] then return false end else if keysDown[modlist[i]] then return false end end end if keysDown[control[name].key] then if control[name].holdDown then return true else if not controlHoldCheck[name] then controlHoldCheck[name] = true return true end end else controlHoldCheck[name] = false return false end end -- converts hex colors to colors api, and back local to_colors, to_blit = { [' '] = 0, ['0'] = 1, ['1'] = 2, ['2'] = 4, ['3'] = 8, ['4'] = 16, ['5'] = 32, ['6'] = 64, ['7'] = 128, ['8'] = 256, ['9'] = 512, ['a'] = 1024, ['b'] = 2048, ['c'] = 4096, ['d'] = 8192, ['e'] = 16384, ['f'] = 32768, }, {} for k,v in pairs(to_colors) do to_blit[v] = k end -- takes two coordinates, and returns every point between the two local getDotsInLine = function( startX, startY, endX, endY ) local out = {} startX = math.floor(startX) startY = math.floor(startY) endX = math.floor(endX) endY = math.floor(endY) if startX == endX and startY == endY then out = {{x=startX,y=startY}} return out end local minX = math.min( startX, endX ) if minX == startX then minY = startY maxX = endX maxY = endY else minY = endY maxX = startX maxY = startY end local xDiff = maxX - minX local yDiff = maxY - minY if xDiff > math.abs(yDiff) then local y = minY local dy = yDiff / xDiff for x=minX,maxX do out[#out+1] = {x=x,y=math.floor(y+0.5)} y = y + dy end else local x = minX local dx = xDiff / yDiff if maxY >= minY then for y=minY,maxY do out[#out+1] = {x=math.floor(x+0.5),y=y} x = x + dx end else for y=minY,maxY,-1 do out[#out+1] = {x=math.floor(x+0.5),y=y} x = x - dx end end end return out end -- deletes a dot on the canvas, fool local deleteDot = function(x, y, frame) x, y = 1 + x - pain.size.x, 1 + y - pain.size.y if canvas[frame][1][y] then if canvas[frame][1][y][x] then canvas[frame][1][y][x] = nil canvas[frame][2][y][x] = nil canvas[frame][3][y][x] = nil end end end -- places a dot on the canvas, predictably enough local placeDot = function(x, y, frame, dot) x, y = 1 - pain.size.x + x, 1 - pain.size.y + y if not canvas[frame][1][y] then canvas[frame][1][y] = {} canvas[frame][2][y] = {} canvas[frame][3][y] = {} end canvas[frame][1][y][x] = dot[1] canvas[frame][2][y][x] = dot[2] canvas[frame][3][y][x] = dot[3] end -- used for tools that involve dragging local dragPos = {} local getGridAtPos = function(x, y) local grid = { "..%%", "..%%", "..%%", "%%..", "%%..", "%%..", } if x < 1 or y < 1 then return "/", "7", "f" else local sx, sy = 1 + (1 + x) % #grid[1], 1 + (2 + y) % #grid return grid[sy]:sub(sx,sx), "7", "f" end end -- shows everything on screen local render = function(x, y, width, height) local buffer = {{},{},{}} local cx, cy x = x or pain.size.x y = y or pain.size.y width = width or pain.size.width height = height or pain.size.height -- see, it wouldn't do if I just individually set the cursor position for every dot if useDebugRenderer then term.clear() local cx, cy for yy, line in pairs(canvas[frame][1]) do for xx, dot in pairs(canvas[frame][1][yy]) do cx = xx - pain.scrollX cy = yy - pain.scrollY if cx >= x and cx <= (x + width - 1) and cy >= y and cy <= (x + width - 1) then term.setCursorPos(cx, cy) term.blit( canvas[frame][1][yy][xx], canvas[frame][2][yy][xx], canvas[frame][3][yy][xx] ) end end end else local gChar, gText, gBack for yy = 1, -1 + height + y do buffer[1][yy] = "" buffer[2][yy] = "" buffer[3][yy] = "" if pain.showBar and yy == height then term.setTextColor(colors.black) term.setBackgroundColor(colors.lightGray) term.setCursorPos(pain.size.x, -1 + pain.size.y + pain.size.height) term.write("[" .. pain.scrollX .. "," .. pain.scrollY .. "] ") for i = 1, #pain.dots do if flashPaletteOnBar then term.blit(table.unpack(pain.dots[i])) else term.blit(tostring(i), "7", pain.dots[i][3]) end end if pain.barlife > 0 then term.write(" " .. pain.barmsg) end term.write((" "):rep(x + width - term.getCursorPos())) else for xx = 1, width do cx = xx + pain.scrollX cy = yy + pain.scrollY if canvas[frame][1][cy] then if canvas[frame][1][cy][cx] then for c = 1, 3 do buffer[c][yy] = buffer[c][yy] .. canvas[frame][c][cy][cx] end else gChar, gText, gBack = getGridAtPos(cx, cy) buffer[1][yy] = buffer[1][yy] .. gChar buffer[2][yy] = buffer[2][yy] .. gText buffer[3][yy] = buffer[3][yy] .. gBack end else gChar, gText, gBack = getGridAtPos(cx, cy) buffer[1][yy] = buffer[1][yy] .. gChar buffer[2][yy] = buffer[2][yy] .. gText buffer[3][yy] = buffer[3][yy] .. gBack end end end end for yy = 0, height - 1 do term.setCursorPos(x, y + yy) term.blit(buffer[1][yy+1], buffer[2][yy+1], buffer[3][yy+1]) end end end -- every tool at your disposal local tools = { pencil = { info = { name = "Pencil", swapTool = "line", -- if swap button is held, will turn into this tool swapArg = { -- any values in this table will overwrite those in 'arg' size = 1 }, }, run = function(arg) if arg.event == "mouse_click" then if arg.button == 1 then placeDot(arg.sx, arg.sy, frame, arg.dot) elseif arg.button == 2 then deleteDot(arg.sx, arg.sy, frame) end dragPos = {arg.sx, arg.sy} else if #dragPos == 0 then dragPos = {arg.sx, arg.sy} end local poses = getDotsInLine(arg.sx, arg.sy, dragPos[1], dragPos[2]) for i = 1, #poses do if arg.button == 1 then placeDot(poses[i].x, poses[i].y, frame, arg.dot) elseif arg.button == 2 then deleteDot(poses[i].x, poses[i].y, frame) end end dragPos = {arg.sx, arg.sy} end end }, brush = { info = { name = "Brush", swapTool = "line", swapArg = {}, }, run = function(arg) if arg.event == "mouse_click" then for y = -arg.size, arg.size do for x = -arg.size, arg.size do if math.sqrt(x^2 + y^2) <= arg.size / 2 then if arg.button == 1 then placeDot(arg.sx + x, arg.sy + y, frame, arg.dot) elseif arg.button == 2 then deleteDot(arg.sx + x, arg.sy + y, frame) end end end end dragPos = {arg.sx, arg.sy} else if #dragPos == 0 then dragPos = {arg.sx, arg.sy} end local poses = getDotsInLine(arg.sx, arg.sy, dragPos[1], dragPos[2]) for i = 1, #poses do for y = -arg.size, arg.size do for x = -arg.size, arg.size do if math.sqrt(x^2 + y^2) <= arg.size / 2 then if arg.button == 1 then placeDot(poses[i].x + x, poses[i].y + y, frame, arg.dot) elseif arg.button == 2 then deleteDot(poses[i].x + x, poses[i].y + y, frame) end end end end end dragPos = {arg.sx, arg.sy} end end }, text = function(arg) pain.paused = true pain.barmsg = "Type text to add to canvas." pain.barlife = 1 render() term.setCursorPos(arg.x, arg.y) term.setTextColor(to_colors[arg.dot[2]]) term.setBackgroundColor(to_colors[arg.dot[3]]) local text = read() -- re-render every keypress, requires custom read function for i = 1, #text do placeDot(arg.sx + i - 1, arg.sy, frame, {text:sub(i,i), pain.dots[dot][2], pain.dots[dot][3]}) end pain.paused = false keysDown = {} miceDown = {} end, line = { info = { name = "Line", swapTool = "pencil", }, run = function(arg) local dots while miceDown[arg.button] do dots = getDotsInLine( dragPoses[arg.button][1].x + (arg.scrollX - pain.scrollX), dragPoses[arg.button][1].y + (arg.scrollY - pain.scrollY), dragPoses[arg.button][2].x, dragPoses[arg.button][2].y ) render() for i = 1, #dots do if dots[i].x >= pain.size.x and dots[i].x < pain.size.x + pain.size.width then for y = -arg.size, arg.size do for x = -arg.size, arg.size do if math.sqrt(x^2 + y^2) <= arg.size / 2 then if (not pain.showBar) or dots[i].y + y < -1 + pain.size.y + pain.size.height then term.setCursorPos(dots[i].x + x, dots[i].y + y) if arg.button == 1 then term.blit(table.unpack(arg.dot)) elseif arg.button == 2 then term.blit(getGridAtPos(dots[i].x + pain.scrollX, dots[i].y + pain.scrollY)) end end end end end end end os.pullEvent() end -- write dots to canvas for i = 1, #dots do for y = -arg.size, arg.size do for x = -arg.size, arg.size do if math.sqrt(x^2 + y^2) <= arg.size / 2 then if arg.button == 1 then placeDot(dots[i].x + x + pain.scrollX, dots[i].y + y + pain.scrollY, frame, arg.dot) elseif arg.button == 2 then deleteDot(dots[i].x + x + pain.scrollX, dots[i].y + y + pain.scrollY, frame) end end end end end end }, } local tryTool = function() local swapArg, t = {} if checkControl("toolMod") then t = tools[tools[pain.tool].info.swapTool] swapArg = tools[pain.tool].info.swapArg or {} else t = tools[pain.tool] end for butt = 1, 3 do if miceDown[butt] and t then t.run({ x = swapArg.x or miceDown[butt].x, y = swapArg.y or miceDown[butt].y, sx = swapArg.sx or ((swapArg.x or miceDown[butt].x) + pain.scrollX), sy = swapArg.sy or ((swapArg.y or miceDown[butt].y) + pain.scrollY), scrollX = swapArg.scrollX or pain.scrollX, scrollY = swapArg.scrollY or pain.scrollY, frame = swapArg.frame or frame, dot = swapArg.dot or pain.dots[dot], size = swapArg.size or pain.brushSize, button = swapArg.button or butt, event = swapArg.event or miceDown[butt].event }) pain.doRender = true break end end end local getInput = function() local evt while true do evt = {os.pullEvent()} if evt[1] == "mouse_click" or evt[1] == "mouse_drag" then if evt[3] >= pain.size.x and evt[3] <= -1 + pain.size.x + pain.size.width and evt[4] >= pain.size.y and evt[4] <= -1 + pain.size.y + pain.size.height then if evt[4] == -1 + pain.size.y + pain.size.height then -- openBarMenu() else dragPoses[evt[2]] = { { x = dragPoses[evt[2]][1].x or evt[3], y = dragPoses[evt[2]][1].y or evt[4] }, { x = evt[3], y = evt[4] } } miceDown[evt[2]] = { event = evt[1], button = evt[2], x = evt[3], y = evt[4], } end end elseif evt[1] == "key" then keysDown[evt[2]] = true elseif evt[1] == "mouse_up" then dragPoses[evt[2]] = {{},{}}, {{},{}}, {{},{}} miceDown[evt[2]] = false elseif evt[1] == "key_up" then keysDown[evt[2]] = false end end end -- executes everything that doesn't run asynchronously main = function() while true do if not pain.paused then if TICKNO % 30 <= 20 then flashPaletteOnBar = true pain.doRender = true elseif (TICKNO + 3) % 30 <= 20 then flashPaletteOnBar = false pain.doRender = true end if pain.doRender then render() pain.doRender = false end if checkControl("quit") then return true end -- handle scrolling if checkControl("resetScroll") then pain.scrollX = 0 pain.scrollY = 0 pain.doRender = true else if checkControl("increaseBrushSize") or checkControl("increaseBrushSize_Alt") then pain.brushSize = math.min(pain.brushSize + 1, 16) setBarMsg("Increased brush size to " .. pain.brushSize .. ".") elseif checkControl("decreaseBrushSize") or checkControl("decreaseBrushSize_Alt") then pain.brushSize = math.max(pain.brushSize - 1, 1) setBarMsg("Decreased brush size to " .. pain.brushSize .. ".") elseif checkControl("scrollLeft") then pain.scrollX = pain.scrollX - 1 pain.doRender = true end if checkControl("scrollRight") then pain.scrollX = pain.scrollX + 1 pain.doRender = true end if checkControl("scrollUp") then pain.scrollY = pain.scrollY - 1 pain.doRender = true end if checkControl("scrollDown") then pain.scrollY = pain.scrollY + 1 pain.doRender = true end end for i = 0, 9 do if checkControl("selectPalette_" .. i) then if pain.dots[i] then dot = i setBarMsg("Selected palette " .. dot .. ".") break else setBarMsg("There is no palette " .. i .. ".") break end end end if checkControl("pencilTool") then pain.tool = "pencil" setBarMsg("Selected pencil tool.") elseif checkControl("textTool") then pain.tool = "text" setBarMsg("Selected text tool.") elseif checkControl("brushTool") then pain.tool = "brush" setBarMsg("Selected brush tool.") elseif checkControl("lineTool") then pain.tool = "line" setBarMsg("Selected line tool.") end pain.barlife = math.max(pain.barlife - 1, 0) if pain.barlife == 0 and pain.barmsg ~= "" then pain.barmsg = "" pain.doRender = true end end TICKNO = TICKNO + 1 sleep(0.05) end end local keepTryingTools = function() while true do os.pullEvent() tryTool() end end term.clear() parallel.waitForAny( main, getInput, keepTryingTools ) -- exit cleanly term.setCursorPos(1, scr_y) term.setBackgroundColor(colors.black) term.setTextColor(colors.white) term.clearLine()