oma-command/game/batteries.lua
nosignal 64ce7f0fcb Initial public release
OMA-COMMAND — Missile Command arcade clone in Love2D with Omarchy theme integration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:32:07 +01:00

232 lines
8.4 KiB
Lua

local World = require("game.world")
local Palette = require("rendering.palette")
local Batteries = {}
local batteries = {}
local DEFS = {
{ name = "Alpha", x = 18, y = World.GROUND_Y + 6, speed = 180, keys = {"a", "1"} },
{ name = "Delta", x = 128, y = World.GROUND_Y + 6, speed = 420, keys = {"s", "2"} },
{ name = "Omega", x = 238, y = World.GROUND_Y + 6, speed = 180, keys = {"d", "3"} },
}
function Batteries.init()
batteries = {}
for i, def in ipairs(DEFS) do
batteries[i] = {
name = def.name,
x = def.x,
y = def.y,
speed = def.speed,
ammo = 10,
alive = true,
index = i,
}
end
end
function Batteries.rearm()
for _, b in ipairs(batteries) do
b.ammo = 10
b.alive = true
end
end
function Batteries.get(index)
return batteries[index]
end
function Batteries.getAll()
return batteries
end
function Batteries.fire(index)
local b = batteries[index]
if b and b.alive and b.ammo > 0 then
b.ammo = b.ammo - 1
return true
end
return false
end
function Batteries.findNearest(gx, gy)
local delta = batteries[2]
if delta.alive and delta.ammo > 0 then
return delta
end
local best = nil
local bestDist = math.huge
for _, i in ipairs({1, 3}) do
local b = batteries[i]
if b.alive and b.ammo > 0 then
local dist = math.abs(b.x - gx)
if dist < bestDist then
bestDist = dist
best = b
end
end
end
return best
end
function Batteries.destroy(index)
if batteries[index] then
batteries[index].alive = false
batteries[index].ammo = 0
end
end
function Batteries.getTargets()
local targets = {}
for _, b in ipairs(batteries) do
if b.alive then
table.insert(targets, {x = b.x, y = b.y, type = "battery", index = b.index})
end
end
return targets
end
function Batteries.draw()
local p = Palette.get(World.wave)
local lw = 1 / World.scale
local t = love.timer.getTime()
for _, b in ipairs(batteries) do
local x, y = b.x, b.y
if b.alive then
-- Tilt launcher inward toward screen centre
local tilt
if b.index == 1 then tilt = 0.35
elseif b.index == 3 then tilt = -0.35
else tilt = 0 end
local ca, sa = math.cos(tilt), math.sin(tilt)
-- Cab-facing direction: Alpha (left battery) faces right, Omega faces left, Delta faces right by default
local dir
if b.index == 1 then dir = 1
elseif b.index == 3 then dir = -1
else dir = 1 end
-- === TRUCK CHASSIS ===
love.graphics.setColor(p.ground[1], p.ground[2], p.ground[3], 0.9)
love.graphics.setLineWidth(lw * 1.5)
-- Flatbed: long rectangle
love.graphics.line(x-10, y-1.5, x+10, y-1.5)
love.graphics.line(x-10, y, x+10, y)
love.graphics.line(x-10, y-1.5, x-10, y)
love.graphics.line(x+10, y-1.5, x+10, y)
-- Cab at front (in direction dir)
local cabX = x + dir * 7
local cabFront = x + dir * 10
local cabBack = x + dir * 4
love.graphics.line(cabBack, y-1.5, cabBack, y-5) -- cab back wall
love.graphics.line(cabBack, y-5, cabBack + dir*2, y-5.5) -- roof slope back
love.graphics.line(cabBack + dir*2, y-5.5, cabFront - dir*0.5, y-5.5) -- roof
love.graphics.line(cabFront - dir*0.5, y-5.5, cabFront, y-3) -- windshield slope
love.graphics.line(cabFront, y-3, cabFront, y-1.5) -- cab front
-- Window
love.graphics.setColor(p.bright[1], p.bright[2], p.bright[3], 0.5)
love.graphics.setLineWidth(lw)
love.graphics.line(cabFront - dir*0.3, y-3.2, cabFront - dir*1.6, y-5)
-- Wheels (4, spaced along chassis)
love.graphics.setColor(p.ground[1], p.ground[2], p.ground[3], 0.9)
love.graphics.setLineWidth(lw)
love.graphics.circle("line", x-7, y+0.6, 1.3, 10)
love.graphics.circle("line", x-3, y+0.6, 1.3, 10)
love.graphics.circle("line", x+3, y+0.6, 1.3, 10)
love.graphics.circle("line", x+7, y+0.6, 1.3, 10)
-- === TURRET mounted on flatbed (offset away from cab) ===
local turretX = x - dir * 3
love.graphics.setColor(p.ground[1], p.ground[2], p.ground[3], 0.75)
love.graphics.setLineWidth(lw * 1.3)
love.graphics.line(turretX-3, y-1.5, turretX-3, y-4, turretX+3, y-4, turretX+3, y-1.5)
-- === LAUNCH RACK: 3 angled tubes with missiles loaded ===
-- Tube length, pivot at (x, y-4), rotated by tilt
local pivotY = y - 4
local tubeLen = 9
local function rot(dx, dy)
return turretX + dx*ca - dy*sa, pivotY + dx*sa + dy*ca
end
love.graphics.setLineWidth(lw * 1.3)
for i = -1, 1 do
local offX = i * 2.2
-- Tube walls
love.graphics.setColor(p.ground[1], p.ground[2], p.ground[3], 0.9)
local wx1, wy1 = rot(offX - 0.8, 0)
local wx2, wy2 = rot(offX - 0.8, -tubeLen)
local wx3, wy3 = rot(offX + 0.8, 0)
local wx4, wy4 = rot(offX + 0.8, -tubeLen)
love.graphics.line(wx1, wy1, wx2, wy2)
love.graphics.line(wx3, wy3, wx4, wy4)
love.graphics.line(wx2, wy2, wx4, wy4) -- tube mouth
-- Missile inside tube (only if ammo remains; stagger by index)
local loaded = b.ammo >= (i + 2) -- shows 1..3 missiles as ammo fills up
if loaded then
love.graphics.setColor(p.bright[1], p.bright[2], p.bright[3], 0.95)
love.graphics.setLineWidth(lw * 1.1)
-- Missile body
local bx1, by1 = rot(offX, -0.5)
local bx2, by2 = rot(offX, -tubeLen + 1)
love.graphics.line(bx1, by1, bx2, by2)
-- Nose cone
local nxL, nyL = rot(offX - 0.7, -tubeLen + 1)
local nxR, nyR = rot(offX + 0.7, -tubeLen + 1)
local ntx, nty = rot(offX, -tubeLen - 1.2)
love.graphics.line(nxL, nyL, ntx, nty)
love.graphics.line(nxR, nyR, ntx, nty)
love.graphics.line(nxL, nyL, nxR, nyR)
love.graphics.setLineWidth(lw * 1.3)
end
end
-- === RESERVE AMMO: small warheads tucked under chassis ===
local reserve = math.max(0, b.ammo - 3)
love.graphics.setLineWidth(lw)
for a = 1, reserve do
local ax = x - 9 + ((a - 1) % 7) * 2.6
local ay = y - 0.8
love.graphics.setColor(p.bright[1], p.bright[2], p.bright[3], 0.7)
love.graphics.line(ax, ay, ax, ay-1.6)
love.graphics.line(ax-0.4, ay-1.6, ax+0.4, ay-1.6)
love.graphics.line(ax-0.4, ay-1.6, ax, ay-2.2)
love.graphics.line(ax+0.4, ay-1.6, ax, ay-2.2)
end
-- === STATUS LIGHT ===
if math.sin(t * 4) > 0.3 then
love.graphics.setColor(p.bright[1], p.bright[2], p.bright[3], 0.7)
love.graphics.circle("fill", x + 8.5, y - 2.8, 0.5, 4)
end
else
-- === DESTROYED SILO ===
love.graphics.setColor(p.dim[1], p.dim[2], p.dim[3], 0.3)
love.graphics.setLineWidth(lw)
-- Cracked outer hull
love.graphics.line(x-11, y, x-9, y-3, x-7, y-2, x-5, y-4)
love.graphics.line(x+5, y-3, x+7, y-1, x+9, y-3, x+11, y)
love.graphics.line(x-12, y, x+12, y)
-- Rubble and collapsed structure
love.graphics.setColor(p.dim[1], p.dim[2], p.dim[3], 0.2)
love.graphics.line(x-4, y, x-3, y-3, x-1, y-1, x+1, y-4, x+3, y-2, x+4, y)
-- Bent launcher tube sticking out at angle
love.graphics.line(x-1, y-2, x+2, y-6, x+3, y-5.5)
love.graphics.line(x, y-2, x+3, y-7)
-- Scattered debris
love.graphics.line(x-6, y-1, x-5, y-2.5)
love.graphics.line(x+6, y-1, x+7, y-2)
end
end
end
return Batteries