Skip to content

Instantly share code, notes, and snippets.

@edubart
Last active January 30, 2024 03:19
Show Gist options
  • Save edubart/a79bf78a249d1fff2b77728c260c7605 to your computer and use it in GitHub Desktop.
Save edubart/a79bf78a249d1fff2b77728c260c7605 to your computer and use it in GitHub Desktop.
Celeste game in Nelua
--[[
This is the famous Celeste game from PICO-8 ported to Nelua
using just SDL all in a single file.
The code is divide between 3 parts;
* SDL2 bindings
* Neco framework, a tiny PICO-8 like framework with a subset of PICO-8 functions.
* Celeste game, code ported from original Celeste from PICO-8 with minor changes.
It render all the pixels using just the CPU,
SDL2 is just used to get inputs and to flush the pixels to the screen.
The spritesheet, map font is contained
in a string with hexadecimal bytes that is processed at compile time.
Celeste is originally made by Matt Thorson + Noel Berry.
]]
-- User lower precision on default integer types.
##[[
primtypes.number = primtypes.float32
primtypes.integer = primtypes.int32
primtypes.uinteger = primtypes.uint32
]]
----------------------------------------------------------------------------------------------------
-- SDL2 bindings
----------------------------------------------------------------------------------------------------
##[[
if ccinfo.is_emscripten then
cflags '-s USE_SDL=2'
end
linklib 'SDL2'
]]
global SDL_Scancode: type <cimport,using> = @enum(cint){
SDL_SCANCODE_C = 6,
SDL_SCANCODE_X = 27,
SDL_SCANCODE_Z = 29,
SDL_SCANCODE_RIGHT = 79,
SDL_SCANCODE_LEFT = 80,
SDL_SCANCODE_DOWN = 81,
SDL_SCANCODE_UP = 82,
SDL_NUM_SCANCODES = 512
}
global SDL_RendererFlags: type <cimport,using> = @enum(cint){
SDL_RENDERER_TARGETTEXTURE = 8
}
global SDL_PixelFormatEnum: type <cimport,using> = @enum(cint){
SDL_PIXELFORMAT_XBGR8888 = 374740996,
}
global SDL_TextureAccess: type <cimport,using> = @enum(cint){
SDL_TEXTUREACCESS_TARGET = 2
}
global SDL_EventType: type <cimport,using> = @enum(cint){
SDL_QUIT = 256,
SDL_KEYDOWN = 768,
}
global SDL_INIT_VIDEO: uint32 <comptime> = 0x00000020
global SDL_WINDOWPOS_UNDEFINED: uint32 <comptime> = 0x1fff0000
global SDL_Window: type <cimport,forwarddecl> = @record{}
global SDL_Renderer: type <cimport,forwarddecl> = @record{}
global SDL_Texture: type <cimport,forwarddecl> = @record{}
global SDL_Rect: type <cimport> = @record{
x: cint,
y: cint,
w: cint,
h: cint
}
global SDL_Keysym: type <cimport> = @record{
scancode: SDL_Scancode,
sym: cint,
mod: uint16,
unused: uint32
}
global SDL_KeyboardEvent: type <cimport> = @record{
type: uint32,
timestamp: uint32,
windowID: uint32,
state: uint8,
Repeat: uint8,
padding2: uint8,
padding3: uint8,
keysym: SDL_Keysym
}
global SDL_Event: type <cimport,cincomplete> = @union{
type: uint32,
key: SDL_KeyboardEvent,
padding: [56]uint8
}
global function SDL_PollEvent(event: *SDL_Event): cint <cimport> end
global function SDL_Init(flags: uint32): cint <cimport> end
global function SDL_GetKeyboardState(numkeys: *cint): *uint8 <cimport> end
global function SDL_CreateWindow(title: cstring, x: cint, y: cint, w: cint, h: cint, flags: uint32): *SDL_Window <cimport> end
global function SDL_CreateRenderer(window: *SDL_Window, index: cint, flags: uint32): *SDL_Renderer <cimport> end
global function SDL_CreateTexture(renderer: *SDL_Renderer, format: uint32, access: cint, w: cint, h: cint): *SDL_Texture <cimport> end
global function SDL_GetPerformanceCounter(): uint64 <cimport> end
global function SDL_GetPerformanceFrequency(): uint64 <cimport> end
global function SDL_UpdateTexture(texture: *SDL_Texture, rect: *SDL_Rect, pixels: pointer, pitch: cint): cint <cimport> end
global function SDL_RenderCopy(renderer: *SDL_Renderer, texture: *SDL_Texture, srcrect: *SDL_Rect, dstrect: *SDL_Rect): cint <cimport> end
global function SDL_Delay(ms: uint32) <cimport> end
global function SDL_RenderPresent(renderer: *SDL_Renderer) <cimport> end
global function SDL_DestroyTexture(texture: *SDL_Texture) <cimport> end
global function SDL_DestroyRenderer(renderer: *SDL_Renderer) <cimport> end
global function SDL_DestroyWindow(window: *SDL_Window) <cimport> end
global function SDL_Quit() <cimport> end
----------------------------------------------------------------------------------------------------
-- Neco framework
----------------------------------------------------------------------------------------------------
require 'allocators.default'
require 'math'
require 'memory'
##[==[
local neco_gfxdata, neco_mapdata, neco_font
local neco_default_font = {
glyph_xadvance=8,
glyph_yadvance=6,
glyph_xspacing=1,
glyph_yspacing=1,
glyph_width=3,
data=[[
00000000070000007070000070700000777000007070000077000000070000000700000007000000707000000000000000000000000000000000000000700000
00000000070000007070000077700000770000000070000077000000700000007000000000700000070000000700000000000000000000000000000007000000
00000000070000000000000070700000077000000700000077000000000000007000000000700000777000007770000000000000777000000000000007000000
00000000000000000000000077700000777000007000000070700000000000007000000000700000070000000700000007000000000000000000000007000000
00000000070000000000000070700000070000007070000077700000000000000700000007000000707000000000000070000000000000000700000070000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
77700000770000007770000077700000707000007770000070000000777000007770000077700000000000000000000000700000000000007000000077700000
70700000070000000070000000700000707000007000000070000000007000007070000070700000070000000700000007000000777000000700000000700000
70700000070000007770000007700000777000007770000077700000007000007770000077700000000000000000000070000000000000000070000007700000
70700000070000007000000000700000007000000070000070700000007000007070000000700000070000000700000007000000777000000700000000000000
77700000777000007770000077700000007000007770000077700000007000007770000000700000000000007000000000700000000000007000000007000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
07000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
70700000777000007700000077700000770000007770000077700000777000007070000077700000777000007070000070000000777000007700000007700000
70700000707000007700000070000000707000007700000077000000700000007070000007000000070000007700000070000000777000007070000070700000
70000000777000007070000070000000707000007000000070000000707000007770000007000000070000007070000070000000707000007070000070700000
07700000707000007770000077700000770000007770000070000000777000007070000077700000770000007070000077700000707000007070000077000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000007700000070000000077000000700000000000000
77700000070000007770000007700000777000007070000070700000707000007070000070700000777000007000000007000000007000007070000000000000
70700000707000007070000070000000070000007070000070700000707000000700000077700000007000007000000007000000007000000000000000000000
77700000770000007700000000700000070000007070000077700000777000007070000000700000700000007000000007000000007000000000000000000000
70000000077000007070000077000000070000000770000007000000777000007070000077700000777000007700000000700000077000000000000077700000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
07000000777000007770000007700000770000007770000077700000077000007070000077700000777000007070000070000000777000007700000007700000
00700000707000007070000070000000707000007000000070000000700000007070000007000000070000007070000070000000777000007070000070700000
00000000777000007700000070000000707000007700000077000000700000007770000007000000070000007700000070000000707000007070000070700000
00000000707000007070000070000000707000007000000070000000707000007070000007000000070000007070000070000000707000007070000070700000
00000000707000007770000007700000777000007770000070000000777000007070000077700000770000007070000077700000707000007070000077000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
77700000070000007770000007700000777000007070000070700000707000007070000070700000777000000770000007000000770000000000000000000000
70700000707000007070000070000000070000007070000070700000707000007070000070700000007000000700000007000000070000000070000007000000
77700000707000007700000077700000070000007070000070700000707000000700000077700000070000007700000007000000077000007770000070700000
70000000770000007070000000700000070000007070000077700000777000007070000000700000700000000700000007000000070000007000000070700000
70000000077000007070000077000000070000000770000007000000777000007070000077700000777000000770000007000000770000000000000077700000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
77777770707070707000007007777700700070000070000000777000077077000077700000777000007770000777770077777770000777000777770000070000
77777770070707007777777077000770007000700077770007770700077777000770770000777000077777007770077070777070000700007700077000777000
77777770707070707077707077000770700070000077700007777700077777007770777007777700777777707700077077777770000700007707077007777700
77777770070707007077707077707770007000700777700007777700007770000770770000777000070707007770077070000070077700007700077000777000
77777770707070700777770007777700700070000000700000777000000700000077700000707000070777000777770077777770077700000777770000070000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000077777000007000007777700077777000000000000000000077777007777777070707070000000000000000000000000000000000000000000000000
00000000770077700077700000777000777077707070000070007000770707700000000070707070000000000000000000000000000000000000000000000000
70707070770007707777777000070000770007700700707007070700777077707777777070707070000000000000000000000000000000000000000000000000
00000000770077700777770000777000770007700000070000700070770707700000000070707070000000000000000000000000000000000000000000000000
00000000077777000700070007777700077777000000000000000000077777007777777070707070000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
]]}
local neco_default_palette = {
[0] = 0x000000,
[1] = 0x1d2b53,
[2] = 0x7e2553,
[3] = 0x008751,
[4] = 0xab5236,
[5] = 0x5f574f,
[6] = 0xc2c3c7,
[7] = 0xfff1e8,
[8] = 0xff004d,
[9] = 0xffa300,
[10] = 0xffec27,
[11] = 0x00e436,
[12] = 0x29adff,
[13] = 0x83769c,
[14] = 0xff77a8,
[15] = 0xffccaa,
}
]==]
## function neco_setup(opts)
local opt_arithmetic = #[concept(function(x) return x.type.is_niltype or x.type.is_arithmetic end)]#
local is_arithmetic = #[concept(function(x) return x.type.is_arithmetic end)]#
-- initialize
##[==[
local function read_chars(data)
if not data or data == '' then return '', 0, 0 end
local width = #data:match('%w+')
local height
data, height = data:gsub('%s', '')
return data, width, math.max(height,1)
end
local function read_u4s(data)
if not data or data == '' then return '', 0, 0 end
local data, width, height = read_chars(data)
local ss = {}
for i=0,width*height-1 do
local x, y = i % width, i // width
local b = tonumber(data:sub(i+1,i+1), 16)
ss[#ss+1] = string.char(b)
end
return table.concat(ss), width, height
end
local function read_u8s(data, to_u16)
if not data or data == '' then return '', 0, 0 end
local data, width, height = read_chars(data)
width = width // 2
local ss = {}
for i=0,width*height-1 do
local b = tonumber(data:sub(2*i+1, 2*i+2), 16)
ss[#ss+1] = string.char(b)
if to_u16 then
ss[#ss+1] = '\0'
end
end
return table.concat(ss), width, height
end
local function build_palette_indexes(palette)
local indexes = {}
for i=0,#palette do
indexes[palette[i]] = i
end
return indexes
end
local function read_indexed_ppm(filename)
local file, open_err = io.open(filename,'rb')
if not file then return nil, string.format('file %s: %s', filename, open_err) end
local filedata,read_err = file:read('*a')
file:close()
if not filedata then return nil, read_err end
if not filedata then return string.format('file %s: %s', filename, read_err) end
local ppm_patt = require'nelua.thirdparty.lpegrex'.compile([[
file <- {| 'P6' skip
{:width: %d+ :} skip
{:height: %d+ :} skip
{:maxval: %d+ :} %s
{:data: .* :} |}
skip <- (%s / '#' (!%nl .)* %nl)+
]])
local ppm = ppm_patt:match(filedata)
if not ppm then return nil, string.format('file %s: unsupported PPM file format', filename) end
ppm.width, ppm.height, ppm.maxval = tonumber(ppm.width), tonumber(ppm.height), tonumber(ppm.maxval)
assert(ppm.maxval == 255)
local pos = 1
local data = ppm.data
local size = ppm.height*ppm.width
local usedcolors = {[0] = true}
local pixels = {}
local colors = {}
for i=1,size do
local color
color, pos = string.unpack('>I3', data, pos)
pixels[i] = color
if not usedcolors[color] then
colors[#colors+1] = color
usedcolors[color] = true
end
end
table.sort(colors)
local index2color = {[0]=0}
local color2index = {[0]=0}
for i,color in ipairs(colors) do
index2color[i] = color
color2index[color] = i
end
local indexdata = {}
for i=1,size do
indexdata[i] = string.char(color2index[pixels[i]])
end
return table.concat(indexdata), ppm.width, ppm.height, index2color
end
local function read_tiled_map(filename)
local tiledmap = dofile(opts.map)
local tileddata = tiledmap.layers[1].data
local mapdata = {}
for i=1,#tileddata do
local id = tileddata[i]
if id ~= 0 then id = id - 1 end
mapdata[#mapdata+1] = string.pack('I2', id)
end
return table.concat(mapdata), tiledmap.width, tiledmap.height
end
local function read_font(fontconf)
local data, width, height
if fontconf.image then
fontconf.image_data, fontconf.image_width, fontconf.image_height = assert(read_indexed_ppm(fontconf.image))
else
fontconf.image_data, fontconf.image_width, fontconf.image_height = read_u4s(fontconf.data)
end
return fontconf
end
-- default configs
neco_scale = opts.scale or 4
neco_fps = opts.fps or 30
neco_width = opts.width or 128
neco_height = opts.height or 128
neco_tilesize = opts.tilesize or 8
neco_title = opts.title or 'Neco'
-- palette
neco_palette = opts.pal or neco_default_palette
neco_palette_size = #neco_palette+1
-- gfx
if not opts.gfx then
neco_gfxdata = string.rep('\0', neco_tilesize*neco_tilesize)
neco_gfxwidth, neco_gfxheight = neco_tilesize, neco_tilesize
elseif opts.gfx:match('%.bmp$') then
neco_gfxdata, neco_gfxwidth, neco_gfxheight, neco_palette = read_bitmap(opts.gfx)
neco_palette_size = #neco_palette+1
elseif opts.gfx:match('%.ppm$') then
neco_gfxdata, neco_gfxwidth, neco_gfxheight, neco_palette = assert(read_indexed_ppm(opts.gfx))
neco_palette_size = #neco_palette+1
else
if neco_palette_size > 16 then
neco_gfxdata, neco_gfxwidth, neco_gfxheight = read_u8s(opts.gfx)
else
neco_gfxdata, neco_gfxwidth, neco_gfxheight = read_u4s(opts.gfx)
end
end
neco_numhsprites = neco_gfxwidth // neco_tilesize
neco_numvsprites = neco_gfxheight // neco_tilesize
neco_numsprites = neco_numhsprites*neco_numvsprites
-- palette indexes
neco_palette_indexes = build_palette_indexes(neco_palette)
-- map
if opts.map then
if opts.map:match('%.lua') then
neco_mapdata, neco_mapwidth, neco_mapheight = read_tiled_map(opts.map)
else
neco_mapdata, neco_mapwidth, neco_mapheight = read_u8s(opts.map, true)
end
else
neco_mapdata, neco_mapwidth, neco_mapheight = '', 0, 0
end
-- gfx flags
local empty_gff = string.rep('00', neco_numsprites)
neco_gffdata, neco_gffwidth, neco_gffheight = read_u8s(opts.gff or empty_gff)
if #neco_gffdata < #empty_gff then
neco_gffdata = neco_gffdata .. empty_gff:sub(1,2*(#empty_gff-#neco_gffdata)+1)
end
-- font
neco_font = read_font(opts.font or neco_default_font)
context.rootscope.symbols.log = symbols.print
]==]
global Color = @record{r: byte, g: byte, b: byte, a: byte}
global neco = @record{}
global neco.scale <comptime> = #[neco_scale]#
global neco.fps <comptime> = #[neco_fps]#
global neco.width <comptime> = #[neco_width]#
global neco.height <comptime> = #[neco_height]#
global neco.tilesize <comptime> = #[neco_tilesize]#
global neco.title <comptime> = #[neco_title]#
global neco.gfxwidth <comptime> = #[neco_gfxwidth]#
global neco.gfxheight <comptime> = #[neco_gfxheight]#
global neco.mapwidth <comptime> = #[neco_mapwidth]#
global neco.mapheight <comptime> = #[neco_mapheight]#
global neco.fontwidth <comptime> = #[neco_mapwidth]#
global neco.fontheight <comptime> = #[neco_mapheight]#
global neco.palette: [#[neco_palette_size]#]Color
global neco.altpalette: [#[neco_palette_size]#]byte
global neco.scnpalette: [#[neco_palette_size]#]byte
global neco.frametime: number
global neco.camera: record{x: integer, y: integer}
global neco.keydowns: [SDL_NUM_SCANCODES]boolean
global neco.renderpixels: [(neco.height)][(neco.width)]Color
global neco.pixels: [(neco.height)][(neco.width)]byte
local GfxPixels = @[(neco.gfxheight)][(neco.gfxwidth)]byte
global neco.gfxpixels: GfxPixels = $(@*GfxPixels)(#[neco_gfxdata]#.data)
local GfxFlags = @[#[neco_numsprites]#]byte
global neco.gfxflags: GfxFlags = $(@*GfxFlags)(#[neco_gffdata]#.data)
local MapData = @[(neco.mapheight)][(neco.mapwidth)]uint16
global neco.map: MapData = $(@*MapData)(#[neco_mapdata]#.data)
local FontPixels = @[#[neco_font.image_height]#][#[neco_font.image_width]#]byte
global neco.fontpixels: FontPixels = $(@*FontPixels)(#[neco_font.image_data]#.data)
## for i=0,#neco_palette do
## local r, g, b = (neco_palette[i] >> 16) & 0xff, (neco_palette[i] >> 8) & 0xff, neco_palette[i] & 0xff
neco.palette[#[i]#] = Color{r=#[r]#, g=#[g]#, b=#[b]#, a=0xff}
neco.altpalette[#[i]#] = #[i]#
neco.scnpalette[#[i]#] = #[i]#
## end
global BTN_LEFT: integer <comptime> = 0
global BTN_RIGHT: integer <comptime> = 1
global BTN_UP: integer <comptime> = 2
global BTN_DOWN: integer <comptime> = 3
global BTN_O: integer <comptime> = 4
global BTN_X: integer <comptime> = 5
global function cos(x: is_arithmetic) <inline,nosideeffect>
return math.cos(x*(math.pi*2))
end
global function sin(x: is_arithmetic) <inline,nosideeffect>
return -math.sin(x*(math.pi*2))
end
global function atan2(y: is_arithmetic, x: is_arithmetic) <inline,nosideeffect>
return (math.pi+math.atan2(x, -y))/(math.pi*2)
end
global function rnd(x: is_arithmetic) <inline,nosideeffect>
return math.random() * x
end
global function sgn(x: is_arithmetic) <inline,nosideeffect>
local r: #[x.type]# <noinit>
if x < 0 then r=-1 else r=1 end
return r
end
global flr: auto = math.floor
global abs: auto = math.abs
global sqrt: auto = math.sqrt
global iflr: auto = math.ifloor
global max: auto = math.max
global min: auto = math.min
global function iclamp(x: is_arithmetic, min: integer, max: integer) <inline,nosideeffect>
local r: integer
if unlikely(x < min) then
r = min
elseif unlikely(x > max) then
r = max
else
r = (@integer)(x)
end
return r
end
global function clamp(x: is_arithmetic, min: is_arithmetic, max: is_arithmetic)
local r: #[x.type]# <noinit>
if unlikely(x < min) then
r = min
elseif unlikely(x > max) then
r = max
else
r = x
end
return r
end
-- Returns the number of seconds elapsed since the cartridge was run.
-- This is not the real-world time, but is calculated by counting the number of times
-- _update or _update60 is called. Multiple calls of time() from the same frame return
-- the same result.
global function time()
return neco.frametime
end
global t: auto = time
-- Clear the screen and reset the clipping rectangle.
-- col defaults to 0 (black).
global function cls(col: opt_arithmetic)
## if col.type.is_niltype then
memory.zero(&neco.pixels, neco.width * neco.height)
## else
local col: byte = iflr(col) % #neco.altpalette
memory.set(&neco.pixels, col, neco.width * neco.height)
## end
end
global function pal(col: opt_arithmetic, newcol: opt_arithmetic, is_screen: opt_arithmetic)
## if not col.type.is_niltype then
local col: byte = iflr(col) % #neco.altpalette
## end
## if not newcol.type.is_niltype then
local newcol: byte = iflr(newcol) % #neco.altpalette
## end
## if not is_screen.type.is_niltype then
if is_screen == 1 then
## if col.type.is_niltype then
for i=0,<#neco.scnpalette do
neco.scnpalette[i] = i
end
## else
neco.scnpalette[col] = newcol
## end
return
end
## end
## if col.type.is_niltype then
for i=0,<#neco.altpalette do
neco.altpalette[i] = i
end
## else
neco.altpalette[col] = newcol
## end
end
-- Draw sprite n (0..255) at position x, y.
-- Width and height are 1,1 by default and specify how many sprites wide to blit.
-- Color 0 drawn as transparent by default (see palt())
-- flip_x=true to flip horizontally
-- flip_y=true to flip vertically
global function spr(n: integer,
x: is_arithmetic, y: is_arithmetic,
w: opt_arithmetic, h: opt_arithmetic,
flip_x: facultative(boolean),
flip_y: facultative(boolean))
if unlikely(n < 0 or n >= neco.gfxwidth*neco.gfxheight) then return end
local x, y = (@integer)(x), (@integer)(y)
## if w.type.is_arithmetic then
local w = (@integer)(w)*neco.tilesize
## else
local w = neco.tilesize
## end
## if h.type.is_arithmetic then
local h = (@integer)(h)*neco.tilesize
## else
local h = neco.tilesize
## end
local hcount: uinteger <comptime> = (neco.gfxwidth // neco.tilesize)
local isx = ((@uinteger)(n) % hcount) * neco.tilesize
local isy = ((@uinteger)(n) // hcount) * neco.tilesize
local idx = x + neco.camera.x
local idy = y + neco.camera.y
-- horizontal flip
local sxf = 1
## if flip_x.type.is_boolean then
if flip_x then
sxf = -1
isx = isx + w - 1
end
## end
-- vertical flip
local syf = 1
## if flip_y.type.is_boolean then
if flip_y then
syf = -1
isy = isy + h - 1
end
## end
-- clip out of bounds width
if idx < 0 then w = w+idx; isx = isx-idx*sxf; idx = 0 end
local idxa = idx+w - neco.width
if idxa > 0 then w = w - idxa end
-- clip out of bounds height
if idy < 0 then h = h+idy; isy = isy-idy*syf; idy = 0 end
local idya = idy+h - neco.height
if idya > 0 then h = h - idya end
-- -- clipped away?
if h < 0 or w < 0 then return end
-- blit
for iy=0,<h do
local sy = isy + iy*syf
local dy = idy + iy
for ix=0,<w do
local sx = isx + ix*sxf
local dx = idx + ix
local col = neco.gfxpixels[sy][sx]
if col ~= 0 then
neco.pixels[dy][dx] = neco.altpalette[col]
end
end
end
end
global function sfx(x: integer)
--TODO
end
global function music(n: integer, fade_len: auto, channel_mask: auto)
--TODO
end
global function sget(x: is_arithmetic, y: is_arithmetic): byte
local x, y = (@integer)(x), (@integer)(y)
return neco.gfxpixels[y][x]
end
global function sset(x: is_arithmetic, y: is_arithmetic, col: byte)
local x, y = (@integer)(x), (@integer)(y)
neco.gfxpixels[y][x] = col
end
global function fget(n: integer, flag: facultative(byte))
## if flag.type.is_niltype then
return neco.gfxflags[n]
## else
return (neco.gfxflags[n] & (1 << flag)) ~= 0
## end
end
global function fset(n: integer, flag: byte)
neco.gfxflags[n] = flag
end
global function pget(x: is_arithmetic, y: is_arithmetic): byte
local x, y = neco.camera.x+(@integer)(x), neco.camera.y+(@integer)(y)
if unlikely(x < 0 or y < 0 or x >= neco.width or y >= neco.height) then return 0 end
return neco.pixels[y][x]
end
global function pset(x: is_arithmetic, y: is_arithmetic, col: byte)
local x, y = neco.camera.x+(@integer)(x), neco.camera.y+(@integer)(y)
if unlikely(x < 0 or y < 0 or x >= neco.width or y >= neco.height) then return end
col = neco.altpalette[col % #neco.altpalette]
neco.pixels[y][x] = col
end
global function camera(x: opt_arithmetic, y: opt_arithmetic)
## if x.type.is_niltype then
neco.camera.x = 0
## else
neco.camera.x = -(@integer)(x)
## end
## if y.type.is_niltype then
neco.camera.y = 0
## else
neco.camera.y = -(@integer)(y)
## end
end
global function line(x0: is_arithmetic, y0: is_arithmetic, x1: is_arithmetic, y1: is_arithmetic, col: byte)
local x0, y0 = (@integer)(neco.camera.x+x0), (@integer)(neco.camera.x+y0)
local x1, y1 = (@integer)(neco.camera.x+x1), (@integer)(neco.camera.x+y1)
col = neco.altpalette[col % #neco.altpalette]
local function line_low(x0: integer, y0: integer, x1: integer, y1: integer, col: byte)
local dx, dy = x1-x0, y1-y0
local yi = 1
if dy < 0 then yi = -1 dy = -dy end
local D = 2*dy - dx
local y = y0
for x=x0,x1 do
if likely(x >= 0 and y >= 0 and x < neco.width and y < neco.height) then
neco.pixels[y][x] = col
end
if D > 0 then
y = y + yi
D = D - 2*dx
end
D = D + 2*dy
end
end
local function line_high(x0: integer, y0: integer, x1: integer, y1: integer, col: byte)
local dx, dy = x1-x0, y1-y0
local xi = 1
if dx < 0 then xi = -1 dx = -dx end
local D = 2*dx - dy
local x = x0
for y=y0,y1 do
if likely(x >= 0 and y >= 0 and x < neco.width and y < neco.height) then
neco.pixels[y][x] = col
end
if D > 0 then
x = x + xi
D = D - 2*dy
end
D = D + 2*dx
end
end
if math.abs(y1 - y0) < math.abs(x1 - x0) then
if x0 > x1 then
x0, y0, x1, y1 = x1, y1, x0, y0
end
line_low(x0, y0, x1, y1, col)
else
if y0 > y1 then
x0, y0, x1, y1 = x1, y1, x0, y0
end
line_high(x0, y0, x1, y1, col)
end
end
global function rectfill(x0: is_arithmetic, y0: is_arithmetic, x1: is_arithmetic, y1: is_arithmetic, col: byte)
local x0, y0 = iclamp(neco.camera.x+x0, 0, neco.width-1), iclamp(neco.camera.y+y0, 0, neco.height-1)
local x1, y1 = iclamp(neco.camera.x+x1, 0, neco.width-1), iclamp(neco.camera.y+y1, 0, neco.height-1)
col = neco.altpalette[col % #neco.altpalette]
for y=y0,y1 do
for x=x0,x1 do
neco.pixels[y][x] = col
end
end
end
global function circfill(x: number, y: number, r: number, col: byte)
if unlikely(r < 0) then return end
if r < 1 then
pset(x, y, col)
return
end
col = neco.altpalette[col % #neco.altpalette]
x, y = neco.camera.x+x, neco.camera.y+y
local sy, sx = -r, -r
local ey, ex = r, r
if y < r then sy = -y end
if x < r then sx = -x end
if y+r >= neco.height then ey = neco.height - y end
if x+r >= neco.width then ex = neco.width - x end
sy, sx, ey, ex = sy+0.5, sx+0.5, ey-0.5, ex-0.5
local r2 = r*r
for iy=sy,ey do
local iy2 = iy*iy
for ix=sx,ex do
local ix2 = ix*ix
if ix2+iy2 <= r2 then
local dx, dy = x + ix, y +iy
neco.pixels[(@integer)(dy)][(@integer)(dx)] = col
end
end
end
end
global function map(cell_x: opt_arithmetic, cell_y: opt_arithmetic,
sx: opt_arithmetic, sy: opt_arithmetic,
cell_w: opt_arithmetic, cell_h: opt_arithmetic,
layers: opt_arithmetic)
## if cell_x.type.is_niltype then
local cell_x <comptime> = 0
## else
local cell_x = (@integer)(cell_x)
## end
## if cell_y.type.is_niltype then
local cell_y <comptime> = 0
## else
local cell_y = (@integer)(cell_y)
## end
## if cell_w.type.is_niltype then
local cell_w <comptime> = neco.mapwidth
## else
local cell_w = (@integer)(cell_w)
## end
## if cell_h.type.is_niltype then
local cell_h <comptime> = neco.mapheight
## else
local cell_h = (@integer)(cell_h)
## end
## if sx.type.is_niltype then
local sx <comptime> = 0
## else
local sx = (@integer)(sx)
## end
## if sy.type.is_niltype then
local sy <comptime> = 0
## else
local sy = (@integer)(sy)
## end
for iy=0,<cell_h do
local cy = cell_y + iy
if unlikely(cy < 0 or cy >= neco.mapheight) then continue end
for ix=0,<cell_w do
local cx = cell_x + ix
if unlikely(cx < 0 or cx >= neco.mapwidth) then continue end
local sid = neco.map[cy][cx]
if unlikely(sid == 0 or sid >= #neco.gfxflags) then
continue
end
## if not layers.type.is_niltype then
if neco.gfxflags[sid] & layers ~= layers then
continue
end
## end
local x, y = sx+ix*neco.tilesize, sy+iy*neco.tilesize
spr(sid, x, y)
end
end
end
global function glyph(n: byte, x: is_arithmetic, y: is_arithmetic, col: byte, size: integer)
local x, y = (@integer)(x), (@integer)(y)
if unlikely(n < 32 or n >= 128) then return end
n = n - 32
local hcount <comptime> = #[neco_font.image_width // neco_font.glyph_xadvance]#
local isx, isy = (n % hcount) * #[neco_font.glyph_xadvance]#, (n // hcount) * #[neco_font.glyph_yadvance]#
local idx, idy = x + neco.camera.x, y + neco.camera.y
local w = #[neco_font.glyph_width]#
local h = #[neco_font.glyph_yadvance]#
local dcol = neco.altpalette[col % #neco.altpalette]
if size == 1 then
-- clip out of bounds width
if idx < 0 then w, isx, idx = w+idx, isx-idx, 0 end
local idxa = idx+w - neco.width
if idxa > 0 then w = w - idxa end
-- clip out of bounds height
if idy < 0 then h, isy, idy = h+idy, isy-idy, 0 end
local idya = idy+h - neco.height
if idya > 0 then h = h - idya end
-- -- clipped away?
if h < 0 or w < 0 then return end
-- blit
for iy=0,<h do
local sy = isy + iy
local dy = idy + iy
for ix=0,<w do
local sx = isx + ix
local dx = idx + ix
if neco.fontpixels[sy][sx] ~= 0 then
neco.pixels[dy][dx] = dcol
end
end
end
else
for iy=0,<h do
local sy = isy + iy
local dy = idy + iy*size
for ix=0,<w do
local sx = isx + ix
local dx = idx + ix*size
if neco.fontpixels[sy][sx] ~= 0 then
for py=dy,<dy+size do
if unlikely(py < 0 or py >= neco.height) then continue end
for px=dx,<dx+size do
if unlikely(px < 0 or px >= neco.width) then continue end
neco.pixels[py][px] = dcol
end
end
end
end
end
end
end
global function print(msg: string, x: is_arithmetic, y: is_arithmetic, col: byte, size: opt_arithmetic)
## if size.type.is_niltype then
local size <comptime> = 1
## end
for i=0,<msg.size do
local n = msg.data[i]
glyph(n, x, y, col, size)
x = x + #[neco_font.glyph_width + neco_font.glyph_xspacing]#*size
end
end
global function mget(x: is_arithmetic, y: is_arithmetic): uint16
local x, y = (@integer)(x), (@integer)(y)
if unlikely(x < 0 or y < 0 or x >= neco.mapwidth or y >= neco.mapheight) then return 0 end
return neco.map[y][x]
end
global function mset(x: is_arithmetic, y: is_arithmetic, v: uint16)
local x, y = (@integer)(x), (@integer)(y)
if unlikely(x < 0 or y < 0 or x >= neco.mapwidth or y >= neco.mapheight) then return end
neco.map[y][x] = v
end
global function btn(key: integer): boolean
local pressed_keys: *[0]uint8 = SDL_GetKeyboardState(nilptr)
switch key do
case 0 then
return pressed_keys[SDL_SCANCODE_LEFT] == 1
case 1 then
return pressed_keys[SDL_SCANCODE_RIGHT] == 1
case 2 then
return pressed_keys[SDL_SCANCODE_UP] == 1
case 3 then
return pressed_keys[SDL_SCANCODE_DOWN] == 1
case 4 then
return pressed_keys[SDL_SCANCODE_Z] == 1 or pressed_keys[SDL_SCANCODE_C] == 1
case 5 then
return pressed_keys[SDL_SCANCODE_X] == 1
end
return false
end
global function btnp(key: integer): boolean
switch key do
case 0 then
return neco.keydowns[SDL_SCANCODE_LEFT]
case 1 then
return neco.keydowns[SDL_SCANCODE_RIGHT]
case 2 then
return neco.keydowns[SDL_SCANCODE_UP]
case 3 then
return neco.keydowns[SDL_SCANCODE_DOWN]
case 4 then
local z: boolean = neco.keydowns[SDL_SCANCODE_Z]
local c: boolean = neco.keydowns[SDL_SCANCODE_C]
return z or c
case 5 then
return neco.keydowns[SDL_SCANCODE_X]
end
return false
end
## if ccinfo.is_emscripten and not pragmas.nogc then
gc:stop() -- conservative GCs cannot run automatically with emscripten
## end
## end
## function neco_run()
-- setup window
if SDL_Init(SDL_INIT_VIDEO) ~= 0 then
error 'Could not initialize SDL'
end
local window: *SDL_Window = SDL_CreateWindow(neco.title,
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
neco.width*neco.scale, neco.height*neco.scale,
0)
if not window then
error 'Could not create SDL Window'
end
local renderer: *SDL_Renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_TARGETTEXTURE)
if not renderer then
error 'Could not create SDL Renderer'
end
local texture: *SDL_Texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_XBGR8888, SDL_TEXTUREACCESS_TARGET, neco.width, neco.height)
if not texture then
error 'Could not create SDL Texture'
end
-- init game
## if symbols._init then
_init()
## end
local first_counter: uint64 = SDL_GetPerformanceCounter()
global function clock(): number
return (SDL_GetPerformanceCounter() - first_counter) / SDL_GetPerformanceFrequency()
end
local next_flip: number = 0.0
global function flip()
for y=0,<neco.height do
for x=0,<neco.width do
neco.renderpixels[y][x] = neco.palette[neco.scnpalette[neco.pixels[y][x]]]
end
end
SDL_UpdateTexture(texture, nilptr, &neco.renderpixels, 4*neco.width)
SDL_RenderCopy(renderer, texture, nilptr, nilptr)
local now: number = clock()
if next_flip > now then
local ms = math.ifloor((next_flip - now) * 1000 + 0.5)
SDL_Delay(ms)
next_flip = next_flip + (1.0/neco.fps)
else
next_flip = now + (1.0/neco.fps)
end
SDL_RenderPresent(renderer)
end
local quit: boolean = false
local function poll()
neco.keydowns = {}
local event: SDL_Event
while SDL_PollEvent(&event) == 1 do
switch event.type do
case SDL_KEYDOWN then
neco.keydowns[event.key.keysym.scancode] = true
case SDL_QUIT then
quit = true
end
end
end
local fps: number = 0.0
local last_fps: number = 0.0
local function frame()
poll()
## if symbols._update then
_update()
## end
_draw()
flip()
neco.frametime = neco.frametime + (1/neco.fps)
## if ccinfo.is_emscripten and not pragmas.nogc then
gc:collect() -- safe to collect garbage here
## end
fps = fps + 1
local now: number = clock()
if now >= last_fps + 1.0 then
fps = math.floor(((fps * 100.0) / (now - last_fps)) + 0.5) / 100.0
log("FPS", fps)
fps = 0
last_fps = now
end
end
## if ccinfo.is_emscripten then
local function emscripten_set_main_loop(func: function(), fps: cint, infloop: cint) <cimport,cinclude'<emscripten.h>',nodecl> end
emscripten_set_main_loop(frame, neco.fps, 1)
## else
repeat
frame()
until quit
## end
-- cleanup
## if symbols._terminate then
_terminate()
## end
SDL_DestroyTexture(texture)
SDL_DestroyRenderer(renderer)
SDL_DestroyWindow(window)
SDL_Quit()
## end
----------------------------------------------------------------------------------------------------
-- Celeste game
----------------------------------------------------------------------------------------------------
##[==[
neco_setup({
width=128, height=128,
tilesize=8,
scale=4,
fps=30,
gfx=[[
000000000000000000000000088888800000000000000000000000000000000000aaaaa0000aaa000000a0000007707770077700000060000000600000060000
000000000888888008888880888888880888888008888800000000000888888000a000a0000a0a000000a0000777777677777770000060000000600000060000
000000008888888888888888888ffff888888888888888800888888088f1ff1800a909a0000a0a000000a0007766666667767777000600000000600000060000
00000000888ffff8888ffff888f1ff18888ffff88ffff8808888888888fffff8009aaa900009a9000000a0007677766676666677000600000000600000060000
0000000088f1ff1888f1ff1808fffff088f1ff1881ff1f80888ffff888fffff80000a0000000a0000000a0000000000000000000000600000006000000006000
0000000008fffff008fffff00033330008fffff00fffff8088fffff8083333800099a0000009a0000000a0000000000000000000000600000006000000006000
00000000003333000033330007000070073333000033337008f1ff10003333000009a0000000a0000000a0000000000000000000000060000006000000006000
000000000070070000700070000000000000070000007000077333700070070000aaa0000009a0000000a0000000000000000000000060000006000000006000
555555550000000000000000000000000000000000000000008888004999999449999994499909940300b0b0666566650300b0b0000000000000000070000000
55555555000000000000000000000000000000000000000008888880911111199111411991140919003b330067656765003b3300007700000770070007000007
550000550000000000000000000000000aaaaaa00000000008788880911111199111911949400419028888206770677002888820007770700777000000000000
55000055007000700499994000000000a998888a1111111108888880911111199494041900000044089888800700070078988887077777700770000000000000
55000055007000700050050000000000a988888a1000000108888880911111199114094994000000088889800700070078888987077777700000700000000000
55000055067706770005500000000000aaaaaaaa1111111108888880911111199111911991400499088988800000000008898880077777700000077000000000
55555555567656760050050000000000a980088a1444444100888800911111199114111991404119028888200000000002888820070777000007077007000070
55555555566656660005500004999940a988888a1444444100000000499999944999999444004994002882000000000000288200000000007000000000000000
5777777557777777777777777777777577cccccccccccccccccccc77577777755555555555555555555555555500000007777770000000000000000000000000
77777777777777777777777777777777777cccccccccccccccccc777777777775555555555555550055555556670000077777777000777770000000000000000
777c77777777ccccc777777ccccc7777777cccccccccccccccccc777777777775555555555555500005555556777700077777777007766700000000000000000
77cccc77777cccccccc77cccccccc7777777cccccccccccccccc7777777cc7775555555555555000000555556660000077773377076777000000000000000000
77cccc7777cccccccccccccccccccc777777cccccccccccccccc777777cccc775555555555550000000055555500000077773377077660000777770000000000
777cc77777cc77ccccccccccccc7cc77777cccccccccccccccccc77777cccc775555555555500000000005556670000073773337077770000777767007700000
7777777777cc77cccccccccccccccc77777cccccccccccccccccc77777c7cc77555555555500000000000055677770007333bb37000000000000007700777770
5777777577cccccccccccccccccccc7777cccccccccccccccccccc7777cccc77555555555000000000000005666000000333bb30000000000000000000077777
77cccc7777cccccccccccccccccccc77577777777777777777777775777ccc775555555550000000000000050000066603333330000000000000000000000000
777ccc7777cccccccccccccccccccc77777777777777777777777777777cc7775055555555000000000000550007777603b333300000000000ee0ee000000000
777ccc7777cc7cccccccccccc77ccc777777ccc7777777777ccc7777777cc77755550055555000000000055500000766033333300000000000eeeee000000030
77ccc77777ccccccccccccccc77ccc77777ccccc7c7777ccccccc77777ccc777555500555555000000005555000000550333b33000000000000e8e00000000b0
77ccc777777cccccccc77cccccccc777777ccccccc7777c7ccccc77777cccc7755555555555550000005555500000666003333000000b00000eeeee000000b30
777cc7777777ccccc777777ccccc77777777ccc7777777777ccc777777cccc775505555555555500005555550007777600044000000b000000ee3ee003000b00
777cc777777777777777777777777777777777777777777777777777777cc7775555555555555550055555550000076600044000030b00300000b00000b0b300
77cccc77577777777777777777777775577777777777777777777775577777755555555555555555555555550000005500999900030330300000b00000303300
5777755777577775077777777777777777777770077777700000000000000000cccccccc00000000000000000000000000000000000000000000000000000000
7777777777777777700007770000777000007777700077770000000000000000c77ccccc00000000000000000000000000000000000000000000000000000000
7777cc7777cc777770cc777cccc777ccccc7770770c777070000000000000000c77cc7cc00000000000000000000000000000000000000000000000000000000
777cccccccccc77770c777cccc777ccccc777c0770777c070000000000000000cccccccc00000000000000000000000000006000000000000000000000000000
77cccccccccccc77707770000777000007770007777700070002eeeeeeee2000cccccccc00000000000000000000000000060600000000000000000000000000
57cc77ccccc7cc7577770000777000007770000777700007002eeeeeeeeee200cc7ccccc00000000000000000000000000d00060000000000000000000000000
577c77ccccccc7757000000000000000000c000770000c0700eeeeeeeeeeee00ccccc7cc0000000000000000000000000d00000c000000000000000000000000
777cccccccccc7777000000000000000000000077000000700e22222e2e22e00cccccccc000000000000000000000000d000000c000000000000000000000000
777cccccccccc7777000000000000000000000077000000700eeeeeeeeeeee000000000000000000000000000000000c0000000c000600000000000000000000
577cccccccccc7777000000c000000000000000770cc000700e22e2222e22e00000000000000000000000000000000d000000000c060d0000000000000000000
57cc7cccc77ccc7570000000000cc0000000000770cc000700eeeeeeeeeeee0000000000000000000000000000000c00000000000d000d000000000000000000
77ccccccc77ccc7770c00000000cc00000000c0770000c0700eee222e22eee0000000000000000000000000000000c0000000000000000000000000000000000
777cccccccccc7777000000000000000000000077000000700eeeeeeeeeeee005555555506666600666666006600c00066666600066666006666660066666600
7777cc7777cc777770000000000000000000000770c0000700eeeeeeeeeeee00555555556666666066666660660c000066666660666666606666666066666660
777777777777777770000000c0000000000000077000000700ee77eee7777e005555555566000660660000006600000066000000660000000066000066000000
57777577775577757000000000000000000000077000c007077777777777777055555555dd000000dddd0000dd000000dddd0000ddddddd000dd0000dddd0000
000000000000000070000000000000000000000770000007007777005000000000000005dd000dd0dd000000dd0000d0dd000000000000d000dd0000dd000000
00aaaaaaaaaaaa00700000000000000000000007700c0007070000705500000000000055ddddddd0dddddd00ddddddd0dddddd00ddddddd000dd0000dddddd00
0a999999999999a0700000000000c00000000007700000077077000755500000000005550ddddd00ddddddd0ddddddd0ddddddd00ddddd0000dd0000ddddddd0
a99aaaaaaaaaa99a7000000cc0000000000000077000cc077077bb07555500000000555500000000000000000000000000000000000000000000000000000000
a9aaaaaaaaaaaa9a7000000cc0000000000c00077000cc07700bbb0755555555555555550000000000000c000000000000000000000000000000c00000000000
a99999999999999a70c00000000000000000000770c00007700bbb075555555555555555000000000000c00000000000000000000000000000000c0000000000
a99999999999999a700000000000000000000007700000070700007055555555555555550000000000cc0000000000000000000000000000000000c000000000
a99999999999999a07777777777777777777777007777770007777005555555555555555000000000c000000000000000000000000000000000000c000000000
aaaaaaaaaaaaaaaa07777777777777777777777007777770004bbb00004b000000400bbb00000000c0000000000000000000000000000000000000c000000000
a49494a11a49494a70007770000077700000777770007777004bbbbb004bb000004bbbbb0000000100000000000000000000000000000000000000c00c000000
a494a4a11a4a494a70c777ccccc777ccccc7770770c7770704200bbb042bbbbb042bbb00000000c0000000000000000000000000000000000000001010c00000
a49444aaaa44494a70777ccccc777ccccc777c0770777c07040000000400bbb004000000000001000000000000000000000000000000000000000001000c0000
a49999aaaa99994a7777000007770000077700077777000704000000040000000400000000000100000000000000000000000000000000000000000000010000
a49444999944494a77700000777000007770000777700c0742000000420000004200000000000100000000000000000000000000000000000000000000001000
a494a444444a494a7000000000000000000000077000000740000000400000004000000000000000000000000000000000000000000000000000000000000000
a49499999999494a0777777777777777777777700777777040000000400000004000000000010000000000000000000000000000000000000000000000000010
]],
gff=[[
0000000000000000000000000000000004020000000000000000000200000000030303030303030304040402020000000303030303030303040404020202020200001313131302020302020202020002000013131313020204020202020202020000131313130004040202020202020200001313131300000002020202020202
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
]],
map=[[
2331252548252532323232323300002425262425252631323232252628282824252525252525323328382828312525253232323233000000313232323232323232330000002432323233313232322525252525482525252525252526282824252548252525262828282824254825252526282828283132323225482525252525
252331323232332900002829000000242526313232332828002824262a102824254825252526002a2828292810244825282828290000000028282900000000002810000000372829000000002a2831482525252525482525323232332828242525254825323338282a283132252548252628382828282a2a2831323232322525
252523201028380000002a0000003d24252523201028292900282426003a382425252548253300002900002a0031252528382900003a676838280000000000003828393e003a2800000000000028002425253232323232332122222328282425252532332828282900002a283132252526282828282900002a28282838282448
3232332828282900000000003f2020244825262828290000002a243300002a2425322525260000000000000000003125290000000021222328280000000000002a2828343536290000000000002839242526212223202123313232332828242548262b000000000000001c00003b242526282828000000000028282828282425
2340283828293a2839000000343522252548262900000000000030000000002433003125333d3f00000000000000003100001c3a3a31252620283900000000000010282828290000000011113a2828313233242526103133202828282838242525262b000000000000000000003b2425262a2828670016002a28283828282425
263a282828102829000000000000312525323300000000110000370000003e2400000037212223000000000000000000395868282828242628290000000000002a2828290000000000002123283828292828313233282829002a002a2828242525332b0c00000011110000000c3b314826112810000000006828282828282425
252235353628280000000000003a282426003d003a3900270000000000002125001a000024252611111111000000002c28382828283831332800000017170000002a000000001111000024261028290028281b1b1b282800000000002a2125482628390000003b34362b000000002824252328283a67003a28282829002a3132
25333828282900000000000000283824252320201029003039000000005824480000003a31323235353536675800003c282828281028212329000000000000000000000000003436003a2426282800003828390000002a29000000000031323226101000000000282839000000002a2425332828283800282828390000001700
2600002a28000000003a283a2828282425252223283900372858390068283132000000282828282820202828283921222829002a28282426000000000000000000000000000020382828312523000000282828290000000000163a67682828003338280b00000010382800000b00003133282828282868282828280000001700
330000002867580000281028283422252525482628286720282828382828212200003a283828102900002a28382824252a0000002838242600000017170000000000000000002728282a283133390000282900000000000000002a28282829002a2839000000002a282900000000000028282838282828282828290000000000
0000003a2828383e3a2828283828242548252526002a282729002a28283432250000002a282828000000002810282425000000002a282426000000000000000000000000000037280000002a28283900280000003928390000000000282800000028290000002a2828000000000000002a282828281028282828675800000000
0000002838282821232800002a28242532322526003a2830000000002a28282400000000002a281111111128282824480000003a28283133000000000000171700013f0000002029000000003828000028013a28281028580000003a28290000002a280c0000003a380c00000000000c00002a2828282828292828290000003a
00013a2123282a313329001111112425002831263a3829300000000000002a310000000000002834222236292a0024253e013a3828292a00000000000000000035353536000020000000003d2a28671422222328282828283900582838283d00003a290000000028280000000000000000002a28282a29000058100012002a28
22222225262900212311112122222525002a3837282900301111110000003a2800013f0000002a282426290000002425222222232900000000000000171700002a282039003a2000003a003435353535252525222222232828282810282821220b10000000000b28100000000b0000002c00002838000000002a283917000028
2548252526111124252222252525482500012a2828673f242222230000003828222223000012002a24260000001224252525252600000000171700000000000000382028392827080028676820282828254825252525262a28282122222225253a28013d0000006828390000000000003c0168282800171717003a2800003a28
25252525252222252525252525252525222222222222222525482667586828282548260000270000242600000021252525254826171700000000000000000000002a2028102830003a282828202828282525252548252600002a2425252548252821222300000028282800000000000022222223286700000000282839002838
2532330000002432323232323232252525252628282828242532323232254825253232323232323225262828282448252525253300000000000000000000005225253232323233313232323233282900262829286700000000002828313232322525253233282800312525482525254825254826283828313232323232322548
26282800000030402a282828282824252548262838282831333828290031322526280000163a28283133282838242525482526000000000000000000000000522526000016000000002a10282838390026281a3820393d000000002a3828282825252628282829003b2425323232323232323233282828282828102828203125
3328390000003700002a3828002a2425252526282828282028292a0000002a313328111111282828000028002a312525252526000000000000000000000000522526000000001111000000292a28290026283a2820102011111121222328281025252628382800003b24262b002a2a38282828282829002a2800282838282831
28281029000000000000282839002448252526282900282067000000000000003810212223283829003a1029002a242532323367000000000000000000004200252639000000212300000000002122222522222321222321222324482628282832323328282800003b31332b00000028102829000000000029002a2828282900
2828280016000000162a2828280024252525262700002a2029000000000000002834252533292a0000002a00111124252223282800002c46472c00000042535325262800003a242600001600002425252525482631323331323324252620283822222328292867000028290000000000283800111100001200000028292a1600
283828000000000000003a28290024254825263700000029000000000000003a293b2426283900000000003b212225252526382867003c56573c4243435363633233283900282426111111111124252525482526201b1b1b1b1b24252628282825252600002a28143a2900000000000028293b21230000170000112867000000
2828286758000000586828380000313232323320000000000000000000272828003b2426290000000000003b312548252533282828392122222352535364000029002a28382831323535353522254825252525252300000000003132332810284825261111113435361111111100000000003b3133111111111127282900003b
2828282810290000002a28286700002835353536111100000000000011302838003b3133000000000000002a28313225262a282810282425252662636400000000160028282829000000000031322525252525252667580000002000002a28282525323535352222222222353639000000003b34353535353536303800000017
282900002a0000000000382a29003a282828283436200000000000002030282800002a29000011110000000028282831260029002a282448252523000000000039003a282900000000000000002831322525482526382900000017000058682832331028293b2448252526282828000000003b201b1b1b1b1b1b302800000017
283a0000000000000000280000002828283810292a000000000000002a3710281111111111112136000000002a28380b2600000000212525252526001c0000002828281000000000001100002a382829252525252628000000001700002a212228282908003b242525482628282912000000001b00000000000030290000003b
3829000000000000003a102900002838282828000000000000000000002a2828223535353535330000000000002828393300000000313225252533000000000028382829000000003b202b00682828003232323233290000000000000000312528280000003b3132322526382800170000000000000000110000370000000000
290000000000000000002a000000282928292a0000000000000000000000282a332838282829000000000000001028280000000042434424252628390000000028002a0000110000001b002a2010292c1b1b1b1b0000000000000000000010312829160000001b1b1b313328106700000000001100003a2700001b0000000000
00000100000011111100000000002a3a2a0000000000000000000000002a2800282829002a000000000000000028282800000000525354244826282800000000290000003b202b39000000002900003c000000000000000000000000000028282800000000000000001b1b2a2829000001000027390038300000000000000000
1111201111112122230000001212002a00010000000000000000000000002900290000000000000000002a6768282900003f01005253542425262810673a3900013f0000002a3829001100000000002101000000000000003a67000000002a382867586800000100000000682800000021230037282928300000000000000000
22222222222324482611111120201111002739000017170000001717000000000001000000001717000000282838393a0021222352535424253328282838290022232b00000828393b27000000001424230000001200000028290000000000282828102867001717171717282839000031333927101228370000000000000000
254825252526242526212222222222223a303800000000000000000000000000001717000000000000003a28282828280024252652535424262828282828283925262b00003a28103b30000000212225260000002700003a28000000000000282838282828390000005868283828000022233830281728270000000000000000
000000000000000028242525252548253329000031323232253232323232253200000000000000003b200000313232252526002a283824252532323232323232000000000000002a10282900313232256363636452535353545500000055525325482525262b0000000000002425252526282828242548252525482525252525
000000000000586828244825252525251b0016001b1b1b1b301b1b1b1b1b301b000000000000001111200000002a282425330000002a313233000029000038290000000000001100002a0000002a28310000000062636363645500000055525325252548262b3a00000000002425482526283828313232323232323225254825
0000000000002a102831322525482525000000000000000037000000000037000000000000003b343536001400001031262b0000000000000000000000002a0000000000003b202b0012000000002a280000000000000000006500000055626325252532332b28290011112024252525261029002829000000002a2831252525
00000000000000002a2828242525254800000000000000001b00000000001b0000000000000000003b3435363900001b267273737373737373737374110000160000001100001b003b202b00000016280000000000000000000000000065000025253328282828103a21222225252525262800002a0011111100002838242525
00000000000000393a282824252525250000160000110000000000110000000011000000000000000000002010280011252222222222222222222222232b000000003b202b000000001b00000000002a000000000000000039000000000000004826282828382828283132322548252526290000001142434400002a28242548
000000000000002a2838282448252525000000003b202b3916003b202b00160027393a00000000000000001b2a283921254825252525253232323232262b00000000001b00000011000000000000000000000000390000682800003a0000000025262828102a002a282829003132323226111111114253535400003b21252525
000000000000000028282831323232322800003a001b3a283900001b000000003728383911000000000000113a282831323232322548262829002a10372b00160000000000003b202b0000160000000000003a582828682828282828390000002526382829000000002a00000000000025222222236263636400003b24252525
000000111111113a2828101b1b1b1b1b283928283900282800000000000000001b002a282711000000003b272828381b22222223313233160000682900000000000100000000001b0000000000000000683928282828102900002a10283a67682526282839000000000000000000000025254825252328382900003b24482525
000068212222232838282839000000002828282828382829000000000000160011003a283727000000003b372a2829112525482638282a0000002a0000000000002011111111111111111111111100162828282a38282800000000002828282825262829000000000000000000000000252525252526102a0000003b24252525
000010313225252235353536000000002838002a282828103939000011000000272828291b3039000000001b002a282125322526290000000000003a0000000000343535353535353535353535362b0028282900282829006061003a2838282a4826000000000000000000000000000025482525252629000011111124252525
00002a28283132261b1b1b1b0000000029000000002a28282828393b272b0000372810001130283a00000011002828312601313316000000000000283900000000202838282828202828282828272b00382800002a283d0070713f28282900002526000000000000390000000000000025252525482600003b21222331254825
0000002838293b30000000000012000000000000000000282828283b302b00001b2a283827302810390000273a28291b26170000000000003900003828000000001b2a282810281b2a28382a28372b002828390000282122232122232800003a3233000000000000282900000000000032323232323300003b24252523312525
0000002a28003b37000000003a27000000010000001111111128383b372b003a000028283730290028380037283800112639000000000000280000282829000000000029162a28000028160028280000102900000020313233244826284243430000000000000000280000586800000038282829000000003b24254825233132
00000100280000280000002a2830000022222311112135353610282829000038000029001b3000002a28000028280021262829000000003a2829002828000000005868000028283a2828390028291600280001000021222222252525235253530000003f0100003a2800002a1000000028290000000039003b24252525252222
40002122232b003839120068383039002548252222260c002a28282800003a28010000003a3708000028390028290031262800000000002838003a2810000000002a282828292a2828382828280000004343434344242548252525252652535300000021360000382839000028001c0028013d3e003a28003b24252525482525
21232425262b68282827282810302800252525254826000000282829000028282300000038283900002a28102800001b2638390000000028280028282839000000000038280000002a28102800000000535353535424252525252548265253530000003042440028282800002839000022222223102838393b24252525252525
2525252525262b1b1b1b3132322526004825323232323232252532323233282825252525252525252525482532332b2832323232322526282800003b2425252525482525252525484825252526382824254825253233282829242532323225250000000000000000000000000000000000000000000000000000000000000000
2525254825262b0000001b1b1b242600323328670000002824332b2a281028382525254825253232323225261b1b00382901003a282426382900003b2432323232323232323232323232322526102831252532331b1b102814313338282824480000000000000000000000000000000000000000000000000000000000000000
2525252525262b00000000002a242667282838290000112a372b003a282729002525252525331b1b1b1b31330000002835353536282426281400003b30282a2a1a2829002a2838282810282426002a3825261b1b00003821232b0000081024250000000000003a00000000000000000000000000000000000000000000000000
2548253232332b00000011002824262928102a00003b2700290000003830000032322525262b000000003b2700003a282828382828242522232b003b372908000010001100292a2829112a3133003a2825262b0000002a31332b0000682824250000000000001000003900000000000000000000000000000000000000000000
2525262122232b003a67272b2a24263828290000003b30000000003a2830000022233132332b000000003b302839002a3828291029244825262b000000000000002a3b202b3a16283b202b001100002825262b000000001b1b00002a382a2425000000003a00280000380000000000000000000000494a4b4c4d4e4f00000000
2525262448262b002a38302b1224262938000000003b3000000000002a303e1425252222232b000000003b30290000002829000000242525262b0000000000000000001b002a2828001b003b202b112a25262b000000000000000000293b2448000000002867280000100039000000000000000000595a5b5c5d5e5f00000000
3232333132262b120028302b1731330028395868393b3011111111111124222225254825262b000011003b302b0000002811111111242548262b0000000000000000000000001110675811001b3b2016482611111111000000160000003b3125000000002838287600286728000000000000000000696a6b6c6d6e6f00000000
280000002a301127002a302b001010392828382834353235353535353525254825252525262b003b27003b302b0000002834353535323225262b00001100000000000000003b20282838272b16001b002525232122232b000000000000001b24000000002a28282123283829000000000000000000797a7b7c7d7e7f00000000
29001100003135262b0031353535353529002a0000102828282829003b24252532323232262b163b30003b3000000000291b1b1b1b1b3b24262b003b272b00000011000000001b2a2828372b000000003232333132332b000011110000003b240000006838282125252328393a00000000000000000000000000000000000000
003b272b002a38302b0000002a28393b0000000000002a2838282867212525251b1b1b1b372b003b30393b30160000000000000000003b24262b173b302b00003b202b110000001100291b000000003a1b1b1b1b1b1b00111121231100003b240000002a28212548252523283868000000000000000000000000000000000000
083b302b00002830111111110028383b111111111100002829002928242548250000003a2800003b30283b37000000000000000000003b2426111111302b0000001b3b202b003b272b000000000000282b01000000003b21222532362b003b315858682829242525252526102828680000000000000000000000000000000000
003b372b00002a31353535360028293b22222222231111202b00002a31322525000000103829003b3028282800000000111111113900112425222222332b00000100001b00003b302b000000005868282b17000000003b2425331b1b0000001b281028380031322525482629002a280000000000000000000000000000000000
39001b000000001b1b1b1b1b002a003b323232323235361b000000001b1b3125000000002800003b30283829000000002222222328383432323232332b0000002339000000003b372b000000002a10281111111100003b31331b003a00160000002a28393f212324253233200000280000000000000000000000000000000000
2829000001001400000000000000003b39013d00002a28000000000000002824000000682867003b30002a286700000025252526002828003a10282a0016003a262800000000001b000000393a283828222222232b00001b1b000038000000680000212222252631332122232839286700000000000000000000000000000000
1067583a21222311111111110012003b222223390010283900000000083a103101003a382829003b370000382900000025482526002a28282838290000000028263839120000000000003a2828282810254825262b160000390000283a003a280100312525482522222525252310382800000000000000000000000000000000
2838282824252522222222230017003b252526283a28382800000000003828282300102828000000380000281000000025252526171728382800000000003a282628102739000000002a282838282828252548262b00003a38003a28102838282122232425252525252548252522222300000000000000000000000000000000
]]
})
]==]
-- globals --
-------------
require 'allocators.default'
require 'sequence'
require 'string'
local PointerCallback = @function(self: pointer)
local Type = @record {
tile: integer,
last: integer,
if_not_fruit: boolean,
init: PointerCallback,
update: PointerCallback,
draw: PointerCallback,
}
local Rect = @record{
x: number, y: number,
w: number, h: number
}
local Point = @record{
x: number, y: number
}
local HairRect = @record{
x: number, y: number, size: number
}
local Particle = @record{
x: number, y: number, h: number, s: number, t: number,
spd: Point, off: number,
c: byte,
}
local Object = @record {
type: *Type,
spr: number,
state: integer,
x: number, y: number,
w: number, h: number,
t: number, s: number,
dir: number,
hitbox: Rect,
spd: Point,
rem: Point,
flip: record{x: boolean, y: boolean},
collideable: boolean,
solids: boolean,
-- platform
last: number,
-- room title / player spawn
delay: integer,
-- player
grace: number,
jbuffer: number,
djump: number,
dash_time: number,
dash_effect_time: number,
dash_target: Point,
dash_accel: Point,
spr_off: number,
p_jump: boolean,
p_dash: boolean,
was_on_ground: boolean,
hair: [5]HairRect,
-- player spawn
target: Point,
-- fall floor
solid: boolean, -- TODO: remove this?
-- spring
hide_in: number,
hide_for: number,
-- flag
score: number,
show: boolean,
-- lifeup
duration: number,
flash: number,
-- fruit
start: number,
off: number,
-- balloon
timer: number,
offset: number,
-- big chest
particles: sequence(Particle),
-- fly fruit
fly: boolean,
step: number,
sfx_delay: number,
-- text
index: number,
text: string,
toff: Point
}
local platform: *Type
local fall_floor: *Type
local fake_wall: *Type
local room_title: *Type
local smoke: *Type
local spring: *Type
local room: record{x: integer, y: integer} = { x=0, y=0 }
local objects: sequence(*Object)
local types: sequence(*Type)
local dead_particles: sequence(Particle)
local got_fruit: [33]boolean
local will_restart: boolean
local pause_player: boolean
local flash_bg: boolean
local start_game: boolean
local has_dashed: boolean
local has_key: boolean
local has_dashed: boolean
local has_key: boolean
local new_bg: boolean
local seconds = 0
local minutes = 0
local freeze = 0
local shake = 0
local delay_restart= 0
local sfx_timer=0
local music_timer = 0
local frames = 0
local deaths = 0
local max_djump = 1
local start_game_flash = 0
local k_left <comptime> = 0
local k_right <comptime> = 1
local k_up <comptime> = 2
local k_down <comptime> = 3
local k_jump <comptime> = 4
local k_dash <comptime> = 5
local room: record{x: integer, y: integer}
-- helper functions --
----------------------
local function appr(val: is_arithmetic, target: is_arithmetic, amount: is_arithmetic)
return val > target and max(val - amount, target) or min(val + amount, target)
end
local function maybe()
return rnd(1)<0.5
end
local function sign(v: is_arithmetic)
local r: #[v.type]#
if v>0 then
r = 1
elseif v< 0 then
r = -1
end
return r
end
local function level_index()
return room.x%8+room.y*8
end
local function is_title()
return level_index()==31
end
local function tile_at(x: number, y: number)
return mget(room.x * 16 + x, room.y * 16 + y)
end
local function tile_flag_at(x: number, y: number, w: number, h: number, flag: integer)
for i=max(0,x//8),min(15,(x+w-1)/8) do
for j=max(0,y//8),min(15,(y+h-1)/8) do
if fget(tile_at(i,j),flag) then
return true
end
end
end
return false
end
local function solid_at(x: number, y: number, w: number, h: number)
return tile_flag_at(x,y,w,h,0)
end
local function ice_at(x: number, y: number, w: number, h: number)
return tile_flag_at(x,y,w,h,4)
end
local function spikes_at(x: number, y: number, w: number, h: number, xspd: number, yspd: number)
for i=max(0,x//8),min(15,(x+w-1)/8) do
for j=max(0,y//8),min(15,(y+h-1)/8) do
local tile=tile_at(i,j)
if tile==17 and ((y+h-1)%8>=6 or y+h==j*8+8) and yspd>=0 then
return true
elseif tile==27 and y%8<=2 and yspd<=0 then
return true
elseif tile==43 and x%8<=2 and xspd<=0 then
return true
elseif tile==59 and ((x+w-1)%8>=6 or x+w==i*8+8) and xspd>=0 then
return true
end
end
end
return false
end
local function psfx(num: integer)
if sfx_timer <= 0 then
sfx(num)
end
end
local function draw_time(x: number, y: number)
local s=seconds
local m=minutes%60
local h=minutes//60
rectfill(x,y,x+32,y+6,0)
print((h<10 and "0"..h or tostring(h))..":"..
(m<10 and "0"..m or tostring(m))..":"..
(s<10 and "0"..s or tostring(s)), x+1, y+1, 7)
end
-- object functions --
-----------------------
function Object:collide(type: *Type, ox: number, oy: number): *Object
local other
for i=1,#objects do
other = objects[i]
if other ~= nilptr and other.type == type and other ~= self and other.collideable and
other.x+other.hitbox.x+other.hitbox.w > self.x+self.hitbox.x+ox and
other.y+other.hitbox.y+other.hitbox.h > self.y+self.hitbox.y+oy and
other.x+other.hitbox.x < self.x+self.hitbox.x+self.hitbox.w+ox and
other.y+other.hitbox.y < self.y+self.hitbox.y+self.hitbox.h+oy then
return other
end
end
return nilptr
end
function Object:check(type: *Type,ox: number,oy: number): boolean
return self:collide(type, ox, oy) ~= nilptr
end
function Object:is_solid(ox: number, oy: number): boolean
if oy>0 and not self:check(platform,ox,0) and self:check(platform,ox,oy) then
return true
end
return solid_at(self.x+self.hitbox.x+ox,self.y+self.hitbox.y+oy,self.hitbox.w,self.hitbox.h)
or self:check(fall_floor,ox,oy)
or self:check(fake_wall,ox,oy)
end
function Object:is_ice(ox: number, oy: number): boolean
return ice_at(self.x+self.hitbox.x+ox,self.y+self.hitbox.y+oy,self.hitbox.w,self.hitbox.h)
end
function Object:move_x(amount: number, start: number)
if self.solids then
local step = sign(amount)
for i=start,abs(amount) do
if not self:is_solid(step,0) then
self.x = self.x + step
else
self.spd.x = 0
self.rem.x = 0
break
end
end
else
self.x = self.x + amount
end
end
function Object:move_y(amount: number)
if self.solids then
local step = sign(amount)
for i=0,abs(amount) do
if not self:is_solid(0,step) then
self.y = self.y + step
else
self.spd.y = 0
self.rem.y = 0
break
end
end
else
self.y = self.y + amount
end
end
function Object:move(ox: number,oy: number)
local amount
-- [x] get move amount
self.rem.x = self.rem.x + ox
amount = flr(self.rem.x + 0.5)
self.rem.x = self.rem.x - amount
self:move_x(amount,0)
-- [y] get move amount
self.rem.y = self.rem.y + oy
amount = flr(self.rem.y + 0.5)
self.rem.y = self.rem.y - amount
self:move_y(amount)
end
local function init_object(type: *Type, x: number, y: number): *Object
if type.if_not_fruit and got_fruit[1+level_index()] then
return nilptr
end
local obj = new(@Object)
obj.type = type
obj.collideable=true
obj.solids=true
obj.spr = type.tile
obj.flip = {x=false,y=false}
obj.x = x
obj.y = y
obj.hitbox = { x=0,y=0,w=8,h=8 }
obj.spd = {x=0,y=0}
obj.rem = {x=0,y=0}
objects:push(obj)
if obj.type.init ~= nilptr then
obj.type.init(obj)
end
return obj
end
local function destroy_object(obj: *Object)
objects:removevalue(obj)
end
-- room functions --
--------------------
local function restart_room()
will_restart=true
delay_restart=15
end
local function load_room(x: integer, y: integer)
has_dashed=false
has_key=false
--remove existing objects
while #objects > 0 do
destroy_object(objects[1])
end
--current room
room.x = x
room.y = y
-- entities
for tx=0,15 do
for ty=0,15 do
local tile = mget(room.x*16+tx,room.y*16+ty);
if tile==11 then
init_object(platform,tx*8,ty*8).dir=-1
elseif tile==12 then
init_object(platform,tx*8,ty*8).dir=1
else
for i=1,#types do
local type = types[i]
if type.tile == tile then
init_object(type,tx*8,ty*8)
end
end
end
end
end
if not is_title() then
init_object(room_title,0,0)
end
end
local function next_room()
if room.x==2 and room.y==1 then
music(30,500,7)
elseif room.x==3 and room.y==1 then
music(20,500,7)
elseif room.x==4 and room.y==2 then
music(30,500,7)
elseif room.x==5 and room.y==3 then
music(30,500,7)
end
if room.x==7 then
load_room(0,room.y+1)
else
load_room(room.x+1,room.y)
end
end
local function kill_player(obj: *Object)
sfx_timer=12
sfx(0)
deaths=deaths+1
shake=10
destroy_object(obj)
dead_particles:clear()
for dir=0,7 do
local angle=(dir/8)
local p: Particle = {
x=obj.x+4,
y=obj.y+4,
t=10,
spd={
x=sin(angle)*3,
y=cos(angle)*3
}
}
dead_particles:push(p)
restart_room()
end
end
local function title_screen()
for i=0,#got_fruit-1 do
got_fruit[i] = false
end
music(40,0,7)
load_room(7,3)
end
-- entry point --
-----------------
local function _init()
title_screen()
end
local function begin_game()
frames=0
seconds=0
minutes=0
music_timer=0
start_game=false
music(0,0,7)
load_room(0,0)
end
-- effects --
-------------
local clouds: sequence(*Object)
for i=0,16 do
local cloud = new(@Object)
$cloud = {
x=rnd(128),
y=rnd(128),
spd={x=1+rnd(4)},
w=32+rnd(32)
}
clouds:push(cloud)
end
local particles: sequence(Particle)
for i=0,24 do
particles:push({
x=rnd(128),
y=rnd(128),
s=0+flr(rnd(5)/4),
spd={x=0.25+rnd(5)},
off=rnd(1),
c=6+iflr(0.5+rnd(1))
})
end
-- hair --
-------------------
local function create_hair(obj: *Object)
for i=0,4 do
obj.hair[i] = {x=obj.x,y=obj.y,size=max(1,min(2,3-i))}
end
end
local function set_hair_color(djump: number)
local col
if djump == 1 then
col = 8
elseif djump == 2 then
col = 7+iflr((frames/3)%2)*4
else
col = 12
end
pal(8, col)
end
local function draw_hair(obj: *Object, facing: number)
local last: HairRect={x=obj.x+4-facing*2,y=obj.y+(btn(k_down) and 4 or 3)}
for i=0,4 do
local h = &obj.hair[i]
h.x=h.x+(last.x-h.x)/1.5
h.y=h.y+(last.y+0.5-h.y)/1.5
circfill(h.x,h.y,h.size,8)
last=$h
end
end
local function unset_hair_color()
pal(8,8)
end
-- player entity --
-------------------
local function player_init(this: *Object)
this.p_jump=false
this.p_dash=false
this.grace=0
this.jbuffer=0
this.djump=max_djump
this.dash_time=0
this.dash_effect_time=0
this.dash_target={x=0,y=0}
this.dash_accel={x=0,y=0}
this.hitbox = {x=1,y=3,w=6,h=5}
this.spr_off=0
this.was_on_ground=false
create_hair(this)
end
local function player_update(this: *Object)
if pause_player then return end
local input = btn(k_right) and 1 or (btn(k_left) and -1 or 0)
-- spikes collide
if spikes_at(this.x+this.hitbox.x,this.y+this.hitbox.y,this.hitbox.w,this.hitbox.h,this.spd.x,this.spd.y) then
kill_player(this)
end
-- bottom death
if this.y>128 then
kill_player(this)
end
local on_ground=this:is_solid(0,1)
local on_ice=this:is_ice(0,1)
-- smoke particles
if on_ground and not this.was_on_ground then
init_object(smoke,this.x,this.y+4)
end
local jump = btn(k_jump) and not this.p_jump
this.p_jump = btn(k_jump)
if jump then
this.jbuffer=4
elseif this.jbuffer>0 then
this.jbuffer=this.jbuffer-1
end
local dash = btn(k_dash) and not this.p_dash
this.p_dash = btn(k_dash)
if on_ground then
this.grace=6
if this.djump<max_djump then
psfx(54)
this.djump=max_djump
end
elseif this.grace > 0 then
this.grace=this.grace-1
end
this.dash_effect_time = this.dash_effect_time - 1
if this.dash_time > 0 then
init_object(smoke,this.x,this.y)
this.dash_time=this.dash_time-1
this.spd.x=appr(this.spd.x,this.dash_target.x,this.dash_accel.x)
this.spd.y=appr(this.spd.y,this.dash_target.y,this.dash_accel.y)
else
-- move
local maxrun=1
local accel=0.6
local deccel=0.15
if not on_ground then
accel=0.4
elseif on_ice then
accel=0.05
if input==(this.flip.x and -1 or 1) then
accel=0.05
end
end
if abs(this.spd.x) > maxrun then
this.spd.x=appr(this.spd.x,sign(this.spd.x)*maxrun,deccel)
else
this.spd.x=appr(this.spd.x,input*maxrun,accel)
end
--facing
if this.spd.x~=0 then
this.flip.x=(this.spd.x<0)
end
-- gravity
local maxfall=2
local gravity=0.21
if abs(this.spd.y) <= 0.15 then
gravity=gravity*0.5
end
-- wall slide
if input~=0 and this:is_solid(input,0) and not this:is_ice(input,0) then
maxfall=0.4
if rnd(10)<2 then
init_object(smoke,this.x+input*6,this.y)
end
end
if not on_ground then
this.spd.y=appr(this.spd.y,maxfall,gravity)
end
-- jump
if this.jbuffer>0 then
if this.grace>0 then
-- normal jump
psfx(1)
this.jbuffer=0
this.grace=0
this.spd.y=-2
init_object(smoke,this.x,this.y+4)
else
-- wall jump
local wall_dir=0
if this:is_solid(-3,0) then
wall_dir=-1
elseif this:is_solid(3,0) then
wall_dir=1
end
if wall_dir~=0 then
psfx(2)
this.jbuffer=0
this.spd.y=-2
this.spd.x=-wall_dir*(maxrun+1)
if not this:is_ice(wall_dir*3,0) then
init_object(smoke,this.x+wall_dir*6,this.y)
end
end
end
end
-- dash
local d_full=5
local d_half=d_full*0.70710678118
if this.djump>0 and dash then
init_object(smoke,this.x,this.y)
this.djump=this.djump-1
this.dash_time=4
has_dashed=true
this.dash_effect_time=10
local v_input=(btn(k_up) and -1 or (btn(k_down) and 1 or 0))
if input~=0 then
if v_input~=0 then
this.spd.x=input*d_half
this.spd.y=v_input*d_half
else
this.spd.x=input*d_full
this.spd.y=0
end
elseif v_input~=0 then
this.spd.x=0
this.spd.y=v_input*d_full
else
this.spd.x=(this.flip.x and -1 or 1)
this.spd.y=0
end
psfx(3)
freeze=2
shake=6
this.dash_target.x=2*sign(this.spd.x)
this.dash_target.y=2*sign(this.spd.y)
this.dash_accel.x=1.5
this.dash_accel.y=1.5
if this.spd.y<0 then
this.dash_target.y=this.dash_target.y*.75
end
if this.spd.y~=0 then
this.dash_accel.x=this.dash_accel.x*0.70710678118
end
if this.spd.x~=0 then
this.dash_accel.y=this.dash_accel.y*0.70710678118
end
elseif dash and this.djump<=0 then
psfx(9)
init_object(smoke,this.x,this.y)
end
end
-- animation
this.spr_off=this.spr_off+0.25
if not on_ground then
if this:is_solid(input,0) then
this.spr=5
else
this.spr=3
end
elseif btn(k_down) then
this.spr=6
elseif btn(k_up) then
this.spr=7
elseif (this.spd.x==0) or (not btn(k_left) and not btn(k_right)) then
this.spr=1
else
this.spr=1+this.spr_off%4
end
-- next level
if this.y<-4 and level_index()<30 then next_room() end
-- was on the ground
this.was_on_ground=on_ground
end
local function player_draw(this: *Object)
-- clamp in screen
if this.x<-1 or this.x>121 then
this.x=clamp(this.x,-1,121)
this.spd.x=0
end
set_hair_color(this.djump)
draw_hair(this,this.flip.x and -1 or 1)
spr(iflr(this.spr),this.x,this.y,1,1,this.flip.x,this.flip.y)
unset_hair_color()
end
local player: *Type = new(@Type)
$player = {
init = (@PointerCallback)(player_init),
update = (@PointerCallback)(player_update),
draw = (@PointerCallback)(player_draw)
}
-- player spawn --
-------------------
local function player_spawn_init(this: *Object)
sfx(4)
this.spr=3
this.target= {x=this.x,y=this.y}
this.y=128
this.spd.y=-4
this.state=0
this.delay=0
this.solids=false
create_hair(this)
end
local function player_spawn_update(this: *Object)
-- jumping up
if this.state==0 then
if this.y < this.target.y+16 then
this.state=1
this.delay=3
end
-- falling
elseif this.state==1 then
this.spd.y=this.spd.y+0.5
if this.spd.y>0 and this.delay>0 then
this.spd.y=0
this.delay=this.delay-1
end
if this.spd.y>0 and this.y > this.target.y then
this.y=this.target.y
this.spd = {x=0,y=0}
this.state=2
this.delay=5
shake=5
init_object(smoke,this.x,this.y+4)
sfx(5)
end
-- landing
elseif this.state==2 then
this.delay=this.delay-1
this.spr=6
if this.delay<0 then
destroy_object(this)
init_object(player,this.x,this.y)
end
end
end
local function player_spawn_draw(this: *Object)
set_hair_color(max_djump)
draw_hair(this,1)
spr(iflr(this.spr),this.x,this.y,1,1,this.flip.x,this.flip.y)
unset_hair_color()
end
local player_spawn: *Type = new(@Type)
$player_spawn = {
tile=1,
init = (@PointerCallback)(player_spawn_init),
update = (@PointerCallback)(player_spawn_update),
draw = (@PointerCallback)(player_spawn_draw)
}
types:push(player_spawn)
-- spring --
-------------------
local function break_spring(obj: *Object)
obj.hide_in=15
end
local function break_fall_floor(obj: *Object)
if obj.state==0 then
psfx(15)
obj.state=1
obj.delay=15 --how long until it falls
init_object(smoke,obj.x,obj.y)
local hit=obj:collide(spring,0,-1)
if hit~=nilptr then
break_spring(hit)
end
end
end
local function spring_init(this: *Object)
this.hide_in=0
this.hide_for=0
end
local function spring_update(this: *Object)
if this.hide_for>0 then
this.hide_for=this.hide_for-1
if this.hide_for<=0 then
this.spr=18
this.delay=0
end
elseif this.spr==18 then
local hit = this:collide(player,0,0)
if hit ~=nilptr and hit.spd.y>=0 then
this.spr=19
hit.y=this.y-4
hit.spd.x=hit.spd.x*0.2
hit.spd.y=-3
hit.djump=max_djump
this.delay=10
init_object(smoke,this.x,this.y)
-- breakable below us
local below=this:collide(fall_floor,0,1)
if below~=nilptr then
break_fall_floor(below)
end
psfx(8)
end
elseif this.delay>0 then
this.delay=this.delay-1
if this.delay<=0 then
this.spr=18
end
end
-- begin hiding
if this.hide_in>0 then
this.hide_in=this.hide_in-1
if this.hide_in<=0 then
this.hide_for=60
this.spr=0
end
end
end
spring = new(@Type)
$spring = {
tile=18,
init = (@PointerCallback)(spring_init),
update = (@PointerCallback)(spring_update)
}
types:push(spring)
-- balloon --
-------------------
local function balloon_init(this: *Object)
this.offset=rnd(1)
this.start=this.y
this.timer=0
this.hitbox={x=-1,y=-1,w=10,h=10}
end
local function balloon_update(this: *Object)
if this.spr==22 then
this.offset=this.offset+0.01
this.y=this.start+sin(this.offset)*2
local hit = this:collide(player,0,0)
if hit~=nilptr and hit.djump<max_djump then
psfx(6)
init_object(smoke,this.x,this.y)
hit.djump=max_djump
this.spr=0
this.timer=60
end
elseif this.timer>0 then
this.timer=this.timer-1
else
psfx(7)
init_object(smoke,this.x,this.y)
this.spr=22
end
end
local function balloon_draw(this: *Object)
if this.spr==22 then
spr(iflr(13+(this.offset*8)%3),this.x,this.y+6)
spr(iflr(this.spr),this.x,this.y)
end
end
local balloon = new(@Type)
$balloon = {
tile=22,
init = (@PointerCallback)(balloon_init),
update = (@PointerCallback)(balloon_update),
draw = (@PointerCallback)(balloon_draw)
}
types:push(balloon)
-- fall floor --
-------------------
local function fall_floor_init(this: *Object)
this.state=0
end
local function fall_floor_update(this: *Object)
-- idling
if this.state == 0 then
if this:check(player,0,-1) or this:check(player,-1,0) or this:check(player,1,0) then
break_fall_floor(this)
end
-- shaking
elseif this.state==1 then
this.delay=this.delay-1
if this.delay<=0 then
this.state=2
this.delay=60--how long it hides for
this.collideable=false
end
-- invisible, waiting to reset
elseif this.state==2 then
this.delay=this.delay-1
if this.delay<=0 and not this:check(player,0,0) then
psfx(7)
this.state=0
this.collideable=true
init_object(smoke,this.x,this.y)
end
end
end
local function fall_floor_draw(this: *Object)
if this.state~=2 then
if this.state~=1 then
spr(23,this.x,this.y)
else
spr(iflr(23+(15-this.delay)/5),this.x,this.y)
end
end
end
fall_floor = new(@Type)
$fall_floor = {
tile=23,
init = (@PointerCallback)(fall_floor_init),
update = (@PointerCallback)(fall_floor_update),
draw = (@PointerCallback)(fall_floor_draw)
}
types:push(fall_floor)
-- smoke --
-------------------
local function smoke_init(this: *Object)
this.spr=29
this.spd.y=-0.1
this.spd.x=0.3+rnd(0.2)
this.x=this.x+-1+rnd(2)
this.y=this.y+-1+rnd(2)
this.flip.x=maybe()
this.flip.y=maybe()
this.solids=false
end
local function smoke_update(this: *Object)
this.spr=this.spr+0.2
if this.spr>=32 then
destroy_object(this)
end
end
smoke = new(@Type)
$smoke = {
init = (@PointerCallback)(smoke_init),
update = (@PointerCallback)(smoke_update),
}
-- lifeup --
-------------------
local function lifeup_init(this: *Object)
this.spd.y=-0.25
this.duration=30
this.x=this.x-2
this.y=this.y-4
this.flash=0
this.solids=false
end
local function lifeup_update(this: *Object)
this.duration=this.duration-1
if this.duration<= 0 then
destroy_object(this)
end
end
local function lifeup_draw(this: *Object)
this.flash=this.flash+0.5
print("1000",this.x-2,this.y,7+iflr(this.flash%2))
end
local lifeup = new(@Type)
$lifeup = {
init = (@PointerCallback)(lifeup_init),
update = (@PointerCallback)(lifeup_update),
draw = (@PointerCallback)(lifeup_draw)
}
-- fruit --
-------------------
local function fruit_init(this: *Object)
this.start=this.y
this.off=0
end
local function fruit_update(this: *Object)
local hit=this:collide(player,0,0)
if hit~=nilptr then
hit.djump=max_djump
sfx_timer=20
sfx(13)
got_fruit[1+level_index()] = true
init_object(lifeup,this.x,this.y)
destroy_object(this)
end
this.off=this.off+1
this.y=this.start+sin(this.off/40)*2.5
end
local fruit = new(@Type)
$fruit = {
tile=26,
if_not_fruit=true,
init = (@PointerCallback)(fruit_init),
update = (@PointerCallback)(fruit_update)
}
types:push(fruit)
-- fly fruit --
-------------------
local function fly_fruit_init(this: *Object)
this.start=this.y
this.fly=false
this.step=0.5
this.solids=false
this.sfx_delay=8
end
local function fly_fruit_update(this: *Object)
--fly away
if this.fly then
if this.sfx_delay>0 then
this.sfx_delay=this.sfx_delay-1
if this.sfx_delay<=0 then
sfx_timer=20
sfx(14)
end
end
this.spd.y=appr(this.spd.y,-3.5,0.25)
if this.y<-16 then
destroy_object(this)
end
-- wait
else
if has_dashed then
this.fly=true
end
this.step=this.step+0.05
this.spd.y=sin(this.step)*0.5
end
-- collect
local hit=this:collide(player,0,0)
if hit~=nilptr then
hit.djump=max_djump
sfx_timer=20
sfx(13)
got_fruit[1+level_index()] = true
init_object(lifeup,this.x,this.y)
destroy_object(this)
end
end
local function fly_fruit_draw(this: *Object)
local off=0.0
if not this.fly then
local dir=sin(this.step)
if dir<0 then
off=1+max(0,sign(this.y-this.start))
end
else
off=(off+0.25)%3
end
spr(iflr(45+off),this.x-6,this.y-2,1,1,true,false)
spr(iflr(this.spr),this.x,this.y)
spr(iflr(45+off),this.x+6,this.y-2)
end
local fly_fruit = new(@Type)
$fly_fruit = {
tile=28,
if_not_fruit=true,
init = (@PointerCallback)(fly_fruit_init),
update = (@PointerCallback)(fly_fruit_update),
draw = (@PointerCallback)(fly_fruit_draw)
}
types:push(fly_fruit)
-- fake wall --
-------------------
local function fake_wall_update(this: *Object)
this.hitbox={x=-1,y=-1,w=18,h=18}
local hit = this:collide(player,0,0)
if hit~=nilptr and hit.dash_effect_time>0 then
hit.spd.x=-sign(hit.spd.x)*1.5
hit.spd.y=-1.5
hit.dash_time=-1
sfx_timer=20
sfx(16)
destroy_object(this)
init_object(smoke,this.x,this.y)
init_object(smoke,this.x+8,this.y)
init_object(smoke,this.x,this.y+8)
init_object(smoke,this.x+8,this.y+8)
init_object(fruit,this.x+4,this.y+4)
end
this.hitbox={x=0,y=0,w=16,h=16}
end
local function fake_wall_draw(this: *Object)
spr(64,this.x,this.y)
spr(65,this.x+8,this.y)
spr(80,this.x,this.y+8)
spr(81,this.x+8,this.y+8)
end
fake_wall = new(@Type)
$fake_wall = {
tile=64,
if_not_fruit=true,
update = (@PointerCallback)(fake_wall_update),
draw = (@PointerCallback)(fake_wall_draw),
}
types:push(fake_wall)
-- key --
-------------------
local function key_update(this: *Object)
local was=flr(this.spr)
this.spr=9+(sin(frames/30)+0.5)*1
local is=flr(this.spr)
if is==10 and is~=was then
this.flip.x=not this.flip.x
end
if this:check(player,0,0) then
sfx(23)
sfx_timer=10
destroy_object(this)
has_key=true
end
end
local key = new(@Type)
$key = {
tile=8,
if_not_fruit=true,
update = (@PointerCallback)(key_update)
}
types:push(key)
-- chest --
-------------------
local function chest_init(this: *Object)
this.x=this.x-4
this.start=this.x
this.timer=20
end
local function chest_update(this: *Object)
if has_key then
this.timer=this.timer-1
this.x=this.start-1+rnd(3)
if this.timer<=0 then
sfx_timer=20
sfx(16)
init_object(fruit,this.x,this.y-4)
destroy_object(this)
end
end
end
local chest = new(@Type)
$chest = {
tile=20,
if_not_fruit=true,
init = (@PointerCallback)(chest_init),
update = (@PointerCallback)(chest_update)
}
types:push(chest)
-- platform --
-------------------
local function platform_init(this: *Object)
this.x=this.x-4
this.solids=false
this.hitbox.w=16
this.last=this.x
end
local function platform_update(this: *Object)
this.spd.x=this.dir*0.65
if this.x<-16 then
this.x=128
elseif this.x>128 then
this.x=-16
end
if not this:check(player,0,0) then
local hit=this:collide(player,0,-1)
if hit~=nilptr then
hit:move_x(this.x-this.last,1)
end
end
this.last=this.x
end
local function platform_draw(this: *Object)
spr(11,this.x,this.y-1)
spr(12,this.x+8,this.y-1)
end
platform = new(@Type)
$platform = {
init = (@PointerCallback)(platform_init),
update = (@PointerCallback)(platform_update),
draw = (@PointerCallback)(platform_draw)
}
-- message --
-----------------------
local function message_draw(this: *Object)
this.text="-- celeste mountain --#this memorial to those# perished on the climb"
if this:check(player,4,0) then
if this.index<#this.text then
this.index=this.index+0.5
if this.index>=this.last+1 then
this.last=this.last+1
sfx(35)
end
end
this.toff={x=8,y=96}
for i=1,iflr(this.index) do
local subtext = this.text:sub(i,i)
if subtext=="#" then
this.toff.x=8
this.toff.y=this.toff.y+7
else
rectfill(this.toff.x-2,this.toff.y-2,this.toff.x+7,this.toff.y+6,7)
print(subtext,this.toff.x,this.toff.y,0)
this.toff.x=this.toff.x+5
end
end
else
this.index=0
this.last=0
end
end
local message = new(@Type)
$message = {
tile=86,
last=0,
draw = (@PointerCallback)(message_draw)
}
types:push(message)
-- orb --
-----------------------
local function orb_init(this: *Object)
this.spd.y=-4
this.solids=false
this.particles={}
end
local function orb_draw(this: *Object)
this.spd.y=appr(this.spd.y,0,0.5)
local hit=this:collide(player,0,0)
if this.spd.y==0 and hit~=nilptr then
music_timer=45
sfx(51)
freeze=10
shake=10
destroy_object(this)
max_djump=2
hit.djump=2
end
spr(102,this.x,this.y)
local off=frames/30
for i=0,7 do
circfill(this.x+4+cos(off+i/8)*8,this.y+4+sin(off+i/8)*8,1,7)
end
end
local orb = new(@Type)
$orb = {
init = (@PointerCallback)(orb_init),
draw=(@PointerCallback)(orb_draw)
}
-- big chest --
-----------------------
local function big_chest_init(this: *Object)
this.state=0
this.hitbox.w=16
end
local function big_chest_draw(this: *Object)
if this.state==0 then
local hit=this:collide(player,0,8)
if hit~=nilptr and hit:is_solid(0,1) then
music(-1,500,7)
sfx(37)
pause_player=true
hit.spd.x=0
hit.spd.y=0
this.state=1
init_object(smoke,this.x,this.y)
init_object(smoke,this.x+8,this.y)
this.timer=60
this.particles={}
end
spr(96,this.x,this.y)
spr(97,this.x+8,this.y)
elseif this.state==1 then
this.timer=this.timer-1
shake=5
flash_bg=true
if this.timer<=45 and #this.particles < 50 then
this.particles:push({
x=1+rnd(14),
y=0,
h=32+rnd(32),
spd={y=8+rnd(8)}
})
end
if this.timer<0 then
this.state=2
this.particles={}
flash_bg=false
new_bg=true
init_object(orb,this.x+4,this.y+4)
pause_player=false
end
for i=1,#this.particles do
local p = &this.particles[i]
p.y=p.y+p.spd.y
line(this.x+p.x,this.y+8-p.y,this.x+p.x,min(this.y+8-p.y+p.h,this.y+8),7)
end
end
spr(112,this.x,this.y+8)
spr(113,this.x+8,this.y+8)
end
local big_chest = new(@Type)
$big_chest = {
tile=96,
init = (@PointerCallback)(big_chest_init),
draw = (@PointerCallback)(big_chest_draw)
}
types:push(big_chest)
-- flag --
-----------------------
local function flag_init(this: *Object)
this.x=this.x+5
this.score=0
this.show=false
for i=1,#got_fruit-1 do
if got_fruit[i] then
this.score=this.score+1
end
end
end
local function flag_draw(this: *Object)
this.spr=118+(frames/5)%3
spr(iflr(this.spr),this.x,this.y)
if this.show then
rectfill(32,2,96,31,0)
spr(26,55,6)
print("x"..this.score,64,9,7)
draw_time(49,16)
print("deaths:"..deaths,48,24,7)
elseif this:check(player,0,0) then
sfx(55)
sfx_timer=30
this.show=true
end
end
local flag = new(@Type)
$flag = {
tile=118,
init = (@PointerCallback)(flag_init),
draw=(@PointerCallback)(flag_draw)
}
types:push(flag)
-- room title --
-----------------------
local function room_title_init(this: *Object)
this.delay=5
end
local function room_title_draw(this: *Object)
this.delay=this.delay-1
if this.delay<-30 then
destroy_object(this)
elseif this.delay<0 then
rectfill(24,58,104,70,0)
if room.x==3 and room.y==1 then
print("old site",48,62,7)
elseif level_index()==30 then
print("summit",52,62,7)
else
local level=(1+level_index())*100
print(level.." m",52+(level<1000 and 2 or 0),62,7)
end
draw_time(4,4)
end
end
room_title = new(@Type)
$room_title = {
init = (@PointerCallback)(room_title_init),
draw = (@PointerCallback)(room_title_draw)
}
-- update function --
-----------------------
local function _update()
frames=((frames+1)%30)
if frames==0 and level_index()<30 then
seconds=((seconds+1)%60)
if seconds==0 then
minutes=minutes+1
end
end
if music_timer>0 then
music_timer=music_timer-1
if music_timer<=0 then
music(10,0,7)
end
end
if sfx_timer>0 then
sfx_timer=sfx_timer-1
end
-- cancel if freeze
if freeze>0 then freeze=freeze-1 return end
-- screenshake
if shake>0 then
shake=shake-1
camera()
if shake>0 then
camera(-2+rnd(5),-2+rnd(5))
end
end
-- restart (soon)
if will_restart and delay_restart>0 then
delay_restart=delay_restart-1
if delay_restart<=0 then
will_restart=false
load_room(room.x,room.y)
end
end
-- update each object
for _,obj in ipairs(objects) do
obj:move(obj.spd.x, obj.spd.y)
if obj.type.update ~= nilptr then
obj.type.update(obj)
end
end
-- start game
if is_title() then
if not start_game and (btn(k_jump) or btn(k_dash)) then
music(-1)
start_game_flash=50
start_game=true
sfx(38)
end
if start_game then
start_game_flash=start_game_flash-1
if start_game_flash<=-30 then
begin_game()
end
end
end
end
-- drawing functions --
-----------------------
local function draw_object(obj: *Object)
if obj.type.draw ~= nilptr then
obj.type.draw(obj)
elseif obj.spr > 0 then
spr(iflr(obj.spr),obj.x,obj.y,1,1,obj.flip.x,obj.flip.y)
end
end
local function _draw()
if freeze>0 then return end
-- reset all palette values
pal()
-- start game flash
if start_game then
local c=10
if start_game_flash>10 then
if frames%10<5 then
c=7
end
elseif start_game_flash>5 then
c=2
elseif start_game_flash>0 then
c=1
else
c=0
end
if c<10 then
pal(6,c)
pal(12,c)
pal(13,c)
pal(5,c)
pal(1,c)
pal(7,c)
end
end
-- clear screen
local bg_col = 0
if flash_bg then
bg_col = frames//5
elseif new_bg then
bg_col=2
end
rectfill(0,0,128,128,bg_col)
-- clouds
if not is_title() then
for i=1,#clouds do
local c = clouds[i]
c.x = c.x + c.spd.x
rectfill(c.x,c.y,c.x+c.w,c.y+4+(1-c.w/64)*12,new_bg and 14 or 1)
if c.x > 128 then
c.x = -c.w
c.y=rnd(128-8)
end
end
end
-- draw bg terrain
map(room.x * 16,room.y * 16,0,0,16,16,4)
-- platforms/big chest
for _,o in ipairs(objects) do
if o.type==platform or o.type==big_chest then
draw_object(o)
end
end
-- draw terrain
local off=is_title() and -4 or 0
map(room.x*16,room.y * 16,off,0,16,16,2)
-- draw objects
for _,o in ipairs(objects) do
if o.type~=platform and o.type~=big_chest then
draw_object(o)
end
end
-- draw fg terrain
map(room.x * 16,room.y * 16,0,0,16,16,8)
-- particles
for i=1,#particles do
local p = &particles[i]
p.x = p.x + p.spd.x
p.y = p.y + sin(p.off)
p.off = p.off + min(0.05,p.spd.x/32)
rectfill(p.x,p.y,p.x+p.s,p.y+p.s,p.c)
if p.x>128+4 then
p.x=-4
p.y=rnd(128)
end
end
-- dead particles
do
local i = 1
while i <= #dead_particles do
local p = &dead_particles[i]
p.x = p.x + p.spd.x
p.y = p.y + p.spd.y
p.t = p.t - 1
local t = p.t/5
rectfill(p.x-t, p.y-t, p.x+t, p.y+t, 14+iflr(t%2))
if p.t <= 0 then
dead_particles:remove(i)
else
i = i + 1
end
end
end
-- draw outside of the screen for screenshake
rectfill(-5,-5,-1,133,0)
rectfill(-5,-5,133,-1,0)
rectfill(-5,128,133,133,0)
rectfill(128,-5,133,133,0)
-- credits
if is_title() then
print("x+c",58,80,5)
print("matt thorson",42,96,5)
print("noel berry",46,102,5)
end
if level_index()==30 then
local p
for i=1,#objects do
if objects[i].type==player then
p = objects[i]
break
end
end
if p~=nilptr then
local diff=min(24,40-abs(p.x+4-64))
rectfill(0,0,diff,128,0)
rectfill(128-diff,0,128,128,0)
end
end
end
## neco_run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment