I found this random dungeon generator on GitHub for making cool randomly generated levels for any game and decided to try and implement it in Roblox.
That part was a success and I got it to work and print randomly generated dungeons, but now returning to the game later the generator for some odd reason does not work at all.
This generator runs off of multiple scripts which I keep in a folder and look like this:
The way the generator runs and generated levels is by running a script with something like this:
local generation = script.Parent.Generation -- "Generation" folder with all the module scripts
function generate(name)
local dungeon = require(generation.Dungeon)
local entity = require(generation.Entity)
local currentDungeon = _G.Dungeons[name]
local floor = dungeon.new(currentDungeon.Height, currentDungeon.Width)
floor:generateDungeon()
entity:initPlayer(floor.level)
floor:printDungeon()
end
This is what the error looks like:
And here are all the scripts:
Dungeon:
local Level = require(script.Parent.Level)
Dungeon = {
["height"] = 0,
["width"] = 0,
["level"] = {}
}
function Dungeon.new(height, width)
Dungeon.height = height
Dungeon.width = width
Dungeon.level = {}
return Dungeon
end
function Dungeon:generateDungeon(advanced, maxRooms, maxRoomSize, scatteringFactor)
local level = Level.new(self.height, self.width)
if advanced then
level:setMaxRooms(maxRooms)
level:setMaxRoomSize(maxRoomSize)
level:setScatteringFactor(scatteringFactor)
end
--level:generateLevel("gift")
self.level = level
end
function Dungeon:printDungeon()
--Dungeon.level:printLevel()
print(Dungeon.level:getLevelData())
end
return Dungeon
Entity
local util = require(script.Parent.GenUtil)
local tile = require(script.Parent.Tile)
local entity = {}
function entity:initPlayer(level)
local c = level:getRoot().center
local adj = util.getAdjacentPos(c[1], c[2])
local i = 1
repeat
endr, endc = adj[i][1], adj[i][2]
i = i + 1
until level:getTile(endr, endc).class == tile.FLOOR
level:getTile(endr, endc).class = tile.PLAYER
end
function entity:initBoss(level)
local c = level:getEnd().center
local adj = util.getAdjacentPos(c[1], c[2])
local i = 1
repeat
endr, endc = adj[i][1], adj[i][2]
i = i + 1
until level:getTile(endr, endc).class == tile.FLOOR
level:getTile(endr, endc).class = tile.BOSS
end
return entity
GenUtil:
local random = math.random
local insert = table.insert
local remove = table.remove
local util = {}
function util.getAdjacentPos(row, col)
local result = {}
for dx = -1, 1 do
for dy = -1, 1 do
result[#result+1] = {row + dy, col + dx}
end
end
for i = 1, #result do
end
return result
end
function util.findNext(start, goal)
if start == goal then return goal end
local row, col = start[1], start[2]
local adj = util.getAdjacentPos(start[1], start[2])
local dist = util.getDist(start, goal)
local nextPos
for i = 1, #adj do
local adjT = adj[i]
if (util.getDist(adjT, goal) < dist) and
i % 2 == 0
then
nextPos = adjT
dist = util.getDist(nextPos, goal)
break
end
end
return nextPos
end
function util.getRandNeighbour(row, col, notDiag)
if notDiag then
local f = {-1, 1}
local d = f[random(1, 2)]
if random() > 0.5 then
return row + d, col
else
return row, col + d
end
else
local dir = {random(-1, 1), random(-1, 1)}
return row + dir[1], col + dir[2]
end
end
function util.withinBounds(r, c, height, width)
return (r < height and r > 0 and c < width and c > 0)
end
function util.prims(unvisited)
local len = #unvisited
local root = remove(unvisited, 1)
if #unvisited == 0 then return root, root end
local visited = {}
insert(visited, root)
repeat
local dist = 1e309
local v0, endIndex
for i = 1 ,#visited do
for j = 1, #unvisited do
if (unvisited[j]:distanceTo(visited[i]) < dist) then
dist = unvisited[j]:distanceTo(visited[i])
v0 = visited[i]
endIndex = j
end
end
end
local v1 = remove(unvisited, endIndex)
v0:addNeighbour(v1)
insert(visited, v1)
until #visited == len
return visited[1], visited[#visited]
end
--source: https://stackoverflow.com/questions/2705793/how-to-get-number-of-entries-in-a-lua-table
function util.tablelength(T)
local count = 0
for i in pairs(T) do count = count + 1 end
return count
end
function util.cloneTable(org)
local newTable = {}
for k, v in pairs(org) do
newTable[k] = v
end
return newTable
end
function util.getDist(start, goal)
return math.sqrt(math.pow(math.abs(goal[1] - start[1]), 2) + math.pow(math.abs(goal[2] - start[2]), 2))
end
return util
Level:
local Func = require(script.Parent.GenUtil)
local Tile = require(script.Parent.Tile)
local Room = require(script.Parent.Room)
local random = math.random
local floor = math.floor
local ceil = math.ceil
local min = math.min
local max = math.max
local insert = table.insert
local prims = Func.prims
local findNext = Func.findNext
local getAdjacentPos = Func.getAdjacentPos
local getRandNeighbour = Func.getRandNeighbour
local withinBounds = Func.withinBounds
seed = os.time()
math.randomseed(seed)
Level = {
["height"] = 0,
["width"] = 0,
["matrix"] = {},
["rooms"] = {},
["entrances"] = {},
["staircases"] = {},
["specials"] = {}
}
Level.__index = Level
Level.MIN_ROOM_SIZE = 3
Level.veinSpawnRate = 0.02
Level.soilSpawnRate = 0.05
function Level.new(height, width)
if height < 10 or width < 10 then error("Level must have height>=10, width>=10") end
Level.height=height
Level.width=width
Level.matrix = {}
Level.rooms = {}
Level.entrances = {}
Level.staircases = {}
Level.specials = {}
Level.rootRoom = nil
Level.endRoom = nil
Level.maxRoomSize = ceil(min(height, width)/10)+5
Level.maxRooms = ceil(max(height, width)/Level.MIN_ROOM_SIZE)
-- Determines amount of random tiles built when generating corridors:
Level.scatteringFactor = ceil(max(height,width)/Level.maxRoomSize)
return Level
end
function Level:generateLevel(special)
self:initMap()
self:generateRooms()
local root = self:getRoomTree()
self:buildCorridors(root)
-- self:addCycles(5)
self:addStaircases()
-- self:addDoors()
if special then
self:addSpecial(special, 1)
end
end
function Level:initMap()
for i=-1,self.height+1 do
self.matrix[i] = {}
for j=0,self.width+1 do
self.matrix[i][j] = Tile:new(Tile.EMPTY)
end
end
self:addWalls(0, 0, self.height+1, self.width+1)
end
function Level:printLevel()
for i = 0, self.height + 1 do
local row = " "
for j = 0, self.width+1 do
row = row..self.matrix[i][j].class..Tile.EMPTY
end
print(row)
end
end
function Level:getLevelData()
local data = {}
for i = 0, self.height + 1 do
local row = ""
for j = 0, self.width+1 do
row = row..self.matrix[i][j].class
end
insert(data, row)
end
return data
end
function Level:buildLevel()
for i = 0, self.height + 1 do
local row = " "
for j = 0, self.width + 1 do
row = row..self.matrix[i][j].class..Tile.EMPTY
end
print(row)
end
end
function Level:getRandRoom()
-- return: Random room in level
local i = random(1,#self.rooms)
return self.rooms[i]
end
function Level:getRoot()
-- return: Room that is root of room tree if such has been generated.
return self.rootRoom
end
function Level:getEnd()
-- return: Leaf room added last to tree if such has been generated.
return self.endRoom
end
function Level:getStaircases()
-- Index [1] for row, [2] for col on individual entry for individual staircase.
return self.staircases
end
function Level:getTile(r, c)
return self.matrix[r][c]
end
function Level:isRoom(row,col)
return (self:getTile(row,col).roomId ~= 0)
end
function Level:setMaxRooms(m)
self.maxRooms=m
end
function Level:setScatteringFactor(f)
self.scatteringFactor = f
end
function Level:setMaxRoomSize(m)
if m > min(self.height, self.width) or m < 3 then
error("MaxRoomSize can't be bigger than height-3/width-3 or smaller than 3")
end
self.maxRoomSize=m
end
function Level:generateRooms()
for i = 1,self.maxRooms do
self:generateRoom()
end
end
function Level:generateRoom()
-- Will randomly place rooms across tiles (no overlapping).
local startRow = random(1, self.height-self.maxRoomSize)
local startCol = random(1, self.width-self.maxRoomSize)
local height = random(Level.MIN_ROOM_SIZE, self.maxRoomSize)
local width = random(Level.MIN_ROOM_SIZE, self.maxRoomSize)
for i=startRow-1, startRow+height+1 do
for j=startCol-1, startCol+width+1 do
if (self:isRoom(i,j)) then
return -- Room is overlapping other room->room is discarded
end
end
end
self:buildRoom(startRow, startCol, startRow+height, startCol+width)
end
function Level:getRoomTree()
if #self.rooms < 1 then error("Can't generate room tree, no rooms exists") end
local root, lastLeaf = prims(table.clone(self.rooms))
self.rootRoom = root
self.endRoom = lastLeaf
return root
end
function Level:buildRoom(startR, startC, endR, endC)
-- init room object and paint room onto tiles.
local id = #self.rooms+1
local room = Room:new(id)
local r,c =endR-floor((endR-startR)/2), endC-floor((endC-startC)/2)
room:setCenter(r,c)
insert(self.rooms, room)
for i=startR, endR do
for j=startC, endC do
local tile = self:getTile(i,j)
tile.roomId, tile.class = id, Tile.FLOOR
end
end
self:addWalls(startR-1, startC-1, endR+1, endC+1)
end
function Level:buildCorridors(root)
-- Recursive DFS function for building corridors to every neighbour of a room (root)
for i = 1, #root.neighbours do
local neigh = root.neighbours[i]
self:buildCorridor(root, neigh)
self:buildCorridors(neigh)
end
end
function Level:buildCorridor(from, to)
-- Parameters from and to are both Room-objects.
local start, goal = from.center, to.center
local nextTile = findNext(start, goal)
local row, col
repeat
row, col = nextTile[1], nextTile[2]
self:buildTile(row, col)
if random() < self.scatteringFactor*0.02 then
self:buildRandomTiles(row,col) -- Makes the corridors a little more interesting
end
nextTile = findNext(nextTile, goal)
until (self:getTile(nextTile[1], nextTile[2]).roomId == to.id)
insert(self.entrances, {row, col})
end
function Level:buildTile(r, c)
-- Builds floor tile surrounded by walls.
-- Only floor and empty tiles around floor tiles turns to walls.
local adj = getAdjacentPos(r,c)
self:getTile(r, c).class = Tile.FLOOR
for i=1,#adj do
r, c = adj[i][1], adj[i][2]
if not (self:getTile(r,c).class == Tile.FLOOR) then
self:placeWall(r,c)
end
end
end
function Level:addDoors(maxDoors)
-- Adds open or closed door randomly to entrance tiles.
if #self.entrances == 0 or #self.rooms < 2 then return end
if not maxDoors then maxDoors = #self.entrances end
for i=1,maxDoors do
local e = self.entrances[i]
if self:isValidEntrance(e[1], e[2]) then
local tile = self:getTile(e[1], e[2])
if random() > 0.5 then
tile.class = Tile.C_DOOR
else
tile.class = Tile.O_DOOR
end
end
end
end
function Level:addStaircases(maxStaircases)
-- Adds both descending and ascending staircases to random rooms.
-- Number of staircases depend on number of rooms.
if (not maxStaircases) or (maxStaircases > #self.rooms) then
maxStaircases = ceil(#self.rooms-(#self.rooms/2))+1
end
local staircases = random(2,maxStaircases)
repeat
local room = self:getRandRoom()
if not room.hasStaircase or #self.rooms == 1 then
self:placeStaircase(room, staircases)
staircases = staircases-1
end
until staircases==0
end
function Level:addSpecial(special, maxTiles)
-- Adds one special item to the current level
-- Special items such as lost gifts, gummi's, etc.
if (not maxTiles) or (maxTiles > #self.rooms) then
maxTiles = ceil(#self.rooms-(#self.rooms/2))+1
end
local specials = random(1, maxTiles)
repeat
local room = self:getRandRoom()
if not room.hasSpecial or #self.rooms == 1 then
local steps = random(0, floor(self.maxRoomSize/2))
local nrow, ncol = room.center[1], room.center[2]
local row, col
repeat
row, col = nrow, ncol
repeat
nrow, ncol = getRandNeighbour(row, col)
until self:getTile(nrow, ncol).class == Tile.FLOOR
steps = steps - 1
until (self:getTile(nrow, ncol).roomId ~= room.id or steps <= 0)
if special == "gift" then
self:getTile(row, col).class = Tile.SPECIAL_GIFT
elseif special == "gummi" then
self:getTile(row, col).class = Tile.SPECIAL_GUMMI
end
room.hasSpecial = true
insert(self.specials, {row, col})
specials = specials-1
end
until specials == 0
end
function Level:addWalls(startR, startC, endR, endC)
-- Places walls on circumference of given rectangle.
-- Upper and lower sides
for j=startC,endC do
self:placeWall(startR, j)
self:placeWall(endR, j)
end
-- Left and right sides
for i=startR,endR do
self:placeWall(i, startC)
self:placeWall(i, endC)
end
end
function Level:placeWall(r, c)
-- Places wall at given coordinate. Could either place
-- regular wall, soil or mineral vein
local tile = self:getTile(r, c)
if random() <= Level.veinSpawnRate then
tile.class = Tile.VEIN
elseif random() <= Level.soilSpawnRate then
tile.class = Tile.SOIL
Level.soilSpawnRate = 0.6 -- for clustering
else
tile.class = Tile.WALL
Level.soilSpawnRate = 0.05
end
end
function Level:placeStaircase(room, staircases)
-- Places staircase in given room.
-- Position is random number of steps away from center.
local steps = random(0, floor(self.maxRoomSize/2))
local nrow, ncol = room.center[1], room.center[2]
local row, col
repeat
row, col = nrow, ncol
repeat
nrow, ncol = getRandNeighbour(row, col)
until self:getTile(nrow, ncol).class == Tile.FLOOR
steps = steps - 1
until (self:getTile(nrow, ncol).roomId ~= room.id or steps <= 0)
if staircases%2 == 0 then
self:getTile(row, col).class = Tile.D_STAIRCASE
else
self:getTile(row, col).class = Tile.A_STAIRCASE
end
room.hasStaircase = true
insert(self.staircases, {row, col})
end
function Level:isValidEntrance(row, col)
return ((self:getTile(row+1,col):isWall() and self:getTile(row-1,col):isWall()) or (self:getTile(row,col+1):isWall() and self:getTile(row,col-1):isWall()))
end
function Level:getAdjacentTiles(row, col)
local result={}
local adj = getAdjacentPos(row, col)
for i = 1, #adj do
local row, col = adj[i][1], adj[i][2]
insert(result, self:getTile(row, col))
end
return result
end
function Level:buildRandomTiles(r, c)
-- Creates random floor tiles starting from given tile.
local rand = random(1, self.scatteringFactor)
for i = 1, rand do
local nr, nc = getRandNeighbour(r, c, true)
if (self:getTile(nr, nc).roomId == 0 and
withinBounds(nr, nc, self.height, self.width)) then
self:buildTile(nr, nc)
r, c= nr, nc
end
end
end
function Level:addCycles(maxCycles)
-- Adds corridors
for i = 1, maxCycles do
local from = self:getRandRoom()
local to = self:getRandRoom()
self:buildCorridor(from, to)
end
end
return Level
Room:
local Func = require(script.Parent.GenUtil)
local pow = math.pow
local abs = math.abs
Room = {
["id"] = 0,
["neighbours"] = {},
["center"] = {},
["hasStaircase"] = {}
}
Room.__index = Room
function Room:new(id)
Room.id = id
Room.neighbours = {}
Room.center = {}
Room.hasStaircase = false
return Room
end
function Room:addNeighbour(other)
table.insert(self.neighbours, other)
end
function Room:hasNeighbours()
return Func.tablelength(self.neighbours) > 1
end
function Room:setCenter(r, c)
self.center = {r, c}
end
function Room:distanceTo(other)
return math.sqrt(pow(abs(self.center[1] - other.center[1]), 2) + pow(abs(self.center[2] - other.center[2]), 2))
end
return Room
Tile:
-- Tile objects:
-- * " " for empty
-- * "." for floor
-- * "#" for wall
-- * "<" for ascending staircase
-- * ">" for descending staircase
-- * "%" for soil
-- * "*" for mineral vein
-- * "'" for open door
-- * "+" for closed door
Tile = {
["class"] = "",
["roomId"] = 0
}
Tile.EMPTY = " "
Tile.FLOOR = "."
Tile.WALL = "#"
Tile.A_STAIRCASE = "<"
Tile.D_STAIRCASE = ">"
Tile.SOIL = "%"
Tile.VEIN = "*"
Tile.SPECIAL_GIFT = "G"
Tile.SPECIAL_GUMMI = "~"
Tile.C_DOOR = "+"
Tile.O_DOOR = "'"
Tile.PLAYER = "@"
Tile.BOSS = "B"
function Tile:new(t)
Tile.class = t
Tile.roomId = 0
return Tile
end
function Tile:isWall()
return self.class == Tile.WALL or self.class == Tile.SOIL or self.class == Tile.VEIN
end
return Tile
I understand how niche this problem is and I hope, atleast considering this system worked flawlessly not even a day ago, that the answer is quite simple. Again, I have not changed the system whatsoever, so I’m completely lost. Thanks!