//title screen
local fingerwag = 30
local function sonicexe(v, player)
	v.draw(0,10,v.cachePatch("EXERING"))
	if fingerwag > 15
	v.draw(0,10,v.cachePatch("EXETITLE1"))
	else
	v.draw(0,10,v.cachePatch("EXETITLE2"))
	end
	v.draw(0,10,v.cachePatch("EXENAME"))
	if fingerwag > 0
	fingerwag = $ - 1
	end
	if fingerwag <= 0
	fingerwag = 30
	end
end

hud.add(sonicexe, "title")

//freeslots

freeslot(
"S_BLOD",
"S_PLAY_LAUGH", 
"SPR_BLOD",
"SPR2_LAGH",
"sfx_kill",
"sfx_scream",
"sfx_exelgh",
"sfx_egckil"
)

//sfx info
sfxinfo[sfx_egckil] = {
        singular = true,
        priority = 64,
        flags = SF_X2AWAYSOUND
}

sfxinfo[sfx_kill].caption = "Kill"
sfxinfo[sfx_scream].caption = "Scream"
sfxinfo[sfx_exelgh].caption = "Laugh"
sfxinfo[sfx_egckil].caption = "Gore"

//states

states[S_PLAY_LAUGH] = {
	sprite = SPR_PLAY,
	frame = SPR2_LAGH,
	tics = 10,
	var1 = S_PLAY_LAUGH,
	nextstate = S_PLAY_LAUGH
}

//console commands
local cv_laughdeath = CV_RegisterVar({"laughdeath", "On", nil,CV_OnOff})
local cv_exescream = CV_RegisterVar({"exescream", "Off", nil,CV_OnOff})

//run passive
addHook("PlayerCanDamage", function(player)
if player.mo.skin == "sonicexe" and player.mo.state == S_PLAY_RUN then
return true end
end)

//stuff
addHook("PlayerThink", function(player)
if player.mo and player.mo.valid
if player.mo.skin ~= "sonicexe" return end

local mo = player.mo

//set warpthok
if mo.warpthoks == nil then mo.warpthoks = 3 end
//reset warpthoks
if (mo.eflags & MFE_JUSTHITFLOOR)
mo.warpthoks = 3
end

//laugh timer
if mo.laughtimer == nil then mo.laughtimer = 70 end

//afterimages
if player.mo.state == S_PLAY_RUN
	local aftimg = P_SpawnGhostMobj(mo)
	aftimg.alpha = FRACUNIT
	aftimg.colorized = true
	aftimg.color = SKINCOLOR_CRIMSON
	aftimg.fuse = 5
end
//no drowning or suffocating
if (mo.eflags & MFE_UNDERWATER)
player.powers[pw_underwater] = 100
end
if P_InSpaceSector(mo)
player.powers[pw_spacetime] = 100
end

//laugh
if P_IsObjectOnGround(player.mo)
and (mo.state == S_PLAY_STND or mo.state == S_PLAY_WAIT)
and player.cmd.buttons & BT_CUSTOM1
and (mo.state ~= S_PLAY_LAUGH)
and not player.powers[pw_nocontrol]
	mo.laughtimer = 70
	player.powers[pw_nocontrol] = 2*TICRATE
	mo.state = S_PLAY_LAUGH
	S_StartSound(player.mo, sfx_exelgh)
end

if mo.state == S_PLAY_LAUGH
	mo.laughtimer = $-1
	if mo.laughtimer == 0
		mo.state = S_PLAY_STND
	end
end

//laugh when opening egg capsule

if player.laugh == nil then player.laugh = false end
if (player.exiting
	or player.finished)
	and gametype == GT_COOP
	and mapheaderinfo[gamemap].bonustype == 1
		if not player.laugh
			mo.laughtimer = 70
			mo.state = S_PLAY_LAUGH
			S_StartSound(player.mo, sfx_exelgh)
			player.laugh = true
		end
else
	player.laugh = false
end

end
end)

//warpthok effects
local thokwarped = function(player)
S_StartSound(player.mo, sfx_thok)
player.mo.warpthoks = $ - 1
player.pflags = $ | PF_JUMPED & ~PF_SPINNING
player.mo.state = S_PLAY_JUMP
if player.mo.warpthoks == 0
player.pflags = $|PF_THOKKED
end
local aftimg = P_SpawnGhostMobj(player.mo)
aftimg.color = SKINCOLOR_CRIMSON
aftimg.colorized = true
aftimg.fuse = 15
end

//warpthok
addHook("AbilitySpecial", function(player)
	if player.mo.skin == "sonicexe" and player.valid
		local tpx = player.mo.x+FixedMul(260*FRACUNIT, cos(player.mo.angle))
		local tpy = player.mo.y+FixedMul(260*FRACUNIT, sin(player.mo.angle))
		if player.pflags & PF_THOKKED return end
	        if (player.cmd.forwardmove ~= 0 or player.cmd.sidemove ~= 0) 
			and not (P_BlockingWall(player.mo, player.mo.angle, 260*FRACUNIT))
				P_MoveOrigin(player.mo, tpx, tpy, player.mo.z)
				player.mo.momz = 0
                P_InstaThrust(player.mo, player.mo.angle, player.speed)
				thokwarped(player)
			end
            if (player.cmd.forwardmove == 0 and player.cmd.sidemove == 0)  
			if not (player.mo.eflags & MFE_VERTICALFLIP)
					P_SetOrigin(player.mo, player.mo.x, player.mo.y, player.mo.z + 120*FRACUNIT)
					P_SetObjectMomZ(player.mo, 15*FRACUNIT, false)
			else
					P_SetOrigin(player.mo, player.mo.x, player.mo.y, player.mo.z - 120*FRACUNIT)
					P_SetObjectMomZ(player.mo, 15*FRACUNIT, false)
			end
				P_InstaThrust(player.mo, player.mo.angle, player.speed)
				thokwarped(player)
			end
	end
end)

//red rings

addHook("MobjThinker", function(ring)
	if (ring.valid)
		local target = P_LookForPlayers(ring, 400*FRACUNIT, true)
		if (target) and (ring.target.skin == "sonicexe") and (ring.color ~= SKINCOLOR_CRIMSON)
			P_SpawnGhostMobj(ring)
			ring.color = SKINCOLOR_CRIMSON
		end
		if (ring.color == SKINCOLOR_CRIMSON) and (ring.sprite ~= SPR_TRNG)
			if (ring.sprite == SPR_SPRK)
				ring.colorized = true
				return
			else
				ring.sprite = SPR_TRNG
			end
		end
	end
end, MT_RING)

addHook("TouchSpecial", function(special, toucher)
if (maptol & TOL_NIGHTS)
   return
end 
if G_RingSlingerGametype() return end
if toucher.player and special.color == SKINCOLOR_CRIMSON
	if toucher.player.mo.skin == "sonicexe"
	    return
	else 
	   toucher.player.rings = $ - 2
	   return
    end
end
return
end, MT_RING)

local function redring(mobj)
local p = consoleplayer
if mobj.color == SKINCOLOR_CRIMSON
S_StartSound(p.mo, sfx_antiri,p)
S_StopSoundByID(p.mo, sfx_itemup)
return
end
end

addHook("MobjDeath", redring, MT_RING)
addHook("MobjDeath", redring, MT_FLINGRING)

addHook("MobjThinker", function(ring)
	if (ring.valid)
		local target = P_LookForPlayers(ring, 500*FRACUNIT, true)
		if (target) and (ring.target.skin == "sonicexe") and (ring.color ~= SKINCOLOR_CRIMSON)
			P_SpawnGhostMobj(ring)
			ring.color = SKINCOLOR_CRIMSON
		end
		if (ring.color == SKINCOLOR_CRIMSON) and (ring.sprite ~= SPR_TRNG)
			if (ring.sprite == SPR_SPRK)
				ring.colorized = true
				return
			else
				ring.sprite = SPR_TRNG
			end
		end
	end
end, MT_FLINGRING)

addHook("TouchSpecial", function(special, toucher)
if (maptol & TOL_NIGHTS)
   return
end 
if G_RingSlingerGametype() return end
if toucher.player and special.color == SKINCOLOR_CRIMSON
	if toucher.player.mo.skin == "sonicexe"
	    return
	else 
	   toucher.player.rings = $ - 2
	   return
    end
end
return
end, MT_FLINGRING)

//blood

sfxinfo[sfx_kill].caption = "Blood"

states[S_BLOD] =
{
	sprite = SPR_BLOD,
	frame = FF_ANIMATE,
	tics = 15,
	var1 = 3,
	var2 = 4,
	nextstate = S_NULL
}


addHook("MobjDeath", function(target, inflictor, source)
    if source
    and source.player
	and source.skin == "sonicexe"
    and (target.flags & MF_ENEMY)
	and target and target.valid
	if target.type == MT_BIGMINE return end
	S_StartSound(source, sfx_kill)
	if (cv_exescream.value)
	S_StartSound(source, sfx_scream)
	end
	P_AddPlayerScore(source.player, 100 + source.player.scoreadd*100)
	source.player.scoreadd = $ + 1
	local blood = P_SpawnMobj(target.x, target.y, target.z - 20*FRACUNIT, MT_GHOST)
	blood.state = S_BLOD
	blood.scale = target.scale + 3/2*FRACUNIT
	target.state = S_NULL
	return true
	end
end)

//fang explodes when he dies because he's a nerd

addHook("BossThinker", function(target)
if target.health <=0
local player = consoleplayer
if player.mo.skin ~= "sonicexe" return end
S_StartSound(player.mo, sfx_kill)
S_StartSound(player.mo, sfx_scream)
for p in players.iterate()
P_DoPlayerExit(p)
end
local blood = P_SpawnMobj(target.x, target.y, target.z - 20*FRACUNIT, MT_GHOST)
blood.state = S_BLOD
blood.scale = target.scale + 3/2*FRACUNIT
target.state = S_NULL
end
end, MT_FANG)

//metal sonic explodes when he dies because he's also a nerd

addHook("BossDeath", function(target)
local player = consoleplayer
if player.mo.skin ~= "sonicexe" return end
S_StartSound (player.mo, sfx_bkpoof)
for p in players.iterate()
P_FlashPal(p, PAL_NUKE, 10)
end
local explode = P_SpawnMobj(target.x, target.y, target.z - 20*FRACUNIT, MT_BOSSEXPLODE)
explode.scale = target.scale + 3*FRACUNIT
target.state = S_NULL
end, MT_METALSONIC_BATTLE)

//kill flicky
addHook("ThinkFrame", function()
if mapheaderinfo[gamemap].bonustype ~= 1
return end
for mo in mobjs.iterate()
if (mo.type == MT_FLICKY_01 
or mo.type == MT_FLICKY_02 
or mo.type == MT_FLICKY_03 
or mo.type == MT_FLICKY_04 
or mo.type == MT_FLICKY_05 
or mo.type == MT_FLICKY_06 
or mo.type == MT_FLICKY_07 
or mo.type == MT_FLICKY_08 
or mo.type == MT_FLICKY_09 
or mo.type == MT_FLICKY_10 
or mo.type == MT_FLICKY_11 
or mo.type == MT_FLICKY_12 
or mo.type == MT_FLICKY_13 
or mo.type == MT_FLICKY_14 
or mo.type == MT_FLICKY_15
or mo.type == MT_FLICKY_16 
or mo.type == MT_SECRETFLICKY_01 
or mo.type == MT_SECRETFLICKY_02)
local player = consoleplayer
S_StartSound(player.mo, sfx_egckil)
if player.mo.skin ~= "sonicexe"
return end
local blood = P_SpawnMobj(mo.x, mo.y, mo.z - 40*FRACUNIT, MT_GHOST)
blood.state = S_BLOD
blood.scale = mo.scale + 3/2*FRACUNIT
mo.state = S_NULL
end
end
end)
//ik this is unoptomized but its only for boss levels i dont think it matters that much

//EXPLODE if hit while laughing cuz why not lmao
//toggeable cuz it can get annoying, on by defautl tho hehehe

addHook("MobjDamage", function(target, inf, src, dmg, dmgType)
if not (cv_laughdeath.value) 
if target.state == S_PLAY_LAUGH
target.player.powers[pw_nocontrol] = 0
S_StopSoundByID(target, sfx_exelgh)
end
return end
	if target.skin == "sonicexe"
	and target.state == S_PLAY_LAUGH
	local blood = P_SpawnMobj(target.x, target.y, target.z- 20*FRACUNIT, MT_GHOST)
    blood.state = S_BLOD
    blood.scale = target.scale + 3/2*FRACUNIT
	S_StartSound(target, sfx_kill)
	S_StartSound(target, sfx_scream)
	P_StartQuake(15*FRACUNIT, TICRATE/3)
    P_KillMobj(target)
	target.state = S_INVISIBLE
	return true
	end
end, MT_PLAYER)

//OTHER STUFF
-- P_BlockingWall: returns whether there's a wall blocking an object from moving a specified angle/distance
-- (NOTE: does NOT return WHICH wall was hit!)
-- You can put this in a separate script lump if you want! Call it from anywhere with P_BlockingWall(mobj, angle, maxdist)
local tester = nil -- Misc object used for testing
rawset(_G, "P_BlockingWall", function(mobj, angle, maxdist)
    if not (tester and tester.valid) then -- Spawn a new one!
        tester = P_SpawnMobj(mobj.x, mobj.y, mobj.z, MT_GARGOYLE) -- Has the best chance of not getting cleared on spawn
        tester.flags = MF_NOGRAVITY|MF_NOCLIPTHING -- Make sure it won't move or block things
        tester.flags2 = $1|MF2_DONTDRAW -- Don't see the gargoyle! (can be commented out for testing if needed)
    end
    
    -- Match tester size to mobj size
    tester.radius = mobj.radius
    tester.height = mobj.height
    
    P_MoveOrigin(tester, mobj.x, mobj.y, mobj.z) -- Move tester to mobj's position
    P_InstaThrust(tester, angle, maxdist) -- Misuse P_InstaThrust to easily get the distance to move
    return not P_TryMove(tester, tester.x+tester.momx, tester.y+tester.momy, true) -- Return the inverse of P_TryMove (since it returns true on successful move, false on blocked)
end)

if not kirbyabilitytable rawset(_G, "kirbyabilitytable", {}) end
kirbyabilitytable["sonicexe"] = 8

if not PlayerAnimInfo
	rawset(_G, "PlayerAnimInfo", {})
end

PlayerAnimInfo["sonicexe"] = {
		["runFrames"] = {-1},
		["dashFrames"] = {-1},
		["walkFrames"] = {3, 7},
		["waitFrames"] = {-1},
		["milnekickFrames"] = {0, 4},
		["run"] = false,
		["dash"] = false,
		["walk"] = true,
		["idle"] = true,
		["wait"] = false,
		["milnekick"] = true,
		["superRun"] = false,
		["superDash"] = false,
		["superWalk"] = true,
		["superIdle"] = true,
		["superWait"] = false
}
