Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local sfxBuffer = {}
- local function playSFX(filename, inputSampleRate, isUnsigned, volume)
- 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 = {}
- local 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 = samples[idx] or 0
- local s2 = samples[idx + 1] or s1
- resampled[i] = s1 + (s2 - s1) * frac
- end
- samples = resampled
- end
- sfxSamples = samples
- sfxPos = 1
- end
- local function playPCM(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 = {}
- local 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 = samples[idx] or 0
- local s2 = samples[idx + 1] or s1
- resampled[i] = s1 + (s2 - s1) * frac
- end
- samples = resampled
- end
- local chunkSize = 128 * 1024
- local totalSamples = #samples
- local playedSamples = 0
- local absSample = 1
- 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 + 0.5)
- if sfxSamples and sfxPos and sfxPos <= #sfxSamples then
- sample = sample + math.floor(sfxSamples[sfxPos])
- if sample > 127 then sample = 127 end
- if sample < -128 then sample = -128 end
- sfxPos = sfxPos + 1
- if sfxPos > #sfxSamples then
- sfxSamples = nil
- sfxPos = nil
- end
- end
- chunk[i] = sample
- absSample = absSample + 1
- 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 GROUND_BMP_URL = "https://files.catbox.moe/w9tlsn.bmp"
- local GROUND_BMP_FILE = "ground.bmp"
- local SPRITE_BMP_URL = "https://files.catbox.moe/fzau50.bmp"
- local SPRITE_BMP_FILE = "sprite.bmp"
- local FONT_BMP_URL = "https://files.catbox.moe/zkdsi9.bmp"
- local FONT_BMP_FILE = "font.bmp"
- local PCM_URL = "https://files.catbox.moe/kak23b.raw"
- local PCM_FILE = "music.raw"
- local SFX_URL = "https://files.catbox.moe/56o55a.raw"
- local SFX_FILE = "jump.raw"
- local LOADING_BMP_URL = "https://files.catbox.moe/l8tnog.bmp"
- local LOADING_BMP_FILE = "loading.bmp"
- local TRANSPARENT_COLOR = { r = 34, g = 177, b = 76 }
- local FONT_CELL_W, FONT_CELL_H = 20, 20
- local function download(url, file)
- if fs.exists(file) then return end
- local h = http.get(url, nil, true)
- assert(h, "Download failed: " .. url)
- local f = fs.open(file, "wb")
- f.write(h.readAll())
- f.close()
- h.close()
- 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 pixels = {}
- for y = height, 1, -1 do
- pixels[y] = {}
- for x = 1, width do
- local b = file.read(1)
- local g = file.read(1)
- local r = file.read(1)
- pixels[y][x] = {r = string.byte(r), g = string.byte(g), b = string.byte(b)}
- end
- 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 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
- buf[y] = {}
- for x = 0, width-1 do buf[y][x] = 0 end
- end
- return buf
- end
- local function swapBuffers(a, b) return b, a end
- local function blitBuffer(buf, output)
- for y = 0, #buf do
- for x = 0, #(buf[y]) do
- output.setPixel(x, y, buf[y][x])
- end
- end
- end
- local function downscaleBMP(img)
- local w, h = math.floor(img.width / 4), math.floor(img.height / 4)
- local out = {width=w, height=h, pixels={}}
- for y=1,h do
- out.pixels[y] = {}
- for x=1,w do
- out.pixels[y][x] = img.pixels[(y-1)*4+1][(x-1)*4+1]
- end
- end
- return out
- end
- local function drawTextHalfRes(font, x, y, str)
- for i=1,#str do
- local c = string.byte(str,i)-32
- if c>=0 and c<120 then
- local sx = (c%15)*20
- local sy = math.floor(c/15)*20
- for dy=0,9 do
- for dx=0,9 do
- local p = font.pixels[sy+dy*2+1][sx+dx*2+1]
- if not (p.r==TRANSPARENT_COLOR.r and p.g==TRANSPARENT_COLOR.g and p.b==TRANSPARENT_COLOR.b) then
- term.setPixel(x+(i-1)*10+dx, y+dy, mapColor256(p.r,p.g,p.b))
- end
- end
- end
- end
- end
- end
- local function drawSprite(img, x, y)
- for iy=1,img.height do
- for ix=1,img.width do
- local p = img.pixels[iy][ix]
- if not (p.r==TRANSPARENT_COLOR.r and p.g==TRANSPARENT_COLOR.g and p.b==TRANSPARENT_COLOR.b) then
- term.setPixel(x+ix-1, y+iy-1, mapColor256(p.r, p.g, p.b))
- end
- end
- end
- end
- local function drawBMPScaled(bmp, pxw, pxh)
- local iw, ih = bmp.width, bmp.height
- for y = 0, pxh-1 do
- local srcY = math.floor(y * ih / pxh) + 1
- for x = 0, pxw-1 do
- local srcX = math.floor(x * iw / pxw) + 1
- local p = bmp.pixels[srcY][srcX]
- term.setPixel(x+1, y+1, mapColor256(p.r, p.g, p.b))
- end
- end
- end
- local function drawLoadingText(font, pxw, pxh, text)
- local x = pxw - (#text * 10) - 10
- local y = pxh - 15
- drawTextHalfRes(font, x, y, text)
- end
- local function showLoadingScreen()
- term.setGraphicsMode(2)
- set256Palette()
- local pxw, pxh = term.getSize(2)
- download(FONT_BMP_URL, FONT_BMP_FILE)
- local font = readBMP(FONT_BMP_FILE)
- download(LOADING_BMP_URL, LOADING_BMP_FILE)
- local loading = readBMP(LOADING_BMP_FILE)
- drawBMPScaled(loading, pxw, pxh)
- drawLoadingText(font, pxw, pxh, "Loading font...")
- download(GROUND_BMP_URL, GROUND_BMP_FILE)
- drawBMPScaled(loading, pxw, pxh)
- drawLoadingText(font, pxw, pxh, "Loading ground...")
- download(SPRITE_BMP_URL, SPRITE_BMP_FILE)
- drawBMPScaled(loading, pxw, pxh)
- drawLoadingText(font, pxw, pxh, "Loading sprite...")
- download(PCM_URL, PCM_FILE)
- drawBMPScaled(loading, pxw, pxh)
- drawLoadingText(font, pxw, pxh, "Loading music...")
- download(SFX_URL, SFX_FILE)
- drawBMPScaled(loading, pxw, pxh)
- drawLoadingText(font, pxw, pxh, "Loading sfx...")
- local ground = readBMP(GROUND_BMP_FILE)
- drawBMPScaled(loading, pxw, pxh)
- drawLoadingText(font, pxw, pxh, "Preparing ground...")
- local sprite_big = readBMP(SPRITE_BMP_FILE)
- local sprite = downscaleBMP(sprite_big)
- drawBMPScaled(loading, pxw, pxh)
- drawLoadingText(font, pxw, pxh, "Preparing sprite...")
- drawBMPScaled(loading, pxw, pxh)
- drawLoadingText(font, pxw, pxh, "Press any key to continue")
- os.pullEvent("key")
- return font, ground, sprite
- end
- local player = { x = 150, y = 150, z = 0, angle = 0, vy = 0 }
- local CAMERA_HEIGHT = 120
- local MOVE_SPEED = 4
- local JUMP_VEL = 5
- local GRAVITY = 0.35
- local function mode7_floor_player()
- return function(frame, sw, sh, iw, ih)
- local cam_x = player.x
- local cam_y = player.y
- local cam_angle = player.angle
- local cam_height = CAMERA_HEIGHT + player.z
- local horizon = math.floor((sh*9) * 0.45)
- local focal_length = 170
- 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 pxw = sw*6
- local dx = sx - pxw/2
- local dy = 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,
- cam_x = cam_x, cam_y = cam_y, cam_angle = cam_angle, cam_height = cam_height
- }
- end
- end
- local function displayBMPMode7Affine(image, output, paramFunc, animate, duration, afterFrame)
- local screenWidth, screenHeight = output.getSize()
- local pxw, pxh = screenWidth * 6, screenHeight * 9
- local imageWidth, imageHeight = image.width, image.height
- local originalPalette = savePalette()
- local originalGraphicsMode = term.getGraphicsMode()
- local originalBackgroundColor = term.getBackgroundColor()
- local originalTextColor = term.getTextColor()
- term.setGraphicsMode(2)
- set256Palette()
- local bufA = makeBuffer(pxw, pxh)
- local bufB = makeBuffer(pxw, pxh)
- local front, back = bufA, bufB
- local numBands = 4
- local bands = {}
- local bandHeight = math.floor(pxh / numBands)
- for i = 1, numBands do
- local y0 = (i-1)*bandHeight
- local y1 = (i==numBands) and (pxh-1) or (i*bandHeight-1)
- bands[i] = {y0=y0, y1=y1}
- end
- local frame = 0
- local t0 = os.epoch("utc")
- local tEnd = t0 + (duration or 8)*1000
- while os.epoch("utc") < tEnd 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 = params.x0, params.y0
- local u0, v0 = params.u0, params.v0
- local floorFunc = params.floor
- local horizon = params.horizon or 0
- for bandIdx = 1, #bands do
- local band = bands[bandIdx]
- for sy = band.y0, band.y1 do
- for sx = 0, pxw-1 do
- local color = 0
- if floorFunc then
- local tx, ty
- if params.multilayer then
- tx, ty = floorFunc(sx, sy, 1)
- else
- tx, ty = floorFunc(sx, sy)
- end
- if tx and ty and tx >= 1 and tx <= imageWidth and ty >= 1 and ty <= imageHeight then
- local pixel = image.pixels[ty][tx]
- color = mapColor256(pixel.r, pixel.g, pixel.b)
- else
- color = 0
- end
- else
- local sa, sb, sc, sd = a, b, c, d
- if params.scanline then
- sa, sb, sc, sd = params.scanline(sy, a, b, c, d, frame)
- end
- local dx = sx - x0
- local dy = sy - y0
- local u = sa * dx + sb * dy + u0
- local v = sc * dx + sd * dy + v0
- local srcX = math.floor(u + 0.5)
- local srcY = math.floor(v + 0.5)
- if srcX >= 1 and srcX <= imageWidth and srcY >= 1 and srcY <= imageHeight then
- local pixel = image.pixels[srcY][srcX]
- color = mapColor256(pixel.r, pixel.g, pixel.b)
- else
- color = 0
- end
- end
- back[sy][sx] = color
- end
- if params.multilayer then
- local tx, ty = params.floor(band.y0, sy, 2)
- if tx and ty and tx >= 1 and tx <= imageWidth and ty >= 1 and ty <= imageHeight then
- for sx = 0, pxw-1 do
- local pixel = image.pixels[ty][tx]
- back[sy][sx] = math.max(back[sy][sx], mapColor256(pixel.r, pixel.g, pixel.b))
- end
- end
- end
- if sy % 10 == 0 then os.queueEvent("yield") os.pullEvent("yield") end
- end
- end
- if params.starfield then params.starfield(back) end
- front, back = swapBuffers(front, back)
- blitBuffer(front, output)
- if afterFrame then afterFrame(params, frame, pxw, pxh) end
- os.queueEvent("yield")
- os.pullEvent("yield")
- end
- term.setGraphicsMode(originalGraphicsMode)
- restorePalette(originalPalette)
- term.setBackgroundColor(originalBackgroundColor)
- term.setTextColor(originalTextColor)
- term.clear()
- term.setCursorPos(1, 1)
- end
- local keymap = {
- [keys.w]="forward", [keys.s]="back", [keys.a]="left", [keys.d]="right",
- [keys.left]="turnl", [keys.right]="turnr", [keys.space]="jump"
- }
- local font, ground, sprite = showLoadingScreen()
- term.clear()
- local screenWidth, screenHeight = term.getSize()
- local pxw, pxh = screenWidth*6, screenHeight*9
- local running = true
- local lastFrameTime = os.epoch("utc")
- parallel.waitForAny(
- function()
- while running do
- local event = {os.pullEventRaw()}
- if event[1] == "key" then
- local action = keymap[event[2]]
- if action == "forward" then
- player.x = player.x + math.cos(player.angle) * MOVE_SPEED
- player.y = player.y + math.sin(player.angle) * MOVE_SPEED
- elseif action == "back" then
- player.x = player.x - math.cos(player.angle) * MOVE_SPEED
- player.y = player.y - math.sin(player.angle) * MOVE_SPEED
- elseif action == "left" then
- player.x = player.x + math.cos(player.angle-math.pi/2) * MOVE_SPEED
- player.y = player.y + math.sin(player.angle-math.pi/2) * MOVE_SPEED
- elseif action == "right" then
- player.x = player.x + math.cos(player.angle+math.pi/2) * MOVE_SPEED
- player.y = player.y + math.sin(player.angle+math.pi/2) * MOVE_SPEED
- elseif action == "turnl" then
- player.angle = player.angle - 0.1
- elseif action == "turnr" then
- player.angle = player.angle + 0.1
- elseif action == "jump" and player.z == 0 then
- player.vy = JUMP_VEL
- coroutine.resume(coroutine.create(function()
- local speaker = peripheral.find("speaker")
- if not speaker then return end
- local file = fs.open(SFX_FILE, "rb")
- if not file then return end
- local samples = {}
- while true do
- local byte = file.read()
- if not byte then break end
- byte = byte - 128
- samples[#samples+1] = byte
- end
- file.close()
- local chunkSize = 512
- local total = #samples
- local played = 0
- while played < total do
- local chunk = {}
- for i=1,chunkSize do
- local idx = played + i
- if idx > total then break end
- chunk[i] = samples[idx]
- end
- if #chunk == 0 then break end
- while not speaker.playAudio(chunk) do
- os.pullEvent("speaker_audio_empty")
- end
- played = played + #chunk
- end
- end))
- end
- elseif event[1] == "terminate" then
- running = false
- return
- end
- end
- end,
- function()
- displayBMPMode7Affine(
- ground, term, mode7_floor_player(), true, 1e9,
- function(params, frame, pxw, pxh)
- local now = os.epoch("utc")
- local dt = now - lastFrameTime
- lastFrameTime = now
- local fps = dt > 0 and math.floor(1000 / dt) or 0
- drawTextHalfRes(font, 10, 10, "FPS: " .. fps)
- local creditText = "SMFX Clone by nonogamer9"
- local textWidth = #creditText * 10
- local centerX = math.floor((pxw - textWidth) / 2)
- drawTextHalfRes(font, centerX, pxh - 20, creditText)
- local sprite_y = math.floor(pxh/2 - sprite.height/2 - player.z)
- drawSprite(sprite, math.floor(pxw/2-sprite.width/2), sprite_y)
- if player.z > 0 or player.vy > 0 then
- player.z = player.z + player.vy
- player.vy = player.vy - GRAVITY
- if player.z < 0 then player.z = 0; player.vy = 0 end
- end
- end
- )
- end,
- function()
- while true do
- playPCM(PCM_FILE, 32000, true, 1)
- end
- end
- )
Add Comment
Please, Sign In to add comment