Files
mmbshmup/vacuum_gambit.p8
Kistaro Windrider bf8297eb72 prevify eships when setting up collider
I will refactor this next: collider.new will take the linked list to ingest, perform the `insert` and "prevify" loops itself, then replace `hide` with `yoink`, which yoinks the item out of the original list. This pairs the `yoink` operation with the context that makes it possible to do (that is, the context when prevification was implemented); eships therefore cannot be edited in complex ways while the collider is still valid, but we can append to it as long as we don't expect those items to be procesesd correctly this frame. a new_eships+vore plan might be better if we turn out to need it, but presently we don't; flotilla spawning and raider spawning happen at a different point.
2025-06-20 16:31:18 -07:00

2231 lines
66 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,
-- after calling setmetatable.
-- use to initialize mutables.
--
-- if there was a previous new,
-- it is invoked before
-- setting tt's metatable, so
-- each new will see its
-- inheritance chain.
function mknew(tt)
local mt,oldinit,more = {__index=tt},tt.superinit,rawget(tt, "init")
tt.new=function(ret)
if(not ret) ret = {}
ret.new = false
setmetatable(ret, mt)
if(oldinit) oldinit(ret)
if (more) more(ret)
return ret
end
if oldinit and more then
tt.superinit = function(ret)
oldinit(ret)
more(ret)
end
elseif more then
tt.superinit = more
end
return tt
end
-- intrusive singly-linked list.
-- cannot be nested or crossed!
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.
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
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?
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()
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()
gframe = 0
interlude = 0
waves_complete = 0
current_wave = nil
end
function _update60()
mode:update()
end
function call_f(x)
return x:f()
end
function call_move(x)
return x:move()
end
function ones(n)
local ret = 0
while n != 0 do
if (n&0x0.0001) ret += 1
n >>= 1
end
return ret
end
function updategame()
local ps = primary_ship
if (ps.xp >= ps.xptarget) and (gframe - ps.last_xp_frame > 0x0.000f) and (not ps.dead) then
mode = rearm_mode.new()
return _update60()
end
gframe += 0x0.0001
if current_wave then
if current_wave:update() then
-- end of stage
waves_complete += 1
current_wave = nil
if waves_complete < 32767 then
interlude = 59 -- plus one dead frame with load_level but no update
else
-- you maxed out the level
-- counter. what the fuck
-- is wrong with you?
-- go outside.
--
-- do not spawn more levels.
interlude = 32767
end
end
elseif interlude > 0 then
interlude -= 1
else
current_wave = flotilla.new()
current_wave:load(0, 0, min(ones(waves_complete)\2, 4))
end
events:vore(new_events)
events:strip(call_move)
for _, lst in ipairs{intangibles_bg, eships, pbullets} do
lst:strip(call_move)
end
if not ps.dead then
ps:move()
local pbox = hurtbox(ps)
eships:strip(function(es)
if(not collides(pbox, hurtbox(es))) return
ps:hitship(es)
return es:hitship(ps)
end)
ebullets:strip(function(eb)
-- loopify this when split moves implemented
eb:move()
if (not collides(pbox, hurtbox(eb))) return
ps:hitbullet(eb)
return eb:hitship(ps)
end)
end
-- many bullets and many enemy ships;
-- use bucket collider for efficiency
local eship_collider, pp = collider.new(), eships
eships:strip(function(s)
eship_collider:insert(s)
s.prev = pp
pp = s
end)
-- lose tokens, save time:
-- unroll `strip` to avoid
-- function call overhead
pbullets:strip(function(pb)
for es in all(eship_collider:get_collisions(pb)) do
if es:hitbullet(pb) then
eship_collider:hide(es)
es.prev.next=es.next
if es.next then
es.next.prev = es.prev
else
eships.tail = es.prev
end
end
if (pb:hitship(es)) return true
end
end)
intangibles_fg:strip(call_move)
if waves_complete == 32767 and not eships.next and not ebullets.next and not events.next then
game_state = win
end
if (ps.dead) game_state = lose
if ps.xp >= ps.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 drawable in all{intangibles_bg, pbullets, primary_ship, eships, ebullets, intangibles_fg} do
drawable: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
-- generic full sprite hurtbox
box8 = {
x_off = 0,
y_off = 1,
width = 8,
height = 8
}
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,
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])
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
return false
end
function ship_m:draw()
if (self.dead) return
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 = gframe + 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 (gframe < self.shield_refresh_ready) return
self.shield += 1
self.shield = min(self.shield, self.maxshield)
self.shield_refresh_ready = gframe + 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,
new_clip = -32768,
clip_size = false,
clip_remain = 0,
clip_interval = 0x0.80,
icon = 20,
ammobonus = 1,
-- fractional frames of
-- cooldown reduction from
-- upgrades, not yet applied
cd_remainder = 0,
veloc = 1,
aim = 0.75, -- down; 0.25, or -0.75, is up
shot_idx = 0,
-- shots: list<list<[3]num>>
-- describing a cycling
-- firing pattern. shot_idx
-- tracks offset into pattern.
-- each nested list: a burst
-- of shots to fire; takes
-- 1 ammo; sequential
-- each shot: angle (turns,
-- relative to `aim`),
-- firing x-offset, velocity;
-- if x-offset is nil, use 0;
-- if velocity is nil, use
-- self.veloc instead
init = function(self)
if (not self.shots) self.shots = {{{0}}}
end
}
-- 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
-- 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 each
shot when
firing.
is: ]]..tostr(c)..[[f
minus: ]]..tostr(c-newc)..[[f
----------
total: ]]..tostr(newc)..[[f
remainder:
]]..sub(tostr(newc-rawnewc),0,5),
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 (gframe < self.shoot_ready) return false
local csz,crm = self.clip_size, self.clip_remain
if csz then
if crm < csz and gframe >= self.new_clip then
self.clip_remain = csz
self.new_clip = gframe + self.clip_interval
elseif crm == 0 then
return false
end
end
if self.ammo then
if (self.ammo <= 0) return false
self.ammo -= 1
end
if csz then
self.clip_remain -= 1
end
self.shoot_ready = gframe + self.cooldown
self:actually_shoot(x, y)
return true
end
function gun_base:actually_shoot(x, y)
local shots,veloc,aim,munition = self.shots,self.veloc,self.aim,self.munition
local idx = self.shot_idx % #shots + 1
self.shot_idx = idx
shots = shots[idx]
for s in all(shots) do
local a,xo,v = unpack(s)
v = v or veloc
xo = xo or 0
-- reverse x-offset for negative base angle
if (aim < 0) xo = -xo
a += aim
-- todo: switch munition
-- depending on angle
-- (allows for non-round
-- sprites and hitboxes on
-- shots from guns with
-- widely varying angles)
local m = munition.new{}
-- todo: automatically make
-- high velocity shots do
-- multiple collision checks
m.dy = sin(a) * veloc
m.dx = cos(a) * veloc
m:spawn_at(x+(xo or 0), y)
end
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,
hitship = const_fxn(true),
category = enemy_blt_cat,
})
zap_p = mknew(zap_e.new{
sprite = 8,
y_off = 0,
category = player_blt_cat,
})
zap_gun_e = mknew(gun_base.new{
cooldown = 0x0.0020, -- frames between shots
veloc = 4,
munition = zap_e,
})
zap_gun_p = mknew(zap_gun_e.new{
icon = 19,
munition = zap_p,
veloc = 8,
aim = 0.25,
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
aim = -0.75,
ammo = 5,
maxammo = 5,
munition = blast,
hdr = "bLASTER",
body= [[---------GUN
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,
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,
veloc = 2,
shots = {{{-0.25}, {-0.165}, {-0.0825}, {0}, {0.0825}, {0.165}, {0.25}}}
})
protron_gun_p = mknew(protron_gun_e.new{
munition = protron_p,
maxammo = 20,
cooldown = 0x0.0018,
veloc = 4,
aim = -0.75,
hdr = "pROTRON",
body = [[---------GUN
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,
category=enemy_blt_cat
})
vulcan_p = mknew(vulcan_e.new{
sprite=22,
y_off = 4,
category=player_blt_cat
})
vulcan_gun_e = mknew(gun_base.new{
icon = 37,
cooldown = 0x0.0003, -- frames between shots
ammo = nil,
maxammo = nil,
munition=vulcan_e,
veloc = 2,
shots = {{{0.02, 2}}, {{-0.02,0}}, {{-0.03, -2}}, {{0.03, 2}}, {{0.02, 0}}, {{-0.02, -2}}}
})
machine_gun_e = mknew(vulcan_gun_e.new{
icon = 38,
clip_size = 12,
clip_interval = 0x0.005a,
shots = {{{0, 2}}, {{0, -2}}}
})
vulcan_gun_p = mknew(vulcan_gun_e.new{
munition=vulcan_p,
maxammo = 100,
aim=-0.75,
veloc=4,
hdr = "vULCAN",
body = [[---------GUN
rapidly fire
in a v.
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
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=[[--------SHIP
survive more
unshielded
hits.
+2 hp]],
action=function()
self.maxhp += 2
self.hp += 2
end,
},{
icon=52,
hdr="capacity",
body=[[------SHIELD
shield can
absorb more
hits before
recharging.
+1 hp]],
action=function()
self.maxshield += 1
self.shield += 1
end,
},{
icon=1,
hdr="thrusters",
body=[[--------SHIP
move faster,
steer more
sharply.]],
action=function()
--maxspd thrust drag
self.maxspd += 0.5
self.thrust += 0.0625
self.drag += 0.03125
end,
},{
icon=20,
hdr="magnet",
body=[[--------SHIP
pick up xp
from further
away.]],
action=function ()
self.magnet += 2
end,
},
self.main_gun:rate_upgrade_opt(),
}
if cdr > 0 then
add(ret, {
icon = 6,
hdr = "recharge",
body=[[------SHIELD
shield will
recharge at
a faster
pace.
]] .. tostr(ceil(100 * cdr / self.shieldcooldown)) .. "% faster",
action = function()
self.shieldcooldown -= cdr
end
})
end
if pr > 0 then
add(ret, {
icon = 6,
hdr = "recovery",
body=[[------SHIELD
reduce the
delay after
a hit before
shield will
start to
recharge.
]] .. tostr(ceil(100 * pr / self.shieldpenalty)) .. "% shorter",
action = function()
self.shieldpenalty -= pr
end
})
end
return ret
end
-- original prototype leftover;
-- likely to be the basis of a
-- standard raider type, so
-- i am keeping it around
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,
init = function(ship)
ship.main_gun=ship.main_gun or zap_gun_e.new{}
end
})
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,
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,
})
-- flotilla ships
ship_f = mknew(ship_m.new{
-- sprite required
size = 1,
hurt = box8,
-- no sparks
hp = 0.5,
xp = 0x0.0001,
fire_off_x = 4,
fire_off_y = 4,
maxspd = 3,
thrust = 0.1,
drag = 0.05,
act = function(self)
local wx,wy=self.want_x,self.want_y
self.xmin,self.xmax,self.ymin,self.ymax = wx,wx,wy,wy
return 0,0,false,false
end,
init = function(self)
if (self.gun_proto) self.main_gun = self.gun_proto.new()
end
})
ship_mook = mknew(ship_f.new{
sprite=103
})
ship_defender = mknew(ship_f.new{
sprite=105,
hp = 2.5,
xp = 0x0.0003,
})
ship_turret = mknew(ship_f.new{
sprite=106,
xp = 0x0.0002,
gun_proto = machine_gun_e,
})
ship_skirmisher = mknew(ship_f.new{
sprite=107,
xp = 0x0.0004,
sparks = smokespark,
sparkodds = 3,
fire_off_y = 7,
})
function rnd_spawn_loc()
local x,y = flr(rnd(304)), flr(rnd(32))
if (x<184) return x-40,-y-8
if (x<244) return -y-8,x-184
return 112+y, x-244
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
-- flotillas
-- a template for a wave, read
-- from the map. each ship can
-- alternate between "attack"
-- and "formation" modes, like
-- galaxian or galaga. ships
-- with different roles have
-- different rules for becoming
-- aggressive, but aggression
-- ramps up during the wave.
-- flotilla placeholders are
-- defined by sprite flags.
-- see obsidian vault for
-- full docs.
flotilla = mknew{
use_var = 0x1111,
opt_odds = split"0.5,0.5,0.5,0.5",
init=function(this)
this.ship_bases={
[0]=mknew(ship_mook.new{ship_t=0}),
[1]=mknew(ship_mook.new{ship_t=1, sprite=104}),
[4]=mknew(ship_defender.new{ship_t=4}),
[5]=mknew(ship_defender.new{ship_t=5, sprite=10}),
[8]=mknew(ship_turret.new{ship_t=8}),
[9]=mknew(ship_turret.new{ship_t=9, sprite=4}),
[12]=mknew(ship_skirmisher.new{ship_t=12}),
[13]=mknew(ship_skirmisher.new{ship_t=13, sprite=26}),
}
end,
}
function flotilla:load(ulc_cx, ulc_cy, lvl)
local rows,cy,uv,counts={},ulc_cy,self.use_var,{
[0]=0,
[1]=0,
[4]=0,
[5]=0,
[8]=0,
[9]=0,
[12]=0,
[13]=0,
}
repeat
local row,cx,opt,f,mode= {},ulc_cx,{},0,0
for i,v in ipairs(self.opt_odds) do
opt[i*4-4]=rnd()<v
end
repeat
f=fget(mget(cx, cy))
-- bits 0x03: control mark or ship?
mode = f&0x03
if mode==2 then
-- bits 0x0c: ship class
local ship_t = f&0x0c
-- bit 0x20: optional?
if f&0x20 == 0 or opt[ship_t] then
-- bit 0x10: alternate ship?
-- increment ship id if
-- alternate is requested
-- and we allow alternates
-- for this type of ship
ship_t+=(uv>>ship_t&0x1)&((f&0x10)>>4)
add(row, self.ship_bases[ship_t].new{col=cx-ulc_cx})
end
end
cx += 1
until mode==1
-- mode 1: end of line control mark
-- bits 0x18: what size flotilla is this row used for?
if (f&0x18)>>3 <= lvl then
-- keep the row; count it
for s in all(row) do
counts[s.ship_t] += 1
s.x,s.y=rnd_spawn_loc()
eships:push_back(s)
end
add(rows, row)
end
cy += 1
-- control mark bit 0x04: end of flotilla
until f&0x04 > 0
self.rows=rows
self:statisfy(counts)
end
function flotilla:statisfy(counts)
-- TODO: now that we know how
-- many ships of each kind
-- exist, build ships to match
-- difficulty target
--
-- no difficulty model is yet
-- implemented, though, so
-- just use base ships only
-- for this prototype
end
function flotilla:update()
-- algorithm: redistribute
-- TODO: alternate flotilla movement algorithms
-- find effective width and height
local min_col,max_col,live_rows=999,0,0
for row in all(self.rows) do
local row_alive=false
for ship in all(row) do
if not ship.dead then
row_alive=true
local col=ship.col
if (col < min_col) min_col = col
if (col > max_col) max_col = col
end
end -- scanned row
if (row_alive) live_rows += 1
end -- extent search
if (live_rows == 0) return true -- done
-- distribute across box:
-- x = [4, 100)
-- y = [4, 60)
local x_interval,x_offset = 0,52
if min_col < max_col then
x_interval=96/(max_col-min_col)
x_offset = 4-min_col*x_interval
end
local y_interval,y_offset=0,40
if live_rows > 1 then
y_interval=52/(live_rows-1)
y_offset=4-y_interval
end
-- now assign target locations
local real_row=0
for row in all(self.rows) do
local row_alive = false
for ship in all(row) do
if not ship.dead then
if not row_alive then
real_row += 1
row_alive = true
end
ship.want_x = ship.col * x_interval + x_offset
ship.want_y = real_row * y_interval + y_offset
end -- ship updated
end -- row updated
end -- table updated
return false -- some ships remain
end
-->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) or (abs(dx) < 0.5 and abs(dy))) ~= 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),
dy = dy * rnd(2),
})
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 gframe up to the the
-- bit with value 4 as a cheap
-- way to pick an anim frame
if (gframe&0x0.003 == 0) qx, qy = (gframe&0x0.0004)<<16, (gframe&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 = gframe
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()
-- todo: avoid duplicates
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()
if(not primary_ship.special_guns) return pick(primary_ship:small_upgrade_opts(), 2)
local opts = {rnd(primary_ship:small_upgrade_opts())}
for g in all(primary_ship.special_guns) do
add(opts, rnd(g:small_upgrade_opts()))
end
return pick(opts, 2)
end
-->8
-- rearm screen
rearm_mode = mknew{
sel=1,
bfm=1,
crt_frm = 0,
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[1+(self.crt_frm&7)])
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,101,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
-- for testing: more guns really early
-- if lev == 4 or lev == 12 then
if lev == 2 or lev == 3 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,102,111,127,self:frame_col(self.sel < 0),1)
spr(96,15,107,4,2)
print("full ammo\nfull shield\n+50% health",54, 107, 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.crt_frm+=0.25
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
044444000444444400000000000000000000000000000000000000000000000000000000000000000000000000000000000bb000000aa0000009900000088000
447777700477777a0000000000000000000000000000000000000000000666000077700008888800000ab00006000060000bb000000aa0000009900000088000
477aaa7a0477aaaa0000000000000000000000000000000000000000006ddd5007fff70008eeee20000ab00006c006d0000bb000000aa0000009900000088000
47a0047a047a0000000000000000000000000000000000000000000006dd7d5007ffff4008eeee200aaabbb006ccccd0000bb000000aa0000009900000088000
47a0447a047a0000000000000000000000000000000000000000000006d7dd5007ffff4008eeee200bbb333006ccccd0000bb000000aa0000009900000088000
47a4477a047a4440000000000000000000000000000000000000000006ddd500004fff4008eeee20000b300006d00cd0000bb000000aa0000009900000088000
477777a00477777a0000000000000000000000000000000000000000005550000004440002222200000b30000d0000d0000bb000000aa0000009900000088000
477770000422aaaa22220002000002000000000000000000000000000000000000000000000000000000000000000000000bb000000aa0000009900000088000
47a77700022ee0002eeee002e00022e00000000000000000000000000000000000000000000000000000000000000000000bb000000aa0000009900000088000
47a4777002ea2e002e002e02ee022ee00000000000000000000000000005600000474000028282000004b000060000000bbbbbb00aaaaaa00999999008888880
47a0477a22ea2e002e002e02e2e2e2e0000000000000000000000000005d0d0004f0f400080e0e00000a000000c000d00b0000b00a0000a00900009008000080
47a0047a2e2222e02e222e02e02e02e000000000000000000000000005d07050070f0f2002e0e02004a0b0b0060c0c000b0bb0b00a0aa0a00909909008088080
47a0047a2eeeeeea2eeee002e02e02e000000000000000000000000006070d1004f0f040080e0e000b0b035000c0c0d00b0bb0b00a0aa0a00909909008088080
0aa000aa2e7aa2ea2e00e002e02e02e000000000000000000000000000d0d100002f0f2002e0e0200000300006000c000b0000b00a0000a00900009008000080
000000002e0002e02e002e02e02e02e0000000000000000000000000000510000002420002020200000b5000000000d00bbbbbb00aaaaaa00999999008888880
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
__gff__
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000212060a0e01091119000000000000002232363a3e050d151d
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
__map__
00006b6b00006f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
7b6a00006a7b6e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
7778686878776c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
6767777767676c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
7877676777787d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000