Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local ASSET_DIR = "assets"
- local FLOOR_BMP_FILE = ASSET_DIR .. "/floor.bmp"
- local SPRITE_BMP_FILE = ASSET_DIR .. "/sprite.bmp"
- local PCM1_FILE = ASSET_DIR .. "/music1.raw"
- local PCM2_FILE = ASSET_DIR .. "/music2.raw"
- local FONT_BMP_FILE = ASSET_DIR .. "/font.bmp"
- local FLOOR_BMP_URL = "https://files.catbox.moe/ipwa9x.bmp"
- local SPRITE_BMP_URL = "https://files.catbox.moe/pemxbh.bmp"
- local PCM1_URL = "https://files.catbox.moe/t1cnnf.raw"
- local PCM2_URL = "https://files.catbox.moe/k3wxw5.raw"
- local FONT_BMP_URL = "https://files.catbox.moe/zkdsi9.bmp"
- local PCM_SAMPLE_RATE = 32000
- local PCM_UNSIGNED = true
- local VOLUME_BOOST = 2.5
- local SPRITE_SCALE = 0.5
- local CHUNK_HEIGHT = 4
- local SCANIMATE_PHASE = 0.15
- local SINE_AMPLITUDE = 16
- local FAST_PERIOD = 36
- local SWITCH_PERIOD = 5
- local FPS = 30
- local FONT_CELL_W, FONT_CELL_H = 20, 20
- local FONT_OUT_W, FONT_OUT_H = 10, 10
- local FONT_COLS = 15
- local FONT_TRANSPARENT = { r=34, g=177, b=76 }
- local math_floor, math_min, math_max, math_sin, math_cos, math_pi, math_abs = math.floor, math.min, math.max, math.sin, math.cos, math.pi, math.abs
- local table_unpack = table.unpack
- local os_pullEvent, os_queueEvent = os.pullEvent, os.queueEvent
- local term_setPaletteColor, term_getPaletteColor = term.setPaletteColor, term.getPaletteColor
- local term_setGraphicsMode, term_getGraphicsMode = term.setGraphicsMode, term.getGraphicsMode
- local term_setBackgroundColor, term_getBackgroundColor = term.setBackgroundColor, term.getBackgroundColor
- local term_setTextColor, term_getTextColor = term.setTextColor, term.getTextColor
- local term_clear, term_setCursorPos = term.clear, term.setCursorPos
- term_clear()
- term_setCursorPos(1, 1)
- print([[
- &$$$&.X&$$$$$ :&$$& .&$$&
- &&&&&x&&&&&&&&&+&&$&x &$&&&
- $$ $$ && $xX& x$+$
- xX xx&&$x X: xxx.:x
- +x ++ X$ xx $& x+
- &&&&&X&&&&&&&&&;&&&; &+ &&&&
- &&&&&:X&&&&&& .&&&+ &&&&
- I486 Demo by nonogamer9!
- Loading demo's assets if missing...
- ]])
- os.sleep(1)
- if not fs.exists(ASSET_DIR) then fs.makeDir(ASSET_DIR) end
- local function downloadContent(url, filename, contentType)
- print("Downloading " .. contentType .. "...")
- local response = http.get(url, nil, true)
- if not response then error("Failed to download " .. contentType .. ": " .. url) end
- local file = fs.open(filename, "wb")
- local content = response.readAll()
- for i = 1, #content, 8192 do
- file.write(content:sub(i, i + 8191))
- os_queueEvent("yield")
- os_pullEvent("yield")
- end
- file.close()
- response.close()
- print("Downloaded " .. contentType .. ".")
- end
- local function readBMP(filename)
- local file = fs.open(filename, "rb")
- if not file then return nil, "Failed to open file" end
- local header = file.read(54)
- local width = string.byte(header, 19) + string.byte(header, 20) * 256
- local height = string.byte(header, 23) + string.byte(header, 24) * 256
- local rowSize = math_floor((width * 3 + 3) / 4) * 4
- local padding = rowSize - width * 3
- local pixels = {}
- for y = height, 1, -1 do
- local row = {}
- for x = 1, width do
- local b = file.read(1)
- local g = file.read(1)
- local r = file.read(1)
- row[x] = {r = string.byte(r), g = string.byte(g), b = string.byte(b)}
- end
- if padding > 0 then file.read(padding) end
- pixels[y] = row
- if y % 10 == 0 then os_queueEvent("yield") os_pullEvent("yield") end
- end
- file.close()
- return {width = width, height = height, pixels = pixels}
- end
- local function scaleSprite(sprite, scale)
- local srcW, srcH = sprite.width, sprite.height
- local dstW, dstH = math_max(1, math_floor(srcW * scale + 0.5)), math_max(1, math_floor(srcH * scale + 0.5))
- local srcPixels, dst = sprite.pixels, {}
- for y = 1, dstH do
- local row, srcY = {}, math_min(srcH, math_max(1, math_floor((y-1)/scale+1)))
- local srcRow = srcPixels[srcY]
- for x = 1, dstW do
- local srcX = math_min(srcW, math_max(1, math_floor((x-1)/scale+1)))
- row[x] = srcRow[srcX]
- end
- dst[y] = row
- end
- return {width = dstW, height = dstH, pixels = dst}
- end
- local function mapColor256(r, g, b)
- local cubeSize = 6
- local rIndex = math_floor(r / (255 / (cubeSize - 1)))
- local gIndex = math_floor(g / (255 / (cubeSize - 1)))
- local bIndex = math_floor(b / (255 / (cubeSize - 1)))
- return 16 + rIndex * cubeSize * cubeSize + gIndex * cubeSize + bIndex
- end
- local function set256Palette()
- local cubeSize = 6
- for i = 16, 231 do
- local index = i - 16
- local rIndex = math_floor(index / (cubeSize * cubeSize))
- local gIndex = math_floor((index % (cubeSize * cubeSize)) / cubeSize)
- local bIndex = index % cubeSize
- local r = math_floor(rIndex * 255 / (cubeSize - 1))
- local g = math_floor(gIndex * 255 / (cubeSize - 1))
- local b = math_floor(bIndex * 255 / (cubeSize - 1))
- term_setPaletteColor(i, r / 255, g / 255, b / 255)
- end
- for i = 232, 255 do
- local gray = math_floor((i - 232) * 255 / 23)
- term_setPaletteColor(i, gray / 255, gray / 255, gray / 255)
- end
- end
- local function savePalette()
- local t = {}
- for i = 0, 255 do t[i] = {term_getPaletteColor(i)} end
- return t
- end
- local function restorePalette(t)
- for i = 0, 255 do term_setPaletteColor(i, table_unpack(t[i])) end
- end
- local function makeBuffer(width, height)
- local buf = {}
- for y = 0, height-1 do
- local row = {}
- for x = 0, width-1 do row[x] = 0 end
- buf[y] = row
- end
- return buf
- end
- local function swapBuffers(a, b) return b, a end
- local function blitBuffer(buf, output)
- for y = 0, #buf do
- local row = buf[y]
- for x = 0, #(row) do
- output.setPixel(x, y, row[x])
- end
- end
- end
- local function playPCMOnce(filename, inputSampleRate, isUnsigned, volume)
- local speaker = peripheral.find("speaker")
- if not speaker then return end
- local file = fs.open(filename, "rb")
- if not file then return end
- local samples, sum, count = {}, 0, 0
- while true do
- local byte = file.read()
- if not byte then break end
- if isUnsigned then byte = byte - 128 end
- samples[#samples + 1] = byte
- sum = sum + byte
- count = count + 1
- end
- file.close()
- if #samples == 0 then return end
- local avg = sum / count
- for i = 1, #samples do samples[i] = samples[i] - avg end
- local outputSampleRate = 48000
- if inputSampleRate ~= outputSampleRate then
- local resampled, ratio = {}, inputSampleRate / outputSampleRate
- for i = 1, math_floor(#samples / ratio) do
- local src = (i - 1) * ratio + 1
- local idx = math_floor(src)
- local frac = src - idx
- local s1, s2 = samples[idx] or 0, samples[idx + 1] or samples[idx] or 0
- resampled[i] = s1 + (s2 - s1) * frac
- end
- samples = resampled
- end
- local chunkSize, totalSamples = 128 * 1024, #samples
- local playedSamples = 0
- while playedSamples < totalSamples do
- local chunk = {}
- for i = 1, chunkSize do
- local idx = playedSamples + i
- if idx > totalSamples then break end
- local sample = math_floor(samples[idx] * volume * VOLUME_BOOST + 0.5)
- if sample > 127 then sample = 127 end
- if sample < -128 then sample = -128 end
- chunk[i] = sample
- end
- if #chunk == 0 then break end
- while not speaker.playAudio(chunk) do
- os_pullEvent("speaker_audio_empty")
- end
- playedSamples = playedSamples + #chunk
- end
- end
- local function playPCMsAlternating()
- while true do
- playPCMOnce(PCM1_FILE, PCM_SAMPLE_RATE, PCM_UNSIGNED, 0.2)
- playPCMOnce(PCM2_FILE, PCM_SAMPLE_RATE, PCM_UNSIGNED, 0.2)
- end
- end
- local function mode7_floor()
- return function(frame, sw, sh, iw, ih)
- local cam_x, cam_y = iw/2, ih/2
- local cam_angle = frame/60
- local cam_height, focal_length = 120, 170
- local pxw, pxh = sw*6, sh*9
- local horizon = math_floor(pxh * 0.45)
- return {
- a=1, b=0, c=0, d=1, x0=0, y0=0, u0=0, v0=0,
- scanline=function(sy,a,b,c,d,frame) return a,b,c,d end,
- floor=function(sx,sy)
- if sy < horizon then return nil,nil end
- local dx, dy = sx - pxw/2, sy - horizon
- local angle = cam_angle
- local distance = cam_height * focal_length / (dy+1)
- local world_x = cam_x + math.cos(angle)*distance + math.sin(angle)*dx*distance/focal_length
- local world_y = cam_y + math.sin(angle)*distance - math.cos(angle)*dx*distance/focal_length
- local tx = ((math_floor(world_x)-1) % iw) + 1
- local ty = ((math_floor(world_y)-1) % ih) + 1
- return tx, ty
- end,
- horizon = horizon
- }
- end
- end
- local SCANIMATE_EFFECTS = {
- function(chunk, y, x, frame)
- local phase = frame / FAST_PERIOD * 2 * math_pi + chunk * SCANIMATE_PHASE
- return 0, math_floor(math_sin(phase) * SINE_AMPLITUDE + 0.5)
- end,
- function(chunk, y, x, frame)
- local phase = frame / FAST_PERIOD * 2 * math_pi + chunk * SCANIMATE_PHASE
- return math_floor(math_sin(phase) * SINE_AMPLITUDE + 0.5), 0
- end,
- function(chunk, y, x, frame)
- local phase = frame / FAST_PERIOD * 2 * math_pi + chunk * SCANIMATE_PHASE
- return math_floor(math_cos(phase) * SINE_AMPLITUDE * 0.7 + 0.5), math_floor(math_sin(phase) * SINE_AMPLITUDE + 0.5)
- end,
- function(chunk, y, x, frame)
- local phase = frame / FAST_PERIOD * 2 * math_pi + chunk * SCANIMATE_PHASE
- return math_floor(math_sin(phase) * SINE_AMPLITUDE * 0.7 + 0.5),
- math_floor(math_sin(phase*2) * SINE_AMPLITUDE * 0.5 + 0.5)
- end,
- function(chunk, y, x, frame)
- local phase = frame / (FAST_PERIOD*0.5) * 2 * math_pi
- local sign = (chunk % 2 == 0) and 1 or -1
- return 0, math_floor(math_sin(phase + chunk * SCANIMATE_PHASE) * SINE_AMPLITUDE * sign + 0.5)
- end,
- function(chunk, y, x, frame)
- local phase = frame / FAST_PERIOD * 2 * math_pi + chunk * SCANIMATE_PHASE
- local angle = phase
- local r = SINE_AMPLITUDE * 0.7
- return math_floor(math_cos(angle) * r + 0.5), math_floor(math_sin(angle) * r + 0.5)
- end,
- function(chunk, y, x, frame)
- local phase = frame / FAST_PERIOD * 2 * math_pi + chunk * SCANIMATE_PHASE
- local scale = 1 + 0.5 * math_sin(phase)
- local ycenter = (chunk * CHUNK_HEIGHT + CHUNK_HEIGHT/2)
- local yoff = math_floor((y - ycenter) * (scale - 1) + 0.5)
- return 0, yoff
- end,
- function(chunk, y, x, frame)
- local phase = frame / FAST_PERIOD * 2 * math_pi + chunk * SCANIMATE_PHASE + y * 0.08
- return math_floor(math_sin(phase) * SINE_AMPLITUDE * (0.3 + 0.7 * (y / (chunk * CHUNK_HEIGHT + 1))) + 0.5), 0
- end,
- function(chunk, y, x, frame)
- local phase = frame / FAST_PERIOD * 2 * math_pi
- local bulge = math_sin(phase + chunk * SCANIMATE_PHASE)
- local ycenter = (chunk * CHUNK_HEIGHT + CHUNK_HEIGHT/2)
- local dist = math_abs(y - ycenter)
- local amt = (1 - dist / (CHUNK_HEIGHT/2 + 1)) * SINE_AMPLITUDE
- return 0, math_floor(bulge * amt + 0.5)
- end,
- function(chunk, y, x, frame)
- local phase = frame / FAST_PERIOD * 2 * math_pi
- local t = (chunk * CHUNK_HEIGHT + y) / (CHUNK_HEIGHT * 8)
- local squish = 1 + 0.7 * math_sin(phase)
- local yoff = math_floor((t - 0.5) * squish * SINE_AMPLITUDE)
- return 0, yoff
- end,
- function(chunk, y, x, frame)
- local phase = frame / FAST_PERIOD * 2 * math_pi
- local ripple = math_sin(phase + y * 0.15)
- return math_floor(ripple * SINE_AMPLITUDE * 0.5 + 0.5), 0
- end,
- function(chunk, y, x, frame)
- local phase = frame / FAST_PERIOD * 2 * math_pi
- local twist = math_sin(phase + y * 0.20)
- return math_floor(twist * SINE_AMPLITUDE * 0.7 + 0.5), math_floor(twist * SINE_AMPLITUDE * 0.2 + 0.5)
- end,
- function(chunk, y, x, frame)
- local phase = frame / FAST_PERIOD * 2 * math_pi
- local t = (chunk * CHUNK_HEIGHT + y) / (CHUNK_HEIGHT * 8)
- local bend = math_sin(phase)
- return math_floor(bend * SINE_AMPLITUDE * t + 0.5), 0
- end,
- function(chunk, y, x, frame)
- local phase1 = frame / FAST_PERIOD * 2 * math_pi + chunk * SCANIMATE_PHASE
- local phase2 = frame / (FAST_PERIOD*0.5) * 2 * math_pi + chunk * SCANIMATE_PHASE * 2
- return math_floor(math_sin(phase1) * SINE_AMPLITUDE * 0.6 + math_sin(phase2) * SINE_AMPLITUDE * 0.4 + 0.5), 0
- end,
- function(chunk, y, x, frame)
- local phase = frame / FAST_PERIOD * 2 * math_pi + chunk * SCANIMATE_PHASE
- local scale = 1 + 0.3 * math_sin(phase)
- local ycenter = (chunk * CHUNK_HEIGHT + CHUNK_HEIGHT/2)
- local xcenter = (x - 1) - 32
- local yoff = math_floor((y - ycenter) * (scale - 1) + 0.5)
- local xoff = math_floor(xcenter * 0.15 * math_sin(phase) + 0.5)
- return xoff, yoff
- end,
- function(chunk, y, x, frame)
- local phase = frame / (FAST_PERIOD*0.8) * 2 * math_pi
- local scale = 1 + 0.6 * math_sin(phase)
- local ycenter = (chunk * CHUNK_HEIGHT + CHUNK_HEIGHT/2)
- local yoff = math_floor((y - ycenter) * (scale - 1) + 0.5)
- return 0, yoff
- end,
- function(chunk, y, x, frame)
- local t = ((frame + chunk*8) % FAST_PERIOD) / FAST_PERIOD
- return 0, math_floor((t - 0.5) * 2 * SINE_AMPLITUDE)
- end,
- function(chunk, y, x, frame)
- local seed = (frame + chunk*13) % 97
- local jitter = ((seed * 37) % 7) - 3
- return 0, jitter
- end,
- function(chunk, y, x, frame)
- local phase = frame / FAST_PERIOD * 2 * math_pi + (chunk + x) * SCANIMATE_PHASE
- local sign = ((chunk + x) % 2 == 0) and 1 or -1
- return 0, math_floor(math_sin(phase) * SINE_AMPLITUDE * sign * 0.5)
- end,
- }
- local function blitSpriteScanimate(buf, sprite, px, py, frame, effectFn)
- local s_pixels, s_width, s_height = sprite.pixels, sprite.width, sprite.height
- local chunkHeight, numChunks = CHUNK_HEIGHT, math.ceil(s_height / CHUNK_HEIGHT)
- for chunk = 0, numChunks-1 do
- local yStart = chunk * chunkHeight + 1
- local yEnd = math_min(s_height, yStart + chunkHeight - 1)
- for y = yStart, yEnd do
- local srcRow = s_pixels[y]
- for x = 1, s_width do
- local pixel = srcRow[x]
- if not (pixel.r == 255 and pixel.g == 0 and pixel.b == 255) then
- local dx, dy = effectFn(chunk, y, x, frame)
- local sx = px + x - 1 + dx
- local sy = py + y - 1 + dy
- local row = buf[sy]
- if row and row[sx] ~= nil then
- row[sx] = mapColor256(pixel.r, pixel.g, pixel.b)
- end
- end
- end
- end
- end
- end
- local function drawTextHalfResWave(buf, font, str, px, py, frame, amplitude, period, phaseStep, scale)
- scale = scale or 1
- for i = 1, #str do
- local c = string.byte(str, i) - 32
- if c >= 0 and c < 120 then
- local sx = (c % FONT_COLS) * FONT_CELL_W
- local sy = math_floor(c / FONT_COLS) * FONT_CELL_H
- local phase = (frame / period) * 2 * math_pi + (i - 1) * phaseStep
- local yoff = math_floor(math_sin(phase) * amplitude + 0.5)
- local baseX = px + (i - 1) * FONT_OUT_W * scale
- local baseY = py + yoff
- for dy = 0, FONT_OUT_H - 1 do
- for dx = 0, FONT_OUT_W - 1 do
- local p = font.pixels[sy + dy * 2 + 1][sx + dx * 2 + 1]
- if not (p.r == FONT_TRANSPARENT.r and p.g == FONT_TRANSPARENT.g and p.b == FONT_TRANSPARENT.b) then
- if scale == 1 then
- local by = baseY + dy
- local bx = baseX + dx
- if buf[by] and buf[by][bx] then
- buf[by][bx] = mapColor256(p.r, p.g, p.b)
- end
- else
- for syy = 0, scale - 1 do
- for sxx = 0, scale - 1 do
- local by = baseY + dy * scale + syy
- local bx = baseX + dx * scale + sxx
- if buf[by] and buf[by][bx] then
- buf[by][bx] = mapColor256(p.r, p.g, p.b)
- end
- end
- end
- end
- end
- end
- end
- end
- end
- end
- local function displayMode7WithSpriteForever(floorImage, spriteImage, fontImage, output, paramFunc)
- local screenWidth, screenHeight = output.getSize()
- local pxw, pxh = screenWidth * 6, screenHeight * 9
- local imageWidth, imageHeight = floorImage.width, floorImage.height
- local savePal, getGM, setGM = savePalette, term_getGraphicsMode, term_setGraphicsMode
- local origPalette, origGM = savePal(), getGM()
- local origBG, origFG = term_getBackgroundColor(), term_getTextColor()
- setGM(2)
- set256Palette()
- local bufA, bufB = makeBuffer(pxw, pxh), makeBuffer(pxw, pxh)
- local front, back, frame = bufA, bufB, 0
- local stop = false
- local function waitForEnter()
- while true do
- local ev, key = os_pullEvent("key")
- if key == keys.enter then stop = true break end
- end
- end
- local numEffects = #SCANIMATE_EFFECTS
- parallel.waitForAny(
- function()
- while not stop do
- frame = frame + 1
- local params = paramFunc(frame, screenWidth, screenHeight, imageWidth, imageHeight)
- local a, b, c, d = params.a, params.b, params.c, params.d
- local x0, y0, u0, v0 = params.x0, params.y0, params.u0, params.v0
- local floorFunc, horizon = params.floor, params.horizon or 0
- for sy = 0, pxh-1 do
- local row = back[sy]
- for sx = 0, pxw-1 do
- local color = 0
- if floorFunc then
- local tx, ty = floorFunc(sx, sy)
- if tx and ty and tx >= 1 and tx <= imageWidth and ty >= 1 and ty <= imageHeight then
- local pixel = floorImage.pixels[ty][tx]
- color = mapColor256(pixel.r, pixel.g, pixel.b)
- end
- else
- local dx, dy = sx - x0, sy - y0
- local u = a * dx + b * dy + u0
- local v = c * dx + d * dy + v0
- local srcX, srcY = math_floor(u + 0.5), math_floor(v + 0.5)
- if srcX >= 1 and srcX <= imageWidth and srcY >= 1 and srcY <= imageHeight then
- local pixel = floorImage.pixels[srcY][srcX]
- color = mapColor256(pixel.r, pixel.g, pixel.b)
- end
- end
- row[sx] = color
- end
- if sy % 10 == 0 then os_queueEvent("yield") os_pullEvent("yield") end
- end
- local effectIdx = math_floor((frame / (SWITCH_PERIOD * FPS)) % numEffects) + 1
- local effectFn = SCANIMATE_EFFECTS[effectIdx]
- local spriteX = math_floor(pxw / 2 - spriteImage.width / 2)
- local baseY = math_floor(pxh * 0.45 - spriteImage.height / 2)
- blitSpriteScanimate(back, spriteImage, spriteX, baseY, frame, effectFn)
- local text1 = "this is possible on CC"
- local text2 = "thanks to nonogamer9"
- local framePeriod = 18
- local amplitude1 = 4
- local amplitude2 = 2
- local phaseStep1 = 0.3
- local phaseStep2 = 0.5
- local scale1 = 1
- local scale2 = 1
- local text1width = #text1 * FONT_OUT_W * scale1
- local text2width = #text2 * FONT_OUT_W * scale2
- local text1x = math_floor((pxw - text1width) / 2)
- local text2x = math_floor((pxw - text2width) / 2)
- local text1y = math_floor(pxh * 0.75)
- local text2y = text1y + FONT_OUT_H * scale1 + 2
- drawTextHalfResWave(back, fontImage, text1, text1x, text1y, frame, amplitude1, framePeriod, phaseStep1, scale1)
- drawTextHalfResWave(back, fontImage, text2, text2x, text2y, frame, amplitude2, framePeriod, phaseStep2, scale2)
- front, back = swapBuffers(front, back)
- blitBuffer(front, output)
- os_queueEvent("yield")
- os_pullEvent("yield")
- end
- end,
- waitForEnter
- )
- setGM(origGM)
- restorePalette(origPalette)
- term_setBackgroundColor(origBG)
- term_setTextColor(origFG)
- term_clear()
- term_setCursorPos(1, 1)
- end
- if not fs.exists(FLOOR_BMP_FILE) then downloadContent(FLOOR_BMP_URL, FLOOR_BMP_FILE, "floor image") end
- if not fs.exists(SPRITE_BMP_FILE) then downloadContent(SPRITE_BMP_URL, SPRITE_BMP_FILE, "sprite image") end
- if not fs.exists(PCM1_FILE) then downloadContent(PCM1_URL, PCM1_FILE, "music1 audio") end
- if not fs.exists(PCM2_FILE) then downloadContent(PCM2_URL, PCM2_FILE, "music2 audio") end
- if not fs.exists(FONT_BMP_FILE) then downloadContent(FONT_BMP_URL, FONT_BMP_FILE, "font image") end
- local floorImage, err1 = readBMP(FLOOR_BMP_FILE)
- if not floorImage then print("Error reading floor BMP:", err1) return end
- local spriteImage, err2 = readBMP(SPRITE_BMP_FILE)
- if not spriteImage then print("Error reading sprite BMP:", err2) return end
- local fontImage, fontErr = readBMP(FONT_BMP_FILE)
- if not fontImage then print("Error reading font BMP:", fontErr) return end
- local scaledSprite = scaleSprite(spriteImage, SPRITE_SCALE)
- parallel.waitForAny(
- playPCMsAlternating,
- function() displayMode7WithSpriteForever(floorImage, scaledSprite, fontImage, term, mode7_floor()) end
- )
Add Comment
Please, Sign In to add comment