106 lines
3.4 KiB
Lua
106 lines
3.4 KiB
Lua
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
|