--╔════════════════════════════╗══════════════════════════════════════════════════════════════════════════════════════════════#
--║       MAP LOAD STUFF,      ║ Easiest to keep this stuff in 1 place so it's easier to sync without using globals too.
--║   MOSTLY FOR PLANET EGGS   ║ Planet Eggs are directly tied to X's Emerald system.
--╚════════════════════════════╝══════════════════════════════════════════════════════════════════════════════════════════════#
local XSon = XSon
local function P_MobjFlip(s) if s and s.valid and ((s.eflags&MFE_VERTICALFLIP) or (s.flags2&MF2_OBJECTFLIP)) return -1 end return 1 end

local XS_LUCKYEGG,XS_EGGFESTIVAL,XS_FOESINMAP,XS_LASTMAP = 0,0,0,gamemap --NOTE: XS_EGGFESTIVAL is a debug variable!
local MAPOFFERED = {}
local EGGNUM = 0
local XLASTGAMETYPE

local GTABLE = {
EMERALD1,
EMERALD2,
EMERALD3,
EMERALD4,
EMERALD5,
EMERALD6,
EMERALD7
}
local GCTABLE = {
SKINCOLOR_FOREST,
SKINCOLOR_CRIMSON,
SKINCOLOR_SANDY,
SKINCOLOR_WAVE,
SKINCOLOR_PURPLE,
SKINCOLOR_COBALT,
SKINCOLOR_JET
}

local function UpdateTotalRings(TOTALRINGS, RINGDIST, s)
	if (RINGDIST) and s
		searchBlockmap("objects",function(s, m)
			if (m.type == MT_RING) or (m.type == MT_COIN) or (m.type == MT_RING_BOX) 
				TOTALRINGS = $+1
			end
		end, s, s.x-RINGDIST,s.x+RINGDIST, s.y-RINGDIST, s.y+RINGDIST)
	end
--	print("\x82"+"RINGS"+"\x86 DETECTOR:\x82 "+TOTALRINGS+"\x86 ---->) ")
	for z in players.iterate
		if z and z.valid and z.xsonictable
			z.xsonictable.TotalRings = TOTALRINGS
		end
	end
end


--Every map, try to spawn eggs!
addHook("MapLoad", function(gamemap)
	local DELETEPOWERRING = false
	local CURMAP = mapheaderinfo[gamemap]
	local CURTITLE = CURMAP and CURMAP.lvlttl and string.upper(CURMAP.lvlttl)
	
	if (XS_LASTMAP==gamemap) and (netgame!=true)
	or XS_LASTMAP and mapheaderinfo[XS_LASTMAP] and mapheaderinfo[XS_LASTMAP].lvlttl == "TUTORIAL X"
	or (CURTITLE == "TUTORIAL X")
	or (tutorialmode==true)
	or (modeattacking) and not (maptol & TOL_NIGHTS)
	or (gametype != GT_COOP)
		DELETEPOWERRING = true --Delete Power Rings in and after the tutorial, or in time-attack.	
	end
	
	for z in players.iterate
		if z and z.valid
			z.YUKgibsLVL = 0
			if z.xsonictable
				local xs = z.xsonictable
				xs.TotalRings,xs.TScooldown = nil
				if DELETEPOWERRING==true
					xs.PowerRing = nil
				end
			end
		end
	end
	if (gametype == GT_COOP)
		if (XLASTGAMETYPE != nil) and (XLASTGAMETYPE != gametype) and (netgame==true)
			XS_FAKEEMERALDS = 0
			XLASTGAMETYPE = gametype --Only record in netgames.
		end	
		XS_KILLEDFOES,XS_FOESINMAP = 0,0
		XSon.SpawnPlanetEgg(nil)
	else
		UpdateTotalRings(0)
		XS_EGGSTHISMAP, XS_LASTMAP = 0, nil
		XLASTGAMETYPE = gametype
	end
	XS_LASTMAP = gamemap
end)

--Place this here too, since it involves map loading.
XSon.SuperTimeAttack = function(p, s, xs)
	if s and IsXSonic(s) and xs
		emeralds = 127
		XS_FAKEEMERALDS = 127
		if xs.SuperTimeAttack != "AllEmeralds" --and not xs.darkphase and not p.powers[pw_super]
			p.rings = max($, 50)
			if xs.SuperTimeAttack=="Super" --Super
				if s.skin != "xsonic"
					if p == consoleplayer and p.speed==0 and p.panim == PA_IDLE
						COM_BufInsertText(p, "skin xsonicserious; skin xsonic")	
					end	
					R_SetPlayerSkin(p, "xsonic")
					s.skin = "xsonic"
				end
				local STATE = s.state
				p.rings = min(100, $+50)
				XSon.StartTransformation(p,s,xs, 1, "superTA")
				p.rings = max(50, $-50)

				s.XStransform = 3 s.tics = 3 --Move to the instant transform for Quality of Life.
				XS_MediumDust(s, SKINCOLOR_SUPERSILVER1, AST_ADD, Q|FF_TRANS30, 42*s.scale, s.angle)
				XS_RingBurstSparkles(s)
				s.XSecho = nil
				S_StopSoundByID(s, sfx_xssfph) S_StopSoundByID(s, sfx_xsvoc7)
				XSon.Sound(s, sfx_xssfpu)
				s.state = STATE	
				s.XS_FORCEdark = {supersonic = true, timer = 16}
			else --Dark
				if s.skin != "xsonicserious"
					if p == consoleplayer and p.speed==0 and p.panim == PA_IDLE
						COM_BufInsertText(p, "skin xsonic; skin xsonicserious")	
					end	
					R_SetPlayerSkin(p, "xsonicserious")
					s.skin = "xsonicserious"
				end
				if xs.SuperTimeAttack=="Dark"
					XSon.ObtainTheNPass(p,s,xs, "superTA")
				else
					xs.darkphase = 2 --FullDark
					XSon.ObtainTheNPass(p,s,xs, "superTA")		
				end	
				s.XS_FORCEdark = {phase = (xs.SuperTimeAttack=="Dark") and 1 or 3, timer = 16}
			end
		end
	end	
end

--Spawn a planet egg!
XSon.SpawnPlanetEgg = function(s)
	local TOTALRINGS = 0
	local XYDIST = 31855<<16 --Almost 32INT max
	local RINGDIST = XYDIST/4
	
	if XS_FAKEEMERALDS and (XS_FAKEEMERALDS >= 127)
		if not (s and s.valid) --Pick a player to use as blockmap point.
			for p in players.iterate
				if p.mo and p.mo.valid
					s = p.mo break--
				end
			end
		end	
		UpdateTotalRings(TOTALRINGS, RINGDIST, s) return --
	end
	
	if not netgame and not multiplayer
		local XSONIC = false
		for z in players.iterate if z and z.mo and z.mo.valid and IsXSonic(z.mo) XSONIC = true break end end
		if XSONIC==false return end --Don't spawn Planet Eggs in Single Player if not X Sonic.
	end
	local NEWMAP = false
	if (XS_LASTMAP != gamemap) or (XS_EGGFESTIVAL)==1
		XS_EGGSTHISMAP = 0
		if XS_LASTMAP != gamemap 
			NEWMAP = true
		end
		XS_LASTMAP = gamemap
	end
	local MAP = mapheaderinfo[gamemap] --Are we in the proper stage to play this?
	if MAP
		if (modeattacking) and not (marathonmode)
			return --Time Attack doesn't use these, but marathon mode does.
		end	
		local TITLE,SUB = string.upper((MAP.lvlttl or "")), string.upper((MAP.subttl or ""))		
		if MAP.bonustype == 1
		or MAP.bonustype == 2
		or ((MAP.ssspheres or 0) > 1) 
		or (maptol & TOL_NIGHTS) 
		or G_IsSpecialStage(gamemap)
		or MAP.typeoflevel!=nil and not (MAP.typeoflevel & (TOL_SP|TOL_COOP))
		or OLDC and (gamemap == OLDC.hub)
		or string.find(TITLE, "HUB") or string.find(SUB, "HUB")
		or string.find(TITLE, "THORNDYKE MANSION") or string.find(TITLE, "TUTORIAL")
		or (tutorialmode==true)
			XS_EGGSTHISMAP = 0 return --Boss level, Special Stage, HUB, or simply not a COOP level.
		end
	else
		XS_EGGSTHISMAP = 0 --uhh....
	end
	--Pick a player to use as the blockmap point.
	if not (s and s.valid)
		for p in players.iterate
			if p.mo and p.mo.valid
				s = p.mo break--
			end
		end
		if not (s and s.valid) return end --
	end
	local STARTABLE,LIFETABLE
	local RESTRICTED = 0
	local MAPHASTOKEN = (token) and max(1, token) or 0
	local CAMPAIGN = mapheaderinfo[1]
	
	if CAMPAIGN and (CAMPAIGN.lvlttl == "Greenflower") and (spstage_start <= 1)
		CAMPAIGN = "vanilla"
		if (MAP.lvlttl == "Greenflower") and (gamemap==1 or gamemap==2) 
		or XS_FAKEEMERALDS==63
			RESTRICTED = (gamemap==2) and 2 or 1
		end
	else
		CAMPAIGN = "modded"
	end
	
	--Let's use a blockmap search for this instead of a mobj search iterate.
	searchBlockmap("objects",function(s, m)
		if (m.type == MT_STARPOST) 
			if (RESTRICTED!=0) or m.X_egged return end --
			if (XS_EGGFESTIVAL)==1 --DEBUG TEST DEBUG TEST
				P_SpawnMobjFromMobj(m,0,0,m.height,MT_XSPLANETEGG) m.X_egged = true
			else
				if STARTABLE == nil STARTABLE = {} end 			
				table.insert(STARTABLE, m)
			end
		elseif (m.type==MT_INVULN_BOX) and not ((m.z < -24<<16) and (gamemap==1)) or (m.type==MT_INVULN_GOLDBOX)
		or (m.type==MT_1UP_BOX) 
		or (m.type == MT_PITY_BOX) or (m.type == MT_PITY_GOLDBOX)
		or (m.type==MT_ARMAGEDDON_BOX) or (m.type==MT_ARMAGEDDON_GOLDBOX)
		or (m.type == MT_TOKEN) 
		or (m.type == MT_EMERHUNT) 
			if (m.type == MT_TOKEN)
				MAPHASTOKEN = $+1
			end
			if m.X_egged return end --
			local SEC = m.subsector and m.subsector.sector
			if not (m.flags & MF_MONITOR)
				local SEC = m.subsector and m.subsector.sector
				if SEC and (abs(m.z-SEC.floorheight) > 200<<16)
					return --Return nil is continue in blockmap terms.
				end
			elseif m.ceilingz and (abs(m.floorz-m.ceilingz) < 50<<16)
			and not (RESTRICTED==1 and (m.z == -32<<16))
				return --Too cramped!
			end
			if SEC
				if SEC.special
					local SPECIAL = GetSecSpecial(SEC.special, 2)
					if SPECIAL == 2 return end --
				end
				if SEC.specialflags and (SEC.specialflags & SSF_EXIT) return end --
				if (abs(SEC.ceilingheight-SEC.floorheight) < 50<<16) return end --	
			end
			if (XS_EGGFESTIVAL)==1 --DEBUG TEST DEBUG TEST
				P_SpawnMobjFromMobj(m,0,0,m.height+24<<16,MT_XSPLANETEGG) m.X_egged = true
			else			
				if LIFETABLE == nil LIFETABLE = {} end
				table.insert(LIFETABLE, m)		
			end
		elseif (m.flags & (MF_ENEMY|MF_BOSS))
			XS_FOESINMAP = $+1
		elseif (m.type == MT_RING) or (m.type == MT_COIN) or (m.type == MT_RING_BOX) 
			if (FixedHypot(s.x-m.x, s.y-m.y) < RINGDIST)
				TOTALRINGS = (m.type==MT_RING_BOX) and $+5 or $+1
			end
		end
	end, s, s.x-XYDIST,s.x+XYDIST, s.y-XYDIST, s.y+XYDIST) 
	
	UpdateTotalRings(TOTALRINGS) 
	
	--Also add Emblems as potential spawn locations.
	local EMBLEMS
	for mt in mapthings.iterate do --Scan for every emblem too!
		if (mt.type == EMBLEMMAPTHINGNUM) or mt.mobj and (mt.mobj.type == MT_EMBLEM) 
			if EMBLEMS == nil EMBLEMS = {} end
			table.insert(EMBLEMS, mt)
		end
	end
	if EMBLEMS
		local X,Y,Z --Coordinates to spawn at. Manipulated later.
		for i = 1,#EMBLEMS
			local mt = EMBLEMS[i] if not (mt and mt.valid) continue end --
			if mt.mobj and mt.mobj.valid
				X,Y,Z = mt.mobj.x, mt.mobj.y, mt.mobj.z
			else
				X,Y,Z = mt.x<<16,mt.y<<16,mt.z<<16
			end
			local ghs = P_SpawnMobj(X,Y,Z, MT_GARGOYLE)
			if ghs and ghs.valid
				ghs.state = S_THOK
				ghs.tics = 33
				ghs.radius,ghs.height = 1,1
				P_SetObjectMomZ(ghs, 2<<16) 
				ghs.flags,ghs.flags2 = $|MF_NOCLIPTHING|MF_NOSECTOR, $|MF2_DONTDRAW
				ghs.EmblemTemp = true
				if mt.mobj and mt.mobj.valid
					ghs.EmblemMobj = mt.mobj
				end
				ghs.EmblemXYZ = {X, Y, Z}
				if LIFETABLE == nil LIFETABLE = {} end 
				table.insert(LIFETABLE, ghs)	
			end			
		end
	end
	local NUM = 0
	EGGNUM = 0
	if STARTABLE and not (XS_EGGSTHISMAP)
	and not ((gamemap < 8) and CAMPAIGN == "vanilla" and (XS_FAKEEMERALDS >= 4))
		local RNG = P_RandomRange(1,#STARTABLE)
		local m = STARTABLE[RNG]
		if m and m.valid
			m.X_egged = true
			m.X_PlanetEgg = P_SpawnMobjFromMobj(m,0,0,m.height,MT_XSPLANETEGG)
			m.X_PlanetEgg.ID = EMERALD1
			EGGNUM = $+1
		end 
	end
	
	if LIFETABLE
		
		NUM = #LIFETABLE
		local LUCKY = 1
		local LUCKYNUM = 0
		if (XS_EGGFESTIVAL)==1
			XS_EGGSTHISMAP = 0
			EGGNUM = $+NUM
			LUCKY = NUM --ALL OF THEM.
		elseif (NUM > 1) and (RESTRICTED != 1)
			if not (XS_FAKEEMERALDS) or (XS_FAKEEMERALDS < ((CAMPAIGN=="vanilla") and 3 or 6))
				LUCKY = (RESTRICTED==2) and $+2 or $+1	--Always get lucky at first.
			else
				LUCKY = max(1, P_RandomRange(3, 6+XS_LUCKYEGG)/3) --50% odds, then increases every stage you don't get it.
				if LUCKY == 1
					if NEWMAP==true
						XS_LUCKYEGG = $+2 --Increase luck for next stage!
					end
				else
					LUCKYNUM = LUCKY
				end
				if CAMPAIGN == "modded"
					if LUCKYNUM
						LUCKYNUM = $+1
					end
					LUCKY = $+1 --Bonus Extra Egg per stage in modded campaigns.
				end	
			end		
		end
		if (LUCKY > (XS_EGGSTHISMAP))
			for i = 1,LUCKY
				if i == 1 
					if (XS_EGGSTHISMAP & EMERALD2) continue end --Hey, it's already set!
				elseif i == 2 
					if (XS_EGGSTHISMAP & EMERALD3) continue end
				else--if i == 3 
					if (XS_EGGSTHISMAP & EMERALD4) continue end
				end
				local RNG = P_RandomRange(1,NUM)
				for d = 0,NUM --Keep trying until we hit a valid number.
					local m = LIFETABLE[min(#LIFETABLE, RNG+d)]
					if (m) and m.valid and m.X_egged!=true
					
						m.X_egged = true
						m.X_PlanetEgg = P_SpawnMobjFromMobj(m, 0,0, (m.EmblemTemp) and 1 or m.height+(23<<16), MT_XSPLANETEGG)
						local EGG = m.X_PlanetEgg
						
						if EGG and EGG.valid
							EGGNUM = $+1
							EGG.MAPHASTOKEN = MAPHASTOKEN
							EGG.ID = (i==1) and EMERALD2 or (i==2) and EMERALD3 or EMERALD4
							MAPHASTOKEN = max(0, $-1)
							if i==LUCKYNUM
								XS_LUCKYEGG = 0
							end
							if m.EmblemTemp --Emblem spawns are a bit trickier.
								if (m.EmblemMobj) and m.EmblemMobj.valid
									EGG.EmblemMobj = m.EmblemMobj
									EGG.z = m.EmblemMobj.z
								end
								EGG.EmblemXYZ = m.EmblemXYZ
								P_RemoveMobj(m)
							end
							break --
						end 
					end
				end
			end
		end	
	end
--	print("EGGS gotten this map:\x81"+XS_EGGSTHISMAP+"\x80 - Total EGGS spawned:\x82"+EGGNUM)
end


local SACRIFICE = {}
SACRIFICE[1] = "20 rings + 1 life"
SACRIFICE[2] = "30 rings + 25000 score"
SACRIFICE[3] = "2 lives"
SACRIFICE[4] = "25000 score + 1 life"
SACRIFICE[5] = "50000 score"
SACRIFICE[6] = "1 continue"
SACRIFICE[7] = "1 shield + 10 rings"
SACRIFICE[8] = "10 rings + 5000 score + 1 life"
SACRIFICE[9] = "1 emerald token"
SACRIFICE[10] = "25 rings + the lives of 7 foes"
SACRIFICE[11] = "the lives of 15 foes"

--Interact with the Planet Egg!
XSon.PlanetEgg = function(p,s,xs, EGG)
	local EGG = s.XS_PlanetEgg 
	local t = (EGG) and EGG.EGG 
	local GMAP = MAPOFFERED[gamemap]
	if not (EGG and t and t.valid) or t.DidOffering or GMAP == 2  or p.powers[pw_carry] or not s.health or P_PlayerInPain(p)
		s.XS_PlanetEgg = nil return  --
	end
	
	s.XS_C3CONTEXT = nil --No, sir.
	s.XSresist = 3 --Not ENTIRELY invincible, do pay SOME attention!
	EGG.timer = max($-1,0) 
	EGG.init = $+1
	if EGG.timer==0 s.XS_PlanetEgg = nil return end --
	
	if t.sacrifice == nil or t.sacrificeNUM == nil
		local MAX = (netgame!=true) and #SACRIFICE or #SACRIFICE-1
		local NUM = P_RandomRange(1,MAX)
		if NUM == 6
			if (multiplayer or netgame) --There's no continues in Multiplayer normally.
				NUM = 1
			end
		elseif NUM == 9
			if not (t and (t.MAPHASTOKEN))
				NUM = 8
			else
				t.MAPHASTOKEN = (t.MAPHASTOKEN) and max($-1, 0) or 0
			end
		elseif (NUM >= 10)
			if NUM == 11 and (XS_FOESINMAP < 18) or (XS_FOESINMAP < 8)
				NUM = 3 --Hardly enough foes in the level to even kill here...
			else
				XS_FOESINMAP = max(0, $-((NUM==11) and 15 or 7))
			end
		end
		if (p.lives==127 or (p.lives<0)) --Infinite lives are on? Make this a bit harder, then.
			if NUM == 3
				NUM = 7
			elseif NUM == 1 or NUM == 4
				NUM = 2
			elseif NUM == 8
				NUM = 5
			end
		end
		t.sacrificeNUM = NUM --This will be our sacrifice!
		t.sacrifice = SACRIFICE[NUM] --The text given to the player's HUD.
	end
	if (EGG.init < 6) return end --
	
	local NUM = t.sacrificeNUM or 0
	local SCORE,RINGS,LIVES = p.score or 0, p.rings or 0, p.lives or 0
	local CONTINUES,TOKEN = p.continues or 0, (token) and token or (s.XS_token) and s.XS_token or 0

	if NUM==1
		t.CanOffer = ((RINGS>=20) and (LIVES>1)) and 2 or 0
	elseif NUM==2
		t.CanOffer = ((RINGS>=30) and (SCORE>=25000)) and 2 or 0
	elseif NUM==3
		t.CanOffer = (LIVES>2) and 2 or 0	
	elseif NUM==4
		t.CanOffer = ((LIVES>1) and (SCORE>=15000)) and 2 or 0	
	elseif NUM==5
		t.CanOffer = (SCORE>=50000) and 2 or 0	
	elseif NUM==6
		t.CanOffer = (CONTINUES>=1) and 2 or 0	
	elseif NUM==7
		local SHIELD = (p.powers[pw_shield] & ~SH_FIREFLOWER)
		t.CanOffer = (SHIELD and (SHIELD != SH_PINK) and (RINGS>=10)) and 2 or 0
	elseif NUM==8
		t.CanOffer = ((LIVES>1) and (SCORE>=5000) and (RINGS>=10)) and 2 or 0		
	elseif NUM==9
		t.CanOffer = (TOKEN!=0) and 2 or 0
	elseif NUM==10
		t.CanOffer = ((XS_KILLEDFOES>=7) and (RINGS>=25)) and 2 or 0	
	elseif NUM==11	
		t.CanOffer = (XS_KILLEDFOES>=15) and 2 or 0	
	end
	if t.CanOffer and xs.custom3 == 1 and not t.DidOffering==true
		if (p.panim == PA_IDLE or p.panim == PA_WALK) and P_IsObjectOnGround(s) and (p.speed < 3*s.scale)
		or (s.eflags & MFE_GOOWATER) and (s.eflags & MFE_UNDERWATER) --Make the sacrifice!
			local LIVES = p.lives
			P_FlashPal(p, PAL_NUKE, 2)
			if NUM==1
				p.rings = max($-15,0) p.lives = max($-1,1) 
				S_StartSoundAtVolume(s, sfx_antiri, 150) S_StartSound(s, sfx_s1a2)
			elseif NUM==2
				p.rings = max($-30,0) p.score = max($-25000,0) S_StartSound(s, sfx_antiri) S_StartSound(s, sfx_shattr)
			elseif NUM==3
				p.lives = max($-2,1) S_StartSound(s, sfx_s1a2)
			elseif NUM==4
				p.lives = max($-1,1) p.score = max($-15000,0) S_StartSound(s, sfx_s1a2) S_StartSound(s, sfx_shattr)
			elseif NUM==5
				p.score = max($-50000, 0) S_StartSound(s, sfx_spkdth)
			elseif NUM==6
				p.continues = max($-1,0) S_StartSound(s, sfx_ngskid)
			elseif NUM==7
				p.rings = max($-10, 0) S_StartSound(s, sfx_antiri)
				p.powers[pw_shield] = (p.powers[pw_shield] & SH_FIREFLOWER)
				P_SpawnShieldOrb(p)
				P_FlashPal(p, PAL_RECYCLE, 3) 
				P_NukeEnemies(s,s,0) S_StartSound(s, sfx_shldls)
			elseif NUM==8
				p.lives = max($-1,1) p.score = max($-5000,0) p.rings = max($-10,0)	
				S_StartSoundAtVolume(s, sfx_antiri, 130) S_StartSoundAtVolume(s, sfx_shattr, 130) S_StartSoundAtVolume(s, sfx_spkdth, 50)		
			elseif NUM==9
				S_StartSound(s, sfx_notadd, 200)
				if s.XS_token
					s.XS_token = max($-1,0)
				end
				if token
					token = max($-1,0)
				end
			elseif NUM == 10
				P_FlashPal(p, PAL_NUKE, 5) 
				S_StartSound(s, sfx_xssfpf) S_StartSound(s, sfx_antiri)
				XS_KILLEDFOES = max($-7,0)
				p.rings = max($-25,0)
			elseif NUM == 11
				P_FlashPal(p, PAL_NUKE, 12) 
				S_StartSound(s, sfx_xssfpf)	
				XS_KILLEDFOES = max($-15,0)	
			end
			if LIVES == 127 or (LIVES < 0)
				p.lives = LIVES --Fix infinite lives mode if we broke it.
			end
			for z in players.iterate
				if z and z.mo and z.mo.health -- and IsXSonic(z.mo)
					z.xssuperflash = {trans = FF_TRANS90, timer = 24, speed = 2, reversal = 13, COLOR = SKINCOLOR_PUREBLACK}
				end
			end
			P_RadiusAttack(s, s, min(800<<16, s.radius*12))
			S_StartSoundAtVolume(nil, sfx_s3k57, 80)
			
			t.DidOffering,t.Offerer = true, p
			t.CanOffer = nil
			s.XS_OfferingEgg = t
			s.XS_EmeraldScam = {timer = 190, frame = A}
			s.momx,s.momy,s.momz = 0,0,0
			XSon.EmeraldScam(p,s,xs, s.XS_EmeraldScam) return true --
		end
	end
end

--This should be illegal!
XSon.EmeraldScam = function(p,s,xs,SCAM)
	s.XS_C3CONTEXT,s.XS_PlanetEgg = nil --We cannot co-exist.
	local t = s.OfferingEgg
	if not s.health or p.powers[pw_super] or xs.darkphase or not IsXSonic(s)
		if t and t.valid
			s.XS_OfferingEgg = nil
		end
		if not IsXSonic(s)
			s.state = S_PLAY_FALL
		end
		s.XS_EmeraldScam = nil return --Cancel conditions!
	end
	SCAM.timer = $-1 
	local TIMER = SCAM.timer
	if not P_IsObjectOnGround(s) --Just...uhh....float, I guess.
		local GRAVITY = P_GetMobjGravity(s) or 0
		s.momz = abs(GRAVITY)*P_MobjFlip(s)
	end
	p.cmd.sidemove,p.cmd.forwardmove = 0,0
	p.speed,s.momx,s.momy = 0,0,0
	xs.jump,xs.spin,xs.custom1,xs.custom2,xs.custom3 = 99<<16, 99<<16,99<<16,99<<16,99<<16
	
	S_SetInternalMusicVolume( min(90, max(10, (TIMER*2/5) )), p)
	p.WasInXMenu = true
	p.powers[pw_ignorelatch] = (1<<15)+2
	p.powers[pw_nocontrol] = 2
	p.pflags = $|PF_JUMPSTASIS|PF_STASIS|PF_FULLSTASIS|PF_JUMPDOWN|PF_USEDOWN & ~(PF_SPINNING|PF_JUMPED|PF_THOKKED|PF_STARTJUMP)
	
	local UW = p.powers[pw_underwater] or 0 --Can't drown/suffocate right now.
	local SPACE = p.powers[pw_spacetime] or 0
	if (UW > 0) and (UW < 1200)
		p.powers[pw_underwater] = $+2
	end
	if (SPACE > 0) and (SPACE < 400)
		p.powers[pw_spacetime] = $+2
	end
	
	if (SCAM.frame > C) and (SCAM.frame < G)
		if (TIMER%3==0)
			SCAM.frame = $+1
		end
	elseif (TIMER % 4 == 0)
		if SCAM.frame == C --Stare up for a bit longer.
			if (TIMER <= 155) 
				SCAM.frame = $+1
			end
		elseif SCAM.frame == J  --Pause on this one for a bit.
			if (TIMER < 38) 
				SCAM.frame = $+1
				S_StartSound(nil, sfx_xssfe7, p)
				XSon.Quake(s, 30<<16, 3, 3000<<16)
				local ghs = XSon.RadiantLight(s, SKINCOLOR_XSSUPERBLUR, 10, nil, S|FF_TRANS60, FRACUNIT/5)
				ghs.blendmode = AST_REVERSESUBTRACT	
				ghs = XSon.DeTransformGFX(p,s,xs, "ghosts")
				ghs.color = SKINCOLOR_XSSUPERBLUR//SKINCOLOR_PUREBLACK
				ghs.blendmode = AST_REVERSESUBTRACT //AST_TRANSLUCENT
				for i = 1,4
					XSon.SmokeMist(s, 0, 99*s.scale)		
				end
				for i = -4,-2
					local ghs = XSon.TransformGFX2(s, i*188*FRACUNIT) 
					if ghs and ghs.valid
						ghs.tracer = s
						ghs.frame = ($ & ~FF_TRANSMASK)|FF_TRANS90 --Rising orbs
					end
				end					
			end
		elseif (SCAM.frame >= O) --Pause on O until the final frame.
			local ghs = XSon.SmokeMist(s, 0, 99*s.scale)
			if ghs and ghs.valid
				ghs.spriteyscale = 120500
				if ghs.sttXspeed
					ghs.sttXspeed = $/3
				end
			end
			if (TIMER < 6)
				if SCAM.emerald and SCAM.emerald.valid
					local ghs = XSon.RadiantLight(SCAM.emerald, SKINCOLOR_XSSUPERBLUR, 8, nil, S|FF_TRANS90, FRACUNIT/3)
					ghs.blendmode = AST_REVERSESUBTRACT		
				end
				SCAM.frame = P
			end
		else
			SCAM.frame = $+1
		end
	end
	s.sprite2 = SPR2_TAL6 --P is the last frame!(16 total) Stay at J and O for a bit. 
	s.frame = ($ & ~FF_FRAMEMASK)| (min(P, SCAM.frame)) --Timer is 160 tics
	if (s.tics >= 0)
		s.tics,s.anim_duration = 105,105
	end
	if not SCAM.timer --The end!
		S_StartSound(nil, sfx_xssfe6) S_StartSoundAtVolume(s, sfx_xssfe6, 120) 
		print(p.name+" has obtained a...\x8f"+"Chaos\x80 Emerald...?")

		P_RadiusAttack(s,s, min(250<<16, s.radius*2))
		local GMAP = MAPOFFERED[gamemap]
		GMAP = (GMAP) and 2 or 1 --Can make 2 offerings per map
		s.XS_OfferingEgg,s.XS_EmeraldScam = nil --Complete!
		s.state = S_PLAY_STND

		if not ((s.flags2 & MF2_TWOD) or twodlevel) --Turn Sonic's sprite at the end...unless it's 2D!
			p.drawangle = (p.cmd.angleturn<<16)+ANGLE_135
		end
		
	elseif (SCAM.timer > 34) --Spawn a fake emerald.
	
		if SCAM.ZX == nil SCAM.ZX = 157*s.scale end
		local GEM = SCAM.emerald
		
		if not (GEM and GEM.valid)
			local FK = XS_FAKEEMERALDS or 0
			local GCOLOR = SKINCOLOR_FOREST
			local ID = 1
			if t and t.valid and t.ID != nil ID = t.ID end
			if (FK < 127)
				for i = 1,7
					if not (FK & GTABLE[i])
						GCOLOR = GCTABLE[i] 
						FK = $|GTABLE[i]
						ID = GTABLE[i] break --
					end
				end
			end
			if XS_EGGSTHISMAP != nil
				XS_EGGSTHISMAP = $+1 --We already got this emerald THIS stage!
			end	
			XS_FAKEEMERALDS = FK
			if netgame!=true
				local SPEMERALDS = xs.SP_FakeEmeralds or 0
				if (SPEMERALDS < FK)
					xs.SP_FakeEmeralds = FK
				end
			end
			
			if (FK >= 127) --Remove the other planet eggs.
				for m in mobjs.iterate(mobjs)  
					if m and m.valid and (m.type == MT_XSPLANETEGG) and m!=t
						P_RemoveMobj(m)
					end
				end
				EGGNUM = 0
			elseif EGGNUM
				EGGNUM = max($-1,0)
			end
			
			SCAM.emerald = P_SpawnMobjFromMobj(s,0,0,SCAM.ZX, MT_XSAUTOFRAME)
			GEM = SCAM.emerald
			GEM.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
			GEM.dispoffset = 33
			GEM.sprite = SPR_XSE9
			GEM.spritexoffset = 15<<16
			GEM.frame = A|FF_TRANS10 --Which Emerald?
			GEM.sttfade = true
			GEM.fuse,GEM.tics = SCAM.timer,SCAM.timer
			GEM.shadowscale = FRACUNIT
			GEM.color = GCOLOR
			
			local ghs = XSon.RadiantLight(GEM, SKINCOLOR_XSSUPERBLUR, 9, nil, S|FF_TRANS60, FRACUNIT/2)
			ghs.blendmode = AST_REVERSESUBTRACT				
			ghs.tracer = GEM
			ghs.dispoffset = 144
			for i = 1,16
				XSon.SmokeMist(GEM,0,99*s.scale, nil, nil, nil, SKINCOLOR_MIDNIGHT)		
			end
			
			GEM.glow = P_SpawnMobjFromMobj(GEM,0,0,0,MT_XSAUTOFRAME)
			GEM.glow.fuse = 3
			GEM.glow.blendmode = AST_ADD
			GEM.glow.renderflags = GEM.renderflags
			GEM.glow.dispoffset = 7	
			GEM.glow.sprite = SPR_XSE9
			GEM.glow.frame = I|FF_TRANS60
			GEM.glow.sttfade = true
			GEM.glow.spritexoffset = GEM.spritexoffset
			GEM.glow.color = GEM.color
			XSon.ElectricBody(GEM.glow)
			
			XSon.SaveLoad(p, true, false, 7)
		end
		if GEM and GEM.valid 
			local GLOW = GEM.glow
			
			if SCAM.timer == 35
				XSon.RadiantLight(GEM, SKINCOLOR_LAVENDER, 8, nil, S|FF_TRANS60, FRACUNIT/3)
				XSon.SquashFlash(GEM, SKINCOLOR_SILVER)
				GEM.destscale, GLOW.destscale = 99<<16, 99<<16
				GEM.sttXspeed,GLOW.sttXspeed = -1<<16,-1<<16
				XSon.ExpandingGlow(s, SKINCOLOR_MIDNIGHT, 15, 90, true, AST_ADD)		
			else--if (SCAM.timer > 35)
				local DARKAURA = XSon.DarkAura(0)
				if (SCAM.timer>70) and ((SCAM.timer > 120) or (SCAM.timer%2))
					XSon.DarkBody(GEM, nil, nil, DARKAURA, (SCAM.timer>130) and 2 or 1)	
					if (SCAM.timer > 130) and (SCAM.ZX > (s.height/4)) --30*4 = 120
						SCAM.ZX = $-(2*s.scale)
					end						
				end					
				GEM.fuse,GEM.tics = 12,12
				GLOW.tics,GLOW.fuse = GEM.tics,GEM.fuse
				
				local light,ths,thss = XSon.ShinySplat(s, SKINCOLOR_FUCHSIA, 3, 95, false)
				if ths and ths.valid ths.frame = ($ & ~FF_TRANSMASK)|FF_TRANS90 end
				if thss and thss.valid thss.frame = ($ & ~FF_TRANSMASK)|FF_TRANS90 end	
				
				P_MoveOrigin(GEM, s.x, s.y, s.z+(SCAM.ZX*P_MobjFlip(s)))
				P_MoveOrigin(GLOW, GEM.x, GEM.y, GEM.z)			
			end
		end	
	end
end

--Progress for the Scoreboard.
hud.add(function(v)
	if (gametype != GT_COOP) or (gamestate != GS_LEVEL) return end local p = displayplayer local s = p and p.mo	
	if not (s and s.valid and IsXSonic(s)) return end --
	
	local EMERALDS = XS_FAKEEMERALDS
	if (EMERALDS) or (EGGNUM)
		if (EMERALDS)
			local GCOLOR = SKINCOLOR_FOREST
			local X,Y = 299,50
			for i = 1,7
				if (EMERALDS & GTABLE[i])
					GCOLOR = GCTABLE[i]
					local CM = v.getColormap(nil, GCOLOR)
					for d = 2,1,-1
						local PATCH = "XSE9A0"
						local FLAGS = V_SNAPTOTOP|V_SNAPTORIGHT|FF_TRANS10
						if d == 2
							PATCH = "XSE9I0"
							FLAGS = ($|V_ADD)+FF_TRANS30
						end
						v.drawScaled(X<<16, Y<<16, 50025, v.cachePatch(PATCH), FLAGS, CM)
					end
				end
				Y = $+22
			end
		end
		if EGGNUM and EMERALDS != 127
			local FLAGS = V_SNAPTOTOP|V_SNAPTORIGHT
			local X,Y = 268,35
			v.drawScaled(X<<16, Y<<16, 1<<15, v.cachePatch("XS_PEGGB0"), FLAGS|V_ADD|FF_TRANS20) --Core
			v.drawScaled(X<<16, Y<<16, 1<<15, v.cachePatch("XS_PEGGA0"), FLAGS|FF_TRANS30) --Egg Crystal
			v.drawString(X-12, Y+5, "\x82"+tostring(EGGNUM)+"\x80"+"x", FLAGS, "thin") --Remaining num.
		end
	end	
end, "scores")

--Actual object.
addHook("MobjSpawn", function(s)
	s.renderflags = RF_SEMIBRIGHT
	s.sprite = SPR_XSG2
	s.frame = 36|FF_TRANS40
	s.spritexscale,s.spriteyscale = 89850,89850
	s.tics,s.anim_duration = -1,-1
	s.dispoffset = 7
	s.countdown = 0
	s.direct = 1
	s.trans = 3
end, MT_XSPLANETEGG)

addHook("MobjThinker", function(s) if not s.valid return end
	s.momx,s.momy,s.momz = 0,0,0
	if s.fuse and (s.fuse < 50)
		if not s.X_PlanetEggGlow 
			local DARKAURA = XSon.DarkAura(0)
			XSon.DarkBody(s, nil, nil, DARKAURA, 2)	
			if (s.fuse%7==0)
				XSon.SquashFlash(s, SKINCOLOR_GOLDENROD)
				local ghs = P_SpawnMobjFromMobj(s, 0,0,0, MT_IVSP)
				if ghs and ghs.valid
					ghs.color,ghs.colorized = DARKAURA,true	
					ghs.destscale = 99<<16
					ghs.blendmode = AST_ADD
					ghs.dispoffset = 133
				end
			end
		end
		local SFUSE = max(1, s.fuse/5)
		local TRANS = min(9, max(1, 10-SFUSE))
		s.frame = ($ & ~FF_TRANSMASK)|(TRANS<<16) 
		s.spritexscale,s.spriteyscale = $+2500,$+850
		return --
	end
	if s.X_PlanetEggGlow
		return --
	end
	if (s.trans >= 3) or (s.trans <= 0)
		s.countdown = max($-1,0)
		if s.countdown == 0
			s.direct = (s.trans>=3) and -1 or 1
			s.trans = $+s.direct
		end
	else
		s.countdown = 8
		if (leveltime % 2 == 0)
			s.trans = $+s.direct
		end
	end	
	if not s.fuse
		s.trans = max(0, min($, 3))
	end
	
	local t = s.glow
	if not (t and t.valid) --Spawn the core of the Planet Egg.
		local ghs = P_SpawnMobjFromMobj(s,0,0,0, MT_XSPLANETEGG)
		if ghs and ghs.valid
			ghs.dispoffset = -9
			ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
			ghs.frame = 37
			ghs.tics, ghs.anim_duration = -1,-1
			ghs.flags = $|MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_NOCLIP|MF_SCENERY|MF_NOCLIPHEIGHT
			ghs.radius,ghs.height = 1,1
			ghs.tracer,ghs.target = s,s
			ghs.blendmode = AST_ADD
			ghs.X_PlanetEggGlow = true
			s.glow = ghs
			t = s.tracer
		end
	end
	if t and t.valid --Alright core! Follow me, set me free!
		if not t.fuse
			t.frame = 37|FF_FULLBRIGHT|(s.trans<<16)
			local DIST = FixedHypot(s.x-t.x, s.y-t.y)
			if (DIST > 77<<16)
				P_MoveOrigin(t, s.x, s.y, s.z)
			end	
		end
		t.scale = s.scale
		t.eflags = s.eflags
		t.spritexscale,t.spriteyscale = s.spritexscale,s.spriteyscale
		t.spritexoffset,t.spriteyoffset = s.spritexoffset,s.spriteyoffset
		if (leveltime%3==0)
			local ghs = XSon.SmokeMist(s, 0, 99*s.scale)
			if ghs and ghs.valid
				ghs.spriteyscale = 120500
				ghs.spriteyoffset = $-22<<16
				if ghs.sttXspeed
					ghs.sttXspeed = $*2/5
				end
			end
		end
		if s.DidOffering
			if not s.fuse or not t.fuse
				s.fuse,t.fuse = 50,50
			end
			S_StopSoundByID(s, sfx_xssfe5)
		elseif not S_SoundPlaying(s, sfx_xssfe5)
			S_StartSound(s, sfx_xssfe5)
		end		
	end
end, MT_XSPLANETEGG) 

--Sync stuff! I tried cramming as much as possible into this one hook.
addHook("NetVars", function(network)
	XS_FAKEEMERALDS = network($)
	XS_EGGSTHISMAP = network($)
	XS_LUCKYEGG = network($)
	XS_LASTMAP = network($)
	XS_KILLEDFOES = network($)
	XS_FOESINMAP = network($)
	XS_NOPVP = network($)
	XS_TENKAICHIMUSIC = network($)
	
	MAPOFFERED = network($) --Table. Maybe don't sync...?
	EGGNUM = network($)
	
	XS_CRAPPYPRIORITIES = network($) --Temporary.
	XS_EGGFESTIVAL = network($) --Debug value.
end)

--Admin debug CMD that spawns eggs everywhere possible.
COM_AddCommand("EGGFESTIVAL", function(p)
	if (gamestate == GS_LEVEL) and p and p.valid
		if XS_EGGFESTIVAL != 0
			XS_EGGFESTIVAL = 0
		else
			XS_FAKEEMERALDS = 0
			XS_EGGFESTIVAL = 1
		end
		print("\x81"+"DEBUG CMD!"+"\x80"+" EGGFESTIVAL = \x82"+XS_EGGFESTIVAL)
	end
end, COM_ADMIN)

--Mess with Super Sonic Tenkaichi's PvP settings.
COM_AddCommand("SST", function(p, var1)
	if not ((gamestate == GS_LEVEL) and p and p.valid)
		CONS_Printf(p, "\x85"+"You must be in a level first!") return --
	end
	if not ((p.specialsmscheatspermission) or (p==server) or ( IsPlayerAdmin(p) ))
		CONS_Printf(p, "\x85"+"You gotta be an admin!") 
		S_StartSound(nil, sfx_kc59, p)
		if p.mo
			XSon.Quake(p.mo, 40<<16, 5, 2)
		end return --
	end
	if var1 and type(var1)=="string"
		var1 = string.upper(var1)

		if string.find(var1, "ENERGY") or string.find(var1, "TENSION")
			if p.XS_energyhaxx
				for z in players.iterate
					if z and z.valid
						z.XS_energyhaxx = nil
					end
				end
				print("\x82"+p.name+"\x85 disabled\x80 SST super energy regeneration.")
			else
				for z in players.iterate
					if z and z.valid
						z.XS_energyhaxx = true
					end
				end
				print("\x82"+p.name+"\x83 enabled\x80 SST high tension mode.")					
			end return --
		elseif string.find(var1, "HEALTH") or string.find(var1, "LIFE")
			if p.XS_healthhaxx
				for z in players.iterate
					if z and z.valid
						z.XS_healthhaxx = nil
					end
				end	
				print("\x82"+p.name+"\x85 disabled\x80 SST health regeneration.")
			else
				for z in players.iterate
					if z and z.valid
						z.XS_healthhaxx = true
					end
				end
				print("\x82"+p.name+"\x83 enabled\x80 SST health regeneration.")
			end return --
		elseif string.find(var1, "MUSIC") or string.find(var1, "SOUND") or string.find(var1, "TRACK") or string.find(var1, "SWAP")	
			if XS_TENKAICHIMUSIC != nil
				XS_TENKAICHIMUSIC = ($<4) and min(4,$+1) or 0
			end
			S_StartSound(nil, sfx_deton)
			print("\x82"+p.name+"\x80 swapped to the next SST music track!\x86 ("+XS_TENKAICHIMUSIC+")")
			for z in players.iterate
				if z and z.xsonictable and z.xsonictable.FreeFlight and z.xsonictable.FreeFlight.LockOn
				and z.mo and z.xsonictable.FreeFlight.LockOn.valid
					XSon.SetMusic(z, z.mo, z.xsonictable, "next")
				end
			end	return --		
		elseif string.find(var1, "CHARGE") or string.find(var1, "TAUNT")	
			if p.XS_chargehaxx
				for z in players.iterate
					if z and z.valid
						z.XS_chargehaxx = nil
					end
				end	
				print("\x82"+p.name+"\x85 disabled\x80 SST taunt super-charging.")
			else
				for z in players.iterate
					if z and z.valid
						z.XS_chargehaxx = true
					end
				end
				print("\x82"+p.name+"\x83 enabled\x80 SST taunt super-charging.")
			end return --				
		elseif (p==server) or (IsPlayerAdmin(p)) --Gotta be a real admin from here on
			if var1 =="FREEFORALL" or var1 =="FREE-FOR-ALL" or var1 =="FREE" or var1 =="F4A"
				if XS_NOPVP == 2
					print("\x81"+"DEBUG CMD:\x82"+p.name+"\x85 disabled"+"\x80 SST Free-For-All.")
					XS_NOPVP = 1
				else
					XS_NOPVP = 2
					print("\x81"+"DEBUG CMD:\x82"+p.name+"\x83 enabled"+"\x80 SST Free-For-All!\n"+
					"\x82"+"NOTE:\x86 This is an unsupported debug feature for fun! Expect bugs.")
				end return --
			elseif var1=="ON" or var1=="1" or string.find(var1, "ENABLE") or var1=="TRUE" or string.find(var1, "ALLOW") 
				XS_NOPVP = 0
				print(p.name+"\x83 enabled"+"\x80 Super Sonic Tenkaichi.\n"+"\x82"+"Go kick your super neighbours in the head!")
				return --
			elseif var1=="OFF" or var1=="0" or var1=="FALSE" or string.find(var1, "DISABLE") 
				XS_NOPVP = 1
				print(p.name+"\x85 disabled\x80 Super Sonic Tenkaichi.\n"+"\x87"+"STOP DESTROYING THEIR WOOOORLD!!!")
				return --
			end
		end
	end
	--Give some help?
	CONS_Printf(p, 
	"\n"+
	"Possible values for\x82 SST\x86"+"(Super Sonic Tenkaichi)"+"\x80"+":"+"\n"+
	"\x83"+"ON"				+"\x80|"+
	"\x85"+"OFF"			+"\x80|"+
	"\x8e"+"HEALTHREGEN"	+"\x80|"+
	"\x8a"+"HIGHTENSION"	+"\x80|"+
	"\x82"+"CHARGETAUNT"	+"\x80|"+
	"\x84"+"SWAPTRACK"		+"\x80|"+
	"\x89"+"FREEFORALL"+"\n"
	)
end)