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
-- new calls tt.init(ret) after
-- ret is definitely not nil,
-- before calling setmetatable.
-- after 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.
-- it is invoked before
-- setting tt's metatable, so
-- each new will see its
-- inheritance chain.
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)
if(not ret) ret = {}
if(more) more(ret)
if(oldnew) oldnew(ret)
ret.new = false
setmetatable(ret, mt)
if(oldinit) oldinit(ret)
if (more) more(ret)
return ret
end
if oldinit and more then
tt.superinit = function(ret)
oldinit(ret)
more(ret)
end
elseif more then
tt.superinit = more
end
return tt
end
@ -159,8 +169,6 @@ function wipe_game()
xpwhoosh = nil
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()
@ -198,7 +206,8 @@ function ones(n)
end
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()
return _update60()
end
@ -228,36 +237,24 @@ function updategame()
end
events:vore(new_events)
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)
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
)
if not ps.dead then
ps:move()
local pbox = hurtbox(ps)
eships:strip(function(es)
if(not collides(pbox, hurtbox(es))) return
ps:hitship(es)
return es:hitship(ps)
end)
ebullets:strip(function(eb)
if (not collides(pbox, hurtbox(eb))) return
ps:hitbullet(eb)
return eb:hitship(ps)
end)
end
-- many bullets and many enemy ships;
-- 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
game_state = win
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
xpwhoosh = 0
else
@ -383,8 +380,8 @@ 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()
for drawable in all{intangibles_bg, pbullets, primary_ship, eships, ebullets, intangibles_fg} do
drawable:draw()
end
clip(0,0,128,128)
drawhud()
@ -520,8 +517,6 @@ ship_m = mknew{
shieldpenalty = 0x0.012c, --5s
shield_refresh_ready = 0,
slip = true, -- most enemies slide
xmomentum = 0,
ymomentum = 0,
@ -605,25 +600,18 @@ function ship_m:move()
self:maybe_shoot(self.main_gun)
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 (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.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.dead) return
if(self.fx_pal) pal(self.fx_pal)
spr(self.sprite, self.x, self.y, self.size, self.size)
pal()
@ -719,6 +707,10 @@ bullet_base = mknew{ }
gun_base = mknew{
shoot_ready = -32768,
new_clip = -32768,
clip_size = false,
clip_remain = 0,
clip_interval = 0x0.80,
icon = 20,
ammobonus = 1,
@ -726,6 +718,27 @@ gun_base = mknew{
-- cooldown reduction from
-- upgrades, not yet applied
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
@ -750,12 +763,6 @@ function gun_base:peel()
self.munition = mknew(self.munition.new())
end
-- default firing behavior:
-- single shot
function gun_base:actually_shoot(x, y)
self.munition.new{}:spawn_at(x, y)
end
-- upgrade
function gun_base:small_upgrade_opts()
local ret = {
@ -849,15 +856,56 @@ end
function gun_base:shoot(x, y)
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 <= 0) return false
self.ammo -= 1
end
if csz then
self.clip_remain -= 1
end
self.shoot_ready = gframe + self.cooldown
self:actually_shoot(x, y)
return true
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
-- bullets and guns
@ -876,8 +924,6 @@ zap_e = mknew(bullet_base.new{
y_off = 8,
damage = 1,
dx = 0, -- px/frame
dy = 4,
hitship = const_fxn(true),
@ -886,19 +932,21 @@ zap_e = mknew(bullet_base.new{
zap_p = mknew(zap_e.new{
sprite = 8,
dy = -8,
y_off = 0,
category = player_blt_cat,
})
zap_gun_e = mknew(gun_base.new{
cooldown = 0x0.0020, -- frames between shots
veloc = 4,
munition = zap_e,
})
zap_gun_p = mknew(zap_gun_e.new{
icon = 19,
munition = zap_p,
veloc = 8,
aim = 0.25,
hdr = "mAIN gUN",
})
@ -955,6 +1003,7 @@ blast = mknew(bullet_base.new{
blast_gun = mknew(gun_base.new{
icon = 13,
cooldown = 0x0.0078, -- 120 frames between shots
aim = -0.75,
ammo = 5,
maxammo = 5,
munition = blast,
@ -987,8 +1036,6 @@ protron_e = mknew(bullet_base.new{
y_off = 4,
damage = 1,
dym = 0.5, -- gun sets dy;
-- this is mult
category = enemy_blt_cat,
})
@ -1004,34 +1051,17 @@ protron_gun_e = mknew(gun_base.new{
cooldown = 0x0.0040, -- frames between shots
ammo = 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{
munition = protron_p,
maxammo = 20,
cooldown = 0x0.0018,
veloc = 4,
aim = -0.75,
hdr = "pROTRON",
body = [[---------GUN
@ -1060,41 +1090,37 @@ vulcan_e = mknew(bullet_base.new{
y_off = 0,
damage = 0.5,
-- dx from gun
dy = 2,
category=enemy_blt_cat
})
vulcan_p = mknew(vulcan_e.new{
sprite=22,
y_off = 4,
dy = -4,
category=player_blt_cat
})
vulcan_gun_e = mknew(gun_base.new{
icon = 37,
enemy = false,
cooldown = 0x0.0003, -- 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 b = self.munition.new{
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
veloc = 2,
shots = {{{0.02, 2}}, {{-0.02,0}}, {{-0.03, -2}}, {{0.03, 2}}, {{0.02, 0}}, {{-0.02, -2}}}
})
machine_gun_e = mknew(vulcan_gun_e.new{
icon = 38,
clip_size = 12,
clip_interval = 0x0.005a,
shots = {{{0, 2}}, {{0, -2}}}
})
vulcan_gun_p = mknew(vulcan_gun_e.new{
munition=vulcan_p,
maxammo = 100,
aim=-0.75,
veloc=4,
hdr = "vULCAN",
body = [[---------GUN
@ -1156,7 +1182,6 @@ player = mknew(ship_m.new{
thrust = 0.1875, -- momentum added from button
ymin = 0, ymax = 120, -- stay on screen
drag = 0.0625, -- 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
@ -1312,7 +1337,6 @@ chasey = mknew(ship_m.new{
maxspd = 2,
thrust = 0.2,
drag = 0.075,
slip = true,
init = function(ship)
ship.main_gun=ship.main_gun or zap_gun_e.new{}
@ -1340,7 +1364,6 @@ xl_chasey=mknew(chasey.new{
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
@ -1351,9 +1374,6 @@ xl_chasey=mknew(chasey.new{
sspr(40, 0, 8, 8, self.x, self.y, 16, 16)
pal()
end,
init = function(ship)
ship.main_gun=ship.main_gun or zap_gun_e.new{}
end,
})
-- flotilla ships
@ -1365,16 +1385,20 @@ ship_f = mknew(ship_m.new{
-- no sparks
hp = 0.5,
xp = 0x0.0001,
fire_off_x = 4,
fire_off_y = 4,
maxspd = 3,
thrust = 0.1,
drag = 0.05,
slip = false,
act = function(self)
local wx,wy=self.want_x,self.want_y
self.xmin,self.xmax,self.ymin,self.ymax = wx,wx,wy,wy
return 0,0,false,false
end,
init = function(self)
if (self.gun_proto) self.main_gun = self.gun_proto.new()
end
})
ship_mook = mknew(ship_f.new{
@ -1388,12 +1412,14 @@ ship_defender = mknew(ship_f.new{
ship_turret = mknew(ship_f.new{
sprite=106,
xp = 0x0.0002,
gun_proto = machine_gun_e,
})
ship_skirmisher = mknew(ship_f.new{
sprite=107,
xp = 0x0.0004,
spark = smokespark,
sparkodds = 4,
sparks = smokespark,
sparkodds = 3,
fire_off_y = 7,
})
function rnd_spawn_loc()
@ -1695,15 +1721,15 @@ function spark_particle:draw()
end
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
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,
dx = dx * rnd(2),
dy = dy * rnd(2),
})
end
-->8
@ -1809,6 +1835,7 @@ end
-- add a new gun
function spec_gun_opts()
-- todo: avoid duplicates
return pick(spec_gunt, 2)
end
@ -1830,7 +1857,6 @@ end
-- ordinary upgrades
function small_opts()
-- todo: include gun opts
if(not primary_ship.special_guns) return pick(primary_ship:small_upgrade_opts(), 2)
local opts = {rnd(primary_ship:small_upgrade_opts())}
for g in all(primary_ship.special_guns) do
@ -1911,7 +1937,10 @@ function rearm_mode:shuffle()
-- until the upgrade deck
-- is a thing that exists
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()
elseif lev % 4 == 0 then
self.options = big_opts()