Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- +--------------------------------------------------------+
- -- | |
- -- | BLittle |
- -- | |
- -- +--------------------------------------------------------+
- local version = "Version 1.1.6beta"
- -- By Jeffrey Alexander, aka Bomb Bloke.
- -- Convenience functions to make use of ComputerCraft 1.76's new "drawing" characters.
- -- http://www.computercraft.info/forums2/index.php?/topic/25354-cc-176-blittle-api/
- -------------------------------------------------------------
- if shell then
- local arg = {...}
- if #arg == 0 then
- print("Usage:")
- print("blittle <scriptName> [args]")
- return
- end
- if not blittle then os.loadAPI(shell.getRunningProgram()) end
- local oldTerm = term.redirect(blittle.createWindow())
- shell.run(unpack(arg))
- term.redirect(oldTerm)
- return
- end
- local relations = {[0] = {8, 4, 3, 6, 5}, {4, 14, 8, 7}, {6, 10, 8, 7}, {9, 11, 8, 0}, {1, 14, 8, 0}, {13, 12, 8, 0}, {2, 10, 8, 0}, {15, 8, 10, 11, 12, 14},
- {0, 7, 1, 9, 2, 13}, {3, 11, 8, 7}, {2, 6, 7, 15}, {9, 3, 7, 15}, {13, 5, 7, 15}, {5, 12, 8, 7}, {1, 4, 7, 15}, {7, 10, 11, 12, 14}}
- local colourNum, exponents, colourChar = {}, {}, {}
- for i = 0, 15 do exponents[2^i] = i end
- do
- local hex = "0123456789abcdef"
- for i = 1, 16 do
- colourNum[hex:sub(i, i)] = i - 1
- colourNum[i - 1] = hex:sub(i, i)
- colourChar[hex:sub(i, i)] = 2 ^ (i - 1)
- colourChar[2 ^ (i - 1)] = hex:sub(i, i)
- local thisRel = relations[i - 1]
- for i = 1, #thisRel do thisRel[i] = 2 ^ thisRel[i] end
- end
- end
- local function getBestColourMatch(usage)
- local lastCol = relations[exponents[usage[#usage][1]]]
- for j = 1, #lastCol do
- local thisRelation = lastCol[j]
- for i = 1, #usage - 1 do if usage[i][1] == thisRelation then return i end end
- end
- return 1
- end
- local function colsToChar(pattern, totals)
- if not totals then
- local newPattern = {}
- totals = {}
- for i = 1, 6 do
- local thisVal = pattern[i]
- local thisTot = totals[thisVal]
- totals[thisVal], newPattern[i] = thisTot and (thisTot + 1) or 1, thisVal
- end
- pattern = newPattern
- end
- local usage = {}
- for key, value in pairs(totals) do usage[#usage + 1] = {key, value} end
- if #usage > 1 then
- -- Reduce the chunk to two colours:
- while #usage > 2 do
- table.sort(usage, function (a, b) return a[2] > b[2] end)
- local matchToInd, usageLen = getBestColourMatch(usage), #usage
- local matchFrom, matchTo = usage[usageLen][1], usage[matchToInd][1]
- for i = 1, 6 do if pattern[i] == matchFrom then
- pattern[i] = matchTo
- usage[matchToInd][2] = usage[matchToInd][2] + 1
- end end
- usage[usageLen] = nil
- end
- -- Convert to character. Adapted from oli414's function:
- -- http://www.computercraft.info/forums2/index.php?/topic/25340-cc-176-easy-drawing-characters/
- local data = 128
- for i = 1, #pattern - 1 do if pattern[i] ~= pattern[6] then data = data + 2^(i-1) end end
- return string.char(data), colourChar[usage[1][1] == pattern[6] and usage[2][1] or usage[1][1]], colourChar[pattern[6]]
- else
- -- Solid colour character:
- return "\128", colourChar[pattern[1]], colourChar[pattern[1]]
- end
- end
- local function snooze()
- local myEvent = tostring({})
- os.queueEvent(myEvent)
- os.pullEvent(myEvent)
- end
- function shrink(image, bgCol)
- local results, width, height, bgCol = {{}, {}, {}}, 0, #image + #image % 3, bgCol or colours.black
- for i = 1, #image do if #image[i] > width then width = #image[i] end end
- for y = 0, height - 1, 3 do
- local cRow, tRow, bRow, counter = {}, {}, {}, 1
- for x = 0, width - 1, 2 do
- -- Grab a 2x3 chunk:
- local pattern, totals = {}, {}
- for yy = 1, 3 do for xx = 1, 2 do
- pattern[#pattern + 1] = (image[y + yy] and image[y + yy][x + xx]) and (image[y + yy][x + xx] == 0 and bgCol or image[y + yy][x + xx]) or bgCol
- totals[pattern[#pattern]] = totals[pattern[#pattern]] and (totals[pattern[#pattern]] + 1) or 1
- end end
- cRow[counter], tRow[counter], bRow[counter] = colsToChar(pattern, totals)
- counter = counter + 1
- end
- results[1][#results[1] + 1], results[2][#results[2] + 1], results[3][#results[3] + 1] = table.concat(cRow), table.concat(tRow), table.concat(bRow)
- end
- results.width, results.height = #results[1][1], #results[1]
- return results
- end
- function shrinkGIF(image, bgCol)
- if not GIF and not os.loadAPI("GIF") then error("blittle.shrinkGIF: Load GIF API first.", 2) end
- image = GIF.flattenGIF(image)
- snooze()
- local prev = GIF.toPaintutils(image[1])
- snooze()
- prev = blittle.shrink(prev, bgCol)
- prev.delay = image[1].delay
- image[1] = prev
- snooze()
- image.width, image.height = prev.width, prev.height
- for i = 2, #image do
- local temp = GIF.toPaintutils(image[i])
- snooze()
- temp = blittle.shrink(temp, bgCol)
- snooze()
- local newImage = {{}, {}, {}, ["delay"] = image[i].delay, ["width"] = temp.width, ["height"] = 0}
- local a, b, c, pa, pb, pc = temp[1], temp[2], temp[3], prev[1], prev[2], prev[3]
- for i = 1, temp.height do
- local a1, b1, c1, pa1, pb1, pc1 = a[i], b[i], c[i], pa[i], pb[i], pc[i]
- if a1 ~= pa1 or b1 ~= pb1 or c1 ~= pc1 then
- local min, max = 1, #a1
- local a2, b2, c2, pa2, pb2, pc2 = {a1:byte(1, max)}, {b1:byte(1, max)}, {c1:byte(1, max)}, {pa1:byte(1, max)}, {pb1:byte(1, max)}, {pc1:byte(1, max)}
- for j = 1, max do if a2[j] ~= pa2[j] or b2[j] ~= pb2[j] or c2[j] ~= pc2[j] then
- min = j
- break
- end end
- for j = max, min, -1 do if a2[j] ~= pa2[j] or b2[j] ~= pb2[j] or c2[j] ~= pc2[j] then
- max = j
- break
- end end
- newImage[1][i], newImage[2][i], newImage[3][i], newImage.height = min > 1 and {min - 1, a1:sub(min, max)} or a1:sub(min, max), b1:sub(min, max), c1:sub(min, max), i
- end
- snooze()
- end
- image[i], prev = newImage, temp
- for j = 1, i - 1 do
- local oldImage = image[j]
- if type(oldImage[1]) == "table" and oldImage.height == newImage.height then
- local same = true
- for k = 1, oldImage.height do
- local comp1, comp2 = oldImage[1][k], newImage[1][k]
- if type(comp1) ~= type(comp2) or
- (type(comp1) == "string" and comp1 ~= comp2) or
- (type(comp1) == "table" and (comp1[1] ~= comp2[1] or comp1[2] ~= comp2[2])) or
- oldImage[2][k] ~= newImage[2][k] or
- oldImage[3][k] ~= newImage[3][k] then
- same = false
- break
- end
- end
- if same then
- newImage[1], newImage[2], newImage[3] = j
- break
- end
- end
- snooze()
- end
- end
- return image
- end
- local function newLine(width, bCol)
- local line = {}
- for i = 1, width do line[i] = {bCol, bCol, bCol, bCol, bCol, bCol} end
- return line
- end
- function createWindow(parent, x, y, width, height, visible)
- if parent == term or not parent then
- parent = term.current()
- elseif type(parent) ~= "table" or not parent.write then
- error("blittle.newWindow: \"parent\" does not appear to be a terminal object.", 2)
- end
- local workBuffer, backBuffer, frontBuffer, window, tCol, bCol, curX, curY, blink, cWidth, cHeight, pal = {}, {}, {}, {}, colours.white, colours.black, 1, 1, false
- if type(visible) ~= "boolean" then visible = true end
- x, y = x and math.floor(x) or 1, y and math.floor(y) or 1
- do
- local xSize, ySize = parent.getSize()
- cWidth, cHeight = (width or xSize), (height or ySize)
- width, height = cWidth * 2, cHeight * 3
- end
- if parent.setPaletteColour then
- pal = {}
- local counter = 1
- for i = 1, 16 do
- pal[counter] = {parent.getPaletteColour(counter)}
- counter = counter * 2
- end
- window.getPaletteColour = function(colour)
- return unpack(pal[colour])
- end
- window.setPaletteColour = function(colour, r, g, b)
- pal[colour] = {r, g, b}
- if visible then return parent.setPaletteColour(colour, r, g, b) end
- end
- window.getPaletteColor, window.setPaletteColor = window.getPaletteColour, window.setPaletteColour
- end
- window.blit = function(_, _, bC)
- local bClen = #bC
- if curX > width or curX + bClen < 2 or curY < 1 or curY > height then
- curX = curX + bClen
- return
- end
- if curX < 1 then
- bC = bC:sub(2 - curX)
- curX, bClen = 1, #bC
- end
- if curX + bClen - 1 > width then bC, bClen = bC:sub(1, width - curX + 1), width - curX + 1 end
- local colNum, rowNum, thisX, yBump = math.floor((curX - 1) / 2) + 1, math.floor((curY - 1) / 3) + 1, (curX - 1) % 2, ((curY - 1) % 3) * 2
- local firstColNum, lastColNum, thisRow = colNum, math.floor((curX + bClen) / 2), backBuffer[rowNum]
- local thisChar = thisRow[colNum]
- for i = 1, bClen do
- thisChar[thisX + yBump + 1] = colourChar[bC:sub(i, i)]
- if thisX == 1 then
- thisX, colNum = 0, colNum + 1
- thisChar = thisRow[colNum]
- if not thisChar then break end
- else thisX = 1 end
- end
- if visible then
- local chars1, chars2, chars3, count = {}, {}, {}, 1
- for i = firstColNum, lastColNum do
- chars1[count], chars2[count], chars3[count] = colsToChar(thisRow[i])
- count = count + 1
- end
- chars1, chars2, chars3 = table.concat(chars1), table.concat(chars2), table.concat(chars3)
- parent.setCursorPos(x + math.floor((curX - 1) / 2), y + math.floor((curY - 1) / 3))
- parent.blit(chars1, chars2, chars3)
- local thisRow = frontBuffer[rowNum]
- frontBuffer[rowNum] = {thisRow[1]:sub(1, firstColNum - 1) .. chars1 .. thisRow[1]:sub(lastColNum + 1), thisRow[2]:sub(1, firstColNum - 1) .. chars2 .. thisRow[2]:sub(lastColNum + 1), thisRow[3]:sub(1, firstColNum - 1) .. chars3 .. thisRow[3]:sub(lastColNum + 1)}
- else
- local thisRow = workBuffer[rowNum]
- if (not thisRow[firstColNum]) or thisRow[firstColNum] < lastColNum then
- local x, newLastColNum = 1, lastColNum
- while x <= lastColNum + 1 do
- local thisSpot = thisRow[x]
- if thisSpot then
- if thisSpot >= firstColNum - 1 then
- if x < firstColNum then firstColNum = x else thisRow[x] = nil end
- if thisSpot > newLastColNum then newLastColNum = thisSpot end
- end
- x = thisSpot + 1
- else x = x + 1 end
- end
- thisRow[firstColNum] = newLastColNum
- if thisRow.max <= newLastColNum then thisRow.max = firstColNum end
- end
- end
- curX = curX + bClen
- end
- window.write = function(text)
- window.blit(nil, nil, string.rep(colourChar[bCol], #tostring(text)))
- end
- window.clearLine = function()
- local oldX = curX
- curX = 1
- window.blit(nil, nil, string.rep(colourChar[bCol], width))
- curX = oldX
- end
- window.clear = function()
- local t, fC, bC = string.rep("\128", cWidth), string.rep(colourChar[tCol], cWidth), string.rep(colourChar[bCol], cWidth)
- for y = 1, cHeight do workBuffer[y], backBuffer[y], frontBuffer[y] = {["max"] = 0}, newLine(cWidth, bCol), {t, fC, bC} end
- window.redraw()
- end
- window.getCursorPos = function()
- return curX, curY
- end
- window.setCursorPos = function(newX, newY)
- curX, curY = math.floor(newX), math.floor(newY)
- if visible and blink then window.restoreCursor() end
- end
- window.restoreCursor = function() end
- window.setCursorBlink = window.restoreCursor
- window.isColour = function()
- return parent.isColour()
- end
- window.isColor = window.isColour
- window.getSize = function()
- return width, height
- end
- window.scroll = function(lines)
- lines = math.floor(lines)
- if lines ~= 0 then
- if lines % 3 == 0 then
- local newWB, newBB, newFB, line1, line2, line3 = {}, {}, {}, string.rep("\128", cWidth), string.rep(colourChar[tCol], cWidth), string.rep(colourChar[bCol], cWidth)
- for y = 1, cHeight do newWB[y], newBB[y], newFB[y] = workBuffer[y + lines] or {["max"] = 0}, backBuffer[y + lines] or newLine(cWidth, bCol), frontBuffer[y + lines] or {line1, line2, line3} end
- workBuffer, backBuffer, frontBuffer = newWB, newBB, newFB
- else
- local newBB, tRowNum, tBump, sRowNum, sBump = {}, 1, 0, math.floor(lines / 3) + 1, (lines % 3) * 2
- local sRow, tRow = backBuffer[sRowNum], {}
- for x = 1, cWidth do tRow[x] = {} end
- for y = 1, height do
- if sRow then
- for x = 1, cWidth do
- local tChar, sChar = tRow[x], sRow[x]
- tChar[tBump + 1], tChar[tBump + 2] = sChar[sBump + 1], sChar[sBump + 2]
- end
- else
- for x = 1, cWidth do
- local tChar = tRow[x]
- tChar[tBump + 1], tChar[tBump + 2] = bCol, bCol
- end
- end
- tBump, sBump = tBump + 2, sBump + 2
- if tBump > 4 then
- tBump, newBB[tRowNum] = 0, tRow
- tRowNum, tRow = tRowNum + 1, {}
- for x = 1, cWidth do tRow[x] = {} end
- end
- if sBump > 4 then
- sRowNum, sBump = sRowNum + 1, 0
- sRow = backBuffer[sRowNum]
- end
- end
- for y = 1, cHeight do workBuffer[y] = {["max"] = 1, cWidth} end
- backBuffer = newBB
- end
- window.redraw()
- end
- end
- window.setTextColour = function(newCol)
- tCol = newCol
- end
- window.setTextColor = window.setTextColour
- window.setBackgroundColour = function(newCol)
- bCol = newCol
- end
- window.setBackgroundColor = window.setBackgroundColour
- window.getTextColour = function()
- return tCol
- end
- window.getTextColor = window.getTextColour
- window.getBackgroundColour = function()
- return bCol
- end
- window.getBackgroundColor = window.getBackgroundColour
- window.redraw = function()
- if visible then
- for i = 1, cHeight do
- local work, front = workBuffer[i], frontBuffer[i]
- local front1, front2, front3 = front[1], front[2], front[3]
- if work.max > 0 then
- local line1, line2, line3, lineLen, skip, back, count = {}, {}, {}, 1, 0, backBuffer[i], 1
- while count <= work.max do if work[count] then
- if skip > 0 then
- line1[lineLen], line2[lineLen], line3[lineLen] = front1:sub(count - skip, count - 1), front2:sub(count - skip, count - 1), front3:sub(count - skip, count - 1)
- skip, lineLen = 0, lineLen + 1
- end
- for i = count, work[count] do
- line1[lineLen], line2[lineLen], line3[lineLen] = colsToChar(back[i])
- lineLen = lineLen + 1
- end
- count = work[count] + 1
- else skip, count = skip + 1, count + 1 end end
- if count < cWidth + 1 then line1[lineLen], line2[lineLen], line3[lineLen] = front1:sub(count), front2:sub(count), front3:sub(count) end
- front1, front2, front3 = table.concat(line1), table.concat(line2), table.concat(line3)
- frontBuffer[i], workBuffer[i] = {front1, front2, front3}, {["max"] = 0}
- end
- parent.setCursorPos(x, y + i - 1)
- parent.blit(front1, front2, front3)
- end
- if pal then
- local counter = 1
- for i = 1, 16 do
- parent.setPaletteColour(counter, unpack(pal[counter]))
- counter = counter * 2
- end
- end
- end
- end
- window.setVisible = function(newVis)
- newVis = newVis and true or false
- if newVis and not visible then
- visible = true
- window.redraw()
- else visible = newVis end
- end
- window.getPosition = function()
- return x, y
- end
- window.reposition = function(newX, newY, newWidth, newHeight)
- x, y = type(newX) == "number" and math.floor(newX) or x, type(newY) == "number" and math.floor(newY) or y
- if type(newWidth) == "number" then
- newWidth = math.floor(newWidth)
- if newWidth > cWidth then
- local line1, line2, line3 = string.rep("\128", newWidth - cWidth), string.rep(colourChar[tCol], newWidth - cWidth), string.rep(colourChar[bCol], newWidth - cWidth)
- for y = 1, cHeight do
- local bRow, fRow = backBuffer[y], frontBuffer[y]
- for x = cWidth + 1, newWidth do bRow[x] = {bCol, bCol, bCol, bCol, bCol, bCol} end
- frontBuffer[y] = {fRow[1] .. line3, fRow[2] .. line2, fRow[3] .. line3}
- end
- elseif newWidth < cWidth then
- for y = 1, cHeight do
- local wRow, bRow, fRow = workBuffer[y], backBuffer[y], frontBuffer[y]
- for x = newWidth + 1, cWidth do bRow[x] = nil end
- frontBuffer[y] = {fRow[1]:sub(1, newWidth), fRow[2]:sub(1, newWidth), fRow[3]:sub(1, newWidth)}
- while wRow[wRow.max] and wRow[wRow.max] > newWidth do
- wRow[wRow.max] = nil
- wRow.max = table.maxn(wRow)
- end
- end
- end
- width, cWidth = newWidth * 2, newWidth
- end
- if type(newHeight) == "number" then
- newHeight = math.floor(newHeight)
- if newHeight > cHeight then
- local line1, line2, line3 = string.rep("\128", cWidth), string.rep(colourChar[tCol], cWidth), string.rep(colourChar[bCol], cWidth)
- for y = cHeight + 1, newHeight do workBuffer[y], backBuffer[y], frontBuffer[y] = {["max"] = 0}, newLine(cWidth, bCol), {line1, line2, line3} end
- elseif newHeight < cHeight then
- for y = newHeight + 1, cHeight do workBuffer[y], backBuffer[y], frontBuffer[y] = nil, nil, nil end
- end
- height, cHeight = newHeight * 3, newHeight
- end
- window.redraw()
- end
- window.clear()
- return window
- end
- function draw(image, x, y, terminal)
- local t, tC, bC = image[1], image[2], image[3]
- x, y, terminal = x or 1, y or 1, terminal or term.current()
- for i = 1, image.height do
- local tI = t[i]
- if type(tI) == "string" then
- terminal.setCursorPos(x, y + i - 1)
- terminal.blit(tI, tC[i], bC[i])
- elseif type(tI) == "table" then
- terminal.setCursorPos(x + tI[1], y + i - 1)
- terminal.blit(tI[2], tC[i], bC[i])
- end
- end
- end
- function save(image, filename)
- local output = fs.open(filename, "wb")
- if not output then error("Can't open "..filename.." for output.") end
- local writeByte = output.write
- local function writeInt(num)
- writeByte(bit.band(num, 255))
- writeByte(bit.brshift(num, 8))
- end
- writeByte(66) -- B
- writeByte(76) -- L
- writeByte(84) -- T
- local animated = image[1].delay ~= nil
- writeByte(animated and 1 or 0)
- if animated then
- writeInt(#image)
- else
- local tempImage = {image[1], image[2], image[3]}
- image[1], image[2], image[3] = tempImage, nil, nil
- end
- local width, height = image.width, image.height
- writeInt(width)
- writeInt(height)
- for k = 1, #image do
- local thisImage = image[k]
- if type(thisImage[1]) == "number" then
- writeByte(3)
- writeInt(thisImage[1])
- else
- for i = 1, height do
- if thisImage[1][i] then
- local rowType, len, thisRow = type(thisImage[1][i])
- if rowType == "string" then
- writeByte(1)
- len = #thisImage[1][i]
- writeInt(len)
- thisRow = {thisImage[1][i]:byte(1, len)}
- elseif rowType == "table" then
- writeByte(2)
- len = #thisImage[1][i][2]
- writeInt(len)
- writeInt(thisImage[1][i][1])
- thisRow = {thisImage[1][i][2]:byte(1, len)}
- else
- error("Malformed row record #"..i.." in frame #"..k.." when attempting to save \""..filename.."\", type is "..rowType..".")
- end
- for x = 1, len do writeByte(thisRow[x]) end
- local txt, bg = thisImage[2][i], thisImage[3][i]
- for x = 1, len do writeByte(colourNum[txt:sub(x, x)] + colourNum[bg:sub(x, x)] * 16) end
- else writeByte(0) end
- end
- end
- if animated then writeInt(thisImage.delay * 20) end
- snooze()
- end
- if image.pal then
- writeByte(#image.pal)
- for i = 0, #image.pal do for j = 1, 3 do writeByte(image.pal[i][j]) end end
- end
- if not animated then
- image[2], image[3] = image[1][2], image[1][3]
- image[1] = image[1][1]
- end
- output.close()
- end
- function load(filename)
- local input = fs.open(filename, "rb")
- if not input then error("Can't open "..filename.." for input.") end
- local read = input.read
- local function readInt()
- local result = read()
- return result + bit.blshift(read(), 8)
- end
- if string.char(read(), read(), read()) ~= "BLT" then
- -- Assume legacy format.
- input.close()
- input = fs.open(filename, "rb")
- read = input.read
- function readInt()
- local result = input.read()
- return result + bit.blshift(input.read(), 8)
- end
- local image = {}
- image.width, image.height = readInt(), readInt()
- for i = 1, 3 do
- local thisSet = {}
- for y = 1, image.height do
- local thisRow = {}
- for x = 1, image.width do thisRow[x] = string.char(input.read()) end
- thisSet[y] = table.concat(thisRow)
- end
- image[i] = thisSet
- end
- input.close()
- return image
- end
- local image, animated, frames = {}, read() == 1
- if animated then frames = readInt() else frames = 1 end
- local width, height = readInt(), readInt()
- image.width, image.height = width, height
- for k = 1, frames do
- local thisImage = {["width"] = width, ["height"] = 0}
- local chr, txt, bg = {}, {}, {}
- for i = 1, height do
- local lineType = read()
- if lineType == 3 then
- chr, txt, bg = readInt()
- break
- elseif lineType > 0 then
- local l1, l2, len, bump = {}, {}, readInt()
- if lineType == 2 then bump = readInt() end
- for x = 1, len do l1[x] = read() end
- chr[i] = string.char(unpack(l1))
- if lineType == 2 then chr[i] = {bump, chr[i]} end
- for x = 1, len do
- local thisVal = read()
- l1[x], l2[x] = colourNum[bit.band(thisVal, 15)], colourNum[bit.brshift(thisVal, 4)]
- end
- txt[i], bg[i], thisImage.height = table.concat(l1), table.concat(l2), i
- end
- end
- if animated then thisImage["delay"] = readInt() / 20 end
- thisImage[1], thisImage[2], thisImage[3] = chr, txt, bg
- image[k] = thisImage
- snooze()
- end
- local palLength = read()
- if palLength and palLength > 0 then
- image.pal = {}
- for i = 0, palLength do image.pal[i] = {read(), read(), read()} end
- end
- if not animated then
- image[2], image[3] = image[1][2], image[1][3]
- image[1] = image[1][1]
- end
- input.close()
- return image
- end
- if term.setPaletteColour then
- function applyPalette(image, terminal)
- terminal = terminal or term
- local col, pal = 1, image.pal
- for i = 0, #pal do
- local thisCol = pal[i]
- terminal.setPaletteColour(col, thisCol[1] / 255, thisCol[2] / 255, thisCol[3] / 255)
- col = col * 2
- end
- end
- end
Add Comment
Please, Sign In to add comment