From a359bc5031e9fe640d10c1a5293ed3862c1d9065 Mon Sep 17 00:00:00 2001 From: Kistaro Windrider Date: Thu, 21 Dec 2023 13:09:33 -0800 Subject: [PATCH] duplicating the file -- preparing for major changes. Vacuum Gambit is about to stop being a Tyrian clone. The hybrid of Mega Man Battle Network and Slay the Spire mechanics lends itself better to Galaga than Tyrian. updatedshmup.p8 remains an excellent basis for a Tyrian-like shmup, especially since it has a (demo of a) level loading engine that reads strings, and maybe I'll even implement something along those lines someday -- but I'm about to tear it all down to build it up again, starting with the entire model for levels and progress, followed shortly by the "energy" system and its interaction with shields. (long-term plan: shields will auto-recover after every "flotilla", but health will be more difficult to recover. Player shots will be limited entirely by ammo and cooldown, replacing the "burst throughput" vs. "sustain throughput" system created by the generator, although some enemy firing patterns may recreate that behavior.) (plan for the "level" system: create Galaga-style flotillas. I think ship behaviors can reasonably be declared in the 8 bits available in sprite flags, meaning I can program simple enemies entirely from the sprite sheet and draw flotillas on the map.) --- vacuum_gambit.p8 | 2109 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2109 insertions(+) create mode 100644 vacuum_gambit.p8 diff --git a/vacuum_gambit.p8 b/vacuum_gambit.p8 new file mode 100644 index 0000000..e690c3b --- /dev/null +++ b/vacuum_gambit.p8 @@ -0,0 +1,2109 @@ +pico-8 cartridge // http://www.pico-8.com +version 41 +__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 + +-- generate standard "overlay" +-- constructor for type tt. +-- if more is defined, generated +-- new calls more(ret) after +-- ret is definitely not nil +-- before calling setmetatable. +-- use to initialize mutables. +-- +-- 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, more) + local mt,oldnew = {__index=tt},tt.new + tt.new=function(ret) + if(not ret) ret = {} + if(more) more(ret) + if(oldnew) oldnew(ret) + setmetatable(ret, mt) + return ret + end +end + +-- intrusive singly-linked list. +-- cannot be nested! +linked_list = {is_linked_list=true} +mknew(linked_list, function(x) + x.next=nil + x.tail=x + end) + +function linked_list:push_back(x) + self.tail.next = x + self.tail = x +end + +function linked_list:push_front(x) + if (not self.next) self.tail = x + x.next = self.next + self.next = x +end + +-- vore eats another linked list +-- by appending its contents. +-- the ingested linked is empty. +function linked_list:vore(x) + if (not x.next) return + self.tail.next = x.next + self.tail = x.tail + x.next = nil + x.tail = x +end + +-- strip calls f(x) for each +-- node, removing each node for +-- which f(x) returns true. it +-- returns the new tail; nil +-- if the list is now empty. +function linked_list:strip(f) + local p, n = self, self.next + while n do + if f(n) then + p.next = n.next + else + p = n + end + n = n.next + end + self.tail = p + return p +end + +-- optimized special case - +-- could be done with strip but +-- this avoids extra function +-- calls and comparisions since +-- draw isn't allowed to kill +-- the item +function linked_list:draw() + local n = self.next + while n do + n:draw() + n = n.next + end +end + + +function linked_list:pop_front() + local ret = self.next + if (not ret) return + self.next = ret.next + if (not ret.next) ret.tail = nil + return ret +end + +function _init() + init_blip_pals() + wipe_level() + primary_ship.main_gun = zap_gun.new() + load_level(example_level_csv) + 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_level() + 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() +end + +function _update60() + updategame() +end + +function call_f(x) + return x:f() +end + +function call_move(x) + return x:move() +end + +function updategame() + leveldone = level_frame() + events:vore(new_events) + events:strip(call_move) + for _, lst in ipairs{intangibles_bg, pships, eships, pbullets, ebullets} do + lst:strip(call_move) + end + + pships:strip( + function(ps) + local pbox, pded = hurtbox(ps), false + eships:strip( + function(es) + if (not collides(pbox, hurtbox(es))) return + pded = pded or ps:hitship(es) + return es:hitship(ps) + end + ) + return pded + end + ) + pships:strip( + function(ps) + local pbox, pded = hurtbox(ps), false + ebullets:strip( + function(eb) + if (not collides(pbox, hurtbox(eb))) return + pded = pded or ps:hitbullet(eb) + return eb:hitship(ps) + end + ) + return pded + end + ) + + -- 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 + state = win + end + if (not pships.next) state = lose +end + +function _draw() + fillp(0) + drawgame() + if (state == game) fadelvl = -45 + if (state == win) dropshadow("win",50,61,11) + if (state == lose) dropshadow("fail",48,61,8) + fadescreen() +end + +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" +function drawhud() + -- 112-and-right is hud zone + rectfill(112, 0, 127, 127,0x56) + rect(112,0,127,127,7) + line(127,1,127,127,5) + line(113,127) + + draw_gun_info("❎",1,116,3,primary_ship.main_gun) + draw_gun_info("🅾️",2,116,31,primary_ship.special_gun) + + dropshadow("pwr",114,59,1) + inset(114,66,125,92) + fillp(0x5a5a) + vertmeter(115,67,124,91,primary_ship.power, primary_ship.max_power, powcols) + + + dropshadow("h s",114,97,1) + inset(114,104,125,125) + line(119,105,119,124,119) + line(120,105,120,125,85) + vertmeter(115,105,118,124,primary_ship.hp, primary_ship.maxhp, hpcols) + vertmeter(121,105,124,124,primary_ship.shield, primary_ship.maxshield,{204,220,221}) + fillp(0) +end + +function draw_gun_info(lbl,fgc,x,y,gun) + dropshadow(lbl,x,y,fgc) + inset(114,y+7,125,y+18) + inset(114,y+20,125,y+24) + if(gun) then + spr(gun.icon,116,y+9,1,1) + --115 to 124 - ammo bar. round up + if gun.ammo == nil then + fillp(0xa5a5) + rectfill(115,y+21,124,y+23,0xea) + fillp(0) + elseif gun.ammo > 0 then + rectfill( + 115,y+21, + 115+flr(9*gun.ammo/gun.maxammo), + y+23,10) + else + line(118, y+22, 121, y+22, 2) + end + end +end + +function vertmeter(x0,y0,x1,y1,val,maxval,cols) + if (val <= 0) return + local h = y1-y0 + local px = -flr(-(h*val)\maxval) + local ncols = #cols + local firstcol = ((h-px)*ncols\h)+1 + local lastbottom = y0+(h*firstcol\ncols) + rectfill(x0, y1-px, x1, lastbottom, cols[firstcol]) + for i=firstcol+1,ncols do + local bottom = y0+h*i\ncols + rectfill(x0,lastbottom,x1,bottom,cols[i]) + lastbottom = bottom + end +end + +function inset(x0,y0,x1,y1) + rectfill(x0,y0,x1,y1,0) + -- use "wide colors" to draw + -- monochrome regardless of + -- fillp + rect(x0,y0,x1,y1,119) + line(x1,y0,x0,y0,85) + line(x0,y1,85) +end + +function dropshadow(str, x, y, col) + print(str, x+1, y+1, 5) + print(str, x, y, col) +end + +-->8 +--ship behavior + +scrollrate = 0.25 --in px/frame + +ship_m = { + + -- ships have no shield by default + shield = 0, + maxshield = 0, + shieldcost = 32767.9, + shieldcooldown = 0x0.00a0, + + -- default generator behavior: + -- 10 seconds for a full charge + max_power = 600, + power = 600, + generator = 1, -- power gen per frame + + slip = true, -- most enemies slide + + xmomentum = 0, + ymomentum = 0, +} +mknew(ship_m) + +function ship_m:die() + self.dead = true + if (self.hp <= 0) boom(self.x+self.size*4, self.y+self.size*4,12*self.size, self.boss) +end + +function ship_m:move() + self:refresh_shield() + self.power = min(self.max_power, self.power + self.generator) + local dx, dy, shoot_spec, shoot_main = self:act() + if (shoot_main) self:maybe_shoot(self.main_gun) + if (shoot_spec) self:maybe_shoot(self.special_gun) + 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 += dx + self.ymomentum += dy + self.xmomentum = mid(-self.maxspd, self.maxspd, self.xmomentum) + self.ymomentum = mid(-self.maxspd, self.maxspd, self.ymomentum) + + self.x += self.xmomentum + self.y += self.ymomentum + + if self == primary_self then + self.x = mid(0, 112 - 8 * self.size, self.x) + self.y = mid(0, 128 - 8 * self.size, self.y) + end + + --friction + local d = self.drag + self.xmomentum -= mid(d, -d, self.xmomentum) + self.ymomentum -= mid(d, -d, 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 + if (self.power < gun.power) return + if (not gun:shoot(self.x + self.fire_off_x, self.y + self.fire_off_y)) return + self.power -= gun.power +end + +function ship_m:hitship(other) + return self:hitsomething(1) +end + +function ship_m:hitbullet(b) + return self:hitsomething(b.damage) +end + +function ship_m:hitsomething(dmg) + if (dmg <= 0) return false + self.shield_refresh_ready = lframe + self.shieldcooldown + if self.shield >= dmg then + self.shield -= dmg + self:ow(true) + return false + end + dmg -= self.shield + self.shield = 0 + self.hp -= dmg + if self.hp <= 0 then + self:die() + return true + end + self:ow(false) + return false +end + +function ship_m:ow(shielded) + if (shielded) then + blip(self,12,3) + return + end + blip(self, 7, 3) +end + +function ship_m:refresh_shield() + if (self.shield >= self.maxshield) return + if (lframe < self.shield_refresh_ready) return + if (self.power < self.shieldcost) return + self.shield += 1 + self.power -= self.shieldcost + self.shield_refresh_ready = lframe + self.shieldcooldown +end + +-->8 +-- bullet and gun behaviors + +bullet_base = { + enemyspd = 0.5 +} +mknew(bullet_base) + +gun_base = { + shoot_ready = -32768, + icon = 20 +} +mknew(gun_base) + +function bullet_base:hitship(_) + self:die() + return true +end + +function bullet_base:die() +end + +function bullet_base:move() + self.x += self.dx + if self.enemy then + self.y += self.dy + if self.y > 128 then + self:die() + return true + end + else + self.y -= self.dy + if self.y < -8*self.height then + self:die() + return true + end + end + return false +end + +function bullet_base:draw() + spr(self.sprite, self.x, self.y, self.width, self.height) +end + +function bullet_base:spawn_at(x, y) + self.x = x - self.center_x_off + if self.enemy then + self.dx *= self.enemyspd + self.dy *= self.enemyspd + self.y = y + self.top_y_off + ebullets:push_back(self) + else + self.y = y - (8 * self.height) + self.bottom_y_off + pbullets:push_back(self) + end +end + +function gun_base:shoot(x, y) + if (lframe < self.shoot_ready) return false + if self.ammo then + if (self.ammo <= 0) return false + self.ammo -= 1 + end + self.shoot_ready = lframe + self.cooldown + self:actually_shoot(x, y) + return true +end + +function gun_base:actually_shoot(x, y) + local typ = self.t + local b = typ.new{ + enemy = self.enemy, + sprite = self.enemy and typ.esprite or typ.psprite, + } + b:spawn_at(x, y) + return true +end + +-->8 +-- bullets and guns + +zap = bullet_base.new{ + --shape + psprite = 8, --index of player ammo sprite + esprite = 9, -- index of enemy ammo sprite + width = 1, --in 8x8 blocks + height = 1, + hurt = { -- hurtbox - where this ship can be hit + x_off = 0, -- upper left corner + y_off = 0, -- relative to sprite + width = 2, + height = 8 + }, + center_x_off = 1, -- how to position by ship + bottom_y_off = 0, + top_y_off = 0, + + damage = 1, + dx = 0, -- px/frame + dy = 8, + + hitship = function(_, _) + return true + end +} +mknew(zap) + +zap_gun = gun_base.new{ + enemy = false, + power = 20, -- power consumed per shot + cooldown = 0x0.000a, -- frames between shots + ammo = nil, -- unlimited ammo - main gun + t = zap -- metatable of bullet to fire +} +mknew(zap_gun) + +blast = bullet_base.new{ + --shape + psprite = 12, --index of player ammo sprite + esprite = 3, -- index of enemy ammo sprite + width = 1, --in 8x8 blocks + height = 1, + hurt = { -- hurtbox - where this ship can be hit + x_off = 1, -- upper left corner + y_off = 1, -- relative to sprite + width = 6, + height = 6 + }, + center_x_off = 4, -- how to position by ship + bottom_y_off = 0, + top_y_off = 0, + + damage = 4, + dx = 0, -- px/frame + dy = 2, + awaitcancel = false, + + -- disable damage for 2 frames + -- when hitting something + hitship = function(self, _) + if self.damage > 0 and not self.awaitcancel then + self.awaitcancel = true + once_next_frame(function() + new_events:push_back{ + wait = 2, + obj = self, + saved_dmg = self.damage, + move = function(self) + self.wait -= 1 + if self.wait <= 0 then + self.obj.damage = self.saved_dmg + return true + end + end, + } + self.damage = 0 + self.awaitcancel = false + end) + end + end +} +mknew(blast) + +blast_gun = gun_base.new{ + icon = 13, + enemy = false, + power = 0, -- ammo, not power + cooldown = 0x0.0020, -- frames between shots + ammo = 5, + maxammo = 5, + t = blast -- type of bullet to fire +} +mknew(blast_gun) + +protron = bullet_base.new{ + --shape + psprite = 23, --index of player ammo sprite + esprite = 24, -- index of enemy ammo sprite + width = 1, --in 8x8 blocks + height = 1, + hurt = { -- hurtbox - where this ship can be hit + x_off = 1, -- upper left corner + y_off = 1, -- relative to sprite + width = 2, + height = 2 + }, + center_x_off = 1, -- how to position by ship + bottom_y_off = 4, + top_y_off = 0, + + damage = 1, + dx = 0, -- px/frame + dy = 3, +} +mknew(protron) + +protron_gun = gun_base.new{ + icon = 25, + enemy = false, + power = 35, + cooldown = 0x0.000f, -- frames between shots + ammo = nil, + maxammo = nil, + actually_shoot = function(self, x, y) + local sprite = protron.psprite + if (self.enemy) sprite=protron.esprite + for i=1,3 do + local b = protron.new{ + enemy=self.enemy, + sprite=sprite, + dx = i, + dy = 4-i + } + b:spawn_at(x,y) + local b2 = protron.new{ + enemy=self.enemy, + sprite=sprite, + dx = -i, + dy = 4-i + } + b2:spawn_at(x,y) + end + local bup = protron.new{ + enemy=self.enemy, + sprite=sprite, + dy=4 + } + bup:spawn_at(x,y) + end +} +mknew(protron_gun) + +vulcan = bullet_base.new{ + --shape + psprite = 22, --index of player ammo sprite + esprite = 21, -- index of enemy ammo sprite + width = 1, --in 8x8 blocks + height = 1, + hurt = { -- hurtbox - where this ship can be hit + x_off = 0, -- upper left corner + y_off = 0, -- relative to sprite + width = 1, + height = 4 + }, + center_x_off = 0.5, -- how to position by ship + bottom_y_off = 4, + top_y_off = 0, + + damage = 0.5, + dx = 0, -- px/frame + dy = 4, +} +mknew(vulcan) + +vulcan_gun = gun_base.new{ + icon = 37, + enemy = false, + power = 8, + cooldown = 0x0.0002, -- frames between shots + ammo = nil, + maxammo = nil, + dxs = {0.35, -0.35, -0.7, 0.7, 0.35, -0.35}, + xoffs = {1, 0, -1, 1, 0, -1}, + dxidx = 1, + actually_shoot = function(self, x, y) + local sprite = self.enemy and vulcan.esprite or vulcan.psprite + local b = vulcan.new{ + enemy=self.enemy, + sprite=sprite, + 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 +} +mknew(vulcan_gun) + +-->8 +--ships, including player + +firespark = split"9, 8, 2, 5, 1" +smokespark = split"13, 13, 5, 5" + +player = ship_m.new{ + --shape + sprite = 1, --index of ship sprite + size = 1, --all ships are square; how many 8x8 sprites? + hurt = { -- hurtbox - where this ship can be hit + x_off = 3, -- upper left corner + y_off = 2, -- relative to ship ulc + width = 1, + height = 3 + }, + sparks = firespark, -- see tab 9 + sparkodds = 2, + boss = true, -- dramatic special effects + + -- health and power + hp = 3, -- current health, non-regenerating + maxhp = 3, -- player only; other ships never heal + shield = 2, -- regenerates, using power + maxshield = 2, + shieldcost = 300, -- power cost to refill shield + generator = 1.5, -- 1 feels too slow + + -- gun + main_gun = nil, -- assign at spawn time + special_gun = nil, + fire_off_x = 4, -- offset where bullets come from + fire_off_y = 0, + + -- position + x=52, -- x and y are for upper left corner + y=96, + xmomentum = 0, + ymomentum = 0, + maxspd = 2.5, -- momentum cap + thrust = 0.25, -- momentum added from button + drag = 0.125, -- momentum lost per frame + slip = false, -- does not slide down screen + 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 +} +mknew(player, + function(p) + p.main_gun = zap_gun.new() + end +) + +frownie = 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, + + -- health and power + hp = 1, -- enemy ships need no max hp + + -- position + x=60, -- x and y are for upper left corner + y=8, + xmomentum = 0, + ymomentum = 0, + maxspd = 2, -- momentum cap + thrust = 0.12, -- momentum added from button + drag = 0.07, -- momentum lost per frame + slip = true, + 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, +} +mknew(frownie) + +blocky = frownie.new{ + sprite = 10, + hp = 2, + hurt = { + x_off = 0, + y_off = 0, + width = 8, + height = 7 + }, + + ow = function(self) + if self.hp <= 1 then + self.sprite = 11 + else + self.sprite = 10 + end + ship_m.ow(self) + end +} +mknew(blocky) + +spewy = frownie.new{ + sprite=26, + power=-20, + hurt = { + x_off=0, + y_off=1, + width=8, + height=5 + }, + hp=1, + maxpower=70, + generator=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, true + end +} +mknew(spewy, function(ship) + ship.main_gun=ship.main_gun or protron_gun.new{enemy=true} +end) + +chasey = ship_m.new{ + sprite = 5, + size = 1, + hurt = { + x_off = 1, + y_off = 2, + width = 6, + height = 5, + }, + sparks = smokespark, + sparkodds = 8, + hp = 2, + shield = 1, + maxshield = 1, + shieldcost = 180, + + fire_off_x = 4, + fire_off_y = 7, + + maxspd = 2, + thrust = 0.2, + drag = 0.075, + slip = true, +} +mknew(chasey, function(ship) + ship.main_gun=ship.main_gun or zap_gun.new{enemy=true} +end) + +function chasey:act() + local dx = 0 + if (self.x < primary_ship.x) dx=self.thrust + if (self.x > primary_ship.x) dx=-self.thrust + return dx, 0, false, self.x - 16 < primary_ship.x and self.x + 16 > primary_ship.x +end + +xl_chasey=chasey.new{ + size=2, + maxspd=1.25, + hurt = { + x_off = 2, + y_off = 4, + width = 12, + height = 10 + }, + hp = 20, + 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, +} +mknew(xl_chasey, function(ship) + ship.main_gun=ship.main_gun or zap_gun.new{enemy=true} +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.width8 +-- 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_bonus_frownie() + local f = spawn_frownie() + f.sprite = 7 + f.die = function(self) + spawn_repair_at(self.x+4, self.y+4) + frownie.die(self) + end +end + +function spawn_bonus_vulcan_chasey() + local c = spawn_chasey() + c.main_gun=vulcan_gun.new{enemy=true} + c.die = function(self) + spawn_main_gun_at(self.x-1, self.y-1, vulcan_gun) + chasey.die(self) + end + c.sprite=4 + return c +end + +helpers = { + spawn_frownie, + spawn_frownie, + spawn_frownie, + spawn_blocky, + spawn_blocky, + spawn_chasey, + spawn_spewy, +} + +function spawn_blocking_boss_chasey() + 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) + spawn_goodie(goodie, self.x, self.y, self.size) + end, + } + if (altspr) s.spr = altspr + eships:push_back(s) + return s +end + +-- TODO: spawn_goodie compatible versions of gun drops +-- TODO: goodie table +function spawn_goodie(goodie_name, x, y, sz) + if (not goodie_name or #goodie_name == 0) return + local sh = sz and sz/2 or 0 + goodies[goodie_name].new{}:spawn_at(x+sh,y+sh) +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_bonus_vulcan_chasey +61,spawn_blocky +85,spawn_spewy +100,spawn_spewy +115,spawn_spewy +130,spawn_bonus_frownie +145,spawn_spewy +200,spawn_chasey +250,spawn_blocking_blocky +285,spawn_spec_gun_at,35,-11,blast_gun +310,spawn_blocking_blocky +310,spawn_blocking_blocky +310,spawn_blocking_blocky +311,spawn_frownie +350,spawn_main_gun_at,70,-11,protron_gun +401,spawn_frownie +420,spawn_blocking_frownie +430,spawn_bonus_vulcan_chasey +450,spawn_frownie +465,spawn_bonus_frownie +480,spawn_chasey +500,multi,20,12,spawn_blocking_blocky +501,spawn_bonus_frownie +620,spawn_blocking_blocky +700,spawn_blocking_boss_chasey +701,eol]] + +-->8 +-- readme.md + +--[[ + +main loop sequence +================== +1. level_frame +2. events +3. merge new_events into events +4. update bg intangibles +5. move ships (player first) +6. move bullets (player first) +7. calculate collisions + 1. pship on eship + 2. ebullet on pship + 3. pbullet on eship +8. update fg intangibles +9. check for end of level + +draw order +---------- +bottom to top: +1. intangibles_bg +2. player bullets +3. player ships +4. enemy ships +5. enemy bullets +6. intangibles_fg + +notes +----- +intangibles_fg move()s after +all collisions and other moves +are processed. if an intangible +is added to the list as a result +of a collision or move, it will +itself be move()d before it is +drawn. + +data-driven items +================= +guns and bullets both allow the +most common behaviors to be +expressed with data alone. +ships only need a movement +algorithm expressed. + +guns +---- +* power - cost in generator + power to fire. may be 0. + field directly read by ships; + required in all guns. +* t - metatable for bullet type. + fired once in the bullet's + default direction per shot. +* enemy - if true, fired bullets + are flagged as enemy bullets. +* icon - sprite index of an + 8x8 sprite to display in the + hud when the player has this + gun. default is 20, a generic + crosshair bullseye thing. +* cooldown - min frames between + shots. +* ammo, maxammo - permitted + number of shots. 0 is empty + and unfireable. maxammo = 0 + will cause a divide by zero + so don't do that. if nil, + ammo is infinite. + +default guns manage ammo and +cooldown in shoot, then call +actually_shoot to create the +projectile. override only +actually_shoot to change +projectile logic while keeping +cooldown and ammo logic. + +ships manage generator power +before asking the gun to shoot. +this behavior is in +ship_m:maybe_shoot. + +bullets +------- +* dx, dy - movement per frame. + player bullets use -dy + instead. +* enemyspd - multiplier for dx + and dy on enemy bullets. + default is 0.5, making enemy + shots much easier to dodge +* damage - damage per hit; + used by ships +* psprite, esprite - index of + player or enemy sprite. +* center_off_x - the horizontal + centerpoint of the bullet, + for positioning when firing. + assume a pixel's coordinates + refer to the upper left corner + of the pixel; the center of + a 2-width bullet with an + upper left corner at 0 is 1, + not 0.5. +* top_off_y, bottom_off_y - + also for positioning when + firing. positive distance from + top or bottom edge to image. + top_off_y will usually be 0, + bottom_off_y will not be when + bullets are smaller than + the sprite box. +* width, height - measured in + full sprites (8x8 boxes), not + pixels. used for drawing. + +bullets despawn when above or +below the screen (player or +enemy bullets, respectively). + +by default, bullets despawn +when they hit something. +override hitship to change this. + +ships +____ + +ships move by calculating +momentum, then offsetting their +position by that momentum, then +clamping their position to the +screen (horizontally only for +ships that autoscroll). ships +that autoscroll (slip==true) +then slide down by scrollspeed. +fractional coordinates are ok. +after movement, ships lose +momentum (ship.drag along each +axis). abs(momentum) can't +exceed ship.maxspeed. + +ships gain momentum by acting +like a player pushing buttons. +the player ship actually reads +buttons for this. + +act -- returns new acceleration: +dx, dy, shoot_spec, shoot_main. +dx and dy are change in momentum +in px/frame. this is controls +only -- friction is handled in +ship:move (`drag` value). + +ships hitting another ship take +1 damage per frame of overlap. +ships hitting a bullet check +bullet.damage to find out how +much damage they take. damage +is applied to shields, then hp. +damaged ships flash briefly - +blue (12) if all damage was +shielded, white (7) if hp was +damaged. a ship that then has 0 +or less hp calls self:die() and +tells the main game loop to +remove it. + +ships have power, from 0 to +ship.maxpower, increasing by +ship.generator per frame. +in maybe_shoot, ships check that +they have power to fire before +trying to fire (the gun itself +checks ammo and cooldown), and +spend that power if they fire. + +power is also used to restore +shields - ship.shieldcost per +point of shields. shieldcooldown +is the interval between +restoring shield points, which +is reset when a ship takes +damage (regardless of whether +that damage is stopped by the +shield or not). + +therefore: +* damaged ships spend power + repairing shields, which may + affect ability to fire guns. + this looks like a slow firing + rate because the ship will + eventually recover enough + energy to fire. +* a ship firing nonstop will + typically be unable to recover + any shields because it will + not have energy to do so. + +ships do not repair hp on their +own. negative-damage bullets +are treated as 0, but a bullet +can choose to repair the ship +it hits in its own hitship +method, or otherwise edit it +(changing weapons, refilling +weapon ammo). powerups are +therefore a kind of bullet. + +levels +====== + +a level is a table mapping +effective frame number to +functions. when a level starts, +it sets lframe ("level frame") +and distance to 0. + +every frame, level_frame +increments lframe by 0x0.0001. +then if the level is not frozen, +it increments distance by 1.0 +and runs the function in the +level table for exactly that +frame number (if any). distance +is therefore "nonfrozen frames", +and is used to trigger level +progress. lframe always +increments. ships are encouraged +to use lframe to control +animation and movement, and may +use distance to react to level +progress separately from overall +time. remember to multiply +lframe-related stuff by 0x0001. + +a special sentinel value, eol, +marks the end of the level. +(the level engine doesn't know +when it's out of events, so +without eol, the level will +simply have no events forever.) +when it finds eol, level_frame +throws away the current level +and tells the main loop that it +might be done. the main loop +agrees the level is over and the +player has won when the level +has reached eol and there are +no more enemy ships, enemy +bullets, or background events +remaining. player ships, player +bullets, and intangibles are +not counted. + +level freezing +-------------- +the level is frozen when the +global value freeze > 0. +generally, something intending +to block level progress (a +miniboss, a minigame, etc.) +increments freeze and prepares +some means of decrementing it +when it no longer wants to block +level progress. + +most commonly, we want to block +until some specific ship or +group of ships has died. for +these ships, override ship:die +to decrement freeze. make sure +to set ship.dead in any new +ship:die method so anything else +looking at it can recognize +the ship as dead. + +for anything else, you probably +want an event to figure out when +to unfreeze. + +levels start at 1 +----------------- + +distance is initialized to 0 +but gets incremented before the +first time the engine looks for +events. therefore, the first +frame of the level executes +level[1]. since levelframe +executes before anything else, +level[1] sets up the first frame +drawn in the level. the player +does not see a blank world +before level[1] runs. +level[1] can therefore be used +to reconfigure the player ship, +set up backgrounds, start music, +kick off some kind of fade-in +animation, etc. + + +events +====== +the global list "events" stores +0-argument functions which are +called every frame. if they +return true, they are removed +from the list and not run again; +if they return false, they stay +and will be called in later +frames. the level does not end +while the events table is +nonempty. + +events are most commonly used +to set up something for later +(for example, blip uses an event +to remove the fx_pallete from +the flashing ship when the blip +expires), but can also be used +to implement a "level within a +level" that does something +complicated until it's done. if +you froze the level when +creating the event, remember +to thaw it (freeze -= 1) on all +paths that return true. + +to do complex stuff in events, +use a closure or a metatable +that specifies __call. + +to avoid editing the events +list while it is being iterated, +events that create new events +must add those events to +new_events rather than events. +new_events is only valid during +the "event execution" stage, so +events created at any other time +must go directly on events +without using new_events. + +intangibles +=========== + +the intangibles_fg and +intangibles_bg lists contain +items with :move and :draw. +like ships and bullets, they +move during _update60 and +draw during _draw. they are +not checked for collisions. + +intangibles_bg moves/draws +before anything else moves or +draws. intangibles_fg +moves/draws last. this controls +whether your intangible object +draws in front of or behind +other stuff. you probably want +intangibles_bg for decorative +elements and intangibles_fg +for explosions, score popups, +etc. + +there's no scrolling background +engine but intangibles_bg could +be used to create one, including +using the map (otherwise unused +in this engine) for the purpose. + +intangibles do not prevent the +level from ending. like bullets +and ships, if :move returns +true, they are dropped. +]] +-->8 +-- standard events + +blip_fx = { + 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 + +mknew(blip_fx) + +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, frames) + obj.fx_pal = blip_pals[col] + if (obj.___fx_pal_event) obj.___fx_pal_event:abort() + events:push_back(blip_fx.new{frames=frames, 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(spark_particle) + +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 + +powerup = bullet_base.new{ + -- animated sprite array: "sprites" + -- to draw under or over anim, + -- override draw, draw the + -- under-part, call into + -- powerup.draw(self), then + -- draw the over-part + width = 1, + height = 1, + -- note: make hurtboxes larger + -- than sprite by 2px per side + -- since ship hitbox is tiny + -- but powerups should feel + -- easy to pick up + dx = 0, + dy = 1.5, -- 0.75 after enemyspd + enemy = true, -- collides with player ship + damage = 0, + + anim_speed = 2, + loop_pause = 30 -- affected by animspeed +} +mknew(powerup) + +-- sprite indexes for "sheen" animation +sheen8x8 = split"2,54,55,56,57,58,59,60,61" + +-- todo: draw two sprites +-- on top of each other here +-- so all powerups can share +-- the "sheen" animation? + +function powerup:draw() + spr(self.sprites[max(1, + ((lframe<<16)\self.anim_speed) + %(#self.sprites+self.loop_pause) + -self.loop_pause + +1)], + self.x, self.y, + self.width, self.height) +end + +repair = powerup.new{ + hurt = { + x_off = -2, + y_off = -2, + width = 12, + height = 12 + }, + center_x_off = 4, + top_y_off = 0, + bottom_y_off = 0, + sprites = sheen8x8, + hitship = function(self, ship) + if (ship ~= primary_ship) return false + primary_ship.hp = min(primary_ship.maxhp, primary_ship.hp + 1) + return true + end, + draw = function(self) + spr(53, self.x, self.y, self.width, self.height) + powerup.draw(self) + end +} +mknew(repair) + +function spawn_repair_at(x, y) + repair.new():spawn_at(x, y) +end + +gun_swap = powerup.new{ + hurt = { + x_off = -2, + y_off = -2, + width = 16, + height = 16 + }, + -- gun = gun_type.new{} + center_x_off = 6, + top_y_off = 0, + bottom_y_off = 4, + width = 2, + height = 2, + sprites = {64, 66, 68, 70, 72, 74, 76, 78}, + hitship = function(self, ship) + if (ship ~= primary_ship) return false + ship.main_gun = self.gun + return true + end, + draw = function(self) + powerup.draw(self) + spr(self.gun.icon, self.x+2, self.y+2, 1, 1) + end +} +mknew(gun_swap) + +function spawn_main_gun_at(x, y, gunt) + if (type(gunt)=="string") gunt=_ENV[gunt] + local gun_p = gun_swap.new{ + gun = gunt.new() + } + gun_p:spawn_at(x, y) +end + +spec_gun_pl = { + [1] = 2, + [14] = 6, + [2] = 14 +} + +function spawn_spec_gun_at(x, y, gunt) + if (type(gunt)=="string") gunt=_ENV[gunt] + local gun_p = gun_swap.new{ + gun = gunt.new(), + hitship = function(self, ship) + if (ship ~= primary_ship) return false + ship.special_gun = self.gun + return true + end, + draw = function(self) + pal(spec_gun_pl) + powerup.draw(self) + pal() + spr(self.gun.icon, self.x+2, self.y+2, 1, 1) + end + } + gun_p:spawn_at(x, y) +end +__gfx__ +00000000000650000000000000000000bb0b50b59909209200cc0c00000000003b00000082000000e00e8002e00e800200333300002222000000000000000000 +00000000006765000000000000cccc00b50b3055920940220c0000c000bbbb0037000000a2000000e0e8880240e8480403bbbb30028888200000000000000000 +00700700006d6500000000000cddddd00b33335009444420c00c000c0b333330b7000000a8000000e88e2882e48e24823bbaabb3288aa8820000000000000000 +00077000067c665000000000cdd10cd10b3dd350094dd42000c0000cb3350b35b7000000a8000000e88e2882484e24423ba77ab328a77a820000000000000000 +00077000067d665000000000cd10cdd100b3350000944200c0000000b350b335b7000000a8000000e88e2882e84e28823ba77ab328a77a820000000000000000 +0070070065666765000000000ddddd100b33355009444220c000000c03333350b7000000a800000008888820048488203bbaabb3288aa8820000000000000000 +000000006506506500000000001111000b0b5050090920200c0000c00055550037000000a2000000008882000048420003bbbb30028888200000000000000000 +00000000650000650000000000000000000b50000009200000c0cc00000000003b00000082000000000820000008200000333300002222000000000000000000 +00000000000650000006500000000000b000000b80000000700000000bb0000008800000000000000009200000000000cccccccd000650000000000000000000 +0000000000675000000765000000000000bbbb0080000000b0000000b76300008a920000000000009009200200000000c111111d006765000000000000000000 +00000000006d6500006d6500000000000b0000b09000000030000000b663000089920000000550009994444200000000c111111d006d65000000000000000000 +00000000067c6650067c6650000000000b0bb0b0a000000030000000033000000220000000576d009446544200000000c111111d067c66500000000000000000 +00000000067d6650067d6650000000000b0bb0b00000000000000000000000000000000000566d009244442200000000c111111d067d66500000000000000000 +000000005666657576667650000000000b0000b000000000000000000000000000000000000dd0009092220200000000c111111d656667650000000000000000 +0000000056565066665656500000000000bbbb0000000000000000000000000000000000000000000090020000000000c111111d650650650000000000000000 +00000000565000566500065000000000b000000b000000000000000000000000000000000000000000a00a0000000000cddddddd650000650000000000000000 +000000000000000000000000000000000000000000a0008000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000090008000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000800a0000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000080090000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000a080000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000009080000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000cccccccc77000000007700000000770000000077000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000c11ee11d70000000077000000007700000000770000000070000000000000000000000000000000000000000 +0000000000000000000000000000000000000000c11ee11d00000000770000000077000000007700000000770000000700000000000000000000000000000000 +0000000000000000000000000000000000000000ceeeeeed00000000700000000770000000077000000007700000007700000000000000000000000000000000 +0000000000000000000000000000000000000000ceeeeeed00000000000000007700000000770000000077000000077000000007000000000000000000000000 +0000000000000000000000000000000000000000c11ee11d00000000000000007000000007700000000770000000770000000077000000000000000000000000 +0000000000000000000000000000000000000000c11ee11d00000000000000000000000077000000007700000007700000000770000000070000000000000000 +0000000000000000000000000000000000000000cddddddd00000000000000000000000070000000077000000077000000007700000000770000000000000000 +cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000cccccccccccc0000 +c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000ceee2222eeed0000c2221111222d0000 +c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c1ee2222ee1d0000ce22111122ed0000c2111111112d0000 +c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c11e2222e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000 +c1111111111d0000c1111111111d0000c1111ee1111d0000c11ee22ee11d0000c1e221122e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000 +c1111111111d0000c1111ee1111d0000c111e22e111d0000c11e2112e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000 +c1111111111d0000c1111ee1111d0000c111e22e111d0000c11e2112e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000 +c1111111111d0000c1111111111d0000c1111ee1111d0000c11ee22ee11d0000c1e221122e1d0000ce21111112ed0000c2111111112d0000c1111111111d0000 +c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c11e2222e11d0000c1e211112e1d0000ce21111112ed0000c2111111112d0000 +c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000c1ee2222ee1d0000ce22111122ed0000c2111111112d0000 +c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c1111111111d0000c111eeee111d0000ceee2222eeed0000c2221111222d0000 +cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000cddddddddddd0000 +__label__ +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007777777777777777 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666666666666665 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666666666666665 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666611111666665 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666115151166665 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666111611156665 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666115161156665 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666611111556665 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666665555566665 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666666666666665 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007655555555555565 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007650b000000b0765 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000765000bbbb000765 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000076500b0000b00765 +00000000000000000000000000000000000000000000e00e800200000000000000000000000000000000000000000000000000000000000076500b0bb0b00765 +00000000000000000000000050000000000000000000e0e8880200000000000000000000000000000000000000000000000000000000000076500b0bb0b00765 +00000000000000000000000000000000000000000000e88e288200000000000000000000000000000000000000000000000000000000000076500b0000b00765 +00000000000000000000000000000000000000000d00e88e2882000000000000000000000000000000000000000000000000000000000000765000bbbb000765 +00000000000000000000000000000000000000000000e88e28820000000000000000000000000000000000000000000000000000000000007650b000000b0765 +00000000000000000000000000000000000000000000088888200000000000000000000000000000000000000000000000000000000000007650000000000765 +00000000000000000000000000000000000000000000008882000000000000000000000000000000000000000000000000000000000000007657777777777765 +00000000000000000000000000000000000000000000000820000000000000000000000000000000000000000000000000000000000000007666666666666665 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007655555555555565 +0000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000765aeaeaeaeae765 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000765eaeaeaeaea765 +0000000000000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000765aeaeaeaeae765 +00000000000000000000000000000000000000000000000000000009909209200000000000000000000000000000000000000000000000007657777777777765 +00000000000000000000000000000000000000000000000000000009209402200000000000000000000000000000000000000000000000007666666666666665 +00000000000000000000000000000000000000000000000000000000944442000000000000000000000000000000000000000000000000007666666666666665 +0000000000000000000000000000000000000000000000000000000094dd42000000000000000000000000000000000000000000000000007666666666666665 +00000000000000000000000000000000000000000000000000000000094420000000000000000000000000000000000000000000000000007666622222666665 +00000000000000000000000000000000000000000000000000500000944422000000000000000000000000000000000000000000000000007666225552266665 +00000000000000000000000000000000000000000000000000000000909202000000000000000000000000000000000000000000000000007666225262256665 +00000000000000000000000000000000000000000000000000000000009200000000000000000000000000000000000000000000000000007666225652256665 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666622222556665 +00000000000000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666665555566665 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666666666666665 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007655555555555565 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 +00000000000000000005000000009200000000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 +00000000050000000000000009009200200000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 +000000000000000000d0000009994444200000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 +00000000000000000000000009446544200000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 +00000000000000000000000009244442200000000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 +0000000000000000000000000909222020000000000000000bbbb000000000000000000000000000000000000000000000000000000000007650000000000765 +000000000000000000000000000900200000000000000000b3333300000000000000000000000000000000000000000000000000000000007650000000000765 +000000000000000000000000000a00a0000000000000000b3350b350000000000000000000000000000000000000000000000000000000007650000000000765 +00000000000000000000000000000000000000000000000b350b3350000000000000000000000000000000000000000000000000000000007657777777777765 +00000000000000000000000000000000000000000000000033333500000000000000000000000000000000000000000000000000000000007666666666666665 +00000000000000000000000000000000088000000000000005555000000000000000000000000000000000000000000000000000000000007655555555555565 +000000000000000000000000000000058a9200000000000000000000000000000000000000000000000000000000000000000000000000007650000000000765 +00000000000000000000000000000000899200000000000000000000008200000000000000000000000000000000000000000000000000007650000000000765 +0000000000000000000000000000000002200000000000000000000000a200000000000000000000000000000000000000000000000000007650000000000765 +0000000000000000000500000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007657777777777765 +0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665 +0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665 +0000000000000000000000000088000000000050000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665 +20000000000000000000000008a9200000000000000000000000000000a200000000000000000000000000000000000000000000000000007611161616111665 +20000000000000000000000008992000000000000000000000000000008200000000000000000000000000000000000000000000000000007615151515151565 +000000000000000000000000002200000d0000000000000000000000000000000000000000000000000000000000000000000000000000007611151515116565 +0000000000000000000000000000000000000e00e800200000000000e00e80020000000000000000000000000000000000000000000000007615551115151665 +0000000000000000000000000000000d00000e0e8880200000000000e0e888020000000000000000000000000000000000000000000000007615661115151565 +0000000000000000000000000000000000d00e88e2882000000d0000e88e28820000000000000000000000000000000000000000000000007665666555656565 +0000088000000000000880000000000000000e88e288200000000000e88e28820000000000000000000000000000000000000000000000007666666666666665 +00008a9200000000008a92000000000000000e88e288200000000000e88e28820000000000000000000000000000000000000000000000007655555555555565 +0000899200000000008992000000000000000088888200000000000008888820000000000000000000000000000000000000000000000000765aaaaaaaaaa765 +0000022000000000000220000000000000000008882000000000000000888200000000000000000000000000000000000000000000000000765aaaaaaaaaa765 +0000000000000000000000000000000000000000820000000000000000082000000000000000000000000000000000000000000000000000765aaaaaaaaaa765 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000765aaaaaaaaaa765 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000765a9a9a9a9a9765 +00000000000088000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659a9a9a9a9a765 +000000000008a920000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000765a9a9a9a9a9765 +00000000000899200000000000000000000008800000000000000000000000000000000000000000000000000000000000000000000000007659a9a9a9a9a765 +0000000000002200000000000000000000008a92000000000000000000000000000000000000000000000000000000000000000000000000765a9a9a9a9a9765 +00000000000000000000000000000000000089920000000000000000000000000000000000000000000000000000000000000000000000007659999999999765 +00000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000000000000007659999999999765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659999999999765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659999999999765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659999999999765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654949494949765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659494949494765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654949494949765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007659494949494765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654949494949765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654444444444765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654444444444765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654444444444765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007654444444444765 +00000000000000000000000000000000000000000000000000000000008200000000000000000000000000000000000000000000000000007654444444444765 +0000000000000000000000000000000000000000000000000000000000a200000000000000000000000000000000000000000000000000007654444444444765 +0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007657777777777765 +0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665 +0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665 +0000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000007666666666666665 +0000000000000000000000000000000000000000000000000000000000a200000000000000000000000000000000000000000000000000007666666666666665 +00000000000000000000000000000000000000000000000000000000008200000000000000000000000000000000000000000000000000007616166666611665 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007615156666165565 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007611156666111665 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007615156666651565 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007615156666116565 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007665656666655665 +00000000000000000000000000000000000000000000000000000000000000000008800000000000000000000000000000000000000000007666666666666665 +0000000000000000000000000000000000000000000000000000000000000000008a920000000000000000000000000000000000000000007655555555555565 +00000000000000000000000000000000000000000000000000000000000000000089920000000000000000000000000000000000000000007652222750000765 +00000000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000007652222750000765 +00000000000000000000000000000000000000000000000000000000006500000000000000000000000000000000000000000000000000007652222750000765 +00000000000000000000000000000000000000000000000000000000067650000000000000000000000000000000000000000000000000007652222750000765 +0000000000000000000000000000000000000000000000000000000006d650000000000000000000000000000000000000000000000000007652222750000765 +0000000000000000000000000000000000000000000000000000000067c665000000000000000000000000000000000000000000000000007652222750000765 +0000000000000000000000000000000000000000000000000000000067d665000000000000000000000000000000000000000000000000007652828750000765 +00000000000000000000000000000000000000000000000000000006566676500000000000000000000000000000000000000000000000007658282750000765 +00000000000000000000000000000000000000000000000000000006506506500000000000000000000000000000000000000000000000007652828750000765 +00000000000000000000000000000000000000000000000000000006500006500000000000000000000000000000000000000000000000007658282750000765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007652828750000765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658282750000765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007658888750000765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007657777757777765 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007666666666666665 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007555555555555555 +