local World = require("game.world") local Palette = require("rendering.palette") local Camera = require("game.camera") local Terrain = {} local points = {} local pads = {} function Terrain.generate() points = {} pads = {} local W = World.WORLD_W local baseline = 1600 -- Place 3 landing pads at fixed zones to avoid overlap issues local padDefs = { {cx = W * 0.2, width = 120, mult = 2, label = "2X"}, {cx = W * 0.55, width = 80, mult = 3, label = "3X"}, {cx = W * 0.8, width = 50, mult = 5, label = "5X"}, } for _, def in ipairs(padDefs) do local py = baseline + (math.random() - 0.5) * 200 table.insert(pads, { x1 = def.cx - def.width / 2, x2 = def.cx + def.width / 2, y = py, mult = def.mult, label = def.label, }) end table.sort(pads, function(a, b) return a.x1 < b.x1 end) -- Generate terrain left to right, inserting pads as flat segments local x = 0 local y = baseline + (math.random() - 0.5) * 80 table.insert(points, {x = 0, y = y}) local padIdx = 1 while x < W do -- Check if next pad is coming up if padIdx <= #pads and x >= pads[padIdx].x1 - 40 then local pad = pads[padIdx] -- Slope to pad start table.insert(points, {x = pad.x1 - 5, y = y}) table.insert(points, {x = pad.x1, y = pad.y}) -- Flat pad table.insert(points, {x = pad.x2, y = pad.y}) x = pad.x2 + 1 y = pad.y padIdx = padIdx + 1 -- Resume jagged local jx = x + 20 + math.random() * 30 y = y + (math.random() - 0.5) * 80 y = math.max(baseline - 250, math.min(baseline + 250, y)) table.insert(points, {x = jx, y = y}) x = jx else -- Normal jagged terrain local step = 25 + math.random() * 35 -- Don't overshoot the next pad's approach zone — would cause out-of-order points if padIdx <= #pads then local approach = pads[padIdx].x1 - 40 if x < approach and x + step > approach then step = approach - x end end x = x + step y = y + (math.random() - 0.5) * 100 y = math.max(baseline - 250, math.min(baseline + 250, y)) table.insert(points, {x = x, y = y}) end end -- Close at right edge if points[#points].x < W then table.insert(points, {x = W, y = points[#points].y}) end end function Terrain.getPoints() return points end function Terrain.getPads() return pads end function Terrain.getHeightAt(wx) if #points < 2 then return 1600 end if wx <= points[1].x then return points[1].y end if wx >= points[#points].x then return points[#points].y end for i = 1, #points - 1 do if points[i].x <= wx and points[i+1].x > wx then local t = (wx - points[i].x) / (points[i+1].x - points[i].x) return points[i].y + t * (points[i+1].y - points[i].y) end end return 1600 end function Terrain.getPadAt(wx) for _, pad in ipairs(pads) do if wx >= pad.x1 and wx <= pad.x2 then return pad end end return nil end function Terrain.draw(visMinX, visMaxX) local p = Palette.get() -- Terrain surface love.graphics.setColor(p.terrain) love.graphics.setLineWidth(2) local pts = {} for _, pt in ipairs(points) do if pt.x >= visMinX - 200 and pt.x <= visMaxX + 200 then table.insert(pts, pt.x) table.insert(pts, pt.y) end end if #pts >= 4 then love.graphics.line(pts) end -- Landing pads (brighter) for _, pad in ipairs(pads) do if pad.x2 >= visMinX and pad.x1 <= visMaxX then love.graphics.setColor(p.pad) love.graphics.setLineWidth(3) love.graphics.line(pad.x1, pad.y, pad.x2, pad.y) -- Multiplier label — constant screen size regardless of zoom love.graphics.setColor(p.pad[1], p.pad[2], p.pad[3], 0.8) local cx = (pad.x1 + pad.x2) / 2 local invZoom = 1 / (World.baseScale * Camera.getZoom()) love.graphics.push() love.graphics.translate(cx, pad.y + 15 * invZoom) love.graphics.scale(invZoom * 0.8, invZoom * 0.8) love.graphics.printf(pad.label, -50, 0, 100, "center") love.graphics.pop() end end end return Terrain