Compare commits

..

12 Commits

Author SHA1 Message Date
85d28ae28b
get rid of pships
lots of things rely on exactly one primary ship now, so this was just
overhead and wasted tokens / cspace.
2025-06-01 17:55:46 -07:00
c514c61b3a
look at me. *look at me.* I'm the gun_base now 2025-06-01 17:41:07 -07:00
9be828dd5c
blast gun is a trig gun
now there are no more base guns
2025-06-01 17:38:45 -07:00
2b51a3472b
protron trig gun 2025-06-01 17:36:15 -07:00
95ea70baae
vulcan_gun as trig gun
causes x offset to reverse for negative aim; may need to be careful
with how I make aiming guns actually work, but this should work for
deriving player guns from enemy guns (by using negative aim)!
2025-06-01 17:26:29 -07:00
b18b4f885d
trig zap gun; fixes
use the trig gun for the zap gun
2025-06-01 17:00:28 -07:00
2fdb8d1a05
trigenometry gun prototype 2025-06-01 16:48:39 -07:00
fc1f84fa28
Delete slip behavior 2025-06-01 16:15:46 -07:00
93b63a5831
Prevent new calls that won't work as expected 2025-06-01 16:11:35 -07:00
297e6e4996
implement clip reload time
two tiers of cooldowns, pretty much
2025-06-01 15:55:11 -07:00
9b3120c47b
rewrite mknew to inherit fields before init
I am horrified to admit that C++'s constructor static initialization
list syntax abruptly makes sense to me and I understand the problem
it is trying to solve
2025-06-01 15:53:50 -07:00
abee6d1206
fix skirmisher sparks 2025-05-31 23:44:36 -07:00

View File

@ -29,23 +29,33 @@ end
-- if tt.init is defined, generated -- if tt.init is defined, generated
-- new calls tt.init(ret) after -- new calls tt.init(ret) after
-- ret is definitely not nil, -- ret is definitely not nil,
-- before calling setmetatable. -- after calling setmetatable.
-- use to initialize mutables. -- use to initialize mutables.
-- --
-- if there was a previous new, -- if there was a previous new,
-- it is invoked on the new -- it is invoked before
-- object *after* more, because -- setting tt's metatable, so
-- this works better with the -- each new will see its
-- `more` impls i use. -- inheritance chain.
function mknew(tt) function mknew(tt)
local mt,oldnew,more = {__index=tt},tt.new,rawget(tt, "init") local mt,oldinit,more = {__index=tt},tt.superinit,rawget(tt, "init")
tt.new=function(ret) tt.new=function(ret)
if(not ret) ret = {} if(not ret) ret = {}
if(more) more(ret) ret.new = false
if(oldnew) oldnew(ret)
setmetatable(ret, mt) setmetatable(ret, mt)
if(oldinit) oldinit(ret)
if (more) more(ret)
return ret return ret
end end
if oldinit and more then
tt.superinit = function(ret)
oldinit(ret)
more(ret)
end
elseif more then
tt.superinit = more
end
return tt return tt
end end
@ -159,8 +169,6 @@ function wipe_game()
xpwhoosh = nil xpwhoosh = nil
primary_ship = player.new() primary_ship = player.new()
init_hpcols() init_hpcols()
pships = linked_list.new()
pships:push_back(primary_ship)
eships = linked_list.new() eships = linked_list.new()
pbullets = linked_list.new() pbullets = linked_list.new()
ebullets = linked_list.new() ebullets = linked_list.new()
@ -198,7 +206,8 @@ function ones(n)
end end
function updategame() function updategame()
if (primary_ship.xp >= primary_ship.xptarget) and (gframe - primary_ship.last_xp_frame > 0x0.000f) and (not primary_ship.dead) then local ps = primary_ship
if (ps.xp >= ps.xptarget) and (gframe - ps.last_xp_frame > 0x0.000f) and (not ps.dead) then
mode = rearm_mode.new() mode = rearm_mode.new()
return _update60() return _update60()
end end
@ -228,36 +237,24 @@ function updategame()
end end
events:vore(new_events) events:vore(new_events)
events:strip(call_move) events:strip(call_move)
for _, lst in ipairs{intangibles_bg, pships, eships, pbullets, ebullets} do for _, lst in ipairs{intangibles_bg, eships, pbullets, ebullets} do
lst:strip(call_move) lst:strip(call_move)
end end
pships:strip( if not ps.dead then
function(ps) ps:move()
local pbox, pded = hurtbox(ps), false local pbox = hurtbox(ps)
eships:strip( eships:strip(function(es)
function(es)
if(not collides(pbox, hurtbox(es))) return if(not collides(pbox, hurtbox(es))) return
pded = pded or ps:hitship(es) ps:hitship(es)
return es:hitship(ps) return es:hitship(ps)
end end)
) ebullets:strip(function(eb)
return pded
end
)
pships:strip(
function(ps)
local pbox, pded = hurtbox(ps), false
ebullets:strip(
function(eb)
if (not collides(pbox, hurtbox(eb))) return if (not collides(pbox, hurtbox(eb))) return
pded = pded or ps:hitbullet(eb) ps:hitbullet(eb)
return eb:hitship(ps) return eb:hitship(ps)
end)
end end
)
return pded
end
)
-- many bullets and many enemy ships; -- many bullets and many enemy ships;
-- use bucket collider for efficiency -- use bucket collider for efficiency
@ -292,9 +289,9 @@ function updategame()
if waves_complete == 32767 and not eships.next and not ebullets.next and not events.next then if waves_complete == 32767 and not eships.next and not ebullets.next and not events.next then
game_state = win game_state = win
end end
if (not pships.next) game_state = lose if (ps.dead) game_state = lose
if primary_ship.xp >= primary_ship.xptarget then if ps.xp >= ps.xptarget then
if not xpwhoosh then if not xpwhoosh then
xpwhoosh = 0 xpwhoosh = 0
else else
@ -383,8 +380,8 @@ end
function drawgame() function drawgame()
clip(0,0,112,128) clip(0,0,112,128)
rectfill(0,0,112,128,0) rectfill(0,0,112,128,0)
for slist in all{intangibles_bg, pbullets, pships, eships, ebullets, intangibles_fg} do for drawable in all{intangibles_bg, pbullets, primary_ship, eships, ebullets, intangibles_fg} do
slist:draw() drawable:draw()
end end
clip(0,0,128,128) clip(0,0,128,128)
drawhud() drawhud()
@ -520,8 +517,6 @@ ship_m = mknew{
shieldpenalty = 0x0.012c, --5s shieldpenalty = 0x0.012c, --5s
shield_refresh_ready = 0, shield_refresh_ready = 0,
slip = true, -- most enemies slide
xmomentum = 0, xmomentum = 0,
ymomentum = 0, ymomentum = 0,
@ -605,25 +600,18 @@ function ship_m:move()
self:maybe_shoot(self.main_gun) self:maybe_shoot(self.main_gun)
if (shoot_spec1 and self.special_guns) self:maybe_shoot(self.special_guns[1]) if (shoot_spec1 and self.special_guns) self:maybe_shoot(self.special_guns[1])
if (shoot_spec2 and self.special_guns) self:maybe_shoot(self.special_guns[2]) if (shoot_spec2 and self.special_guns) self:maybe_shoot(self.special_guns[2])
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) 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.xmomentum = self:calc_velocity(self.xmomentum, dx)
self.ymomentum = self:calc_velocity(self.ymomentum, dy) self.ymomentum = self:calc_velocity(self.ymomentum, dy)
self.x += self.xmomentum self.x += self.xmomentum
self.y += self.ymomentum 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 return false
end end
function ship_m:draw() function ship_m:draw()
if (self.dead) return
if(self.fx_pal) pal(self.fx_pal) if(self.fx_pal) pal(self.fx_pal)
spr(self.sprite, self.x, self.y, self.size, self.size) spr(self.sprite, self.x, self.y, self.size, self.size)
pal() pal()
@ -719,6 +707,10 @@ bullet_base = mknew{ }
gun_base = mknew{ gun_base = mknew{
shoot_ready = -32768, shoot_ready = -32768,
new_clip = -32768,
clip_size = false,
clip_remain = 0,
clip_interval = 0x0.80,
icon = 20, icon = 20,
ammobonus = 1, ammobonus = 1,
@ -726,6 +718,27 @@ gun_base = mknew{
-- cooldown reduction from -- cooldown reduction from
-- upgrades, not yet applied -- upgrades, not yet applied
cd_remainder = 0, cd_remainder = 0,
veloc = 1,
aim = 0.75, -- down; 0.25, or -0.75, is up
shot_idx = 0,
-- shots: list<list<[3]num>>
-- describing a cycling
-- firing pattern. shot_idx
-- tracks offset into pattern.
-- each nested list: a burst
-- of shots to fire; takes
-- 1 ammo; sequential
-- each shot: angle (turns,
-- relative to `aim`),
-- firing x-offset, velocity;
-- if x-offset is nil, use 0;
-- if velocity is nil, use
-- self.veloc instead
init = function(self)
if (not self.shots) self.shots = {{{0}}}
end
} }
-- gun_base subtypes are -- gun_base subtypes are
@ -750,12 +763,6 @@ function gun_base:peel()
self.munition = mknew(self.munition.new()) self.munition = mknew(self.munition.new())
end end
-- default firing behavior:
-- single shot
function gun_base:actually_shoot(x, y)
self.munition.new{}:spawn_at(x, y)
end
-- upgrade -- upgrade
function gun_base:small_upgrade_opts() function gun_base:small_upgrade_opts()
local ret = { local ret = {
@ -849,15 +856,56 @@ end
function gun_base:shoot(x, y) function gun_base:shoot(x, y)
if (gframe < self.shoot_ready) return false if (gframe < self.shoot_ready) return false
local csz,crm = self.clip_size, self.clip_remain
if csz then
if crm < csz and gframe >= self.new_clip then
self.clip_remain = csz
self.new_clip = gframe + self.clip_interval
elseif crm == 0 then
return false
end
end
if self.ammo then if self.ammo then
if (self.ammo <= 0) return false if (self.ammo <= 0) return false
self.ammo -= 1 self.ammo -= 1
end end
if csz then
self.clip_remain -= 1
end
self.shoot_ready = gframe + self.cooldown self.shoot_ready = gframe + self.cooldown
self:actually_shoot(x, y) self:actually_shoot(x, y)
return true return true
end end
function gun_base:actually_shoot(x, y)
local shots,veloc,aim,munition = self.shots,self.veloc,self.aim,self.munition
local idx = self.shot_idx % #shots + 1
self.shot_idx = idx
shots = shots[idx]
for s in all(shots) do
local a,xo,v = unpack(s)
v = v or veloc
xo = xo or 0
-- reverse x-offset for negative base angle
if (aim < 0) xo = -xo
a += aim
-- todo: switch munition
-- depending on angle
-- (allows for non-round
-- sprites and hitboxes on
-- shots from guns with
-- widely varying angles)
local m = munition.new{}
-- todo: automatically make
-- high velocity shots do
-- multiple collision checks
m.dy = sin(a) * veloc
m.dx = cos(a) * veloc
m:spawn_at(x+(xo or 0), y)
end
end
-->8 -->8
-- bullets and guns -- bullets and guns
@ -876,8 +924,6 @@ zap_e = mknew(bullet_base.new{
y_off = 8, y_off = 8,
damage = 1, damage = 1,
dx = 0, -- px/frame
dy = 4,
hitship = const_fxn(true), hitship = const_fxn(true),
@ -886,19 +932,21 @@ zap_e = mknew(bullet_base.new{
zap_p = mknew(zap_e.new{ zap_p = mknew(zap_e.new{
sprite = 8, sprite = 8,
dy = -8,
y_off = 0, y_off = 0,
category = player_blt_cat, category = player_blt_cat,
}) })
zap_gun_e = mknew(gun_base.new{ zap_gun_e = mknew(gun_base.new{
cooldown = 0x0.0020, -- frames between shots cooldown = 0x0.0020, -- frames between shots
veloc = 4,
munition = zap_e, munition = zap_e,
}) })
zap_gun_p = mknew(zap_gun_e.new{ zap_gun_p = mknew(zap_gun_e.new{
icon = 19, icon = 19,
munition = zap_p, munition = zap_p,
veloc = 8,
aim = 0.25,
hdr = "mAIN gUN", hdr = "mAIN gUN",
}) })
@ -955,6 +1003,7 @@ blast = mknew(bullet_base.new{
blast_gun = mknew(gun_base.new{ blast_gun = mknew(gun_base.new{
icon = 13, icon = 13,
cooldown = 0x0.0078, -- 120 frames between shots cooldown = 0x0.0078, -- 120 frames between shots
aim = -0.75,
ammo = 5, ammo = 5,
maxammo = 5, maxammo = 5,
munition = blast, munition = blast,
@ -987,8 +1036,6 @@ protron_e = mknew(bullet_base.new{
y_off = 4, y_off = 4,
damage = 1, damage = 1,
dym = 0.5, -- gun sets dy;
-- this is mult
category = enemy_blt_cat, category = enemy_blt_cat,
}) })
@ -1004,34 +1051,17 @@ protron_gun_e = mknew(gun_base.new{
cooldown = 0x0.0040, -- frames between shots cooldown = 0x0.0040, -- frames between shots
ammo = nil, ammo = nil,
maxammo = nil, maxammo = nil,
munition = protron_e munition = protron_e,
veloc = 2,
shots = {{{-0.25}, {-0.165}, {-0.0825}, {0}, {0.0825}, {0.165}, {0.25}}}
}) })
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 = mknew(protron_gun_e.new{ protron_gun_p = mknew(protron_gun_e.new{
munition = protron_p, munition = protron_p,
maxammo = 20, maxammo = 20,
cooldown = 0x0.0018, cooldown = 0x0.0018,
veloc = 4,
aim = -0.75,
hdr = "pROTRON", hdr = "pROTRON",
body = [[---------GUN body = [[---------GUN
@ -1060,41 +1090,37 @@ vulcan_e = mknew(bullet_base.new{
y_off = 0, y_off = 0,
damage = 0.5, damage = 0.5,
-- dx from gun
dy = 2,
category=enemy_blt_cat category=enemy_blt_cat
}) })
vulcan_p = mknew(vulcan_e.new{ vulcan_p = mknew(vulcan_e.new{
sprite=22, sprite=22,
y_off = 4, y_off = 4,
dy = -4,
category=player_blt_cat category=player_blt_cat
}) })
vulcan_gun_e = mknew(gun_base.new{ vulcan_gun_e = mknew(gun_base.new{
icon = 37, icon = 37,
enemy = false,
cooldown = 0x0.0003, -- frames between shots cooldown = 0x0.0003, -- frames between shots
ammo = nil, ammo = nil,
maxammo = nil, maxammo = nil,
munition=vulcan_e, munition=vulcan_e,
dxs = {0.35, -0.35, -0.7, 0.7, 0.35, -0.35}, veloc = 2,
xoffs = {1, 0, -1, 1, 0, -1}, shots = {{{0.02, 2}}, {{-0.02,0}}, {{-0.03, -2}}, {{0.03, 2}}, {{0.02, 0}}, {{-0.02, -2}}}
dxidx = 1, })
actually_shoot = function(self, x, y)
local b = self.munition.new{ machine_gun_e = mknew(vulcan_gun_e.new{
dx = self.dxs[self.dxidx], icon = 38,
} clip_size = 12,
b:spawn_at(self.xoffs[self.dxidx]+x,y) clip_interval = 0x0.005a,
self.dxidx += 1 shots = {{{0, 2}}, {{0, -2}}}
if (self.dxidx > #self.dxs) self.dxidx = 1
end
}) })
vulcan_gun_p = mknew(vulcan_gun_e.new{ vulcan_gun_p = mknew(vulcan_gun_e.new{
munition=vulcan_p, munition=vulcan_p,
maxammo = 100, maxammo = 100,
aim=-0.75,
veloc=4,
hdr = "vULCAN", hdr = "vULCAN",
body = [[---------GUN body = [[---------GUN
@ -1156,7 +1182,6 @@ player = mknew(ship_m.new{
thrust = 0.1875, -- momentum added from button thrust = 0.1875, -- momentum added from button
ymin = 0, ymax = 120, -- stay on screen ymin = 0, ymax = 120, -- stay on screen
drag = 0.0625, -- momentum lost per frame drag = 0.0625, -- momentum lost per frame
slip = false, -- does not slide down screen
act = function(self) -- fetch buttons act = function(self) -- fetch buttons
local b,th = btn(),self.thrust local b,th = btn(),self.thrust
local blr = b&0x3 local blr = b&0x3
@ -1312,7 +1337,6 @@ chasey = mknew(ship_m.new{
maxspd = 2, maxspd = 2,
thrust = 0.2, thrust = 0.2,
drag = 0.075, drag = 0.075,
slip = true,
init = function(ship) init = function(ship)
ship.main_gun=ship.main_gun or zap_gun_e.new{} ship.main_gun=ship.main_gun or zap_gun_e.new{}
@ -1340,7 +1364,6 @@ xl_chasey=mknew(chasey.new{
hp = 19.5, hp = 19.5,
shield = 5, shield = 5,
boss = true, boss = true,
slip = false,
act = function(self) act = function(self)
local dx,dy,shoot_spec,shoot_main = chasey.act(self) local dx,dy,shoot_spec,shoot_main = chasey.act(self)
if (self.y < 4) dy=self.thrust if (self.y < 4) dy=self.thrust
@ -1351,9 +1374,6 @@ xl_chasey=mknew(chasey.new{
sspr(40, 0, 8, 8, self.x, self.y, 16, 16) sspr(40, 0, 8, 8, self.x, self.y, 16, 16)
pal() pal()
end, end,
init = function(ship)
ship.main_gun=ship.main_gun or zap_gun_e.new{}
end,
}) })
-- flotilla ships -- flotilla ships
@ -1365,16 +1385,20 @@ ship_f = mknew(ship_m.new{
-- no sparks -- no sparks
hp = 0.5, hp = 0.5,
xp = 0x0.0001, xp = 0x0.0001,
fire_off_x = 4,
fire_off_y = 4,
maxspd = 3, maxspd = 3,
thrust = 0.1, thrust = 0.1,
drag = 0.05, drag = 0.05,
slip = false,
act = function(self) act = function(self)
local wx,wy=self.want_x,self.want_y local wx,wy=self.want_x,self.want_y
self.xmin,self.xmax,self.ymin,self.ymax = wx,wx,wy,wy self.xmin,self.xmax,self.ymin,self.ymax = wx,wx,wy,wy
return 0,0,false,false return 0,0,false,false
end, end,
init = function(self)
if (self.gun_proto) self.main_gun = self.gun_proto.new()
end
}) })
ship_mook = mknew(ship_f.new{ ship_mook = mknew(ship_f.new{
@ -1388,12 +1412,14 @@ ship_defender = mknew(ship_f.new{
ship_turret = mknew(ship_f.new{ ship_turret = mknew(ship_f.new{
sprite=106, sprite=106,
xp = 0x0.0002, xp = 0x0.0002,
gun_proto = machine_gun_e,
}) })
ship_skirmisher = mknew(ship_f.new{ ship_skirmisher = mknew(ship_f.new{
sprite=107, sprite=107,
xp = 0x0.0004, xp = 0x0.0004,
spark = smokespark, sparks = smokespark,
sparkodds = 4, sparkodds = 3,
fire_off_y = 7,
}) })
function rnd_spawn_loc() function rnd_spawn_loc()
@ -1695,15 +1721,15 @@ function spark_particle:draw()
end end
function spark(sprs, x, y, dx, dy, odds, fg) function spark(sprs, x, y, dx, dy, odds, fg)
if (sprs==nil or flr(rnd(odds)) ~= 0) return if (sprs==nil or flr(rnd(odds) or (abs(dx) < 0.5 and abs(dy))) ~= 0) return
local target = fg and intangibles_fg or intangibles_bg local target = fg and intangibles_fg or intangibles_bg
target:push_back(spark_particle.new{ target:push_back(spark_particle.new{
x = x + rnd(4) - 2, x = x + rnd(4) - 2,
y = y + rnd(4) - 2, y = y + rnd(4) - 2,
sprs = sprs, sprs = sprs,
sidx = 1, sidx = 1,
dx = dx + rnd(2) - 1, dx = dx * rnd(2),
dy = dy + rnd(2) - 1, dy = dy * rnd(2),
}) })
end end
-->8 -->8
@ -1809,6 +1835,7 @@ end
-- add a new gun -- add a new gun
function spec_gun_opts() function spec_gun_opts()
-- todo: avoid duplicates
return pick(spec_gunt, 2) return pick(spec_gunt, 2)
end end
@ -1830,7 +1857,6 @@ end
-- ordinary upgrades -- ordinary upgrades
function small_opts() function small_opts()
-- todo: include gun opts
if(not primary_ship.special_guns) return pick(primary_ship:small_upgrade_opts(), 2) if(not primary_ship.special_guns) return pick(primary_ship:small_upgrade_opts(), 2)
local opts = {rnd(primary_ship:small_upgrade_opts())} local opts = {rnd(primary_ship:small_upgrade_opts())}
for g in all(primary_ship.special_guns) do for g in all(primary_ship.special_guns) do
@ -1911,7 +1937,10 @@ function rearm_mode:shuffle()
-- until the upgrade deck -- until the upgrade deck
-- is a thing that exists -- is a thing that exists
local lev = primary_ship.level + 1 local lev = primary_ship.level + 1
if lev == 4 or lev == 12 then
-- for testing: more guns really early
-- if lev == 4 or lev == 12 then
if lev == 2 or lev == 3 then
self.options = spec_gun_opts() self.options = spec_gun_opts()
elseif lev % 4 == 0 then elseif lev % 4 == 0 then
self.options = big_opts() self.options = big_opts()