Scripts Suddenly Stopped working for no apparent reason

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:
image

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!

1 Like

What’re you passing to this?

ExtraExtraExtra

Basically, when I used some of the scripts to generate a level, I’m using that level variable created by said scripts to pass it. And the function simply chooses a random room to place the player in.

This function is the source of all your problems. Inside the function, can you print the visited table before it returns anything?

Odd… I added the print(visited) before it returns; However, the output doesn’t show anything being printed…

That might be because of this. Can you print unvisited?

I put three instances where I print unvisited in different areas just for good measure, but it still isn’t printing anything. Very weird. Are we sure the function is even being utilized at this point of the generator process?

what if you wait for generation? idk im stoopid but i wanna help

I appreciate that! If you don’t mind though, where do you propose I should put a task.wait() in the scripts?

no like waitforchild(‘generation’) in the first script

also maybe add a print statement here

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

printing the root to see if it can successfully generate the root?

ig just add print statements to check the generation of the rooms, roots, and levels. maybe one of them is not being created and handled properly or smth. just check that their values are as expected. maybe.

Yes. I’ll show you.

prims is what returns the rootRoom that you later check for its center. If the function returns nil you can’t access the center giving you the error. The fact that the function doesn’t do anything is very concerning.

just a suggestion :man_shrugging:

function entity:initPlayer(level)
    while not level:getRoot() do
        print('Root not found for level:')
        print(level)
        task.wait()
    end
	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