mmbshmup/vacuum_gambit.p8
Kistaro Windrider e5b8a30cb6
cooldown reduction prototype
I decided to keep cooldown in the same unit as the frame counter,
because the extra math when calculating an upgrade is going to happen
much less frequently than actual cooldown checks and calculations, so
leaving the upgrade logic as the less efficient path seems like the
more appropriate choice.
2025-05-03 16:28:54 -07:00

2220 lines
64 KiB
Lua

pico-8 cartridge // http://www.pico-8.com
version 42
__lua__
-- vacuum gambit
-- by kistaro windrider
game = 1
win = 2
lose = 3
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
function const_fxn(x)
return function()
return x
end
end
-- generate standard "overlay"
-- constructor for type tt.
-- if tt.init is defined, generated
-- new calls tt.init(ret) after
-- ret is definitely not nil,
-- before calling setmetatable.
-- use to initialize mutables.
--
-- if there was a previous new,
-- it is invoked on the new
-- object *after* more, because
-- this works better with the
-- `more` impls i use.
function mknew(tt)
local mt,oldnew,more = {__index=tt},tt.new,rawget(tt, "init")
tt.new=function(ret)
if(not ret) ret = {}
if(more) more(ret)
if(oldnew) oldnew(ret)
setmetatable(ret, mt)
return ret
end
return tt
end
-- intrusive singly-linked list.
-- cannot be nested!
linked_list = mknew{
is_linked_list=true,
init = 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
function _init()
mode = game_mode
init_blip_pals()
wipe_game() -- redundant?
load_level(example_level_csv)
game_state = game
pal(2,129)
pal()
end
function once_next_frame(f)
new_events:push_back{
move = function()
f()
return true
end,
}
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]]
-- call after any change to maxhp
-- configures health gradient
function init_hpcols()
hpcols = hpcols_lut[min(primary_ship.maxhp,6)]
end
function wipe_game()
xpwhoosh = nil
primary_ship = player.new()
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()
primary_ship.main_gun = zap_gun_p.new()
primary_ship.main_gun:peel()
end
function _update60()
mode:update()
end
function call_f(x)
return x:f()
end
function call_move(x)
return x:move()
end
function updategame()
if (primary_ship.xp >= primary_ship.xptarget) and (lframe - primary_ship.last_xp_frame > 0x0.000f) and (not primary_ship.dead) then
mode = rearm_mode.new()
return _update60()
end
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
)
-- many bullets and many enemy ships;
-- use bucket collider for efficiency
local pbullet_collider = collider.new()
local p, n = pbullets, pbullets.next
while n do
n.prev = p
pbullet_collider:insert(n)
p = n
n = p.next
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
end
end
)
intangibles_fg:strip(call_move)
if leveldone and not eships.next and not ebullets.next and not events.next then
game_state = win
end
if (not pships.next) game_state = lose
if primary_ship.xp >= primary_ship.xptarget then
if not xpwhoosh then
xpwhoosh = 0
else
xpwhoosh += 1
if (xpwhoosh > 60) xpwhoosh = 0
end
else
xpwhoosh = nil
end
end
function _draw()
mode:draw()
end
function drawgame_top()
camera()
fillp(0)
drawgame()
if (game_state == game) fadelvl = -45
if (game_state == win) dropshadow("win",50,61,11)
if (game_state == lose) dropshadow("fail",48,61,8)
fadescreen()
end
game_mode = {
update = updategame,
draw = drawgame_top,
}
fadetable = split"0,1.5,1025.5,1029.5,1285.5,1413.5,9605.5,9637.5,-23130.5,-23066.5,-18970.5,-18954.5,-2570.5,-2568.5,-520.5,-8.5,-0.5"
function fadescreen()
fadelvl += 0.25
if (fadelvl < 1) return
local i = min(flr(fadelvl), #fadetable)
fillp(fadetable[#fadetable+1-i])
rectfill(0,0,128,128,0)
end
-- puke emits a verbose string
-- describing item, indented to
-- the specified depth (0 by
-- default). used for table
-- debugging. table-type keys
-- are not legible here
function puke(item, indent, seen, hidekey)
if (type(item) ~= "table") return tostr(item)
seen = seen or {}
if (seen[item]) return "<<...>>"
seen[item] = true
indent = indent or 0
local pfx = "\n"
for _=1,indent do
pfx ..= " "
end
local xpfx = pfx.." "
if item.is_linked_list then
local ret,n = "linked_list <",0
item:strip(function(x)
n += 1
ret ..= xpfx..tostr(n)..": "..puke(x, indent+2, seen, "next")
end)
return ret..pfx..">"
end
local ret = "{"
for k, v in pairs(item) do
if (k ~= hidekey) ret ..= xpfx..tostr(k)..": "..puke(v, indent+2, seen)
end
return ret..pfx.."}"
end
-- convenience for debugging
function puketh(item, ...)
printh(puke(item), ...)
end
function pukeboard(item)
puketh(item, "@clip")
end
function drawgame()
clip(0,0,112,128)
rectfill(0,0,112,128,0)
for slist in all{intangibles_bg, pbullets, pships, eships, ebullets, intangibles_fg} do
slist:draw()
end
clip(0,0,128,128)
drawhud()
end
powcols=split"170,154,153,148,68"
shlcols = split"204,220,221"
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,1)
draw_gun_info("🅾️",1,116,29,2)
inset(114,57,119,118)
rectfill(119,57,124,58,13)
inset(120,64,125,125)
rectfill(114,124,120,125,7)
print("XP",119,55,1)
print("HP",114,122,1)
fillp(0x5a5a)
if xpwhoosh then
clip(115,58,4,60)
rectfill(115,58,118,117,0xaa)
local voff = 5*xpwhoosh+6
rectfill(115,118-voff,118,117-voff+10,0xbb)
rectfill(115,118-voff+11,118,117-voff+20,0xba)
clip()
else
vertmeter(115,58,118,117,primary_ship.xp, primary_ship.xptarget, powcols)
end
-- 60 px vertically. note that
-- there was at one point an
-- off-by-one and I'm not sure
-- it's actually fixed
local mxs, cs, mxh, ch = primary_ship.maxshield, primary_ship.shield, primary_ship.maxhp, primary_ship.hp
if (mxs > 0) and (mxh > 0) then
local split = 59 * (mxs / (mxs + mxh)) \ 1 + 64
line(121, split, 124, split, 0xba)
vertmeter(121,65,124,split-1,cs, mxs,shlcols)
vertmeter(121,split+1,124,124,ch, mxh, hpcols)
elseif mxs > 0 then
vertmeter(121,65,124,124,cs,mxs,shlcols)
elseif mxh > 0 then
vertmeter(121,65,124,124,ch,mxh,hpcols)
else
print("!", 122, 93, 9)
print("!", 121, 92, 8)
end
fillp(0)
end
function draw_gun_info(lbl,fgc,x,y,gn)
dropshadow(lbl,x,y,fgc)
inset(114,y+7,125,y+18)
inset(114,y+20,125,y+24)
if (not primary_ship.special_guns) return
local gun = primary_ship.special_guns[gn]
if (not gun) return
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
function vertmeter(x0,y0,x1,y1,val,maxval,cols)
if ((val <= 0) or (maxval <= 0)) return
if val < 0x0.001 or maxval < 0x0.001 then
val *= 16
maxval *= 16
end
val=min(val, maxval)
local h = y1-y0
local px = val*h/maxval \ 1
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-1,85)
end
function dropshadow(str, x, y, col)
print(str, x+1, y+1, 5)
print(str, x, y, col)
end
-->8
--ship behavior
scrollrate = 0.25 --in px/frame
ship_m = mknew{
-- ships have no shield by default
shield = 0,
maxshield = 0,
shieldcooldown = 0x0.003c,--1s
shieldpenalty = 0x0.012c, --5s
shield_refresh_ready = 0,
slip = true, -- most enemies slide
xmomentum = 0,
ymomentum = 0,
-- xmin, xmax, ymin, ymax:
-- movement constraints
-- enforced by `constrain`.
xmin = 0, xmax = 104,
-- ymin, ymax default to nil
-- pship needs more constraint
}
function ship_m:die()
self.dead = true
if (self.hp >= 0) return
-- blow up and drop xp
local sz4 = self.size * 4
local cx, cy, xp, z = self.x + sz4, self.y + sz4, self.xp or 0, 0
boom(cx, cy, 3*sz4, self.boss)
if xp > 0x0.01f3 then -- dec 499
-- spawn a huge gem with all
-- overage XP, min 100
spawn_xp_at(cx, cy, 0, xp-0x0.018f)
xp = 0x0.018f -- dec 399
z += 2
end
-- 100, 25, 5, 1
for gsz in all{0x0.0064, 0x0.0019, 0x0.0005, 0x0.0001} do
while xp >= gsz do
spawn_xp_at(cx, cy, z, gsz)
xp -= gsz
z += 2
end
end
end
function ship_m:calc_velocity(v0, t)
v0 = mid(v0 + t, self.maxspd, -self.maxspd)
return v0 - mid(self.drag, -self.drag, v0)
end
function ship_m:brake_dist(v0)
local brake_max = self.thrust + self.drag
local tri_frames = abs(v0\brake_max)
local chunks = tri_frames * (tri_frames - 1) >> 1
local chunk_zone = chunks * brake_max
local overage = abs(v0) - tri_frames * brake_max
return (chunk_zone + overage * (tri_frames + 1)) * sgn(v0), (overage > 0) and tri_frames + 1 or tri_frames
end
function ship_m:constrain(p, dp, pmin, pmax, want)
if (not pmin) return want
local v1, bd, bf, bp
function calc_targets()
-- velocity after move
v1 = self:calc_velocity(dp, want)
-- brake distance and frames
bd, bf = self:brake_dist(v1)
-- brake point
bp = p + bd + v1
end
calc_targets()
if bp < pmin then
-- undershoot. max thrust,
-- then treat as overshoot
-- targeting minimum bound
want, pmax = self.thrust, pmin
calc_targets()
end
if (bp <= pmax) return want
-- spread overshoot across frames
want -= (bp - pmax)/max(bf,1)
return max(want, -self.thrust)
end
function ship_m:move()
self:refresh_shield()
local dx, dy, shoot_spec1, shoot_spec2 = self:act()
dx = self:constrain(self.x, self.xmomentum, self.xmin, self.xmax, dx)
dy = self:constrain(self.y, self.ymomentum, self.ymin, self.ymax, dy)
self:maybe_shoot(self.main_gun)
if (shoot_spec1 and self.special_guns) self:maybe_shoot(self.special_guns[1])
if (shoot_spec2 and self.special_guns) self:maybe_shoot(self.special_guns[2])
if (dx ~= 0 or dy ~= 0) spark(self.sparks, self.x + 4*self.size, self.y + 4*self.size, dx*2.5, dy*2.5, self.sparkodds)
self.xmomentum = self:calc_velocity(self.xmomentum, dx)
self.ymomentum = self:calc_velocity(self.ymomentum, dy)
self.x += self.xmomentum
self.y += self.ymomentum
-- "scrolling" behavior
if self.slip then
self.y += scrollrate
if self.y >= 128 then
self:die()
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)
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
return gun:shoot(self.x + self.fire_off_x, self.y + self.fire_off_y)
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
self.shield_refresh_ready = lframe + self.shieldpenalty
if self.shield > 0 then
self.shield -= dmg
if self.shield > 0 then
blip(self,12)
else
self.shield = 0
blip(self,7)
end
return false
end
self.hp -= dmg
if self.hp < 0 then
self:die()
return true
end
blip(self, self.friendly and 8 or 7)
return false
end
function ship_m:refresh_shield()
if (self.shield >= self.maxshield) return
if (lframe < self.shield_refresh_ready) return
self.shield += 1
self.shield = min(self.shield, self.maxshield)
self.shield_refresh_ready = lframe + self.shieldcooldown
end
-->8
-- bullet and gun behaviors
function player_blt_cat()
return pbullets
end
function enemy_blt_cat()
return ebullets
end
-- x, y: position
-- dx, dy: movement (linear)
-- f: frames remaining; nil for no limit
-- sprite: what sprite to draw
-- hurt -- hurtbox
-- width, height -- in sprites
-- x_off, y_off -- how to
-- initially position relative
-- to firing point. weird
-- details, check impl
-- damage -- damage to do to
-- a ship that gets hit
-- category -- function that
-- returns which bullet list
-- to spawn onto
-- hitship -- event handler,
-- takes ship as argument.
-- default: die, return true.
-- returns whether to delete
-- the bullet
-- die -- on-removal event,
-- default no-op
bullet_base = mknew{ }
gun_base = mknew{
shoot_ready = -32768,
icon = 20,
ammobonus = 1,
-- fractional frames of
-- cooldown reduction from
-- upgrades, not yet applied
cd_remainder = 0,
}
-- gun_base subtypes are
-- level-up options that,
-- as an action, assign
-- themselves to the player
function gun_base:action()
local item = self.new()
item:peel()
item.ammo = item.maxammo
if not primary_ship.special_guns then
primary_ship.special_guns = {item}
else
add(primary_ship.special_guns, item)
end
end
-- make shot type unique so
-- stat modifications do not
-- damage base data
function gun_base:peel()
self.munition = mknew(self.munition.new())
end
-- default firing behavior:
-- single shot
function gun_base:actually_shoot(x, y)
self.munition.new{}:spawn_at(x, y)
end
-- upgrade
function gun_base:small_upgrade_opts()
local ret = {
self:ammo_upgrade_opt(),
self:rate_upgrade_opt(),
}
local s = self.special_upgrade_opt
if (s) add(ret, s(self))
return ret
end
function gun_base:ammo_upgrade_opt()
local a=self.maxammo
local x=a\10+self.ammobonus
return {
icon=self.icon,
hdr=self.hdr,
body=[[----------AMMO
more shots
before you
run out.
is: ]]..tostr(a)..[[
add: ]]..tostr(x)..[[
----------
total: ]]..tostr(a+x),
action=function()
self.maxammo+=x
self.ammo+=x
end,
}
end
function gun_base:rate_upgrade_opt()
local c=self.cooldown<<16
local rawnewc=0.85*(c-self.cd_remainder)
local newc=ceil(rawnewc)
return {
icon=self.icon,
hdr=self.hdr,
body=[[----------RATE
reduce delay
between shots
in frames.
fractions
add up across
upgrades.
is: ]]..tostr(c)..[[
minus: ]]..tostr(c-newc)..[[
----------
total: ]]..tostr(newc),
action=function()
self.cooldown=newc>>16
self.cd_remainder=newc-rawnewc
end,
}
end
function bullet_base:hitship(_)
self:die()
return true
end
function bullet_base:die()
end
function bullet_base:move()
self.x += self.dx
self.y += self.dy
if (self.f) self.f -= 1
if (self.y > 145) or (self.y < -8 * self.height) or (self.f and self.f < 0) then
self:die()
return true
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.x_off
self.y = y - self.y_off
self.category():push_back(self)
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
-->8
-- bullets and guns
zap_e = mknew(bullet_base.new{
--shape
sprite = 9, --index of 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
},
x_off = 1, -- how to position by ship
y_off = 8,
damage = 1,
dx = 0, -- px/frame
dy = 4,
hitship = const_fxn(true),
category = enemy_blt_cat,
})
zap_p = mknew(zap_e.new{
sprite = 8,
dy = -8,
y_off = 0,
category = player_blt_cat,
})
zap_gun_e = mknew(gun_base.new{
cooldown = 0x0.0020, -- frames between shots
munition = zap_e,
})
zap_gun_p = mknew(zap_gun_e.new{
icon = 19,
munition = zap_p,
hdr = "mAIN gUN",
})
blast = mknew(bullet_base.new{
--shape
sprite = 12, --index of player 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
},
x_off = 4, -- how to position by ship
y_off = 0,
damage = 4,
dx = 0, -- px/frame
dy = -1,
awaitcancel = false,
-- disable damage for 4 frames
-- when hitting something
-- todo: rewrite all ship hit
-- logic so i can avoid
-- repeating hits to the
-- same ship instead of
-- using a cooldown
hitship = function(self, _)
if self.damage > 0 and not self.awaitcancel then
self.awaitcancel = true
once_next_frame(function()
new_events:push_back{
wait = 4,
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
end)
end
end,
category=player_blt_cat
})
blast_gun = mknew(gun_base.new{
icon = 13,
cooldown = 0x0.0078, -- 120 frames between shots
ammo = 5,
maxammo = 5,
munition = blast,
hdr = "bLASTER",
body= [[plasma orb
cuts through
enemies.
slow.
ammo: 5
rate: 1/2sec
dmg: 4
]],
})
protron_e = mknew(bullet_base.new{
--shape
sprite = 24,
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
},
x_off = 1, -- how to position by ship
y_off = 4,
damage = 1,
dym = 0.5, -- gun sets dy;
-- this is mult
category = enemy_blt_cat,
})
protron_p = mknew(protron_e.new{
sprite=23,
dym = -1,
y_off = 0,
category=player_blt_cat,
})
protron_gun_e = mknew(gun_base.new{
icon = 25,
cooldown = 0x0.0040, -- frames between shots
ammo = nil,
maxammo = nil,
munition = protron_e
})
function protron_gun_e:actually_shoot(x, y)
local m = self.munition.dym
for i=1,3 do
local b = self.munition.new{
dx = i*m,
dy = (4-i)*m,
}
b:spawn_at(x,y)
local b2 = self.munition.new{
dx = -i*m,
dy = (4-i)*m,
}
b2:spawn_at(x,y)
end
local bup = self.munition.new{
dx=0,
dy=4*m,
}
bup:spawn_at(x,y)
end
protron_gun_p = mknew(protron_gun_e.new{
munition = protron_p,
maxammo = 20,
cooldown = 0x0.0018,
hdr = "pROTRON",
body = [[spray shots
in a dense
arc.
ammo: 20
rate: 2/sec
dmg: 1
]],
})
vulcan_e = mknew(bullet_base.new{
--shape
sprite = 21,
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
},
x_off = 0.5, -- how to position by ship
y_off = 0,
damage = 0.5,
-- dx from gun
dy = 2,
category=enemy_blt_cat
})
vulcan_p = mknew(vulcan_e.new{
sprite=22,
y_off = 4,
dy = -4,
category=player_blt_cat
})
vulcan_gun_e = mknew(gun_base.new{
icon = 37,
enemy = false,
cooldown = 0x0.0003, -- frames between shots
ammo = nil,
maxammo = nil,
munition=vulcan_e,
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 b = self.munition.new{
dx = self.dxs[self.dxidx],
}
b:spawn_at(self.xoffs[self.dxidx]+x,y)
self.dxidx += 1
if (self.dxidx > #self.dxs) self.dxidx = 1
end
})
vulcan_gun_p = mknew(vulcan_gun_e.new{
munition=vulcan_p,
maxammo = 100,
hdr = "vULCAN",
body = [[rapid fire
in a v
shape.
ammo: 100
rate: 20/sec
dmg: 0.5
]],
})
-->8
--ships, including player
firespark = split"9, 8, 2, 5, 1"
smokespark = split"13, 13, 5, 5"
player = mknew(ship_m.new{
friendly=true,
--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
hp = 3, -- current health, non-regenerating
maxhp = 3, -- player only; other ships never heal
shield = 2, -- regenerates
maxshield = 2,
-- xp in increments of 0x0.0001
xp = 0,
xptarget = 0x0.0004,
last_xp_frame = 0,
level = 1,
magnet = 10,
-- gun
main_gun = nil, -- assign at spawn time
special_guns = 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 = 1.5, -- momentum cap
thrust = 0.1875, -- momentum added from button
ymin = 0, ymax = 120, -- stay on screen
drag = 0.0625, -- momentum lost per frame
slip = false, -- does not slide down screen
act = function(self) -- fetch buttons
local b,th = btn(),self.thrust
local blr = b&0x3
if blr == 1 then
self.sprite=17
elseif blr==2 then
self.sprite=18
else
self.sprite=1
end
--dx, dy, shoot_spec, shoot_main
return (((b&0x2)>>1) - (b&0x1)) * th, (((b&0x8)>>3) - ((b&0x4)>>2)) * th, (b&0x10) > 0, (b&0x20) > 0
end,
init = function(p)
p.main_gun = zap_gun_p.new()
-- ONE HIT MODE
--
-- p.hp = 0
-- p.maxhp = 0
-- p.shield = 0
-- p.maxshield = 0
end,
})
function player:small_upgrade_opts()
local cdr, pr = (self.shieldcooldown - 0x0.000f) / 8, (self.shieldpenalty - 0x0.003c) / 9
if (cdr == 0 and self.shieldcooldown > 0x0.000f) cdr = 0x0.0001
if (pr == 0 and self.shieldpenalty > 0x0.003c) pr = 0x0.0001
local ret = {{
icon=53,
hdr="hull",
body=[[ armor
+2 hp]],
action=function()
self.maxhp += 2
self.hp += 2
end,
},{
icon=52,
hdr="shield",
body=[[ capacity
+1 hp]],
action=function()
self.maxshield += 1
self.shield += 1
end,
},{
icon=1,
hdr="thrusters",
body=[[performance
move faster,
steer faster]],
action=function()
--maxspd thrust drag
self.maxspd += 0.5
self.thrust += 0.0625
self.drag += 0.03125
end,
},{
icon=20,
hdr="hull",
body=[[ magnet
pick up xp
from further
away]],
action=function ()
self.magnet += 2
end,
}}
if cdr > 0 then
add(ret, {
icon = 6,
hdr = "shield",
body=[[charge rate
]] .. tostr(ceil(100 * cdr / self.shieldcooldown)) .. "% faster",
action = function()
self.shieldcooldown -= cdr
end
})
end
if pr > 0 then
add(ret, {
icon = 6,
hdr = "shield",
body=[[disruption
]] .. tostr(ceil(100 * pr / self.shieldpenalty)) .. "% shorter",
action = function()
self.shieldpenalty -= pr
end
})
end
return ret
end
frownie = mknew(ship_m.new{
--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,
hp = 0.5, -- enemy ships need no max hp
xp = 0x0.0001,
-- 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,
act = function(self)
local tstate,dx = (1 + flr(4*t() + 0.5)) % 6,0
if (tstate==1 or tstate==2) dx=-self.thrust
if (tstate>=4) dx=self.thrust
return dx,0,false,false
end,
})
blocky = mknew(frownie.new{
sprite = 10,
hp = 1.5,
xp = 0x0.0002,
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_m.ow(self)
end
})
spewy = mknew(frownie.new{
sprite=26,
xp = 0x0.0003,
hurt = {
x_off=0,
y_off=1,
width=8,
height=5
},
hp=0.5,
fire_off_x=4,
fire_off_y=7,
act=function(self)
local dx,dy,shoot_spec=frownie.act(self)
return dx, dy, shoot_spec, self.y > 10
end,
init = function(ship)
ship.main_gun=ship.main_gun or protron_gun_e.new{}
end
})
chasey = mknew(ship_m.new{
sprite = 5,
xp = 0x0.0004,
size = 1,
hurt = {
x_off = 1,
y_off = 2,
width = 6,
height = 5,
},
sparks = smokespark,
sparkodds = 8,
hp = 1.5,
shield = 1,
maxshield = 1,
fire_off_x = 4,
fire_off_y = 7,
maxspd = 2,
thrust = 0.2,
drag = 0.075,
slip = true,
init = function(ship)
ship.main_gun=ship.main_gun or zap_gun_e.new{}
end
})
-- todo: use constraints
function chasey:act()
self.xmin = max(primary_ship.x-8, 0)
self.xmax = min(primary_ship.x + 8, 112 - 8*self.size)
return 0, 0, false, self.y > 10 and self.x - 16 < primary_ship.x and self.x + 16 > primary_ship.x
end
xl_chasey=mknew(chasey.new{
size=2,
xp = 0x0.000a,
maxspd=1.25,
hurt = {
x_off = 2,
y_off = 4,
width = 12,
height = 10
},
fire_off_x = 8,
fire_off_y = 15,
hp = 19.5,
shield = 5,
boss = true,
slip = false,
act = function(self)
local dx,dy,shoot_spec,shoot_main = chasey.act(self)
if (self.y < 4) dy=self.thrust
return dx,dy,shoot_spec,shoot_main
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,
init = function(ship)
ship.main_gun=ship.main_gun or zap_gun_e.new{}
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 = mknew{
init = function(x)
x.suppress = {}
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
-- a list of actions for that
-- frame. an action is a
-- method name and its args.
-- 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 times 0x0.0001
lframe = 0
-- do not advance distance when
-- nonzero
freeze = 0
eol = {}
function load_level(levelfile)
distance = 0
lframe = 0
freeze = 0
leveldone = false
current_level = {}
local found_eol = false
if (type(levelfile)=="string") levelfile = csv(levelfile)
for row in all(levelfile) do
local x = current_level[row[1]]
if row[2] == "eol" then
found_eol = true
assert(x==nil, "events on eol frame")
current_level[row[1]] = eol
else
row.next = x
current_level[row[1]]=row
end
end
assert(found_eol)
end
function level_frame()
lframe += 0x0.0001
if (current_level == nil) return true
if freeze == 0 then
distance += 1
local cbs = current_level[distance]
if cbs ~= nil then
if cbs == eol then
current_level = nil
return true
else
while cbs do
assert(cbs[1] == distance)
local f = _ENV[cbs[2]]
assert(type(f) == "function", cbs[2].." at "..distance.." is not a function")
f(unpack(cbs, 3))
cbs=cbs.next
end
end
end
end
return false
end
-->8
-- example level
function spawn_blocking_rnd_x(typ)
freeze += 1
s = typ.new{
x = rnd(104),
y = -7,
ice = 1,
orig_die = typ.die,
die = function(self)
freeze -= self.ice
self.ice = 0
self:orig_die()
end,
}
eships:push_back(s)
return s
end
function spawn_frownie()
return spawn_rnd(frownie)
end
function spawn_blocking_frownie()
spawn_blocking_rnd_x(frownie)
end
function spawn_blocky()
spawn_rnd(blocky)
end
function spawn_blocking_blocky()
spawn_rnd(blocky, 1)
end
function spawn_spewy()
return spawn_rnd(spewy)
end
function spawn_chasey()
return spawn_rnd(chasey)
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.die(self)
end
end
function spawn_vulcan_chasey()
local c = spawn_chasey()
c.main_gun=vulcan_gun_e.new{enemy=true}
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()
local c = spawn_rnd(xl_chasey, 1)
local nextspawn = lframe + 0x0.0080
events:push_back{move=function()
if lframe >= nextspawn then
helpers[flr(rnd(#helpers))+1]()
nextspawn += 0x0.0040
end
return c.dead
end}
return c
end
function std_spawn(tnm, n, blocking, goodie,altspr)
local typ = _ENV[tnm]
assert(typ and typ.new, tostr(tnm).." not a class")
for i=1,(n or 1) do
spawn_rnd(typ, blocking, goodie,altspr)
end
end
-- blocking: 1 or 0
function spawn_rnd(typ, blocking, goodie,altspr)
blocking = blocking or 0
freeze += blocking
s = typ.new{
x = rnd(104),
y = -(typ.size * 8 - 1),
ice=blocking,
die=function(self)
freeze -= self.ice
self.ice=0
typ.die(self)
end,
}
if (altspr) s.spr = altspr
eships:push_back(s)
return s
end
function multi(times, interval, fnm, ...)
local f,irm,vargs = _ENV[fnm],interval,pack(...)
assert(type(f) == "function", fnm.." not a function")
f(...)
events:push_back{move=function()
irm-=1
if irm <= 0 then
irm=interval
times-=1
f(unpack(vargs))
return times <= 1
end
end}
end
-- then convert sample_level to csv.
-- spawn_spec_gun_at and spawn_main_gun_at will need parsed forms.
-- the boss also needs to be reachable, but one-off is fine.
-- each row of level csv is offset,event,event-args...
-- where offset,eol is a special case.
example_level_csv=[[1,spawn_frownie
60,spawn_vulcan_chasey
61,spawn_blocky
85,spawn_spewy
115,spawn_spewy
130,spawn_frownie
145,spawn_frownie
180,spawn_spewy
230,spawn_chasey
250,spawn_blocking_blocky
310,spawn_blocking_blocky
310,spawn_blocking_blocky
310,spawn_blocking_blocky
311,spawn_frownie
401,spawn_frownie
420,spawn_blocking_frownie
430,spawn_vulcan_chasey
450,spawn_frownie
465,spawn_frownie
480,spawn_chasey
500,multi,20,12,spawn_blocking_blocky
501,spawn_frownie
620,spawn_blocking_blocky
630,spawn_vulcan_chasey
720,spawn_blocking_boss_chasey
721,eol]]
-->8
-- standard events
blip_fx = mknew{
cancel=false
}
function blip_fx:move()
if (self.cancel) return true
self.frames -= 1
if self.frames < 0 then
self.obj.fx_pal = nil
return true
end
return false
end
function blip_fx:abort()
self.cancel=true
end
blip_pals = {}
function init_blip_pals()
for i=0,15 do
local pp = {[0]=0}
for j=1,15 do
pp[j] = i
end
blip_pals[i]=pp
end
end
function blip(obj, col)
obj.fx_pal = blip_pals[col]
if (obj.___fx_pal_event) obj.___fx_pal_event:abort()
events:push_back(blip_fx.new{frames=3, obj=obj})
end
bossspark = split"7,7,10,10,9,9,9,8,8,8,2,2,5,5"
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)
spark(sp,x+4,y+4,cos(angle), sin(angle),boombase+rnd(boombonus),1, true)
end
return
end
spark_particle=mknew{}
function spark_particle:move()
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
function spark_particle:draw()
pset(self.x,self.y,self.sprs[self.sidx])
end
function spark(sprs, x, y, dx, dy, odds, fg)
if (sprs==nil or flr(rnd(odds)) ~= 0) return
local target = fg and intangibles_fg or intangibles_bg
target:push_back(spark_particle.new{
x = x + rnd(4) - 2,
y = y + rnd(4) - 2,
sprs = sprs,
sidx = 1,
dx = dx + rnd(2) - 1,
dy = dy + rnd(2) - 1,
})
end
-->8
-- powerups
xp_gem = mknew(bullet_base.new{
dx = 0,
dy = 0.75,
width=1, -- not used for spr but
height=1,-- bullet_base uses it
category = enemy_blt_cat,
damage = 0,
hurt = {
x_off = -2,
y_off = -2,
width = 8,
height = 8,
},
x_off = 2,
y_off = 2,
})
function xp_gem:draw()
local s,qx,qy = self.qsprite,0,0
-- sprite map position:
-- sprite id to x and y,
-- offset shifts specific low
-- bits of lframe up to the the
-- bit with value 4 as a cheap
-- way to pick an anim frame
if (lframe&0x0.003 == 0) qx, qy = (lframe&0x0.0004)<<16, (lframe&0x0.0008)<<15
sspr(
(s%16<<3)+qx,
(s\16<<3)+qy,
4, 4,
self.x, self.y
)
end
function xp_gem:move()
if not primary_ship.dead and abs(self.x + 1 - primary_ship.x - primary_ship.hurt.x_off) <= primary_ship.magnet and abs(self.y + 1 - primary_ship.y - primary_ship.hurt.y_off) <= primary_ship.magnet then
if (self.x < primary_ship.x + 3) self.x += 1
if (self.x > primary_ship.x + 5) self.x -= 1
if (self.y < primary_ship.y + 3) self.y += 1
if (self.y > primary_ship.y + 5) self.y -= 1
end
return bullet_base.move(self)
end
-- todo: "magnetic" behavior
-- when near player ship
function xp_gem:hitship(ship)
if (ship ~= primary_ship or primary_ship.dead) return false
primary_ship.xp += self.val
primary_ship.last_xp_frame = lframe
return true
end
-- small gems for 1, 5, 25
-- exactly; else huge
function spawn_xp_at(x, y, off, amt)
x += rnd(off+off)-off
y += rnd(off+off)-off
xp_gem.new{
qsprite=amt == 0x0.0001 and 32 or amt == 0x0.0005 and 33 or amt == 0x0.0019 and 34 or 35,
val = amt,
}:spawn_at(mid(x, 0, 124),mid(y,-4,125))
end
-->8
-- upgrade options
-- all these return
-- a [2] of rearm_t:
--
-- icon: sprite id
-- hdr: title text
-- body: text
-- action: callback
-- (method)
spec_gunt = {
protron_gun_p,
vulcan_gun_p,
blast_gun,
}
-- picks n random items from
-- tbl; permutes tbl, selected
-- items at end
function pick(tbl, n)
local ret, top={}, #tbl
for x=top,top-n,-1 do
local idx = 1+rnd(x)\1
add(ret, tbl[idx])
tbl[idx]=tbl[x]
tbl[x]=ret[#ret]
end
return ret
end
-- add a new gun
function spec_gun_opts()
return pick(spec_gunt, 2)
end
-- major upgrades
function big_opts()
return {{
icon=1,
hdr="placeholder",
body="placeholder",
action = function() end,
},
{
icon=1,
hdr="placeholder",
body="placeholder",
action = function() end,
}}
end
-- ordinary upgrades
function small_opts()
-- todo: include gun opts
return pick(primary_ship:small_upgrade_opts(), 2)
end
-->8
-- rearm screen
rearm_mode = mknew{
sel=1,
bfm=1,
crt_frm = 1,
pos=-1,
init=function(this)
poke(0x5f5c, 255) --no btnp repeat
rearm_mode.shuffle(this)
end,
}
crt={-91,-166,-2641,-1441,-23041,23295,-20491,24570}
function rearm_mode:glow_box(x0, y0, x1, y1, c, cf)
for i,v in ipairs{c[1],c[2],c[1],0} do
i -= 1
rect(x0+i,y0+i,x1-i,y1-i,v)
end
fillp(crt[self.crt_frm&0xff])
rectfill(x0+4, y0+4, x1-4, y1-4, cf)
fillp()
end
function easeoutbounce(t)
local n1=7.5625
local d1=2.75
if (t<1/d1) then
return n1*t*t;
elseif(t<2/d1) then
t-=1.5/d1
return n1*t*t+.75;
elseif(t<2.5/d1) then
t-=2.25/d1
return n1*t*t+.9375;
else
t-=2.625/d1
return n1*t*t+.984375;
end
end
function rearm_mode:frame_col(hot)
if (not hot) return {4,10}
if (self.bfm<=16) return {14,7}
return {2,8}
end
function rearm_mode:draw_option(id)
local rec = self.options[id]
self:glow_box(0,0,55,100,self:frame_col(self.sel == id),1)
spr(rec.icon,5, 5)
print(rec.hdr, 13, 8, 7)
print(rec.body, 5, 15, 6)
end
function rearm_mode:pos_frac()
local pos = self.pos
if (not pos) return
if (pos < 0) return 1-easeoutbounce(1+pos)
if (pos > 0) return (1-pos)*(1-pos)
return 0
end
function rearm_mode:shuffle()
-- these will be placeholders
-- until the upgrade deck
-- is a thing that exists
local lev = primary_ship.level + 1
if lev == 4 or lev == 12 then
self.options = spec_gun_opts()
elseif lev % 4 == 0 then
self.options = big_opts()
else
self.options = small_opts()
end
end
function rearm_mode:draw()
drawgame_top()
local frac = self:pos_frac()
camera(frac * 55, 0)
self:draw_option(1)
camera(frac * -128 + (1-frac) * -56, 0)
self:draw_option(2)
camera(0, -28 * frac)
self:glow_box(0,101,111,127,self:frame_col(self.sel < 0),1)
spr(96,15,107,4,2)
print("full ammo\nfull shield\n+50% health",54, 106, 6)
end
function rearm_mode:update_pos()
local pos = self.pos
if (not pos) return
if (pos == 0) then
if (primary_ship.xp < primary_ship.xptarget) self.pos = 1
xpwhoosh = nil
return
end
if (pos < 0) pos = min(pos + 0x0.05, 0)
if pos > 0 then
pos -= 0x0.1
if (pos <= 0) pos = 999
end
self.pos = pos
end
function rearm_mode:update()
self:update_pos()
if self.pos > 1 then
mode = game_mode
return -- do not advance frame
end
local sel, bfm = self.sel, self.bfm
if (btn(3) and sel > 0 or btn(2) and sel < 0) sel=-sel
if (btn(0)) sel = 1
if (btn(1)) sel = 2
if (btn()&0xF ~= 0) and bfm >= 10 or bfm >= 30 then
bfm = 1
else
bfm += 1
end
self.bfm = bfm
if primary_ship.xp < primary_ship.xptarget then
sel = 0
elseif btnp(4) or btnp(5) and self.pos == 0 then
if sel < 0 then
-- todo: sound: rearm
primary_ship.shield = primary_ship.maxshield
-- todo: rewrite for three guns
local specs = primary_ship.special_guns
if specs then
specs[1].ammo = specs[1].maxammo
if (specs[2]) specs[2].ammo = specs[2].maxammo
end
primary_ship.hp = min(primary_ship.maxhp, primary_ship.hp + primary_ship.maxhp/2)
primary_ship.xp -= primary_ship.xptarget / 2
else
local c = self.options[sel]
if c then
-- todo: sound: upgrade
c:action()
primary_ship.xp -= primary_ship.xptarget
primary_ship.xptarget += primary_ship.level * 0x0.0002
primary_ship.level += 1
if (primary_ship.xp >= primary_ship.xptarget) self:shuffle()
end
end
end
self.sel = sel
end
__gfx__
00000000000650000000000000000000bb0b50b59909209200cc0c00000000003b00000082000000e00e8002e00e800200333300002222000000000000000000
00000000006765000000000000cccc00b50b3055920940220c0000c000bbbb0037000000a2000000e0e8880240e8480403bbbb30028888200000000000000000
00700700006d6500000000000cddddd00b33335009444420c00c000c0b333330b7000000a8000000e88e2882e48e24823bbaabb3288aa8820000000000000000
00077000067c665000000000cdd10cd10b3dd350094dd42000c0000cb3350b35b7000000a8000000e88e2882484e24423ba77ab328a77a820000000000000000
00077000067d665000000000cd10cdd100b3350000944200c0000000b350b335b7000000a8000000e88e2882e84e28823ba77ab328a77a820000000000000000
0070070065666765000000000ddddd100b33355009444220c000000c03333350b7000000a800000008888820048488203bbaabb3288aa8820000000000000000
000000006506506500000000001111000b0b5050090920200c0000c00055550037000000a2000000008882000048420003bbbb30028888200000000000000000
00000000650000650000000000000000000b50000009200000c0cc00000000003b00000082000000000820000008200000333300002222000000000000000000
0000000000065000000650000003b0000070070080000000700000000bb0000008800000000000000009200000000000cccccccd000650000000000000000000
000000000067500000076500000370000005500080000000b0000000b76300008a920000000000009009200200000000c111111d006765000000000000000000
00000000006d6500006d6500000b7000700660079000000030000000b663000089920000000550009994444200000000c111111d006d65000000000000000000
00000000067c6650067c6650000b7000056ccd50a000000030000000033000000220000000576d009446544200000000c111111d067c66500000000000000000
00000000067d6650067d6650000b7000056ccd500000000000000000000000000000000000566d009244442200000000c111111d067d66500000000000000000
000000005666657576667650000b7000700dd00700000000000000000000000000000000000dd0009092220200000000c111111d656667650000000000000000
000000005656506666565650000370000005500000000000000000000000000000000000000000000090020000000000c111111d650650650000000000000000
0000000056500056650006500003b00000700700000000000000000000000000000000000000000000a00a0000000000cddddddd650000650000000000000000
060007000600070006600770766c777c0000000000a0008000000000000000000000000000000000000000000000000000000000000000000000000000000000
6cd07cd06cd07cd06ccd7ccd6ccd7ccd000000000090008000000000000000000000000000000000000000000000000000000000000000000000000000000000
0d000d006cd07cd06ccd7ccd6ccd7ccd0000000000800a0000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000d000d000dd00dd0cdd1cdd0000000000080090000000000000000000000000000000000000000000000000000000000000000000000000000000000
0600060006000600066006607667766c00000000000a080000000000000000000000000000000000000000000000000000000000000000000000000000000000
67d06c7067d06c70677d6cc7677d6cc7000000000009080000000000000000000000000000000000000000000000000000000000000000000000000000000000
0d00070067d06c7067cd6cc767cd6cc7000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000d0007000dd007707dd1c771000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000cccccccccccccccc77000000007700000000770000000077000000000000000000000000000000000000000000000000
00000000000000000000000000000000c116611dc11ee11d70000000077000000007700000000770000000070000000000000000000000000000000000000000
00000000000000000000000000000000c1611c1dc11ee11d00000000770000000077000000007700000000770000000700000000000000000000000000000000
00000000000000000000000000000000c61111cdceeeeeed00000000700000000770000000077000000007700000007700000000000000000000000000000000
00000000000000000000000000000000c6111bcdceeeeeed00000000000000007700000000770000000077000000077000000007000000000000000000000000
00000000000000000000000000000000c161bbbdc11ee11d00000000000000007000000007700000000770000000770000000077000000000000000000000000
00000000000000000000000000000000c11ccb1dc11ee11d00000000000000000000000077000000007700000007700000000770000000070000000000000000
00000000000000000000000000000000cdddddddcddddddd00000000000000000000000070000000077000000077000000007700000000770000000000000000
cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000
c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000ceee2222eeed0000c2221111222d0000
c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c1ee2222ee1d0000ce22111122ed0000c2111111112d0000
c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c11e2222e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000
c1111111111d0000c1111111111d0000c1111ee1111d0000c11ee22ee11d0000c1e221122e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000
c1111111111d0000c1111ee1111d0000c111e22e111d0000c11e2112e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000
c1111111111d0000c1111ee1111d0000c111e22e111d0000c11e2112e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000
c1111111111d0000c1111111111d0000c1111ee1111d0000c11ee22ee11d0000c1e221122e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000
c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c11e2222e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000
c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c1ee2222ee1d0000ce22111122ed0000c2111111112d0000
c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000ceee2222eeed0000c2221111222d0000
cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
04444400044444440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
447777700477777a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
477aaa7a0477aaaa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
47a0047a047a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
47a0447a047a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
47a4477a047a44400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
477777a00477777a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
477770000422aaaa2222000200000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
47a77700022ee0002eeee002e00022e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
47a4777002ea2e002e002e02ee022ee0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
47a0477a22ea2e002e002e02e2e2e2e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
47a0047a2e2222e02e222e02e02e02e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
47a0047a2eeeeeea2eeee002e02e02e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0aa000aa2e7aa2ea2e00e002e02e02e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000002e0002e02e002e02e02e02e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000e0000e00e000e00e00e00e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
__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