AllyBullet = {} class('AllyBullet').extends(playdate.graphics.sprite) local killPhrases = { "stolen!", "mine!", "gotcha", "sorry :)", "too slow", "ez" } function AllyBullet:init(targetEnemy) AllyBullet.super.init(self) self.target = targetEnemy self.speed = 3 self.removed = false self:setSize(4, 8) self:setCenter(0.5, 0.5) self:setZIndex(ZIndex.fx) self:moveTo(targetEnemy.x + math.random(-30, 30), 250) self:add() self:markDirty() end function AllyBullet:update() if self.removed then return end -- Fly upward toward target local dx = 0 local dy = -self.speed if self.target and not self.target.removed and not self.target.isDying then dx = (self.target.x - self.x) * 0.05 end self:moveBy(dx, dy) -- Rotate to match flight vector local angle = math.deg(math.atan(dy, dx)) + 90 self:setRotation(angle) -- Check if reached target if self.target and not self.target.removed and not self.target.isDying then local dist = math.abs(self.y - self.target.y) + math.abs(self.x - self.target.x) if dist < 20 then -- Kill enemy without counting toward player score self.target:setImage(self.target.deadImage) self.target.isDying = true self.target.vx = math.random(-2, 2) self.target.vy = math.random(-1, 1) self.target:setRotation(math.random() * 360) -- Show "stolen" text local phrase = killPhrases[math.random(1, #killPhrases)] FloatingText.spawnCustom(self.target.x, self.target.y, phrase) self.removed = true self:remove() return end end -- Off screen if self.y < -10 then self.removed = true self:remove() end self:markDirty() end function AllyBullet:draw() playdate.graphics.fillRect(0, 0, 4, 8) end