oma-tank/game/world.lua
2026-04-18 11:35:06 +01:00

105 lines
3.3 KiB
Lua

local World = {
GAME_W = 1024,
GAME_H = 768,
FIELD_SIZE = 4000,
visibleH = 768,
scale = 1,
offsetX = 0,
offsetY = 0,
screenW = 0,
screenH = 0,
-- Viewport regions (screen pixels)
radarH = 0,
viewY = 0,
viewH = 0,
-- Classic vector-arcade thresholds (defaults matching the most common DIP
-- switch settings documented in arcade-preservation records).
MISSILE_THRESHOLD = 10000, -- binary: below score, no missiles; at/above, missiles mix in
SUPERTANK_MISSILE_COUNT = 6, -- super tanks replace slow tanks after this many missiles have launched
SAUCER_THRESHOLD = 2000, -- saucer becomes eligible
SAUCER_MIN_DELAY = 0,
SAUCER_MAX_DELAY = 17,
EVADE_TANK_TIME_MIN = 48, -- if current tank stays alive this long, swap it for a missile (arcade: 48-64s)
EVADE_TANK_TIME_MAX = 64,
RESPAWN_SPAWN_BEAT = 2.5, -- seconds between enemy death and next spawn
FIRE_SUPPRESSION = 2.0, -- seconds after enemy spawn during which it cannot fire (arcade rule)
-- State
state = "title",
score = 0,
highScore = 0,
lives = 3,
nextExtraLife = 15000,
enemiesDestroyed = 0,
gameOverTimer = 0,
spawnTimer = 0,
saucerCooldown = 0, -- seconds until next saucer eligible
missilesLaunched = 0, -- total missile spawns this game (gates super-tank transition)
evadeDeadline = 0, -- RNG'd 48..64s lifetime limit for the current tank
}
function World.resize(w, h)
if not w or not h then w, h = love.graphics.getDimensions() end
World.screenW = w
World.screenH = h
World.scale = w / World.GAME_W
World.visibleH = h / World.scale
World.offsetX = 0
World.offsetY = 0
-- Viewport layout
World.radarH = math.floor(h * 0.18)
World.viewY = World.radarH
World.viewH = h - World.radarH
end
function World.ensureScale()
local w, h = love.graphics.getDimensions()
if w ~= World.screenW or h ~= World.screenH then
World.resize(w, h)
local Fonts = require("rendering.fonts")
Fonts.init(World.scale)
local Camera = require("game.camera")
Camera.initViewport()
end
end
function World.wrapField(x, z)
local S = World.FIELD_SIZE
return (x % S + S) % S, (z % S + S) % S
end
function World.wrappedDelta(a, b, size)
local d = b - a
if d > size / 2 then d = d - size end
if d < -size / 2 then d = d + size end
return d
end
function World.wrappedDist(x1, z1, x2, z2)
local S = World.FIELD_SIZE
local dx = World.wrappedDelta(x1, x2, S)
local dz = World.wrappedDelta(z1, z2, S)
return dx, dz, math.sqrt(dx*dx + dz*dz)
end
function World.addScore(points)
World.score = World.score + points
local granted = false
while World.score >= World.nextExtraLife do
World.lives = World.lives + 1
granted = true
if World.nextExtraLife < 100000 then
World.nextExtraLife = 100000
else
World.nextExtraLife = World.nextExtraLife + 100000
end
end
if granted then
local ok, Sounds = pcall(require, "audio.sounds")
if ok and Sounds.play then Sounds.play("extra_life") end
end
if World.score > World.highScore then
World.highScore = World.score
end
end
return World