81 Commits

Author SHA1 Message Date
58da8e6dc3 fix hokey pokey menu
the menu eases in, the menu eases out, the menu eases in and you shake it all about
2025-01-20 18:02:50 -08:00
8ff0732cbc draw rearm pane; exit behavior is broken
Nil transparency has screwed me over; I am uploading this version as
an illustrative example
2025-01-20 17:43:21 -08:00
c88e7c0657 I really wish Lua had real classes rather than forcing you to assemble it yourself 2025-01-20 17:09:47 -08:00
ff3552bc45 fix unit error for xp frame logic 2025-01-20 17:02:02 -08:00
2dcb95b0cd menu prototype 2025-01-20 16:59:57 -08:00
87451bbd3a incomplete start to porting ui 2025-01-12 22:58:20 -08:00
89a42e6c8b improve xp gem art 2025-01-12 21:51:32 -08:00
e2be11a2da fix order of magnitude error in XP logic 2025-01-12 21:47:11 -08:00
175099d778 make maxval work better near p8 precision limits 2024-12-29 23:44:39 -08:00
33fede4ed8 update comment about off-by-1 error 2024-12-29 23:41:47 -08:00
afa1f22170 go back to 0x0.0001 increments for xp
I foresee the 32K limit being a bigger problem than the math in vertmeter, although I will probably need to go patch vertmeter up too
2024-12-29 23:39:56 -08:00
78b200272e fix shield crash 2024-12-29 23:33:45 -08:00
42ac2abc20 groundwork for full mode switching 2024-12-29 23:26:48 -08:00
c55ea000fd whoosh animation when level up pending 2024-12-28 19:56:42 -08:00
2c1ad0a0b3 drop xp gems 2024-12-28 19:27:54 -08:00
e0b784ce7d clean up, run at 60 fps
now the bounce animation feels like it takes too long, trying to fix it
2024-12-26 17:43:04 -08:00
e1a70cc6fc dark blue, not dark gray, for pane bg 2024-12-26 17:16:48 -08:00
cbdf2a27cd fast quadratic exit feels better 2024-12-26 17:05:55 -08:00
caaf848722 fixed it 2024-12-26 16:59:43 -08:00
25f58d5cce messed up entry animation but it's a start
* wrong Y positions for everything
* both directions are "enter"
2024-12-26 16:19:52 -08:00
c15ec61494 partial prototype of object-oriented drawing and entry 2024-12-26 12:32:31 -08:00
7ff5cf97ad functioning prototype 2024-12-24 19:23:14 -08:00
f761d1a172 prototype for REARM screen UI 2024-12-24 19:04:07 -08:00
98f56328a6 off by 1 2024-12-24 18:10:48 -08:00
93792c36c9 sketch for possible REARM ui blank 2024-12-24 18:10:17 -08:00
d799947c46 it would hlep if I actually removed the old text 2024-12-24 15:06:18 -08:00
f3a84573e6 remove readme from .p8 into a separate .md file 2024-12-24 15:04:23 -08:00
1a7bc7094e HP/XP labels look much better and correctly-associated now 2024-10-27 13:16:37 -07:00
39d53c45aa xp and hp brighter and closer to meters 2024-09-07 16:36:30 -07:00
5870c129eb staggered "xp", "hp" labels in UI 2024-09-07 16:33:58 -07:00
68863280f3 stagger the bars 2024-09-07 16:17:38 -07:00
3f7c4f59c0 stretch meter area 2024-09-07 16:15:00 -07:00
804eb62ae7 p is now x in ui
"x" looks pretty bad with a drop shadow! will tweak soon anyway
2024-09-07 16:07:40 -07:00
303148876d Remove power mechanics. Replace with XP stub.
Also a few random comments and cleanups along the way.
2024-09-07 16:04:01 -07:00
b379e47dbf bonus shield powerup
mostly to test whether redistributing the shield and health meters works
2024-09-02 15:22:33 -07:00
4ca3913637 it's still tyrian-like so update last_tyrianlike 2024-09-02 15:09:27 -07:00
f9ba59d992 refactor mknew
saves tokens, harder to forget to use it
2024-09-02 15:08:58 -07:00
dd143060ac squash: refactor bullets to remove enemy flag
commit ead2a7d874
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Mon Sep 2 14:45:56 2024 -0700

    fix remaining `vulcan`-family bug

commit 571412b15e
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Mon Sep 2 14:15:56 2024 -0700

    fix chasey xl offsets

commit 907bd8318c
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Mon Sep 2 14:12:35 2024 -0700

    several more fixes, now runs to the end but shot offset is wrong

commit 7170552448
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Mon Sep 2 13:41:42 2024 -0700

    first three waves of bug fixes

commit 01ab6d3969
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Mon Sep 2 12:59:49 2024 -0700

    maybe the rest of the refactor?

commit 7869192dee
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Tue Aug 20 01:21:13 2024 -0700

    partial refactor continued

commit a4658e3ef4
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Mon Aug 19 16:00:16 2024 -0700

    halfway through bullet refactor
2024-09-02 14:46:51 -07:00
60b685d94b save a copy of vacuum_gambit.p8 as last_tyrianlike
next step is to pick a real direction -- MMBN-like (+STS-like) or
Survivors-like -- and adapt to match. I am likely to completely remove
the energy system and use permanent autofire, freeing both fire buttons
for more interesting tasks. It loses the opportunity to create a dynamic
around baiting an enemy to keep shooting so its shields don't recover,
but I don't think it loses a lot else.

Either energy management needs to become really important and the game
becomes strategic and tactical, or it needs to be a non-issue and it is
an arcade game. Tyrian itself did not make the energy system interesting
and it was just a tax, so making it interesting would be doing something
new. But I think it's a kind of "interesting" that almost nobody would
adopt unless I go _very hard_ into creating a tactical/strategic shmup.
A shmup that is actually a strange kind of RTS sounds... really cool,
actually, but I'm not at all confident I could design it.

Removing energy entirely gives us a button _and_ a meter back, which
can be used for XP (Survivors-like) or rearm time (MMBN-like).
2024-08-18 15:04:17 -07:00
cc1e7ea5b7 surive at 0hp and adjust hp values to match
Instead of doing a special case for 1HP, 0HP is survivable, ships die
at negative HP instead. all ship health is adjusted to match, assuming
the weakest shot is 0.5hp, which is currently true. the "real" game will
totally rebalance all ships and weapons anyway.

we're getting close to when I have to stop dawdling and implement the
real game, the engine is _there._
2024-08-18 14:57:29 -07:00
6b8efe3438 fix HP-only mode
it was showing the bar intended as the warning that there's no HP
under the shield. but I tried it and that bar just makes it look like
there *is* a sliver of health, which there isn't. so it's better off
without that in either mode.
2024-08-18 14:49:44 -07:00
c2668cefea handle 0 shield and 1 max HP cases 2024-08-18 14:40:11 -07:00
eebd84544b one hit comment now shows correct value to use
some guns do less than one damage per shot (vulcan gun does 0.5), so
Instant Death Mode needs to max at 0x0.0001, the smallest nonzero value
in Pico-8's fixed-point numeric type.

One Hit Mode is just a comment for now, but I've been uncommenting it
to test it. Note that replacing the health meter with a "!" is triggered
by max HP + max shield <= 1 because 1 hp shows an empty bar.

this needs some more special cases for low-HP ships with active shields.
2024-08-18 01:57:53 -07:00
965fc0d688 major rebalances
10s generator is too slow -- 10 seconds ago is an eternity in a shmup
and a player who has stopped firing should recover much faster. The
generator's max capacity is much lower and shield cost has been
rebalanced to match.

The Protron is much more expensive to fire, it was previously just
easy mode.

Shields now recover faster _once they start recovering_ (every second
if energy is available) but getting hit causes a "penalty cooldown"
that is much longer than the standard recovery interval. This behavior
is taken from Halo and basically every modern FPS that came after it;
it's unlike Tyrian, which had consistent shield recovery behavior.
But I think Halo's rule plays much better.
2024-08-18 01:46:39 -07:00
cc3ed20f76 fix overshield 2024-08-18 01:29:27 -07:00
fa0cff1ffc one hit mode, fix vertmeter
full height meters overflowed p8num range
2024-08-18 01:28:12 -07:00
4f8b861cdb okay I special cased it 2024-08-18 01:14:05 -07:00
5dc259c094 the Secret Hit Point: you have 1hp when the meter is empty
this won't work if your maxhp is 1, will need to special case that
2024-08-18 01:13:25 -07:00
51629376f2 adjust HUD
Health and shields now share one bar. one hit point is (about) the same
size in each. There is an indicator splitting the two categories of HP.
2024-08-18 01:10:20 -07:00
c5e49740c4 reorganize UI 2024-08-17 23:22:42 -07:00
59738d0376 use constraints to make chasey chase; now it is not wiggly 2024-08-16 19:48:44 -07:00
f736f50870 port improvements to autobrake_test too, excluding the weird calc_targets thing 2024-08-16 19:37:43 -07:00
ccb897af24 fix calculation 2024-08-16 19:37:05 -07:00
c130f4cf52 actually invoke calc_targets 2024-08-16 19:25:10 -07:00
cf48497432 ymin and ymax for player 2024-08-16 19:23:20 -07:00
9dc36a95ee port ship constraints logic 2024-08-16 19:20:30 -07:00
d33d7ad6d1 xmax constraint -- imperfect but good enough 2024-07-27 18:49:48 -07:00
00678f97fd Fix brake location calculation; was applying it to the wrong spot. 2024-07-27 17:47:29 -07:00
8fa98e3132 handle undershot and ok cases 2024-01-28 01:33:25 -08:00
5d2dafa64c I am baffled I never noticed this before 2024-01-27 13:25:47 -08:00
bd61ca2639 remove spark stub, not worth it in the test 2024-01-20 11:52:41 -08:00
54426be303 now it actually works! 2024-01-14 20:06:09 -08:00
84a803b828 fixed it! mostly 2024-01-14 20:02:45 -08:00
a4ed50d9e2 okay now i fucked it up differently 2024-01-14 19:59:51 -08:00
a1df463a16 braking distance and visualization. bad calculation
the visualization is really helpful for showing me that I fucked up my math
2024-01-14 19:57:22 -08:00
d0a17488d0 pull out calc_velocity
also prepare constraints to change color based on whether it changed the requested thrust
2024-01-14 19:33:57 -08:00
aa25f87c46 Full debug diagnostics display for autobrake_test 2024-01-14 19:03:34 -08:00
edbde8e689 ship mover, no constraint yet 2024-01-14 18:21:10 -08:00
5b668cf9c9 empty template for autobrake test cart 2024-01-12 11:14:54 -08:00
34af172ca5 Add code-of-conduct 2023-12-30 12:50:18 -08:00
4804402f32 fix search-and-replace damage from a while back
a batch change from ship to self broke the thing that kept the player on
the screen. also spawn_goodie got missed in the migration to `_ENV` for
looking up stuff by name.
2023-12-24 21:39:52 -08:00
97ddfb876b fix weird line break 2023-12-22 00:40:01 -08:00
fbeb313078 ignore vscode config 2023-12-21 23:30:17 -08:00
a359bc5031 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.)
2023-12-21 13:09:33 -08:00
cdf517c51c fix missing self plumbing 2023-12-20 18:16:55 -08:00
9aac99ef30 replace grab_butts with act
marshaling through a table is a waste of time, the duplication betweeen
positive and negative thrust vectors is pointless, and pre-multiplying
thrust complicates "stay in a box" goals later on.
2023-12-20 18:14:39 -08:00
5fef5bad00 Squash level_parser into main: parse levels in CSV
commit b91ebeb775
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Wed Dec 20 13:33:00 2023 -0800

    fix boss

    it works now except for a square being drawn in the shield. good enough

commit ab687f8f6d
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Wed Dec 20 13:23:29 2023 -0800

    adjust spawning

    now it runs for a little tiny bit!

commit bef95df6a1
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Wed Dec 20 13:18:20 2023 -0800

    typo

commit 24435a3c15
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Wed Dec 20 13:18:01 2023 -0800

    move guns before ships

commit 0c3a36f1fd
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Wed Dec 20 13:15:34 2023 -0800

    defer zap_gun creation until it exists

commit a39c419e5f
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Wed Dec 20 13:13:51 2023 -0800

    fix mknew

commit 9ef762268f
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Wed Dec 20 13:04:13 2023 -0800

    many assorted syntax errors

commit e50f516b11
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Wed Dec 20 12:00:34 2023 -0800

    allow strings when spawning guns

commit f9e28fa0e2
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Wed Dec 20 11:59:20 2023 -0800

    fix missing paren

commit 38a054dec1
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Wed Dec 20 11:48:08 2023 -0800

    candidate conversion to csv for level format

commit fd391ff3bc
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Wed Dec 20 11:40:11 2023 -0800

    use _ENV to get rid of level_events and spawns

commit fbd9f97429
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Wed Dec 20 11:33:59 2023 -0800

    maybe fix the level parser

commit 2a61e8b5d6
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Sun Oct 15 21:09:12 2023 -0700

    partial conversion to CSV-based levels, does not run yet

commit 4ccbe1dc35
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Fri Oct 13 01:02:43 2023 -0700

    okay honestly this all can and should just be CSVs

commit b536d2c987
Author: Kistaro Windrider <kistaro@gmail.com>
Date:   Sun Oct 8 00:41:24 2023 -0700

    base for representing a level as a string
2023-12-20 14:09:07 -08:00
2bbc4e598c clean up macOS garbage 2023-12-19 16:26:11 -08:00
62fe5f51d3 val helper function 2023-10-07 23:57:27 -07:00
fd68ef88ec document The Parser, take an emit function. 2023-10-07 23:53:40 -07:00
fd9866e963 The Parser 2023-10-03 22:41:52 -07:00
10 changed files with 5720 additions and 574 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.DS_Store
.vscode/settings.json

244
autobrake_test.p8 Normal file
View File

@ -0,0 +1,244 @@
pico-8 cartridge // http://www.pico-8.com
version 41
__lua__
-- vacuum gambit automatic brake test
-- by kistaro windrider
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
function _init()
pal(1,129,1)
the_ship = ship.new()
constraints:setup()
slomo = 1
sloc = 0
reroll()
end
function reroll()
frames=0
sloc=0
the_ship:reroll()
end
function _update60()
if (btnp(4)) reroll()
if (btnp(5)) constraints:cycle()
if (btnp(3)) slomo <<= 1
if (btnp(2)) slomo >>= 1
slomo = (slomo < 1) and 1 or (slomo > 8192) and 8192 or slomo
sloc += 1
if sloc >= slomo then
frames += 1
the_ship:update()
sloc=0
end
end
function _draw()
cls(1)
constraints:draw()
the_ship:draw()
print("frames: " .. frames, 4, 64, 7)
print("speed: 1/" .. slomo, 8, 70, 7)
print("thrust: ".. actual_t, 4, 80, 7)
meter(80, 80, 128, 84, actual_t/the_ship.thrust/2)
print("dx: ".. the_ship.dx, 20, 86, 7)
meter(80, 86, 128, 90, the_ship.dx/the_ship.maxspd/2)
print("x: "..the_ship.x, 24, 92, 7)
print("bx: "..gbx, 20, 98, 7)
print("xmin:"..tostr(constraints.xmin), 12, 108, 7)
print("xmax:"..tostr(constraints.xmax), 12, 114, 7)
end
function meter(x0, y0, x1, y1, frac)
local c = 11
if frac < 0 then
frac = -frac
c = 8
end
local range = x1-x0
local midpoint = x0 + (range/2)
rectfill(x0, y0-1, x0, y1+1, 13)
rectfill(midpoint, y0-1, midpoint, y1 + 1, 13)
local width = range * frac
if (width ~= 0) rectfill(x0, y0, x0 + width, y1, c)
end
-->8
-- ship
ship = {
maxspd=4,
thrust=0.25,
drag=0.0625,
y=32,
}
mknew(ship)
function ship:reroll()
self.x=rnd(128)
self.dx=rnd(2*self.maxspd)-self.maxspd
end
function ship:draw()
if self.x < -7 then
spr(2, 0, self.y-7)
spr(2, 0, self.y+8)
elseif self.x > 127 then
spr(2, 120, self.y-7, 1, 1, true)
spr(2, 120, self.y+8, 1, 1, true)
else
spr(1,self.x,self.y)
end
--if (self.dx == 0) return
local bd, f = brake_dist(self.dx, self.thrust + self.drag)
gbx = self.x+bd
spr(3, gbx-2,self.y-2)
print(tostr(f), gbx-2, self.y - 8, 14)
end
function calc_velocity(v0, t, vmax, drag)
v0 = mid(v0 + t, vmax, -vmax)
return v0 - mid(drag, -drag, v0)
end
function ship:update()
local t = btn(0) and -1 or btn(1) and 1 or 0
t *= self.thrust
t = constraints:constrain(self, t)
-- t = constraints:constrain(self, t)
-- t = constraints:constrain(self, t)
local s = calc_velocity(self.dx, t, self.maxspd, self.drag)
self.x += s
self.dx = s
actual_t = t
end
-->8
-- constraints
constraints = {
ymin=20,
ymax=52,
color=10
}
function constraints:constrain(s, want)
self.color=10
if (not self.xmin) return want
-- bmx: brake max
local v1, bmx = calc_velocity(s.dx, want, s.maxspd, s.drag), s.thrust + s.drag
local bd, bf = brake_dist(v1, bmx)
local bx, txm = s.x + bd + v1, self.xmax
if bx < self.xmin then
-- predicted brake point left
-- of xmin; apply max reverse
-- thrust, treat xmin as our
-- max target, and handle
-- overbraking by coalescing
-- with past +xmax case
self.color = 9
want = s.thrust
txm = self.xmin
v1 = calc_velocity(s.dx, want, s.maxspd, s.drag)
bd, bf = brake_dist(v1, bmx)
bx = bd + s.x + v1
end
if (bx <= txm) return want
self.color = 8
local overage = bx - txm
want -= overage/max(bf,1)
if (want < -s.thrust) want = -s.thrust
return want
end
function brake_dist(v0, brake_max)
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 constraints:cycle()
if self.ctype=="bounds" then
self.ctype="point"
elseif self.ctype=="point" then
self.ctype="off"
else
self.ctype="bounds"
end
self:setup()
end
function constraints:setup()
if self.ctype=="point" then
self.xmin = 64
self.xmax = 64
elseif self.ctype=="bounds" then
self.xmin = 32
self.xmax = 96
else
self.xmin = nil
self.xmax = nil
end
end
function constraints:draw()
if (not self.xmin) return
rect(self.xmin, self.ymin, self.xmax, self.ymax, self.color)
end
-->8
-- fx
-- todo: spark ring buffer
__gfx__
000000008000000000080000a000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000006666000080000009090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000067777600800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000675555758008888009090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000006750007508000000a000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000067777500080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000005555000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

1
code-of-conduct.md Normal file
View File

@ -0,0 +1 @@
Trans rights.

View File

@ -75,7 +75,7 @@ function updategame()
edeaths = {}
for ip, ps in ipairs(pships) do
for ie, eb in ipairs(ebullets) do
if collides(hurtobx(ps), hurtbox(eb)) then
if collides(hurtbox(ps), hurtbox(eb)) then
if (eb:hitship(ps)) add(edeaths, ie)
if ps:hitbullet(eb) then
add(pdeaths, ip)

2162
last_tyrianlike.p8 Normal file

File diff suppressed because it is too large Load Diff

356
old_readme.md Normal file
View File

@ -0,0 +1,356 @@
This file contains text that used to be in the cartridge itself, but
I'm getting increasingly anxious about cartridge space so I'm moving
it out ot a separate file.
---
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
----
* 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.
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
* sprite - sprite index.
* x_off, y_off - renamed for
the next two vars. may revert
* 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.
shieldcooldown is the interval
between restoring shield points.
shieldpenalty is the delay
before restoring points after
any damage, reset to this value
on every damaging hit (whether
it is absorbed by the shield or
not) -- shield behaves like
halo and other shooters in its
heritage, where it recovers if
you avoid damage for a while.
not that there is any safe cover
in this kind of game.
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.

223
rearm_prototype.p8 Normal file
View File

@ -0,0 +1,223 @@
pico-8 cartridge // http://www.pico-8.com
version 42
__lua__
-- vacuum gambit
-- by kistaro windrider
-- stdlib
-- generate standard "overlay"
-- constructor for type tt.
-- if tt.init is defined, generated
-- new calls tt.init(ret) after
-- ret is definitely not nil,
-- before calling setmetatable.
-- use to initialize mutables.
--
-- if there was a previous new,
-- it is invoked on the new
-- object *after* more, because
-- this works better with the
-- `more` impls i use.
function mknew(tt)
local mt,oldnew,more = {__index=tt},tt.new,rawget(tt, "init")
tt.new=function(ret)
if(not ret) ret = {}
if(more) more(ret)
if(oldnew) oldnew(ret)
setmetatable(ret, mt)
return ret
end
return tt
end
function easeoutbounce(t)
local n1=7.5625
local d1=2.75
if (t<1/d1) then
return n1*t*t;
elseif(t<2/d1) then
t-=1.5/d1
return n1*t*t+.75;
elseif(t<2.5/d1) then
t-=2.25/d1
return n1*t*t+.9375;
else
t-=2.625/d1
return n1*t*t+.984375;
end
end
-->8
-- entry points
function _draw()
cls()
draw_hud_placeholder()
left_pane:draw()
right_pane:draw()
rearm_pane_instance:draw()
end
function _init()
item=1
bfm=1
crt_frm = 1
left_pane = weapon_pane.new{}
right_pane = weapon_pane.new{
is_left=false,
s = 2,
hdr = "vulc",
body = " rate\n\n faster\n firing\n rate",
hot = function() return item == 2 end}
rearm_pane_instance = rearm_pane.new{hot=function() return item < 0 end}
end
function _update60()
crt_frm += 0.25
if (crt_frm >= 9) crt_frm = 1
if (btn(3) and item > 0 or btn(2) and item < 0) item = -item
if (btn(0)) item = 1
if (btn(1)) item = 2
if (btn() & 0xF ~= 0) and bfm >= 10 or bfm >= 30 then
bfm = 1
else
bfm += 1
end
if btnp(4) then
left_pane.pos = -1
right_pane.pos = -1
rearm_pane_instance.pos = -1
end
if btnp(5) then
left_pane.pos = 1
right_pane.pos = 1
rearm_pane_instance.pos = 1
end
left_pane:update()
right_pane:update()
rearm_pane_instance:update()
end
function draw_hud_placeholder()
rectfill(112, 0, 127, 127,0x56)
rect(112,0,127,127,7)
line(127,1,127,127,5)
line(113,127)
end
-->8
-- rearm pane drawing
crt={-91,-166,-2641,-1441,-23041,23295,-20491,24570}
function glow_box(x0, y0, x1, y1, c, cf)
for i,v in ipairs{c[1],c[2],c[1],0} do
i -= 1
rect(x0+i,y0+i,x1-i,y1-i,v)
end
fillp(crt[crt_frm&0xff])
rectfill(x0+4, y0+4, x1-4, y1-4, cf)
fillp()
end
function frame_col(hot)
if (not hot) return {4,10}
if (bfm<=16) return {14,7}
return {2,8}
end
function draw_weap_opt(x, y, c, s, hdr, body)
camera(-x,-y)
glow_box(0,0,55,100,c,1)
spr(s,5, 5)
print(hdr, 13, 8, 7)
print(body, 5, 15, 6)
camera()
end
function draw_rearm(c)
glow_box(0,101,111,127,c,1)
spr(5,15,107,4,2)
print("full ammo\nfull shield\n+50% health",54, 106, 6)
end
-->8
-- rearm pane objects
easing_pane = mknew{
-- to enter: pos = -1; to exit: pos = 1
-- runs for 32 frames in, 16 frames out
}
function easing_pane:frac()
local pos = self.pos
if (not pos) return
if (pos < 0) return 1-easeoutbounce(1+pos)
if (pos > 0) return (1-pos)*(1-pos)
return 0
end
function easing_pane:update()
local pos = self.pos
if (not pos or pos == 0) return
if (pos < 0) pos = min(pos + 0x0.05, 0)
if pos > 0 then
pos -= 0x0.1
if (pos <= 0) pos = nil
end
self.pos = pos
end
weapon_pane = mknew(easing_pane.new{
is_left = true,
s = 1,
hdr = "hull",
body = "\n +1\n max\n health",
hot = function() return item == 1 end,
})
function weapon_pane:draw()
local frac, is_left = self:frac(), self.is_left
if (not frac) return
camera(
frac * (is_left and 55 or -128) + (1-frac) * (is_left and 0 or -56),
0)
glow_box(0,0,55,100,frame_col(self:hot()),1)
spr(self.s,5, 5)
print(self.hdr, 13, 8, 7)
print(self.body, 5, 15, 6)
camera()
end
rearm_pane = mknew(easing_pane.new{})
function rearm_pane:draw()
local frac = self:frac()
if (not frac) return
camera(0, -28 * frac)
glow_box(0,101,111,127,frame_col(self:hot()),1)
spr(5,15,107,4,2)
print("full ammo\nfull shield\n+50% health",54, 106, 6)
camera()
end
__gfx__
000000000b00000000000a0007700770000aa0000444440004444444000000000000000000000000000000000000000000000000000000000000000000000000
00000000bba80880000008000aa00aa00a0880a0447777700477777a000000000000000000000000000000000000000000000000000000000000000000000000
007007000aaa28780a0000000990099008000080477aaa7a0477aaaa000000000000000000000000000000000000000000000000000000000000000000000000
0007000008a8887808000000099009900080080047a0047a047a0000000000000000000000000000000000000000000000000000000000000000000000000000
00007000088888820000a000088008800000000047a0447a047a0000000000000000000000000000000000000000000000000000000000000000000000000000
00700700008888200000800008800880a000000a47a4477a047a4440000000000000000000000000000000000000000000000000000000000000000000000000
000000000008820000a0000008800880080aa080477777a00477777a000000000000000000000000000000000000000000000000000000000000000000000000
0000000000002000008000000880088000088000477770000422aaaa222200020000020000000000000000000000000000000000000000000000000000000000
0d5000000000000000000000000000000000000047a77700022ee0002eeee002e00022e000000000000000000000000000000000000000000000000000000000
d00000000000000000000000000000000000000047a4777002ea2e002e002e02ee022ee000000000000000000000000000000000000000000000000000000000
500000000000000000000000000000000000000047a0477a22ea2e002e002e02e2e2e2e000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000047a0047a2e2222e02e222e02e02e02e000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000047a0047a2eeeeeea2eeee002e02e02e000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000aa000aa2e7aa2ea2e00e002e02e02e000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000002e0002e02e002e02e02e02e000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000e0000e00e000e00e00e00e000000000000000000000000000000000000000000000000000000000

61
the_parser.p8 Normal file
View File

@ -0,0 +1,61 @@
pico-8 cartridge // http://www.pico-8.com
version 41
__lua__
-- the parser
parser = {}
mknew(parser)
-- calls parse_into with a nop
-- emit function.
function parser:parse(str)
self:parse_into(str, function() end)
end
-- read a file of commands and
-- execute them, emitting the
-- results from each call into
-- `emit` as a table per row.
--
-- a "command" is a method on
-- self. a row alternates
-- commands with args. when
-- calling a command, it also
-- gets a table of previous
-- results as the first arg.
-- args are split on ','.
function parser:parse_into(str, emit)
for row in all(split(str, "\n")) do
local prev = {}
local sectors = split(row, ":")
for i=1,#sectors,2 do
local x = self[sectors[i]](self, prev, usplit(sectors[i+1]))
if (x) add(prev, x)
end
emit(prev)
end
end
-- saves prev[sel] as self.name.
-- if sel is unspecified, saves
-- all of prev (as a table).
function parser:saveas(prev, name, sel)
self[name] = sel and prev[sel] or prev
end
-- returns its args, ignoring
-- prev. Used to stuff things
-- into prev. args are packed
-- if there's multiple.
function parser:val(_, ...)
local ret := pack(...)
if (#ret == 1) return ret[1]
return ret
end
function parser:bind(_, fn, ...)
local f = self[fn]
return function()
f(...)
end
end

File diff suppressed because it is too large Load Diff

2109
vacuum_gambit.p8 Normal file

File diff suppressed because it is too large Load Diff