// Boring Arrays

// Default Maps.

local map1 = {
9,0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,1,1,1,0,2,0,1,0,1,0,0,4,1,0,0,0,1,0,1,0,1,1,1,0,0,1,1,0,1,0,0,0,1,5,0,1,0,0,1,1,1,0,1,1,0,0,0,1,1,0,0,0,0,8,0,1,0,1,5,0,1,0,1,0,0,1,0,1,1,1,1,0,1,0,1,1,0,0,0,0,0,0,0,0,
}

local map2 = {
8,0,0,0,0,0,0,0,0,4,1,1,1,1,1,1,1,0,1,1,0,0,0,1,0,0,0,0,0,0,0,9,0,1,0,1,1,1,1,1,0,0,0,1,0,0,0,0,0,0,1,0,1,1,1,1,1,5,1,0,0,0,0,0,0,0,0,0,1,0,1,1,1,1,1,1,1,0,1,4,3,0,1,1,1,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,
}

local map3 = {
0,0,0,0,0,0,0,0,0,9,0,1,0,1,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,3,1,1,1,0,1,4,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,1,1,1,5,1,1,1,0,1,2,0,0,0,0,0,0,0,0,0,0,8,1,0,1,0,1,0,1,1,1,
}

local map4 = {
9,0,0,0,0,0,0,0,0,0,1,1,1,1,1,5,1,1,1,0,0,0,0,8,1,0,0,0,0,0,0,1,4,1,1,0,1,1,1,1,0,1,1,1,1,0,1,0,0,0,0,0,0,0,1,0,1,4,1,0,5,1,1,0,1,0,1,0,1,0,0,0,0,0,1,0,1,0,0,0,0,1,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,
}

local map5 = {
2,1,8,1,1,1,1,1,1,1,0,1,0,1,5,0,0,0,0,0,0,1,0,1,1,1,1,0,1,0,0,0,0,1,1,0,0,0,1,0,1,1,0,1,1,0,1,1,1,0,0,0,0,1,9,0,1,0,0,0,0,1,1,1,1,1,1,0,1,0,0,1,0,0,0,4,1,4,1,0,0,1,1,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,
}

local map6 = {
9,0,0,0,0,0,0,0,1,4,0,1,1,1,1,5,1,1,1,0,0,1,0,0,0,0,0,0,1,0,0,1,0,1,1,0,1,0,1,0,0,5,0,0,0,0,1,0,1,0,0,1,0,1,8,0,0,0,0,0,0,1,0,1,0,1,1,0,1,0,0,1,0,0,0,0,0,2,1,0,0,1,1,1,5,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,
}

local map7 = {
3,0,0,0,0,0,0,0,0,8,0,1,0,1,1,1,1,1,1,1,0,0,0,1,0,0,0,1,4,0,0,1,1,1,0,9,0,1,0,0,0,0,0,1,0,0,0,1,1,0,0,1,0,1,1,1,0,1,0,0,0,1,0,0,0,1,0,1,0,0,0,1,4,0,0,0,0,1,0,0,0,1,1,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,
}

local map8 = {
0,0,0,1,1,1,1,8,1,1,0,9,0,1,1,1,0,0,0,1,0,0,0,1,1,0,0,0,0,0,1,0,1,1,1,0,0,3,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,0,1,1,1,1,0,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,4,0,0,0,1,0,0,0,0,0,0,
}

local map9 = {
8,0,0,0,1,1,0,0,0,4,0,0,1,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,0,0,1,0,0,0,0,1,0,0,1,5,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,2,0,0,5,1,1,0,0,0,9,
}

local map10 = {
0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,1,0,0,0,0,0,0,1,0,9,0,0,0,0,0,0,0,0,0,0,
}

// Custom Floors are always added BEFORE map10.

local maps = {map1, map2, map3, map4, map5, map6, map7, map8, map9, map10}

// Variable stuff.

local dungeon_musicchange = CV_RegisterVar({
	name = "dungeon_musicchange",
	defaultvalue = 1,
	flags = nil,
	PossibleValue = CV_OnOff,
	func = nil
})	

// Swords.
// "X": Name of the Sword.
// X: ATK of the Sword.

local nothing = {"Nothing", 0} 
local dagger = {"Dagger", 1}
local smallsword = {"Small Sword", 2}
local broadsword = {"Broadsword", 4}
local greatsword = {"Greatsword", 8}
local mastersword = {"Master Sword", 16}
local goldensword = {"Golden Sword", 32}
local ultimasword = {"Ultima Sword", 64}

local swords = {nothing, dagger, smallsword, broadsword, greatsword, mastersword, goldensword, ultimasword}

// Shields.
// "X": Name of the Shield.
// X: DEF of the Shield.

local nothingagain = {"Nothing", 0}
local potlid = {"Pot Lid", 15}
local smallshield = {"Small Shield", 45}
local shield = {"Shield", 75}
local metalshield = {"Metal Shield", 105}
local mirrorshield = {"Mirror Shield", 135}
local hallofshield = {"Hall o'Shield", 165}

local shields = {nothingagain, potlid, smallshield, shield, metalshield, mirrorshield, hallofshield}

// Healing potions.
// "X": Name of the Potion.
// X: HP of the Potion.

local smallhppotion = {"Small HP Potion", 35}
local hppotion = {"HP Potion", 75}
local bighppotion = {"Big HP Potion", 125}
local ultrahppotion = {"Ult. HP Potion", 350}

local potions = {smallhppotion, hppotion, bighppotion, ultrahppotion}

// Base Enemy HP.

local hptable = {
	[MT_BLUECRAWLA] = 5,
	[MT_REDCRAWLA] = 6,
	[MT_JETTGUNNER] = 12,
	[MT_SPINCUSHION] = 4,
	[MT_EGGGUARD] = 12,
	[MT_FACESTABBER] = 16,
	[MT_EGGMOBILE] = 100,
}

// Base Enemy Damage.

local dmgtable = {
	[MT_BLUECRAWLA] = 25,
	[MT_REDCRAWLA] = 35,
	[MT_JETTGUNNER] = 35,
	[MT_SPINCUSHION] = 40,
	[MT_EGGGUARD] = 35,
	[MT_FACESTABBER] = 45,
	[MT_EGGMOBILE] = 36,
}

// Challenges.

local challenges = {
	{0, "None", 0},
	{1, "Minimal Kills", 1*#maps},
	{2, "Minimal Level", 1*(#maps/2)},
	{3, "No Equipment", 1, 1},
	{4, "Genocide", (5*#maps)+25}
}

// Starting stats.

local charstats = {
	{100, 2, 10}, // Sonic
	{120, 1, 11}, // Tails
	{70, 5, 7}, // Knuckles
	{90, 4, 8}, // Amy
	{50, 6, 6}, // Fang
	{100, 2, 10}, // Metal Sonic
	{90, 4, 8}, // Other Characters
}

// Per-Level Up stats.

local levelupstats = {
	{6, 3, 7}, // Sonic
	{7, 4, 8}, // Tails
	{4, 8, 4}, // Knuckles
	{5, 5, 7}, // Amy
	{4, 7, 4}, // Fang
	{6, 4, 7}, // Metal Sonic
	{5, 5, 7}, // Other Characters
}

// Boring Functions of Mine.

// Load Map stuff.

local function P_LoadArray(p, filename)
	local thing = io.openlocal("client/Dungeon/Maps/"+filename, "r+")
	local stringy = thing:read("*a")
	local maparray = {}
	for i=0,10
		local array = {}
		for k=0,20
			if string.sub(stringy, k, k) ~= ","
				table.insert(maparray, tonumber(string.sub(stringy, k+(20*i), k+(20*i))))
			end
		end
	end
	table.insert(maps, #maps, maparray)
	thing:close()
end

// Cheat checker.

local function canCheat(p)
	if (p.rpg.challenge == challenges[1][1]) and ((IsPlayerAdmin(p)) or (p == server))
		return true
	else
		return false
	end
end

// Level Up stuff.

local function P_LevelUp(p)
	local skin = skins[p.mo.skin]
	
	if #skin > 6
		skin = 7
	else
		skin = #skin
	end
	
	local vars = levelupstats[skin+1]
	
	if p.rpg.level < 999
		p.rpg.level = $ + 1
		p.rpg.xp = 0
		p.rpg.nextxp = $ + (75 * p.rpg.level)
		p.rpg.maxhp = $ + (vars[1] * p.rpg.level)
		p.rpg.baseatk = $ + (vars[2] * p.rpg.level)
		p.rpg.basedef = $ + (vars[3] * p.rpg.level)
		p.rpg.hp = p.rpg.maxhp
	end
end

// Thanks LJ Sonic, this is very useful.
local function blockToSector(x, y)
    return R_PointInSubsector(x * 64*FRACUNIT - FRACUNIT, y * 64*FRACUNIT - FRACUNIT).sector
end

local curspringcoords = {}

local function P_BuildMap(p)
	if p.rpg
	
		p.pflags = $|PF_NOCLIP
		p.rpg.map = maps[p.rpg.floor]
		p.rpg.bossspawned = false
		curspringcoords = {}
		
		for r=1,10
			for c=1,10
			
				local block = blockToSector(c, r)
				block.floorheight = 0
				
				if p.rpg.floor == #maps
					block.ceilingheight = 128*FRACUNIT
				else
					block.ceilingheight = 64*FRACUNIT
				end
				
				for s in sectors.iterate
					if p.rpg.floor < #maps - ((#maps/2) + (#maps/4))
						local floor = "GFZFLR01"
						local texture = 1495
						s.floorpic = floor
						s.ceilingpic = floor
						for i=0,3
							local l = s.lines[i]
							l.frontside.bottomtexture = texture
							if l.backside
								l.backside.bottomtexture = texture
							end
						end
					elseif p.rpg.floor < #maps - (#maps/4)
						local floor = "THZFLR24"
						local texture = 2290
						s.floorpic = floor
						s.ceilingpic = floor
						for i=0,3
							local l = s.lines[i]
							l.frontside.bottomtexture = texture
							if l.backside
								l.backside.bottomtexture = texture
							end
						end
					elseif p.rpg.floor < #maps
						local floor = "CEZFLR06"
						local texture = 3100
						s.floorpic = floor
						s.ceilingpic = floor
						for i=0,3
							local l = s.lines[i]
							l.frontside.bottomtexture = texture
							if l.backside
								l.backside.bottomtexture = texture
							end
						end
					else
						local floor = "ERZRCKF1"
						local texture = 2500
						s.floorpic = floor
						s.ceilingpic = floor
						for i=0,3
							local l = s.lines[i]
							l.frontside.bottomtexture = texture
							if l.backside
								l.backside.bottomtexture = texture
							end
						end
					end
				end
				
				if p.rpg.map[block.tag] == 1
					if p.rpg.floor == #maps
						block.floorheight = 128*FRACUNIT
					else
						block.floorheight = 64*FRACUNIT
					end
				elseif p.rpg.map[block.tag] == 2
					P_SpawnMobj(c*(64*FRACUNIT)-32*FRACUNIT, r*(64*FRACUNIT)-32*FRACUNIT, 0, MT_ARMAGEDDON_BOX)
				elseif p.rpg.map[block.tag] == 3
					P_SpawnMobj(c*(64*FRACUNIT)-32*FRACUNIT, r*(64*FRACUNIT)-32*FRACUNIT, 0, MT_FORCE_BOX)
				elseif p.rpg.map[block.tag] == 4
					P_SpawnMobj(c*(64*FRACUNIT)-32*FRACUNIT, r*(64*FRACUNIT)-32*FRACUNIT, 0, MT_RING_BOX)
				elseif p.rpg.map[block.tag] == 5
					P_SpawnMobj(c*(64*FRACUNIT)-32*FRACUNIT, r*(64*FRACUNIT)-32*FRACUNIT, 0, MT_EGGMAN_BOX)
				elseif p.rpg.map[block.tag] == 8
					P_SpawnMobj(c*(64*FRACUNIT)-32*FRACUNIT, r*(64*FRACUNIT)-32*FRACUNIT, 0, MT_YELLOWSPRING)
					table.insert(curspringcoords, c)
					table.insert(curspringcoords, r)
				elseif p.rpg.map[block.tag] == 9
					P_TeleportMove(p.mo, c*(64*FRACUNIT)-32*FRACUNIT, r*(64*FRACUNIT)-32*FRACUNIT, 0)
				elseif p.rpg.map[block.tag] == 0
					if p.rpg.floor < #maps - ((#maps/2) + (#maps/4))
						if P_RandomRange(1,8) == 7
							P_SpawnMobj(c*(64*FRACUNIT)-32*FRACUNIT, r*(64*FRACUNIT)-32*FRACUNIT, 0, MT_REDCRAWLA)
						elseif P_RandomRange(1,8) == 3
							P_SpawnMobj(c*(64*FRACUNIT)-32*FRACUNIT, r*(64*FRACUNIT)-32*FRACUNIT, 0, MT_BLUECRAWLA)
						end
					elseif p.rpg.floor < #maps - (#maps/4)
						if P_RandomRange(1,10) == 1
							P_SpawnMobj(c*(64*FRACUNIT)-32*FRACUNIT, r*(64*FRACUNIT)-32*FRACUNIT, 0, MT_JETTGUNNER)
						elseif P_RandomRange(1,10) == 5
							P_SpawnMobj(c*(64*FRACUNIT)-32*FRACUNIT, r*(64*FRACUNIT)-32*FRACUNIT, 0, MT_SPINCUSHION)
						end
					elseif p.rpg.floor < #maps
						if P_RandomRange(1,12) == 2
							P_SpawnMobj(c*(64*FRACUNIT)-32*FRACUNIT, r*(64*FRACUNIT)-32*FRACUNIT, 0, MT_EGGGUARD)
						elseif P_RandomRange(1,12) == 8
							P_SpawnMobj(c*(64*FRACUNIT)-32*FRACUNIT, r*(64*FRACUNIT)-32*FRACUNIT, 0, MT_FACESTABBER)
						end
					else
						if (not p.rpg.bossspawned) and (c == 5) and (r == 5)
							P_SpawnMobj(c*(64*FRACUNIT)-32*FRACUNIT, r*(64*FRACUNIT)-32*FRACUNIT, 0, MT_EGGMOBILE)
							p.rpg.bossspawned = true
						end
					end
				end
			end
		end

		if dungeon_musicchange.value > 0
			if p.rpg.floor < #maps - ((#maps/2) + (#maps/4))
				S_ChangeMusic("lowflr", true, p)
			elseif p.rpg.floor < #maps - (#maps/4)
				S_ChangeMusic("midflr", true, p)
			elseif p.rpg.floor < #maps
				S_ChangeMusic("hiflr", true, p)
			else
				S_ChangeMusic("rpboss", true, p)
			end
		end
		
		p.rpg.spawncoords = {p.mo.x, p.mo.y, p.mo.z}
		p.pflags = $ & ~PF_NOCLIP
		
	end	
end

local function P_RPGVars(p)
	if p.rpg == nil
		p.rpg = {}
		p.rpg.musicchange = true
		local fp
		
		for pl in players.iterate
			if pl ~= nil
				fp = pl
				break
			end
		end
		

		if p == fp
			p.rpg.floor = 1
			p.rpg.map = maps[p.rpg.floor]
			p.rpg.bossspawned = false
		else
			p.rpg.floor = fp.rpg.floor
			p.rpg.map = maps[fp.rpg.floor]
			p.rpg.bossspawned = fp.rpg.bossspawned
		end
		
		local skin = skins[p.mo.skin]
	
		if #skin > 6
			skin = 7
		else
			skin = #skin
		end
		
		local vars = charstats[skin+1]
		
		p.continues = 1
		p.lives = 3
		p.rpg.level = 1
		p.rpg.xp = 0
		p.rpg.nextxp = 100
		p.rpg.maxhp = vars[1]
		p.rpg.baseatk = vars[2]
		p.rpg.basedef = vars[3]
		
		p.rpg.swordnum = 2
		p.rpg.shieldnum = 2
		p.rpg.inventory = {potions[1]}
		
		p.rpg.challenge = challenges[1][1]
		p.rpg.kills = 0
		
	end
	
	if p.rpg.challenge ~= challenges[1][1]
		p.lives = 1
		p.continues = 0
		p.xtralife = 0
	end
		
	p.rpg.hp = p.rpg.maxhp 
	p.mo.scale = FRACUNIT/4
	
end

// Commands 

COM_AddCommand("challenge", function(p, arg)
	if arg
		if challenges[tonumber(arg)+1] ~= nil
			if p.rpg.challenge == challenges[1][1]
				CONS_Printf(p, "You chose "+challenges[tonumber(arg)+1][2]+" as your challenge!")
				p.rpg = nil
				for mob in mobjs.iterate()
					if mob.type ~= MT_PLAYER
						P_RemoveMobj(mob)
					end
				end
				P_RPGVars(p)
				p.rpg.challenge = challenges[tonumber(arg)+1][1]
				P_RPGVars(p)
				if p.rpg.challenge == challenges[2][1]
					CONS_Printf(p, "Don't kill more than "+challenges[2][3]+" enemies or else!")
				elseif p.rpg.challenge == challenges[3][1]
					CONS_Printf(p, "Don't let your level reach more than "+challenges[3][3]+" or else!")
				elseif p.rpg.challenge == challenges[5][1]
					CONS_Printf(p, "Kill at least "+challenges[5][3]+" enemies before the Boss or else!")
				end
				P_BuildMap(p)
			else
				CONS_Printf(p, "You can't change your challenge!")
			end
		else
			CONS_Printf(p, "Challenges: Want more difficulty in your Dungeon? Use this!\nYou can't change challenges until you exit the dungeon!\n\n0 - None.\n1 - Minimal Kills - You can't kill more than "+tostring(challenges[2][3])+" enemies!\n2 - Minimal Level - Your Level can't be any higher than "+tostring(challenges[3][3])+"!\n3 - No Equipment - You have no equipment making you weaker!\n4 - Genocide - Kill at least "+challenges[5][3]+" enemies!")
		end
	else
		CONS_Printf(p, "Challenges: Want more difficulty in your Dungeon? Use this!\nYou can't change challenges until you exit the dungeon!\n\nHow to use: challenge <number>\n\n0 - None.\n1 - Minimal Kills - You can't kill more than "+tostring(challenges[2][3])+" enemies!\n2 - Minimal Level - Your Level can't be any higher than "+tostring(challenges[3][3])+"!\n3 - No Equipment - You have no equipment making you weaker!\n4 - Genocide - Kill at least "+challenges[5][3]+" enemies!")
	end
end)

COM_AddCommand("addgamefloor", function(p, arg)
	if p == server
		local testfile = io.openlocal("client/Dungeon/Maps/"+arg, "r+")
		
		if testfile ~= nil
			P_LoadArray(p, arg)
		else
			CONS_Printf(p, "There is no file with this filename!")
		end
	end
end)

// Cheats

COM_AddCommand("levelup", function(p, arg)
	if p.rpg
		if canCheat(p)
			for i=1,tonumber(arg)
				if p.rpg.level < 999
					P_LevelUp(p)
				end
			end
		end
	end
end)

COM_AddCommand("swordtype", function(p, arg)
	if p.rpg
		if canCheat(p)
			arg = tonumber(arg)
			if swords[arg] ~= nil
				p.rpg.swordnum = arg
			else
				CONS_Printf(p, "Invalid Sword ID!")
			end
		end
	end
end)

COM_AddCommand("shieldtype", function(p, arg)
	if p.rpg
		if canCheat(p)
			arg = tonumber(arg)
			if shields[arg] ~= nil
				p.rpg.shieldnum = arg
			else
				CONS_Printf(p, "Invalid Shield ID!")
			end
		end
	end
end)

COM_AddCommand("loadgamefloor", function(p, arg)
	if not netgame
		if maps[tonumber(arg)] ~= nil
			p.rpg.floor = tonumber(arg)
			for mob in mobjs.iterate()
				if mob.type ~= MT_PLAYER
					P_RemoveMobj(mob)
				end
			end
			P_BuildMap(p)
			return false
		else
			if arg == "last"
				p.rpg.floor = #maps
				for mob in mobjs.iterate()
					if mob.type ~= MT_PLAYER
						P_RemoveMobj(mob)
					end
				end
				P_BuildMap(p)
				return false
			else
				CONS_Printf(p, "Invalid floor number!")
			end
		end
	end
end)

// Hooks.

addHook("MapLoad", function()
	local fp
		
	for pl in players.iterate
		if pl ~= nil
			fp = pl
			break
		end
	end
	
	if fp
		P_BuildMap(fp)
		fp.spawned = true
	end
	
	for pl in players.iterate
		P_TeleportMove(pl.mo, fp.rpg.spawncoords[1], fp.rpg.spawncoords[2], fp.rpg.spawncoords[3])
		pl.pflags = $ & ~PF_NOCLIP
	end
end)

addHook("PlayerSpawn", function(p)
	if mapheaderinfo[gamemap].lvlttl == "Dungeon Crawler"
	
		p.pflags = $|PF_NOCLIP
	
		P_RPGVars(p)
		
		local fp
		
		for pl in players.iterate
			if pl ~= nil
				fp = pl
				break
			end
		end
		
		if netgame
			p.inf = true
		end
		
		if not fp.spawned 
			if (p == server) or (p == fp)
				P_BuildMap(p)
				fp.spawned = true
			end
		end
		
		if dungeon_musicchange.value > 0
			if p.rpg.floor < #maps - ((#maps/2) + (#maps/4))
				S_ChangeMusic("lowflr", true, p)
			elseif p.rpg.floor < #maps - (#maps/4)
				S_ChangeMusic("midflr", true, p)
			elseif p.rpg.floor < #maps
				S_ChangeMusic("hiflr", true, p)
			else
				S_ChangeMusic("rpboss", true, p)
			end
		end
		
		P_TeleportMove(p.mo, fp.rpg.spawncoords[1], fp.rpg.spawncoords[2], fp.rpg.spawncoords[3])
		p.pflags = $ & ~PF_NOCLIP
	
	end
end)

addHook("PlayerThink", function(p)
	if p.mo and p.mo.valid
		if p.rpg
			
			// Generic SRB2 stuff.
			p.powers[pw_shield] = SH_NONE
			p.rings = 1
			
			if p.inf
				p.lives = 99
			end
			
			// Re-doing the Spring code, was so fucking janky before. Old code below this for comparison.
			
			local sx = curspringcoords[1]
			local sy = curspringcoords[2]
			local ts = 0
			local pt = 0
			
			for pl in players.iterate
				if (p.realmo and p.realmo.valid) and not p.spectator
					local px = (((pl.mo.x/FRACUNIT/64)+1))
					local py = (((pl.mo.y/FRACUNIT/64)+1))
					if (px == sx) and (py == sy)
						ts = $ + 1
					end
					pt = $+1
				end
			end
			
			if ts == pt
				local fp
				for pl in players.iterate
					if pl ~= nil
						fp = pl
						break
					end
				end
				if p == fp
					if maps[p.rpg.floor+1] == nil
						G_ExitLevel(1100, 0)
						return true
					end
					p.rpg.floor = $ + 1
					for pl in players.iterate
						pl.bouncing = true
						pl.rpg.floor = fp.rpg.floor
						pl.rpg.map = maps[fp.rpg.floor]
						pl.pflags = $|PF_GODMODE
					end
					for mob in mobjs.iterate()
						if mob.type ~= MT_PLAYER
							P_RemoveMobj(mob)
						end
					end
					P_BuildMap(p)
					for pl in players.iterate
						P_TeleportMove(pl.mo, fp.mo.x, fp.mo.y, fp.mo.z)
						pl.pflags = $ & ~PF_GODMODE
						pl.bouncing = false
						if dungeon_musicchange.value > 0
							if p.rpg.floor < #maps - ((#maps/2) + (#maps/4))
								S_ChangeMusic("lowflr", true, pl)
							elseif p.rpg.floor < #maps - (#maps/4)
								S_ChangeMusic("midflr", true, pl)
							elseif p.rpg.floor < #maps
								S_ChangeMusic("hiflr", true, pl)
							else
								S_ChangeMusic("rpboss", true, pl)
							end
						end
					end
					
				end
			end
			
			// Old code here, you can tell why it's jank.
			
			/*
			
			// Spring hitting code, could use some work.
			
			if (p.mo.eflags & MFE_SPRUNG)
			
				if maps[p.rpg.floor+1] == nil
					G_ExitLevel(1100, 0)
					return true
				end
				
				for mob in mobjs.iterate()
					if mob.type ~= MT_PLAYER
						P_RemoveMobj(mob)
					end
				end
				
				p.rpg.floor = $ + 1
				P_RPGVars(p)
				P_BuildMap(p)
			
			end
			
			*/
			
			// Level up.
			
			if p.rpg.xp >= p.rpg.nextxp
				P_LevelUp(p)
			end
			
			// Input checker(s)
			
			if not (p.cmd.buttons & BT_CUSTOM3)
				p.c3ready = true
				p.c3tap = false
			elseif p.c3ready
				p.c3ready = false
				p.c3tap = true
			else
				p.c3tap = false
			end
			
			// Sword & Shield setter.
			
			p.rpg.sword = swords[p.rpg.swordnum]
			p.rpg.shield = shields[p.rpg.shieldnum]
			
			// Challenge related.
			
			if p.rpg.challenge
				if p.rpg.challenge == challenges[2][1]
					if p.rpg.kills > challenges[2][3]
						P_DamageMobj(p.mo, nil, nil, 100, DMG_INSTAKILL)
					end
				elseif p.rpg.challenge == challenges[3][1]
					if p.rpg.level > challenges[3][3]
						P_DamageMobj(p.mo, nil, nil, 100, DMG_INSTAKILL)
					end
				elseif p.rpg.challenge == challenges[4][1]
					p.rpg.swordnum = 1
					p.rpg.shieldnum = 1
				elseif p.rpg.challenge == challenges[5][1]
					if (p.rpg.kills < challenges[5][3]) and (p.rpg.floor == #maps)
						P_DamageMobj(p.mo, nil, nil, 100, DMG_INSTAKILL)
					end
				end
			end
			
			// Inventory related.
			
			if p.c3tap
				if p.rpg.hp < p.rpg.maxhp
					local inv = p.rpg.inventory
					if #inv > 0
						local item = inv[#inv]
						p.rpg.hp = $+item[2]
						table.remove(inv, #inv)
					else
						S_StartSound(p.mo, sfx_lose, p)
					end
				else
					S_StartSound(p.mo, sfx_lose, p)
				end
			end
			
			// Stat caps.
			
			if p.rpg.maxhp > 999999*2
				p.rpg.maxhp = 999999*2
			end
			
			if p.rpg.basedef > 99999*2
				p.rpg.basedef = 99999*2
			end
			
			if p.rpg.baseatk > 99999*2
				p.rpg.baseatk = 99999*2
			end
			
			if p.rpg.level > 999
				p.rpg.level = 999
			end
			
			if p.rpg.hp > p.rpg.maxhp
				p.rpg.hp = p.rpg.maxhp
			end
			
			// Player Death.
			
			if p.rpg.hp < 1
				p.rpg.hp = 0
				P_DamageMobj(p.mo, nil, nil, 100, DMG_INSTAKILL)
				S_ChangeMusic("DEATH", false, p)
			end
			
		end
	end
end)

// NO BOUNCING AAAAA IT HURTS MY EARS, ok ok lol.

addHook("MobjCollide", function(mo, toucher)
	if toucher and toucher.valid
		if toucher.player
			if not toucher.player.bouncing
				return false // It never happened.
			else
				return true
			end
		end
	end
end, MT_YELLOWSPRING)

// Enemy Spawn stuff.

addHook("MobjSpawn", function(mo)
	mo.scale = FRACUNIT
	mo.colorized = true
	if (mo.flags & MF_ENEMY)
		for p in players.iterate
			if p.rpg
				mo.scale = FRACUNIT/4
				mo.color = SKINCOLOR_RED
				mo.customhp = hptable[mo.type]*p.rpg.floor
				mo.health = 2
			end
		end
	elseif (mo.flags & MF_BOSS)
		for p in players.iterate
			if p.rpg
				mo.scale = FRACUNIT/3
				mo.color = SKINCOLOR_RED
				mo.customhp = hptable[mo.type]*p.rpg.floor
				mo.health = 4
				mo.spawnhp = mo.customhp
				mo.pinch = false
			end
		end
	elseif (mo.flags & MF_MONITOR)
		mo.scale = FRACUNIT/2
	end
end)

// Self-explanitory.

addHook("MobjThinker", function(mo)
	if (mo.flags & MF_BOSS)
		if mo.customhp > (mo.spawnhp / 2)
			mo.health = 4
		else
			mo.health = 2
			mo.pinch = true
		end
	end
end)

// Enemy Damage.

addHook("MobjDamage", function(mo, inf, src, dmg, dmgtype)
	if mo and mo.valid
		if src and src.valid
			if mo.player
				if mo.player.rpg
					if (src.flags & MF_ENEMY) or (src.flags & MF_BOSS)
						local dmg = (dmgtable[src.type] * mo.player.rpg.floor) - (mo.player.rpg.basedef + mo.player.rpg.shield[2])
						if dmg > 0
							if mo.pinch
								mo.player.rpg.hp = $ - (dmg*2)
							else
								mo.player.rpg.hp = $ - dmg
							end
						else
							S_StartSound(mo, sfx_s3k81, mo.player)
							P_InstaThrust(src, mo.angle, 5*FRACUNIT)
							return false
						end
					else
						if dmgtype == DMG_INSTAKILL
							return true
						else
							mo.player.rpg.hp = $ - (P_RandomRange(2, 5) * mo.player.rpg.floor)
						end
					end
				end
			end
		end
	end
end, MT_PLAYER)

// No Score Mobjs.

addHook("MobjThinker", function(mo)
	P_KillMobj(mo, nil, nil)
end, MT_SCORE)

// Enemy Death.

addHook("MobjDeath", function(mo, inf, src)
	if (mo.flags & MF_ENEMY)
		if src and src.valid
			if src.type == MT_PLAYER
				if src.player
					if src.player.rpg
						src.player.rpg.xp = $ + (P_RandomRange(10,15) * src.player.rpg.floor)
						src.player.rpg.kills = $ + 1
					end
				end
			end
		end
	elseif (mo.flags & MF_BOSS)
		if src and src.valid
			if src.player
				if src.player.rpg
					src.player.rpg.xp = $ + (P_RandomRange(200,250) * src.player.rpg.floor)
					P_SpawnMobj(mo.x, mo.y, mo.z, MT_YELLOWSPRING)
				end
			end
		end
	end
end)

// Player Damage.

addHook("MobjDamage", function(mo, inf, src)
	if (mo.flags & MF_ENEMY) or (mo.flags & MF_BOSS)
		if (src and src.valid) and (mo and mo.valid)
			if src.player
				if src.player.rpg
					mo.customhp = $ - (src.player.rpg.sword[2] + src.player.rpg.baseatk)
					
					if mo.customhp < 1
						mo.customhp = 0
						if mo and mo.valid
							P_KillMobj(mo, nil, src)
						end
					end
					
					if mo and mo.valid
						if (mo.flags & MF_ENEMY)
							return true
						end
					end
					
				end
			end
		end
	end
end)

// Sword Box.

addHook("MobjDeath", function(mo, inf, src)
	if src and src.valid
		if src.type == MT_PLAYER
			if src.player and src.player.valid
				if src.player.rpg
					if src.player.rpg.swordnum < #swords
						src.player.rpg.swordnum = $ + 1
					end
					
					local block = blockToSector(mo.x, mo.y)
					src.player.rpg.map[block.tag] = 0		
				end
			end
		end
	end
end, MT_ARMAGEDDON_BOX)

// Shield Box.

addHook("MobjDeath", function(mo, inf, src)
	if src and src.valid
		if src.type == MT_PLAYER
			if src.player and src.player.valid
				if src.player.rpg
					if src.player.rpg.shieldnum < #shields
						src.player.rpg.shieldnum = $ + 1
					end
					
					local block = blockToSector(mo.x, mo.y)
					src.player.rpg.map[block.tag] = 0	
				end
			end
		end
	end
end, MT_FORCE_BOX)

// Potion Box.

addHook("MobjDeath", function(mo, inf, src)
	if src and src.valid
		if src.type == MT_PLAYER
			if src.player and src.player.valid
				if src.player.rpg
					local inv = src.player.rpg.inventory
					if #inv < 5
						if src.player.rpg.floor < #maps - ((#maps/2) + (#maps/4))
							table.insert(inv, potions[P_RandomRange(1,2)])
						elseif src.player.rpg.floor < #maps - (#maps/4)
							table.insert(inv, potions[P_RandomRange(1,3)])
						else
							table.insert(inv, potions[P_RandomRange(1,4)])
						end
					end
							
					local block = blockToSector(mo.x, mo.y)
					src.player.rpg.map[block.tag] = 0		
				end
			end
		end
	end
end, MT_RING_BOX)

// Eggman, that's what he is.

addHook("MobjDeath", function(mo, inf, src)
	if src and src.valid
		if src.type == MT_PLAYER
			if src.player and src.player.valid
				if (src.skin == "speccy") or (src.skin == "sspeccy")
					if src.player.spec
						if src.player.rpg
							src.player.rpg.hp = $ - P_RandomRange(5,15)*p.rpg.floor
						end
					end
				end
			end
		end
	end
end, MT_EGGMAN_BOX)

// Hud.

hud.add(function(v, p)
		if mapheaderinfo[gamemap].lvlttl == "Dungeon Crawler"
			if p.rpg
			
				// Disable Vanilla HUD elements.
				
				hud.disable("lives")
				hud.disable("score")
				hud.disable("rings")
				hud.disable("time")
				
				// HP & EXP Bar
				
				if not p.inf
					v.drawString(4, 4, "\x85HP: \x80"+p.rpg.hp+"/"+p.rpg.maxhp+" \x83Lives: \x80"+p.lives)
				else
					v.drawString(4, 4, "\x85HP: \x80"+p.rpg.hp+"/"+p.rpg.maxhp)
				end
				v.drawFill(4, 14, ((90*p.rpg.maxhp/p.rpg.maxhp*90)/90)+4, 8, 44)
				v.drawFill(6, 16, (p.rpg.hp*90/p.rpg.maxhp*90)/90, 4, 33)
				if p.rpg.challenge == challenges[3][1]
					if p.rpg.level < (challenges[3][3]/2) - (challenges[3][3]/8)
						v.drawString(4, 23, "LV. \x83"+p.rpg.level+"\x80     XP: "+p.rpg.xp+"/"+p.rpg.nextxp, nil, "small")
					elseif p.rpg.level < challenges[3][3] - (challenges[3][3]/4)
						v.drawString(4, 23, "LV. \x82"+p.rpg.level+"\x80     XP: "+p.rpg.xp+"/"+p.rpg.nextxp, nil, "small")
					elseif p.rpg.level < challenges[3][3]
						v.drawString(4, 23, "LV. \x85"+p.rpg.level+"\x80     XP: "+p.rpg.xp+"/"+p.rpg.nextxp, nil, "small")
					else
						v.drawString(4, 23, "LV. \x86"+p.rpg.level+"\x80     XP: "+p.rpg.xp+"/"+p.rpg.nextxp, nil, "small")
					end
				else
					v.drawString(4, 23, "LV. "+p.rpg.level+"     XP: "+p.rpg.xp+"/"+p.rpg.nextxp, nil, "small")
				end
				v.drawFill(4, 27, (100*p.rpg.nextxp/p.rpg.nextxp*100)/100, 2, 8)
				v.drawFill(4, 27, (p.rpg.xp*100/p.rpg.nextxp*100)/100, 2, 129)
				
				// Boss Bar
				
				for mo in mobjs.iterate()
					if (mo.flags & MF_BOSS)
						v.drawString(160-49, 180-4, "\x85 BOSS:")
						v.drawFill(160-45, 200-16, ((90*mo.spawnhp/mo.spawnhp*90)/90)+4, 8, 44)
						v.drawFill(160-43, 200-14, (mo.customhp*90/mo.spawnhp*90)/90, 4, 33)
					end
				end
				
				// Minimap & Floor Counter
				
				v.drawFill((320-64),16,4*12,4*12,0)
				v.drawString(320-40, 4, "Floor "+p.rpg.floor+"/"+#maps, nil, "thin-center")
				for r=1,10
					for c=1,10
						local block = blockToSector(c, r)
						if (p.rpg.map[block.tag] == 0) or (p.rpg.map[block.tag] == 9)
							v.drawFill((320-64)+c*4,(5*12)-r*4,4,4,31)
						elseif p.rpg.map[block.tag] == 1
							v.drawFill((320-64)+c*4,(5*12)-r*4,4,4,0)
						elseif p.rpg.map[block.tag] == 2
							v.drawFill((320-64)+c*4,(5*12)-r*4,4,4,54)
						elseif p.rpg.map[block.tag] == 3
							v.drawFill((320-64)+c*4,(5*12)-r*4,4,4,153)
						elseif p.rpg.map[block.tag] == 4
							v.drawFill((320-64)+c*4,(5*12)-r*4,4,4,115)
						elseif p.rpg.map[block.tag] == 5
							v.drawFill((320-64)+c*4,(5*12)-r*4,4,4,35)
						elseif p.rpg.map[block.tag] == 8
							v.drawFill((320-64)+c*4,(5*12)-r*4,4,4,182)
						end
						v.drawFill((320-64)+(((p.mo.x/FRACUNIT/64)+1)*4),(5*12)-(((p.mo.y/FRACUNIT/64)+1)*4),4,4,73)
					end
				end
	
				// Enemies Killed.
				
				if p.rpg.challenge == challenges[2][1]
					if p.rpg.kills < (challenges[2][3]/2) - (challenges[2][3]/4)
						v.drawString(1, 200-72, "Enemies Killed: \x83"+p.rpg.kills, nil, "thin")
					elseif p.rpg.kills < (challenges[2][3]/2) + (challenges[2][3]/4)
						v.drawString(1, 200-72, "Enemies Killed: \x82"+p.rpg.kills, nil, "thin")
					elseif p.rpg.kills < challenges[2][3] 
						v.drawString(1, 200-72, "Enemies Killed: \x85"+p.rpg.kills, nil, "thin")
					else
						v.drawString(1, 200-72, "Enemies Killed: \x86"+p.rpg.kills, nil, "thin")
					end
				elseif p.rpg.challenge == challenges[5][1]
					if p.rpg.kills < (challenges[5][3]/4) - (challenges[5][3]/8)
						v.drawString(1, 200-72, "Enemies Killed: \x86"+p.rpg.kills, nil, "thin")
					elseif p.rpg.kills < (challenges[5][3]/2) - (challenges[5][3]/8)
						v.drawString(1, 200-72, "Enemies Killed: \x85"+p.rpg.kills, nil, "thin")
					elseif p.rpg.kills < challenges[5][3]
						v.drawString(1, 200-72, "Enemies Killed: \x82"+p.rpg.kills, nil, "thin")
					elseif p.rpg.kills >= challenges[5][3]
						v.drawString(1, 200-72, "Enemies Killed: \x83"+p.rpg.kills, nil, "thin")
					end
				else
					v.drawString(1, 200-72, "Enemies Killed: "+p.rpg.kills, nil, "thin")
				end
				
				// Player Stats.
				
				v.drawFill(320-86, 200-64, 86, 64, 0)
				v.drawFill(320-84, 200-62, 84, 62, 31)
				
				v.drawString(320-83, 200-60, p.name, nil, "thin")
				v.drawString(320-83, 200-40, "ATK: "+tostring(tonumber(p.rpg.baseatk)+tonumber(p.rpg.sword[2]))+" ("+p.rpg.baseatk+")", nil, "thin")
				v.drawString(320-83, 200-30, "DEF: "+tostring(tonumber(p.rpg.basedef)+tonumber(p.rpg.shield[2]/3))+" ("+p.rpg.basedef+")", nil, "thin")
				v.drawString(320-83, 200-20, "WPN: "+p.rpg.sword[1], nil, "thin")
				v.drawString(320-83, 200-10, "SHD: "+p.rpg.shield[1], nil, "thin")
				
				// Player Inventory.
				
				v.drawFill(0, 200-64, 86, 64, 0)
				v.drawFill(0, 200-62, 84, 62, 31)
				
				for i=1,5
					if p.rpg.inventory[i] ~= nil
						local item = p.rpg.inventory[i]
						v.drawString(1, (152-24)+(i*12), item[1], nil, "thin") 
					end
				end
			end
		end
end)