diff --git a/last_tyrianlike.p8 b/last_tyrianlike.p8 new file mode 100644 index 0000000..e5712bc --- /dev/null +++ b/last_tyrianlike.p8 @@ -0,0 +1,2164 @@ +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" +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,primary_ship.main_gun) + draw_gun_info("🅾️",1,116,31,primary_ship.special_gun) + + dropshadow("p h",114,59,1) + inset(114,66,119,125) + fillp(0x5a5a) + vertmeter(115,67,118,124,primary_ship.power, primary_ship.max_power, powcols) + + inset(120,66,125,125) + -- 57 px vertically + 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 = 57 * (mxs / (mxs + mxh)) \ 1 + 66 + line(121, split, 124, split, 0xba) + vertmeter(121,67,124,split-1,cs, mxs,shlcols) + vertmeter(121,split+1,124,124,ch, mxh, hpcols) + elseif mxs > 0 then + vertmeter(121,67,124,124,cs,mxs,shlcols) + elseif mxh > 0 then + vertmeter(121,67,124,124,ch,mxh,hpcols) + else + print("!", 122, 94, 9) + print("!", 121, 93, 8) + end + 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) or (maxval <= 0)) return + local h = y1-y0 + local px = val/maxval * h \ 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,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.003c,--1s + shieldpenalty = 0x0.012c, --5s + + max_power = 120, + power = 120, + generator = 2, -- power gen per frame + + slip = true, -- most enemies slide + + xmomentum = 0, + ymomentum = 0, + + -- xmin, xmax, ymin, ymax: + -- movement constraints + -- enforced by `constrain`. + xmin = 0, xmax = 104, + -- ymin, ymax default to nil + -- pship needs more constraint +} +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: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() + self.power = min(self.max_power, self.power + self.generator) + local dx, dy, shoot_spec, shoot_main = 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) + 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 = self:calc_velocity(self.xmomentum, dx) + self.ymomentum = self:calc_velocity(self.ymomentum, dy) + + self.x += self.xmomentum + self.y += self.ymomentum + + -- "scrolling" behavior + if self.slip then + self.y += scrollrate + if self.y >= 128 then + self:die() + return true + end + end + return false +end + +function ship_m:draw() + if(self.fx_pal) pal(self.fx_pal) + spr(self.sprite, self.x, self.y, self.size, self.size) + pal() +end + +function hurtbox(ship) + local h = ship.hurt + return { + x=ship.x + h.x_off, + y=ship.y + h.y_off, + width=h.width, + height=h.height + } +end + +function ship_m:maybe_shoot(gun) + if (not gun) return + 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.shieldpenalty + 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.shield = min(self.shield, self.maxshield) + 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 = 60, + 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 = 60, -- power cost to refill shield + generator = 2, + + -- 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 + ymin = 0, ymax = 120, -- stay on screen + 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() + -- ONE HIT MODE + -- + -- p.hp = 0 + -- p.maxhp = 0 + -- p.shield = 0 + -- p.maxshield = 0 + 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 = 0.5, -- 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 = 1.5, + 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=0.5, + 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 = 1.5, + 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() + self.xmin = max(primary_ship.x-8, 0) + self.xmax = min(primary_ship.x + 8, 112 - 8*self.size) + return 0, 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 = 19.5, + shield = 5, + boss = true, + slip = false, + act = function(self) + local dx,dy,shoot_spec,shoot_main = chasey.act(self) + if (self.y < 4) dy=self.thrust + return dx,dy,shoot_spec,shoot_main + end, + draw = function(self) + if(self.fx_pal) pal(self.fx_pal) + sspr(40, 0, 8, 8, self.x, self.y, 16, 16) + pal() + end, +} +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 + _ENV[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 to shieldpenalty when a +ship takes damage (regardless of +whether that damage is stopped +by the shield or not). +shieldpenalty is much worse than +shieldcooldown (hALO shield). + +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 +