OMA-COMMAND — Missile Command arcade clone in Love2D with Omarchy theme integration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
265 lines
8.4 KiB
Lua
265 lines
8.4 KiB
Lua
local World = require("game.world")
|
|
local Palette = require("rendering.palette")
|
|
|
|
local Cities = {}
|
|
|
|
Cities.destroyedThisWave = 0
|
|
|
|
-- City outlines: each city has a main profile polyline plus detail lines
|
|
-- {outline = {x,y,...}, details = {{x1,y1,x2,y2}, ...}}
|
|
-- All coords relative to city centre bottom
|
|
|
|
local SHAPES = {
|
|
-- 1: Office tower cluster — tall centre, two flanking blocks
|
|
{
|
|
outline = {-7,0, -7,-6, -5,-6, -5,-8, -3,-8, -3,-4, -1,-4, -1,-14, 1,-14, 1,-4, 3,-4, 3,-7, 5,-7, 5,-10, 7,-10, 7,0},
|
|
details = {
|
|
{-1,-14, 0,-16, 1,-14}, -- antenna spire
|
|
{-6,-2, -6,-5}, -- window column left
|
|
{-5.5,-2, -5.5,-5},
|
|
{0,-5, 0,-12}, -- centre line
|
|
{6,-2, 6,-9}, -- window column right
|
|
{5.5,-2, 5.5,-9},
|
|
},
|
|
},
|
|
-- 2: Twin towers with skybridge
|
|
{
|
|
outline = {-7,0, -7,-12, -4,-12, -4,-4, -2,-4, -2,-11, 2,-11, 2,-4, 4,-4, 4,-13, 7,-13, 7,0},
|
|
details = {
|
|
{-4,-8, -2,-8}, -- skybridge
|
|
{-6,-3, -6,-11}, -- left window col
|
|
{-5,-3, -5,-11},
|
|
{5,-3, 5,-12}, -- right window col
|
|
{6,-3, 6,-12},
|
|
{4,-13, 5,-15, 6,-15, 7,-13}, -- right tower top detail
|
|
},
|
|
},
|
|
-- 3: Stepped pyramid / ziggurat
|
|
{
|
|
outline = {-8,0, -8,-4, -6,-4, -6,-7, -4,-7, -4,-10, -2,-10, -2,-13, 2,-13, 2,-10, 4,-10, 4,-7, 6,-7, 6,-4, 8,-4, 8,0},
|
|
details = {
|
|
{-1,-13, 0,-15, 1,-13}, -- antenna
|
|
{-7,-1, -7,-3}, -- tier details
|
|
{-5,-5, -5,-6},
|
|
{-3,-8, -3,-9},
|
|
{3,-8, 3,-9},
|
|
{5,-5, 5,-6},
|
|
{7,-1, 7,-3},
|
|
},
|
|
},
|
|
-- 4: Gothic spire with flying buttresses
|
|
{
|
|
outline = {-6,0, -6,-5, -4,-5, -4,-8, -2,-8, -2,-11, -1,-14, 0,-17, 1,-14, 2,-11, 2,-8, 4,-8, 4,-5, 6,-5, 6,0},
|
|
details = {
|
|
{-6,-5, -4,-8}, -- left buttress
|
|
{6,-5, 4,-8}, -- right buttress
|
|
{-1,-8, -1,-11}, -- internal frame
|
|
{1,-8, 1,-11},
|
|
{-0.5,-11, -0.5,-14}, -- upper spire frame
|
|
{0.5,-11, 0.5,-14},
|
|
{-3,-3, -3,-7}, -- window columns
|
|
{3,-3, 3,-7},
|
|
},
|
|
},
|
|
-- 5: Industrial complex — wide, boxy, with chimney
|
|
{
|
|
outline = {-8,0, -8,-6, -6,-6, -6,-8, -4,-8, -4,-6, -1,-6, -1,-10, 1,-10, 1,-6, 3,-6, 3,-5, 5,-5, 5,-8, 6,-8, 6,-12, 7,-12, 7,-5, 8,-5, 8,0},
|
|
details = {
|
|
{6,-12, 6.5,-14, 7,-12}, -- chimney smoke wisp
|
|
{-7,-2, -7,-5}, -- windows
|
|
{-5,-2, -5,-5},
|
|
{0,-7, 0,-9}, -- centre structure
|
|
{-3,-2, -3,-5},
|
|
{4,-2, 4,-4},
|
|
},
|
|
},
|
|
-- 6: Domed building with towers
|
|
{
|
|
outline = {-7,0, -7,-8, -6,-8, -6,-10, -5,-10, -5,-8, -3,-8, -3,-9, -2,-11, 0,-12, 2,-11, 3,-9, 3,-8, 5,-8, 5,-11, 6,-11, 6,-8, 7,-8, 7,0},
|
|
details = {
|
|
{-2,-11, 0,-12, 2,-11}, -- dome highlight
|
|
{0,-12, 0,-14}, -- dome antenna
|
|
{-6,-8, -6,-10}, -- left tower internal
|
|
{5,-8, 5,-11}, -- right tower internal
|
|
{-5.5,-10, -5.5,-10.5, -6.5,-10.5, -6.5,-10}, -- left tower cap
|
|
{5,-11, 5,-11.5, 6,-11.5, 6,-11}, -- right tower cap
|
|
{-2,-3, -2,-7}, -- window columns
|
|
{2,-3, 2,-7},
|
|
},
|
|
},
|
|
}
|
|
|
|
-- Rubble: broken jagged debris wireframe
|
|
local RUBBLE = {
|
|
outline = {-6,0, -5,-2, -4,-1, -3,-3, -1,-1, 0,-2.5, 1,-1, 3,-3, 4,-1.5, 5,-2, 6,0},
|
|
details = {
|
|
{-4,-1, -4.5,-3, -3,-2},
|
|
{1,-1, 0.5,-3, 2,-2},
|
|
{-2,-0.5, -1.5,-2},
|
|
{3,-0.5, 3.5,-2.5},
|
|
},
|
|
}
|
|
|
|
local cities = {}
|
|
local POSITIONS = { 40, 68, 96, 160, 188, 216 }
|
|
|
|
function Cities.init()
|
|
cities = {}
|
|
Cities.destroyedThisWave = 0
|
|
for i = 1, 6 do
|
|
cities[i] = {
|
|
x = POSITIONS[i],
|
|
y = World.GROUND_Y,
|
|
alive = true,
|
|
index = i,
|
|
}
|
|
end
|
|
end
|
|
|
|
function Cities.resetWaveCount()
|
|
Cities.destroyedThisWave = 0
|
|
end
|
|
|
|
function Cities.get()
|
|
return cities
|
|
end
|
|
|
|
function Cities.destroy(index)
|
|
if cities[index] and cities[index].alive then
|
|
if Cities.destroyedThisWave >= 3 then
|
|
-- Max 3 cities destroyed per wave - city survives
|
|
return false
|
|
end
|
|
cities[index].alive = false
|
|
Cities.destroyedThisWave = Cities.destroyedThisWave + 1
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function Cities.allDestroyed()
|
|
for _, c in ipairs(cities) do
|
|
if c.alive then return false end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function Cities.aliveCount()
|
|
local count = 0
|
|
for _, c in ipairs(cities) do
|
|
if c.alive then count = count + 1 end
|
|
end
|
|
return count
|
|
end
|
|
|
|
function Cities.destroyedCount()
|
|
local count = 0
|
|
for _, c in ipairs(cities) do
|
|
if not c.alive then count = count + 1 end
|
|
end
|
|
return count
|
|
end
|
|
|
|
function Cities.deployBonusCities()
|
|
-- Deploy reserve bonus cities to destroyed city slots
|
|
local deployed = 0
|
|
for _, c in ipairs(cities) do
|
|
if not c.alive and World.bonusCities > 0 then
|
|
c.alive = true
|
|
World.bonusCities = World.bonusCities - 1
|
|
deployed = deployed + 1
|
|
end
|
|
end
|
|
return deployed
|
|
end
|
|
|
|
function Cities.getTargets()
|
|
local targets = {}
|
|
for _, c in ipairs(cities) do
|
|
if c.alive then
|
|
table.insert(targets, {x = c.x, y = c.y, type = "city", index = c.index})
|
|
end
|
|
end
|
|
return targets
|
|
end
|
|
|
|
function Cities.draw()
|
|
local p = Palette.get(World.wave)
|
|
local lw = 1 / World.scale
|
|
local t = love.timer.getTime()
|
|
|
|
for i, c in ipairs(cities) do
|
|
if c.alive then
|
|
local shape = SHAPES[i]
|
|
|
|
-- Main building outline
|
|
love.graphics.setColor(p.cities[1], p.cities[2], p.cities[3], 0.9)
|
|
love.graphics.setLineWidth(lw * 1.5)
|
|
local pts = {}
|
|
for j = 1, #shape.outline, 2 do
|
|
table.insert(pts, c.x + shape.outline[j])
|
|
table.insert(pts, c.y + shape.outline[j+1])
|
|
end
|
|
if #pts >= 4 then
|
|
love.graphics.line(pts)
|
|
end
|
|
|
|
-- Architectural details (windows, antennae, internal structure)
|
|
love.graphics.setColor(p.cities[1], p.cities[2], p.cities[3], 0.35)
|
|
love.graphics.setLineWidth(lw)
|
|
for _, d in ipairs(shape.details) do
|
|
local dpts = {}
|
|
for j = 1, #d, 2 do
|
|
table.insert(dpts, c.x + d[j])
|
|
table.insert(dpts, c.y + d[j+1])
|
|
end
|
|
if #dpts >= 4 then
|
|
love.graphics.line(dpts)
|
|
end
|
|
end
|
|
|
|
-- Faint glow at base
|
|
love.graphics.setColor(p.glow[1], p.glow[2], p.glow[3], 0.12)
|
|
love.graphics.setLineWidth(lw * 5)
|
|
love.graphics.line(c.x - 8, c.y, c.x + 8, c.y)
|
|
|
|
-- Occasional window twinkle
|
|
local twinkle = math.sin(t * 1.5 + i * 2.1)
|
|
if twinkle > 0.85 then
|
|
love.graphics.setColor(p.bright[1], p.bright[2], p.bright[3], 0.4)
|
|
local wx = c.x + math.sin(i * 7.3) * 3
|
|
local wy = c.y - 3 - math.abs(math.sin(i * 4.1)) * 6
|
|
love.graphics.circle("fill", wx, wy, 0.4, 4)
|
|
end
|
|
else
|
|
-- Rubble — same for all destroyed cities
|
|
love.graphics.setColor(p.dim[1], p.dim[2], p.dim[3], 0.4)
|
|
love.graphics.setLineWidth(lw)
|
|
|
|
-- Main rubble outline
|
|
local pts = {}
|
|
for j = 1, #RUBBLE.outline, 2 do
|
|
table.insert(pts, c.x + RUBBLE.outline[j])
|
|
table.insert(pts, c.y + RUBBLE.outline[j+1])
|
|
end
|
|
if #pts >= 4 then
|
|
love.graphics.line(pts)
|
|
end
|
|
|
|
-- Rubble debris details
|
|
love.graphics.setColor(p.dim[1], p.dim[2], p.dim[3], 0.2)
|
|
for _, d in ipairs(RUBBLE.details) do
|
|
local dpts = {}
|
|
for j = 1, #d, 2 do
|
|
table.insert(dpts, c.x + d[j])
|
|
table.insert(dpts, c.y + d[j+1])
|
|
end
|
|
if #dpts >= 4 then
|
|
love.graphics.line(dpts)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return Cities
|