nonogamer9

CC-NICCC: ST-NICCC 2000 Port For ComputerCraft Tweaked (Version 1.0)

Jul 21st, 2024 (edited)
414
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 11.22 KB | Software | 0 0
  1. local AUDIO_URL = "https://files.catbox.moe/4as46o.raw"
  2. local AUDIO_FILE = "audio.raw"
  3. local SCENE_PATH = "scene.bin"
  4. local SCENE_URL = "https://files.catbox.moe/r5mp9l.bin"
  5. local WIDTH, HEIGHT = term.getSize()
  6. local TARGET_FPS = 30
  7. local PLAYBACK_RATE = 16000
  8. local SPEAKER_RATE = 48000
  9. local TOTAL_FRAMES = 1800
  10.  
  11. local stopAudio = false
  12. local frameCountGlobal = 0
  13.  
  14. local speaker = peripheral.find("speaker")
  15. local useAudio = false
  16.  
  17. if not speaker then
  18.     print("No speaker attached! Rendering will proceed without audio.")
  19. else
  20.     useAudio = true
  21. end
  22.  
  23. local function downloadFile(url, filename)
  24.     if not fs.exists(filename) then
  25.         print("Downloading " .. filename .. "...")
  26.         local response = http.get(url, nil, true)
  27.         if not response then
  28.             error("Failed to connect to " .. url)
  29.         end
  30.         local file = fs.open(filename, "wb")
  31.         file.write(response.readAll())
  32.         file.close()
  33.         response.close()
  34.         print(filename .. " downloaded.")
  35.     end
  36. end
  37.  
  38. if useAudio then
  39.     downloadFile(AUDIO_URL, AUDIO_FILE)
  40. end
  41. downloadFile(SCENE_URL, SCENE_PATH)
  42.  
  43. local function prepareAudio()
  44.     if not useAudio then return nil end
  45.     print("Processing audio...")
  46.    
  47.     local file = fs.open(AUDIO_FILE, "rb")
  48.     local rawData = file.readAll()
  49.     file.close()
  50.    
  51.     local samples = {}
  52.     for i = 1, #rawData do
  53.         samples[i] = string.byte(rawData, i) - 128
  54.     end
  55.    
  56.     if PLAYBACK_RATE ~= SPEAKER_RATE then
  57.         local resampled = {}
  58.         local ratio = PLAYBACK_RATE / SPEAKER_RATE
  59.         local numSamples = math.floor(#samples / ratio)
  60.         for i = 1, numSamples do
  61.             local src = (i - 1) * ratio + 1
  62.             local idx = math.floor(src)
  63.             local frac = src - idx
  64.             local s1 = samples[idx] or 0
  65.             local s2 = samples[idx + 1] or s1
  66.             resampled[i] = math.floor(s1 + (s2 - s1) * frac)
  67.         end
  68.         samples = resampled
  69.     end
  70.    
  71.     return samples
  72. end
  73.  
  74. local audioSamples = prepareAudio()
  75.  
  76. if not bit then bit = {} end
  77. local function toBits(num)
  78.     local t = {}
  79.     for i = 0, 31 do
  80.         t[i] = num % 2
  81.         num = math.floor(num / 2)
  82.     end
  83.     return t
  84. end
  85. local function fromBits(t)
  86.     local num = 0
  87.     for i = 0, 31 do num = num + (t[i] or 0) * 2^i end
  88.     return num
  89. end
  90. function bit.band(a, b)
  91.     local ta, tb = toBits(a), toBits(b)
  92.     local t = {}
  93.     for i = 0, 31 do t[i] = ta[i] * tb[i] end
  94.     return fromBits(t)
  95. end
  96. function bit.bor(a, b)
  97.     local ta, tb = toBits(a), toBits(b)
  98.     local t = {}
  99.     for i = 0, 31 do t[i] = (ta[i] + tb[i] > 0) and 1 or 0 end
  100.     return fromBits(t)
  101. end
  102. function bit.lshift(a, n) return math.floor((a * 2^n) % 2^32) end
  103. function bit.rshift(a, n) return math.floor(a / 2^n) % 2^32 end
  104.  
  105. local ccPalette = {
  106.     colors.white, colors.orange, colors.magenta, colors.lightBlue,
  107.     colors.yellow, colors.lime, colors.pink, colors.gray,
  108.     colors.lightGray, colors.cyan, colors.purple, colors.blue,
  109.     colors.brown, colors.green, colors.red, colors.black
  110. }
  111.  
  112. local originalPalette = {}
  113. for i = 1, #ccPalette do
  114.     local col = ccPalette[i]
  115.     local r, g, b = term.getPaletteColor(col)
  116.     originalPalette[i] = {r, g, b}
  117. end
  118.  
  119. local backBuffer = {}
  120. local frontBuffer = {}
  121. for y = 1, HEIGHT do
  122.     backBuffer[y] = {}
  123.     frontBuffer[y] = {}
  124.     for x = 1, WIDTH do
  125.         backBuffer[y][x] = -1
  126.         frontBuffer[y][x] = -1
  127.     end
  128. end
  129.  
  130. local function bufferSet(x, y, colorIdx)
  131.     if x < 1 or x > WIDTH or y < 1 or y > HEIGHT then return end
  132.     backBuffer[y][x] = colorIdx
  133. end
  134.  
  135. local function flushBuffer(fps)
  136.     for y = 1, HEIGHT do
  137.         local lastColor = -2
  138.         for x = 1, WIDTH do
  139.             local backColor = backBuffer[y][x]
  140.             if backColor ~= frontBuffer[y][x] then
  141.                 if backColor ~= lastColor then
  142.                     term.setCursorPos(x, y)
  143.                     term.setBackgroundColor(ccPalette[backColor + 1])
  144.                     lastColor = backColor
  145.                 else
  146.                     term.setCursorPos(x, y)
  147.                 end
  148.                 term.write(" ")
  149.                 frontBuffer[y][x] = backColor
  150.             end
  151.         end
  152.     end
  153.     term.setCursorPos(1, 1)
  154.     term.setBackgroundColor(colors.black)
  155.     term.setTextColor(colors.white)
  156.     term.write(string.format("%.1f FPS | Frame %d/%d", fps, frameCountGlobal, TOTAL_FRAMES))
  157. end
  158.  
  159. local sceneFile = fs.open(SCENE_PATH, "rb")
  160. local data = sceneFile.readAll()
  161. sceneFile.close()
  162. local pos = 1
  163.  
  164. local function safeByte(offset)
  165.     local b = data:byte(offset)
  166.     return b or 0xFD
  167. end
  168.  
  169. local function setCCPalette(ccIndex, r, g, b)
  170.     term.setPaletteColor(ccPalette[ccIndex + 1], b / 255, g / 255, r / 255)
  171. end
  172.  
  173. local function decode(raw)
  174.     raw = bit.band(raw, 0xF)
  175.     local bits = bit.bor(bit.band(bit.lshift(raw, 1), 0xE), bit.band(bit.rshift(raw, 3), 1))
  176.     return bits * 17
  177. end
  178.  
  179. local function fillPolygon(pts, count, colorIdx)
  180.     if count < 3 then return end
  181.     local minY, maxY = HEIGHT + 1, 0
  182.     for i = 1, count do
  183.         local y = pts[i*2]
  184.         if y then
  185.             minY = math.min(minY, y)
  186.             maxY = math.max(maxY, y)
  187.         end
  188.     end
  189.     if minY > HEIGHT or maxY < 1 then return end
  190.     for y = math.max(1, math.ceil(minY)), math.min(HEIGHT, math.floor(maxY)) do
  191.         local nodes = {}
  192.         local nIdx = 1
  193.         local j = count
  194.         for i = 1, count do
  195.             local iy = pts[i*2]; local ix = pts[i*2 - 1]
  196.             local jy = pts[j*2]; local jx = pts[j*2 - 1]
  197.             if ix and iy and jx and jy then
  198.                 if (iy < y and jy >= y) or (jy < y and iy >= y) then
  199.                     if math.abs(jy - iy) > 0.001 then
  200.                         nodes[nIdx] = ix + (y - iy) * (jx - ix) / (jy - iy)
  201.                         nIdx = nIdx + 1
  202.                     end
  203.                 end
  204.             end
  205.             j = i
  206.         end
  207.         if nIdx > 2 then
  208.             table.sort(nodes)
  209.             for i = 1, nIdx - 1, 2 do
  210.                 if nodes[i] and nodes[i + 1] then
  211.                     local x1 = math.max(1, math.floor(nodes[i] + 0.5))
  212.                     local x2 = math.min(WIDTH, math.floor(nodes[i + 1] + 0.5))
  213.                     if x1 <= x2 then
  214.                         for x = x1, x2 do bufferSet(x, y, colorIdx) end
  215.                     end
  216.                 end
  217.             end
  218.         end
  219.     end
  220. end
  221.  
  222. local function renderFrame()
  223.     if pos > #data or frameCountGlobal >= TOTAL_FRAMES then
  224.         return true
  225.     end
  226.     frameCountGlobal = frameCountGlobal + 1
  227.     local flags = safeByte(pos)
  228.     pos = pos + 1
  229.     if bit.band(flags, 1) ~= 0 then
  230.         for y = 1, HEIGHT do
  231.             for x = 1, WIDTH do backBuffer[y][x] = 15 end
  232.         end
  233.     end
  234.     if bit.band(flags, 2) ~= 0 then
  235.         local mask = bit.bor(bit.lshift(safeByte(pos), 8), safeByte(pos + 1))
  236.         pos = pos + 2
  237.         for i = 0, 15 do
  238.             if bit.band(mask, bit.lshift(1, 15 - i)) ~= 0 then
  239.                 local colorWord = bit.bor(bit.lshift(safeByte(pos), 8), safeByte(pos + 1))
  240.                 pos = pos + 2
  241.                 local r = decode(bit.band(colorWord, 0xF))
  242.                 local g = decode(bit.band(bit.rshift(colorWord, 4), 0xF))
  243.                 local b = decode(bit.band(bit.rshift(colorWord, 8), 0xF))
  244.                 setCCPalette(i, r, g, b)
  245.             end
  246.         end
  247.     end
  248.     local indexed = bit.band(flags, 4) ~= 0
  249.     local points = {}
  250.     if indexed then
  251.         local vCount = safeByte(pos)
  252.         pos = pos + 1
  253.         for i = 0, vCount - 1 do
  254.             points[i*2 + 1] = safeByte(pos) / 256 * WIDTH
  255.             points[i*2 + 2] = safeByte(pos + 1) / 200 * HEIGHT
  256.             pos = pos + 2
  257.         end
  258.     end
  259.     while pos <= #data do
  260.         local b = safeByte(pos)
  261.         pos = pos + 1
  262.         if b >= 0xFD then
  263.             if b == 0xFE then pos = math.floor((pos - 2) / 65536) * 65536 + 65536 + 1 end
  264.             break
  265.         end
  266.         local numPts = bit.band(b, 0xF)
  267.         local colIdx = bit.rshift(b, 4)
  268.         if numPts < 3 or colIdx > 15 then break end
  269.         local poly = {}
  270.         local validPoints = 0
  271.         for i = 1, numPts do
  272.             if indexed then
  273.                 local idx = safeByte(pos)
  274.                 pos = pos + 1
  275.                 if idx and idx < (#points/2) then
  276.                     poly[(i*2) - 1] = points[idx*2 + 1]
  277.                     poly[i*2] = points[idx*2 + 2]
  278.                     validPoints = validPoints + 1
  279.                 end
  280.             else
  281.                 poly[(i*2) - 1] = safeByte(pos) / 256 * WIDTH
  282.                 poly[i*2] = safeByte(pos + 1) / 200 * HEIGHT
  283.                 validPoints = validPoints + 1
  284.                 pos = pos + 2
  285.             end
  286.         end
  287.         if validPoints >= 3 then fillPolygon(poly, numPts, colIdx) end
  288.     end
  289.     return false
  290. end
  291.  
  292. term.setBackgroundColor(colors.black)
  293. term.clear()
  294. term.setCursorPos(1,1)
  295. print("CC-NICCC! A ST-NICCC 2000 Port For ComputerCraft Made By nonogamer9!")
  296. print("This is")
  297. print("*NOT*")
  298. print("An OpenComputers Demo")
  299. sleep(1)
  300.  
  301. local function playAudioThread()
  302.     if not useAudio or not speaker or not audioSamples then return end
  303.     local chunkSize = 1024
  304.     local totalSamples = #audioSamples
  305.     local playedSamples = 0
  306.    
  307.     while playedSamples < totalSamples and not stopAudio do
  308.         local chunk = {}
  309.         local chunkSamples = 0
  310.         for i = 1, chunkSize do
  311.             local idx = playedSamples + i
  312.             if idx > totalSamples or stopAudio then break end
  313.             chunk[i] = audioSamples[idx]
  314.             chunkSamples = i
  315.         end
  316.        
  317.         if chunkSamples == 0 then break end
  318.        
  319.         local success = speaker.playAudio(chunk)
  320.         if not success then
  321.             os.pullEvent("speaker_audio_empty")
  322.         end
  323.        
  324.         playedSamples = playedSamples + chunkSamples
  325.        
  326.         if frameCountGlobal >= TOTAL_FRAMES then
  327.             stopAudio = true
  328.             break
  329.         end
  330.     end
  331. end
  332.  
  333. local function renderThread()
  334.     local startTime = os.clock()
  335.     local lastFpsDisplay = 0
  336.     local currentFps = 0
  337.    
  338.     while frameCountGlobal < TOTAL_FRAMES do
  339.         local frameStart = os.clock()
  340.         if renderFrame() then break end
  341.        
  342.         local now = os.clock()
  343.         local elapsed = now - startTime
  344.         if elapsed - lastFpsDisplay > 0.5 then
  345.             currentFps = frameCountGlobal / elapsed
  346.             lastFpsDisplay = elapsed
  347.         end
  348.         flushBuffer(currentFps)
  349.        
  350.         local frameTime = os.clock() - frameStart
  351.         local sleepTime = (1 / TARGET_FPS) - frameTime
  352.         if sleepTime > 0 then sleep(sleepTime) end
  353.     end
  354. end
  355.  
  356. local function mainThread()
  357.     renderThread()
  358. end
  359.  
  360. if useAudio then
  361.     parallel.waitForAny(mainThread, playAudioThread)
  362. else
  363.     mainThread()
  364. end
  365.  
  366. if speaker then
  367.     speaker.stop()
  368. end
  369.  
  370. for i = 1, #ccPalette do
  371.     local col = ccPalette[i]
  372.     local rgb = originalPalette[i]
  373.     term.setPaletteColor(col, rgb[1], rgb[2], rgb[3])
  374. end
  375. term.setBackgroundColor(colors.black)
  376. term.clear()
  377. term.setCursorPos(1, HEIGHT/2)
  378. print("Demo complete! Frame: " .. frameCountGlobal)
Add Comment
Please, Sign In to add comment