// EVERY ABLILITIES FROM BANJO-KAZZOIE

// CROUCH
freeslot("S_PLAY_CROUCH", "S_PLAY_UNCROUCH")

states[S_PLAY_CROUCH] = {
	sprite = SPR_PLAY,
	frame = A|SPR2_LAND,
	var1 = 0,
	var2 = 0,
	tics = 2,
	nextstate = S_PLAY_UNCROUCH
}

states[S_PLAY_UNCROUCH] = {
	sprite = SPR_PLAY,
	frame = FF_SPR2ENDSTATE|SPR2_LAND,
	var1 = S_PLAY_STND,
	var2 = 0,
	tics = 2,
	nextstate = S_PLAY_UNCROUCH
}

addHook("PlayerThink", function(player)
	if (player.mo and player.mo.valid) and not (player.mo.eflags & MFE_UNDERWATER) and player.mo.skin == "banjokazzoie"  then
		local mo = player.mo
		local cmd = player.cmd

		if player.thecrouch == nil then
			player.thecrouch = {}
		end

		local ya_crouch = player.thecrouch

		if ya_crouch.state == nil then
			ya_crouch.state = 0
			ya_crouch.stasisdelay = 0

		elseif ya_crouch.state == 1 then -- Crouching
			if mo.state ~= S_PLAY_CROUCH then
				ya_crouch.state = 3
				player.height = P_GetPlayerHeight(player)
				return
			else
				if not (player.cmd.buttons & BT_CUSTOM1) then
					ya_crouch.state = 2
					mo.state = S_PLAY_UNCROUCH
					ya_crouch.stasisdelay = 8
					player.height = P_GetPlayerHeight(player)
				else
					player.height = P_GetPlayerSpinHeight(player)
					mo.state = S_PLAY_CROUCH
					mo.frame = $|0 & ~FF_FRAMEMASK
					player.pflags = $ | PF_STASIS
				end
			end
			player.drawangle = player.mo.angle
		elseif ya_crouch.state == 2 then -- Uncrouching
			player.pflags = $ & ~PF_STASIS
			mo.height = P_GetPlayerHeight(player)
			player.height = skins[mo.skin].height

			if (cmd.forwardmove ~= 0 or cmd.sidemove ~= 0) and ya_crouch.stasisdelay < 4 or mo.state ~= S_PLAY_UNCROUCH then
				ya_crouch.state = 0

				if mo.state == S_PLAY_UNCROUCH then
					P_Thrust(mo, cmd.angleturn << 16 + R_PointToAngle2(0, 0, cmd.forwardmove*FRACUNIT, cmd.sidemove*FRACUNIT), FixedMul(player.accelstart, player.thrustfactor))
					mo.state = S_PLAY_WALK
					player.pflags = $ & ~PF_STASIS
				end
			end

		elseif ya_crouch.state == 3 then -- Reset
			mo.height = P_GetPlayerHeight(player)
			player.height = skins[mo.skin].height
			ya_crouch.state = 0
		end

		ya_crouch.stasisdelay = max($ - 1, 0)
	end
end)

addHook("PlayerThink", function(player)
	if (player.mo and player.mo.valid) and not (player.mo.eflags & MFE_UNDERWATER) and (player.cmd.buttons & BT_CUSTOM1) and player.mo.skin == "banjokazzoie"  then
		local mo = player.mo
		
		if player.thecrouch ~= nil and player.thecrouch.state then
			return false
		end

		if P_IsObjectOnGround(mo)
			and not (player.pflags & PF_SPINNING)
			and not (player.powers[pw_carry])
			and (player.speed <= 5*FRACUNIT and 1 == 0 or 1 == 1)
			and player.charability2 == skins[player.mo.skin].ability2
			and player.thecrouch ~= nil
			and (player.thecrouch.state == 0 or player.thecrouch.state == 2 and not player.thecrouch.stasisdelay)
		then
			mo.state = S_PLAY_CROUCH
			player.thecrouch.state = 1
			player.pflags = $ | PF_STASIS
			return true
		end
	end
end)

-- Fix player height for crouching/spin gaps
addHook("PlayerHeight", function(player)
	if player.mo and player.blackout and player.mo.valid then
		if player.thecrouch ~= nil and player.thecrouch.state >= 2
			or player.mo.state == S_PLAY_UNCROUCH
		then
			return P_GetPlayerHeight(player)
		elseif player.thecrouch ~= nil and player.thecrouch.state == 1
			or player.mo.state == S_PLAY_CROUCH
		then
			return P_GetPlayerSpinHeight(player)
		end
	end
end)

//STOMP
addHook("PlayerThink", function(player)
	if (player.mo and player.mo.valid) and not (player.mo.eflags & MFE_UNDERWATER) and player.custom1tic == 1 and player.control and player.mo.skin == "banjokazzoie"  then
		
		local mo = player.mo

		if player.thecrouch ~= nil and player.thecrouch.state then
			return false
		end

		if not P_IsObjectOnGround(mo)
			and not (player.pflags & PF_SPINNING)
			and not (player.powers[pw_carry])
			and player.charability2 == skins[player.mo.skin].ability2
			and player.thecrouch ~= nil
			and (player.thecrouch.state == 0 or player.thecrouch.state == 2 and not player.thecrouch.stasisdelay)
		then
			mo.state = S_PLAY_FALL
			player.powers[pw_strong] = STR_ANIM|STR_FLOOR|STR_BOUNCE|STR_SPRING
			P_SetObjectMomZ(player.mo,-15*FRACUNIT)
			player.powers[pw_tailsfly] = 0
			player.mo.momy = 0
			player.mo.momx = 0
			return true
		end
	end
end)

//FLY
addHook("PlayerThink", function(player)
	if (player.mo and player.mo.valid) and not (player.mo.eflags & MFE_UNDERWATER) and player.mo.skin == "banjokazzoie"  then
		if player.redplumes == nil then
			player.redplumes = 0
		end
		
		if player.redplumes > 0 then
			player.charability = CA_FLY
			player.actionspd = 200*FRACUNIT
		else
			player.charability = CA_DOUBLEJUMP
			player.actionspd = 50
		end
		
		if player.powers[pw_tailsfly] then
			player.powers[pw_tailsfly] = 200
			
			if player.custom3tic == 1 and player.control then
				P_InstaThrust(player.mo, player.drawangle, player.speed + 20*FRACUNIT)
				player.redplumes = max($ - 5, 0)
			else
				player.redplumes = max($ - 1, 0)
			end
		end
	end
end)

//INVINCIBLE
addHook("PlayerThink", function(player)
	if (player.mo and player.mo.valid) and not (player.mo.eflags & MFE_UNDERWATER) and player.custom3tic == 1 and player.control and (player.thecrouch ~= nil and player.thecrouch.state == 1) and player.mo.skin == "banjokazzoie" then
		if player.goldplumes == nil then
			player.goldplumes = 0
		end
		
		if player.goldplumes >= 1 and player.powers[pw_invulnerability] == 0 then
			player.goldplumes = $ - 1
			player.powers[pw_invulnerability] = 10*TICRATE
			S_ChangeMusic("GOLD", false, player)
		end
	end
end)

//SHOOT
freeslot("sfx_shegg", "MT_FIREEGG", "MT_ICEEGG", "MT_BOMBEGG", "S_ICEEGG", "S_NOANIM")

states[S_ICEEGG] = {
	sprite = SPR_ROII,
	frame = A,
	var1 = 0,
	var2 = 0,
	tics = -1,
	nextstate = S_ICEEGG
}

//elemental eggs
rawset(_G, "eggshoot", {
[1] = "normal",
[2] = "fire",
[3] = "ice",
[4] = "bomb"})


addHook("PlayerThink", function(player)
	if player.mo.shootaim == nil then
		player.mo.shootaim = 0
	end
	if player.mo.projangle == nil then
		player.mo.projangle = 0
	end
	if player.neggs == nil then
		player.neggs = 0
	end
	if player.feggs == nil then
		player.feggs = 0
	end
	if player.ieggs == nil then
		player.ieggs = 0
	end
	if player.beggs == nil then
		player.beggs = 0
	end
	if player.eggstoshoot == nil then
		player.eggstoshoot = 1
	end
	
	if player.mo and player.mo.skin == "banjokazzoie"
	and ((player.custom2tic == 1) or ((player.cmd.buttons & BT_CUSTOM2) and (eggshoot[player.eggstoshoot] == "ice")))
	and player.control
	and ((player.neggs > 0 and eggshoot[player.eggstoshoot] == "normal")
	or (player.feggs > 0 and eggshoot[player.eggstoshoot] == "fire")
	or (player.ieggs > 0 and eggshoot[player.eggstoshoot] == "ice")
	or (player.beggs > 0 and eggshoot[player.eggstoshoot] == "bomb"))
	and ((player.thecrouch ~= nil and player.thecrouch.state == 1) or (camera.chase == false and player == displayplayer)) then
	
		local eggmobjnum = 0
	
		if eggshoot[player.eggstoshoot] == "normal" then
			eggmobjnum = MT_REDRING
		end
		
		if eggshoot[player.eggstoshoot] == "fire" then
			eggmobjnum = MT_FIREEGG
		end
		
		if eggshoot[player.eggstoshoot] == "ice" then
			eggmobjnum = MT_ICEEGG
		end
		
		if eggshoot[player.eggstoshoot] == "bomb" then
			eggmobjnum = MT_BOMBEGG
		end
	
		if eggmobjnum ~= 0 then
			if camera.chase == false then
				local firez = player.mo.z + (player.mo.height/2)
				local bullet = P_SpawnPointMissile(
					player.mo,
					player.mo.x + P_ReturnThrustX(nil, player.mo.angle, player.mo.scale*8),
					player.mo.y + P_ReturnThrustY(nil, player.mo.angle, player.mo.scale*8),
					firez + (sin(player.aiming)*90),
					eggmobjnum,
					player.mo.x, player.mo.y, firez
				)
			
				if bullet and bullet.valid
					P_InstaThrust(bullet, bullet.angle, 100*cos(player.aiming))
					bullet.momz =  100*sin(player.aiming)
				end
				player.drawangle = player.mo.angle
				player.weapondelay = 5
			else
				player.mo.momy = 0
				player.mo.momx = 0
				P_SPMAngle(player.mo, eggmobjnum, player.mo.angle, 0)
			end
			S_StartSound(player.mo, sfx_shegg)
			
			if eggshoot[player.eggstoshoot] == "normal" then player.neggs = $ - 1 end
			if eggshoot[player.eggstoshoot] == "fire" then player.feggs = $ - 1 end
			if eggshoot[player.eggstoshoot] == "ice" then player.ieggs = $ - 1 end
			if eggshoot[player.eggstoshoot] == "bomb" then player.beggs = $ - 1 end
		end
		
	end
	
		player.neggs = min(player.neggs, 100)
		player.neggs = max(player.neggs, 0)
		
		player.feggs = min(player.feggs, 50)
		player.feggs = max(player.feggs, 0)
		
		player.ieggs = min(player.ieggs, 800)
		player.ieggs = max(player.ieggs, 0)
		
		player.beggs = min(player.beggs, 20)
		player.beggs = max(player.beggs, 0)
	
	if player.mo and player.mo.skin == "banjokazzoie"
	and player.custom3tic == 1
	and (player.thecrouch ~= nil and player.thecrouch.state ~= 1) and not player.powers[pw_tailsfly] then
		if player.eggstoshoot < 4 then
		player.eggstoshoot = $ + 1
		else
		player.eggstoshoot = 1
		end
	end
end)

addHook("MobjDeath", function(mobj, inf, src, dmgtype)
	P_NukeEnemies(mobj, mobj, 500*FRACUNIT)
	S_StartSound(mobj, sfx_bkpoof)
end, MT_BOMBEGG)

addHook("HUD", function(v, player, cam)
	if player.mo and player.mo.skin == "banjokazzoie" and eggshoot and player.eggstoshoot then
		local xpos = hudinfo[HUD_RINGS].x
		local ypos = hudinfo[HUD_RINGS].y
		if eggshoot[player.eggstoshoot] == "normal" then
			v.drawString(xpos, ypos + 50, ("EGGS: " + (player.neggs) + " / 100"), V_PERPLAYER|V_SKYMAP)
		end
		if eggshoot[player.eggstoshoot] == "fire" then
			v.drawString(xpos, ypos + 50, ("FIRE EGGS: " + (player.feggs) + " / 50"), V_PERPLAYER|V_ORANGEMAP)
		end
		if eggshoot[player.eggstoshoot] == "ice" then
			v.drawString(xpos, ypos + 50, ("ICE EGGS: " + (player.ieggs) + " / 800"), V_PERPLAYER|V_AQUAMAP)
		
		end
		if eggshoot[player.eggstoshoot] == "bomb" then
			v.drawString(xpos, ypos + 50, ("BOMB EGGS: " + (player.beggs) + " / 20"), V_PERPLAYER|V_GRAYMAP)
		end
	end
end, "game")

for i = 1, #mobjinfo-1 do
	
	if mobjinfo[i].damage_hooks_added then
	mobjinfo[i].damage_hooks_added = true
	continue
	end
	
	addHook("MobjThinker", function(mo)
			if mo and mo.valid and mo.frezze and mo.frezze == true and mo.health then
				mo.momx = 0
				mo.momy = 0
				mo.momz = 0
				mo.sprite = states[mobjinfo[mo.type].spawnstate].sprite
				mo.target = nil
				mo.color = SKINCOLOR_ICY
				mo.colorized = true
			end
			
			if mo.target and mo.target.frezze then
				P_RemoveMobj(mo)
			end
	end, i)
	
	addHook("MobjDamage", function(target, inflictor, source, damage, damagetype)
		if not target then return false end
		if target.fuse then return false end
		
		if inflictor.type == MT_ICEEGG and (target.flags & MF_ENEMY) then
			target.frezze = true
			return true
		end
		if inflictor.type == MT_ICEEGG and (target.flags & MF_BOSS) then
			return true
		end
		
		return false
	end, i)
	
	mobjinfo[i].damage_hooks_added = true
end

addHook("ShouldDamage", function(target, inflictor, source, damage, damagetype)
	if not target and target.player then return true end
	
	if inflictor.frezze == true and (inflictor.flags & MF_ENEMY) then
		P_KillMobj(inflictor)
		return false
	elseif inflictor.frezze == true then
		return false
	end
	
end, MT_PLAYER)

//PUNCH
freeslot("S_PLAY_NPUNCH")

states[S_PLAY_NPUNCH] = {
	sprite = SPR_PLAY,
	frame = SPR2_WALK|C,
	tics = 10,
	nextstate = S_PLAY_STND
}

freeslot("S_PLAY_DPUNCH")

states[S_PLAY_DPUNCH] = {
	sprite = SPR_PLAY,
	frame = SPR2_ROLL|C,
	tics = 15,
	nextstate = S_PLAY_CROUCH
}

freeslot("S_PLAY_APUNCH")

states[S_PLAY_APUNCH] = {
	sprite = SPR_PLAY,
	frame = SPR2_RUN|C,
	tics = 15,
	nextstate = S_PLAY_FALL
}

addHook("PlayerThink", function(player)
	if (player.mo and player.mo.valid) and not (player.mo.eflags & MFE_UNDERWATER) and player.thecrouch ~= nil and player.spintic == 1 and player.control and player.mo.skin == "banjokazzoie" then
		if player.thecrouch.state == 1 then
			if player.mo.momx == 0 and player.mo.momy == 0 then
			P_Thrust(player.mo, player.mo.angle, 100*FRACUNIT)
			player.mo.state = S_PLAY_DPUNCH
			player.powers[pw_strong] = STR_ANIM|STR_WALL|STR_PUNCH
			end
		elseif P_IsObjectOnGround(player.mo) == true and player.speed < FRACUNIT then
			player.mo.momz = 2*player.mo.scale*P_MobjFlip(player.mo)
			P_Thrust(player.mo, player.mo.angle, 50*FRACUNIT)
			player.mo.state = S_PLAY_NPUNCH
			player.powers[pw_strong] = STR_ANIM|STR_WALL|STR_PUNCH
			player.pflags = $|PF_STASIS
		elseif P_IsObjectOnGround(player.mo) == true and player.speed > FRACUNIT then
			P_InstaThrust(player.mo, player.mo.angle, 50*FRACUNIT)
			player.mo.state = S_PLAY_ROLL
			player.pflags = $|PF_SPINNING
		elseif P_IsObjectOnGround(player.mo) == false and not player.powers[pw_tailsfly] and not (player.pflags & PF_THOKKED) then
			P_Thrust(player.mo, player.mo.angle, 25*FRACUNIT)
			player.mo.state = S_PLAY_APUNCH
			player.powers[pw_strong] = STR_ANIM|STR_WALL|STR_PUNCH|STR_UPPER
			player.mo.momz = 0
			player.pflags = $|PF_THOKKED
		end
		
		if player.mo.tics < 5 and player.mo.state == S_PLAY_NPUNCH then
			player.pflags = $ & ~PF_STASIS
		end
	end
end)

//LEDGE GRAB
freeslot("s_play_ledge_grab", "s_play_ledge_release", "spr2_lgrb", "spr2_lrls")
spr2defaults[SPR2_LGRB] = SPR2_RIDE -- Ledge grab.
spr2defaults[SPR2_LRLS] = SPR2_FALL -- Ledge release.

function A_PlayerRidePAnim(mo, var1, var2)
	local player = mo.player
	if not (player) then return end
	
	player.panim = PA_RIDE
end
function A_PlayerFallPAnim(mo, var1, var2)
	local player = mo.player
	if not (player) then return end
	
	player.panim = PA_FALL
end
states[S_PLAY_LEDGE_GRAB] = {SPR_PLAY, SPR2_LGRB|FF_ANIMATE, 7, A_PlayerRidePAnim, 0, 4, S_PLAY_LEDGE_RELEASE}
states[S_PLAY_LEDGE_RELEASE] = {SPR_PLAY, SPR2_LRLS|FF_ANIMATE, TICRATE/2, A_PlayerFallPAnim, 0, 2, S_PLAY_FALL}

addHook("MobjMoveBlocked", function(pmo)
	if not (pmo and pmo.valid) then return end
	if (P_IsObjectOnGround(pmo) or pmo.eflags & MFE_JUSTHITFLOOR) then return end
	if not pmo.skin == "banjokazzoie" then return end
	if (pmo.eflags & MFE_UNDERWATER) then return end
	
	local player = pmo.player
	if not (player and player.valid) then return end
	if P_PlayerInPain(player) then return end
	if player.powers[pw_carry] then return end
	
	if (player.isjettysyn or player.iseggrobo or player.revenge or player.actionstate) then return end -- BattleMod
	
	player.lastlinehit = -1
	player.lastsidehit = -1
	P_SlideMove(pmo)
	player.powers[pw_pushing] = 3
	
	local line = lines[player.lastlinehit]
	if (line and line.valid) and pmo.skin == "banjokazzoie" then
		if (line.flags & ML_IMPASSIBLE) then return end -- Self explanatory. Nothing can pass it, so don't bother.
		
		local wallangle = R_PointToAngle2(pmo.x, pmo.y, P_ClosestPointOnLine(pmo.x, pmo.y, line))
		local gravitydirection = P_MobjFlip(pmo)
		local playerradius = FixedMul(skins[player.skin].radius, pmo.scale)
		local playerheight = FixedMul(skins[player.skin].height, pmo.scale)
		local playerspinheight = FixedMul(skins[player.skin].spinheight, pmo.scale)
		local playergrabrange = pmo.scale*60
		local playergrabz
		if (gravitydirection > 0) then
			playergrabz = pmo.z + pmo.height - (pmo.scale*10)
		elseif (gravitydirection < 0) then
			playergrabz = pmo.z + (pmo.scale*10)
		else
			return
		end
		
		local linex, liney = P_ClosestPointOnLine(pmo.x, pmo.y, line)
		local ledgex = linex + P_ReturnThrustX(nil, wallangle, playerradius)
		local ledgey = liney + P_ReturnThrustY(nil, wallangle, playerradius)
		local ledgez = pmo.z
		local ledgeheight = pmo.scale*4
		
		local floorz, ceilingz
		if (gravitydirection > 0) then -- Normal gravity.
			floorz = P_FloorzAtPos(ledgex, ledgey, ledgez, playerheight + ledgeheight)
			ceilingz = P_CeilingzAtPos(ledgex, ledgey, ledgez, playerheight + ledgeheight)
		elseif (gravitydirection < 0) then -- Reverse gravity.
			floorz = P_FloorzAtPos(ledgex, ledgey, ledgez, -ledgeheight)
			ceilingz = P_CeilingzAtPos(ledgex, ledgey, ledgez, -ledgeheight)
		else
			-- Failsafe.
			return
		end
		
		if (floorz == ceilingz) then
			-- This is a wall. Don't even DARE grabbing onto it.
			return
		end
		
		if (abs(floorz - ceilingz) <= playerspinheight) -- Can't fit in this space, it's too small!
		or (gravitydirection > 0) and (abs(pmo.z + pmo.height + playerheight) >= pmo.ceilingz) -- We're gonna bonk our head against the ceiling!
		or (gravitydirection < 0) and (abs(pmo.z - playerheight) <= pmo.floorz) -- Same case with ceiling, only in reverse gravity.
		or (gravitydirection == 0) -- Failsafe.
		then
			-- If conditions are met...
			-- ... Then don't bother.
			return
		end
		
		local ledgegrabrange, ledgegrabz
		if (gravitydirection > 0) then -- Normal gravity.
			ledgegrabrange = (playergrabz < floorz) and (playergrabz > (floorz - playergrabrange)) -- Z range where we can grab the ledge.
			ledgegrabz = floorz - playerheight -- Z position we'll snap to when grabbing the ledge.
		elseif (gravitydirection < 0) then -- Inverse gravity.
			ledgegrabrange = (playergrabz > ceilingz) and (playergrabz < (ceilingz + playergrabrange))
			ledgegrabz = ceilingz
		end
		
		if (ledgegrabrange and ledgegrabz)
		and ((pmo.momz*gravitydirection) < (pmo.scale*2))
		and not (player.cmd.buttons & BT_SPIN)
		and not (pmo.state == S_PLAY_LEDGE_RELEASE)
		then
			-- Let's grab onto the ledge if we found one!
			if (pmo.state != S_PLAY_LEDGE_GRAB) then
				S_StartSound(pmo, sfx_s3k4a)
				pmo.state = S_PLAY_LEDGE_GRAB
				P_ResetPlayer(player)
				if (player.actionstate) then player.actionstate = 0 end -- BattleMod.
				if (player.guard) then player.guard = 0 end -- BattleMod.
				if (player.airdodge) then player.airdodge = 0 end -- BattleMod.
			end
			if (pmo.state == S_PLAY_LEDGE_GRAB) then
				pmo.z = ledgegrabz
				pmo.tics = 7 -- Necessary to keep this state from switching to the release state.
				P_InstaThrust(pmo, wallangle, playerradius/4)
				pmo.momz = max(-pmo.scale*2, min($, pmo.scale*2))
				player.drawangle = wallangle
				
				if (player.cmd.buttons & BT_JUMP) then
					-- Execute a jump when we want to get up the ledge.
					P_DoJump(player, true)
				end
			end
			return true
		end
	end
end, MT_PLAYER)

//NOAIRWALK
addHook("PlayerThink", function(player)
	if player.mo and player.mo.skin == "banjokazzoie"
	and not P_IsObjectOnGround(player.mo)
	and not (player.pflags & PF_JUMPED)
		if player.mo.state == S_PLAY_WALK
		or player.mo.state == S_PLAY_RUN
		or player.mo.state == S_PLAY_DASH
		or player.mo.state == S_PLAY_STND
		or player.mo.state == S_PLAY_WAIT
			player.mo.state = S_PLAY_FALL
			player.pflags = $|PF_JUMPED
			player.pflags = $ & ~PF_THOKKED
		end
	end
end)