-
-
Save edubart/a79bf78a249d1fff2b77728c260c7605 to your computer and use it in GitHub Desktop.
Celeste game in Nelua
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
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