105 lines
3.3 KiB
Lua
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
|