/**
*
*	Footstool.lua
*
*	by Stonecutter
*
*
*	A footstool custom ability.
*
*	TO DO:
*		Needs testing vs Metal Sonic again.
*		Redo placement logic taking motion into account.
*/

// The string "--##" preceedes notes that I (Stonecutter) have written to myself for improving my code.
// The string "--/" preceeds notes that explain code.

// Please note that some comments are outdated and may not be accurate any more.
// I will fix this once I return to the project.


local grabBoxSize = 40 * FRACUNIT	--## should use radius in final version
local footstoolSpeed = 15				-- the thrust provided by a footstool.	--## maybe use jumpfactor in final version?
local footstoolTime = 2 * TICRATE /4	-- the duration of the footstool anim
local footstoolWait = 3 * TICRATE		-- the duration of the footstool wait anim.	--## maxdash could work well for this if it's not already being used.

local footstoolees = {}
local toRemove = {}

local boolnum = {[true]=1,[false]=0}


freeslot("S_PLAY_FOOTSTOOL", "S_PLAY_FOOTSTOOL_WAIT", "MT_FOOTSTOOL_HITBOX", "S_FOOTSTOOL_HITBOX", "MT_HTECH")	--## remove anything that doesn't need to be here.
states[S_PLAY_FOOTSTOOL] = 
{
    SPR_PLAY,
    SPR2_CRCH,
	-1,
	nil,
	0,
	0,
	S_PLAY_FOOTSTOOL
}
states[S_PLAY_FOOTSTOOL_WAIT] = 
{
    SPR_PLAY,
    SPR2_ROLL|FF_ANIMATE,
	footstoolWait,
	nil,
	3,
	4,
	S_PLAY_FALL
}

--## add docs
local function FootstoolHold(footstooler, other)
	
		local fFlip = P_MobjFlip(footstooler)
		local oFlip = P_MobjFlip(other)
		
		local oCeil = other.ceilingz
		local oFloor = other.floorz
		local clearance = oCeil-oFloor
		local oHeight = other.height
		local fHeight = footstooler.height
		local oZ = other.z
			
		--// other.z+(((1+(1*oFlip))/2)*oHeight) --/ This is the top of other.	--## maybe make ths a local?
		
		--## probably make these local here, rather than above.
		local isRoom1 = boolnum[(abs((((1+(1*fFlip))/2)*oCeil) + (((1-(1*fFlip))/2)*oFloor) -  (oZ + (((1+(1*oFlip))/2)*oHeight))) >= fHeight)]
			--//isRoom1 = 1	--/ There is room between the enemy and the ceiling/floor (depending on the footstooler's gravity status
			--//isRoom1 = 0	--/ There is not room between the enemy and the ceiling/floor (depending on the footstooler's gravity status)
		
		
		local isRoom2 = boolnum[((clearance)>=oHeight+fHeight)]
			--// isRoom2 = 1 --/ There is room to squeeze the footstooler above/below the footstoolee
			--// isRoom2 = 0 --/ There is no room to squeeze the footstooler above/below the footstoolee
			
			other.momx = footstooler.momx
			other.momy = footstooler.momy
			other.momz = 0
			
			other.flags2 = $ | MF2_JUSTATTACKED	--## should this be uncommented?
			other.flags2 = $ | MF2_AMBUSH	--## Will remove these later when target-type specific code is written.
			other.flags = $ & ~MF_NOGRAVITY
			other.flags = $ & ~MF_SPECIAL	--## should this be uncommented?
			
			if(other.player ~= nil and other.player.valid) then
				other.player.powers[pw_nocontrol] = 1
				other.player.guard = -1
				other.state = S_PLAY_PAIN
				footstooler.player.powers[pw_flashing] = 0		
			end
			
			footstooler.momz = 0
			footstooler.state = S_PLAY_FOOTSTOOL						-- sets footstooler's state to footstooling
			footstooler.player.powers[pw_nocontrol] = 1				-- takes control from the footstooler. It should be returned at the end of the manuever.
			
			footstooler.target = other
			other.footstooler = footstooler
			
				if(mobjinfo[other.type].painstate ~= S_NULL)
					other.state = mobjinfo[other.type].painstate
				else
					other.state = mobjinfo[other.type].spawnstate
				end
			
			//footstooler.pushed_last = nil
			//footstooler.pushed = nil
			//other.pushed_last = nil
			//other.pushed = nil
			
			other.flags = $ & ~MF_SPECIAL
			footstooler.flags = $ & ~MF_SPECIAL
			
			local flipMatch = (1+(oFlip*fFlip))/2	--/ 1 = Gravity faces the same direction for both parties, 0 = Gravity faces different directions for the two parties.
			
			--/ These next few lines are actually much more readable than they appear at first glance.
			--/ Simply copy and paste the third arguements into another file then indent them like you would nested if-else phrases.
			--/ When doing so, treat operators like and's and or's.
			--/ Keep in mind that the math inside the functions serve the same purpose as nested if-else phrases.
			
			--## Actually, I should just create a local variable arg3 and set it equal to the math that currently exists in arguement three of the function about to be called.
			--## That way, I could write it expanded and indented and commented so that everyone can read it easier, better understand what's going on.
			
			--/ The next line works as follows. If there was not enough room to perform the footstool, but there would be if the target moved up or down, moves the target up or down appropriately.
			P_TeleportMove(other,other.x,other.y,((isRoom2-isRoom1)*(((oCeil-((oHeight*((oFlip+1)/2))+fHeight))*((fFlip+1)/2))+((oFloor+((oHeight*((1-oFlip)/(2)))+fHeight))*((fFlip-1)/2)*(-1))))+((1-(isRoom2-isRoom1))*oZ))
			--/ Since the last line's use case should happen only infrequently and P_TeleportMove is probably expensive compared to an if statement (it results in collision calculations) I'm commenting it out and using an if statement instead.
			
			--if(isRoom2*((isRoom1*(-1))+1))
			--	P_TeleportMove(other,other.x,other.y,(((oCeil-(oHeight*(oFlip+1)/2)+fHeight)*(fFlip+1)/2)+((oFloor+(oHeight*(oFlip-1)/2)+fHeight)*(fFlip-1)/2)))	--## math is messed up
			--end
			

			--/ The next line works as follows. Basically, if there was room to move the footstooler into position, it does so. Otherwise, the line does basically nothing.
			P_TeleportMove(footstooler,(footstooler.x*((isRoom2*(-1))+1))+(other.x*isRoom2),(footstooler.y*((isRoom2*(-1))+1))+(other.y*isRoom2),((oZ+((flipMatch)*oHeight*oFlip)+((flipMatch-1)*oHeight*(oFlip)))*isRoom2)+(footstooler.z*((-1*isRoom2)+1)))
			--/ Calling P_TeleportMove is probably much more expensive than making an if branch, but we would almost always be calling it anyway.
			
			--/ Don't worry, I won't be writing code this complicated again any time soon!
		return isRoom2
end


--## add docs	--## just realized that I could probably simply call P_DoJump(footstooler.player, true)
local function FootstoolJump(footstooler)

	local isInWater = (footstooler.eflags & MFE_UNDERWATER)/8	--/ MFE_UNDERWATER = 8
	local isInSpace = boolnum[(footstooler.player.powers[pw_spacetime] ~= 0)]	--## I'm probably going to move this so I can share it with FootstoolCrash
	P_SetObjectMomZ(footstooler, FRACUNIT*(footstoolSpeed + (isInWater*footstoolSpeed))/(1+(isInWater*2)), 2)	--## take space and goop into account in the final version
	--A_ZThrust(footstooler, footstoolSpeed, 3)
	S_StartSound(footstooler, sfx_jump)	--## needs to access the particular skin's version of the jump sound effect in the final version.
	footstooler.state = S_PLAY_JUMP
	footstooler.player.pflags = $ | PF_JUMPED
	footstooler.flags = mobjinfo[footstooler.type].flags
	
end

--##add docs
local function FootstoolCrash(other, footstooler)
	local isInWater = (other.eflags & MFE_UNDERWATER)/8	--/ MFE_UNDERWATER is 8
	local isInSpace	--##
	other.momz = (-(footstoolSpeed + (isInWater*footstoolSpeed))/(1+(isInWater*2)))*P_MobjFlip(footstooler)*footstooler.scale - footstooler.momz
	table.insert(footstoolees, #footstoolees, other)
end

/**	--## Update documentation
*	FootstoolBegin
*
*	Summary:
*			A function that lets a mobj grab another and set up for a footstool.
*
*	Params:
*			footstooler	- the mobj performing the footstool
*			other		- the mobj getting footstooled
*			oType		- number representing what is being footstooled.
*							1 = enemy
*							2 = boss
*							3 = player
*
*	Return:
*			true if footstool can be performed, false if it can't.
*
*	Preconditions:
*		-	Both parameters are valid mobj's
*
*		-	footstoolTime must be defined and be a number. It is expected to be
*			the number of seconds that the footstool should take multiplied by
*			the ticrate.
*
*		-	footstoolSpeed must be defined and be a number. It is expected to
*			be speed at which a footatool launches its actors.
*
*		-	S_PLAY_FOOTSTOOL must be defined and is not nil. It should point to
*			the footstooling sprites.
*
*		-	If footstooler is supposed to be able to footstool at this time,
*			then footstooler.canFootstool must be set to true.
*
*		-	While not required, it is assumed that footstooler.type == MT_PLAYER
*			and is not an ally and either other.type == MT_PLAYER, or
*			other.flags contains either MF_BOSS or MF_ENEMY. If this assumption
*			is incorrect, this will be capable of footstooling non-hostile
*			mobj's.
*
*	Postconditions: If this.canFootstool == true:
*		-	footstooler has its momentum dampened and is placed in the footstool
*			state.
*
*		-	footstooler.isFootstooling is set to true. this.isFootstooling can
*			be used by a second function to hold footstooler in place, and, when
*			the footstool is complete, launch footstooler.
*
*		-	other's momentum is reset unless it has the boss flag.
*
*		-	other.beingFootstooled is set to true. other.wasFootstooled should
*			be used by some other function to hold it in place. prevent it from
*			interfering with "this," and, when the footstool is complete, both
*			launching other away from the player and inflicting damage to it
*			when it lands.	--## needs updating
*
*		-	If other.type == MT_PLAYER, then other is placed in its pain state	--## needs updating
*
*		-	If other has the boss flag, then it is damaged instantly and both
*			this.isFootstooling and other.beingFootstooled are set to 1 with the
*			intention that the footstool ends the following frame.
*
*		-	If there is no room to place other under footstooler, then other
*			will recieve damage and footstooler will immediately transition into
*			the next phase of the footstool.
*
*		-	footstooler.canFootstool will be set to false.
*
*	Description:
*			Checks to see if footstooler.canFootstool is not equal to nil and is
*			true. If it is true, this function attempts to move other below
*			footstooler and sets fields in both of them that a second function
*			can use to complete the footstool. If there is not enough room to
*			perform a footstool or other is a boss, other will receive damage
*			and the footstooler will immediately begin the next phase of the
*			footstool.
*					
*
*	Notes:
*			This function adds the "isFootstooling" field to footstooler if 
*			other was not a boss, there was enough room to perform the complete
*			footstool, and this function returned true.
*
*			Metal Sonic cannot be hit out of his float phase by this function.
*			Instead, the footstooler must not have the nojumpdamage player flag
*			during the footstool wait phase, with collisions with other enemies,
*			bosses, and hostile players overriden appropriately.
*/
local function FootstoolBegin(footstooler, other, oType)
	
	if
	(
		footstooler.isFootstooling ~= nil and footstooler.isFootstooling == 0
		and (other.flags & MF_ENEMY or other.flags & MF_BOSS or other.player ~= nil)	--## optimize this if possible
	)
	
		footstooler.player.pflags = $ | PF_NOJUMPDAMAGE	--##
		
		--footstooler.momx = 0	--##
		--footstooler.momy = 0	--##
		
		if(oType == 2)	-- other is a boss
			
			if not (other.state == S_METALSONIC_FLOAT)	-- if metal sonic is in his float phase, lets him get hit by jumpdamage the next frame.
				P_DamageMobj(other, footstooler, footstooler, 1)	-- otherwise, inflicts damage.
				FootstoolJump(footstooler)
			end
			
		else	--/ other is not a boss
			footstooler.isFootstooling = 1 + (footstoolTime * FootstoolHold(footstooler,other))
				--/ sets footstooler's timer for the footstool duration. If there is no room for a proper footstool, timer is 1 tic.
		end
	end
end



/*	--## update docs
*	FootstoolEnd
*
*	Summary:	An attack that launches one mobj downwards and another upwards.
*
*	Params:
*		footstooler	- the mobj performing the footstool
*		other		- the mobj getting footstooled
*
*	Return:	true if footstool is performed, false if it isn't.
*
*	Preconditions:
*		-	Both parameters are valid mobj's
*
*		-	footstoolVelocity must be defined and be a number.
*
*		-	If footstooler is supposed to be footstooling at this time, then
*			footstooler.isFootstooling must be a positive number. Otherwise,
*			footstooler.isFootstooling should be zero. This number is expected
*			to be the time spent performing a footstool in seconds * TICRATE.
*
*		-	While not required, it is assumed that this.type == MT_PLAYER and
*			is not an ally and either other.type == MT_PLAYER, or other.flags
*			contains MF_ENEMY. If this assumption is incorrect, then footstooler
*			is footstooling something unintended.
*
*	Postconditions:
*		-	If this.isFootstooling == 0, then footstooler has its vertical
*			momentum set to footstoolVelocity if footstooler is upright,
*			negative footstoolVelocity if footstooler is upside-down.
*
*		-	other.beingFootstooled == 0, other is shot downwards relative to
*			footstooler's sprite flip status at footstoolSpeed and
*			other.wasFootstooled is set to true. other.wasFootstooled should be
*			used by another function to prevent other from taking action and to
*			damage other upon landing.
*
*		-	If other.type == MT_PLAYER, then other is placed in its fall state
*
*	Description:
*			--## to be updated
*					
*
*	Notes:
*
*			This function adds the wasFootstooled field to other.	--## no longer true
*/
local function FootstoolEnd(footstooler, other)
	
	if(footstooler.isFootstooling > 0)
		--footstooler.xmom = 0	--##
		--footstooler.ymom = 0	--##
		footstooler.zmom = 0
		other.xmom = footstooler.xmom
		other.ymom = footstooler.ymom
		other.zmom = 0
		return false
	end
	
	
	P_DamageMobj(other,footstooler,footstooler,1)
	other.flags = $ & ~MF_SPECIAL
	footstooler.flags = $ & ~MF_SPECIAL	--## needs to get added back the following frame.
	
	FootstoolCrash(other, footstooler)
	FootstoolJump(footstooler)
	--footstooler.isFootstooling = -1 * TICRATE	--/ just finished footstooling.
	footstooler.isFootstooling = 0
	return true
end



--## add docs
local function FootstoolWait(player)
	local mo = player.mo
	if
	(
		mo ~= nil
		and mo.valid
		and mo.skin == "honey"
		and (mo.state == S_PLAY_JUMP or mo.state == S_PLAY_FALL or player.mo.state == S_PLAY_HONEYDIVE or player.mo.state == S_PLAY_HONEYDIVEEND or player.mo.state == S_PLAY_HONEYBACKFLIP or player.mo.state == S_PLAY_HONEYMELEE2 or player.mo.state == S_PLAY_ROLL or player.mo.state == S_PLAY_SPRING or player.mo.state == S_PLAY_WALK or player.mo.state == S_PLAY_RUN)
		and (not mo.player.powers[pw_flashing])
	)
		player.pflags = $ | PF_NOJUMPDAMAGE
		player.pflags = $ & ~PF_STARTJUMP
		mo.player.powers[pw_nocontrol] = 1
		mo.state = S_PLAY_FOOTSTOOL_WAIT
		mo.footstoolWait = footstoolWait
		
		local ukemi = P_SpawnMobjFromMobj(player.mo, 0, 0, player.mo.scale * 20, MT_HTECH)
		ukemi.angle = player.mo.angle
		ukemi.fuse = 20
		ukemi.scale = player.mo.scale*4/3
		ukemi.color = player.mo.color
		ukemi.source = player.mfo
		player.honey.colorizing = 20
		return true
	end
	return false
end

--addHook("JumpSpinSpecial", FootstoolWait)	--## move to battlemod ability

--## add docs
addHook("MobjThinker", function (mobj)
	if(mobj ~= nil and mobj.valid and mobj.skin == "honey")
		if(mobj.footstoolWait == nil)
			mobj.footstoolWait = 0
			mobj.isFootstooling = 0
		elseif(mobj.footstoolWait > 0 and not P_IsObjectOnGround(mobj))	--## clean this up
			if(mobj.state == S_PLAY_FOOTSTOOL_WAIT)
				mobj.isFootstooling = 0
				mobj.footstoolWait = $ - 1
				if(mobj.footstoolWait == 0)
					mobj.state = S_PLAY_FALL
					return false
				end
				--mobj.momz = 0	--##
				mobj.player.powers[pw_nocontrol] = 1
			else
				mobj.footstoolWait = 0
				return false
			end
		elseif(mobj.isFootstooling > 0)
			if(mobj.state == S_PLAY_FOOTSTOOL and not P_IsObjectOnGround(mobj))	--## clean this up
				mobj.isFootstooling = $ - 1
				if(mobj.isFootstooling == 0)
					FootstoolEnd(mobj, mobj.target)
					return false
				end
				FootstoolHold(mobj,mobj.target)
				mobj.player.powers[pw_nocontrol] = 1
				return false
			--elseif(mobj.isFootstooling < 0)	--/## just finished footstooling. It needed 1 more tic before MF_SPECIAl came back or Battlemod's collision would wreck the thrust.	--## is this actually needed?
			--	mobj.isFootstooling = $ + 1
			--	footstooler.target = nil
			--	other.footstooler = nil
			--	other.flags = $ & ~MF_SPECIAL
			--	footstooler.flags = $ & ~MF_SPECIAL
			end
		else
			mobj.isFootstooling = 0
		end
		return false
	end
end, MT_PLAYER)

--## add docs
addHook("TouchSpecial",function(other, footstooler)
	if
	(
		other ~= nil
		and other.valid
		and footstooler ~= nil
		and footstooler.valid
		and not P_IsObjectOnGround(footstooler)
		and 
		(
			(	--## probably depreciated
				footstooler.state == S_PLAY_FOOTSTOOL
				and other.footstooler ~= nil
				and other.footstooler == footstooler
			)
			or
			(
				footstooler.sate == S_PLAY_FOOTSTOOL_WAIT 
				and other.type ~= MT_TNTBARREL 
				and other.flags & ~MF_MONITOR
			)
		)
	)
		return true
	end
	return false
end)

--## add docs
addHook("MobjDamage", function(target, inflictor, source)	--## need to check for z-position collision with the hitbox	--## checking for skin name here rather than in FootstoolBegin would probably be much more optimal
	if
	(
		target ~= nil
		and target.valid
		and target.state == S_PLAY_FOOTSTOOL_WAIT	--/ Preempts the need to check for skin name.
		and source ~= nil and source.valid
		--and ((source.flags & MF_BOSS) or (source.flags & MF_ENEMY) or (other.type == MT_PLAYER))
		--and (not(inflictor.flags & MF_FIRE))	--## needs testing
		and	--## It could be better to make this another if statement so that appropriate locals can be declared
		(
			(
				cos(target.angle) >= 0
				and (source.x + source.radius >= target.x or source.x - source.radius >= target.x)
				and abs(P_AproxDistance(source.x-target.x,source.y-target.y)) - (source.radius + target.radius) < grabBoxSize
			)
			or
			(
				cos(target.angle) < 0
				and (source.x + source.radius <= target.x or source.x - source.radius <= target.x)
				and abs(P_AproxDistance(source.x-target.x,source.y-target.y)) - (source.radius + target.radius) < grabBoxSize
			)
		)
		and
		(
			(
				sin(target.angle) >= 0
				and (source.y + source.radius >= target.y or source.y - source.radius >= target.y)
				and abs(P_AproxDistance(source.x-target.x,source.y-target.y)) - (source.radius + target.radius) < grabBoxSize
			)
			or
			(
				sin(target.angle) < 0
				and (source.y + source.radius <= target.y or source.y - source.radius <= target.y)
				and abs(P_AproxDistance(source.x-target.x,source.y-target.y)) - (source.radius + target.radius) < grabBoxSize
			)
		)
	)
		if(source.state == S_METALSONIC_FLOAT)
			target.player.pflags = $ & ~PF_NOJUMPDAMAGE
			P_TeleportMove(target, target.x, target.y, target.z)
			target.player.pflags = $ | PF_NOJUMPDAMAGE	--##
			
			FootstoolJump(target)	--## probably not the desired behavior here.
			
			return true
		end
		local oType = 1
		if(source.flags & MF_BOSS)	--## could be optimized
			oType = 2
		elseif(source.player ~= nil)
			oType = 3
		end
		FootstoolBegin(target, source, oType)
		return true
	elseif(target ~= nil and target.valid and target.state == S_PLAY_FOOTSTOOL and source ~= nil and source.valid)	--## might be able to avoid some redundant checks here.
		return true
	end
end, MT_PLAYER)


--/ 
--## Add docs
addHook("ThinkFrame", function()
	//##for k, v in ipairs(footstoolees) do
	local k = #footstoolees
	while (k > 0 or (k == 0 and footstoolees[0] and footstoolees[0].valid))
		local v = footstoolees[k]
		if v==nil or not v.valid then
			table.remove(footstoolees,k)
			k=$-1
		elseif P_IsObjectOnGround(v) or (v.standingslope and P_GetZAt(v.standingslope,v.x,v.y)==v.z)--##
			v.flags2 = $ & ~MF2_JUSTATTACKED
			v.flags2 = $ & ~MF2_AMBUSH
			--##v.flags = $ | MF_SPECIAL
			v.flags = mobjinfo[v.type].flags
			//##v.flags = $ & ~MF_NOTHINK
			local footstooler = v.footstooler
			if(footstooler and footstooler.valid) then
				footstoolees[k] = nil
				table.remove(footstoolees,k)
			else
				footstoolees[k] = nil
				table.remove(footstoolees,k)
			end
			if (v ~= nil and v.valid) then
			end
			k=$-1
		else
			if(v.player ~= nil and v.player.valid)then
				v.player.powers[pw_nocontrol] = 1
			end
			v.flags2 = $ | MF2_JUSTATTACKED
			v.flags2 = $ | MF2_AMBUSH
			v.flags = $ & ~MF_SPECIAL
			k=$-1
		end
	end
end)

rawset(_G, "HoneyFootstool", FootstoolWait)