if SM64COOPDX_VERSION == nil then return end
local play_step_sound,set_mario_action,play_character_sound,vec3f_length,perform_ground_step,vec3f_copy,perform_air_step,clampf,vec3f_add,set_mario_particle_flags,play_mario_heavy_landing_sound = play_step_sound,set_mario_action,play_character_sound,vec3f_length,perform_ground_step,vec3f_copy,perform_air_step,clampf,vec3f_add,set_mario_particle_flags,play_mario_heavy_landing_sound



local function play_local_footstep_sounds(m,rate,max) 
	cl_footstepTimer = cl_footstepTimer + rate
	if (cl_footstepTimer >= max) then
		m.marioObj.header.gfx.animInfo.animFrame = 0
		play_step_sound(m, 0, 1)
		cl_footstepTimer = 0
	end
end

-------------------------------
-- Code for our local player --
-------------------------------

--- @param m MarioState
local function local_act_dm_grounded_movement(m)
	local valid_update = local_update(m) 

	local metal_underwater = (m.flags & MARIO_METAL_CAP) ~= 0 and m.pos.y <= m.waterLevel-sv_playersubmergelevel
	local crouch = gPlayerSyncTable[m.playerIndex].playerCrouching
	
	if (metal_underwater and m.action ~= ACT_DM_GROUNDED_MOVEMENT_METAL) then
		SV_PlayerMovement_Store(m,true)
		set_mario_action(m, ACT_DM_GROUNDED_MOVEMENT_METAL, 0)
		m.action = ACT_DM_GROUNDED_MOVEMENT_METAL
		SV_PlayerMovement_Store(m,false)
		-- return
	elseif (m.action == ACT_DM_GROUNDED_MOVEMENT_METAL and not metal_underwater) then
		set_mario_action(m, ACT_DM_GROUNDED_MOVEMENT, 0)
		m.action = ACT_DM_GROUNDED_MOVEMENT
		-- return
	end
	
	if (m.floor ~= nil) then
		if (m.floor.normal.y <= SV_GetSurfaceStandableMinimum(m.floor.type,m.area.terrainType)) then
			SV_PlayerMovement_Relative(m)
			set_mario_action(m, ACT_DM_AIR_MOVEMENT, 0)
			act_dm_air_movement(m)
			return
		end
	end
	
	if ((gGlobalSyncTable.Convar_PlayerAutoHop and (m.controller.buttonDown & A_BUTTON) ~= 0) or (not gGlobalSyncTable.Convar_PlayerAutoHop and (m.controller.buttonPressed & A_BUTTON) ~= 0)) and no_dialog_open() and m.floor ~= nil then
		set_character_animation(m, CHAR_ANIM_IDLE_HEAD_CENTER)
		
		-- m.controller.buttonPressed = m.controller.buttonPressed & ~A_BUTTON
		
		if (m.vel.y < 0) then m.vel.y = m.vel.y * 0.33 end
		
		m.vel.y = m.vel.y + ifelse(metal_underwater,135*sv_jumpheight,140*sv_jumpheight)
		
		if (m.vel.y > 320*sv_jumpheight) then m.vel.y = 320*sv_jumpheight end
		
		play_mario_jump_sound(m)
		
		SV_PlayerMovement_Relative(m)
		
		-- SV_PlayerMovement_Store(m,true)
		
		set_mario_action(m, ACT_DM_AIR_MOVEMENT, 0)
		act_dm_air_movement(m)
		return
	end
	
	SV_PlayerMovement(m)
	
	set_character_animation(m, CHAR_ANIM_A_POSE)
	if (not cl_bowserthrow) then
		local len = vec3f_length(vec3_qscale(m.vel,1,0,1))
		if (len < 1) then
			m.faceAngle.y = m.area.camera.yaw + 0x8000
			if (m.action == ACT_DM_GROUNDED_MOVEMENT) then m.action = ACT_DM_GROUNDED_IDLE end 
		else
			play_local_footstep_sounds(m,len * 0.08,60) 
			m.faceAngle.y = atan2s(m.vel.z, m.vel.x)
			if (m.action == ACT_DM_GROUNDED_IDLE) then m.action = ACT_DM_GROUNDED_MOVEMENT end 
		end
	end
	
	if (valid_update) then
	m.forwardVel = vec3f_length(vec3_qscale(m.vel,sv_scalerate,0,sv_scalerate))
	else m.forwardVel = 0 end
	
	SV_PlayerMovement_Scale(m,false)
	local stepResult = perform_ground_step(m)
	SV_PlayerMovement_Scale(m,true)
	
	if (stepResult == GROUND_STEP_LEFT_GROUND) then 
		SV_PlayerMovement_Relative(m)
		if (m.action == ACT_DM_GROUNDED_MOVEMENT_METAL) then
			set_mario_action(m, ACT_DM_AIR_MOVEMENT_METAL, 0)
		else
			set_mario_action(m, ACT_DM_AIR_MOVEMENT, 0)
		end
	end
	
	if m.wall ~= nil and (stepResult == GROUND_STEP_HIT_WALL) then --or (stepResult == GROUND_STEP_HIT_WALL_CONTINUE_QSTEPS) or (stepResult == GROUND_STEP_HIT_WALL_STOP_QSTEPS)) then 
		vec3f_copy(m.vel, PHYS_CollideWithNormal(m.vel,m.wall.normal,0.0))
	end
	
	if (m.pos.y <= m.waterLevel-sv_playersubmergelevel and m.waterLevel > m.floorHeight + sv_playersubmergelevel) and (m.flags & MARIO_METAL_CAP) == 0 then
		if (m.action == ACT_DM_GROUNDED_MOVEMENT_METAL) then
			set_mario_action(m, ACT_DM_AIR_MOVEMENT_METAL, 0)
		else
			set_mario_action(m, ACT_DM_WATER_MOVEMENT, 0)
		end
	end
end
--- @param m MarioState
local function local_act_dm_air_movement(m)
	local valid_update = local_update(m) 
	-- AngleVectors(m)
	local metal_underwater = (m.flags & MARIO_METAL_CAP) ~= 0 and m.pos.y <= m.waterLevel-sv_playersubmergelevel
	local act = m.action
	local crouch = gPlayerSyncTable[m.playerIndex].playerCrouching
	
	if (metal_underwater and act ~= ACT_DM_AIR_MOVEMENT_METAL) then
		SV_PlayerMovement_Store(m,true)
		set_mario_action(m, ACT_DM_AIR_MOVEMENT_METAL, 0)
		m.action = ACT_DM_AIR_MOVEMENT_METAL
		SV_PlayerMovement_Store(m,false)
		-- return
	elseif (act == ACT_DM_AIR_MOVEMENT_METAL and not metal_underwater) then
		set_mario_action(m, ACT_DM_AIR_MOVEMENT, 0)
		m.action = ACT_DM_AIR_MOVEMENT
		-- return
	end
	
	if (m.controller.buttonPressed & Z_TRIG) ~= 0 and (m.controller.buttonDown & A_BUTTON) == 0 and sv_groundpound and m.heldObj == nil and no_dialog_open() then --and m.prevAction ~= ACT_DM_WATER_MOVEMENT then 
		m.vel.y = 0
		m.controller.buttonPressed = m.controller.buttonPressed & ~Z_TRIG
		set_mario_action(m, ifelse(metal_underwater,ACT_DM_GROUND_POUND_METAL,ACT_GROUND_POUND), 1)
		play_sound(SOUND_ACTION_TWIRL,m.marioObj.header.gfx.cameraToObject)
		return 
	end
	
	SV_PlayerMovement(m)
	
	local len_spd = vec3f_length(vec3_qscale(m.vel,1,0,1))
	
	if (len_spd < 1) then
		m.faceAngle.y = m.area.camera.yaw + 0x8000
	else
		m.faceAngle.y = atan2s(m.vel.z, m.vel.x)
	end
	set_character_animation(m, CHAR_ANIM_A_POSE)
	
	if (metal_underwater) then
		SV_UserGravity(m,0.66)
	else
		SV_UserGravity(m,1.0)
	end
	
	if ((m.flags & MARIO_WING_CAP) ~= 0 and (m.controller.buttonPressed & A_BUTTON) ~= 0) and no_dialog_open() then 
		if (m.vel.y < 0) then m.vel.y = 0 end
		m.vel.y = m.vel.y + ifelse(metal_underwater,100,200)
		if (m.vel.y > 320) then m.vel.y = 320 end
		set_mario_particle_flags(m, (PARTICLE_DUST | PARTICLE_MIST_CIRCLE), false)
	end
	
	local preStepYVel = m.vel.y
	
	if (valid_update) then
	m.forwardVel = len_spd*sv_scalerate
	else m.forwardVel = 0 end
	
	SV_PlayerMovement_Scale(m,false)
	local stepResult = perform_air_step(m,0)
	SV_PlayerMovement_Scale(m,true)
	
	if (stepResult == AIR_STEP_LANDED) then 
		if (m.floor ~= nil) then
			if (m.floor.normal.y > SV_GetSurfaceStandableMinimum(m.floor.type,m.area.terrainType) and ifelse(sv_sticky,m.vel.y < (sv_gravity*sv_tickrate)*2,true)) then
				vec3f_copy(m.vel, PHYS_CollideWithNormal(m.vel,m.floor.normal,0.0))
				-- SV_PlayerMovement_Store(m,true)
				
				cl_airstrafelimiter = 0
				m.marioObj.header.gfx.animInfo.animFrame = 0
				play_step_sound(m, 0, 1)
				gLakituState.roll = gLakituState.roll + clampf(-1.75*(preStepYVel+200),0,1000)
				
				if (m.action == ACT_DM_AIR_MOVEMENT_METAL) then
					set_mario_action(m, ACT_DM_GROUNDED_MOVEMENT_METAL, 0)
				else
					set_mario_action(m, ACT_DM_GROUNDED_MOVEMENT, 0)
				end
				return
			else
				vec3f_copy(m.vel, PHYS_CollideWithNormal(m.vel,m.floor.normal,0))
				vec3f_add(m.pos,vec3_scale(m.floor.normal,6))
				-- vec3f_add(m.vel,vec3_scale(m.floor.normal,clampf(preStepYVel*0.75,-1,1)))
				return
			end
		end
	end
	if (stepResult == AIR_STEP_HIT_WALL) and m.wall ~= nil then 
		if (m.controller.buttonDown & ifelse(cl_altWalljumpKey,B_BUTTON,A_BUTTON)) ~= 0 and sv_walljump and no_dialog_open() and m.heldObj == nil and len_spd > 100 then 
			cl_airstrafelimiter = 0.66
			vec3f_copy(m.vel, PHYS_CollideWithNormal(m.vel,m.wall.normal,0.965))
			set_mario_particle_flags(m, (PARTICLE_VERTICAL_STAR | PARTICLE_DUST), false)
			m.vel.y = 250*sv_jumpheight
			play_sound(SOUND_ACTION_BONK,m.marioObj.header.gfx.cameraToObject)
			play_character_sound(m,CHAR_SOUND_YAH_WAH_HOO)
		else	
			vec3f_copy(m.vel, PHYS_CollideWithNormal(m.vel,m.wall.normal,0.05))
		end
	end
	
	if (m.pos.y <= m.waterLevel-sv_playersubmergelevel and m.waterLevel > m.floorHeight + sv_playersubmergelevel) and (m.flags & MARIO_METAL_CAP) == 0 then
		if (m.action ~= ACT_DM_AIR_MOVEMENT_METAL) then
			set_mario_action(m, ACT_DM_WATER_MOVEMENT, 0)
		end
	end
end
--- @param m MarioState
local function local_act_dm_water_movement(m)
	local valid_update = local_update(m) 
	-- AngleVectors(m)
	SV_WaterMovement(m)
	
	SV_PlayerMovement_Store(m,true)
	
	if (m.playerIndex == 0) then
		if (vec3f_length(vec3_qscale(m.vel,1,0,1)) < 1) then
			m.faceAngle.y = m.area.camera.yaw + 0x8000
		else
			m.faceAngle.y = atan2s(m.vel.z, m.vel.x)
		end
		set_character_animation(m, CHAR_ANIM_A_POSE)
	else
		local scale = m.forwardVel
		if (scale < 10) then
			set_character_animation(m, CHAR_ANIM_WATER_IDLE)
		else
			set_character_anim_with_accel(m, CHAR_ANIM_FLUTTERKICK, scale*0x800)
		end
	end
	
	if (valid_update) then
	m.forwardVel = vec3f_length(vec3_qscale(m.vel,sv_scalerate,0,sv_scalerate))
	else m.forwardVel = 0 end
		
	if (m.pos.y+(m.vel.y*sv_scalerate) < m.floorHeight + 2) then
		m.pos.y = m.floorHeight + 2
		m.vel.y = 0
	end
	
	SV_PlayerMovement_Scale(m,false)
	local stepResult = perform_air_step(m,0)
	SV_PlayerMovement_Scale(m,true)
	
	if (stepResult == AIR_STEP_HIT_WALL) and m.wall ~= nil then 
		vec3f_copy(m.vel, PHYS_CollideWithNormal(m.vel,m.wall.normal,0.05))
	end
	
	local isSnowy = ((m.area.terrainType & TERRAIN_MASK) == TERRAIN_SNOW)
	
	if (m.area.camera.pos.y >= m.waterLevel - 15 and not isSnowy) or mario_holding_underwater_shell(m) then
		m.healCounter = 1
		if (m.controller.buttonDown & A_BUTTON) ~= 0 and m.vel.y > 0 then
			m.vel.y = 180
		end
	elseif (not mario_holding_underwater_shell(m)) then
		m.health = m.health - ifelse(isSnowy,3,1)
	end
		
	if (m.pos.y > m.waterLevel-sv_playersubmergelevel) then
		set_mario_action(m, ACT_DM_AIR_MOVEMENT, 0)
		return
	end
end
--- @param m MarioState
local function local_act_dm_ground_pound(m)
	local valid_update = local_update(m) 
	
	local metal_underwater = (m.flags & MARIO_METAL_CAP) ~= 0 and m.pos.y <= m.waterLevel-sv_playersubmergelevel
	
	if (metal_underwater and act ~= ACT_DM_GROUND_POUND_METAL) then
		SV_PlayerMovement_Store(m,true)
		set_mario_action(m, ACT_DM_GROUND_POUND_METAL, 0)
		SV_PlayerMovement_Store(m,false)
	elseif (act == ACT_DM_AIR_MOVEMENT_METAL and not metal_underwater) then
		set_mario_action(m, ACT_DM_GROUND_POUND, 0)
	end
	
	if (m.controller.buttonPressed & Z_TRIG) ~= 0 then
		m.vel.x = 0
		m.vel.z = 0
	end
	
	m.faceAngle.y = m.area.camera.yaw + 0x8000
	set_character_animation(m, CHAR_ANIM_A_POSE)
	
	SV_UserGravityEx(m,1.0,0.75)
	
	SV_PlayerMovement_Scale(m,false)
	local stepResult = perform_air_step(m,0)
	SV_PlayerMovement_Scale(m,true)
	
	if (stepResult == AIR_STEP_LANDED) then 
		if (m.floor ~= nil) then
			play_mario_heavy_landing_sound(m, SOUND_ACTION_TERRAIN_HEAVY_LANDING)
			set_mario_particle_flags(m, (PARTICLE_MIST_CIRCLE | PARTICLE_HORIZONTAL_STAR), false)
				gLakituState.roll = gLakituState.roll + 500
			if (m.controller.buttonDown & A_BUTTON) ~= 0 and m.health > 0xFF then
				SV_HandleSpecialFloors(m,true)
			
				local direction = GetAngleVectors(m).forward
				local yV = m.vel.y * 0.32 * sv_scalerate
				local len = vec3f_length(vec3_qscale(m.vel,1,0,1))
				
				vec3f_copy(m.vel,vec3_scale(direction,len*0.895))
				
				vec3f_add(m.vel,vec3_qscale(m.floor.normal,-0.3,85 * clampf((len+math.abs(yV)) / 320, 0.5, 1.0),-0.3))
				
				m.vel.y = (m.vel.y + (250 * clampf((len+math.abs(yV)) / 320, 0.65, 1.0)))*sv_jumpheight
				-- PHYS_CollideWithNormal(m.vel,m.floor.normal,0.0,1.0)
				
				set_mario_action(m, ifelse(metal_underwater,ACT_DM_AIR_MOVEMENT_METAL,ACT_DM_AIR_MOVEMENT), 0)
				play_character_sound(m,CHAR_SOUND_YAHOO)
			else
				m.forwardVel = 0
				m.vel.x = 0
				m.vel.y = 0
				m.vel.z = 0
				set_mario_action(m, ifelse(metal_underwater,ACT_DM_GROUNDED_MOVEMENT_METAL,ACT_GROUND_POUND_LAND), 0)
			end
			return;
		end
	end
end
--- @param m MarioState
local function local_act_dm_pole_movement(m)
	local_update(m) 
	
	m.vel.x = 0
	m.vel.z = 0
	
	if (m.health <= 0xFF or m.usedObj == nil or (m.controller.buttonPressed & Z_TRIG) ~= 0) then 
		m.vel.y = 0
		m.forwardVel = 3
		m.controller.buttonPressed = m.controller.buttonPressed & ~(Z_TRIG|A_BUTTON)
		return set_mario_action(m, ACT_DM_AIR_MOVEMENT, 0)
	end
	
	m.faceAngle.y = m.area.camera.yaw + 0x8000
	
	if (m.controller.buttonPressed & A_BUTTON) ~= 0 then 
		m.vel.y = 12
		m.forwardVel = 36
		return set_mario_action(m, ACT_WALL_KICK_AIR, 0)
	end
	
	set_character_animation(m, CHAR_ANIM_A_POSE)
	
	local o = m.usedObj
	local i = ifelse(gFirstPersonCamera.pitch < 0,1.0,-1.0)
	m.pos.y = m.pos.y + (m.usedObj.oPosY-cl_poleY)
	cl_poleY = m.usedObj.oPosY
	
	m.pos.x = o.oPosX + sins(m.faceAngle.y)
	m.pos.z = o.oPosZ + coss(m.faceAngle.y)
	
	m.vel.y = m.controller.rawStickY*0.66*i
	local intendedPos = m.pos.y + (m.vel.y*sv_scalerate)

	if (intendedPos < o.oPosY) then
		m.vel.y = 0
		m.pos.y = o.oPosY
	end

	if (intendedPos > o.oPosY+o.hitboxHeight) then
		m.vel.y = 0
		m.pos.y = o.oPosY+o.hitboxHeight
	end
	
	SV_PlayerMovement_Scale(m,false)
	local stepResult = perform_air_step(m,0)
	SV_PlayerMovement_Scale(m,true)
end


----------------------------------
-- Prediction for other players --
----------------------------------

--- @param m MarioState
local function prediction_act_dm_grounded_movement(m)
	local crouch = gPlayerSyncTable[m.playerIndex].playerCrouching
	
	if ((m.controller.buttonDown & A_BUTTON) ~= 0 or (m.controller.buttonPressed & A_BUTTON) ~= 0) and m.floor ~= nil then
		play_character_sound(m,CHAR_SOUND_YAH_WAH_HOO)
		-- return
	end
	
	if (not cl_disableAccelPred) then SV_PlayerMovement(m) end
	
	local scale = m.forwardVel
	if (scale < 5) then
		set_character_animation(m, ifelse(crouch,CHAR_ANIM_CROUCHING,CHAR_ANIM_FIRST_PERSON))
	else
		play_step_sound(m, ifelse(crouch,26,9), ifelse(crouch,79,45))
		set_character_anim_with_accel(m, ifelse(crouch,CHAR_ANIM_CRAWLING,CHAR_ANIM_RUNNING), scale*ifelse(crouch,0x4000,0x2600))
	end
	
	m.forwardVel = vec3f_length(vec3_qscale(m.vel,sv_scalerate,0,sv_scalerate))
	
	SV_PlayerMovement_Scale(m,false)
	
	local stepResult = perform_ground_step(m)
	
	SV_PlayerMovement_Scale(m,true)
end
--- @param m MarioState
local function prediction_act_dm_air_movement(m)
	local crouch = gPlayerSyncTable[m.playerIndex].playerCrouching
	
	if (not cl_disableAccelPred) then SV_PlayerMovement(m) end
	
	set_character_animation(m, ifelse(crouch,CHAR_ANIM_FAST_LONGJUMP,ifelse(m.vel.y < 0,CHAR_ANIM_DOUBLE_JUMP_FALL,CHAR_ANIM_DOUBLE_JUMP_RISE)))
	
	if (metal_underwater) then
		SV_UserGravity(m,0.66)
	else
		SV_UserGravity(m,1.0)
	end
	
	if ((m.flags & MARIO_WING_CAP) ~= 0 and (m.controller.buttonDown & A_BUTTON) ~= 0 and m.vel.y < 0) then m.vel.y = 0 end
	
	m.forwardVel = vec3f_length(vec3_qscale(m.vel,sv_scalerate,0,sv_scalerate))
	
	SV_PlayerMovement_Scale(m,false)
	
	local stepResult = perform_air_step(m,0)
	
	SV_PlayerMovement_Scale(m,true)
end
--- @param m MarioState
local function prediction_act_dm_water_movement(m)

	if (not cl_disableAccelPred) then SV_WaterMovement(m) end
	
	local scale = m.forwardVel
	if (scale < 10) then
		set_character_animation(m, CHAR_ANIM_WATER_IDLE)
	else
		set_character_anim_with_accel(m, CHAR_ANIM_FLUTTERKICK, scale*0x800)
	end
	
	if (vec3f_length(m.vel) <= 10) then 
		m.forwardVel = 0
		return
	end
	
	m.forwardVel = vec3f_length(vec3_qscale(m.vel,sv_scalerate,0,sv_scalerate))
	
	SV_PlayerMovement_Scale(m,false)
	local stepResult = perform_air_step(m,0)
	SV_PlayerMovement_Scale(m,true)
end
--- @param m MarioState
local function prediction_act_dm_ground_pound(m)
	set_character_animation(m, CHAR_ANIM_TRIPLE_JUMP_GROUND_POUND)
	
	SV_UserGravityEx(m,1.0,0.75)
	
	SV_PlayerMovement_Scale(m,false)
	local stepResult = perform_air_step(m,0)
	SV_PlayerMovement_Scale(m,true)
	
	if (stepResult == AIR_STEP_LANDED) then 
		if (m.floor ~= nil) then
			play_mario_heavy_landing_sound(m, SOUND_ACTION_TERRAIN_HEAVY_LANDING)
			set_mario_particle_flags(m, (PARTICLE_MIST_CIRCLE | PARTICLE_HORIZONTAL_STAR), false)
			if (m.controller.buttonDown & A_BUTTON) ~= 0 then
				play_character_sound(m,CHAR_SOUND_YAHOO)
			end
		end
	end
end
--- @param m MarioState
local function prediction_act_dm_pole_movement(m)	
	set_character_animation(m, CHAR_ANIM_IDLE_ON_POLE)
	
	SV_PlayerMovement_Scale(m,false)
	local stepResult = perform_air_step(m,0)
	SV_PlayerMovement_Scale(m,true)
end


--- @param m MarioState
function act_dm_grounded_movement(m)
	if (m.playerIndex == 0) then return local_act_dm_grounded_movement(m) else return prediction_act_dm_grounded_movement(m) end
end
--- @param m MarioState
function act_dm_air_movement(m)
	if (m.playerIndex == 0) then return local_act_dm_air_movement(m) else return prediction_act_dm_air_movement(m) end
end
--- @param m MarioState
function act_dm_water_movement(m)
	if (m.playerIndex == 0) then return local_act_dm_water_movement(m) else return prediction_act_dm_water_movement(m) end
end
--- @param m MarioState
function act_dm_ground_pound(m)
	if (m.playerIndex == 0) then return local_act_dm_ground_pound(m) else return prediction_act_dm_ground_pound(m) end
end
--- @param m MarioState
function act_dm_pole_movement(m)
	if (m.playerIndex == 0) then return local_act_dm_pole_movement(m) else return prediction_act_dm_pole_movement(m) end
end



