Created
June 17, 2018 19:18
-
-
Save sherjilozair/0663956e065e9588fd3ab5c36f77575c to your computer and use it in GitHub Desktop.
Source code for pico-8 version of Celeste
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
-- ~celeste~ | |
-- matt thorson + noel berry | |
-- globals -- | |
------------- | |
room = { x=0, y=0 } | |
objects = {} | |
types = {} | |
freeze=0 | |
shake=0 | |
will_restart=false | |
delay_restart=0 | |
got_fruit={} | |
has_dashed=false | |
sfx_timer=0 | |
has_key=false | |
pause_player=false | |
flash_bg=false | |
music_timer=0 | |
k_left=0 | |
k_right=1 | |
k_up=2 | |
k_down=3 | |
k_jump=4 | |
k_dash=5 | |
-- entry point -- | |
----------------- | |
function _init() | |
title_screen() | |
end | |
function title_screen() | |
got_fruit = {} | |
for i=0,29 do | |
add(got_fruit,false) end | |
frames=0 | |
deaths=0 | |
max_djump=1 | |
start_game=false | |
start_game_flash=0 | |
music(40,0,7) | |
load_room(7,3) | |
end | |
function begin_game() | |
frames=0 | |
seconds=0 | |
minutes=0 | |
music_timer=0 | |
start_game=false | |
music(0,0,7) | |
load_room(0,0) | |
end | |
function level_index() | |
return room.x%8+room.y*8 | |
end | |
function is_title() | |
return level_index()==31 | |
end | |
-- effects -- | |
------------- | |
clouds = {} | |
for i=0,16 do | |
add(clouds,{ | |
x=rnd(128), | |
y=rnd(128), | |
spd=1+rnd(4), | |
w=32+rnd(32) | |
}) | |
end | |
particles = {} | |
for i=0,24 do | |
add(particles,{ | |
x=rnd(128), | |
y=rnd(128), | |
s=0+flr(rnd(5)/4), | |
spd=0.25+rnd(5), | |
off=rnd(1), | |
c=6+flr(0.5+rnd(1)) | |
}) | |
end | |
dead_particles = {} | |
-- player entity -- | |
------------------- | |
player = | |
{ | |
init=function(this) | |
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, | |
update=function(this) | |
if (pause_player) return | |
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-=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-=1 | |
end | |
this.dash_effect_time -=1 | |
if this.dash_time > 0 then | |
init_object(smoke,this.x,this.y) | |
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*=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=(this.is_solid(-3,0) and -1 or this.is_solid(3,0) and 1 or 0) | |
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-=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*=.75 | |
end | |
if this.spd.y!=0 then | |
this.dash_accel.x*=0.70710678118 | |
end | |
if this.spd.x!=0 then | |
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+=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, --<end update loop | |
draw=function(this) | |
-- 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(this.spr,this.x,this.y,1,1,this.flip.x,this.flip.y) | |
unset_hair_color() | |
end | |
} | |
psfx=function(num) | |
if sfx_timer<=0 then | |
sfx(num) | |
end | |
end | |
create_hair=function(obj) | |
obj.hair={} | |
for i=0,4 do | |
add(obj.hair,{x=obj.x,y=obj.y,size=max(1,min(2,3-i))}) | |
end | |
end | |
set_hair_color=function(djump) | |
pal(8,(djump==1 and 8 or djump==2 and (7+flr((frames/3)%2)*4) or 12)) | |
end | |
draw_hair=function(obj,facing) | |
local last={x=obj.x+4-facing*2,y=obj.y+(btn(k_down) and 4 or 3)} | |
foreach(obj.hair,function(h) | |
h.x+=(last.x-h.x)/1.5 | |
h.y+=(last.y+0.5-h.y)/1.5 | |
circfill(h.x,h.y,h.size,8) | |
last=h | |
end) | |
end | |
unset_hair_color=function() | |
pal(8,8) | |
end | |
player_spawn = { | |
tile=1, | |
init=function(this) | |
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, | |
update=function(this) | |
-- 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+=0.5 | |
if this.spd.y>0 and this.delay>0 then | |
this.spd.y=0 | |
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-=1 | |
this.spr=6 | |
if this.delay<0 then | |
destroy_object(this) | |
init_object(player,this.x,this.y) | |
end | |
end | |
end, | |
draw=function(this) | |
set_hair_color(max_djump) | |
draw_hair(this,1) | |
spr(this.spr,this.x,this.y,1,1,this.flip.x,this.flip.y) | |
unset_hair_color() | |
end | |
} | |
add(types,player_spawn) | |
spring = { | |
tile=18, | |
init=function(this) | |
this.hide_in=0 | |
this.hide_for=0 | |
end, | |
update=function(this) | |
if this.hide_for>0 then | |
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 ~=nil and hit.spd.y>=0 then | |
this.spr=19 | |
hit.y=this.y-4 | |
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~=nil then | |
break_fall_floor(below) | |
end | |
psfx(8) | |
end | |
elseif this.delay>0 then | |
this.delay-=1 | |
if this.delay<=0 then | |
this.spr=18 | |
end | |
end | |
-- begin hiding | |
if this.hide_in>0 then | |
this.hide_in-=1 | |
if this.hide_in<=0 then | |
this.hide_for=60 | |
this.spr=0 | |
end | |
end | |
end | |
} | |
add(types,spring) | |
function break_spring(obj) | |
obj.hide_in=15 | |
end | |
balloon = { | |
tile=22, | |
init=function(this) | |
this.offset=rnd(1) | |
this.start=this.y | |
this.timer=0 | |
this.hitbox={x=-1,y=-1,w=10,h=10} | |
end, | |
update=function(this) | |
if this.spr==22 then | |
this.offset+=0.01 | |
this.y=this.start+sin(this.offset)*2 | |
local hit = this.collide(player,0,0) | |
if hit~=nil 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-=1 | |
else | |
psfx(7) | |
init_object(smoke,this.x,this.y) | |
this.spr=22 | |
end | |
end, | |
draw=function(this) | |
if this.spr==22 then | |
spr(13+(this.offset*8)%3,this.x,this.y+6) | |
spr(this.spr,this.x,this.y) | |
end | |
end | |
} | |
add(types,balloon) | |
fall_floor = { | |
tile=23, | |
init=function(this) | |
this.state=0 | |
this.solid=true | |
end, | |
update=function(this) | |
-- 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-=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-=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, | |
draw=function(this) | |
if this.state!=2 then | |
if this.state!=1 then | |
spr(23,this.x,this.y) | |
else | |
spr(23+(15-this.delay)/5,this.x,this.y) | |
end | |
end | |
end | |
} | |
add(types,fall_floor) | |
function break_fall_floor(obj) | |
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~=nil then | |
break_spring(hit) | |
end | |
end | |
end | |
smoke={ | |
init=function(this) | |
this.spr=29 | |
this.spd.y=-0.1 | |
this.spd.x=0.3+rnd(0.2) | |
this.x+=-1+rnd(2) | |
this.y+=-1+rnd(2) | |
this.flip.x=maybe() | |
this.flip.y=maybe() | |
this.solids=false | |
end, | |
update=function(this) | |
this.spr+=0.2 | |
if this.spr>=32 then | |
destroy_object(this) | |
end | |
end | |
} | |
fruit={ | |
tile=26, | |
if_not_fruit=true, | |
init=function(this) | |
this.start=this.y | |
this.off=0 | |
end, | |
update=function(this) | |
local hit=this.collide(player,0,0) | |
if hit~=nil 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+=1 | |
this.y=this.start+sin(this.off/40)*2.5 | |
end | |
} | |
add(types,fruit) | |
fly_fruit={ | |
tile=28, | |
if_not_fruit=true, | |
init=function(this) | |
this.start=this.y | |
this.fly=false | |
this.step=0.5 | |
this.solids=false | |
this.sfx_delay=8 | |
end, | |
update=function(this) | |
--fly away | |
if this.fly then | |
if this.sfx_delay>0 then | |
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+=0.05 | |
this.spd.y=sin(this.step)*0.5 | |
end | |
-- collect | |
local hit=this.collide(player,0,0) | |
if hit~=nil 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, | |
draw=function(this) | |
local off=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(45+off,this.x-6,this.y-2,1,1,true,false) | |
spr(this.spr,this.x,this.y) | |
spr(45+off,this.x+6,this.y-2) | |
end | |
} | |
add(types,fly_fruit) | |
lifeup = { | |
init=function(this) | |
this.spd.y=-0.25 | |
this.duration=30 | |
this.x-=2 | |
this.y-=4 | |
this.flash=0 | |
this.solids=false | |
end, | |
update=function(this) | |
this.duration-=1 | |
if this.duration<= 0 then | |
destroy_object(this) | |
end | |
end, | |
draw=function(this) | |
this.flash+=0.5 | |
print("1000",this.x-2,this.y,7+this.flash%2) | |
end | |
} | |
fake_wall = { | |
tile=64, | |
if_not_fruit=true, | |
update=function(this) | |
this.hitbox={x=-1,y=-1,w=18,h=18} | |
local hit = this.collide(player,0,0) | |
if hit~=nil 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, | |
draw=function(this) | |
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 | |
} | |
add(types,fake_wall) | |
key={ | |
tile=8, | |
if_not_fruit=true, | |
update=function(this) | |
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 | |
} | |
add(types,key) | |
chest={ | |
tile=20, | |
if_not_fruit=true, | |
init=function(this) | |
this.x-=4 | |
this.start=this.x | |
this.timer=20 | |
end, | |
update=function(this) | |
if has_key then | |
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 | |
} | |
add(types,chest) | |
platform={ | |
init=function(this) | |
this.x-=4 | |
this.solids=false | |
this.hitbox.w=16 | |
this.last=this.x | |
end, | |
update=function(this) | |
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~=nil then | |
hit.move_x(this.x-this.last,1) | |
end | |
end | |
this.last=this.x | |
end, | |
draw=function(this) | |
spr(11,this.x,this.y-1) | |
spr(12,this.x+8,this.y-1) | |
end | |
} | |
message={ | |
tile=86, | |
last=0, | |
draw=function(this) | |
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+=0.5 | |
if this.index>=this.last+1 then | |
this.last+=1 | |
sfx(35) | |
end | |
end | |
this.off={x=8,y=96} | |
for i=1,this.index do | |
if sub(this.text,i,i)~="#" then | |
rectfill(this.off.x-2,this.off.y-2,this.off.x+7,this.off.y+6 ,7) | |
print(sub(this.text,i,i),this.off.x,this.off.y,0) | |
this.off.x+=5 | |
else | |
this.off.x=8 | |
this.off.y+=7 | |
end | |
end | |
else | |
this.index=0 | |
this.last=0 | |
end | |
end | |
} | |
add(types,message) | |
big_chest={ | |
tile=96, | |
init=function(this) | |
this.state=0 | |
this.hitbox.w=16 | |
end, | |
draw=function(this) | |
if this.state==0 then | |
local hit=this.collide(player,0,8) | |
if hit~=nil 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-=1 | |
shake=5 | |
flash_bg=true | |
if this.timer<=45 and count(this.particles)<50 then | |
add(this.particles,{ | |
x=1+rnd(14), | |
y=0, | |
h=32+rnd(32), | |
spd=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 | |
foreach(this.particles,function(p) | |
p.y+=p.spd | |
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 | |
} | |
add(types,big_chest) | |
orb={ | |
init=function(this) | |
this.spd.y=-4 | |
this.solids=false | |
this.particles={} | |
end, | |
draw=function(this) | |
this.spd.y=appr(this.spd.y,0,0.5) | |
local hit=this.collide(player,0,0) | |
if this.spd.y==0 and hit~=nil 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 | |
} | |
flag = { | |
tile=118, | |
init=function(this) | |
this.x+=5 | |
this.score=0 | |
this.show=false | |
for i=1,count(got_fruit) do | |
if got_fruit[i] then | |
this.score+=1 | |
end | |
end | |
end, | |
draw=function(this) | |
this.spr=118+(frames/5)%3 | |
spr(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 | |
} | |
add(types,flag) | |
room_title = { | |
init=function(this) | |
this.delay=5 | |
end, | |
draw=function(this) | |
this.delay-=1 | |
if this.delay<-30 then | |
destroy_object(this) | |
elseif this.delay<0 then | |
rectfill(24,58,104,70,0) | |
--rect(26,64-10,102,64+10,7) | |
--print("---",31,64-2,13) | |
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 | |
--print("---",86,64-2,13) | |
draw_time(4,4) | |
end | |
end | |
} | |
-- object functions -- | |
----------------------- | |
function init_object(type,x,y) | |
if type.if_not_fruit~=nil and got_fruit[1+level_index()] then | |
return | |
end | |
local obj = {} | |
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} | |
obj.is_solid=function(ox,oy) | |
if oy>0 and not obj.check(platform,ox,0) and obj.check(platform,ox,oy) then | |
return true | |
end | |
return solid_at(obj.x+obj.hitbox.x+ox,obj.y+obj.hitbox.y+oy,obj.hitbox.w,obj.hitbox.h) | |
or obj.check(fall_floor,ox,oy) | |
or obj.check(fake_wall,ox,oy) | |
end | |
obj.is_ice=function(ox,oy) | |
return ice_at(obj.x+obj.hitbox.x+ox,obj.y+obj.hitbox.y+oy,obj.hitbox.w,obj.hitbox.h) | |
end | |
obj.collide=function(type,ox,oy) | |
local other | |
for i=1,count(objects) do | |
other=objects[i] | |
if other ~=nil and other.type == type and other != obj and other.collideable and | |
other.x+other.hitbox.x+other.hitbox.w > obj.x+obj.hitbox.x+ox and | |
other.y+other.hitbox.y+other.hitbox.h > obj.y+obj.hitbox.y+oy and | |
other.x+other.hitbox.x < obj.x+obj.hitbox.x+obj.hitbox.w+ox and | |
other.y+other.hitbox.y < obj.y+obj.hitbox.y+obj.hitbox.h+oy then | |
return other | |
end | |
end | |
return nil | |
end | |
obj.check=function(type,ox,oy) | |
return obj.collide(type,ox,oy) ~=nil | |
end | |
obj.move=function(ox,oy) | |
local amount | |
-- [x] get move amount | |
obj.rem.x += ox | |
amount = flr(obj.rem.x + 0.5) | |
obj.rem.x -= amount | |
obj.move_x(amount,0) | |
-- [y] get move amount | |
obj.rem.y += oy | |
amount = flr(obj.rem.y + 0.5) | |
obj.rem.y -= amount | |
obj.move_y(amount) | |
end | |
obj.move_x=function(amount,start) | |
if obj.solids then | |
local step = sign(amount) | |
for i=start,abs(amount) do | |
if not obj.is_solid(step,0) then | |
obj.x += step | |
else | |
obj.spd.x = 0 | |
obj.rem.x = 0 | |
break | |
end | |
end | |
else | |
obj.x += amount | |
end | |
end | |
obj.move_y=function(amount) | |
if obj.solids then | |
local step = sign(amount) | |
for i=0,abs(amount) do | |
if not obj.is_solid(0,step) then | |
obj.y += step | |
else | |
obj.spd.y = 0 | |
obj.rem.y = 0 | |
break | |
end | |
end | |
else | |
obj.y += amount | |
end | |
end | |
add(objects,obj) | |
if obj.type.init~=nil then | |
obj.type.init(obj) | |
end | |
return obj | |
end | |
function destroy_object(obj) | |
del(objects,obj) | |
end | |
function kill_player(obj) | |
sfx_timer=12 | |
sfx(0) | |
deaths+=1 | |
shake=10 | |
destroy_object(obj) | |
dead_particles={} | |
for dir=0,7 do | |
local angle=(dir/8) | |
add(dead_particles,{ | |
x=obj.x+4, | |
y=obj.y+4, | |
t=10, | |
spd={ | |
x=sin(angle)*3, | |
y=cos(angle)*3 | |
} | |
}) | |
restart_room() | |
end | |
end | |
-- room functions -- | |
-------------------- | |
function restart_room() | |
will_restart=true | |
delay_restart=15 | |
end | |
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 | |
function load_room(x,y) | |
has_dashed=false | |
has_key=false | |
--remove existing objects | |
foreach(objects,destroy_object) | |
--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 | |
foreach(types, | |
function(type) | |
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 | |
-- update function -- | |
----------------------- | |
function _update() | |
frames=((frames+1)%30) | |
if frames==0 and level_index()<30 then | |
seconds=((seconds+1)%60) | |
if seconds==0 then | |
minutes+=1 | |
end | |
end | |
if music_timer>0 then | |
music_timer-=1 | |
if music_timer<=0 then | |
music(10,0,7) | |
end | |
end | |
if sfx_timer>0 then | |
sfx_timer-=1 | |
end | |
-- cancel if freeze | |
if freeze>0 then freeze-=1 return end | |
-- screenshake | |
if shake>0 then | |
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-=1 | |
if delay_restart<=0 then | |
will_restart=false | |
load_room(room.x,room.y) | |
end | |
end | |
-- update each object | |
foreach(objects,function(obj) | |
obj.move(obj.spd.x,obj.spd.y) | |
if obj.type.update~=nil 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-=1 | |
if start_game_flash<=-30 then | |
begin_game() | |
end | |
end | |
end | |
end | |
-- drawing functions -- | |
----------------------- | |
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~=nil then | |
bg_col=2 | |
end | |
rectfill(0,0,128,128,bg_col) | |
-- clouds | |
if not is_title() then | |
foreach(clouds, function(c) | |
c.x += c.spd | |
rectfill(c.x,c.y,c.x+c.w,c.y+4+(1-c.w/64)*12,new_bg~=nil 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 | |
foreach(objects, function(o) | |
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 | |
foreach(objects, function(o) | |
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 | |
foreach(particles, function(p) | |
p.x += p.spd | |
p.y += sin(p.off) | |
p.off+= min(0.05,p.spd/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 | |
foreach(dead_particles, function(p) | |
p.x += p.spd.x | |
p.y += p.spd.y | |
p.t -=1 | |
if p.t <= 0 then del(dead_particles,p) end | |
rectfill(p.x-p.t/5,p.y-p.t/5,p.x+p.t/5,p.y+p.t/5,14+p.t%2) | |
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,count(objects) do | |
if objects[i].type==player then | |
p = objects[i] | |
break | |
end | |
end | |
if p~=nil 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 | |
function draw_object(obj) | |
if obj.type.draw ~=nil then | |
obj.type.draw(obj) | |
elseif obj.spr > 0 then | |
spr(obj.spr,obj.x,obj.y,1,1,obj.flip.x,obj.flip.y) | |
end | |
end | |
function draw_time(x,y) | |
local s=seconds | |
local m=minutes%60 | |
local h=flr(minutes/60) | |
rectfill(x,y,x+32,y+6,0) | |
print((h<10 and "0"..h or h)..":"..(m<10 and "0"..m or m)..":"..(s<10 and "0"..s or s),x+1,y+1,7) | |
end | |
-- helper functions -- | |
---------------------- | |
function clamp(val,a,b) | |
return max(a, min(b, val)) | |
end | |
function appr(val,target,amount) | |
return val > target | |
and max(val - amount, target) | |
or min(val + amount, target) | |
end | |
function sign(v) | |
return v>0 and 1 or | |
v<0 and -1 or 0 | |
end | |
function maybe() | |
return rnd(1)<0.5 | |
end | |
function solid_at(x,y,w,h) | |
return tile_flag_at(x,y,w,h,0) | |
end | |
function ice_at(x,y,w,h) | |
return tile_flag_at(x,y,w,h,4) | |
end | |
function tile_flag_at(x,y,w,h,flag) | |
for i=max(0,flr(x/8)),min(15,(x+w-1)/8) do | |
for j=max(0,flr(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 | |
function tile_at(x,y) | |
return mget(room.x * 16 + x, room.y * 16 + y) | |
end | |
function spikes_at(x,y,w,h,xspd,yspd) | |
for i=max(0,flr(x/8)),min(15,(x+w-1)/8) do | |
for j=max(0,flr(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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment