mmbshmup/updatedshmup.p8

2219 lines
62 KiB
Plaintext
Raw Normal View History

2023-09-13 05:49:01 +00:00
pico-8 cartridge // http://www.pico-8.com
version 41
__lua__
-- generic shmup
-- by kistaro windrider
-- dev docs in tab 8
game = 1
win = 2
lose = 3
debug = {}
function usplit(str)
return unpack(split(str))
end
function csv(s)
local ret=split(s,"\n")
for i,v in ipairs(ret) do
ret[i] = type(v) == "string" and split(v) or {v} end
return ret
end
-- generate standard "overlay"
-- constructor for type tt.
-- if more is defined, generated
-- new calls more(ret) after
-- ret is definitely not nil
-- before calling setmetatable.
-- use to initialize mutables.
function mknew(tt, more)
local mt = {__index=tt}
-- check "more" only once ever
if more then
tt.new = function(ret)
if (not ret) ret = {}
more(ret)
setmetatable(ret, mt)
return ret
end
else
tt.new=function(ret)
if (not ret) ret = {}
setmetatable(ret, mt)
return ret
end
end
end
linked_list = {}
mknew(linked_list, function(x)
x.next=nil
x.tail=x
end)
function linked_list:push_back(x)
self.tail.next = x
self.tail = x
end
function linked_list:push_front(x)
if (not self.next) self.tail = x
x.next = self.next
self.next = x
end
-- vore eats another linked list
-- by appending its contents.
-- the ingested linked is empty.
function linked_list:vore(x)
if (not x.next) return
self.tail.next = x.next
self.tail = x.tail
x.next = nil
x.tail = x
end
-- strip calls f(x) for each
-- node, removing each node for
-- which f(x) returns true. it
-- returns the new tail; nil
-- if the list is now empty.
function linked_list:strip(f)
local p, n = self, self.next
while n do
if f(n) then
p.next = n.next
else
p = n
end
n = n.next
end
self.tail = p
return p
end
-- optimized special case -
-- could be done with strip but
-- this avoids extra function
-- calls and comparisions since
-- draw isn't allowed to kill
-- the item
function linked_list:draw()
local n = self.next
while n do
n:draw()
n = n.next
end
end
function linked_list:pop_front()
local ret = self.next
if (not ret) return
self.next = ret.next
if (not ret.next) ret.tail = nil
return ret
end
2023-09-13 05:49:01 +00:00
function _init()
init_bullet_mt()
init_powerup_mt()
init_ship_mt()
wipe_level()
primary_ship.main_gun = new_gun_of(zap_gun_t)
load_level(example_level)
state = game
pal(2,129)
pal()
end
-- health gradients for 1..5 hp
-- exactly, then all "more".
hpcols_lut = csv[[36
34, 136
34, 130, 136
34, 34, 130, 136
34, 34, 130, 130, 136]]
2023-09-13 05:49:01 +00:00
-- call after any change to maxhp
-- configures health gradient
function init_hpcols()
hpcols = hpcols_lut[min(primary_ship.maxhp,6)]
end
function wipe_level()
primary_ship = new_p1()
init_hpcols()
pships = linked_list.new()
pships:push_back(primary_ship)
eships = linked_list.new()
pbullets = linked_list.new()
ebullets = linked_list.new()
intangibles_fg = linked_list.new()
intangibles_bg = linked_list.new()
events = linked_list.new()
new_events = linked_list.new()
2023-09-13 05:49:01 +00:00
end
function _update60()
updategame()
end
function call_f(x)
return x:f()
end
function call_move(x)
return x:move()
end
2023-09-13 05:49:01 +00:00
function updategame()
leveldone = level_frame()
events:vore(new_events)
events:strip(call_move)
for _, lst in ipairs{intangibles_bg, pships, eships, pbullets, ebullets} do
lst:strip(call_move)
end
pships:strip(
function(ps)
local pbox, pded = hurtbox(ps), false
eships:strip(
function(es)
if (not collides(pbox, hurtbox(es))) return
pded = pded or ps:hitship(es)
return es:hitship(ps)
end
)
return pded
end
)
pships:strip(
function(ps)
local pbox, pded = hurtbox(ps), false
ebullets:strip(
function(eb)
if (not collides(pbox, hurtbox(eb))) return
pded = pded or ps:hitbullet(eb)
return eb:hitship(ps)
end
)
return pded
end
)
2023-09-13 05:49:01 +00:00
-- many bullets and many enemy ships;
-- use bucket collider for efficiency
local pbullet_collider = new_collider()
2023-09-30 09:16:24 +00:00
local p, n = pbullets, pbullets.next
while n do
n.prev = p
pbullet_collider:insert(n)
p = n
n = p.next
2023-09-13 05:49:01 +00:00
end
eships:strip(
function(es)
for pb in all(pbullet_collider:get_collisions(es)) do
if pb:hitship(es) then
pbullet_collider:hide(pb)
pb.prev.next = pb.next
if pb.next then
pb.next.prev = pb.prev
else
pbullets.tail = pb.prev
end
end
if (es:hitbullet(pb)) return true
2023-09-13 05:49:01 +00:00
end
end
)
intangibles_fg:strip(call_move)
if leveldone and not eships.next and not ebullets.next and not events.next then
2023-09-13 05:49:01 +00:00
state = win
end
2023-09-30 09:17:19 +00:00
if (not pships.next) state = lose
2023-09-13 05:49:01 +00:00
end
function _draw()
drawgame()
draw_debug()
if (state == game) fadelvl = -45
if (state == win) dropshadow("win",50,61,11)
if (state == lose) dropshadow("fail",48,61,8)
fadescreen()
end
fadetable=csv[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
1,1,129,129,129,129,129,129,129,129,0,0,0,0,0
2,2,2,130,130,130,130,130,128,128,128,128,128,0,0
3,3,3,131,131,131,131,129,129,129,129,129,0,0,0
4,4,132,132,132,132,132,132,130,128,128,128,128,0,0
5,5,133,133,133,133,130,130,128,128,128,128,128,0,0
6,6,134,13,13,13,141,5,5,5,133,130,128,128,0
7,6,6,6,134,134,134,134,5,5,5,133,130,128,0
8,8,136,136,136,136,132,132,132,130,128,128,128,128,0
9,9,9,4,4,4,4,132,132,132,128,128,128,128,0
10,10,138,138,138,4,4,4,132,132,133,128,128,128,0
11,139,139,139,139,3,3,3,3,129,129,129,0,0,0
12,12,12,140,140,140,140,131,131,131,1,129,129,129,0
13,13,141,141,5,5,5,133,133,130,129,129,128,128,0
14,14,14,134,134,141,141,2,2,133,130,130,128,128,0
15,143,143,134,134,134,134,5,5,5,133,133,128,128,0]]
2023-09-13 05:49:01 +00:00
function fadescreen()
fadelvl += 0.25
if fadelvl < 0 then
pal()
return
end
local i = flr(fadelvl)
for c=0,15 do
if i+1>=16 then
pal(c,0,1)
else
pal(c,fadetable[c+1][i+1],1)
end
end
end
function draw_debug()
cursor(0,0,7)
-- uncomment the followingh
-- to display object counts
--[[
print("es "..tostr(#eships))
print("eb "..tostr(#ebullets))
print("pb "..tostr(#pbullets))
print("ib "..tostr(#intangibles_bg))
print("if "..tostr(#intangibles_fg))
print("v "..tostr(#events))
]]
if (#debug==0) return
foreach(debug,print)
debug={}
end
function drawgame()
cls()
clip(0,0,112,128)
for slist in all{intangibles_bg, pbullets, pships, eships, ebullets, intangibles_fg} do
slist:draw()
2023-09-13 05:49:01 +00:00
end
clip(0,0,128,128)
drawhud()
end
powcols=split"170,154,153,148,68"
2023-09-13 05:49:01 +00:00
function drawhud()
-- 112-and-right is hud zone
rectfill(112, 0, 127, 127,0x56)
rect(112,0,127,127,7)
line(127,1,127,127,5)
line(113,127)
draw_gun_info("❎",1,116,3,primary_ship.main_gun)
draw_gun_info("🅾️",2,116,31,primary_ship.special_gun)
dropshadow("pwr",114,59,1)
inset(114,66,125,92)
fillp(0x5a5a)
vertmeter(115,67,124,91,primary_ship.power, primary_ship.max_power, powcols)
2023-09-13 05:49:01 +00:00
dropshadow("h s",114,97,1)
inset(114,104,125,125)
line(119,105,119,124,119)
line(120,105,120,125,85)
vertmeter(115,105,118,124,primary_ship.hp, primary_ship.maxhp, hpcols)
vertmeter(121,105,124,124,primary_ship.shield, primary_ship.maxshield,{204,220,221})
fillp(0)
end
function draw_gun_info(lbl,fgc,x,y,gun)
dropshadow(lbl,x,y,fgc)
inset(114,y+7,125,y+18)
inset(114,y+20,125,y+24)
if(gun) then
spr(gun.icon,116,y+9,1,1)
--115 to 124 - ammo bar. round up
if gun.ammo == nil then
fillp(0xa5a5)
rectfill(115,y+21,124,y+23,0xea)
fillp(0)
elseif gun.ammo > 0 then
rectfill(
115,y+21,
115+flr(9*gun.ammo/gun.maxammo),
y+23,10)
else
line(118, y+22, 121, y+22, 2)
end
end
end
function vertmeter(x0,y0,x1,y1,val,maxval,cols)
if (val <= 0) return
local h = y1-y0
local px = -flr(-(h*val)\maxval)
local ncols = #cols
local firstcol = ((h-px)*ncols\h)+1
local lastbottom = y0+(h*firstcol\ncols)
rectfill(x0, y1-px, x1, lastbottom, cols[firstcol])
for i=firstcol+1,ncols do
local bottom = y0+h*i\ncols
rectfill(x0,lastbottom,x1,bottom,cols[i])
lastbottom = bottom
end
end
function inset(x0,y0,x1,y1)
rectfill(x0,y0,x1,y1,0)
-- use "wide colors" to draw
-- monochrome regardless of
-- fillp
rect(x0,y0,x1,y1,119)
line(x1,y0,x0,y0,85)
line(x0,y1,85)
end
function dropshadow(str, x, y, col)
print(str, x+1, y+1, 5)
print(str, x, y, col)
end
function grab_p1_butts()
if state ~= game then
local r = {0,0,0,0,0}
r[0] = 0
return r
end
local b = btn()
return {
[0]=b&0x1,
[1]=(b&0x2)>>1,
[2]=(b&0x4)>>2,
[3]=(b&0x8)>>3,
[4]=(b&0x10)>>4,
[5]=(b&0x20)>>5
}
end
-->8
--ships, including player
-- XXX BOOKMARK XXX
2023-09-13 05:49:01 +00:00
function init_ship_mt()
setmetatable(player, ship_t)
setmetatable(frownie, ship_t)
setmetatable(blocky, frownie_t)
setmetatable(chasey, ship_t)
end
2023-09-13 06:52:14 +00:00
firespark = split"9, 8, 2, 5, 1"
smokespark = split"13, 13, 5, 5"
2023-09-13 05:49:01 +00:00
player = {
--shape
sprite = 1, --index of ship sprite
size = 1, --all ships are square; how many 8x8 sprites?
hurt = { -- hurtbox - where this ship can be hit
x_off = 3, -- upper left corner
y_off = 2, -- relative to ship ulc
width = 1,
height = 3
},
sparks = firespark, -- see tab 9
sparkodds = 2,
boss = true, -- dramatic special effects
-- health and power
hp = 3, -- current health, non-regenerating
maxhp = 3, -- player only; other ships never heal
shield = 2, -- regenerates, using power
maxshield = 2,
shieldcost = 300, -- power cost to refill shield
generator = 1.5, -- 1 feels too slow
-- gun
main_gun = nil, -- assign at spawn time
special_gun = nil,
fire_off_x = 4, -- offset where bullets come from
fire_off_y = 0,
-- position
x=52, -- x and y are for upper left corner
y=96,
xmomentum = 0,
ymomentum = 0,
maxspd = 2.5, -- momentum cap
thrust = 0.25, -- momentum added from button
drag = 0.125, -- momentum lost per frame
slip = false, -- does not slide down screen
grab_butts = function(self) -- fetch buttons
local butts = grab_p1_butts()
if butts[0] == butts[1] then
self.sprite = 1
elseif butts[0] > 0 then
self.sprite = 17
else
self.sprite = 18
end
return butts
end
}
player_t = {
__index = player
}
function new_p1()
p = {
main_gun = new_gun_of(zap_gun_t, false)
}
setmetatable(p, player_t)
return p
end
frownie = {
--shape
sprite = 3, --index of ship sprite
size = 1, --all ships are square; how many 8x8 sprites?
hurt = { -- hurtbox - where this ship can be hit
x_off = 0, -- upper left corner
y_off = 1, -- relative to ship ulc
width = 8,
height = 6
},
sparks = smokespark,
sparkodds = 8,
-- health and power
hp = 1, -- enemy ships need no max hp
-- position
x=60, -- x and y are for upper left corner
y=8,
xmomentum = 0,
ymomentum = 0,
maxspd = 2, -- momentum cap
thrust = 0.12, -- momentum added from button
drag = 0.07, -- momentum lost per frame
slip = true,
grab_butts = function(discard_self)
-- buttons are effectively analog
-- and negative buttons work just fine!
local butts = {}
local tstate = (1 + flr(4*t() + 0.5)) % 6
butts[0] = ((tstate==1 or tstate==2) and 1) or 0
butts[1] = ((tstate==4 or tstate==5) and 1) or 0
for b=2, 5 do
butts[b]=0
end
return butts
end, -- button fetch algorithm
}
frownie_t = {
__index = frownie
}
blocky = {
sprite = 10,
hp = 2,
hurt = {
x_off = 0,
y_off = 0,
width = 8,
height = 7
},
ow = function(self)
if self.hp <= 1 then
self.sprite = 11
else
self.sprite = 10
end
ship_t.__index.ow(self)
end
}
blocky_t = {
__index = blocky
}
function spawn_spewy_at(x, y)
local spewy = {
x = x,
y = y,
sprite = 26,
power = -20,
hurt = {
x_off = 0,
y_off = 1,
width = 8,
height = 5
},
hp = 1,
maxpower = 70,
generator = 0.5,
main_gun = new_gun_of(protron_gun_t, true),
fire_off_x = 4,
fire_off_y = 7,
grab_butts = function()
local butts = frownie.grab_butts()
butts[5] = 1
return butts
end,
}
setmetatable(spewy, frownie_t)
eships:push_back(spewy)
2023-09-13 05:49:01 +00:00
return spewy
end
chasey = {
sprite = 5,
size = 1,
hurt = {
x_off = 1,
y_off = 2,
width = 6,
height = 5,
},
sparks = smokespark,
sparkodds = 8,
hp = 2,
shield = 1,
maxshield = 1,
shieldcost = 180,
fire_off_x = 4,
fire_off_y = 7,
maxspd = 2,
thrust = 0.2,
drag = 0.075,
slip = true,
grab_butts = function(self)
local butts = {0,0,0,0,0}
butts[0] = 0
if (self.x < primary_ship.x) butts[1] = 1
if (self.x > primary_ship.x) butts[0] = 1
if (self.x - 16 < primary_ship.x and self.x + 16 > primary_ship.x) butts[5] = 1
return butts
end,
}
chasey_t = {
__index = chasey
}
function spawn_chasey_at(x, y)
local c = {
x = x,
y = y,
main_gun = new_gun_of(zap_gun_t, true)
}
setmetatable(c, chasey_t)
eships:push_back(c)
2023-09-13 05:49:01 +00:00
return c
end
function spawn_xl_chasey_at(x, y)
local c = {
x = x,
y = y,
size = 2,
maxspd = 1.25,
hurt = {
x_off = 2,
y_off = 4,
width = 12,
height = 10
},
hp = 20,
shield = 5,
boss = true,
slip = false,
main_gun = new_gun_of(zap_gun_t, true),
grab_butts = function(self)
local butts = chasey.grab_butts(self)
if (self.y < 4) butts[3] = 1
return butts
end,
draw = function(self)
if(self.fx_pal) pal(self.fx_pal)
sspr(40, 0, 8, 8, self.x, self.y, 16, 16)
pal()
end,
}
setmetatable(c, chasey_t)
eships:push_back(c)
2023-09-13 05:49:01 +00:00
return c
end
-->8
--ship behavior
scrollrate = 0.25 --in px/frame
ship_m = {
-- ships have no shield by default
shield = 0,
maxshield = 0,
shieldcost = 32767.9,
shieldcooldown = 180,
-- default generator behavior:
-- 10 seconds for a full charge
max_power = 600,
power = 600,
generator = 1, -- power gen per frame
invincible_until = -32768,
slip = true, -- most enemies slide
xmomentum = 0,
ymomentum = 0,
}
ship_t = {
__index = ship_m,
}
function ship_m:die()
self.dead = true
if (self.hp <= 0) boom(self.x+self.size*4, self.y+self.size*4,12*self.size, self.boss)
end
function ship_m:move()
self:refresh_shield()
self.power = min(self.max_power, self.power + self.generator)
butt = self:grab_butts()
if (butt[5] > 0) self:maybe_shoot(self.main_gun)
if (butt[4] > 0) self:maybe_shoot(self.special_gun)
if (butt[0]-butt[1] ~= 0 or butt[2]-butt[3] ~= 0) spark(self.sparks, self.x + 4*self.size, self.y + 4*self.size, butt, self.thrust, self.sparkodds)
self.xmomentum += (self.thrust * butt[1]) - (self.thrust * butt[0])
self.ymomentum += (self.thrust * butt[3]) - (self.thrust * butt[2])
self.xmomentum = mid(-self.maxspd, self.maxspd, self.xmomentum)
self.ymomentum = mid(-self.maxspd, self.maxspd, self.ymomentum)
2023-09-13 05:49:01 +00:00
self.x += self.xmomentum
self.y += self.ymomentum
2023-09-13 05:49:01 +00:00
if self == primary_self then
self.x = mid(0, 112 - 8 * self.size, self.x)
self.y = mid(0, 128 - 8 * self.size, self.y)
2023-09-13 05:49:01 +00:00
end
--friction
local d = self.drag
self.xmomentum -= mid(d, -d, self.xmomentum)
self.ymomentum -= mid(d, -d, self.ymomentum)
2023-09-13 05:49:01 +00:00
-- "scrolling" behavior
if self.slip then
self.y += scrollrate
if self.y >= 128 then
self:die()
2023-09-13 05:49:01 +00:00
return true
end
end
return false
end
function ship_m:draw()
if(self.fx_pal) pal(self.fx_pal)
spr(self.sprite, self.x, self.y, self.size, self.size)
2023-09-13 05:49:01 +00:00
pal()
end
function hurtbox(ship)
local h = ship.hurt
return {
x=ship.x + h.x_off,
y=ship.y + h.y_off,
width=h.width,
height=h.height
}
end
function ship_m:maybe_shoot(gun)
if (not gun) return
if (self.power < gun.power) return
if (not gun:shoot(self.x + self.fire_off_x, self.y + self.fire_off_y)) return
self.power -= gun.power
end
function ship_m:hitship(other)
return self:hitsomething(1)
end
function ship_m:hitbullet(b)
return self:hitsomething(b.damage)
end
function ship_m:hitsomething(dmg)
if (dmg <= 0) return false
if (lframe < self.invincible_until) return false
self.shield_refresh_ready = lframe + self.shieldcooldown
if self.shield >= dmg then
self.shield -= dmg
self:ow(true)
return false
end
dmg -= self.shield
self.shield = 0
self.hp -= dmg
if self.hp <= 0 then
self:die()
return true
end
self:ow(false)
return false
end
function ship_m:ow(shielded)
if (shielded) then
blip(self,12,3)
return
end
blip(self, 7, 3)
end
function ship_m:refresh_shield()
if (self.shield >= self.maxshield) return
if (lframe < self.shield_refresh_ready) return
if (self.power < self.shieldcost) return
self.shield += 1
self.power -= self.shieldcost
self.shield_refresh_ready = lframe + self.shieldcooldown
end
-->8
-- collisions
-- box: x, y, width, height
function collides(box1, box2)
return not (
box1.x>box2.x+box2.width
or box1.y>box2.y+box2.height
or box1.x+box1.width<box2.x
or box1.y+box1.height<box2.y)
end
collider = { }
collider_t = {
__index = collider
}
function new_collider()
local c = { }
setmetatable(c, collider_t)
c.suppress = { }
return c
end
function collider_indexes(box)
local ret = {}
for x = box.x\8, (box.x+box.width)\8 do
for y = box.y\8, (box.y+box.height)\8 do
add(ret, x+256*y)
end
end
return ret
end
function collider:insert(item)
-- todo: separate "big items" list?
local bdx = collider_indexes(hurtbox(item))
for i in all(bdx) do
local x = self[i]
if not x then
x = {}
self[i] = x
end
add(x, item)
end
end
function collider:hide(item)
self.suppress[item]=true
end
function collider:get_collisions(item)
local found = { }
local seen = { }
local box = hurtbox(item)
local bucket_ids = collider_indexes(box)
for b_idx in all(bucket_ids) do
local bucket = self[b_idx]
if bucket then
for candidate in all(bucket) do
if not (seen[candidate] or self.suppress[candidate]) then
seen[candidate] = true
if (collides(box, hurtbox(candidate))) add(found, candidate)
end
end
end
end
return found
end
-->8
-- level and event system
-- a level is a map from
-- effective frame number to
-- callback for that frame.
-- effective frame number stops
-- when freeze count is nonzero
-- a level is won when it hits
-- the end-of-level sentinel
-- and there are no more
-- tracked enemies.
-- lost when there are no
-- player ships left.
-- effective frame
distance = 0
-- actual frame count since
-- start of level
lframe = 0
-- do not advance distance when
-- nonzero
freeze = 0
eol = {}
function load_level(lvltbl)
distance = 0
lframe = 0
freeze = 0
current_level = {}
-- copy the level so we can
-- modify it dynamically
-- without losing the original
for frame, cb in pairs(lvltbl) do
current_level[frame] = cb
end
leveldone = false
end
function level_frame()
lframe += 1
if (current_level == nil) return true
if freeze == 0 then
distance += 1
local cb = current_level[distance]
if cb ~= nil then
if cb == eol then
current_level = nil
return true
else
cb()
end
end
end
return false
end
-->8
-- example level
function spawn_rnd_x(mt)
s = {
x = rnd(104),
y = -(mt.__index.size * 8 - 1)
}
setmetatable(s, mt)
eships:push_back(s)
2023-09-13 05:49:01 +00:00
return s
end
function spawn_blocking_rnd_x(mt)
freeze += 1
s = {
x = rnd(104),
y = -7,
ice = 1,
die = function(self)
freeze -= self.ice
self.ice = 0
mt.__index.die(self)
end
}
setmetatable(s, mt)
eships:push_back(s)
2023-09-13 05:49:01 +00:00
return s
end
function spawn_frownie()
return spawn_rnd_x(frownie_t)
end
function spawn_blocking_frownie()
spawn_blocking_rnd_x(frownie_t)
end
function spawn_blocky()
spawn_rnd_x(blocky_t)
end
function spawn_blocking_blocky()
spawn_blocking_rnd_x(blocky_t)
end
function spawn_spewy()
return spawn_spewy_at(rnd(104), -7)
end
function spawn_chasey()
return spawn_chasey_at(rnd(104), -7)
end
function spawn_blocking_spewy()
freeze += 1
local s = spawn_spewy()
s.ice = 1
s.die = function(self)
freeze -= self.ice
self.ice = 0
frownie_t.__index.die(self)
end
end
function spawn_bonus_frownie()
local f = spawn_frownie()
f.sprite = 7
f.die = function(self)
spawn_repair_at(self.x+4, self.y+4)
frownie_t.__index.die(self)
end
end
function spawn_bonus_vulcan_chasey()
local c = spawn_chasey()
c.main_gun=new_gun_of(vulcan_gun_t, true)
c.die = function(self)
spawn_main_gun_at(self.x-1, self.y-1, vulcan_gun_t)
chasey.die(self)
end
c.sprite=4
return c
end
helpers = {
spawn_frownie,
spawn_frownie,
spawn_frownie,
spawn_blocky,
spawn_blocky,
spawn_chasey,
spawn_spewy,
}
function spawn_blocking_boss_chasey()
freeze += 1
local c = spawn_xl_chasey_at(44, -15)
c.ice = 1
c.die = function(self)
freeze -= self.ice
self.ice = 0
chasey_t.__index.die(self)
end
local nextspawn = lframe + 120
events:push_back{move=function()
2023-09-13 05:49:01 +00:00
if lframe >= nextspawn then
helpers[flr(rnd(#helpers))+1]()
nextspawn += 60
end
return c.dead
end}
2023-09-13 05:49:01 +00:00
return c
end
example_level = {
[1]=spawn_frownie,
[60]=spawn_bonus_vulcan_chasey,
[61]=spawn_blocky,
[85]=spawn_spewy,
[100]=spawn_spewy,
[115]=spawn_spewy,
[130]=spawn_bonus_frownie,
[145]=spawn_spewy,
[200]=spawn_chasey,
[250]=spawn_blocking_blocky,
[285]=function()
spawn_spec_gun_at(35, -11, blast_gun_t)
end,
[310]=function()
spawn_blocking_blocky()
spawn_blocking_blocky()
spawn_blocking_blocky()
end,
[311]=spawn_frownie,
[350]=function()
spawn_main_gun_at(70, -11, protron_gun_t)
end,
[401]=spawn_frownie,
[420]=spawn_blocking_frownie,
[430]=spawn_bonus_vulcan_chasey,
[450]=spawn_frownie,
[465]=spawn_bonus_frownie,
[480]=spawn_chasey,
[500]=function()
local tnext = lframe
local remain = 20
events:push_back{move=function()
2023-09-13 05:49:01 +00:00
if (lframe < tnext) return false
spawn_blocking_blocky()
tnext = lframe + 12
remain -= 1
return (remain <= 0)
end}
2023-09-13 05:49:01 +00:00
end,
[501]=spawn_bonus_frownie,
[620]=spawn_blocking_blocky,
[700]=spawn_blocking_boss_chasey,
[701]=eol
}
-->8
-- bullets and guns
function init_bullet_mt()
setmetatable(zap, bullet_t)
setmetatable(zap_gun, gun_t)
setmetatable(blast, bullet_t)
setmetatable(blast_gun, gun_t)
setmetatable(protron, bullet_t)
setmetatable(protron_gun, gun_t)
setmetatable(vulcan, bullet_t)
setmetatable(vulcan_gun, gun_t)
end
function new_gun_of(mt, is_enemy)
local g = {
enemy = is_enemy
}
setmetatable(g, mt)
return g
end
zap = {
--shape
psprite = 8, --index of player ammo sprite
esprite = 9, -- index of enemy ammo sprite
width = 1, --in 8x8 blocks
height = 1,
hurt = { -- hurtbox - where this ship can be hit
x_off = 0, -- upper left corner
y_off = 0, -- relative to sprite
width = 2,
height = 8
},
center_x_off = 1, -- how to position by ship
bottom_y_off = 0,
top_y_off = 0,
damage = 1,
dx = 0, -- px/frame
dy = 8,
hitship = function(_, _)
return true
end
}
zap_t = {
__index = zap
}
zap_gun = {
enemy = false,
power = 20, -- power consumed per shot
cooldown = 10, -- frames between shots
ammo = nil, -- unlimited ammo - main gun
t = zap_t -- metatable of bullet to fire
}
zap_gun_t = {
__index = zap_gun
}
blast = {
--shape
psprite = 12, --index of player ammo sprite
esprite = 3, -- index of enemy ammo sprite
width = 1, --in 8x8 blocks
height = 1,
hurt = { -- hurtbox - where this ship can be hit
x_off = 1, -- upper left corner
y_off = 1, -- relative to sprite
width = 6,
height = 6
},
center_x_off = 4, -- how to position by ship
bottom_y_off = 0,
top_y_off = 0,
damage = 4,
dx = 0, -- px/frame
dy = 2,
awaitcancel = false,
2023-09-13 05:49:01 +00:00
-- disable damage for 2 frames
-- when hitting something
hitship = function(self, _)
if self.damage > 0 and not self.awaitcancel then
self.awaitcancel = true
events:push_back{move = function()
new_events:push_back{
wait = 2,
obj = self,
saved_dmg = self.damage,
move = function(self)
self.wait -= 1
if self.wait <= 0 then
self.obj.damage = self.saved_dmg
return true
end
end,
}
self.damage = 0
self.awaitcancel = false
2023-09-13 05:49:01 +00:00
return true
end}
end
2023-09-13 05:49:01 +00:00
end
}
blast_t = {
__index = blast
}
blast_gun = {
icon = 13,
enemy = false,
power = 0, -- ammo, not power
cooldown = 30, -- frames between shots
ammo = 5,
maxammo = 5,
t = blast_t -- metatable of bullet to fire
}
blast_gun_t = {
__index = blast_gun
}
protron = {
--shape
psprite = 23, --index of player ammo sprite
esprite = 24, -- index of enemy ammo sprite
width = 1, --in 8x8 blocks
height = 1,
hurt = { -- hurtbox - where this ship can be hit
x_off = 1, -- upper left corner
y_off = 1, -- relative to sprite
width = 2,
height = 2
},
center_x_off = 1, -- how to position by ship
bottom_y_off = 4,
top_y_off = 0,
damage = 1,
dx = 0, -- px/frame
dy = 3,
}
protron_t = {
__index = protron
}
protron_gun = {
icon = 25,
enemy = false,
power = 35,
cooldown = 15, -- frames between shots
ammo = nil,
maxammo = nil,
actually_shoot = function(self, x, y)
local sprite = protron.psprite
if (self.enemy) sprite=protron.esprite
for i=1,3 do
local b = {
enemy=self.enemy,
sprite=sprite,
dx = i,
dy = 4-i
}
setmetatable(b, protron_t)
b:spawn_at(x,y)
local b2 = {
enemy=self.enemy,
sprite=sprite,
dx = -i,
dy = 4-i
}
setmetatable(b2, protron_t)
b2:spawn_at(x,y)
end
local bup = {
enemy=self.enemy,
sprite=sprite,
dy=4
}
setmetatable(bup, protron_t)
bup:spawn_at(x,y)
end
}
protron_gun_t = {
__index = protron_gun
}
vulcan = {
--shape
psprite = 22, --index of player ammo sprite
esprite = 21, -- index of enemy ammo sprite
width = 1, --in 8x8 blocks
height = 1,
hurt = { -- hurtbox - where this ship can be hit
x_off = 0, -- upper left corner
y_off = 0, -- relative to sprite
width = 1,
height = 4
},
center_x_off = 0.5, -- how to position by ship
bottom_y_off = 4,
top_y_off = 0,
damage = 0.5,
dx = 0, -- px/frame
dy = 4,
}
vulcan_t = {
__index = vulcan
}
vulcan_gun = {
icon = 37,
enemy = false,
power = 8,
cooldown = 2, -- frames between shots
ammo = nil,
maxammo = nil,
dxs = {0.35, -0.35, -0.7, 0.7, 0.35, -0.35},
xoffs = {1, 0, -1, 1, 0, -1},
dxidx = 1,
actually_shoot = function(self, x, y)
local sprite = self.enemy and vulcan.esprite or vulcan.psprite
local b = {
enemy=self.enemy,
sprite=sprite,
dx = self.dxs[self.dxidx],
}
setmetatable(b, vulcan_t)
b:spawn_at(self.xoffs[self.dxidx]+x,y)
self.dxidx += 1
if (self.dxidx > #self.dxs) self.dxidx = 1
end
}
vulcan_gun_t = {
__index = vulcan_gun
}
-->8
-- bullet and gun behaviors
bullet_base = {
enemyspd = 0.5
}
bullet_t = {
__index = bullet_base
}
gun_base = {
shoot_ready = -32768,
icon = 20
}
gun_t = {
__index = gun_base
}
function bullet_base:hitship(_)
self:die()
return true
end
function bullet_base:die()
end
function bullet_base:move()
self.x += self.dx
if self.enemy then
self.y += self.dy
if self.y > 128 then
self:die()
return true
end
else
self.y -= self.dy
if self.y < -8*self.height then
self:die()
return true
end
end
return false
end
function bullet_base:draw()
spr(self.sprite, self.x, self.y, self.width, self.height)
end
function bullet_base:spawn_at(x, y)
self.x = x - self.center_x_off
if self.enemy then
self.dx *= self.enemyspd
self.dy *= self.enemyspd
self.y = y + self.top_y_off
ebullets:push_back(self)
2023-09-13 05:49:01 +00:00
else
self.y = y - (8 * self.height) + self.bottom_y_off
pbullets:push_back(self)
2023-09-13 05:49:01 +00:00
end
end
function gun_base:shoot(x, y)
if (lframe < self.shoot_ready) return false
if self.ammo then
if (self.ammo <= 0) return false
self.ammo -= 1
end
self.shoot_ready = lframe + self.cooldown
self:actually_shoot(x, y)
return true
end
function gun_base:actually_shoot(x, y)
b = { }
setmetatable(b, self.t)
if self.enemy then
b.enemy = true
b.sprite = b.esprite
else
b.enemy = false
b.sprite = b.psprite
end
b:spawn_at(x, y)
return true
end
-->8
-- readme.md
--[[
main loop sequence
==================
1. level_frame
2. events
3. merge new_events into events
4. update bg intangibles
5. move ships (player first)
6. move bullets (player first)
7. calculate collisions
1. pship on eship
2. ebullet on pship
3. pbullet on eship
8. update fg intangibles
9. check for end of level
draw order
----------
bottom to top:
1. intangibles_bg
2. player bullets
3. player ships
4. enemy ships
5. enemy bullets
6. intangibles_fg
notes
-----
intangibles_fg move()s after
all collisions and other moves
are processed. if an intangible
is added to the list as a result
of a collision or move, it will
itself be move()d before it is
drawn.
don't modify a list while it's
being iterated. ships that want
to create ships and bullets
that want to create bullets
need to create an event to
do this (which will run before
the ship and bullet phases of
the next frame). events that
want to create events add to
new_events, rather than events.
data-driven items
=================
guns and bullets both allow the most
common behaviors to be
expressed with data alone.
ships only need a movement
algorithm expressed.
guns
----
* power - cost in generator
power to fire. may be 0.
field directly read by ships;
required in all guns.
* t - metatable for bullet type.
fired once in the bullet's
default direction per shot.
* enemy - if true, fired bullets
are flagged as enemy bullets.
* icon - sprite index of an
8x8 sprite to display in the
hud when the player has this
gun. default is 20, a generic
crosshair bullseye thing.
* cooldown - min frames between
shots.
* ammo, maxammo - permitted
number of shots. 0 is empty
and unfireable. maxammo = 0
will cause a divide by zero
so don't do that. if nil,
ammo is infinite.
default guns manage ammo and
cooldown in shoot, then call
actually_shoot to create the
projectile. override only
actually_shoot to change
projectile logic while keeping
cooldown and ammo logic.
ships manage generator power
before asking the gun to shoot.
this behavior is in
ship_m:maybe_shoot.
bullets
-------
* dx, dy - movement per frame.
player bullets use -dy
instead.
* enemyspd - multiplier for dx
and dy on enemy bullets.
default is 0.5, making enemy
shots much easier to dodge
* damage - damage per hit;
used by ships
* psprite, esprite - index of
player or enemy sprite.
* center_off_x - the horizontal
centerpoint of the bullet,
for positioning when firing.
assume a pixel's coordinates
refer to the upper left corner
of the pixel; the center of
a 2-width bullet with an
upper left corner at 0 is 1,
not 0.5.
* top_off_y, bottom_off_y -
also for positioning when
firing. positive distance from
top or bottom edge to image.
top_off_y will usually be 0,
bottom_off_y will not be when
bullets are smaller than
the sprite box.
* width, height - measured in
full sprites (8x8 boxes), not
pixels. used for drawing.
bullets despawn when above or
below the screen (player or
enemy bullets, respectively).
by default, bullets despawn
when they hit something.
override hitship to change this.
ships
____
ships move by calculating
momentum, then offsetting their
position by that momentum, then
clamping their position to the
screen (horizontally only for
ships that autoscroll). ships
that autoscroll (slip==true)
then slide down by scrollspeed.
fractional coordinates are ok.
after movement, ships lose
momentum (ship.drag along each
axis). abs(momentum) can't
exceed ship.maxspeed.
ships gain momentum by acting
like a player pushing buttons.
the player ship actually reads
buttons for this.
grab_butts - ship thrust control
based on btn() api. returns a
table indexed from 0..5 with
0 to not push this button and 1
to push it. ships can use
fractional or out-of-range
numbers to get varying amounts
of thrust, including using
negative numbers for thrust in
the opposite direction. 4 and 5
just check for nonzeroness to
attempt to fire.
ships hitting another ship take
1 damage per frame of overlap.
ships hitting a bullet check
bullet.damage to find out how
much damage they take. damage
is applied to shields, then hp.
damaged ships flash briefly -
blue (12) if all damage was
shielded, white (7) if hp was
damaged. a ship that then has 0
or less hp calls self:die() and
tells the main game loop to
remove it.
ships have power, from 0 to
ship.maxpower, increasing by
ship.generator per frame.
in maybe_shoot, ships check that
they have power to fire before
trying to fire (the gun itself
checks ammo and cooldown), and
spend that power if they fire.
power is also used to restore
shields - ship.shieldcost per
point of shields. shieldcooldown
is the interval between
restoring shield points, which
is reset when a ship takes
damage (regardless of whether
that damage is stopped by the
shield or not).
therefore:
* damaged ships spend power
repairing shields, which may
affect ability to fire guns.
this looks like a slow firing
rate because the ship will
eventually recover enough
energy to fire.
* a ship firing nonstop will
typically be unable to recover
any shields because it will
not have energy to do so.
ships do not repair hp on their
own. negative-damage bullets
are treated as 0, but a bullet
can choose to repair the ship
it hits in its own hitship
method, or otherwise edit it
(changing weapons, refilling
weapon ammo). powerups are
therefore a kind of bullet.
levels
======
a level is a table mapping
effective frame number to
functions. when a level starts,
it sets lframe ("level frame")
and distance to 0.
every frame, level_frame
increments lframe. then if the
level is not frozen (more on
that later), it increments
distance and runs the function
in the level table for exactly
that frame number (if any).
distance is therefore "nonfrozen
frames", and is used to trigger
level progress. lframe always
increments. ships are encouraged
to use lframe to control
animation and movement, and may
use distance to react to level
progress separately from overall
time.
a special sentinel value, eol,
marks the end of the level.
(the level engine doesn't know
when it's out of events, so
without eol, the level will
simply have no events forever.)
when it finds eol, level_frame
throws away the current level
and tells the main loop that it
might be done. the main loop
agrees the level is over and the
player has won when the level
has reached eol and there are
no more enemy ships, enemy
bullets, or background events
remaining. player ships, player
bullets, and intangibles are
not counted.
level freezing
--------------
the level is frozen when the
global value freeze > 0.
generally, something intending
to block level progress (a
miniboss, a minigame, etc.)
increments freeze and prepares
some means of decrementing it
when it no longer wants to block
level progress.
most commonly, we want to block
until some specific ship or
group of ships has died. for
these ships, override ship:die
to decrement freeze. make sure
to set ship.dead in any new
ship:die method so anything else
looking at it can recognize
the ship as dead.
for anything else, you probably
want an event to figure out when
to unfreeze.
levels start at 1
-----------------
distance is initialized to 0
but gets incremented before the
first time the engine looks for
events. therefore, the first
frame of the level executes
level[1]. since levelframe
executes before anything else,
level[1] sets up the first frame
drawn in the level. the player
does not see a blank world
before level[1] runs.
level[1] can therefore be used
to reconfigure the player ship,
set up backgrounds, start music,
kick off some kind of fade-in
animation, etc.
events
======
the global list "events" stores
0-argument functions which are
called every frame. if they
return true, they are removed
from the list and not run again;
if they return false, they stay
and will be called in later
frames. the level does not end
while the events table is
nonempty.
events are most commonly used
to set up something for later
(for example, blip uses an event
to remove the fx_pallete from
the flashing ship when the blip
expires), but can also be used
to implement a "level within a
level" that does something
complicated until it's done. if
you froze the level when
creating the event, remember
to thaw it (freeze -= 1) on all
paths that return true.
to do complex stuff in events,
use a closure or a metatable
that specifies __call.
to avoid editing the events
list while it is being iterated,
events that create new events
must add those events to
new_events rather than events.
new_events is only valid during
the "event execution" stage, so
events created at any other time
must go directly on events
without using new_events.
intangibles
===========
the intangibles_fg and
intangibles_bg lists contain
items with :move and :draw.
like ships and bullets, they
move during _update60 and
draw during _draw. they are
not checked for collisions.
intangibles_bg moves/draws
before anything else moves or
draws. intangibles_fg
moves/draws last. this controls
whether your intangible object
draws in front of or behind
other stuff. you probably want
intangibles_bg for decorative
elements and intangibles_fg
for explosions, score popups,
etc.
there's no scrolling background
engine but intangibles_bg could
be used to create one, including
using the map (otherwise unused
in this engine) for the purpose.
intangibles do not prevent the
level from ending. like bullets
and ships, if :move returns
true, they are dropped.
weird coding conventions
========================
* i am inconsistent between
including underscores or not
(sorry)
* a triple underscore prefix
is used for fields created
by invasive algorithms
(those algorithms that
modify the items they're
given to perform some kind
of bookkeeping)
]]
-->8
-- standard events
blip_fx = {
cancel=false
}
2023-09-13 05:49:01 +00:00
function blip_fx:move()
if (self.cancel) return true
self.frames -= 1
if self.frames < 0 then
self.obj.fx_pal = nil
return true
2023-09-13 05:49:01 +00:00
end
return false
end
function blip_fx:abort()
self.cancel=true
end
mknew(blip_fx)
2023-09-13 05:49:01 +00:00
function blip(obj, col, frames)
2023-09-13 06:52:14 +00:00
local p = {[0]=0}
obj.fx_pal = p
for i=1,15 do p[i]=col end
2023-09-13 05:49:01 +00:00
if (obj.___fx_pal_event) obj.___fx_pal_event:abort()
events:push_back(blip_fx.new{frames=frames, obj=obj})
2023-09-13 05:49:01 +00:00
end
2023-09-13 06:52:14 +00:00
bossspark = split"7,7,10,10,9,9,9,8,8,8,2,2,5,5"
2023-09-13 05:49:01 +00:00
function boom(x,y,boominess,is_boss)
local sp = firespark
if is_boss then
boominess *= 10
sp = bossspark
end
local boombase = min(0.023 * boominess, 0.25)
local boombonus = min(0.05 * boominess, 1.25)
for _=1,boominess do
local angle = rnd(1)
local butts = {0, sin(angle), 0}
butts[0] = cos(angle)
spark(sp,x+4,y+4,butts,boombase+rnd(boombonus),1, true)
end
return
end
function spark(sprs, x, y, butts, thrust, odds, fg)
if (sprs==nil or flr(rnd(odds)) ~= 0) return
thrust *= 2.5
local target = fg and intangibles_fg or intangibles_bg
target:push_back{
2023-09-13 05:49:01 +00:00
x = x + rnd(4) - 2,
y = y + rnd(4) - 2,
sprs = sprs,
sidx = 1,
dx = (butts[0] - butts[1]) * thrust + rnd(2) - 1,
dy = (butts[2] - butts[3]) * thrust + rnd(2) - 1,
draw = function(self)
pset(self.x, self.y, self.sprs[self.sidx])
end,
move = function(self)
if (rnd(4) < 1) self.sidx += 1
if (self.sidx > #self.sprs) return true
self.x += self.dx
self.y += self.dy
self.dx -= mid(0.05,-0.05, self.dx)
self.dy -= mid(0.05,-0.05, self.dy)
end
}
2023-09-13 05:49:01 +00:00
end
-->8
-- powerups
function init_powerup_mt()
setmetatable(powerup, bullet_t)
setmetatable(gun_swap, powerup_t)
end
powerup = {
-- animated sprite array: "sprites"
-- to draw under or over anim,
-- override draw, draw the
-- under-part, call into
-- powerup.draw(self), then
-- draw the over-part
width = 1,
height = 1,
-- note: make hurtboxes larger
-- than sprite by 2px per side
-- since ship hitbox is tiny
-- but powerups should feel
-- easy to pick up
dx = 0,
dy = 1.5, -- 0.75 after enemyspd
enemy = true, -- collides with player ship
damage = 0,
anim_speed = 2,
loop_pause = 30 -- affected by animspeed
}
-- sprite indexes for "sheen" animation
2023-09-13 07:00:09 +00:00
sheen8x8 = split"2,54,55,56,57,58,59,60,61"
2023-09-13 05:49:01 +00:00
powerup_t = {
__index = powerup
}
-- todo: draw two sprites
-- on top of each other here
-- so all powerups can share
-- the "sheen" animation?
function powerup:draw()
spr(self.sprites[max(1,
(lframe\self.anim_speed)
%(#self.sprites+self.loop_pause)
-self.loop_pause
+1)],
self.x, self.y,
self.width, self.height)
end
function spawn_repair_at(x, y)
local repair = {
hurt = {
x_off = -2,
y_off = -2,
width = 12,
height = 12
},
center_x_off = 4,
top_y_off = 0,
bottom_y_off = 0,
sprites = sheen8x8,
hitship = function(self, ship)
if (ship ~= primary_ship) return false
primary_ship.hp = min(primary_ship.maxhp, primary_ship.hp + 1)
return true
end,
draw = function(self)
spr(53, self.x, self.y, self.width, self.height)
powerup.draw(self)
end
}
setmetatable(repair, powerup_t)
repair:spawn_at(x, y)
end
gun_swap = {
hurt = {
x_off = -2,
y_off = -2,
width = 16,
height = 16
},
-- gun = new_gun_of(t, false)
center_x_off = 6,
top_y_off = 0,
bottom_y_off = 4,
width = 2,
height = 2,
sprites = {64, 66, 68, 70, 72, 74, 76, 78},
hitship = function(self, ship)
if (ship ~= primary_ship) return false
ship.main_gun = self.gun
return true
end,
draw = function(self)
powerup.draw(self)
spr(self.gun.icon, self.x+2, self.y+2, 1, 1)
end
}
gun_swap_t = {
__index=gun_swap
}
function spawn_main_gun_at(x, y, mt)
local gun_p = {
gun = new_gun_of(mt, false)
}
setmetatable(gun_p, gun_swap_t)
gun_p:spawn_at(x, y)
end
spec_gun_pl = {
[1] = 2,
[14] = 6,
[2] = 14
}
function spawn_spec_gun_at(x, y, mt)
local gun_p = {
gun = new_gun_of(mt, false),
hitship = function(self, ship)
if (ship ~= primary_ship) return false
ship.special_gun = self.gun
return true
end,
draw = function(self)
pal(spec_gun_pl)
powerup.draw(self)
pal()
spr(self.gun.icon, self.x+2, self.y+2, 1, 1)
end
}
setmetatable(gun_p, gun_swap_t)
gun_p:spawn_at(x, y)
end
__gfx__
00000000000650000000000000000000bb0b50b59909209200cc0c00000000003b00000082000000e00e8002e00e800200333300002222000000000000000000
00000000006765000000000000cccc00b50b3055920940220c0000c000bbbb0037000000a2000000e0e8880240e8480403bbbb30028888200000000000000000
00700700006d6500000000000cddddd00b33335009444420c00c000c0b333330b7000000a8000000e88e2882e48e24823bbaabb3288aa8820000000000000000
00077000067c665000000000cdd10cd10b3dd350094dd42000c0000cb3350b35b7000000a8000000e88e2882484e24423ba77ab328a77a820000000000000000
00077000067d665000000000cd10cdd100b3350000944200c0000000b350b335b7000000a8000000e88e2882e84e28823ba77ab328a77a820000000000000000
0070070065666765000000000ddddd100b33355009444220c000000c03333350b7000000a800000008888820048488203bbaabb3288aa8820000000000000000
000000006506506500000000001111000b0b5050090920200c0000c00055550037000000a2000000008882000048420003bbbb30028888200000000000000000
00000000650000650000000000000000000b50000009200000c0cc00000000003b00000082000000000820000008200000333300002222000000000000000000
00000000000650000006500000000000b000000b80000000700000000bb0000008800000000000000009200000000000cccccccd000650000000000000000000
0000000000675000000765000000000000bbbb0080000000b0000000b76300008a920000000000009009200200000000c111111d006765000000000000000000
00000000006d6500006d6500000000000b0000b09000000030000000b663000089920000000550009994444200000000c111111d006d65000000000000000000
00000000067c6650067c6650000000000b0bb0b0a000000030000000033000000220000000576d009446544200000000c111111d067c66500000000000000000
00000000067d6650067d6650000000000b0bb0b00000000000000000000000000000000000566d009244442200000000c111111d067d66500000000000000000
000000005666657576667650000000000b0000b000000000000000000000000000000000000dd0009092220200000000c111111d656667650000000000000000
0000000056565066665656500000000000bbbb0000000000000000000000000000000000000000000090020000000000c111111d650650650000000000000000
00000000565000566500065000000000b000000b000000000000000000000000000000000000000000a00a0000000000cddddddd650000650000000000000000
000000000000000000000000000000000000000000a0008000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000090008000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000800a0000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000080090000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000a080000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000009080000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000cccccccc77000000007700000000770000000077000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000c11ee11d70000000077000000007700000000770000000070000000000000000000000000000000000000000
0000000000000000000000000000000000000000c11ee11d00000000770000000077000000007700000000770000000700000000000000000000000000000000
0000000000000000000000000000000000000000ceeeeeed00000000700000000770000000077000000007700000007700000000000000000000000000000000
0000000000000000000000000000000000000000ceeeeeed00000000000000007700000000770000000077000000077000000007000000000000000000000000
0000000000000000000000000000000000000000c11ee11d00000000000000007000000007700000000770000000770000000077000000000000000000000000
0000000000000000000000000000000000000000c11ee11d00000000000000000000000077000000007700000007700000000770000000070000000000000000
0000000000000000000000000000000000000000cddddddd00000000000000000000000070000000077000000077000000007700000000770000000000000000
cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000
c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000ceee2222eeed0000c2221111222d0000
c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c1ee2222ee1d0000ce22111122ed0000c2111111112d0000
c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c11e2222e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000
c1111111111d0000c1111111111d0000c1111ee1111d0000c11ee22ee11d0000c1e221122e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000
c1111111111d0000c1111ee1111d0000c111e22e111d0000c11e2112e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000
c1111111111d0000c1111ee1111d0000c111e22e111d0000c11e2112e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000
c1111111111d0000c1111111111d0000c1111ee1111d0000c11ee22ee11d0000c1e221122e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000
c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c11e2222e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000
c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c1ee2222ee1d0000ce22111122ed0000c2111111112d0000
c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000ceee2222eeed0000c2221111222d0000
cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000
__label__
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007777777777777777
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666666666666665
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666666666666665
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666611111666665
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666115151166665
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666111611156665
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666115161156665
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666611111556665
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666665555566665
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666666666666665
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007655555555555565
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007650b000000b0765
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000765000bbbb000765
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000076500b0000b00765
00000000000000000000000000000000000000000000e00e800200000000000000000000000000000000000000000000000000000000000076500b0bb0b00765
00000000000000000000000050000000000000000000e0e8880200000000000000000000000000000000000000000000000000000000000076500b0bb0b00765
00000000000000000000000000000000000000000000e88e288200000000000000000000000000000000000000000000000000000000000076500b0000b00765
00000000000000000000000000000000000000000d00e88e2882000000000000000000000000000000000000000000000000000000000000765000bbbb000765
00000000000000000000000000000000000000000000e88e28820000000000000000000000000000000000000000000000000000000000007650b000000b0765
00000000000000000000000000000000000000000000088888200000000000000000000000000000000000000000000000000000000000007650000000000765
00000000000000000000000000000000000000000000008882000000000000000000000000000000000000000000000000000000000000007657777777777765
00000000000000000000000000000000000000000000000820000000000000000000000000000000000000000000000000000000000000007666666666666665
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007655555555555565
0000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000765aeaeaeaeae765
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000765eaeaeaeaea765
0000000000000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000765aeaeaeaeae765
00000000000000000000000000000000000000000000000000000009909209200000000000000000000000000000000000000000000000007657777777777765
00000000000000000000000000000000000000000000000000000009209402200000000000000000000000000000000000000000000000007666666666666665
00000000000000000000000000000000000000000000000000000000944442000000000000000000000000000000000000000000000000007666666666666665
0000000000000000000000000000000000000000000000000000000094dd42000000000000000000000000000000000000000000000000007666666666666665
00000000000000000000000000000000000000000000000000000000094420000000000000000000000000000000000000000000000000007666622222666665
00000000000000000000000000000000000000000000000000500000944422000000000000000000000000000000000000000000000000007666225552266665
00000000000000000000000000000000000000000000000000000000909202000000000000000000000000000000000000000000000000007666225262256665
00000000000000000000000000000000000000000000000000000000009200000000000000000000000000000000000000000000000000007666225652256665
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666622222556665
00000000000000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666665555566665
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666666666666665
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007655555555555565
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765
00000000000000000005000000009200000000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765
00000000050000000000000009009200200000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765
000000000000000000d0000009994444200000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765
00000000000000000000000009446544200000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765
00000000000000000000000009244442200000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765
0000000000000000000000000909222020000000000000000bbbb000000000000000000000000000000000000000000000000000000000007650000000000765
000000000000000000000000000900200000000000000000b3333300000000000000000000000000000000000000000000000000000000007650000000000765
000000000000000000000000000a00a0000000000000000b3350b350000000000000000000000000000000000000000000000000000000007650000000000765
00000000000000000000000000000000000000000000000b350b3350000000000000000000000000000000000000000000000000000000007657777777777765
00000000000000000000000000000000000000000000000033333500000000000000000000000000000000000000000000000000000000007666666666666665
00000000000000000000000000000000088000000000000005555000000000000000000000000000000000000000000000000000000000007655555555555565
000000000000000000000000000000058a9200000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765
00000000000000000000000000000000899200000000000000000000008200000000000000000000000000000000000000000000000000007650000000000765
0000000000000000000000000000000002200000000000000000000000a200000000000000000000000000000000000000000000000000007650000000000765
0000000000000000000500000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007657777777777765
0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665
0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665
0000000000000000000000000088000000000050000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665
20000000000000000000000008a9200000000000000000000000000000a200000000000000000000000000000000000000000000000000007611161616111665
20000000000000000000000008992000000000000000000000000000008200000000000000000000000000000000000000000000000000007615151515151565
000000000000000000000000002200000d0000000000000000000000000000000000000000000000000000000000000000000000000000007611151515116565
0000000000000000000000000000000000000e00e800200000000000e00e80020000000000000000000000000000000000000000000000007615551115151665
0000000000000000000000000000000d00000e0e8880200000000000e0e888020000000000000000000000000000000000000000000000007615661115151565
0000000000000000000000000000000000d00e88e2882000000d0000e88e28820000000000000000000000000000000000000000000000007665666555656565
0000088000000000000880000000000000000e88e288200000000000e88e28820000000000000000000000000000000000000000000000007666666666666665
00008a9200000000008a92000000000000000e88e288200000000000e88e28820000000000000000000000000000000000000000000000007655555555555565
0000899200000000008992000000000000000088888200000000000008888820000000000000000000000000000000000000000000000000765aaaaaaaaaa765
0000022000000000000220000000000000000008882000000000000000888200000000000000000000000000000000000000000000000000765aaaaaaaaaa765
0000000000000000000000000000000000000000820000000000000000082000000000000000000000000000000000000000000000000000765aaaaaaaaaa765
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000765aaaaaaaaaa765
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000765a9a9a9a9a9765
00000000000088000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659a9a9a9a9a765
000000000008a920000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000765a9a9a9a9a9765
00000000000899200000000000000000000008800000000000000000000000000000000000000000000000000000000000000000000000007659a9a9a9a9a765
0000000000002200000000000000000000008a92000000000000000000000000000000000000000000000000000000000000000000000000765a9a9a9a9a9765
00000000000000000000000000000000000089920000000000000000000000000000000000000000000000000000000000000000000000007659999999999765
00000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000000000000007659999999999765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659999999999765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659999999999765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659999999999765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654949494949765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659494949494765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654949494949765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659494949494765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654949494949765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654444444444765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654444444444765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654444444444765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654444444444765
00000000000000000000000000000000000000000000000000000000008200000000000000000000000000000000000000000000000000007654444444444765
0000000000000000000000000000000000000000000000000000000000a200000000000000000000000000000000000000000000000000007654444444444765
0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007657777777777765
0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665
0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665
0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665
0000000000000000000000000000000000000000000000000000000000a200000000000000000000000000000000000000000000000000007666666666666665
00000000000000000000000000000000000000000000000000000000008200000000000000000000000000000000000000000000000000007616166666611665
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007615156666165565
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007611156666111665
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007615156666651565
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007615156666116565
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007665656666655665
00000000000000000000000000000000000000000000000000000000000000000008800000000000000000000000000000000000000000007666666666666665
0000000000000000000000000000000000000000000000000000000000000000008a920000000000000000000000000000000000000000007655555555555565
00000000000000000000000000000000000000000000000000000000000000000089920000000000000000000000000000000000000000007652222750000765
00000000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000007652222750000765
00000000000000000000000000000000000000000000000000000000006500000000000000000000000000000000000000000000000000007652222750000765
00000000000000000000000000000000000000000000000000000000067650000000000000000000000000000000000000000000000000007652222750000765
0000000000000000000000000000000000000000000000000000000006d650000000000000000000000000000000000000000000000000007652222750000765
0000000000000000000000000000000000000000000000000000000067c665000000000000000000000000000000000000000000000000007652222750000765
0000000000000000000000000000000000000000000000000000000067d665000000000000000000000000000000000000000000000000007652828750000765
00000000000000000000000000000000000000000000000000000006566676500000000000000000000000000000000000000000000000007658282750000765
00000000000000000000000000000000000000000000000000000006506506500000000000000000000000000000000000000000000000007652828750000765
00000000000000000000000000000000000000000000000000000006500006500000000000000000000000000000000000000000000000007658282750000765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007652828750000765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658282750000765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007657777757777765
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666666666666665
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007555555555555555