oma-command/game/cities.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

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