// --------------------------------------
// Lua_Kirby
// Kirby's main abilities
// Yep, definetly Kirby!
// --------------------------------------
local kirbydebug = true
local deathtimer = TICRATE

local duckbutton = BT_CUSTOM2
local tossbutton = BT_TOSSFLAG

rawset(_G, "AF_NOFLOAT", 1)		// Disable floating
rawset(_G, "AF_NODUCK", 2)		// Disable ducking
rawset(_G, "AF_NOJUMP", 4)		// Disable jumping
rawset(_G, "AF_NOSWIM", 8)		// Disable swimming
rawset(_G, "AF_NODROP", 16)		// Disable dropping the ability
rawset(_G, "AF_NOLOSE", 32)		// Disable losing this ability from damage or death
rawset(_G, "AF_ALLOWSUCK", 64)	// Allows Kirby to suck
rawset(_G, "AF_WALKSWIM", 128)	// Use walking for swimming always

// Character abilities!
local characterabilities = {}
characterabilities["metaknight"] = K_MetaKnight

local duckheights = {}
duckheights["metaknight"] = FRACUNIT*16/24

local slideheights = {}
slideheights["metaknight"] = FRACUNIT*22/24

// Dash sparkles
local sparklecolors = {}
sparklecolors[0] = SKINCOLOR_PINK
sparklecolors[1] = SKINCOLOR_MOSS
sparklecolors[2] = SKINCOLOR_AZURE

local function RandomOff(num)
	return (P_RandomByte()%num)-(num/2)
end
local function SpawnSparkles(mobj, spriteheight, star)
	local stype = MT_BALLSPARKLE
//	if(star)
//		stype = MT_KIRBY_STARPARTICLE
//	end
	local zoff = 8*mobj.scale*P_MobjFlip(mobj)
	local sparkle = P_SpawnMobj(mobj.x+(RandomOff(30)*mobj.scale), mobj.y+(RandomOff(30)*mobj.scale), mobj.z+(mobj.height/2)+(RandomOff(spriteheight)*mobj.scale)+zoff, stype)
	sparkle.scale = mobj.scale
	if(mobj.eflags & MFE_VERTICALFLIP)
		sparkle.eflags = $1|MFE_VERTICALFLIP
	end
	if(star)
		if(P_RandomByte()&1)					// 50%
			sparkle.color = SKINCOLOR_GOLD
		elseif(P_RandomByte()&1)				// 25%
			sparkle.color = SKINCOLOR_SUNSET
		else									// 25%
			sparkle.color = SKINCOLOR_YELLOW
		end
	else
		sparkle.color = sparklecolors[P_RandomByte()%3]
	end
end

local function resetKirbyVars(player)
	if(player.mkvars == nil)
		player.mkvars = {}
	end
	player.mkvars.prevcmd = player.cmd.buttons
	player.mkvars.ducking = false
	player.mkvars.usedjumps = 0
	player.mkvars.ablstate = 0
	player.mkvars.ablvar1 = 0
	player.mkvars.ablvar2 = 0
	player.mkvars.ablvar3 = 0
	player.mkvars.ablcharge = false
	player.mkvars.deathtimer = 0
	player.mkvars.fastswimtime = 0
	player.mkvars.prevfoward = player.cmd.forwardmove
	player.mkvars.prevground = true
	player.mkvars.viewztween = 0
	player.mkvars.prevsuper = 0
	player.mkvars.prevcarry = player.powers[pw_carry]
	
	// RNG when getting an ability
	player.mkvars.abilityrandom = P_RandomByte()
	
	player.mkvars.industdevil = false
	player.mkvars.inmud = false
	player.mkvars.squish = false
end
local CheckMud

local function buttonPressed(player, button)
	if(player.cmd.buttons & button)
		and not(player.mkvars.prevcmd & button)
		return true
	end
	return false
end

local function lockControl(player)
	if(player.powers[pw_nocontrol] <= 2)
		player.powers[pw_nocontrol] = 2
	end
	player.pflags = $1|PF_STASIS
end

local function callAbilityFunc(player, forvalue, destroy, late)
	if(player.powers[pw_carry] == CR_NIGHTSMODE)
		player.mkvars.ablstate = 0
		player.mkvars.ablvar1 = 0
		player.mkvars.ablvar2 = 0
		player.mkvars.ablvar3 = 0
		player.mkvars.suckstate = 0
		return 0
	end
	if(player.mo and player.mo.valid)
		if(characterabilities[player.mo.skin])
			return characterabilities[player.mo.skin](forvalue, destroy, late, player)
		end
	end
	return 0
end

rawset(_G, "K_IsDirChar", function(player)
	if not(player.pflags & PF_DIRECTIONCHAR)
		or(player.climbing) // stuff where the direction is forced at all times
		or((player.pflags & PF_ANALOGMODE) or twodlevel or (player.mo.flags2 & MF2_TWOD)) // keep things synchronised up there, since the camera IS seperate from player motion when that happens
		or(G_RingSlingerGametype()) // no firing rings in directions your player isn't aiming
		return false
	end
	return true
end)

// Reset on spawn
addHook("PlayerSpawn", function(player)
	resetKirbyVars(player, true)
end)

addHook("PlayerThink", function(player)
	if(player.mo and player.mo.valid)
		and(characterabilities[player.mo.skin])
		if(player.powers[pw_super])
			or(player.mo.state >= S_PLAY_NIGHTS_TRANS1 and player.mo.state <= S_PLAY_NIGHTS_TRANS5)
			player.mo.color = player.skincolor	// We don't want to super flash
		end
	end
end)

addHook("ThinkFrame", function()
	for player in players.iterate
		// Init Kirby's variables (actually already done, but eh)
		if(player.mkvars == nil)
			or not(player.mo and player.mo.valid and(characterabilities[player.mo.skin]))
			resetKirbyVars(player, false)
		end
		
		// Not Kirby?
		if not(player.mo and player.mo.valid and(characterabilities[player.mo.skin]))
			continue
		end
		
		if(player.mo and player.mo.valid and player.mo.issucked)
			continue
		end
		
		// 2D variable
		local twod = false
		if(player.mo.flags2 & MF2_TWOD)
			or(twodlevel)
			or(player.mo.currentaxis)
			twod = true
		end
		
		if not(player.dashmode)
			player.normalspeed = skins[player.mo.skin].normalspeed
		end
		player.thrustfactor = skins[player.mo.skin].thrustfactor
		player.charflags = skins[player.mo.skin].flags
		
		// Important
		player.mkvars.inmud = CheckMud(player)	// Check mud
		
		// Meta Knight transformation
		if(player.mo.skin == "metaknight")
			if(player.mo.state == S_PLAY_NIGHTS_TRANS1)
				and(player.mo.anim_duration > 3)
				player.mo.anim_duration = 3
			elseif(player.mo.state == S_PLAY_NIGHTS_TRANS4)
				and(player.mo.frame&FF_FRAMEMASK == 5)
				local of = player.mo.frame
				player.mo.state = S_PLAY_NIGHTS_TRANS2
				player.mo.frame = of
			end
		end
		
		// --------------------------------
		// Death!
		// --------------------------------
		if(player.deadtimer)
			and(leveltime & 1)
			and(player.deadtimer <= 70*TICRATE/35)
			player.deadtimer = $1 - 1
		end
		if(player.mkvars.deathtimer)
			player.mkvars.deathtimer = $1 - 1
			player.mo.momx = 0
			player.mo.momy = 0
			// Copied directly from source, this will be 100% accurate!
			if(player.mo.movedir == DMG_DROWNED)
				P_SetObjectMomZ(player.mo, FRACUNIT / 2) // slower fall from drowning
			else
				P_SetObjectMomZ(player.mo, 2 * FRACUNIT / 3)
			end
			if not(player.mkvars.deathtimer)
				player.mo.state = S_KIRBY_DEATHTUMBLE
				S_StartSound(player.mo, sfx_kdiet)
				P_SetObjectMomZ(player.mo, 15*FRACUNIT)
				
				// Meta Knight's mask
				if(player.mo.skin == "metaknight")
					player.mo.flags2 = $1|MF2_DONTDRAW
					local mask = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z, MT_METAMASK)
					mask.scale = player.mo.scale
					mask.angle = player.drawangle	// Dumb :ohh_yeah:
					mask.color = player.mo.color	// Also dumb :ohh_yeah:
					mask.skin = player.mo.skin		// Not dumb, actually
					mask.state = mobjinfo[mask.type].spawnstate	// Thank you SRB2
					if(player.mo.eflags & MFE_VERTICALFLIP)
						mask.eflags = $1|MFE_VERTICALFLIP
						mask.flags2 = $1|MF2_OBJECTFLIP
					end
					P_SetObjectMomZ(mask, 13*FRACUNIT)
					// Sideways thrust
					P_InstaThrust(mask, (player.cmd.angleturn<<16)+ANGLE_90, 2*mask.scale)
					
					// Dissapearing effect
					local snum = 0
					while(snum < 16)
						SpawnSparkles(player.mo, 30, true)
						snum = $1 + 1
					end
				end
			end
		end
		
		// Can't do anything else if we're dead!
		if not(player.mo.health)
			continue
		end
		
		local oldbutt = player.cmd.buttons
		if(player.powers[pw_super])
			player.charflags = $1|SF_SUPER
		elseif(player.cmd.buttons & BT_TOSSFLAG)
			and(P_SuperReady(player))
			and not(player.mkvars.prevcmd & BT_USE)	// A
			and not(player.mkvars.ability)
			and not(characterabilities[player.mo.skin])
			player.cmd.buttons = $1 & !BT_USE	// Allow transform
			player.charflags = $1|SF_SUPER
		else
			player.charflags = $1 & !SF_SUPER
		end
		
		player.jumpfactor = skins[player.mo.skin].jumpfactor
		player.height = skins[player.mo.skin].height
		player.shieldscale = skins[player.mo.skin].shieldscale
		player.charability = skins[player.mo.skin].ability
		if(player.powers[pw_carry] == CR_NIGHTSMODE)
			player.mo.height = FixedMul(player.height, player.mo.scale)
		end
		if(player.mo.eflags & MFE_UNDERWATER)
			and not(starship)	// Starship can't swim
			and not(player.mkvars.inmud)
			player.jumpfactor = 0
		end
		
		// --------------------------------
		// Abilities!
		// --------------------------------
		if(characterabilities[player.mo.skin])
			callAbilityFunc(player, false, false, false)
		end
		
		// --------------------------------
		// Ducking!
		// --------------------------------
		if((((player.cmd.buttons & duckbutton) or (twod and player.cmd.forwardmove < -25 and not player.mo.currentaxis)) and not(player.exiting)) or player.mo.state == S_KIRBY_SLIDE)
			and(P_IsObjectOnGround(player.mo))
			and not(P_PlayerInPain(player))
			and not(callAbilityFunc(player, true, false, false) & AF_NODUCK)
			and not(player.powers[pw_carry] or player.mkvars.prevcarry)
			and not(player.mkvars.inmud)
			and not(player.mo.eflags & MFE_GOOWATER)
			player.jumpfactor = 0
			// Start ducking
			if not(player.mkvars.ducking)
				S_StartSound(player.mo, sfx_kflat)
			end
			player.mkvars.ducking = true
			if not(player.mo.state == S_KIRBY_SLIDE)
				player.mo.state = S_KIRBY_DUCK
			end
			
			// Can't move when ducking!
			if(player.mo.currentaxis)
				lockControl(player)
			end
			player.thrustfactor = 0
			
			// Sliding!
			if(buttonPressed(player, BT_JUMP))
				and not(player.mo.state == S_KIRBY_SLIDE)
				// Slide!
				P_Thrust(player.mo, player.drawangle, 20*player.mo.scale)
				player.mo.state = S_KIRBY_SLIDE
				player.mo.savedstate = S_KIRBY_SLIDE
				S_StartSound(player.mo, sfx_kbrake)
			end
			// End the slide
			if(R_PointToDist2(player.cmomx, player.cmomy, player.mo.momx, player.mo.momy) < 3*player.mo.scale)
				and(player.mo.state == S_KIRBY_SLIDE)
				player.mo.state = S_KIRBY_DUCK
			end
			// Hit things when sliding!
			if(player.mo.state == S_KIRBY_SLIDE)
				player.pflags = ($1|PF_JUMPED) & !PF_NOJUMPDAMAGE
			else
				player.pflags = ($1|PF_NOJUMPDAMAGE) & !PF_JUMPED
			end
			// Slide dust
			if(player.mo.state == S_KIRBY_SLIDE)
				if not(leveltime % 3)
					local particle = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z, MT_SPINDUST)
					particle.scale = player.mo.scale
					if(player.mo.eflags & MFE_VERTICALFLIP)
						P_TeleportMove(particle, player.mo.x, player.mo.y, player.mo.z+player.mo.height-particle.scale)
					end
					particle.momz = (((P_RandomByte()*player.mo.scale)/255)+player.mo.scale)*P_MobjFlip(player.mo)
					
					local rundir = R_PointToAngle2(player.cmomx, player.cmomy, player.mo.momx, player.mo.momy)+ANGLE_90
					P_InstaThrust(particle, rundir, (((P_RandomByte()*player.mo.scale*2)/255)-player.mo.scale)*16)
					
					local goodmomx = player.mo.momx-player.cmomx
					local goodmomy = player.mo.momy-player.cmomy
					particle.momx = $1 + player.cmomx + (goodmomx*2/3)
					particle.momy = $1 + player.cmomy + (goodmomy*2/3)
					
					P_TeleportMove(particle, particle.x-particle.momx, particle.y-particle.momy, particle.z+particle.momz)
					
					if(P_MobjFlip(player.mo) < 0)
						particle.eflags = $1|MFE_VERTICALFLIP
						particle.flags2 = $1|MF2_OBJECTFLIP
					end
					
					if(player.mo.eflags & MFE_UNDERWATER)
						or(player.mo.eflags & MFE_TOUCHWATER)
						particle.state = S_SPINDUST_BUBBLE1
					end
					
					particle.tics = 6
				end
				
				// Height!
				player.height = FixedMul($1, slideheights[player.mo.skin])
			else
				player.height = FixedMul($1, duckheights[player.mo.skin])
			end
		else
			if(player.mkvars.ducking)
				player.pflags = ($1|PF_NOJUMPDAMAGE) & !PF_JUMPED
				if(player.mo.state == S_KIRBY_DUCK)
					if(player.mo.momx-player.cmomx or player.mo.momy-player.cmomy)
						player.mo.state = S_PLAY_WALK
					else
						player.mo.state = S_PLAY_STND
					end
				end
			//	player.normalspeed = skins[player.mo.skin].normalspeed
				player.thrustfactor = skins[player.mo.skin].thrustfactor
			end
			player.mkvars.ducking = false
		end
		
		// Kinda useless at this point...
		if(callAbilityFunc(player, true, false, false) & AF_NOJUMP)
			player.jumpfactor = 0
		end
		
		// --------------------------------
		// Floating
		// --------------------------------
		// Reset floating time
		if(P_IsObjectOnGround(player.mo))
			or(player.powers[pw_carry] or player.mkvars.prevcarry)
			or(player.mo.eflags & (MFE_UNDERWATER|MFE_SPRUNG))
			or(player.mkvars.inmud)
			player.mkvars.usedjumps = 0
		end
		
		// --------------------------------
		// Swimming!
		// --------------------------------
		if(player.mo.eflags & MFE_UNDERWATER)
			and not(player.mkvars.inmud)			// Don't swim in mud!
			if not(player.mkvars.ducking)
				and not(P_PlayerInPain(player))
				and not(player.powers[pw_carry] or player.mkvars.prevcarry)
				and not(callAbilityFunc(player, true, false, false) & AF_NOSWIM)
				if(buttonPressed(player, BT_JUMP) or (((player.cmd.buttons & BT_JUMP) or (twod and player.cmd.forwardmove > 25)) and player.mkvars.fastswimtime <= 1))
					and not(player.pflags & PF_JUMPSTASIS)
					and not(player.powers[pw_nocontrol])
					and not(player.exiting)
					player.mkvars.fastswimtime = TICRATE*2/3
					player.mo.momz = 5*player.mo.scale*P_MobjFlip(player.mo)
				end
				
				// Animation
				if not(player.mo.state == S_PLAY_GASP)
					and not(P_IsObjectOnGround(player.mo))
					local swimanim = S_KIRBY_SWIM
					
					if(callAbilityFunc(player, true, false, false) & AF_WALKSWIM)
						swimanim = S_PLAY_WALK
					end
					
					if(player.mkvars.fastswimtime)
						player.mkvars.fastswimtime = $1 - 1
						if(player.mo.tics > 2)
							player.mo.tics = 2
						end
					end
					
					if not(player.mkvars.fastswimtime)
						and not(player.cmd.forwardmove)
						and not(player.cmd.sidemove)
						if not(player.mo.state == S_PLAY_FALL)
							player.mo.state = S_PLAY_FALL
						end
					elseif not(player.mo.state == swimanim)
						player.mo.state = swimanim
					end
				end
			else
				player.mkvars.fastswimtime = 0
			end
		else
			player.mkvars.fastswimtime = 0
			if(player.mo.state == S_KIRBY_SWIM)
				player.mo.state = S_PLAY_FALL
			end
		end
		
		// --------------------------------
		// MISC
		// --------------------------------
		// Falling animation
		if not(P_IsObjectOnGround(player.mo))
			and((player.mo.state >= S_PLAY_STND and player.mo.state <= S_PLAY_RUN) or player.mo.state == S_KIRBY_DUCK or player.mo.state == S_KIRBY_SLIDE)
			and not(player.powers[pw_carry] or player.mkvars.prevcarry)	// We got minecarts in 2.2
			and not(player.mo.eflags & MFE_UNDERWATER)	// Fat uses this!
			and not(player.mkvars.inmud)
			player.mo.state = S_PLAY_FALL
		end
		
		// Minecart
		if(player.powers[pw_carry] == CR_MINECART)
			player.mo.state = S_KIRBY_MINECART
		end
		
		// Running particles
		if(P_IsObjectOnGround(player.mo))
			and(player.mo.state == S_PLAY_RUN)
			if not(leveltime % 3)
				local particle = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z, MT_SPINDUST)
				particle.scale = player.mo.scale
				if(player.mo.eflags & MFE_VERTICALFLIP)
					P_TeleportMove(particle, player.mo.x, player.mo.y, player.mo.z+player.mo.height-particle.scale)
				end
				particle.momz = (((P_RandomByte()*player.mo.scale)/255)+player.mo.scale)*P_MobjFlip(player.mo)
				
				local rundir = R_PointToAngle2(player.cmomx, player.cmomy, player.mo.momx, player.mo.momy)+ANGLE_90
				P_InstaThrust(particle, rundir, (((P_RandomByte()*player.mo.scale*2)/255)-player.mo.scale)*16)
				
				local goodmomx = player.mo.momx-player.cmomx
				local goodmomy = player.mo.momy-player.cmomy
				particle.momx = $1 + player.cmomx + (goodmomx*2/3)
				particle.momy = $1 + player.cmomy + (goodmomy*2/3)
				
				P_TeleportMove(particle, particle.x-particle.momx, particle.y-particle.momy, particle.z+particle.momz)
				
				if(P_MobjFlip(player.mo) < 0)
					particle.eflags = $1|MFE_VERTICALFLIP
					particle.flags2 = $1|MF2_OBJECTFLIP
				end
				
				if(player.mo.eflags & MFE_UNDERWATER)
					or(player.mo.eflags & MFE_TOUCHWATER)
					particle.state = S_SPINDUST_BUBBLE1
				end
				
				particle.scale = player.mo.scale
			end
			
			// Speed up animation
			player.mo.tics = 1
		end
		
		// No shield abilities!
		if not(player.pflags & PF_SPINNING)	// Don't set thokked when spinning
			player.pflags = $1|PF_THOKKED
		elseif(P_IsObjectOnGround(player.mo))
			player.pflags = $1 & !PF_THOKKED
		end
		
		// Color stuff
		player.mo.colorized = false
		if(P_PlayerInPain(player))
			local color = SKINCOLOR_ORANGE
			if(player.skincolor == SKINCOLOR_ORANGE)
				color = SKINCOLOR_RUST
			end
			if(leveltime & 1)
				player.mo.color = color
				player.mo.colorized = true
			else
				player.mo.color = player.skincolor
				player.mo.colorized = false
			end
		elseif not(mariomode and player.powers[pw_invulnerability])
			player.mo.color = player.skincolor
			player.mo.colorized = false
			if(player.powers[pw_shield] & SH_FIREFLOWER)
				player.mo.color = SKINCOLOR_WHITE
			end
		end
		
		// Charge
		if(player.mkvars.ablcharge)
			and(leveltime & 1)
			player.mo.colorized = true
		end
		
		// Late stuff
		callAbilityFunc(player, false, false, true)
		
		// Prev cmd!
		player.mkvars.prevcmd = player.cmd.buttons
		player.mkvars.prevforward = player.cmd.forwardmove
		player.mkvars.prevground = P_IsObjectOnGround(player.mo)
		player.mkvars.prevsuper = starship
		player.mkvars.prevcarry = player.powers[pw_carry]
		
		player.spinheight = player.height
		player.mkvars.industdevil = false
		player.cmd.buttons = oldbutt	// For other things added later I guess?
	end
end)

addHook("MobjDeath", function(mo, inflict, source, dtype)		// This line causes an error when a player spectates when the level transitions to score tally? What? Battle also errors on a few lines...
	if(mo and mo.valid and mo.player and mo.player.valid)
		and(characterabilities[mo.skin])
		if not(dtype == DMG_DROWNED or dtype == DMG_SPACEDROWN)
			mo.player.mkvars.deathtimer = deathtimer
		end
		if not(multiplayer)
			S_StopMusic(mo.player)
		end
	end
end, MT_PLAYER)

// Dust devils
local function OnDustDevil(dustdevil, pmobj)
	if not(pmobj.type == MT_PLAYER)
		return
	end

	// Kirby only!
	if not(characterabilities[pmobj.skin])
		return
	end

	if(abs(pmobj.x - dustdevil.x) > dustdevil.radius or abs(pmobj.y - dustdevil.y) > dustdevil.radius)
		return
	end

	if(pmobj.z + pmobj.height >= dustdevil.z and dustdevil.z + dustdevil.height >= pmobj.z)
		local pos = pmobj.z - dustdevil.z
		local thresh = dustdevil.scale * 20

		local player = pmobj.player
		//Player in the swirl part.
		if(dustdevil.height - pos > thresh)
			// General
			player.mkvars.ducking = false
			player.mkvars.usedjumps = 0
			
			// Abilities
			player.mo.state = S_PLAY_PAIN
			player.mkvars.ablstate = 0
			player.mkvars.ablvar1 = 0
			player.mkvars.ablvar2 = 0
			player.mkvars.ablvar3 = 0
			player.mkvars.sucktimer = 0
			
			player.mkvars.industdevil = true
		else
			// On top
			player.mkvars.usedjumps = 0
		end
	end

	return
end
addHook("MobjThinker", function(dustdevil)
	searchBlockmap("objects", OnDustDevil, dustdevil)
end, MT_DUSTDEVIL)

CheckMud = function(player)
	for rover in player.mo.subsector.sector.ffloors()
		if not(rover.flags & FF_EXISTS)
			or not(rover.flags & FF_QUICKSAND)
			continue
		end
		
		local topheight = rover.topheight
		local bottomheight = rover.bottomheight
		if(rover.t_slope) topheight = P_GetZAt(rover.t_slope, player.mo.x, player.mo.y) end
		if(rover.b_slope) bottomheight = P_GetZAt(rover.b_slope, player.mo.x, player.mo.y) end

		if(topheight >= player.mo.z and bottomheight < player.mo.z + player.mo.height)
			return true
		end
	end
	return false
end

states[S_KIRBYMONINVIS] = {SPR_INVS, 0, -1, nil, 0, 0, S_KIRBYMONINVIS}
states[S_KIRBYMONWILLDIE] = {SPR_PLAY, 0, 1, nil, 0, 0, S_NULL}
