local World = require("game.world") local Palette = require("rendering.palette") local Lander = {} local GRAVITY = 25 local THRUST_ACCEL = 60 local ROT_SPEED = 2.1 -- ~120 deg/sec in radians local FUEL_RATE = 12 -- fuel per second at full thrust -- Lander shape (local coords, 0 = pointing up, Y+ down) local BODY = { {-8, -8}, {-4, -12}, {4, -12}, {8, -8}, {10, 0}, {8, 6}, {-8, 6}, {-10, 0}, } local LEGS = { {{-8, 6}, {-14, 16}}, {{-6, 6}, {-14, 16}}, {{8, 6}, {14, 16}}, {{6, 6}, {14, 16}}, } local FEET = { {{-17, 16}, {-11, 16}}, {{11, 16}, {17, 16}}, } local WINDOW = { {{-3, -6}, {3, -6}}, {{3, -6}, {3, -2}}, {{3, -2}, {-3, -2}}, {{-3, -2}, {-3, -6}}, } local ANTENNA = { {{0, -12}, {0, -18}}, {{-3, -18}, {3, -18}}, } local lander = {} function Lander.init() lander.x = World.WORLD_W / 2 + (math.random() - 0.5) * 1000 lander.y = 200 lander.vx = 20 + math.random() * 20 lander.vy = 5 + math.random() * 10 lander.angle = 0 -- 0 = pointing up lander.alive = true lander.landed = false lander.thrusting = false end function Lander.get() return lander end local function transformPoint(px, py, angle, cx, cy) local cos_a = math.cos(angle) local sin_a = math.sin(angle) return cx + px * cos_a - py * sin_a, cy + px * sin_a + py * cos_a end function Lander.update(dt) if not lander.alive or lander.landed then return end -- Gravity lander.vy = lander.vy + GRAVITY * dt -- Rotation if love.keyboard.isDown("left", "a") then lander.angle = lander.angle - ROT_SPEED * dt end if love.keyboard.isDown("right", "d") then lander.angle = lander.angle + ROT_SPEED * dt end -- Thrust lander.thrusting = love.keyboard.isDown("up", "w") and World.fuel > 0 if lander.thrusting then -- Thrust in the direction the lander is pointing (up from lander's perspective) lander.vx = lander.vx - math.sin(lander.angle) * THRUST_ACCEL * dt lander.vy = lander.vy - math.cos(lander.angle) * THRUST_ACCEL * dt World.fuel = math.max(0, World.fuel - FUEL_RATE * dt) end -- Move lander.x = lander.x + lander.vx * dt lander.y = lander.y + lander.vy * dt -- Clamp X lander.x = math.max(20, math.min(World.WORLD_W - 20, lander.x)) end function Lander.abort() if not lander.alive or lander.landed then return end if World.fuel < 10 then return end -- Auto-level: snap angle toward 0 lander.angle = lander.angle * 0.3 -- Full thrust burst lander.vy = lander.vy - THRUST_ACCEL * 0.5 -- Costs a chunk of fuel World.fuel = math.max(0, World.fuel - 50) end function Lander.getCollisionPoints() -- Return transformed foot positions for terrain collision local pts = {} -- Foot endpoints local footPts = {{-17, 16}, {-11, 16}, {11, 16}, {17, 16}} for _, fp in ipairs(footPts) do local wx, wy = transformPoint(fp[1], fp[2], lander.angle, lander.x, lander.y) table.insert(pts, {x = wx, y = wy}) end -- Body bottom local bodyBottom = {{-8, 6}, {8, 6}} for _, bp in ipairs(bodyBottom) do local wx, wy = transformPoint(bp[1], bp[2], lander.angle, lander.x, lander.y) table.insert(pts, {x = wx, y = wy}) end return pts end function Lander.die() lander.alive = false end function Lander.land() lander.landed = true lander.vx = 0 lander.vy = 0 end function Lander.draw() if not lander.alive then return end local p = Palette.get() local a = lander.angle local cx, cy = lander.x, lander.y -- Body outline love.graphics.setColor(p.lander) love.graphics.setLineWidth(2) local bodyPts = {} for _, v in ipairs(BODY) do local wx, wy = transformPoint(v[1], v[2], a, cx, cy) table.insert(bodyPts, wx) table.insert(bodyPts, wy) end table.insert(bodyPts, bodyPts[1]) table.insert(bodyPts, bodyPts[2]) love.graphics.line(bodyPts) -- Legs love.graphics.setLineWidth(1.5) for _, leg in ipairs(LEGS) do local x1, y1 = transformPoint(leg[1][1], leg[1][2], a, cx, cy) local x2, y2 = transformPoint(leg[2][1], leg[2][2], a, cx, cy) love.graphics.line(x1, y1, x2, y2) end -- Feet love.graphics.setLineWidth(2) for _, foot in ipairs(FEET) do local x1, y1 = transformPoint(foot[1][1], foot[1][2], a, cx, cy) local x2, y2 = transformPoint(foot[2][1], foot[2][2], a, cx, cy) love.graphics.line(x1, y1, x2, y2) end -- Window love.graphics.setColor(p.lander[1], p.lander[2], p.lander[3], 0.5) love.graphics.setLineWidth(1) for _, seg in ipairs(WINDOW) do local x1, y1 = transformPoint(seg[1][1], seg[1][2], a, cx, cy) local x2, y2 = transformPoint(seg[2][1], seg[2][2], a, cx, cy) love.graphics.line(x1, y1, x2, y2) end -- Antenna love.graphics.setColor(p.lander) for _, seg in ipairs(ANTENNA) do local x1, y1 = transformPoint(seg[1][1], seg[1][2], a, cx, cy) local x2, y2 = transformPoint(seg[2][1], seg[2][2], a, cx, cy) love.graphics.line(x1, y1, x2, y2) end -- Thrust flame if lander.thrusting then love.graphics.setColor(p.thrust) love.graphics.setLineWidth(2) local flameLen = 10 + math.random() * 15 local flameSpread = 3 + math.random() * 3 local fx1, fy1 = transformPoint(-flameSpread, 6, a, cx, cy) local fx2, fy2 = transformPoint(flameSpread, 6, a, cx, cy) local ftx, fty = transformPoint((math.random()-0.5)*3, 6 + flameLen, a, cx, cy) love.graphics.line(fx1, fy1, ftx, fty) love.graphics.line(fx2, fy2, ftx, fty) -- Inner flame local flameLen2 = 5 + math.random() * 8 local ft2x, ft2y = transformPoint((math.random()-0.5)*2, 6 + flameLen2, a, cx, cy) love.graphics.setColor(p.bright) love.graphics.line(cx, cy + 3, ft2x, ft2y) end end return Lander