local World = require("game.world") local Palette = require("rendering.palette") local Projection = require("rendering.projection") local Models = require("data.models") local Player = require("game.player") local Obstacles = {} local obstacles = {} -- Obstacle type mix — four arcade-authentic shapes. local OBSTACLE_TYPES = { "pyramid", "pyramid", "pyramid", -- low/wide pyramid (shape 12) "pyramidTall", "pyramidTall", -- tall/narrow pyramid (shape 0) "cube", "cube", "cube", -- tall block (shape 1) "halfPrism", "halfPrism", -- low block/slab (shape 15) } local RADIUS_BY_TYPE = { pyramid = 18, pyramidTall = 13, cube = 20, halfPrism = 14, } -- Effective obstacle heights for shot-blocking. Shells that are in flight above -- the obstacle's top pass over cleanly; below, they hit. local HEIGHT_BY_TYPE = { pyramid = 18, pyramidTall = 16, cube = 16, halfPrism = 7, } -- The field is a 4000-unit torus; at 900-unit visible radius, ~50 obstacles gives -- ~5–7 in view at any time — dense enough to read as an obstacle field, sparse -- enough that navigation doesn't become a maze. local OBSTACLE_COUNT = 50 local function tooCloseToExisting(x, z, newRadius, minClearance) for _, o in ipairs(obstacles) do local dx = x - o.x local dz = z - o.z local gap = math.sqrt(dx * dx + dz * dz) - (o.radius + newRadius) if gap < minClearance then return true end end return false end function Obstacles.init() obstacles = {} local S = World.FIELD_SIZE local centre = S / 2 local CLEARANCE = 40 -- minimum gap between any two obstacles' edges local SPAWN_CLEAR = 150 -- no obstacles within this radius of player spawn -- Obstacle layout is random per game; global RNG is seeded once in love.load. local placed = 0 local attempts = 0 while placed < OBSTACLE_COUNT and attempts < 4000 do attempts = attempts + 1 local x = math.random(50, S - 50) local z = math.random(50, S - 50) local dx = x - centre local dz = z - centre local otype = OBSTACLE_TYPES[math.random(1, #OBSTACLE_TYPES)] local r = RADIUS_BY_TYPE[otype] or 20 if math.sqrt(dx*dx + dz*dz) > SPAWN_CLEAR and not tooCloseToExisting(x, z, r, CLEARANCE) then table.insert(obstacles, { x = x, z = z, otype = otype, radius = r, height = HEIGHT_BY_TYPE[otype] or 16, }) placed = placed + 1 end end end function Obstacles.getAll() return obstacles end function Obstacles.checkCollision(x, z, radius) for _, o in ipairs(obstacles) do local dx = World.wrappedDelta(x, o.x, World.FIELD_SIZE) local dz = World.wrappedDelta(z, o.z, World.FIELD_SIZE) local dist = math.sqrt(dx*dx + dz*dz) if dist < radius + o.radius then return true, o end end return false end function Obstacles.draw() local p = Palette.get() love.graphics.setColor(p.obstacle) local pl = Player.get() local px, pz = pl.x, pl.z for _, o in ipairs(obstacles) do local dx, dz, dist = World.wrappedDist(px, pz, o.x, o.z) if dist < 900 then local model = Models[o.otype] or Models.cube Projection.drawModel(model, px + dx, 0, pz + dz) end end end return Obstacles