-- configurable constants local CELL_SIZE = 100 local WIDTH = 40 local HEIGHT = 40 local MIN_ROOMS = 13 local MAX_ROOMS = 17 local MIN_ROOM_SIZE = 1 local MAX_ROOM_SIZE = 4 local DIRECTION = { LEFT = 1, RIGHT = 2, UP = 3, DOWN = 4 } Tunnel = { -- list of speedzones by group groups = { }, -- map of loadout zone ids to group numbers loadoutZoneMap = { }, new = function(horizontal, parity, endpoint, group) return { isTunnel = true, isIntersection = false, -- group this piece belongs to, used for the loadout zone hook group = group, -- orientation of this tunnel horizontal = horizontal, parity = parity, endpoint = endpoint, render = function(self, i, j) local speedZone = SpeedZone.new() local start = point.new((i - .5) * CELL_SIZE, (j - .5) * CELL_SIZE ) local xOffset, yOffset = 0, 0 if self.endpoint > 0 then local theta, delta = 0, point.zero if self.endpoint == DIRECTION.LEFT then theta = 180 delta = point.new(0, -75) elseif self.endpoint == DIRECTION.RIGHT then theta = 0 delta = point.new(0, 75) elseif self.endpoint == DIRECTION.DOWN then theta = -90 delta = point.new(-75, 0) elseif self.endpoint == DIRECTION.UP then theta = 90 delta = point.new(75, 0) end local zoneGeom = { point.new(-75, 25), point.new( 75, 25), point.new( 75,-25), point.new(-75,-25) } delta = delta + start zoneGeom = Geom.rotate(zoneGeom, theta) zoneGeom = Geom.translate(zoneGeom, delta.x, delta.y) local loadoutZone = LoadoutZone.new(zoneGeom, Team.Neutral) bf:addItem(loadoutZone) Tunnel.loadoutZoneMap[loadoutZone:getId()] = self.group speedZone:setSpeed(300) else speedZone:setSpeed(2500) end if horizontal then xOffset = CELL_SIZE start = point.new(start.x - CELL_SIZE / 2, start.y) else yOffset = CELL_SIZE start = point.new(start.x, start.y - CELL_SIZE / 2) end local geom = { start, start + point.new(xOffset, yOffset) } if parity then geom = sd.reverse(geom) end -- create the table for this group unless it exists if Tunnel.groups[self.group] == nil then Tunnel.groups[self.group] = { } end if not self.isIntersection then speedZone:setGeom(geom) bf:addItem(speedZone) -- add this speed zone as a member of this group table.insert(Tunnel.groups[self.group], speedZone:getId()) local guardRails = { { point.new( 0, 55), point.new(100, 55), point.new(100, 45), point.new( 0, 45) }, { point.new( 0, -55), point.new(100, -55), point.new(100, -45), point.new( 0, -45) } } local theta = 0 if not self.horizontal then theta = 90 end guardRails[1] = Geom.transform(guardRails[1], 1, 1, theta, start.x, start.y) guardRails[2] = Geom.transform(guardRails[2], 1, 1, theta, start.x, start.y) return guardRails end end } end } local Wall = { new = function() return { isWall = true, render = function(self, i, j) local x = i * CELL_SIZE - CELL_SIZE / 2 local y = j * CELL_SIZE - CELL_SIZE / 2 return makeSquare(x, y, CELL_SIZE) end } end } local Room = { new = function() return { isRoom = true, render = function(self, i, j) end } end } local RoomCenter = { new = function() return { isRoomCenter = true, render = function(self, i, j) local spawn = Spawn.new(point.new( (math.floor(i) + .5) * CELL_SIZE, (math.floor(j) + .5) * CELL_SIZE )) spawn:setTeam(1) bf:addItem(spawn) end } end } local pendingZoneChecks = { } function onShipEnteredZone(ship, zone) if pendingZoneChecks[zone:getId()] then return end pendingZoneChecks[zone:getId()] = true zone:setTeam(1) Timer:scheduleOnce(function() pendingZoneChecks[zone:getId()] = false zone:setTeam(Team.Neutral) if ship and zone and zone:containsPoint(ship:getPos()) then local group = Tunnel.loadoutZoneMap[zone:getId()] for k,speedZoneId in pairs(Tunnel.groups[group]) do local speedZone = bf:findObjectById(speedZoneId) speedZone:setGeom(sd.reverse(speedZone:getGeom())) end end end, 1000) end function main() bf:subscribe(Event.ShipEnteredZone) -- initialize the map to all walls local map = { } for i=1,HEIGHT do map[i] = { } for j=1,WIDTH do map[i][j] = Wall.new() end end -- generate some room locations local roomCoords = { } for i = 1,rand(MIN_ROOMS, MAX_ROOMS) do local row = rand(1 + MAX_ROOM_SIZE, HEIGHT - MAX_ROOM_SIZE) local col = rand(1 + MAX_ROOM_SIZE, WIDTH - MAX_ROOM_SIZE) roomCoords[i] = point.new(col, row) end -- spread them out a bit, but keep them within bounds for i = 1,20 do roomCoords = sd.spread(roomCoords, 10) clamp(roomCoords, 1 + MAX_ROOM_SIZE, WIDTH - MAX_ROOM_SIZE, 1 + MAX_ROOM_SIZE, HEIGHT - MAX_ROOM_SIZE) end -- fill them out into full rooms. rooms are 2 * roomSize + 1 cells square, centered -- on roomCoord local rooms = { } for i,roomCoord in ipairs(roomCoords) do local roomSize = math.random(MIN_ROOM_SIZE, MAX_ROOM_SIZE) local centerCol = math.ceil(roomCoord.x) local centerRow = math.ceil(roomCoord.y) for row=-roomSize,roomSize do for col=-roomSize,roomSize do if row == 0 and col == 0 then map[centerRow + row][centerCol + col] = RoomCenter.new() else map[centerRow + row][centerCol + col] = Room.new() end end end end xTunnels(map) yTunnels(map) local polys = { } for i=1,WIDTH do for j=1,HEIGHT do geom = map[i][j]:render(i,j) if geom == nil then -- do nothing elseif geom[1].x ~= nil then table.insert(polys, geom) else for _,realGeom in ipairs(geom) do table.insert(polys, realGeom) end end end end local pointWidth = CELL_SIZE * (WIDTH + 1) local pointHeight = CELL_SIZE * HEIGHT local border = { -- top { point.new(-CELL_SIZE, 0), point.new(pointWidth, 0), point.new(pointWidth, -CELL_SIZE), point.new(-CELL_SIZE, -CELL_SIZE) }, -- left { point.new(0, 0), point.new(-CELL_SIZE, 0), point.new(-CELL_SIZE, pointHeight), point.new(0, pointHeight) }, -- right { point.new(pointWidth + 0, 0), point.new(pointWidth + -CELL_SIZE, 0), point.new(pointWidth + -CELL_SIZE, pointHeight), point.new(pointWidth + 0, pointHeight) }, -- bottom { point.new(-CELL_SIZE, pointHeight + CELL_SIZE), point.new(pointWidth, pointHeight + CELL_SIZE), point.new(pointWidth, pointHeight), point.new(-CELL_SIZE, pointHeight) } } local result = Geom.clipPolygons(ClipType.Union, border, polys) for k,geom in ipairs(result) do bf:addItem(PolyWall.new(unpack(geom))) end -- find a place for the carrot local spawns = bf:findAllObjects({ }, ObjType.ShipSpawn) bf:addItem(FlagItem.new(spawns[math.random(1, #spawns)]:getPos(), Team.Neutral)) end function makeRectangle(x, y, w, h) return { point.new(math.floor(x - w / 2), math.floor(y - h / 2)), point.new(math.floor(x + w / 2), math.floor(y - h / 2)), point.new(math.floor(x + w / 2), math.floor(y + h / 2)), point.new(math.floor(x - w / 2), math.floor(y + h / 2)) } end function makeSquare(x, y, size) return makeRectangle(x, y, size, size) end function rand(a, b) return a + math.random() * (b - a) end function clamp(points, minx, maxx, miny, maxy) for i,p in ipairs(points) do points[i] = point.new(math.min(math.max(p.x, minx), maxx), math.min(math.max(p.y, miny), maxy) ) end return points end function xTunnels(map) -- add tunnels using an x sweep line local tunnels = { } local lastTunnel = nil for i = 1,HEIGHT do local tunnelStart = nil local inRoom = false if lastTunnel == nil or i - lastTunnel > 2 then for j = 1,WIDTH do if map[i][j].isRoom then inRoom = true if tunnelStart ~= nil then if j-tunnelStart > 1 then table.insert(tunnels, { point.new(tunnelStart, i), point.new(j - 1, i) }) lastTunnel = i end tunnelStart = nil end elseif map[i][j].isWall then -- when we hit a wall and we were in a room, then start a new tunnel if inRoom then inRoom = false tunnelStart = j end end end end end for i,tunnel in ipairs(tunnels) do for col=tunnel[1].x, tunnel[2].x do local endpoint = 0 if col == tunnel[1].x then endpoint = DIRECTION.LEFT elseif col == tunnel[2].x then endpoint = DIRECTION.RIGHT end map[tunnel[1].y][col] = Tunnel.new(false, i % 2 == 1, endpoint, -i) end end end function yTunnels(map) -- add tunnels using a y sweep line local tunnels = { } local lastTunnel = nil for j = 1,WIDTH do local tunnelStart = nil local inRoom = false for i = 1,HEIGHT do if map[i][j].isRoom then -- if we hit a room, end this tunnel inRoom = true if tunnelStart ~= nil and (lastTunnel == nil or j - lastTunnel > 2) then if i-tunnelStart > 1 then table.insert(tunnels, { point.new(j, tunnelStart), point.new(j, i - 1) }) lastTunnel = j end tunnelStart = nil end elseif map[i][j].isWall then -- when we hit a wall and we were in a room, then start a new tunnel if inRoom then inRoom = false tunnelStart = i end end end end for i,tunnel in ipairs(tunnels) do for row=tunnel[1].y, tunnel[2].y do local endpoint = 0 if row == tunnel[1].y then endpoint = DIRECTION.DOWN elseif row == tunnel[2].y then endpoint = DIRECTION.UP end local isIntersection = false if map[row][tunnel[1].x].isTunnel then -- we hit an existing tunnel, and need to put an intersection here isIntersection = true end map[row][tunnel[1].x] = Tunnel.new(true, i % 2 == 1, endpoint, i) map[row][tunnel[1].x].isIntersection = isIntersection end end end