local World = require("game.world") local Lander = require("game.lander") local Terrain = require("game.terrain") local Camera = require("game.camera") local Particles = require("game.particles") local Stars = require("game.stars") local HUD = require("game.hud") local HighScores = require("game.highscores") local Palette = require("rendering.palette") local Fonts = require("rendering.fonts") local Sounds = require("audio.sounds") local thrustSoundTimer = 0 local fuelWarnTimer = 0 local STATE_PAUSE = 3.0 local DIFFICULTIES = { {name = "CADET", gravity = 8, fuel = 1000, description = "TRAINING - LOW GRAVITY"}, {name = "PILOT", gravity = 12, fuel = 750, description = "STANDARD MISSION"}, {name = "COMMANDER", gravity = 16, fuel = 600, description = "EXPERIENCED - HEAVIER PULL"}, {name = "ASTRONAUT", gravity = 22, fuel = 500, description = "ELITE - EXTREME GRAVITY"}, } local difficultyIndex = 2 local function applyDifficulty(d) World.gravity = d.gravity World.startFuel = d.fuel World.difficultyName = d.name end local function startGame() applyDifficulty(DIFFICULTIES[difficultyIndex]) World.state = "playing" World.score = 0 World.fuel = World.startFuel World.time = 0 World.stateTimer = 0 Terrain.generate() Lander.init() Camera.reset() Particles.clear() end local function nextAttempt() World.stateTimer = 0 Terrain.generate() Lander.init() Camera.reset() Particles.clear() World.state = "playing" end local function checkLanding() local l = Lander.get() local colPts = Lander.getCollisionPoints() for _, pt in ipairs(colPts) do local terrainY = Terrain.getHeightAt(pt.x) if pt.y >= terrainY then -- Contact! Evaluate landing local pad = Terrain.getPadAt(l.x) local vspeed = math.abs(l.vy) local hspeed = math.abs(l.vx) local tilt = math.abs(l.angle) if pad and vspeed < 8 and hspeed < 15 and tilt < math.rad(5) then -- A perfect landing — tightest tolerances local pts = 50 * pad.mult World.addScore(pts) World.fuel = World.fuel + 50 World.landingResult = "CONGRATULATIONS — A PERFECT LANDING " .. pts .. " PTS" World.landingPoints = pts World.state = "landed" Lander.land() Sounds.play("land_good") elseif pad and vspeed < 15 and hspeed < 30 and tilt < math.rad(15) then -- Good landing local pts = 25 * pad.mult World.addScore(pts) World.fuel = World.fuel + 25 World.landingResult = "GOOD LANDING " .. pts .. " PTS" World.landingPoints = pts World.state = "landed" Lander.land() Sounds.play("land_good") elseif pad and vspeed < 25 and hspeed < 45 and tilt < math.rad(25) then -- Rough but survivable on-pad landing local pts = 10 * pad.mult World.addScore(pts) World.landingResult = "ROUGH LANDING " .. pts .. " PTS" World.landingPoints = pts World.state = "landed" Lander.land() Sounds.play("land_hard") elseif (not pad) and vspeed < 20 and hspeed < 40 and tilt < math.rad(20) then -- Missed the pad but touched down intact World.addScore(5) World.landingResult = "YOU MISSED THE LANDING AREA 5 PTS" World.landingPoints = 5 World.state = "landed" Lander.land() Sounds.play("land_hard") else -- Destroyed World.landingResult = "CRAFT DESTROYED" World.landingPoints = 0 World.state = "crashed" Particles.spawnCrash(l.x, l.y, l.vx, l.vy) Lander.die() Sounds.play("crash") end World.stateTimer = 0 return end end end local function drawTitleScreen() local p = Palette.get() local sw, sh = World.screenW, World.screenH local t = love.timer.getTime() -- Background love.graphics.setColor(p.bg) love.graphics.rectangle("fill", 0, 0, sw, sh) Stars.draw(sw, sh) -- Title local titleY = sh * 0.15 love.graphics.setFont(Fonts.large) love.graphics.setColor(p.bright) love.graphics.printf("OMA-LANDER", 0, titleY, sw, "center") -- Controls love.graphics.setFont(Fonts.small) love.graphics.setColor(p.fg[1], p.fg[2], p.fg[3], 0.4) love.graphics.printf("ARROWS / WASD: ROTATE & THRUST SPACE: ABORT", 0, sh * 0.38, sw, "center") love.graphics.printf("LAND GENTLY ON MARKED PADS FOR POINTS", 0, sh * 0.42, sw, "center") -- Press Enter local pulse = 0.3 + math.sin(t * 3) * 0.3 love.graphics.setFont(Fonts.medium) love.graphics.setColor(p.bright[1], p.bright[2], p.bright[3], pulse + 0.2) love.graphics.printf("PRESS ENTER TO SELECT MISSION", 0, sh * 0.52, sw, "center") -- High scores local allScores = HighScores.getScores() if #allScores > 0 then HighScores.drawTable(sw, sh, p, Fonts) end end local function drawDifficultyScreen() local p = Palette.get() local sw, sh = World.screenW, World.screenH local t = love.timer.getTime() love.graphics.setColor(p.bg) love.graphics.rectangle("fill", 0, 0, sw, sh) Stars.draw(sw, sh) love.graphics.setFont(Fonts.large) love.graphics.setColor(p.bright) love.graphics.printf("SELECT MISSION", 0, sh * 0.12, sw, "center") love.graphics.setFont(Fonts.medium) local lineH = Fonts.medium:getHeight() + 12 local menuTop = sh * 0.30 for i, d in ipairs(DIFFICULTIES) do local y = menuTop + (i - 1) * lineH local selected = (i == difficultyIndex) if selected then local pulse = 0.7 + math.sin(t * 4) * 0.3 love.graphics.setColor(p.bright[1], p.bright[2], p.bright[3], pulse) love.graphics.printf("> " .. d.name .. " <", 0, y, sw, "center") else love.graphics.setColor(p.fg[1], p.fg[2], p.fg[3], 0.5) love.graphics.printf(d.name, 0, y, sw, "center") end end -- Description of selected mission love.graphics.setFont(Fonts.small) love.graphics.setColor(p.fg[1], p.fg[2], p.fg[3], 0.7) local d = DIFFICULTIES[difficultyIndex] local descY = menuTop + #DIFFICULTIES * lineH + 20 love.graphics.printf(d.description, 0, descY, sw, "center") love.graphics.printf(string.format("GRAVITY %d FUEL %d", d.gravity, d.fuel), 0, descY + Fonts.small:getHeight() + 4, sw, "center") -- Instructions love.graphics.setColor(p.fg[1], p.fg[2], p.fg[3], 0.4) love.graphics.printf("UP / DOWN: CHANGE MISSION ENTER: BEGIN ESC: BACK", 0, sh - Fonts.small:getHeight() - 12, sw, "center") end function love.load() love.mouse.setVisible(false) love.graphics.setBackgroundColor(0, 0, 0) love.graphics.setLineStyle("smooth") Palette.loadFromSystem() World.resize(love.graphics.getDimensions()) Fonts.init() HighScores.init() Sounds.init() Stars.init() World.highScore = HighScores.getHighest() World.state = "title" end function love.resize(w, h) World.resize(w, h) Fonts.init() end function love.update(dt) World.ensureScale() if World.state == "title" or World.state == "difficulty_select" then return end if World.state == "playing" then World.time = World.time + dt Lander.update(dt) Camera.update(Lander.get(), Terrain, dt) Particles.update(dt) -- Thrust sound local l = Lander.get() if l.thrusting then thrustSoundTimer = thrustSoundTimer + dt if thrustSoundTimer > 0.2 then thrustSoundTimer = 0 Sounds.play("thrust") end -- Thrust particles — spawn at engine-bell exit transformed by lander angle local sa, ca = math.sin(l.angle), math.cos(l.angle) local fx = l.x - sa * 12 local fy = l.y + ca * 12 Particles.spawnThrust(fx, fy, l.angle) else thrustSoundTimer = 0.15 end -- Fuel warning if World.fuel < 150 and World.fuel > 0 then fuelWarnTimer = fuelWarnTimer + dt if fuelWarnTimer > 0.5 then fuelWarnTimer = 0 Sounds.play("fuel_warn") end end -- Check terrain collision checkLanding() elseif World.state == "landed" or World.state == "crashed" then World.stateTimer = World.stateTimer + dt Particles.update(dt) Camera.update(Lander.get(), Terrain, dt) if World.stateTimer >= STATE_PAUSE then if World.fuel <= 0 then World.state = "game_over" World.stateTimer = 0 else nextAttempt() end end elseif World.state == "game_over" then World.stateTimer = World.stateTimer + dt elseif World.state == "high_score_entry" then HighScores.updateEntry(dt) end end function love.draw() love.graphics.clear(0, 0, 0) local p = Palette.get() local sw, sh = World.screenW, World.screenH if World.state == "title" then drawTitleScreen() return end if World.state == "difficulty_select" then drawDifficultyScreen() return end if World.state == "high_score_entry" then love.graphics.setColor(p.bg) love.graphics.rectangle("fill", 0, 0, sw, sh) Stars.draw(sw, sh) HighScores.drawEntry(sw, sh, p, Fonts) return end -- Draw game world love.graphics.setColor(p.bg) love.graphics.rectangle("fill", 0, 0, sw, sh) -- Stars (screen space, behind everything) Stars.draw(sw, sh) -- World space Camera.applyTransform() local minX, minY, maxX, maxY = Camera.getVisibleRect() Terrain.draw(minX, maxX) Lander.draw() Particles.draw() Camera.popTransform() -- HUD (screen space) local l = Lander.get() local altitude = Camera.getAltitude(l.y, Terrain, l.x) HUD.draw(altitude, l.vx, l.vy) -- Landing/crash result text if World.state == "landed" or World.state == "crashed" then love.graphics.setFont(Fonts.medium) local flash = World.state == "landed" or (math.floor(World.stateTimer * 4) % 2 == 0) if flash then if World.state == "crashed" then love.graphics.setColor(p.warning) else love.graphics.setColor(p.bright) end love.graphics.printf(World.landingResult, 0, sh * 0.45, sw, "center") end end -- Game over if World.state == "game_over" then local t = love.timer.getTime() love.graphics.setColor(0, 0, 0, 0.5) love.graphics.rectangle("fill", 0, 0, sw, sh) if World.stateTimer > 0.5 then love.graphics.setFont(Fonts.large) local pulse = 0.7 + math.sin(t * 3) * 0.3 love.graphics.setColor(p.bright[1], p.bright[2], p.bright[3], pulse) love.graphics.printf("OUT OF FUEL", 0, sh * 0.3, sw, "center") end if World.stateTimer > 1.5 then love.graphics.setFont(Fonts.medium) love.graphics.setColor(p.fg[1], p.fg[2], p.fg[3], 0.7) love.graphics.printf("FINAL SCORE: " .. string.format("%d", World.score), 0, sh * 0.3 + Fonts.large:getHeight() + 8, sw, "center") end if World.stateTimer > 2.5 then local pulse = 0.3 + math.sin(t * 3) * 0.3 love.graphics.setFont(Fonts.small) love.graphics.setColor(p.bright[1], p.bright[2], p.bright[3], pulse + 0.2) love.graphics.printf("PRESS ENTER", 0, sh * 0.3 + Fonts.large:getHeight() + Fonts.medium:getHeight() + 24, sw, "center") end end end function love.keypressed(key) if World.state == "title" then if key == "return" then World.state = "difficulty_select" end if key == "escape" then love.event.quit() end return end if World.state == "difficulty_select" then if key == "up" or key == "w" then difficultyIndex = ((difficultyIndex - 2) % #DIFFICULTIES) + 1 elseif key == "down" or key == "s" then difficultyIndex = (difficultyIndex % #DIFFICULTIES) + 1 elseif key == "return" then startGame() elseif key == "escape" then World.state = "title" end return end if World.state == "high_score_entry" then local result = HighScores.keypressedEntry(key) if result == "done" then World.highScore = HighScores.getHighest() World.state = "title" end return end if World.state == "playing" then if key == "space" then Lander.abort() Sounds.play("abort") end end if World.state == "game_over" then if key == "return" and World.stateTimer > 2.0 then if HighScores.isHighScore(World.score) then World.state = "high_score_entry" HighScores.startEntry(World.score) else World.state = "title" end return end if key == "escape" then World.state = "title" return end end if key == "escape" then love.event.quit() end end