13 Commits

Author SHA1 Message Date
ead2a7d874 fix remaining vulcan-family bug 2024-09-02 14:45:56 -07:00
571412b15e fix chasey xl offsets 2024-09-02 14:15:56 -07:00
907bd8318c several more fixes, now runs to the end but shot offset is wrong 2024-09-02 14:12:35 -07:00
7170552448 first three waves of bug fixes 2024-09-02 13:41:42 -07:00
01ab6d3969 maybe the rest of the refactor? 2024-09-02 12:59:49 -07:00
7869192dee partial refactor continued 2024-08-20 01:21:13 -07:00
a4658e3ef4 halfway through bullet refactor 2024-08-19 16:00:16 -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
2 changed files with 2351 additions and 160 deletions

2164
last_tyrianlike.p8 Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
pico-8 cartridge // http://www.pico-8.com
version 41
version 42
__lua__
-- vacuum gambit
-- by kistaro windrider
@ -18,6 +18,11 @@ function csv(s)
end
return ret
end
function const_fxn(x)
return function()
return x
end
end
-- generate standard "overlay"
-- constructor for type tt.
@ -118,7 +123,7 @@ end
function _init()
init_blip_pals()
wipe_level()
primary_ship.main_gun = zap_gun.new()
primary_ship.main_gun = zap_gun_p.new() -- redundant?
load_level(example_level_csv)
state = game
pal(2,129)
@ -320,6 +325,7 @@ function drawgame()
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)
@ -337,12 +343,16 @@ function drawhud()
inset(120,66,125,125)
-- 57 px vertically
local mxs, mxh = primary_ship.maxshield, primary_ship.maxhp
if mxs + mxh > 1 then
local split = 57 * (mxs / (mxs + mxh-1)) \ 1 + 66
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,primary_ship.shield, mxs,{204,220,221})
vertmeter(121,split+1,124,124,primary_ship.hp-1, mxh-1, hpcols)
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)
@ -413,13 +423,12 @@ ship_m = {
shield = 0,
maxshield = 0,
shieldcost = 32767.9,
shieldcooldown = 0x0.00a0,
shieldcooldown = 0x0.003c,--1s
shieldpenalty = 0x0.012c, --5s
-- default generator behavior:
-- 10 seconds for a full charge
max_power = 600,
power = 600,
generator = 1, -- power gen per frame
max_power = 120,
power = 120,
generator = 2, -- power gen per frame
slip = true, -- most enemies slide
@ -437,7 +446,7 @@ 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)
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)
@ -538,7 +547,7 @@ end
function ship_m:hitsomething(dmg)
if (dmg <= 0) return false
self.shield_refresh_ready = lframe + self.shieldcooldown
self.shield_refresh_ready = lframe + self.shieldpenalty
if self.shield >= dmg then
self.shield -= dmg
self:ow(true)
@ -547,7 +556,7 @@ function ship_m:hitsomething(dmg)
dmg -= self.shield
self.shield = 0
self.hp -= dmg
if self.hp <= 0 then
if self.hp < 0 then
self:die()
return true
end
@ -576,9 +585,15 @@ end
-->8
-- bullet and gun behaviors
bullet_base = {
enemyspd = 0.5
}
function player_blt_cat()
return pbullets
end
function enemy_blt_cat()
return ebullets
end
bullet_base = { }
mknew(bullet_base)
gun_base = {
@ -597,18 +612,10 @@ 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
self.y += self.dy
if (self.y > 128) or (self.y < -8 * self.height) then
self:die()
return true
end
return false
end
@ -617,19 +624,20 @@ 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)
-- An `actually_shoot` factory
-- for trivial guns
function spawn_one(t)
return function(gun, x, y)
t.new{}:spawn_at(x, y)
end
end
function bullet_base:spawn_at(x, y)
self.x = x - self.x_off
self.y = y - self.y_off
self.category():push_back(self)
end
function gun_base:shoot(x, y)
if (lframe < self.shoot_ready) return false
if self.ammo then
@ -641,23 +649,12 @@ function gun_base: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{
zap_e = bullet_base.new{
--shape
psprite = 8, --index of player ammo sprite
esprite = 9, -- index of enemy ammo sprite
sprite = 9, --index of enemy ammo sprite
width = 1, --in 8x8 blocks
height = 1,
hurt = { -- hurtbox - where this ship can be hit
@ -666,33 +663,43 @@ zap = bullet_base.new{
width = 2,
height = 8
},
center_x_off = 1, -- how to position by ship
bottom_y_off = 0,
top_y_off = 0,
x_off = 1, -- how to position by ship
y_off = 8,
damage = 1,
dx = 0, -- px/frame
dy = 8,
dy = 4,
hitship = function(_, _)
return true
end
hitship = const_fxn(true),
category = enemy_blt_cat,
}
mknew(zap)
mknew(zap_e)
zap_gun = gun_base.new{
enemy = false,
zap_p = zap_e.new{
sprite = 8,
dy = -8,
y_off = 0,
category = player_blt_cat,
}
mknew(zap_p)
zap_gun_e = gun_base.new{
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
actually_shoot = spawn_one(zap_e),
}
mknew(zap_gun)
mknew(zap_gun_e)
zap_gun_p = zap_gun_e.new{
actually_shoot = spawn_one(zap_p),
}
mknew(zap_gun_p)
blast = bullet_base.new{
--shape
psprite = 12, --index of player ammo sprite
esprite = 3, -- index of enemy ammo sprite
sprite = 12, --index of player ammo sprite
width = 1, --in 8x8 blocks
height = 1,
hurt = { -- hurtbox - where this ship can be hit
@ -701,13 +708,12 @@ blast = bullet_base.new{
width = 6,
height = 6
},
center_x_off = 4, -- how to position by ship
bottom_y_off = 0,
top_y_off = 0,
x_off = 4, -- how to position by ship
y_off = 0,
damage = 4,
dx = 0, -- px/frame
dy = 2,
dy = -2,
awaitcancel = false,
-- disable damage for 2 frames
@ -732,25 +738,24 @@ blast = bullet_base.new{
self.awaitcancel = false
end)
end
end
end,
category=player_blt_cat
}
mknew(blast)
blast_gun = gun_base.new{
icon = 13,
enemy = false,
power = 0, -- ammo, not power
power = 0, -- only cost is ammo
cooldown = 0x0.0020, -- frames between shots
ammo = 5,
maxammo = 5,
t = blast -- type of bullet to fire
actually_shoot = spawn_one(blast),
}
mknew(blast_gun)
protron = bullet_base.new{
protron_e = bullet_base.new{
--shape
psprite = 23, --index of player ammo sprite
esprite = 24, -- index of enemy ammo sprite
sprite = 24,
width = 1, --in 8x8 blocks
height = 1,
hurt = { -- hurtbox - where this ship can be hit
@ -759,56 +764,63 @@ protron = bullet_base.new{
width = 2,
height = 2
},
center_x_off = 1, -- how to position by ship
bottom_y_off = 4,
top_y_off = 0,
x_off = 1, -- how to position by ship
y_off = 4,
damage = 1,
dx = 0, -- px/frame
dy = 3,
dym = 0.5, -- gun sets dy;
-- this is mult
category = enemy_blt_cat,
}
mknew(protron)
mknew(protron_e)
protron_gun = gun_base.new{
protron_p = protron_e.new{
sprite=23,
dym = -1,
y_off = 0,
category=player_blt_cat,
}
mknew(protron_p)
protron_gun_e = gun_base.new{
icon = 25,
enemy = false,
power = 35,
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
munition = protron_e
}
mknew(protron_gun)
mknew(protron_gun_e)
vulcan = bullet_base.new{
function protron_gun_e:actually_shoot(x, y)
local m = self.munition.dym
for i=1,3 do
local b = self.munition.new{
dx = i*m,
dy = (4-i)*m,
}
b:spawn_at(x,y)
local b2 = self.munition.new{
dx = -i*m,
dy = (4-i)*m,
}
b2:spawn_at(x,y)
end
local bup = self.munition.new{
dx=0,
dy=4*m,
}
bup:spawn_at(x,y)
end
protron_gun_p = protron_gun_e.new{
munition = protron_p,
}
mknew(protron_gun_p)
vulcan_e = bullet_base.new{
--shape
psprite = 22, --index of player ammo sprite
esprite = 21, -- index of enemy ammo sprite
sprite = 21,
width = 1, --in 8x8 blocks
height = 1,
hurt = { -- hurtbox - where this ship can be hit
@ -817,31 +829,37 @@ vulcan = bullet_base.new{
width = 1,
height = 4
},
center_x_off = 0.5, -- how to position by ship
bottom_y_off = 4,
top_y_off = 0,
x_off = 0.5, -- how to position by ship
y_off = 0,
damage = 0.5,
dx = 0, -- px/frame
dy = 4,
-- dx from gun
dy = 2,
category=enemy_blt_cat
}
mknew(vulcan)
mknew(vulcan_e)
vulcan_gun = gun_base.new{
vulcan_p = vulcan_e.new{
sprite=22,
y_off = 4,
dy = -4,
category=player_blt_cat
}
mknew(vulcan_p)
vulcan_gun_e = gun_base.new{
icon = 37,
enemy = false,
power = 8,
cooldown = 0x0.0002, -- frames between shots
ammo = nil,
maxammo = nil,
munition=vulcan_e,
dxs = {0.35, -0.35, -0.7, 0.7, 0.35, -0.35},
xoffs = {1, 0, -1, 1, 0, -1},
dxidx = 1,
actually_shoot = function(self, x, y)
local sprite = self.enemy and vulcan.esprite or vulcan.psprite
local b = vulcan.new{
enemy=self.enemy,
sprite=sprite,
local b = self.munition.new{
dx = self.dxs[self.dxidx],
}
b:spawn_at(self.xoffs[self.dxidx]+x,y)
@ -849,7 +867,12 @@ vulcan_gun = gun_base.new{
if (self.dxidx > #self.dxs) self.dxidx = 1
end
}
mknew(vulcan_gun)
mknew(vulcan_gun_e)
vulcan_gun_p = vulcan_gun_e.new{
munition=vulcan_p,
}
mknew(vulcan_gun_p)
-->8
--ships, including player
@ -876,8 +899,8 @@ player = ship_m.new{
maxhp = 3, -- player only; other ships never heal
shield = 2, -- regenerates, using power
maxshield = 2,
shieldcost = 300, -- power cost to refill shield
generator = 1.5, -- 1 feels too slow
shieldcost = 60, -- power cost to refill shield
generator = 2,
-- gun
main_gun = nil, -- assign at spawn time
@ -911,10 +934,11 @@ player = ship_m.new{
}
mknew(player,
function(p)
p.main_gun = zap_gun.new()
p.main_gun = zap_gun_p.new()
-- ONE HIT MODE
-- p.hp = 1
-- p.maxhp = 1
--
-- p.hp = 0
-- p.maxhp = 0
-- p.shield = 0
-- p.maxshield = 0
end
@ -934,7 +958,7 @@ frownie = ship_m.new{
sparkodds = 8,
-- health and power
hp = 1, -- enemy ships need no max hp
hp = 0.5, -- enemy ships need no max hp
-- position
x=60, -- x and y are for upper left corner
@ -956,7 +980,7 @@ mknew(frownie)
blocky = frownie.new{
sprite = 10,
hp = 2,
hp = 1.5,
hurt = {
x_off = 0,
y_off = 0,
@ -965,7 +989,7 @@ blocky = frownie.new{
},
ow = function(self)
if self.hp <= 1 then
if self.hp < 1 then
self.sprite = 11
else
self.sprite = 10
@ -984,7 +1008,7 @@ spewy = frownie.new{
width=8,
height=5
},
hp=1,
hp=0.5,
maxpower=70,
generator=0.5,
fire_off_x=4,
@ -995,7 +1019,7 @@ spewy = frownie.new{
end
}
mknew(spewy, function(ship)
ship.main_gun=ship.main_gun or protron_gun.new{enemy=true}
ship.main_gun=ship.main_gun or protron_gun_e.new{enemy=true}
end)
chasey = ship_m.new{
@ -1009,7 +1033,7 @@ chasey = ship_m.new{
},
sparks = smokespark,
sparkodds = 8,
hp = 2,
hp = 1.5,
shield = 1,
maxshield = 1,
shieldcost = 180,
@ -1023,7 +1047,7 @@ chasey = ship_m.new{
slip = true,
}
mknew(chasey, function(ship)
ship.main_gun=ship.main_gun or zap_gun.new{enemy=true}
ship.main_gun=ship.main_gun or zap_gun_e.new{}
end)
function chasey:act()
@ -1041,7 +1065,9 @@ xl_chasey=chasey.new{
width = 12,
height = 10
},
hp = 20,
fire_off_x = 8,
fire_off_y = 15,
hp = 19.5,
shield = 5,
boss = true,
slip = false,
@ -1057,7 +1083,7 @@ xl_chasey=chasey.new{
end,
}
mknew(xl_chasey, function(ship)
ship.main_gun=ship.main_gun or zap_gun.new{enemy=true}
ship.main_gun=ship.main_gun or zap_gun_e.new{}
end)
-->8
-- collisions
@ -1266,9 +1292,9 @@ end
function spawn_bonus_vulcan_chasey()
local c = spawn_chasey()
c.main_gun=vulcan_gun.new{enemy=true}
c.main_gun=vulcan_gun_e.new{enemy=true}
c.die = function(self)
spawn_main_gun_at(self.x-1, self.y-1, vulcan_gun)
spawn_main_gun_at(self.x-1, self.y-1, vulcan_gun_p)
chasey.die(self)
end
c.sprite=4
@ -1371,7 +1397,7 @@ example_level_csv=[[1,spawn_frownie
310,spawn_blocking_blocky
310,spawn_blocking_blocky
311,spawn_frownie
350,spawn_main_gun_at,70,-11,protron_gun
350,spawn_main_gun_at,70,-11,protron_gun_p
401,spawn_frownie
420,spawn_blocking_frownie
430,spawn_bonus_vulcan_chasey
@ -1481,8 +1507,9 @@ bullets
shots much easier to dodge
* damage - damage per hit;
used by ships
* psprite, esprite - index of
player or enemy sprite.
* 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.
@ -1569,10 +1596,12 @@ shields - ship.shieldcost per
point of shields. shieldcooldown
is the interval between
restoring shield points, which
is reset when a ship takes
damage (regardless of whether
that damage is stopped by the
shield or not).
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
@ -1869,7 +1898,7 @@ powerup = bullet_base.new{
-- easy to pick up
dx = 0,
dy = 1.5, -- 0.75 after enemyspd
enemy = true, -- collides with player ship
category = enemy_blt_cat, -- collides with player ship
damage = 0,
anim_speed = 2,
@ -1902,9 +1931,8 @@ repair = powerup.new{
width = 12,
height = 12
},
center_x_off = 4,
top_y_off = 0,
bottom_y_off = 0,
x_off = 4,
y_off = 0,
sprites = sheen8x8,
hitship = function(self, ship)
if (ship ~= primary_ship) return false
@ -1930,9 +1958,8 @@ gun_swap = powerup.new{
height = 16
},
-- gun = gun_type.new{}
center_x_off = 6,
top_y_off = 0,
bottom_y_off = 4,
x_off = 6,
y_off = 0,
width = 2,
height = 2,
sprites = {64, 66, 68, 70, 72, 74, 76, 78},