15 Commits

Author SHA1 Message Date
2596f8aa6c I can't spell 2025-06-21 15:13:07 -07:00
ef40c245f8 multi-step shot prototype
nothing reaches this new logic yet, and multiple steps aren't drawn
2025-06-21 15:12:40 -07:00
6d6e13cf3b special case strip(call_move) to stripmove()
this gets called so much the extra function overhead actually seems bad
2025-06-21 14:55:20 -07:00
99323be298 despawn shots aggressively when offscreen
avoids hitting enemies before they spawn in.

may need to revisit this for the X coordinate if I want to implement a "Wild Ball" kind of weapon, but I think I won't have the tokens for that anyway
2025-06-20 19:13:28 -07:00
85c5091804 pbullets also move just before the collision check.
This also sets up for the "fast shots" refactor.
2025-06-20 19:08:58 -07:00
a77180d89a bullet_base:die doesn't actually make sense 2025-06-20 18:55:48 -07:00
c01c3400b7 fix eternal horizontal bullets 2025-06-20 18:51:58 -07:00
0f791b193c more efficient collision iteration, fix eternal shots 2025-06-20 18:50:04 -07:00
d3351d9a05 Use eship_collider for ship collisions, too. 2025-06-20 17:56:57 -07:00
ecddb56d72 loops work better when you increment them 2025-06-20 17:50:03 -07:00
723c0f791c Refactor collider to collaborate with linked_list.
The only use of a collider is intertwined with the ship list, so I can combine the "prepare to yoink" and "populate collider" and "iterate list" steps, and I can combine the "hide" and "yoink" steps. This doesn't save tokens now but it's about to, when I use the eship collider to test pship collision.
2025-06-20 17:48:02 -07:00
e018578754 keep animating bullets while dead 2025-06-20 16:36:18 -07:00
bf8297eb72 prevify eships when setting up collider
I will refactor this next: collider.new will take the linked list to ingest, perform the `insert` and "prevify" loops itself, then replace `hide` with `yoink`, which yoinks the item out of the original list. This pairs the `yoink` operation with the context that makes it possible to do (that is, the context when prevification was implemented); eships therefore cannot be edited in complex ways while the collider is still valid, but we can append to it as long as we don't expect those items to be procesesd correctly this frame. a new_eships+vore plan might be better if we turn out to need it, but presently we don't; flotilla spawning and raider spawning happen at a different point.
2025-06-20 16:31:18 -07:00
1c8bcae44c put ebullet moves inside the collision check loop 2025-06-20 16:01:59 -07:00
325d7444e7 invert eship/pbullet collision
Also mildly trims up linked_list impl.
2025-06-20 15:09:18 -07:00

View File

@ -60,7 +60,7 @@ function mknew(tt)
end end
-- intrusive singly-linked list. -- intrusive singly-linked list.
-- cannot be nested! -- cannot be nested or crossed!
linked_list = mknew{ linked_list = mknew{
is_linked_list=true, is_linked_list=true,
init = function(x) init = function(x)
@ -93,9 +93,7 @@ end
-- strip calls f(x) for each -- strip calls f(x) for each
-- node, removing each node for -- node, removing each node for
-- which f(x) returns true. it -- which f(x) returns true.
-- returns the new tail; nil
-- if the list is now empty.
function linked_list:strip(f) function linked_list:strip(f)
local p, n = self, self.next local p, n = self, self.next
while n do while n do
@ -107,7 +105,22 @@ function linked_list:strip(f)
n = n.next n = n.next
end end
self.tail = p self.tail = p
return p end
-- stripmove calls x:move() for
-- each node, removing each node
-- for which x:move() is true.
function linked_list:stripmove()
local p, n = self, self.next
while n do
if n:move() then
p.next = n.next
else
p = n
end
n = n.next
end
self.tail = p
end end
-- optimized special case - -- optimized special case -
@ -188,14 +201,6 @@ function _update60()
mode:update() mode:update()
end end
function call_f(x)
return x:f()
end
function call_move(x)
return x:move()
end
function ones(n) function ones(n)
local ret = 0 local ret = 0
while n != 0 do while n != 0 do
@ -236,55 +241,49 @@ function updategame()
current_wave:load(0, 0, min(ones(waves_complete)\2, 4)) current_wave:load(0, 0, min(ones(waves_complete)\2, 4))
end end
events:vore(new_events) events:vore(new_events)
events:strip(call_move) for _, lst in ipairs{events, intangibles_bg, eships} do
for _, lst in ipairs{intangibles_bg, eships, pbullets, ebullets} do lst:stripmove()
lst:strip(call_move)
end end
-- eship collider will be used
-- both for pship and pbullets.
local eship_collider = collider.new{from=eships}
if not ps.dead then if not ps.dead then
ps:move() ps:move()
local pbox = hurtbox(ps) local pbox = hurtbox(ps)
eships:strip(function(es) for es in eship_collider:iterate_collisions(pbox) do
if(not collides(pbox, hurtbox(es))) return
ps:hitship(es) ps:hitship(es)
return es:hitship(ps) if(es:hitship(ps)) eship_collider:yoink(es)
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
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 end
) ebullets:strip(function(eb)
local disposition
repeat
disposition=eb:step()
if collides(pbox, hurtbox(eb)) then
ps:hitbullet(eb)
if (eb:hitship(ps)) return true
end
until disposition
return disposition == "dead"
end)
else
ebullets:strip(function(eb) repeat until eb:step() end)
end
pbullets:strip(function(pb)
local disposition
repeat
disposition=pb:step()
for es in eship_collider:iterate_collisions(hurtbox(pb)) do
if (es:hitbullet(pb)) eship_collider:yoink(es)
if (pb:hitship(es)) return true
end
until disposition
return disposition == "dead"
end)
intangibles_fg:strip(call_move) intangibles_fg:stripmove()
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
@ -701,9 +700,10 @@ end
-- default: die, return true. -- default: die, return true.
-- returns whether to delete -- returns whether to delete
-- the bullet -- the bullet
-- die -- on-removal event, bullet_base = mknew{
-- default no-op steps=1,
bullet_base = mknew{ } current_step=0
}
gun_base = mknew{ gun_base = mknew{
shoot_ready = -32768, shoot_ready = -32768,
@ -826,22 +826,16 @@ remainder:
end end
function bullet_base:hitship(_) function bullet_base:hitship(_)
self:die()
return true return true
end end
function bullet_base:die() function bullet_base:step()
end self.current_step=(self.current_step+1)%self.steps
function bullet_base:move()
self.x += self.dx self.x += self.dx
self.y += self.dy self.y += self.dy
if (self.f) self.f -= 1 if (self.f) self.f -= 1
if (self.y > 145) or (self.y < -8 * self.height) or (self.f and self.f < 0) then if ((self.y > 130) or (self.y < -self.height*8) or (self.f and self.f < 0) or (self.x > 128) or (self.x < -self.width*8)) return "dead"
self:die() if (self.current_step == 0) return "stop"
return true
end
return false
end end
function bullet_base:draw() function bullet_base:draw()
@ -1445,6 +1439,22 @@ end
collider = mknew{ collider = mknew{
init = function(x) init = function(x)
x.suppress = {} x.suppress = {}
local p, n = x.from, x.from.next
while n do
-- insert
for i in all(collider_indexes(hurtbox(n))) do
local a = x[i]
if not a then
a = {}
x[i] = a
end
add(a, n)
end
-- prepare yoink
n.prev = p
p = n
n = n.next
end
end, end,
} }
@ -1458,40 +1468,39 @@ function collider_indexes(box)
return ret return ret
end end
function collider:insert(item) function collider:yoink(item)
-- todo: separate "big items" list?
local bdx = collider_indexes(hurtbox(item))
for i in all(bdx) do
local x = self[i]
if not x then
x = {}
self[i] = x
end
add(x, item)
end
end
function collider:hide(item)
self.suppress[item]=true self.suppress[item]=true
local p,n = item.prev,item.next
p.next = n
if n then
n.prev = p
else
self.from.tail = p
end
end end
function collider:get_collisions(item) function collider:iterate_collisions(box)
local found = { }
local seen = { } local seen = { }
local box = hurtbox(item)
local bucket_ids = collider_indexes(box) local bucket_ids = collider_indexes(box)
for b_idx in all(bucket_ids) do local bii, bidl, bucket, bi, blen = 1, #bucket_ids, false, 1, 0
local bucket = self[b_idx] return function()
if bucket then while bii <= bidl do
for candidate in all(bucket) do if not bucket then
if not (seen[candidate] or self.suppress[candidate]) then bucket,blen = self[bucket_ids[bii]],0
seen[candidate] = true if (bucket) blen=#bucket
if (collides(box, hurtbox(candidate))) add(found, candidate)
end
end end
while bi <= blen do
local candidate = bucket[bi]
bi += 1
if not seen[candidate] then
seen[candidate] = true
if (not self.suppress[candidate] and collides(box, hurtbox(candidate))) return candidate
end
end -- done with this bucket
bi=1
bii += 1
end end
end end -- end of closure def
return found
end end
-->8 -->8
@ -1770,7 +1779,6 @@ function xp_gem:draw()
end end
function xp_gem:move() function xp_gem:move()
if not primary_ship.dead and abs(self.x + 1 - primary_ship.x - primary_ship.hurt.x_off) <= primary_ship.magnet and abs(self.y + 1 - primary_ship.y - primary_ship.hurt.y_off) <= primary_ship.magnet then if not primary_ship.dead and abs(self.x + 1 - primary_ship.x - primary_ship.hurt.x_off) <= primary_ship.magnet and abs(self.y + 1 - primary_ship.y - primary_ship.hurt.y_off) <= primary_ship.magnet then
if (self.x < primary_ship.x + 3) self.x += 1 if (self.x < primary_ship.x + 3) self.x += 1
if (self.x > primary_ship.x + 5) self.x -= 1 if (self.x > primary_ship.x + 5) self.x -= 1
@ -1780,9 +1788,6 @@ function xp_gem:move()
return bullet_base.move(self) return bullet_base.move(self)
end end
-- todo: "magnetic" behavior
-- when near player ship
function xp_gem:hitship(ship) function xp_gem:hitship(ship)
if (ship ~= primary_ship or primary_ship.dead) return false if (ship ~= primary_ship or primary_ship.dead) return false
primary_ship.xp += self.val primary_ship.xp += self.val