101 lines
3.9 KiB
Lua
101 lines
3.9 KiB
Lua
local World = require("game.world")
|
|
local Palette = require("rendering.palette")
|
|
local Fonts = require("rendering.fonts")
|
|
|
|
local HUD = {}
|
|
|
|
-- Tiny top-down tank icon (like the original lives display)
|
|
local function drawTankIcon(cx, cy, s)
|
|
-- Hull outline
|
|
love.graphics.line(cx - s*1.2, cy - s*0.5, cx + s*0.6, cy - s*0.5)
|
|
love.graphics.line(cx + s*0.6, cy - s*0.5, cx + s*0.8, cy)
|
|
love.graphics.line(cx + s*0.8, cy, cx + s*0.6, cy + s*0.5)
|
|
love.graphics.line(cx + s*0.6, cy + s*0.5, cx - s*1.2, cy + s*0.5)
|
|
love.graphics.line(cx - s*1.2, cy + s*0.5, cx - s*1.2, cy - s*0.5)
|
|
-- Barrel
|
|
love.graphics.line(cx + s*0.4, cy - s*0.15, cx + s*1.8, cy - s*0.15)
|
|
love.graphics.line(cx + s*0.4, cy + s*0.15, cx + s*1.8, cy + s*0.15)
|
|
end
|
|
|
|
local function bearingLabel(player, enemy)
|
|
-- Returns an arcade-style directional message: "ENEMY IN RANGE",
|
|
-- "ENEMY TO LEFT", "ENEMY TO RIGHT", or "ENEMY TO REAR". Returns nil
|
|
-- when there is no targetable enemy (e.g. only a saucer present).
|
|
if not (enemy and enemy.alive) then return nil end
|
|
local dx, dz = World.wrappedDist(player.x, player.z, enemy.x, enemy.z)
|
|
local cosA = math.cos(player.angle)
|
|
local sinA = math.sin(player.angle)
|
|
local rx = dx * cosA - dz * sinA -- rightward
|
|
local rz = dx * sinA + dz * cosA -- forward
|
|
local dist = math.sqrt(rx * rx + rz * rz)
|
|
local bearing = math.atan2(rx, rz)
|
|
if rz > 0 and math.abs(bearing) < 0.25 and dist < 800 then
|
|
return "ENEMY IN RANGE"
|
|
end
|
|
if math.abs(bearing) > 3 * math.pi / 4 then return "ENEMY TO REAR" end
|
|
if bearing > 0 then return "ENEMY TO RIGHT" end
|
|
return "ENEMY TO LEFT"
|
|
end
|
|
|
|
function HUD.draw(player, enemy)
|
|
local p = Palette.get()
|
|
local sw = World.screenW
|
|
local sh = World.screenH
|
|
local rh = World.radarH
|
|
local sc = sw / 1024
|
|
|
|
-- === LIVES (top-left) as tank icons ===
|
|
love.graphics.setColor(p.bright)
|
|
love.graphics.setLineWidth(1.3 * sc)
|
|
local iconSize = 7 * sc
|
|
local iconSpacing = 26 * sc
|
|
local livesStartX = 30 * sc
|
|
local livesY = rh * 0.25
|
|
|
|
for i = 1, math.min(World.lives, 6) do
|
|
drawTankIcon(livesStartX + (i - 1) * iconSpacing, livesY, iconSize)
|
|
end
|
|
|
|
-- === STATUS MESSAGES (authentic arcade: top-left of display) ===
|
|
-- "MOTION BLOCKED BY OBJECT" takes priority while active, then falls back to
|
|
-- the directional enemy text ("ENEMY IN RANGE" / "ENEMY TO LEFT/RIGHT/REAR").
|
|
if player and player.alive then
|
|
love.graphics.setFont(Fonts.small)
|
|
if (player.blockedMessageTimer or 0) > 0 then
|
|
local alpha = math.min(1, player.blockedMessageTimer / 0.3)
|
|
love.graphics.setColor(p.bright[1], p.bright[2], p.bright[3], alpha)
|
|
love.graphics.print("MOTION BLOCKED BY OBJECT", livesStartX, rh * 0.55)
|
|
else
|
|
local label = bearingLabel(player, enemy)
|
|
if label then
|
|
love.graphics.setColor(p.bright)
|
|
love.graphics.print(label, livesStartX, rh * 0.55)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- === SCORE (top-right, right-aligned) ===
|
|
love.graphics.setFont(Fonts.medium)
|
|
love.graphics.setColor(p.bright)
|
|
local scoreStr = string.format("%09d", World.score)
|
|
local rightEdge = sw - 30 * sc
|
|
local valW = Fonts.medium:getWidth(scoreStr)
|
|
local scoreY = rh * 0.35
|
|
love.graphics.print(scoreStr, rightEdge - valW, scoreY)
|
|
|
|
-- === HIGH SCORE (below score) ===
|
|
love.graphics.setFont(Fonts.small)
|
|
love.graphics.setColor(p.bright[1], p.bright[2], p.bright[3], 0.7)
|
|
local hsY = scoreY + Fonts.medium:getHeight() + 2 * sc
|
|
local hsStr = string.format("%09d", World.highScore)
|
|
local hsW = Fonts.small:getWidth(hsStr)
|
|
love.graphics.print(hsStr, rightEdge - hsW, hsY)
|
|
|
|
-- === SEPARATOR LINE ===
|
|
love.graphics.setColor(p.dim[1], p.dim[2], p.dim[3], 0.5)
|
|
love.graphics.setLineWidth(1)
|
|
love.graphics.line(0, rh, sw, rh)
|
|
|
|
end
|
|
|
|
return HUD
|