--!strict

local System = require(script.Parent)
local Animations = System.Animations
local Sounds = System.Sounds
local Enums = System.Enums
local Util = System.Util

local Action = Enums.Action
local ActionFlags = Enums.ActionFlags
local ActionGroup = Enums.ActionGroups

local MarioEyes = Enums.MarioEyes
local GroundStep = Enums.GroundStep
local InputFlags = Enums.InputFlags
local MarioFlags = Enums.MarioFlags
local SurfaceClass = Enums.SurfaceClass
local ParticleFlags = Enums.ParticleFlags

type Mario = System.Mario

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Landing Actions
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

type LandingAction = {
	NumFrames: number,
	JumpTimer: number,
	EndAction: number,
	APressedAction: number,
}

local sJumpLandAction: LandingAction = {
	NumFrames = 4,
	JumpTimer = 5,

	EndAction = Action.JUMP_LAND_STOP,
	APressedAction = Action.DOUBLE_JUMP,
}

local sFreefallLandAction: LandingAction = {
	NumFrames = 4,
	JumpTimer = 5,

	EndAction = Action.FREEFALL_LAND_STOP,
	APressedAction = Action.DOUBLE_JUMP,
}

local sSideFlipLandAction: LandingAction = {
	NumFrames = 4,
	JumpTimer = 5,

	EndAction = Action.SIDE_FLIP_LAND_STOP,
	APressedAction = Action.DOUBLE_JUMP,
}

local sLongJumpLandAction: LandingAction = {
	NumFrames = 6,
	JumpTimer = 5,

	EndAction = Action.LONG_JUMP_LAND_STOP,
	APressedAction = Action.LONG_JUMP,
}

local sDoubleJumpLandAction: LandingAction = {
	NumFrames = 4,
	JumpTimer = 5,

	EndAction = Action.DOUBLE_JUMP_LAND_STOP,
	APressedAction = Action.JUMP,
}

local sTripleJumpLandAction: LandingAction = {
	NumFrames = 4,
	JumpTimer = 0,

	EndAction = Action.TRIPLE_JUMP_LAND_STOP,
	APressedAction = Action.UNINITIALIZED,
}

local sBackflipLandAction: LandingAction = {
	NumFrames = 4,
	JumpTimer = 0,

	EndAction = Action.BACKFLIP_LAND_STOP,
	APressedAction = Action.BACKFLIP,
}

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Helpers
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

local DEF_ACTION: (number, (Mario) -> boolean) -> () = System.RegisterAction
local sPunchingForwardVelocities = { 0, 1, 1, 2, 3, 5, 7, 10 }

local function tiltBodyRunning(m: Mario)
	local pitch = m:FindFloorSlope(0)
	pitch = pitch * m.ForwardVel / 40

	return -pitch
end

local function playStepSound(m: Mario, frame1: number, frame2: number)
	if m:IsAnimPastFrame(frame1) or m:IsAnimPastFrame(frame2) then
		if m.Flags:Has(MarioFlags.METAL_CAP) then
			m:PlaySoundAndSpawnParticles(Sounds.ACTION_METAL_STEP, 0)
		else
			m:PlaySoundAndSpawnParticles(Sounds.ACTION_TERRAIN_STEP, 0)
		end
	end
end

local function alignWithFloor(m: Mario)
	local pos = Util.SetY(m.Position, m.FloorHeight)
	m.Position = pos

	local radius = 40
	local minY = -radius * 3
	local yaw = m.FaceAngle.Y

	local p0_x = pos.X + radius * Util.Sins(yaw + 0x2AAA)
	local p0_z = pos.Z + radius * Util.Coss(yaw + 0x2AAA)

	local p1_x = pos.X + radius * Util.Sins(yaw + 0x8000)
	local p1_z = pos.Z + radius * Util.Coss(yaw + 0x8000)

	local p2_x = pos.X + radius * Util.Sins(yaw + 0xD555)
	local p2_z = pos.Z + radius * Util.Coss(yaw + 0xD555)

	local test0 = Vector3.new(p0_x, pos.Y + 150, p0_z)
	local test1 = Vector3.new(p1_x, pos.Y + 150, p1_z)
	local test2 = Vector3.new(p2_x, pos.Y + 150, p2_z)

	local p0_y = Util.FindFloor(test0)
	local p1_y = Util.FindFloor(test1)
	local p2_y = Util.FindFloor(test2)

	p0_y = p0_y - pos.Y < minY and pos.Y or p0_y
	p1_y = p1_y - pos.Y < minY and pos.Y or p1_y
	p2_y = p2_y - pos.Y < minY and pos.Y or p2_y

	local avgY = (p0_y + p1_y + p2_y) / 3
	local forward = Vector3.new(Util.Sins(yaw), 0, Util.Coss(yaw))

	if avgY >= pos.Y then
		pos = Util.SetY(pos, avgY)
	end

	local a = Vector3.new(p0_x, p0_y, p0_z)
	local b = Vector3.new(p1_x, p1_y, p1_z)
	local c = Vector3.new(p2_x, p2_y, p2_z)

	local yColumn = (b - a):Cross(c - a).Unit
	local xColumn = yColumn:Cross(forward).Unit
	m.ThrowMatrix = CFrame.fromMatrix(pos, xColumn, yColumn)
end

local function beginWalkingAction(m: Mario, forwardVel: number, action: number, actionArg: number?)
	m:SetForwardVel(forwardVel)
	m.FaceAngle = Util.SetY(m.FaceAngle, m.IntendedYaw)
	return m:SetAction(action, actionArg)
end

local function checkLedgeClimbDown(m: Mario)
	if m.ForwardVel < 10 then
		local pos, wall = Util.FindWallCollisions(m.Position, -10, 10)

		if wall then
			local floorHeight, floor = Util.FindFloor(pos)

			if floor and pos.Y - floorHeight > 160 then
				local wallAngle = Util.Atan2s(wall.Normal.Z, wall.Normal.X)
				local wallDYaw = wallAngle - m.FaceAngle.Y

				if math.abs(wallDYaw) < 0x4000 then
					pos -= Vector3.new(20 * wall.Normal.X, 0, 20 * wall.Normal.Z)
					m.Position = pos

					m.FaceAngle *= Vector3int16.new(0, 1, 1)
					m.FaceAngle = Util.SetY(m.FaceAngle, wallAngle + 0x8000)

					m:SetAction(Action.LEDGE_CLIMB_DOWN)
					m:SetAnimation(Animations.CLIMB_DOWN_LEDGE)
				end
			end
		end
	end
end

local function slideBonk(m: Mario, fastAction: number, slowAction: number)
	if m.ForwardVel > 16 then
		m:BonkReflection(true)
		m:SetAction(fastAction)
	else
		m:SetForwardVel(0)
		m:SetAction(slowAction)
	end
end

local function setTripleJumpAction(m: Mario)
	if m.Flags:Has(MarioFlags.WING_CAP) then
		return m:SetAction(Action.FLYING_TRIPLE_JUMP)
	elseif m.ForwardVel > 20 then
		return m:SetAction(Action.TRIPLE_JUMP)
	else
		return m:SetAction(Action.JUMP)
	end
end

local function updateSlidingAngle(m: Mario, accel: number, lossFactor: number)
	local newFacingDYaw
	local facingDYaw

	local floor = m.Floor

	if not floor then
		return
	end

	assert(floor)

	local slopeAngle = Util.Atan2s(floor.Normal.Z, floor.Normal.X)
	local steepness = math.sqrt(floor.Normal.X ^ 2 + floor.Normal.Z ^ 2)

	m.SlideVelX += accel * steepness * Util.Sins(slopeAngle)
	m.SlideVelZ += accel * steepness * Util.Coss(slopeAngle)

	m.SlideVelX *= lossFactor
	m.SlideVelZ *= lossFactor

	m.SlideYaw = Util.Atan2s(m.SlideVelZ, m.SlideVelX)

	facingDYaw = Util.SignedShort(m.FaceAngle.Y - m.SlideYaw)
	newFacingDYaw = facingDYaw

	if newFacingDYaw > 0 and newFacingDYaw <= 0x4000 then
		newFacingDYaw -= 0x200

		if newFacingDYaw < 0 then
			newFacingDYaw = 0
		end
	elseif newFacingDYaw > -0x4000 and newFacingDYaw < 0 then
		newFacingDYaw += 0x200

		if newFacingDYaw > 0 then
			newFacingDYaw = 0
		end
	elseif newFacingDYaw > 0x4000 and newFacingDYaw < 0x8000 then
		newFacingDYaw += 0x200

		if newFacingDYaw > 0x8000 then
			newFacingDYaw = 0x8000
		end
	elseif newFacingDYaw > -0x8000 and newFacingDYaw < -0x4000 then
		newFacingDYaw -= 0x200

		if newFacingDYaw < -0x8000 then
			newFacingDYaw = -0x8000
		end
	end

	m.FaceAngle = Util.SetY(m.FaceAngle, m.SlideYaw + newFacingDYaw)
	m.Velocity = Vector3.new(m.SlideVelX, 0, m.SlideVelZ)

	--! Speed is capped a frame late (butt slide HSG)
	m.ForwardVel = math.sqrt(m.SlideVelX ^ 2 + m.SlideVelZ ^ 2)

	if m.ForwardVel > 100 then
		m.SlideVelX = m.SlideVelX * 100 / m.ForwardVel
		m.SlideVelZ = m.SlideVelZ * 100 / m.ForwardVel
	end

	if math.abs(newFacingDYaw) > 0x4000 then
		m.ForwardVel *= -1
	end
end

local function updateSliding(m: Mario, stopSpeed: number)
	local intendedDYaw = Util.SignedShort(m.IntendedYaw - m.SlideYaw)
	local forward = Util.Coss(intendedDYaw)
	local sideward = Util.Sins(intendedDYaw)

	--! 10k glitch
	if forward < 0 and m.ForwardVel > 0 then
		forward *= 0.5 + 0.5 * m.ForwardVel / 100
	end

	local floorClass = m:GetFloorClass()
	local lossFactor
	local accel

	if floorClass == SurfaceClass.VERY_SLIPPERY then
		accel = 10
		lossFactor = m.IntendedMag / 32 * forward * 0.02 + 0.98
	elseif floorClass == SurfaceClass.SLIPPERY then
		accel = 8
		lossFactor = m.IntendedMag / 32 * forward * 0.02 + 0.96
	elseif floorClass == SurfaceClass.DEFAULT then
		accel = 7
		lossFactor = m.IntendedMag / 32 * forward * 0.02 + 0.92
	elseif floorClass == SurfaceClass.NOT_SLIPPERY then
		accel = 5
		lossFactor = m.IntendedMag / 32 * forward * 0.02 + 0.92
	end

	local oldSpeed = math.sqrt(m.SlideVelX ^ 2 + m.SlideVelZ ^ 2)

	--! This is attempting to use trig derivatives to rotate Mario's speed.
	--  It is slightly off/asymmetric since it uses the new X speed, but the old
	--  Z speed.

	m.SlideVelX += m.SlideVelZ * (m.IntendedMag / 32) * sideward * 0.05
	m.SlideVelZ -= m.SlideVelX * (m.IntendedMag / 32) * sideward * 0.05

	local newSpeed = math.sqrt(m.SlideVelX ^ 2 + m.SlideVelZ ^ 2)

	if oldSpeed > 0 and newSpeed > 0 then
		m.SlideVelX *= oldSpeed / newSpeed
		m.SlideVelZ *= oldSpeed / newSpeed
	end

	local stopped = false
	updateSlidingAngle(m, accel, lossFactor)

	if not m:FloorIsSlope() and m.ForwardVel ^ 2 < stopSpeed ^ 2 then
		m:SetForwardVel(0)
		stopped = true
	end

	return stopped
end

local function applySlopeAccel(m: Mario)
	local floor = m.Floor
	local floorNormal: Vector3

	if floor then
		floorNormal = floor.Normal
	else
		floorNormal = Vector3.yAxis
	end

	local floorDYaw = m.FloorAngle - m.FaceAngle.Y
	local steepness = math.sqrt(floorNormal.X ^ 2 + floorNormal.Z ^ 2)

	if m:FloorIsSlope() then
		local slopeClass = 0
		local slopeAccel

		if m.Action() ~= Action.SOFT_BACKWARD_GROUND_KB then
			if m.Action() ~= Action.SOFT_FORWARD_GROUND_KB then
				slopeClass = m:GetFloorClass()
			end
		end

		if slopeClass == SurfaceClass.VERY_SLIPPERY then
			slopeAccel = 5.3
		elseif slopeClass == SurfaceClass.SLIPPERY then
			slopeAccel = 2.7
		elseif slopeClass == SurfaceClass.DEFAULT then
			slopeAccel = 1.7
		else
			slopeAccel = 0
		end

		if floorDYaw > -0x4000 and floorDYaw < 0x4000 then
			m.ForwardVel += slopeAccel * steepness
		else
			m.ForwardVel -= slopeAccel * steepness
		end
	end

	m.SlideYaw = m.FaceAngle.Y
	m.SlideVelX = m.ForwardVel * Util.Sins(m.FaceAngle.Y)
	m.SlideVelZ = m.ForwardVel * Util.Coss(m.FaceAngle.Y)
	m.Velocity = Vector3.new(m.SlideVelX, 0, m.SlideVelZ)
end

local function applyLandingAccel(m: Mario, frictionFactor: number)
	local stopped = false
	applySlopeAccel(m)

	if not m:FloorIsSlope() then
		m.ForwardVel *= frictionFactor

		if m.ForwardVel ^ 2 < 1 then
			m:SetForwardVel(0)
			stopped = true
		end
	end

	return stopped
end

local function applySlopeDecel(m: Mario, decelCoef: number)
	local decel
	local stopped = false
	local floorClass = m:GetFloorClass()

	if floorClass == SurfaceClass.VERY_SLIPPERY then
		decel = decelCoef * 0.2
	elseif floorClass == SurfaceClass.SLIPPERY then
		decel = decelCoef * 0.7
	elseif floorClass == SurfaceClass.DEFAULT then
		decel = decelCoef * 2
	elseif floorClass == SurfaceClass.NOT_SLIPPERY then
		decel = decelCoef * 3
	end

	m.ForwardVel = Util.ApproachFloat(m.ForwardVel, 0, decel)

	if m.ForwardVel == 0 then
		stopped = true
	end

	applySlopeAccel(m)
	return stopped
end

local function updateDeceleratingSpeed(m: Mario)
	local stopped = false
	m.ForwardVel = Util.ApproachFloat(m.ForwardVel, 0, 1)

	if m.ForwardVel == 0 then
		stopped = true
	end

	m:SetForwardVel(m.ForwardVel)
	return stopped
end

local function updateWalkingSpeed(m: Mario)
	local maxTargetSpeed = 32
	local floor = m.Floor

	local targetSpeed = if m.IntendedMag < maxTargetSpeed then m.IntendedMag else maxTargetSpeed

	if m.ForwardVel < 0 then
		m.ForwardVel += 1.1
	elseif m.ForwardVel <= targetSpeed then
		m.ForwardVel += 1.1 - m.ForwardVel / 43
	elseif floor and floor.Normal.Y >= 0.95 then
		m.ForwardVel -= 1
	end

	if m.ForwardVel > 48 then
		m.ForwardVel = 48
	end

	local currY = Util.SignedShort(m.IntendedYaw - m.FaceAngle.Y)
	local faceY = m.IntendedYaw - Util.ApproachInt(currY, 0, 0x800)

	m.FaceAngle = Util.SetY(m.FaceAngle, faceY)
	applySlopeAccel(m)
end

local function shouldBeginSliding(m: Mario)
	if m.Input:Has(InputFlags.ABOVE_SLIDE) then
		if m.ForwardVel < -1 or m:FacingDownhill() then
			return true
		end
	end

	return false
end

local function analogStickHeldBack(m: Mario)
	local intendedDYaw = Util.SignedShort(m.IntendedYaw - m.FaceAngle.Y)
	return math.abs(intendedDYaw) > 0x471C
end

local function checkGroundDiveOrPunch(m: Mario)
	if m.Input:Has(InputFlags.B_PRESSED) then
		--! Speed kick (shoutouts to SimpleFlips)
		if m.ForwardVel >= 29 and m.Controller.StickMag > 48 then
			m.Velocity = Util.SetY(m.Velocity, 20)
			return m:SetAction(Action.DIVE, 1)
		end

		return m:SetAction(Action.MOVE_PUNCHING)
	end

	return false
end

local function beginBrakingAction(m: Mario)
	if m.ActionState == 1 then
		m.FaceAngle = Util.SetY(m.FaceAngle, m.ActionArg)
		return m:SetAction(Action.STANDING_AGAINST_WALL)
	end

	if m.ForwardVel > 16 then
		local floor = m.Floor

		if floor and floor.Normal.Y >= 0.17364818 then
			return m:SetAction(Action.BRAKING)
		end
	end

	return m:SetAction(Action.DECELERATING)
end

local function animAndAudioForWalk(m: Mario)
	local baseAccel = if m.IntendedMag > m.ForwardVel then m.IntendedMag else m.ForwardVel

	if baseAccel < 4 then
		baseAccel = 4
	end

	local targetPitch = 0
	local accel

	while true do
		if m.ActionTimer == 0 then
			if baseAccel > 8 then
				m.ActionTimer = 2
			else
				accel = baseAccel / 4 * 0x10000

				if accel < 0x1000 then
					accel = 0x1000
				end

				m:SetAnimationWithAccel(Animations.START_TIPTOE, accel)
				playStepSound(m, 7, 22)

				if m:IsAnimPastFrame(23) then
					m.ActionTimer = 2
				end

				break
			end
		elseif m.ActionTimer == 1 then
			if baseAccel > 8 then
				m.ActionTimer = 2
			else
				accel = baseAccel * 0x10000

				if accel < 0x1000 then
					accel = 0x1000
				end

				m:SetAnimationWithAccel(Animations.TIPTOE, accel)
				playStepSound(m, 14, 72)

				break
			end
		elseif m.ActionTimer == 2 then
			if baseAccel < 5 then
				m.ActionTimer = 1
			elseif baseAccel > 22 then
				m.ActionTimer = 3
			else
				accel = baseAccel / 4 * 0x10000
				m:SetAnimationWithAccel(Animations.WALKING, accel)
				playStepSound(m, 10, 49)
				break
			end
		elseif m.ActionTimer == 3 then
			if baseAccel < 18 then
				m.ActionTimer = 2
			else
				accel = baseAccel / 4 * 0x10000
				m:SetAnimationWithAccel(Animations.RUNNING, accel)

				playStepSound(m, 9, 45)
				targetPitch = tiltBodyRunning(m)

				break
			end
		end
	end

	local walkingPitch = Util.ApproachInt(m.WalkingPitch, targetPitch, 0x800)
	walkingPitch = Util.SignedShort(walkingPitch)

	m.WalkingPitch = walkingPitch
	m.GfxAngle = Util.SetX(m.GfxAngle, walkingPitch)
end

local function pushOrSidleWall(m: Mario, startPos: Vector3)
	local wallAngle: number
	local dWallAngle: number

	local dx = m.Position.X - startPos.X
	local dz = m.Position.Z - startPos.Z

	local movedDist = math.sqrt(dx ^ 2 + dz ^ 2)
	local accel = movedDist * 2 * 0x10000

	if m.ForwardVel > 6 then
		m:SetForwardVel(6)
	end

	local wall = m.Wall

	if wall then
		wallAngle = Util.Atan2s(wall.Normal.Z, wall.Normal.X)
		dWallAngle = Util.SignedShort(assert(wallAngle) - m.FaceAngle.Y)
	end

	if wall == nil or math.abs(dWallAngle) >= 0x71C8 then
		m:SetAnimation(Animations.PUSHING)
		playStepSound(m, 6, 18)
	else
		if dWallAngle < 0 then
			m:SetAnimationWithAccel(Animations.SIDESTEP_RIGHT, accel)
		else
			m:SetAnimationWithAccel(Animations.SIDESTEP_LEFT, accel)
		end

		if m.AnimFrame < 20 then
			m:PlaySound(Sounds.MOVING_TERRAIN_SLIDE)
			m.ParticleFlags:Add(ParticleFlags.DUST)
		end

		m.ActionState = 1
		m.ActionArg = Util.SignedShort(wallAngle + 0x8000)

		m.GfxAngle = Util.SetY(m.GfxAngle, m.ActionArg)
		m.GfxAngle = Util.SetZ(m.GfxAngle, m:FindFloorSlope(0x4000))
	end
end

local function tiltBodyWalking(m: Mario, startYaw: number)
	local anim = m.AnimCurrent
	local bodyState = m.BodyState

	if anim == Animations.WALKING or anim == Animations.RUNNING then
		local dYaw = m.FaceAngle.Y - startYaw

		local tiltZ = -math.clamp(dYaw * m.ForwardVel / 12, -0x1555, 0x1555)
		local tiltX = math.clamp(m.ForwardVel * 170, 0, 0x1555)

		local torsoAngle = bodyState.TorsoAngle
		tiltZ = Util.ApproachInt(torsoAngle.Z, tiltZ, 0x400)
		tiltX = Util.ApproachInt(torsoAngle.X, tiltX, 0x400)

		bodyState.TorsoAngle = Vector3int16.new(tiltX, torsoAngle.Y, tiltZ)
	else
		bodyState.TorsoAngle *= Vector3int16.new(0, 1, 0)
	end
end

local function tiltBodyButtSlide(m: Mario)
	local intendedDYaw = m.IntendedYaw - m.FaceAngle.Y
	local bodyState = m.BodyState

	local tiltX = 5461.3335 * m.IntendedMag / 32 * Util.Coss(intendedDYaw)
	local tiltZ = -(5461.3335 * m.IntendedMag / 32 * Util.Sins(intendedDYaw))

	local torsoAngle = bodyState.TorsoAngle
	bodyState.TorsoAngle = Vector3int16.new(tiltX, torsoAngle.Y, tiltZ)
end

local function commonSlideAction(m: Mario, endAction: number, airAction: number, anim: Animation)
	local pos = m.Position
	m:PlaySound(Sounds.MOVING_TERRAIN_SLIDE)
	m:AdjustSoundForSpeed()

	local step = m:PerformGroundStep()

	if step == GroundStep.LEFT_GROUND then
		m:SetAction(airAction)

		if math.abs(m.ForwardVel) >= 50 then
			m:PlaySound(Sounds.MARIO_HOOHOO)
		end
	elseif step == GroundStep.NONE then
		m:SetAnimation(anim)
		alignWithFloor(m)

		m.ParticleFlags:Add(ParticleFlags.DUST)
	elseif step == GroundStep.HIT_WALL then
		local wall = m.Wall

		if not m:FloorIsSlippery() then
			if m.ForwardVel > 16 then
				m.ParticleFlags:Add(ParticleFlags.VERTICAL_STAR)
			end

			slideBonk(m, Action.GROUND_BONK, endAction)
		elseif wall then
			local wallAngle = Util.Atan2s(wall.Normal.Z, wall.Normal.X)
			local slideSpeed = math.sqrt(m.SlideVelX ^ 2 + m.SlideVelZ ^ 2) * 0.9

			if slideSpeed < 4 then
				slideSpeed = 4
			end

			local slideYaw = Util.SignedShort(m.SlideYaw - wallAngle)
			m.SlideYaw = Util.SignedShort(wallAngle - slideYaw + 0x8000)
			m.SlideVelX = slideSpeed * Util.Sins(m.SlideYaw)
			m.SlideVelZ = slideSpeed * Util.Coss(m.SlideYaw)
			m.Velocity = Vector3.new(m.SlideVelX, m.Velocity.Y, m.SlideVelZ)
		end

		alignWithFloor(m)
	end
end

local function commonSlideActionWithJump(m: Mario, stopAction: number, airAction: number, anim: Animation)
	if m.ActionTimer == 5 then
		if m.Input:Has(InputFlags.A_PRESSED) then
			return m:SetJumpingAction(Action.JUMP)
		end
	else
		m.ActionTimer += 1
	end

	if updateSliding(m, 4) then
		m:SetAction(stopAction)
	end

	commonSlideAction(m, stopAction, airAction, anim)
	return false
end

local function commonLandingCancels(
	m: Mario,
	landingAction: LandingAction,
	setAPressAction: (Mario, number, any) -> any
)
	local floor = m.Floor

	if floor and floor.Normal.Y < 0.2923717 then
		return m:PushOffSteepFloor(Action.FREEFALL)
	end

	m.DoubleJumpTimer = landingAction.JumpTimer

	if shouldBeginSliding(m) then
		return m:SetAction(Action.BEGIN_SLIDING)
	end

	if m.Input:Has(InputFlags.FIRST_PERSON) then
		return m:SetAction(landingAction.EndAction)
	end

	m.ActionTimer += 1

	if m.ActionTimer >= landingAction.NumFrames then
		return m:SetAction(landingAction.EndAction)
	end

	if m.Input:Has(InputFlags.A_PRESSED) then
		return setAPressAction(m, landingAction.APressedAction, 0)
	end

	if m.Input:Has(InputFlags.OFF_FLOOR) then
		return m:SetAction(Action.FREEFALL)
	end

	return false
end

local function stomachSlideAction(m: Mario, stopAction: number, airAction: number, anim: Animation)
	if m.ActionTimer == 5 then
		if not m.Input:Has(InputFlags.ABOVE_SLIDE) and m.Input:Has(InputFlags.A_PRESSED, InputFlags.B_PRESSED) then
			return m:SetAction(if m.ForwardVel >= 0 then Action.FORWARD_ROLLOUT else Action.BACKWARD_ROLLOUT)
		end
	else
		m.ActionTimer += 1
	end

	if updateSliding(m, 4) then
		return m:SetAction(stopAction)
	end

	commonSlideAction(m, stopAction, airAction, anim)
	return false
end

local function commonGroundKnockbackAction(
	m: Mario,
	anim: Animation,
	minFrame: number,
	playHeavyLanding: boolean,
	attacked: number
)
	local animFrame

	if playHeavyLanding then
		m:PlayHeavyLandingSoundOnce(Sounds.ACTION_TERRAIN_BODY_HIT_GROUND)
	end

	if attacked > 0 then
		m:PlaySoundIfNoFlag(Sounds.MARIO_ATTACKED, MarioFlags.MARIO_SOUND_PLAYED)
	else
		m:PlaySoundIfNoFlag(Sounds.MARIO_OOOF, MarioFlags.MARIO_SOUND_PLAYED)
	end

	m.ForwardVel = math.clamp(m.ForwardVel, -32, 32)
	animFrame = m:SetAnimation(anim)

	if animFrame < minFrame then
		applyLandingAccel(m, 0.9)
	elseif m.ForwardVel > 0 then
		m:SetForwardVel(0.1)
	else
		m:SetForwardVel(-0.1)
	end

	if m:PerformGroundStep() == GroundStep.LEFT_GROUND then
		if m.ForwardVel >= 0 then
			m:SetAction(Action.FORWARD_AIR_KB, attacked)
		else
			m:SetAction(Action.BACKWARD_AIR_KB, attacked)
		end
	elseif m:IsAnimAtEnd() then
		if m.Health < 0x100 then
			m:SetAction(Action.STANDING_DEATH)
		else
			if attacked > 0 then
				m.InvincTimer = 30
			end

			m:SetAction(Action.IDLE)
		end
	end

	return animFrame
end

local function commonLandingAction(m: Mario, anim: Animation)
	if m.Input:Has(InputFlags.NONZERO_ANALOG) then
		applyLandingAccel(m, 0.98)
	elseif m.ForwardVel > 16 then
		applySlopeDecel(m, 2)
	else
		m.Velocity *= Vector3.new(1, 0, 1)
	end

	local stepResult = m:PerformGroundStep()

	if stepResult == GroundStep.LEFT_GROUND then
		m:SetAction(Action.FREEFALL)
	elseif stepResult == GroundStep.HIT_WALL then
		m:SetAnimation(Animations.PUSHING)
	end

	if m.ForwardVel > 16 then
		m.ParticleFlags:Add(ParticleFlags.DUST)
	end

	m:SetAnimation(anim)
	m:PlayLandingSoundOnce(Sounds.ACTION_TERRAIN_LANDING)

	return stepResult
end

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

DEF_ACTION(Action.WALKING, function(m: Mario)
	local startPos
	local startYaw = m.FaceAngle.Y

	if shouldBeginSliding(m) then
		return m:SetAction(Action.BEGIN_SLIDING)
	end

	if m.Input:Has(InputFlags.FIRST_PERSON) then
		return beginBrakingAction(m)
	end

	if m.Input:Has(InputFlags.A_PRESSED) then
		return m:SetJumpFromLanding()
	end

	if checkGroundDiveOrPunch(m) then
		return true
	end

	if m.Input:Has(InputFlags.NO_MOVEMENT) then
		return beginBrakingAction(m)
	end

	if analogStickHeldBack(m) and m.ForwardVel >= 16 then
		return m:SetAction(Action.TURNING_AROUND)
	end

	if m.Input:Has(InputFlags.Z_PRESSED) then
		return m:SetAction(Action.CROUCH_SLIDE)
	end

	local step
	do
		m.ActionState = 0
		startPos = m.Position

		updateWalkingSpeed(m)
		step = m:PerformGroundStep()
	end

	if step == GroundStep.LEFT_GROUND then
		m:SetAction(Action.FREEFALL)
		m:SetAnimation(Animations.GENERAL_FALL)
	elseif step == GroundStep.NONE then
		animAndAudioForWalk(m)

		if m.IntendedMag - m.ForwardVel > 16 then
			m.ParticleFlags:Add(ParticleFlags.DUST)
		end
	elseif step == GroundStep.HIT_WALL then
		pushOrSidleWall(m, startPos)
		m.ActionTimer = 0
	end

	checkLedgeClimbDown(m)
	tiltBodyWalking(m, startYaw)

	return false
end)

DEF_ACTION(Action.MOVE_PUNCHING, function(m: Mario)
	if shouldBeginSliding(m) then
		return m:SetAction(Action.BEGIN_SLIDING)
	end

	if m.ActionState == 0 and m.Input:Has(InputFlags.A_DOWN) then
		return m:SetAction(Action.JUMP_KICK)
	end

	m.ActionState = 1
	m:UpdatePunchSequence()

	if m.ForwardVel > 0 then
		applySlopeDecel(m, 0.5)
	else
		m.ForwardVel += 8

		if m.ForwardVel >= 0 then
			m.ForwardVel = 0
		end

		applySlopeAccel(m)
	end

	local step = m:PerformGroundStep()

	if step == GroundStep.LEFT_GROUND then
		m:SetAction(Action.FREEFALL)
	elseif step == GroundStep.NONE then
		m.ParticleFlags:Add(ParticleFlags.DUST)
	end

	return false
end)

DEF_ACTION(Action.TURNING_AROUND, function(m: Mario)
	if m.Input:Has(InputFlags.ABOVE_SLIDE) then
		return m:SetAction(Action.BEGIN_SLIDING)
	end

	if m.Input:Has(InputFlags.A_PRESSED) then
		return m:SetAction(Action.SIDE_FLIP)
	end

	if not analogStickHeldBack(m) then
		return m:SetAction(Action.WALKING)
	end

	if applySlopeDecel(m, 2) then
		return beginWalkingAction(m, 8, Action.FINISH_TURNING_AROUND)
	end

	m:PlaySound(Sounds.MOVING_TERRAIN_SLIDE)
	m:AdjustSoundForSpeed()

	local step = m:PerformGroundStep()

	if step == GroundStep.LEFT_GROUND then
		m:SetAction(Action.FREEFALL)
	elseif step == GroundStep.NONE then
		m.ParticleFlags:Add(ParticleFlags.DUST)
	end

	if m.ForwardVel >= 18 then
		m:SetAnimation(Animations.TURNING_PART1)
	else
		m:SetAnimation(Animations.TURNING_PART2)
		m.GfxAngle += Vector3int16.new(0, 0x8000, 0)

		if m:IsAnimAtEnd() then
			if m.ForwardVel > 0 then
				beginWalkingAction(m, -m.ForwardVel, Action.WALKING)
			else
				beginWalkingAction(m, 8, Action.WALKING)
			end
		end
	end

	return false
end)

DEF_ACTION(Action.FINISH_TURNING_AROUND, function(m: Mario)
	if m.Input:Has(InputFlags.ABOVE_SLIDE) then
		return m:SetAction(Action.BEGIN_SLIDING)
	end

	if m.Input:Has(InputFlags.A_PRESSED) then
		return m:SetAction(Action.SIDE_FLIP)
	end

	updateWalkingSpeed(m)
	m:SetAnimation(Animations.TURNING_PART2)

	if m:PerformGroundStep() == GroundStep.LEFT_GROUND then
		m:SetAction(Action.FREEFALL)
	end

	if m:IsAnimAtEnd() then
		m:SetAction(Action.WALKING)
		m.AnimSkipInterp = 2
	end

	return false
end)

DEF_ACTION(Action.BRAKING, function(m: Mario)
	if not m.Input:Has(InputFlags.FIRST_PERSON) then
		if
			m.Input:Has(InputFlags.NONZERO_ANALOG, InputFlags.A_PRESSED, InputFlags.OFF_FLOOR, InputFlags.ABOVE_SLIDE)
		then
			return m:CheckCommonActionExits()
		end
	end

	if applySlopeDecel(m, 2) then
		return m:SetAction(Action.BRAKING_STOP)
	end

	if m.Input:Has(InputFlags.B_PRESSED) then
		return m:SetAction(Action.MOVE_PUNCHING)
	end

	local stepResult = m:PerformGroundStep()

	if stepResult == GroundStep.LEFT_GROUND then
		m:SetAction(Action.FREEFALL)
	elseif stepResult == GroundStep.NONE then
		m.ParticleFlags:Add(ParticleFlags.DUST)
	elseif stepResult == GroundStep.HIT_WALL then
		slideBonk(m, Action.BACKWARD_GROUND_KB, Action.BRAKING_STOP)
	end

	m:PlaySound(Sounds.MOVING_TERRAIN_SLIDE)
	m:SetAnimation(Animations.SKID_ON_GROUND)
	m:AdjustSoundForSpeed()

	return false
end)

DEF_ACTION(Action.DECELERATING, function(m: Mario)
	if not m.Input:Has(InputFlags.FIRST_PERSON) then
		if shouldBeginSliding(m) then
			return m:SetAction(Action.BEGIN_SLIDING)
		end

		if m.Input:Has(InputFlags.A_PRESSED) then
			return m:SetJumpFromLanding()
		end

		if checkGroundDiveOrPunch(m) then
			return true
		end

		if m.Input:Has(InputFlags.NONZERO_ANALOG) then
			return m:SetAction(Action.WALKING)
		end

		if m.Input:Has(InputFlags.Z_PRESSED) then
			return m:SetAction(Action.CROUCH_SLIDE)
		end
	end

	if updateDeceleratingSpeed(m) then
		return m:SetAction(Action.IDLE)
	end

	local slopeClass = m:GetFloorClass()
	local stepResult = m:PerformGroundStep()

	if stepResult == GroundStep.LEFT_GROUND then
		m:SetAction(Action.FREEFALL)
	elseif stepResult == GroundStep.HIT_WALL then
		if slopeClass == SurfaceClass.VERY_SLIPPERY then
			m:BonkReflection(true)
		else
			m:SetForwardVel(0)
		end
	end

	if slopeClass == SurfaceClass.VERY_SLIPPERY then
		m:SetAnimation(Animations.IDLE_HEAD_LEFT)
		m:PlaySound(Sounds.MOVING_TERRAIN_SLIDE)

		m:AdjustSoundForSpeed()
		m.ParticleFlags:Add(ParticleFlags.DUST)
	else
		local accel = m.ForwardVel / 4 * 0x10000

		if accel < 0x1000 then
			accel = 0x1000
		end

		m:SetAnimationWithAccel(Animations.WALKING, accel)
		playStepSound(m, 10, 49)
	end

	return false
end)

DEF_ACTION(Action.CRAWLING, function(m: Mario)
	if shouldBeginSliding(m) then
		return m:SetAction(Action.BEGIN_SLIDING)
	end

	if m.Input:Has(InputFlags.FIRST_PERSON) then
		return m:SetAction(Action.STOP_CRAWLING)
	end

	if m.Input:Has(InputFlags.A_PRESSED) then
		return m:SetJumpingAction(Action.JUMP)
	end

	if checkGroundDiveOrPunch(m) then
		return true
	end

	if m.Input:Has(InputFlags.NO_MOVEMENT) then
		return m:SetAction(Action.STOP_CRAWLING)
	end

	if not m.Input:Has(InputFlags.Z_DOWN) then
		return m:SetAction(Action.STOP_CRAWLING)
	end

	m.IntendedMag *= 0.1
	updateWalkingSpeed(m)

	local stepResult = m:PerformGroundStep()

	if stepResult == GroundStep.LEFT_GROUND then
		m:SetAction(Action.FREEFALL)
	elseif stepResult == GroundStep.HIT_WALL then
		if m.ForwardVel > 10 then
			m:SetForwardVel(10)
		end

		alignWithFloor(m)
	elseif stepResult == GroundStep.NONE then
		alignWithFloor(m)
	end

	local accel = m.IntendedMag * 2 * 0x10000
	m:SetAnimationWithAccel(Animations.CRAWLING, accel)
	playStepSound(m, 26, 79)

	return false
end)

DEF_ACTION(Action.BURNING_GROUND, function(m: Mario)
	if m.Input:Has(InputFlags.A_PRESSED) then
		return m:SetAction(Action.BURNING_JUMP)
	end

	m.BurnTimer += 2

	if m.BurnTimer > 160 then
		return m:SetAction(Action.WALKING)
	end

	if m.ForwardVel < 8 then
		m.ForwardVel = 8
	end

	if m.ForwardVel > 48 then
		m.ForwardVel = 48
	end

	m.ForwardVel = Util.ApproachFloat(m.ForwardVel, 32, 4, 1)

	if m.Input:Has(InputFlags.NONZERO_ANALOG) then
		local faceY = m.IntendedYaw - Util.ApproachFloat(m.IntendedYaw - m.FaceAngle.Y, 0, 0x600)
		m.FaceAngle = Util.SetY(m.FaceAngle, faceY)
	end

	applySlopeAccel(m)

	if m:PerformGroundStep() == GroundStep.LEFT_GROUND then
		m:SetAction(Action.BURNING_FALL)
	end

	local accel = m.ForwardVel / 2 * 0x10000
	m:SetAnimationWithAccel(Animations.RUNNING, accel)
	playStepSound(m, 9, 45)

	m.ParticleFlags:Add(ParticleFlags.FIRE)
	m:PlaySound(Sounds.MOVING_LAVA_BURN)

	m.Health -= 10

	if m.Health < 0x100 then
		m:SetAction(Action.STANDING_DEATH)
	end

	m.BodyState.EyeState = MarioEyes.DEAD
	return false
end)

DEF_ACTION(Action.BUTT_SLIDE, function(m: Mario)
	local cancel = commonSlideActionWithJump(m, Action.BUTT_SLIDE_STOP, Action.BUTT_SLIDE_AIR, Animations.SLIDE)
	tiltBodyButtSlide(m)

	return cancel
end)

DEF_ACTION(Action.CROUCH_SLIDE, function(m: Mario)
	if m.Input:Has(InputFlags.ABOVE_SLIDE) then
		return m:SetAction(Action.BUTT_SLIDE)
	end

	if m.ActionTimer < 30 then
		m.ActionTimer += 1

		if m.Input:Has(InputFlags.A_PRESSED) then
			if m.ForwardVel > 10 then
				return m:SetJumpingAction(Action.LONG_JUMP)
			end
		end
	end

	if m.Input:Has(InputFlags.B_PRESSED) then
		if m.ForwardVel >= 10 then
			return m:SetAction(Action.SLIDE_KICK)
		else
			return m:SetAction(Action.MOVE_PUNCHING, 9)
		end
	end

	if m.Input:Has(InputFlags.A_PRESSED) then
		return m:SetAction(Action.JUMP)
	end

	if m.Input:Has(InputFlags.FIRST_PERSON) then
		return m:SetAction(Action.BRAKING)
	end

	return commonSlideActionWithJump(m, Action.CROUCHING, Action.FREEFALL, Animations.START_CROUCHING)
end)

DEF_ACTION(Action.SLIDE_KICK_SLIDE, function(m: Mario)
	local step

	if m.Input:Has(InputFlags.A_PRESSED) then
		return m:SetAction(Action.FORWARD_ROLLOUT)
	end

	m:SetAnimation(Animations.SLIDE_KICK)

	if m:IsAnimAtEnd() and m.ForwardVel < 1 then
		return m:SetAction(Action.SLIDE_KICK_SLIDE_STOP)
	end

	updateSliding(m, 1)
	step = m:PerformGroundStep()

	if step == GroundStep.LEFT_GROUND then
		m:SetAction(Action.FREEFALL, 2)
	elseif step == GroundStep.HIT_WALL then
		m:BonkReflection(true)
		m.ParticleFlags:Add(ParticleFlags.VERTICAL_STAR)
		m:SetAction(Action.BACKWARD_GROUND_KB)
	end

	m:PlaySound(Sounds.MOVING_TERRAIN_SLIDE)
	m.ParticleFlags:Add(ParticleFlags.DUST)

	return false
end)

DEF_ACTION(Action.STOMACH_SLIDE, function(m: Mario)
	if m.ActionTimer == 5 then
		if not m.Input:Has(InputFlags.ABOVE_SLIDE) and m.Input:Has(InputFlags.A_PRESSED, InputFlags.B_PRESSED) then
			return m:SetAction(if m.ForwardVel >= 0 then Action.FORWARD_ROLLOUT else Action.BACKWARD_ROLLOUT)
		end
	else
		m.ActionTimer += 1
	end

	if updateSliding(m, 4) then
		return m:SetAction(Action.STOMACH_SLIDE_STOP)
	end

	commonSlideAction(m, Action.STOMACH_SLIDE_STOP, Action.FREEFALL, Animations.SLIDE_DIVE)
	return false
end)

DEF_ACTION(Action.DIVE_SLIDE, function(m: Mario)
	if not m.Input:Has(InputFlags.ABOVE_SLIDE) and m.Input:Has(InputFlags.A_PRESSED, InputFlags.B_PRESSED) then
		return m:SetAction(if m.ForwardVel >= 0 then Action.FORWARD_ROLLOUT else Action.BACKWARD_ROLLOUT)
	end

	m:PlayLandingSoundOnce(Sounds.ACTION_TERRAIN_BODY_HIT_GROUND)

	if updateSliding(m, 8) and m:IsAnimAtEnd() then
		m:SetForwardVel(0)
		m:SetAction(Action.STOMACH_SLIDE_STOP)
	end

	commonSlideAction(m, Action.STOMACH_SLIDE_STOP, Action.FREEFALL, Animations.DIVE)
	return false
end)

DEF_ACTION(Action.HARD_BACKWARD_GROUND_KB, function(m: Mario)
	local animFrame = commonGroundKnockbackAction(m, Animations.FALL_OVER_BACKWARDS, 43, true, m.ActionArg)

	if animFrame == 43 and m.Health < 0x100 then
		m:SetAction(Action.DEATH_ON_BACK)
	end

	if animFrame == 54 and m.PrevAction() == Action.SPECIAL_DEATH_EXIT then
		m:PlaySound(Sounds.MARIO_MAMA_MIA)
	end

	if animFrame == 69 then
		m:PlayLandingSoundOnce(Sounds.ACTION_TERRAIN_LANDING)
	end

	return false
end)

DEF_ACTION(Action.HARD_FORWARD_GROUND_KB, function(m: Mario)
	local animFrame = commonGroundKnockbackAction(m, Animations.LAND_ON_STOMACH, 21, true, m.ActionArg)

	if animFrame == 23 and m.Health < 0x100 then
		m:SetAction(Action.DEATH_ON_STOMACH)
	end

	return false
end)

DEF_ACTION(Action.BACKWARD_GROUND_KB, function(m: Mario)
	commonGroundKnockbackAction(m, Animations.BACKWARD_KB, 22, true, m.ActionArg)
	return false
end)

DEF_ACTION(Action.FORWARD_GROUND_KB, function(m: Mario)
	commonGroundKnockbackAction(m, Animations.FORWARD_KB, 20, true, m.ActionArg)
	return false
end)

DEF_ACTION(Action.SOFT_BACKWARD_GROUND_KB, function(m: Mario)
	commonGroundKnockbackAction(m, Animations.SOFT_BACK_KB, 100, false, m.ActionArg)
	return false
end)

DEF_ACTION(Action.SOFT_FORWARD_GROUND_KB, function(m: Mario)
	commonGroundKnockbackAction(m, Animations.SOFT_FRONT_KB, 100, false, m.ActionArg)
	return false
end)

DEF_ACTION(Action.GROUND_BONK, function(m: Mario)
	local animFrame = commonGroundKnockbackAction(m, Animations.GROUND_BONK, 32, true, m.ActionArg)

	if animFrame == 32 then
		m:PlayLandingSound(Sounds.ACTION_TERRAIN_LANDING)
	end

	return false
end)

DEF_ACTION(Action.JUMP_LAND, function(m: Mario)
	if commonLandingCancels(m, sJumpLandAction, m.SetJumpingAction) then
		return true
	end

	commonLandingAction(m, Animations.LAND_FROM_SINGLE_JUMP)
	return false
end)

DEF_ACTION(Action.FREEFALL_LAND, function(m: Mario)
	if commonLandingCancels(m, sFreefallLandAction, m.SetJumpingAction) then
		return true
	end

	commonLandingAction(m, Animations.GENERAL_LAND)
	return false
end)

DEF_ACTION(Action.SIDE_FLIP_LAND, function(m: Mario)
	if commonLandingCancels(m, sSideFlipLandAction, m.SetJumpingAction) then
		return true
	end

	if commonLandingAction(m, Animations.SLIDEFLIP_LAND) ~= GroundStep.HIT_WALL then
		--m.GfxAngle += Vector3int16.new(0, 0x8000, 0)
	end

	return false
end)

DEF_ACTION(Action.LONG_JUMP_LAND, function(m: Mario)
	if not m.Input:Has(InputFlags.Z_DOWN) then
		m.Input:Remove(InputFlags.A_PRESSED)
	end

	if commonLandingCancels(m, sLongJumpLandAction, m.SetJumpingAction) then
		return true
	end

	if not m.Input:Has(InputFlags.NONZERO_ANALOG) then
		m:PlaySoundIfNoFlag(Sounds.MARIO_UH, MarioFlags.MARIO_SOUND_PLAYED)
	end

	commonLandingAction(
		m,
		if m.LongJumpIsSlow then Animations.CROUCH_FROM_FAST_LONGJUMP else Animations.CROUCH_FROM_SLOW_LONGJUMP
	)

	return false
end)

DEF_ACTION(Action.DOUBLE_JUMP_LAND, function(m: Mario)
	if commonLandingCancels(m, sDoubleJumpLandAction, setTripleJumpAction) then
		return true
	end

	commonLandingAction(m, Animations.LAND_FROM_DOUBLE_JUMP)
	return false
end)

DEF_ACTION(Action.TRIPLE_JUMP_LAND, function(m: Mario)
	m.Input:Remove(InputFlags.A_PRESSED)

	if commonLandingCancels(m, sTripleJumpLandAction, m.SetJumpingAction) then
		return true
	end

	if not m.Input:Has(InputFlags.NONZERO_ANALOG) then
		m:PlaySoundIfNoFlag(Sounds.MARIO_HAHA, MarioFlags.MARIO_SOUND_PLAYED)
	end

	commonLandingAction(m, Animations.TRIPLE_JUMP_LAND)
	return false
end)

DEF_ACTION(Action.BACKFLIP_LAND, function(m: Mario)
	if not m.Input:Has(InputFlags.Z_DOWN) then
		m.Input:Remove(InputFlags.A_PRESSED)
	end

	if commonLandingCancels(m, sBackflipLandAction, m.SetJumpingAction) then
		return true
	end

	if not m.Input:Has(InputFlags.NONZERO_ANALOG) then
		m:PlaySoundIfNoFlag(Sounds.MARIO_HAHA, MarioFlags.MARIO_SOUND_PLAYED)
	end

	commonLandingAction(m, Animations.TRIPLE_JUMP_LAND)
	return false
end)

DEF_ACTION(Action.PUNCHING, function(m: Mario)
	if m.Input:Has(InputFlags.STOMPED) then
		return m:SetAction(Action.SHOCKWAVE_BOUNCE)
	end

	if m.Input:Has(InputFlags.NONZERO_ANALOG, InputFlags.A_PRESSED, InputFlags.OFF_FLOOR, InputFlags.ABOVE_SLIDE) then
		return m:CheckCommonActionExits()
	end

	if m.ActionState and m.Input:Has(InputFlags.A_DOWN) then
		return m:SetAction(Action.JUMP_KICK)
	end

	m.ActionState = 1

	if m.ActionArg == 0 then
		m.ActionTimer = 7
	end

	m:SetForwardVel(sPunchingForwardVelocities[m.ActionTimer + 1])

	if m.ActionTimer > 0 then
		m.ActionTimer -= 1
	end

	m:UpdatePunchSequence()
	m:PerformGroundStep()

	return false
end)
