--╔══════════════════════════════════╗═══════════════════════════════════════════════════════════════════════════════════════════#
--║ Golden Shine's Optimization Menu ║ To help mappers focus on improving stages without having to cater to performance as much!
--╚══════════════════════════════════╝═══════════════════════════════════════════════════════════════════════════════════════════#

-- Block the next load if there's one added.
if (rawget(_G, "MT_GSOPTIMENU")) print("\x85"+"Blocked loading\x80 of additional Opti-Menu.") return end

--Here's a function that checks if the freeslot entry exist BEFORE adding it. 
local function SingleFreeSlot(...)
	for _,slot in ipairs({...})
		if not rawget(_G, slot) freeslot(slot) end
	end
end
SingleFreeSlot("SPR_G7OM", "MT_GSOPTIMENU")

mobjinfo[MT_GSOPTIMENU] = {
//$Name GSOptiMenu
//$Sprite G7OMB0
//$Category Performance
//$Arg0 [1-157]Custom Color
//$Arg1 [1]No Shadows Option
//$Arg2 [1]No Scenery Option
//$Arg3 [1]No BlockMap Option
//$Arg4 [1]No Collision Option
//$Arg5 [1]No Objects Option
doomednum = 4580,
spawnstate = S_THOK,
radius = 121*FRACUNIT,
height = 512*FRACUNIT,
flags = MF_SPECIAL,
}

local AOPTIONS,OPTIONS = nil --Option tables.
local RTABLE = {} --Render Table
local GSLASTMAP = gamemap
local RETAIN,OM_RETAIN,OM_AUTOSPAWN = false

--Set it to default?
local function SetOptiDefaults(p, var1)
	local COLOR = SKINCOLOR_BLUE
	if var1=="nocolor" and OPTIONS and OPTIONS[0]
		COLOR = OPTIONS[0]
	end
	
	RTABLE = {} --Reset render table too.
	
	--Personal options that only affect their side.
	OPTIONS = {
	[0] = COLOR, --MENU COLOR
	[1] = true, --SKYBOX TOGGLE
	[2] = true, --WEATHER TOGGLE
	[3] = true --SHADOWS TOGGLE
	}
	if (GS_RAILS!=nil) --GRINDRAIL OPTIMIZATION
		OPTIONS[8] = true
	else
		OPTIONS[8] = false
	end

	--Admin options that affect everyone in some way.
	AOPTIONS = { 
	[4] = true, --SCENERY OPTIMIZATION
	[5] = true, --BLOCKMAP OPTIMIZATION
	[6] = true, --COLLISSION REMOVAL
	[7] = true --INTENSIVE OBJECT OPTIMIZATION
	}
end if OPTIONS==nil SetOptiDefaults(nil) end

--Mappers can turn off a few options if they don't want their stuff messed with.
local function MapThingAdjust(s) if (udmf!=true) return end --UDMF only.
	local spn = s.spawnpoint --Add spawnpoint parameters if available!
	if spn and spn.valid and spn.options != nil and spn.args != nil
		local ARGS = spn.args		
		if (ARGS[0]) and type(ARGS[0])=="number"
		and not (ARGS[0] > #skincolors-1) --Invalid color!
			if OPTIONS!=nil
				OPTIONS[0] = ARGS[0] --Custom menu color!
			end
			s.color = ARGS[0]
		end	
		if ARGS[1]==1 and OPTIONS!=nil
			OPTIONS[3] = false
		end
		
		if AOPTIONS!=nil
			if ARGS[2]==1
				AOPTIONS[4] = false
			end
			if ARGS[3]==1
				AOPTIONS[5] = false
			end
			if ARGS[4]==1
				AOPTIONS[6] = false
			end
			if ARGS[5]==1
				AOPTIONS[7] = false
			end	
		end
	end
end

--Determine first available option in the menu.
local function FIRSTOPTION(_, ADMIN)
	local NUM = 0
	for i = 1,3
		NUM = $+1
		if OPTIONS[i]==true return NUM end
	end	
	if ADMIN != false
		for i = 4,7
			NUM = $+1
			if AOPTIONS[i]==true return NUM end
		end
	end
	if OPTIONS[8]==true and (GS_RAILS!=nil)
		return NUM --
	end return 1 --
end

--Calculate total menu options available.
local function TOTALOPTIONS(_, ADMIN)
	local NUM = 0
	for i = 1,3
		if OPTIONS[i]==true NUM = $+1 end
	end
	if ADMIN != false
		for i = 4,7
			if AOPTIONS[i]==true NUM = $+1 end
		end
	end
	if OPTIONS[8]==true and (GS_RAILS!=nil)
		NUM = $+1
	end
	return NUM --
end


--Setting the object's flags as appropriate
local function SetOptiMenu(s, t)
	if type(s)=="string" --You want to look for just about ANY menu out there, huh?
		s = string.upper(s)
		if s=="SEARCH"
			for m in mobjs.iterate(mobjs)
				if m and m.valid and (m.type == MT_GSOPTIMENU)
					s = m break --
				end
			end
		end
	end
	if not (type(s)=="userdata" and s.valid and (s.type==MT_GSOPTIMENU))
		print("\x85"+"Could not find Opti-Menu to set!") return --
	end
	
	if (twodlevel)
		s.angle = ANGLE_90
	end
	s.flags2 = $ & ~MF2_DONTDRAW --Just making sure!
	
	if type(t)=="userdata" and t.valid --Adjust to a target?
		if (twodlevel) or (t.flags2 & MF2_TWOD)
			s.angle = ANGLE_90 --Face 2D angle
		else
			s.angle = t.angle+ANGLE_90 --Face the player
			P_TryMove(s, t.x+t.momx+FixedMul(64*t.scale, cos(t.angle)), t.y+t.momy+FixedMul(64*t.scale, sin(t.angle)), true) 
		end	
		if (t.eflags & MFE_VERTICALFLIP)
			s.eflags = $|MFE_VERTICALFLIP
		else
			s.eflags = $ & ~MFE_VERTICALFLIP
		end
	end
end

--Render text options?
local function Render_OM_Options(s, MENU, TOTAL, SELECT) 
	local p = displayplayer
	if not (p and p.valid and p==consoleplayer and s and s.valid)
	or (splitscreen) and (p==players[1]) 
	or (s.OM_scaling)
		return --
	end
	local ADMIN = false
	if (netgame!=true) or MENU and MENU.ADMIN or IsPlayerAdmin(p) or p==server or p.specialsmscheatspermission
		ADMIN = true
	end
	if TOTAL==nil
		TOTAL = TOTALOPTIONS(_, ADMIN)
	end
	if SELECT==nil
		if MENU and MENU.floor
			SELECT = MENU.floor
		else
			SELECT = 0
		end
	end
	
	--1/C: SKYBOX
	--2/D: WEATHER
	--3/E: SHADOWS
	--4/F: SCENERY
	--5/G: BLOCKMAP
	--6/H: COLLISION
	--7/I: OBJECTS
	--8/J: RAILS
	
	local YOFFSET = 316*s.scale -- Always start at the top!
	local MIN = (294/max(TOTAL or 0, 1))*s.scale --Subtract this much each time.
	for i = 1,8
		local TEXT = nil
		if ((i < 4) or (i==8)) and (OPTIONS[i])
		or (ADMIN!=false) and ((i>3) and (i<8)) and (AOPTIONS[i])
		
			if i==8 and (GS_RAILS==nil) continue end
			
			if RTABLE[i] and RTABLE[i].valid
				TEXT = RTABLE[i]
			else
				TEXT = P_SpawnMobj(s.x, s.y, s.z+YOFFSET, MT_THOK)
				TEXT.scale = s.scale
				TEXT.flags = MF_SCENERY|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY
				TEXT.flags2 = MF2_DONTRESPAWN
				TEXT.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS|RF_PAPERSPRITE
				TEXT.dispoffset = 99
				TEXT.tracer,TEXT.target = s, p.mo
				RTABLE[i] = TEXT
			end
			YOFFSET = $-MIN
		end
		if TEXT and TEXT.valid
			local TSCALE = (SELECT==i) and 79000 or 1<<16 --Scale of text.
			local FRAME = B+i
			local TRANS = 0
			local TCOLOR = nil
			
			if type(p.didOM)=="table"
				if i==2
					if (p.didOM[i]!=nil)
						TCOLOR = SKINCOLOR_MIDNIGHT
						TRANS = FF_TRANS20
					end
				elseif i==4
					if p.didOM[i]==1
						TCOLOR = SKINCOLOR_MIDNIGHT
						TRANS = FF_TRANS20
					elseif (p.didOM[i])
						TCOLOR = SKINCOLOR_BLACK
						TRANS = FF_TRANS60
					end	
				else
					if (p.didOM[i])
						TRANS = FF_TRANS60
						TCOLOR = SKINCOLOR_BLACK
					end
				end
			end
			TEXT.renderflags = ((TCOLOR==SKINCOLOR_BLACK) and RF_FULLDARK or RF_FULLBRIGHT)|RF_NOCOLORMAPS|RF_PAPERSPRITE
			TEXT.eflags = s.eflags
			TEXT.sprite = SPR_G7OM
			TEXT.frame = ( min(J, max(C, FRAME)) )|FF_PAPERSPRITE|TRANS
			TEXT.tics = 3 --Constantly respawn them so they can adjust on the spot.
			TEXT.angle = s.angle
			TEXT.spritexscale,TEXT.spriteyscale = TSCALE,TSCALE
			if TCOLOR
				TEXT.color,TEXT.colorized = TCOLOR,true
				if SELECT==i and (TRANS!=FF_TRANS20)
					TEXT.color = SKINCOLOR_GOLD
				end
			elseif SELECT==i
				TEXT.color,TEXT.colorized = SKINCOLOR_SANDY,true
			end
			if TCOLOR != SKINCOLOR_BLACK --	and not (p.didOM and p.didOM[3]==true) --Because funny?
				local ghs = TEXT.Shade
				if not (ghs and ghs.valid)
					TEXT.Shade = P_SpawnMobjFromMobj(TEXT, 0,0,0, MT_THOK)
					ghs = TEXT.Shade
					ghs.flags = TEXT.flags
					ghs.flags2 = TEXT.flags2
					ghs.dispoffset = 49				
					ghs.renderflags = RF_FULLDARK|RF_NOCOLORMAPS|RF_PAPERSPRITE
					ghs.color,ghs.colorized = SKINCOLOR_BLACK, true
					ghs.spritexoffset,ghs.spriteyoffset = TEXT.spritexoffset-3<<16, TEXT.spriteyoffset-4<<16
					ghs.tracer,ghs.target = s,TEXT 
				end
				ghs.sprite = TEXT.sprite
				ghs.frame = (TEXT.frame & ~(FF_TRANSMASK))|FF_TRANS10
				ghs.tics = 3
				ghs.angle = TEXT.angle
				ghs.spritexscale,ghs.spriteyscale = TSCALE,TSCALE
			end
		end
	end
end

--Spawn with some default values.
addHook("MobjSpawn", function(s)
	s.tics = -1
	s.sprite = SPR_G7OM
	s.frame = A|FF_TRANS20|FF_PAPERSPRITE
	s.renderflags = RF_SEMIBRIGHT|RF_NOCOLORMAPS|RF_PAPERSPRITE
	s.color = OPTIONS[0] or SKINCOLOR_BLUE
	s.shadowscale = 28850
	s.blendmode = AST_ADD
	s.dispoffset = -44
	MapThingAdjust(s)
	
	if (twodlevel) or s.flags2 and (s.flags2 & MF2_TWOD)
		s.angle = ANGLE_90 --Always face the camera in 2D.
	end
	if (GS_RAILS==nil) OPTIONS[8] = false end --No point having this if Grind Rails don't exist.
	
	AOPTIONS[99] = s --For easy finding.
	s.Ptable = {}
	s.OM_active = 0
	s.OM_scaling = 36
end, MT_GSOPTIMENU)

--What the options do when activated. Does not require a mo.
local function Activate_Option(p, NUM, EXTRA) if not (NUM and p) return end --
	local GOT,ADMIN = 0,false
	if (IsPlayerAdmin(p) or p==server or p.specialsmscheatspermission)
		ADMIN = true
	end
	
	if NUM==1 --Skybox!
		if (p==consoleplayer and p==displayplayer)
			P_SetSkyboxMobj(nil)	
			if (EXTRA!="silent") and (p.didOM[1]!=true)
				S_StartSoundAtVolume(nil, sfx_cannon, 160, p)
				CONS_Printf(p, "\x88"+"Removed the skybox.")
			end
		end
		p.didOM[1] = true
	elseif NUM==2 --Remove weather.
		if (p==consoleplayer and p==displayplayer)
			if p.didOM[2]!=nil
				P_SwitchWeather(p.didOM[2])
				if (EXTRA!="silent")
					CONS_Printf(p, "\x8a"+"Re-enabled the weather.")
				end
				p.didOM[2] = nil
			else
				p.didOM[2] = globalweather
				P_SwitchWeather(PRECIP_NONE)
				if (EXTRA!="silent")
					CONS_Printf(p, "\x8b"+"Disabled the weather.")
				end
			end
			if (EXTRA!="silent")
				S_StartSoundAtVolume(nil, sfx_ghit, 160, p)
			end
		end
	elseif NUM==3 --Remove shadows.
		if p.didOM[3] != true
			if (p==consoleplayer and p==displayplayer)
				for m in mobjs.iterate(mobjs) 
					if m and m.valid and m.shadowscale and not m.player
						m.shadowscale = 0 GOT = $+1
					end
				end
				if GOT and (EXTRA!="silent")
					S_StartSoundAtVolume(nil, sfx_cybdth, 160, p)
					CONS_Printf(p, "\x8e"+"Removed "+GOT+" total shadows.")
				end
			end
			p.didOM[3] = true
		end
	elseif NUM==4 --Adjust scenery.
		if ADMIN==false return end
		if p.didOM[4] != 2
			local GOT2 = 0
			for m in mobjs.iterate(mobjs) 
				if m and m.valid and (m.flags & MF_SCENERY)
				and not (m.flags & (MF_SPECIAL|MF_GRENADEBOUNCE|MF_BOUNCE|MF_SOLID|MF_ENEMY|MF_SHOOTABLE|MF_STICKY))
				and not (m.flags & (MF_MISSILE|MF_SPRING|MF_FIRE|MF_PAIN|MF_MONITOR|MF_AMBIENT|MF_PUSHABLE|MF_NOSECTOR))
				and not (m.type <= MT_METALJETFUME)
				and not ((m.type >= MT_TELEPORTMAN) and (m.type <= MT_SKYBOX))
				and (m.sprite > SPR_PLAY) and not m.tracer and not m.target and not m.player
					if m.StupidScenery == true
						P_RemoveMobj(m)
						GOT2 = $+1
					else
						local SPAWNSTATE = states[m.info.spawnstate]
						if SPAWNSTATE 
						and (SPAWNSTATE.nextstate==SPAWNSTATE or (SPAWNSTATE.tics < 0) or (SPAWNSTATE.tics >= 1<<15))
						and not (m.flags & MF_BOXICON)
							m.flags = MF_SCENERY|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOBLOCKMAP
							m.shadowscale = 0
						else
							m.flags = $|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOBLOCKMAP
						end
						m.height,m.radius = 1,1
						GOT = $+1
						m.StupidScenery = true
					end
						
				end
			end
			if (EXTRA!="silent")
				if (GOT2!=0)
					if p==displayplayer
						P_StartQuake(60<<16, 8)
						S_StartSound(nil, sfx_crumbl, p)
					end
					print("\x85"+"Destroyed \x83"+GOT2+"\x85 scenery objects altogether!")
				elseif (GOT!=0)
					if p==displayplayer
						S_StartSound(nil, sfx_dmpain)
					end
					print("\x88"+"Dumbed down scenery thinkers on \x83"+GOT+"\x88 objects")
				end	
			end
			p.didOM[4] = p.didOM[4] and $+1 or 1
		end
	elseif NUM==5 --Mess with blockmap stuff. Can be risky.
		if ADMIN==false return end
		if p.didOM[5]!=true
			for m in mobjs.iterate(mobjs) 
				if m and m.valid and not (m.flags & MF_NOBLOCKMAP) and not m.player
				and not (m.flags & (MF_SPECIAL|MF_GRENADEBOUNCE|MF_BOUNCE|MF_ENEMY|MF_SHOOTABLE|MF_MONITOR|MF_AMBIENT) )
				and not (m.flags & (MF_PUSHABLE|MF_MISSILE|MF_SPRING|MF_FIRE|MF_PAIN|MF_BOXICON|MF_NIGHTSITEM))
				and not (m.type <= MT_METALJETFUME) 
				and not ((m.type >= MT_TELEPORTMAN) and (m.type <= MT_SKYBOX))		
				and (m.sprite>SPR_PLAY)	
					m.flags = $|MF_NOBLOCKMAP
					GOT = $+1
				end
			end
			if GOT!=0 and (EXTRA!="silent")
				S_StartSoundAtVolume(nil, sfx_gloop, 190)
				print("\x81"+"Removed blockmap listing from \x83"+GOT+"\x81 objects")
			end
			p.didOM[5]=true
		end
	elseif NUM==6 --Remove object collission if it's not needed.
		if ADMIN==false return end
		if p.didOM[6]!=true
			for m in mobjs.iterate(mobjs) 
				if m and m.valid
					if (m.type == MT_FLAME)
						m.flags = $|MF_NOGRAVITY
					elseif (m.flags & MF_SOLID) and not m.player and not (m.type<=MT_PLAYER) and not (m.type==MT_SKYBOX)
						if ((m.flags & MF_SCENERY) or m.flags==MF_SOLID)
						and not (m.flags & (MF_SPECIAL|MF_SHOOTABLE|MF_ENEMY|MF_PAIN|MF_FIRE|MF_MONITOR|MF_MISSILE))
						and not (m.type <= MT_METALJETFUME) 
						and not ((m.type >= MT_TELEPORTMAN) and (m.type <= MT_SKYBOX))	
						and (m.sprite>SPR_PLAY)	
							local SPAWNSTATE = m.info.spawnstate					
							m.flags = $|MF_NOCLIPTHING|MF_NOBLOCKMAP & ~MF_SOLID
							m.height,m.radius = 1,1
							GOT = $+1
						end
					end
				end
			end
			if GOT!=0 and (EXTRA!="silent")
				S_StartSoundAtVolume(nil, sfx_gravch, 130)
				print("\x86"+"Removed collission from \x83"+GOT+"\x86 objects")
			end
			p.didOM[6]=true
		end
	elseif NUM==7 --Try to do some optimization magic on objects with lots of intensive targeting.
		if ADMIN==false return end
		if p.didOM[7]!=true
			for m in mobjs.iterate(mobjs) 
				if m and m.valid and not m.player and m.flags
				and not (m.flags & (MF_SPECIAL|MF_MONITOR|MF_NOSECTOR|MF_AMBIENT|MF_NOTHINK|MF_PUSHABLE|MF_NOGRAVITY|MF_NOBLOCKMAP))
				and not (m.type <= MT_METALJETFUME)
				and not ((m.type >= MT_TELEPORTMAN) and (m.type <= MT_SKYBOX))	
				and not (m.flags2 & MF2_DONTDRAW)	
				and (m.sprite>SPR_PLAY)	
				and m.info.spawnstate
				and not m.tracer and not m.target
				and not m.gotoptimized
					local SPAWNSTATE = states[m.info.spawnstate]
					if (m.flags & (MF_ENEMY|MF_BOSS)) and (m.flags & MF_SHOOTABLE)
						if m.reactiontime and (m.reactiontime < 12)
							m.reactiontime = 12 GOT = $+1
							m.gotoptimized = true
						end
						if (SPAWNSTATE.tics < 30)
							if SPAWNSTATE.var1 == MT_PLAYER
							--	mobjinfo[SPAWNSTATE].tics = 30
								if m.state == m.info.spawnstate
									m.tics = 30 GOT = $+1
									m.gotoptimized = true
								end							
							elseif m.info.seestate
								local SEESTATE = m.info.seestate
								if SPAWNSTATE.var1 == SEESTATE or SPAWNSTATE.var2 == SEESTATE
								--	mobjinfo[SPAWNSTATE].tics = 30
									if m.state == m.info.spawnstate
										m.tics = 30 GOT = $+1
										m.gotoptimized = true
									end
								end
							end
						end
					end
					if (SPAWNSTATE.nextstate==SPAWNSTATE) or (SPAWNSTATE.tics < 0) or (SPAWNSTATE.tics >= 1<<15)
						if not (m.frame & (FF_ANIMATE|FF_RANDOMANIM|FF_GLOBALANIM))
						and not SPAWNSTATE.var1 and not SPAWNSTATE.var2
						--	SPAWNSTATE.tics = 30
							if m.state == m.info.spawnstate
								m.tics = -1
							end
							m.gotoptimized = true
							GOT = $+1
						end
					end
				end	
			end
			if GOT!=0 and (EXTRA!="silent")
				S_StartSoundAtVolume(nil, sfx_jshard, 250)
				print("\x8d"+"Optimized \x83"+GOT+"\x86 objects")
			end	
			p.didOM[7]=true
		end
	elseif NUM==8 --Optimize Grind Rails regardless of the mapper's intitial preferences
		if p.didOM[8]!=true and GS_RAILS!=nil
			if (p==consoleplayer and p==displayplayer)
				for m in mobjs.iterate(mobjs) 
					if m and m.valid and (m.type==MT_GSRAILSEG) and not m.player
					and m.GSrailsideL and m.GSrailsideL.valid and m.GSmainrail
						if (m.GSmainrail.GSrenderstyle != "notop")
							m.GSrailsideL.flags2 = $|MF2_DONTDRAW GOT = $+1
						elseif (m.GSmainrail.GSrenderstyle != "nosides")
							m.flags2 = $|MF2_DONTDRAW GOT = $+1
						end
					end
				end
				if GOT!=0 and (EXTRA!="silent")
					S_StartSoundAtVolume(nil, sfx_rail2, 250)
					print("\x84"+"Optimized \x83"+GOT+"\x84 rail pieces")
				end
			end
			p.didOM[8]=true
		end
	end
end

--Interacting with the menu and being stuck to it.
local function OPTIMENU(MENU, p, s, m)
	if not MENU.timer
	or MENU.banned
	or not (s.health)
	or P_PlayerInPain(p)
	or (p.panim >= PA_RUN)
	or (p.panim < PA_IDLE)
	or s.state==S_PLAY_DEAD
	or p.powers[pw_carry] and (p.powers[pw_carry]!=11777)
	or p.powers[pw_nocontrol]
	or (FixedHypot(m.x-s.x, m.y-s.y) > 3000*s.scale) --Way too far away!
	or p.yusonictable and (p.yusonictable.tauntmenu or p.yumenutable) --First, cancel conditions.
	or p.cc_menu or p.sms_intoship or p.sam_intoship --Not in other menus!
	or p.xsonictable and (p.xsonictable.FreeFlight or p.XSonicMenu)	
	or MENU.spin==1 and (p.cmd.buttons & BT_SPIN)
		if MENU.modifiedaim and p and p.valid
			p.aiming = 0
			MENU.modifiedaim = nil
		end
		if p.movefactor == 0
			p.movefactor = nil
		end
		MENU.SHIFT,MENU.TRANS,MENU.INIT = 0,0
		MENU.banned = 40
		MENU.timer = 0 
		if s and s.GS_mymenu
			s.GS_mymenu = nil
		end
		if p and p.powers[pw_carry] == 11777
			p.powers[pw_carry] = 0
		end
		return false--
	end
	
	--Some timers.
	MENU.timer = max($-1,0)
	MENU.INIT = $+1 
	local INIT = MENU.INIT
	if MENU.SHIFT
		MENU.SHIFT = $-1
	end
	if MENU.TRANS
		MENU.TRANS = $-1
	end
	
	if INIT==1 --Initial setup.
		MENU.lock = 8
		MENU.floor = FIRSTOPTION(p, MENU.ADMIN)
		MENU.updown = 0
		MENU.side = 0
		MENU.helddirect = 0
		MENU.jump,MENU.spin,MENU.custom1,MENU.custom2 = 99<<16,99<<16,99<<16,99<<16
		s.momx,s.momy = $/2,$/2
		S_StartSoundAtVolume(s, sfx_cdfm41, 140)
		S_StartSound(s, sfx_menu1, p)
		local XDIST,YDIST,ghs = FixedMul(44<<16, cos(m.angle)),  FixedMul(44<<16, sin(m.angle)), nil
		local MOMZ = (1<<16)*P_MobjFlip(m)
		for i = -3,3 if i==0 continue end --Activation effect.
			for d = 1,6
				ghs = P_SpawnMobjFromMobj(m, XDIST*i, YDIST*i, (d*33)*MOMZ, MT_THOK)
				ghs.state = S_NIGHTSPARKLE1
				ghs.angle = m.angle
				ghs.flags = MF_SCENERY|MF_NOCLIPHEIGHT|MF_NOCLIP|MF_NOCLIPTHING|MF_NOBLOCKMAP|MF_NOGRAVITY
				ghs.fuse = 16
				ghs.renderflags = $|RF_FULLBRIGHT|RF_NOCOLORMAPS|RF_PAPERSPRITE
				ghs.frame = ($ & ~FF_TRANSMASK)|FF_TRANS30|FF_FULLBRIGHT
				ghs.blendmode = AST_ADD
				ghs.scale = (s.scale*2)/max(3, d)
				ghs.scalespeed = (1<<16)/max(2,d)
				ghs.destscale = 99<<16
				ghs.color,ghs.colorized = SKINCOLOR_SUPERSILVER3-i+(d*2), true
				ghs.momx = (XDIST/14-(d/2))*i
				ghs.momy = (YDIST/14-(d/2))*i
				ghs.momz = MOMZ*((d*3)+abs(i))
			end
		end
	end
	
	--Player manipulation.
	p.pflags = $|PF_JUMPDOWN|PF_USEDOWN
	p.powers[pw_ignorelatch] = (1<<15)+2
	p.powers[pw_carry] = 11777
	if s.skin=="samus" or s.skin=="basesamus"
		p.sams_morphcd,p.sams_firecd,p.sams_normfcd = 5,5,5
	else
		if p.yusonictable
			local yu = p.yusonictable
			yu.jump,yu.spin,yu.custom3,yu.tossflag = 99<<16,99<<16,99<<16,99<<16
		end
		if p.xsonictable
			local xs = p.xsonictable
			xs.jump,xs.spin,xs.custom3,xs.custom1,xs.custom2,xs.tossflag = 99<<16,99<<16,99<<16,99<<16,99<<16,99<<16
		end
		if (p.pflags & PF_STARTDASH)
			p.pflags = $ & ~(PF_SPINNING|PF_STARTDASH|PF_JUMPED|PF_NOJUMPDAMAGE)
			s.state = S_PLAY_FALL
			P_SetObjectMomZ(s, -9, true)		
		end
	end
	
	--Face the menu.
	if (p.panim == PA_IDLE) or s.state == S_PLAY_EDGE
	or (p.panim == PA_WALK) and (p.speed < 16*s.scale)
		if not ((MENU.helddirect or 0) > 20)
			if s.state == S_PLAY_STND and (s.tics > 0)
				s.tics = max($,25)
			end
			if s.movefactor==nil
				s.movefactor = 0
			end
			s.momx,s.momy = 0,0
			p.powers[pw_noautobrake] = 2
			if S_OriginPlaying(s)
				S_StopSoundByID(s, skins[s.skin].soundsid[SKSJUMP])
				S_StopSoundByID(s, skins[s.skin].soundsid[SKSZOOM])
				S_StopSoundByID(s, skins[s.skin].soundsid[SKSSPNDSH])
			end
			
			local DESTX,DESTY = m.x+FixedMul(236*m.scale, cos(m.angle-ANGLE_90)), m.y+FixedMul(236*m.scale, sin(m.angle-ANGLE_90))
			local TOANG = R_PointToAngle2(s.x, s.y, DESTX, DESTY)
			local DIST = FixedHypot(s.x-DESTX, s.y-DESTY)
			local TWOD = ((s.flags2 & MF2_TWOD) or twodlevel) and true or false
			if TWOD!=true
				if (DIST > max(15*s.scale, 24*m.scale))
					local TOANG = R_PointToAngle2(s.x, s.y, DESTX, DESTY)
					local STEP = min(15*s.scale, max(36*m.scale, 10*s.scale))
					if P_TryMove(s, s.x+FixedMul(STEP, cos(TOANG)), s.y+FixedMul(STEP, sin(TOANG)), false)
						P_SpawnSkidDust(p, 32*s.scale)
					end
				elseif (DIST >= max(2<<16, 2*s.scale))
					P_TryMove(s, DESTX, DESTY, false) --Don't allow it to fall off!
				end
			end
			if (DIST <  min(2700<<16, 220*m.scale))
				MENU.timer = 36
				if TWOD!=true
					local TOANG = R_PointToAngle2(s.x, s.y, m.x, m.y)
					if (m.scale >= (s.scale*2/3))
						local AIMANG = ANG1*27
						if (s.eflags & MFE_VERTICALFLIP) or (m.eflags & MFE_VERTICALFLIP) 
							p.aiming = -AIMANG
						else
							p.aiming = AIMANG
						end
						MENU.modifiedaim = true
					else
						if (s.sprite2 & ~FF_SPR2SUPER)==SPR2_STND
							s.frame = ($ & ~FF_TRANSMASK)|FF_TRANS70
						end
						s.flags2 = $|MF2_SHADOW
					end
					s.angle = TOANG
					p.cmd.angleturn = TOANG>>16
					p.drawangle = TOANG
				end
			end		
		end
	elseif s.movefactor == 0
		s.movefactor = nil
	end	

	MENU.spin = (p.cmd.buttons & BT_SPIN) and $+1 or 0
	
	if MENU.lock
		MENU.lock = max($-1,0)
		MENU.updown,MENU.side = 0,0
	else
		local FORWARD = abs(p.cmd.forwardmove)
		local SIDE =  abs(p.cmd.sidemove)
		if (FORWARD > 25) or (SIDE > 25)
			MENU.helddirect = MENU.helddirect and $+1 or 1
		else
			MENU.helddirect = 0
		end	
		local SET = 30
		MENU.jump = (p.cmd.buttons & BT_JUMP) and $+1 or 0
		MENU.custom1 = (p.cmd.buttons & BT_CUSTOM1) and $+1 or 0
		MENU.custom2 = (p.cmd.buttons & BT_CUSTOM2) and $+1 or 0
		local SET = 30 --Amount to push the control stick for a reaction.
		if (FORWARD > SET)
			if (p.cmd.forwardmove < -SET)
				MENU.updown = max(1, $+1)
			else
				MENU.updown = min($-1,-1)
			end
			MENU.side = 0
		else
			MENU.updown = 0
			if (SIDE > SET)
				if (p.cmd.sidemove > SET)
					MENU.side = max(1, $+1)
				else
					MENU.side = min($-1,-1)
				end
			else
				MENU.side = 0
			end			
		end
	end
	local TOTALFLOORS = TOTALOPTIONS(_, ADMIN)
	local CURFLOOR = MENU.floor --The floor we're currently on.
	if abs(MENU.updown)==1
		for i = 1,9
			if (i>=9) MENU.floor = CURFLOOR break end --Abort...!
			
			MENU.floor = max(1, min(TOTALFLOORS, $+MENU.updown))
			
			if (MENU.floor < 4) or MENU.floor==8
				if (OPTIONS[MENU.floor])
					break --If the option is available, we're done! Otherwise, keep scrolling!
				end
			elseif MENU.ADMIN and (AOPTIONS[MENU.floor]) --If you're an admin AND the option is available, scroll away!
				break --
			end
		end
		MENU.updown = $*2 --Go to 2.
	end
	if CURFLOOR != MENU.floor --Did we change? Make a sound!
		S_StartSound(nil, sfx_menu1, p)
		MENU.SHIFT = 4
		if (MENU.floor > TOTALFLOORS) or (MENU.floor < 1)
			MENU.floor = FIRSTOPTION(p, MENU.ADMIN)
		end
	end
	m.OM_active = 45 --Keep it active.	
	
	if MENU.jump==1 and (MENU.spin!=1) and MENU.floor --Activate an option?
		if p.didOM == nil p.didOM = {} end
		Activate_Option(p, MENU.floor, MENU)
		MENU.jump,MENU.lock = 99<<16, 1
	end return true --
end

--A fairly simply MobjThinker that's really only active if players are currently using it.
addHook("MobjThinker", function(m) if not (m and m.valid) return end --
	if m.OM_notouch 
		m.OM_notouch = max($-1,0)
	end
	if m.OM_scaling
		m.OM_scaling = max($-1,0)
		if m.OM_scaling == 35
			RTABLE = {}
			MapThingAdjust(m)
			if AOPTIONS and AOPTIONS[99] == nil
				AOPTIONS[99] = m
			end
			S_StartSoundAtVolume(m, sfx_kc5a, (m.OM_notouch) and 64 or 120)
			m.spritexscale,m.spriteyscale = 65000,18500
			if m.CalledMe
				if m.CalledMe.valid
					m.angle = m.CalledMe.angle+ANGLE_270
				end
				if m.slant
					m.angle = $+m.slant
				end
				m.CalledMe,m.slant = nil
			elseif not (m.flags2&MF2_TWOD) and not (twodlevel)
				m.angle = $+ANGLE_90
			end
		else
			m.spritexscale = min(13<<16, $+39000)
			if (m.OM_scaling < 20)
				m.spriteyscale = min(10<<16, $+46000)
			elseif not m.OM_scaling
				m.spritexscale,m.spriteyscale = 13<<16, 10<<16
				S_StartSoundAtVolume(s, sfx_kc55, 150)
			end
		end
	elseif not m.OM_active
		if (displayplayer==consoleplayer) and displayplayer Render_OM_Options(m) end return --
	end

	m.OM_active = max($-1,0)
	
	if not (m.Ptable)
		if (displayplayer==consoleplayer) and displayplayer Render_OM_Options(m) end return --
	end
	local RENDER,MEN = nil
	
	for i = 0,31
		local MENU = m.Ptable[i]
		if type(MENU)=="table"
			if MENU.banned
				MENU.banned = max($-1,0)
			end
			local p = MENU.player
			if p and p.valid and p.mo and p.mo.valid
				if ((OPTIMENU(MENU, MENU.player, MENU.player.mo, m))!=false)
					if (p==displayplayer)
						RENDER,MEN = p,MENU
					end
				end
				MENU.timer = max($-1,0)
			else
				MENU.timer = 0
			end
		end
	end
	if (displayplayer==consoleplayer) and displayplayer
		if (RENDER==displayplayer)
			Render_OM_Options(m, MEN, nil, MEN and MEN.floor)
		else
			Render_OM_Options(m)
		end
	end
end, MT_GSOPTIMENU)

--Code for touching the menu list.
addHook("TouchSpecial",function(s, v) if not (s and s.valid and not s.OM_scaling and not s.OM_notouch) return true end --
	if s.OM_active and v.valid and v.GS_Touched!=nil and type(s.Ptable[v.GS_Touched])=="table"
		s.Ptable[v.GS_Touched].timer = 35
		s.OM_active = 45 
		return true --Yeah, yeah, we're good.
	end
	
	if not (v.valid and v.player and v.player.valid) return true end --
	local p = v.player
	
	if (p.speed > 34*v.scale) 
	or p.cmd.buttons!=0
	or p.cmd.sidemove!=0 
	or p.cmd.forwardmove!=0
	or (p.pflags & (PF_SPINNING|PF_THOKKED|PF_JUMPED|PF_NOJUMPDAMAGE|PF_SLIDING)) 
	or not ((p.panim <= PA_RUN) and (p.panim >= PA_IDLE))
	or not P_IsObjectOnGround(v)
	or p.yusonictable and (p.yusonictable.tauntmenu or p.yumenutable)
	or p.cc_menu or p.sms_intoship or p.sam_intoship --Not in other menus!
	or p.xsonictable and (p.xsonictable.FreeFlight or p.XSonicMenu)
	or not (v and v.health)
	or P_PlayerInPain(p) 
	or p.powers[pw_carry] 
	or p.powers[pw_nocontrol]		
		return true --
	end
	
	for i = 0, #players
		if p==players[i]
			local ADMIN = false
			if (IsPlayerAdmin(p) or p==server or p.specialsmscheatspermission)
				ADMIN = true
			end
			v.GS_mymenu = s
			v.GS_Touched = i 
			if s.Ptable == nil s.Ptable[i] = {} end
			s.Ptable[i] = {player = p, timer = 35, INIT = 0, ADMIN = ADMIN}
			s.OM_active = 45 return true --Add 'em to the array.
		end
	end
	return true --Make sure it returns true, otherwise it dies.	
end, MT_GSOPTIMENU)

--Add descriptions to the Opti-Menu HUD?
hud.add(function(v, p)
	if not (p.powers[pw_carry]==11777 and (gamestate==GS_LEVEL) and p.mo and p.mo.GS_mymenu and p.mo.GS_Touched!=nil)
		return --
	end
	local s = p.mo
	local m = s.GS_mymenu
	if not (m.valid and m.Ptable and m.Ptable[s.GS_Touched] )
		return --
	end
	local MENU = m.Ptable[s.GS_Touched] 
	local SELECT = MENU.floor
	if not MENU.INIT or not SELECT
		return --
	end
	
	local YOFF,TRANS = 0,0
	if (MENU.INIT < 20)
		YOFF = $+(20-MENU.INIT)
		TRANS = 20-(MENU.INIT/2)
	end
	if MENU.TRANS
		TRANS = $+MENU.TRANS
	end
	TRANS = max(0, min(9, $))<<16
	
	
	--Also, add a description for the option you're currently hovering on.	
	local DESCRIPTION = {
	[1] = "Disable the Skybox if there is one.",
	[2] = "Disable rain/snow if active.",
	[3] = "Disable most object's shadows.",
	[4] = "Attempt to optimize scenery objects.",
	[5] = "Remove objects from the blockmap list",
	[6] = "Remove collission from non-vital objects",
	[7] = "Attempt to optimize intensive objects",
	[8] = "Force Grind Rails to use papersprites only."
	}
		
	--Formatting descriptions is a bit of a bitch.
	local TEXT = DESCRIPTION[SELECT]
	if type(TEXT) == "string"
		local T1 = "["+"\x80"+"PERFORMANCE BOOST:\x82 " --Predicted boost.
		local T2 = "\x80"+"]"
		local GAIN = {
		[1] = T1+"*****"+T2,
		[2] = T1+"****"+T2,
		[3] = T1+"**"+T2,
		[4] = T1+"***"+T2,
		[5] = T1+"*"+T2,
		[6] = T1+"**"+T2, --★ Damnit, won't work.
		[7] = T1+"***"+T2,
		[8] = T1+"****"+T2
		}
	
		T1 = "\x82"+"NOTE:\x86 "
		local NOTE = {
		[1] = T1+"Must reload stage to re-enable.",
		[2] = T1+"Must reload stage to re-enable.",
		[3] = T1+"Must reload stage to re-enable.",
		[4] = T1+"\x85"+"Risk of creating issues. Must reload stage to re-enable.",
		[5] = T1+"\x85"+"Big risk of creating issues. Must reload stage to re-enable.",
		[6] = T1+"\x85"+"Risk of creating issues. Must reload stage to re-enable.",
		[7] = T1+"\x85"+"Risk of creating issues. Must reload stage to re-enable.",
		[8] = T1+"Must reload stage to re-enable."
		}
		
		T1 = "[\x87"+"ADMIN ONLY"+"\x80"+"]"
		T2 = "[\x84"+"CLIENTSIDE"+"\x80"+"]"
		local TADMIN = {
		[1] = T2,
		[2] = T2,
		[3] = T2,
		[4] = T1,
		[5] = T1,
		[6] = T1,
		[7] = T1,
		[8] = T2
		}		
		local FLAGS = V_SNAPTOBOTTOM|V_PERPLAYER|V_HUDTRANS|V_ALLOWLOWERCASE
		
		v.drawString(160, 167+YOFF, TEXT, FLAGS|TRANS, "center")
		
		--Just gonna assume the rest is valid too.
		v.drawString(80, 155+YOFF, GAIN[SELECT], FLAGS|TRANS, "thin-center")
		v.drawString(246, 155+YOFF, TADMIN[SELECT], FLAGS|TRANS, "thin-center")			
		v.drawString(160, 191+(YOFF/2), NOTE[SELECT], FLAGS|(max(TRANS or 0, FF_TRANS30)), "small-center")
	end
	
	--Yes, you can indeed exit the menu.
	v.drawString(7-(YOFF/3), 191+(YOFF/2), "\x85"+"SPIN:\x80 Exit", V_ALLOWLOWERCASE|V_SNAPTOBOTTOM|V_SNAPTOLEFT|TRANS, "small")	
end)

addHook("MapChange", function(mapnum)
	for z in players.iterate
		if z and z.valid
			if (netgame!=true) --or (RETAIN==true)
				if GSLASTMAP and (mapnum==GSLASTMAP) and type(z.didOM)=="table" and z==players[0]
					OM_RETAIN = true
					z.redoOM = z.didOM
				end
			end
			z.didOM = {}
		end
	end
	if GSLASTMAP!=mapnum
		SetOptiDefaults(_) --Reset optimenu settings each map.
		GSLASTMAP = mapnum
	end	
	if AOPTIONS!=nil
		AOPTIONS[99] = nil
	end
end)

--Gotta move it to this hook.
addHook("PlayerSpawn", function(p)
	if OM_RETAIN==true
		if p==players[0] 
			if type(p.redoOM)=="table" 
				for i = 1,8
					if i==4 and p.redoOM[4] == 2
						for d = 1,2
							Activate_Option(p, 4, "silent")
						end
					elseif p.redoOM[i]
						Activate_Option(p, i, "silent")
					end
				end	
			end
			RETAIN = nil
		end
		p.redoOM = nil
	end
	--Want it to always spawn?
	if OM_AUTOSPAWN==true and not (AOPTIONS and AOPTIONS[99] and AOPTIONS[99].valid)
		local s = p.mo
		if s and s.valid
			local ghs = P_SpawnMobjFromMobj(s,0,0,0,MT_GSOPTIMENU)
			ghs.CalledMe = s
			ghs.scale = s.scale/4
			ghs.OM_notouch = 80
			SetOptiMenu(ghs, s)
		end
	end
end)

--Command that spawns an Opti-Menu, or brings the current one to you, adding all settings.
COM_AddCommand("GSOptiMenu", function(p, var1, size)
	if not (p and p.valid) return end --
	if type(var1)=="string"
		var1 = string.lower($)
		if var1=="autospawn" or var1=="auto" or var1=="always"
			if OM_AUTOSPAWN==true
				print("\x82"+p.name+"\x80 turned\x85 off\x80 Auto-Spawn for the Opti-Menu.")
				OM_AUTOSPAWN = false
			else
				OM_AUTOSPAWN = true
				print("\x82"+p.name+"\x80 turned\x83 on\x80 Auto-Spawn for the Opti-Menu. It'll spawn even if a level has none placed.")
			end
			S_StartSound(nil, sfx_menu1) return --
		end
	end 
	
	if not ((gamestate == GS_LEVEL) and p.mo and p.mo.health)
		print("\x85"+"You must be alive in a stage to spawn the Optimization-Menu!") return --
	end
	
	local s = p.mo
	local ghs = AOPTIONS[99]
	if var1=="alloptions" and ghs and ghs.valid
		P_RemoveMobj(ghs)
		AOPTIONS[99] = nil
	end
	local SCALE = nil
	if ghs and ghs.valid
		SCALE = ghs.scale
		ghs.OM_scaling,ghs.OM_notouch = 36,36
		ghs.scale = 9535
		P_MoveOrigin(ghs, s.x, s.y, s.z)
	else
		ghs = P_SpawnMobj(s.x, s.y, s.z,MT_GSOPTIMENU)
		var1 = "alloptions"
	end
	if ghs and ghs.valid
		ghs.angle = s.angle+ANGLE_270
		ghs.CalledMe = s
		S_StartSound(nil, sfx_menu1)
		if var1=="alloptions"
			SetOptiDefaults(p, "nocolor")
			print("\x82"+p.name+"\x80"+" spawned a brand new Opti-Menu at their location.")
		else
			print("\x82"+p.name+"\x80"+" warped the Opti-Menu to their location.")
		end
		SetOptiMenu(ghs, s)
		if (AOPTIONS) and AOPTIONS[99] == nil
			AOPTIONS[99] = ghs
		end
		if size and (tonumber(size))
			size = tonumber($)
			if (size >= 1)
				CONS_Printf(p, "\x88"+"Also set menu size to\x82 "+min(100,size))
				ghs.scale = max(9535, 6535*min(100, size) )
			elseif SCALE
				ghs.destscale = SCALE
			end
		elseif SCALE
			ghs.destscale = SCALE
		end
	else
		S_StartSound(nil, sfx_adderr, p)
		CONS_Printf(p, "\x89"+"Couldn't spawn an Opti-Menu...try moving elsewhere.")
	end
end, COM_ADMIN)

addHook("NetVars", function(network)
	AOPTIONS = network($) --Admin options should be synced since they can affect non-cosmetic things.
	OM_AUTOSPAWN = network($) --Autospawn should sync too.
/*	RETAIN = network($)
	GSLASTMAP = network($)
	OM_RETAIN = network($) */
end)