-- Global to all starfalls
local checkluatype = SF.CheckLuaType
local checkvalidnumber = SF.CheckValidNumber
local registerprivilege = SF.Permissions.registerPrivilege
local ENT_META = FindMetaTable("Entity")
local PLY_META = FindMetaTable("Player")

local Ent_SetCycle = ENT_META.SetCycle

if CLIENT then
	registerprivilege("player.getFriendStatus", "FriendStatus", "Whether friend status can be retrieved", { client = { default = 1 } })
end

-- Player animation
local playerAnimAdd
local playerAnimRemove
local playerAnimGet
if CLIENT then
	local playerAnimation

	playerAnimAdd = function(ply, anim)
		if next(playerAnimation) == nil then
			hook.Add("CalcMainActivity", "sf_player_animation", function(ply, vel)
				local anim = playerAnimation[ply]
				if not anim then return end

				if anim.auto then
					anim.progress = anim.progress + FrameTime() / anim.duration * anim.rate / anim.range

					local more = anim.progress > 1
					if more or anim.progress < 0 then
						if anim.loop then
							if anim.bounce then
								anim.rate = -anim.rate
								anim.progress = -anim.progress + (more and 2 or 0)
							else
								anim.progress = anim.progress % 1
							end
						else
							playerAnimRemove(ply)

							return
						end
					end
				end

				Ent_SetCycle(ply, anim.min + anim.progress * anim.range)

				local seq = anim.sequence
				return anim.activity or seq, seq
			end)
		end

		playerAnimation[ply] = anim

		return anim
	end

	playerAnimRemove = function(ply)
		if table.IsEmpty(playerAnimation) then
			hook.Remove("CalcMainActivity", "sf_player_animation")
		end
	end

	playerAnimGet = function(ply)
		return playerAnimation[ply]
	end

	playerAnimation = SF.EntityTable("playerAnimation", playerAnimRemove)
end

--- Player type
-- @name Player
-- @class type
-- @libtbl player_methods
SF.RegisterType("Player", false, true, PLY_META, "Entity")


return function(instance)
local checkpermission = instance.player ~= SF.Superuser and SF.Permissions.check or function() end

local Ent_GetFriction,Ent_GetGroundEntity,Ent_GetMoveType,Ent_GetSequenceActivity,Ent_IsValid,Ent_LookupSequence,Ent_SequenceDuration,Ent_SetCycle = ENT_META.GetFriction,ENT_META.GetGroundEntity,ENT_META.GetMoveType,ENT_META.GetSequenceActivity,ENT_META.IsValid,ENT_META.LookupSequence,ENT_META.SequenceDuration,ENT_META.SetCycle
local Ply_Alive,Ply_AnimResetGestureSlot,Ply_AnimRestartGesture,Ply_AnimSetGestureWeight,Ply_Armor,Ply_Crouching,Ply_Deaths,Ply_FlashlightIsOn,Ply_Frags,Ply_GetActiveWeapon,Ply_GetAimVector,Ply_GetAmmoCount,Ply_GetCrouchedWalkSpeed,Ply_GetDuckSpeed,Ply_GetEntityInUse,Ply_GetEyeTrace,Ply_GetFOV,Ply_GetFriendStatus,Ply_GetJumpPower,Ply_GetLadderClimbSpeed,Ply_GetMaxArmor,Ply_GetMaxSpeed,Ply_GetName,Ply_GetPlayerColor,Ply_GetRagdollEntity,Ply_GetRunSpeed,Ply_GetShootPos,Ply_GetSlowWalkSpeed,Ply_GetStepSize,Ply_GetUnDuckSpeed,Ply_GetUserGroup,Ply_GetVehicle,Ply_GetViewEntity,Ply_GetViewModel,Ply_GetViewPunchAngles,Ply_GetWalkSpeed,Ply_GetWeapon,Ply_GetWeaponColor,Ply_GetWeapons,Ply_InVehicle,Ply_IsAdmin,Ply_IsBot,Ply_IsFrozen,Ply_IsMuted,Ply_IsSpeaking,Ply_IsSprinting,Ply_IsSuperAdmin,Ply_IsTyping,Ply_IsUserGroup,Ply_IsWalking,Ply_KeyDown,Ply_OwnerSteamID64,Ply_Ping,Ply_ShouldDrawLocalPlayer,Ply_SteamID,Ply_SteamID64,Ply_Team,Ply_UserID,Ply_VoiceVolume = PLY_META.Alive,PLY_META.AnimResetGestureSlot,PLY_META.AnimRestartGesture,PLY_META.AnimSetGestureWeight,PLY_META.Armor,PLY_META.Crouching,PLY_META.Deaths,PLY_META.FlashlightIsOn,PLY_META.Frags,PLY_META.GetActiveWeapon,PLY_META.GetAimVector,PLY_META.GetAmmoCount,PLY_META.GetCrouchedWalkSpeed,PLY_META.GetDuckSpeed,PLY_META.GetEntityInUse,PLY_META.GetEyeTrace,PLY_META.GetFOV,PLY_META.GetFriendStatus,PLY_META.GetJumpPower,PLY_META.GetLadderClimbSpeed,PLY_META.GetMaxArmor,PLY_META.GetMaxSpeed,PLY_META.GetName,PLY_META.GetPlayerColor,PLY_META.GetRagdollEntity,PLY_META.GetRunSpeed,PLY_META.GetShootPos,PLY_META.GetSlowWalkSpeed,PLY_META.GetStepSize,PLY_META.GetUnDuckSpeed,PLY_META.GetUserGroup,PLY_META.GetVehicle,PLY_META.GetViewEntity,PLY_META.GetViewModel,PLY_META.GetViewPunchAngles,PLY_META.GetWalkSpeed,PLY_META.GetWeapon,PLY_META.GetWeaponColor,PLY_META.GetWeapons,PLY_META.InVehicle,PLY_META.IsAdmin,PLY_META.IsBot,PLY_META.IsFrozen,PLY_META.IsMuted,PLY_META.IsSpeaking,PLY_META.IsSprinting,PLY_META.IsSuperAdmin,PLY_META.IsTyping,PLY_META.IsUserGroup,PLY_META.IsWalking,PLY_META.KeyDown,PLY_META.OwnerSteamID64,PLY_META.Ping,PLY_META.ShouldDrawLocalPlayer,PLY_META.SteamID,PLY_META.SteamID64,PLY_META.Team,PLY_META.UserID,PLY_META.VoiceVolume

local player_methods, player_meta, wrap, unwrap = instance.Types.Player.Methods, instance.Types.Player, instance.Types.Player.Wrap, instance.Types.Player.Unwrap
local owrap, ounwrap = instance.WrapObject, instance.UnwrapObject
local ent_meta, ewrap, eunwrap = instance.Types.Entity, instance.Types.Entity.Wrap, instance.Types.Entity.Unwrap
local vec_meta, vwrap, vunwrap = instance.Types.Vector, instance.Types.Vector.Wrap, instance.Types.Vector.Unwrap
local ang_meta, awrap, aunwrap = instance.Types.Angle, instance.Types.Angle.Wrap, instance.Types.Angle.Unwrap
local wep_meta, wwrap, wunwrap = instance.Types.Weapon, instance.Types.Weapon.Wrap, instance.Types.Weapon.Unwrap
local veh_meta, vhwrap, vhunwrap = instance.Types.Vehicle, instance.Types.Vehicle.Wrap, instance.Types.Vehicle.Unwrap

local getent
instance:AddHook("initialize", function()
	getent = ent_meta.GetEntity
	player_meta.__tostring = ent_meta.__tostring
end)

local function getply(self)
	local ent = player_meta.sf2sensitive[self]
	if Ent_IsValid(ent) then
		return ent
	else
		SF.Throw("Entity is not valid.", 3)
	end
end
player_meta.GetPlayer = getply

-- ------------------------------------------------------------------------- --
--- Returns whether the player is alive
-- @shared
-- @return boolean True if player alive
function player_methods:isAlive()
	return Ply_Alive(getply(self))
end

--- Returns the players armor
-- @shared
-- @return number Armor
function player_methods:getArmor()
	return Ply_Armor(getply(self))
end

--- Returns the players maximum armor capacity
-- @shared
-- @return number Armor limit
function player_methods:getMaxArmor()
	return Ply_GetMaxArmor(getply(self))
end

--- Returns the players Crouched Walk Speed
-- @shared
-- @return number Crouch Walk Speed value
function player_methods:getCrouchedWalkSpeed()
	return Ply_GetCrouchedWalkSpeed(getply(self))
end

--- Returns the players Duck Speed, a rate from 0-1 for how quickly they can crouch
-- @shared
-- @return number Duck Speed value
function player_methods:getDuckSpeed()
	return Ply_GetDuckSpeed(getply(self))
end

--- Returns the players UnDuck Speed, a rate from 0-1 for how quickly they can uncrouch
-- @shared
-- @return number UnDuck Speed value
function player_methods:getUnDuckSpeed()
	return Ply_GetUnDuckSpeed(getply(self))
end

--- Returns the players Ladder Climb Speed, probably unstable
-- @shared
-- @return number Ladder Climb Speed value
function player_methods:getLadderClimbSpeed()
	return Ply_GetLadderClimbSpeed(getply(self))
end

--- Returns the players Max Speed, probably unstable
-- @shared
-- @return number Max Speed value
function player_methods:getMaxSpeed()
	return Ply_GetMaxSpeed(getply(self))
end

--- Returns the players Run Speed, which is +speed
-- @shared
-- @return number Run Speed value
function player_methods:getRunSpeed()
	return Ply_GetRunSpeed(getply(self))
end

--- Returns the players Slow Walk Speed, which is +walk
-- @shared
-- @return number Slow Walk Speed value
function player_methods:getSlowWalkSpeed()
	return Ply_GetSlowWalkSpeed(getply(self))
end

--- Returns the players Walk Speed
-- @shared
-- @return number Walk Speed value
function player_methods:getWalkSpeed()
	return Ply_GetWalkSpeed(getply(self))
end

--- Returns the players Jump Power
-- @shared
-- @return number Jump Power value
function player_methods:getJumpPower()
	return Ply_GetJumpPower(getply(self))
end

--- Returns the players Friction
-- @shared
-- @return number Friction value
function player_methods:getFriction()
	return Ent_GetFriction(getply(self)) * cvars.Number("sv_friction")
end

--- Returns the players Step Size
-- @shared
-- @return number Step Size Value
function player_methods:getStepSize()
	return Ply_GetStepSize(getply(self))
end

--- Returns whether the player is crouching
-- @shared
-- @return boolean True if player crouching
function player_methods:isCrouching()
	return Ply_Crouching(getply(self))
end

--- Returns the amount of deaths of the player
-- @shared
-- @return number Amount of deaths
function player_methods:getDeaths()
	return Ply_Deaths(getply(self))
end

--- Returns whether the player's flashlight is on
-- @shared
-- @return boolean True if player has flashlight on
function player_methods:isFlashlightOn()
	return Ply_FlashlightIsOn(getply(self))
end

--- Returns true if the player is noclipped
-- @shared
-- @return boolean True if the player is noclipped
function player_methods:isNoclipped()
	return Ent_GetMoveType(getply(self)) == MOVETYPE_NOCLIP
end

--- Returns the amount of kills of the player
-- @shared
-- @return number Amount of kills
function player_methods:getFrags()
	return Ply_Frags(getply(self))
end

--- Returns the name of the player's active weapon
-- @shared
-- @return Weapon The weapon
function player_methods:getActiveWeapon()
	return wwrap(Ply_GetActiveWeapon(getply(self)))
end

--- Returns the player's aim vector
-- @shared
-- @return Vector Aim vector
function player_methods:getAimVector()
	return vwrap(Ply_GetAimVector(getply(self)))
end

--- Returns the player's field of view
-- @shared
-- @return number Field of view as a float
function player_methods:getFOV()
	return Ply_GetFOV(getply(self))
end

--- Returns the player's name
-- @shared
-- @return string Name
function player_methods:getName()
	return Ply_GetName(getply(self))
end

--- Returns the entity the player is currently using, like func_tank mounted turrets or +use prop pickups.
-- @shared
-- @return Entity Entity
function player_methods:getEntityInUse()
	return owrap(Ply_GetEntityInUse(getply(self)))
end

--- Returns the player's shoot position
-- @shared
-- @return Vector Shoot position
function player_methods:getShootPos()
	return vwrap(Ply_GetShootPos(getply(self)))
end

--- Returns whether the player is in a vehicle
-- @shared
-- @return boolean True if player in vehicle
function player_methods:inVehicle()
	return Ply_InVehicle(getply(self))
end

--- Returns the vehicle the player is driving
-- @shared
-- @return Vehicle Vehicle if player in vehicle or nil
function player_methods:getVehicle()
	return vhwrap(Ply_GetVehicle(getply(self)))
end

--- Returns whether the player is an admin
-- @shared
-- @return boolean True if player is admin
function player_methods:isAdmin()
	return Ply_IsAdmin(getply(self))
end

--- Returns whether the player is a bot
-- @shared
-- @return boolean True if player is a bot
function player_methods:isBot()
	return Ply_IsBot(getply(self))
end

--- Returns whether the player is frozen
-- @shared
-- @return boolean True if player is frozen
function player_methods:isFrozen()
	return Ply_IsFrozen(getply(self))
end

--- Returns whether the player is a super admin
-- @shared
-- @return boolean True if player is super admin
function player_methods:isSuperAdmin()
	return Ply_IsSuperAdmin(getply(self))
end

--- Returns whether the player belongs to a usergroup
-- @shared
-- @param string groupName Group to check against
-- @return boolean True if player belongs to group
function player_methods:isUserGroup(group)
	return Ply_IsUserGroup(getply(self), group)
end

--- Returns the usergroup of the player
-- @shared
-- @return string Usergroup, "user" if player has no group
function player_methods:getUserGroup()
	return Ply_GetUserGroup(getply(self))
end

--- Returns the player's current ping
-- @shared
-- @return number The player's ping
function player_methods:getPing()
	return Ply_Ping(getply(self))
end

--- Returns the player's SteamID
-- @shared
-- @return string SteamID
function player_methods:getSteamID()
	return Ply_SteamID(getply(self))
end

--- Returns the player's SteamID64 / Community ID
-- In singleplayer, this will return no value serverside.
-- For bots, this will return 90071996842377216 (equivalent to STEAM_0:0:0) for the first bot to join, and adds 1 to the id for the bot id.
-- Returns no value for bots clientside.
-- @shared
-- @param boolean? owner Return the actual game owner account id
-- @return string SteamID64 aka Community ID
function player_methods:getSteamID64(owner)
	if owner then
		return Ply_OwnerSteamID64(getply(self))
	else
		return Ply_SteamID64(getply(self))
	end
end

--- Returns the player's current team
-- @shared
-- @return number Team Index, from TEAM enums or custom teams
function player_methods:getTeam()
	return Ply_Team(getply(self))
end

--- Returns the name of the player's current team
-- @shared
-- @return string Team Name
function player_methods:getTeamName()
	return team.GetName(Ply_Team(getply(self)))
end

--- Returns the player's UserID
-- @shared
-- @return number UserID
function player_methods:getUserID()
	return Ply_UserID(getply(self))
end

--- Returns a table with information of what the player is looking at
-- @shared
-- @return table Trace data https://wiki.facepunch.com/gmod/Structures/TraceResult
function player_methods:getEyeTrace()
	return SF.StructWrapper(instance, Ply_GetEyeTrace(getply(self)), "TraceResult")
end

--- Returns the player's current view entity
-- @shared
-- @return Entity Player's current view entity
function player_methods:getViewEntity()
	return owrap(Ply_GetViewEntity(getply(self)))
end

--- Returns the player's view model
-- In the Client realm, other players' viewmodels are not available unless they are being spectated
-- @shared
-- @return Entity Player's view model
function player_methods:getViewModel()
	return owrap(Ply_GetViewModel(getply(self), 0))
end

--- Returns the camera punch offset angle
-- @return Angle The angle of the view offset
function player_methods:getViewPunchAngles()
	return awrap(Ply_GetViewPunchAngles(getply(self)))
end

--- Returns a table of weapons the player is carrying
-- @shared
-- @return table Table of weapons
function player_methods:getWeapons()
	return instance.Sanitize(Ply_GetWeapons(getply(self)))
end

--- Returns the specified weapon or nil if the player doesn't have it
-- @shared
-- @param string wep Weapon class name
-- @return Weapon Weapon
function player_methods:getWeapon(wep)
	checkluatype(wep, TYPE_STRING)
	return wwrap(Ply_GetWeapon(getply(self), wep))
end

--- Returns a player's weapon color
-- The part of the model that is colored is determined by the model itself, and is different for each model
-- The format is Vector(r,g,b), and each color should be between 0 and 1
-- @shared
-- @return Vector The color
function player_methods:getWeaponColor()
	return vwrap(Ply_GetWeaponColor(getply(self)))
end

--- Returns a player's color
-- The part of the model that is colored is determined by the model itself, and is different for each model
-- The format is Vector(r,g,b), and each color should be between 0 and 1
-- @shared
-- @return Vector The color
function player_methods:getPlayerColor()
	return vwrap(Ply_GetPlayerColor(getply(self)))
end

--- Returns the entity that the player is standing on
-- @shared
-- @return Entity Ground entity
function player_methods:getGroundEntity()
	return owrap(Ent_GetGroundEntity(getply(self)))
end

--- Gets the amount of ammo the player has.
-- @shared
-- @param string|number idOrName The string ammo name or number id of the ammo
-- @return number The amount of ammo player has in reserve.
function player_methods:getAmmoCount(id)
	if not isnumber(id) and not isstring(id) then SF.ThrowTypeError("number or string", SF.GetType(id), 2) end

	return Ply_GetAmmoCount(getply(self), id)
end

--- Returns whether the player is typing in their chat
-- @shared
-- @return boolean Whether they are typing in the chat
function player_methods:isTyping()
	return Ply_IsTyping(getply(self))
end

--- Returns whether the player is sprinting
-- @shared
-- @return boolean Whether they are sprinting
function player_methods:isSprinting()
	return Ply_IsSprinting(getply(self))
end

--- Returns whether the player is walking
-- In singleplayer, this will return false clientside
-- @shared
-- @return boolean Whether they are walking
function player_methods:isWalking()
	return Ply_IsWalking(getply(self))
end

--- Gets the player's death ragdoll
-- @return Entity? The entity or nil if it doesn't exist
function player_methods:getDeathRagdoll()
	return owrap(Ply_GetRagdollEntity(getply(self)))
end

--- Returns whether or not the player is pushing the key.
-- @shared
-- @param number key Key to check. IN_KEY table values
-- @return boolean Whether they key is down
function player_methods:keyDown(key)
	checkvalidnumber(key)
	return Ply_KeyDown(getply(self), key)
end

if CLIENT then
	--- Returns the relationship of the player to the local client
	-- @client
	-- @return string One of: "friend", "blocked", "none", "requested"
	function player_methods:getFriendStatus()
		checkpermission(instance, nil, "player.getFriendStatus")
		return Ply_GetFriendStatus(getply(self))
	end

	--- Returns whether the local player has muted the player
	-- @client
	-- @return boolean True if the player was muted
	function player_methods:isMuted()
		return Ply_IsMuted(getply(self))
	end

	--- Returns whether the player is heard by the local player.
	-- @client
	-- @return boolean Whether they are speaking and able to be heard by LocalPlayer
	function player_methods:isSpeaking()
		return Ply_IsSpeaking(getply(self))
	end

	--- Returns the voice volume of the player
	-- @client
	-- @return number Returns the players voice volume, how loud the player's voice communication currently is, as a normal number. Doesn't work on local player unless the voice_loopback convar is set to 1.
	function player_methods:voiceVolume()
		return Ply_VoiceVolume(getply(self))
	end

	--- Plays gesture animations on a player
	-- @client
	-- @param string|number animation Sequence string or act number. https://wiki.facepunch.com/gmod/Enums/ACT
	-- @param boolean? loop Optional boolean (Default true), should the gesture loop
	-- @param number? slot Optional int (Default GESTURE_SLOT.CUSTOM), the gesture slot to use. GESTURE_SLOT table values
	-- @param number? weight Optional float (Default 1), the weight of the gesture. Ranging from 0-1
	function player_methods:playGesture(animation, loop, slot, weight)
		local ply = getply(self)
		if instance.owner ~= ply then checkpermission(instance, ply, "entities.setRenderProperty") end

		if slot == nil then
			slot = GESTURE_SLOT_CUSTOM
		else
			checkvalidnumber(slot)
			if slot < 0 or slot > 6 then return end
		end

		if weight == nil then weight = 1 else checkvalidnumber(weight) end

		if isstring(animation) then
			animation = Ent_GetSequenceActivity(ply, Ent_LookupSequence(ply, animation))
		elseif not isnumber(animation) then
			SF.ThrowTypeError("number or string", SF.GetType(animation), 2)
		end

		Ply_AnimResetGestureSlot(ply, slot)
		Ply_AnimRestartGesture(ply, slot, animation, not loop)
		Ply_AnimSetGestureWeight(ply, slot, weight)
	end

	--- Resets gesture animations on a player
	-- @client
	-- @param number? slot Optional int (Default GESTURE_SLOT.CUSTOM), the gesture slot to use. GESTURE_SLOT table values
	function player_methods:resetGesture(slot)
		local ply = getply(self)
		if instance.owner ~= ply then checkpermission(instance, ply, "entities.setRenderProperty") end

		if slot == nil then slot = GESTURE_SLOT_CUSTOM else checkvalidnumber(slot) end

		Ply_AnimResetGestureSlot(ply, slot)
	end

	--- Sets the weight of the gesture animation in the given gesture slot
	-- @client
	-- @param number? slot Optional int (Default GESTURE_SLOT.CUSTOM), the gesture slot to use. GESTURE_SLOT table values
	-- @param number? weight Optional float (Default 1), the weight of the gesture. Ranging from 0-1
	function player_methods:setGestureWeight(slot, weight)
		local ply = getply(self)
		if instance.owner ~= ply then checkpermission(instance, ply, "entities.setRenderProperty") end

		if slot == nil then slot = GESTURE_SLOT_CUSTOM else checkvalidnumber(slot) end
		if weight == nil then weight = 1 else checkvalidnumber(weight) end

		Ply_AnimSetGestureWeight(ply, slot, weight)
	end

	--- Plays an animation on the player
	-- @client
	-- @param number|string sequence Sequence number or string name
	-- @param number? progress Optional float (Default 0), the progress of the animation. Ranging from 0-1
	-- @param number? rate Optional float (Default 1), the playback rate of the animation
	-- @param boolean? loop Optional boolean (Default false), should the animation loop
	-- @param boolean? auto_advance Optional boolean (Default true), should the animation handle advancing itself
	-- @param number|string|nil act Optional number or string name (Default sequence value), the activity the player should use
	function player_methods:setAnimation(seq, progress, rate, loop, auto_advance, act)
		local ply = getply(self)
		if instance.owner ~= ply then checkpermission(instance, ply, "entities.setRenderProperty") end

		if isstring(seq) then
			seq = Ent_LookupSequence(ply, seq)
		elseif not isnumber(seq) then
			SF.ThrowTypeError("number or string", SF.GetType(seq), 2)
		end

		if progress == nil then progress = 0 else checkvalidnumber(progress) end
		if rate == nil then rate = 1 else checkvalidnumber(rate) end
		if loop == nil then loop = false else checkluatype(loop, TYPE_BOOL) end
		if auto_advance == nil then auto_advance = true else checkluatype(auto_advance, TYPE_BOOL) end

		if act ~= nil then
			if isstring(act) then
				act = Ent_LookupSequence(ply, act)
			elseif not isnumber(act) then
				SF.ThrowTypeError("number, string or nil", SF.GetType(act), 2)
			end
		end

		Ent_SetCycle(ply, progress)

		local anim = playerAnimAdd(ply, {})
		anim.sequence = seq
		anim.activity = act
		anim.rate = rate
		anim.loop = loop
		anim.auto = auto_advance
		anim.bounce = false
		anim.min = 0
		anim.max = 1

		anim.range = 1
		anim.progress = progress
		anim.duration = Ent_SequenceDuration(ply, seq)
	end

	--- Resets the animation
	-- @client
	function player_methods:resetAnimation()
		local ply = getply(self)
		if instance.owner ~= ply then checkpermission(instance, ply, "entities.setRenderProperty") end

		playerAnimRemove(ply)
	end

	--- Sets the animation activity
	-- @client
	-- @param number|string|nil activity Activity, nil to use the current animation sequence
	function player_methods:setAnimationActivity(act)
		local ply = getply(self)
		if instance.owner ~= ply then checkpermission(instance, ply, "entities.setRenderProperty") end

		local anim = playerAnimGet(ply)
		if not anim then SF.Throw("No animation is playing.", 2) end

		if isstring(act) then
			act = Ent_LookupSequence(ply, act)
		elseif act ~= nil and not isnumber(act) then
			SF.ThrowTypeError("number, string or nil", SF.GetType(act), 2)
		end

		anim.activity = act
	end

	--- Sets the animation progress
	-- @client
	-- @param number progress The progress of the animation. Ranging from 0-1
	function player_methods:setAnimationProgress(progress)
		local ply = getply(self)
		if instance.owner ~= ply then checkpermission(instance, ply, "entities.setRenderProperty") end

		local anim = playerAnimGet(ply)
		if not anim then SF.Throw("No animation is playing.", 2) end

		checkvalidnumber(progress)

		anim.progress = progress
	end

	--- Sets the animation time
	-- @client
	-- @param number time The time of the animation in seconds. Float
	function player_methods:setAnimationTime(time)
		local ply = getply(self)
		if instance.owner ~= ply then checkpermission(instance, ply, "entities.setRenderProperty") end

		local anim = playerAnimGet(ply)
		if not anim then SF.Throw("No animation is playing.", 2) end

		checkvalidnumber(time)

		anim.progress = (time / anim.duration - anim.min) * (1 / anim.range)
	end

	--- Sets the animation playback rate
	-- @client
	-- @param number rate The playback rate of the animation. Float
	function player_methods:setAnimationRate(rate)
		local ply = getply(self)
		if instance.owner ~= ply then checkpermission(instance, ply, "entities.setRenderProperty") end

		local anim = playerAnimGet(ply)
		if not anim then SF.Throw("No animation is playing.", 2) end

		checkvalidnumber(rate)

		anim.rate = rate
	end

	--- Sets the animation auto advance
	-- @client
	-- @param boolean auto_advance Should the animation handle advancing itself?
	function player_methods:setAnimationAutoAdvance(auto_advance)
		local ply = getply(self)
		if instance.owner ~= ply then checkpermission(instance, ply, "entities.setRenderProperty") end

		local anim = playerAnimGet(ply)
		if not anim then SF.Throw("No animation is playing.", 2) end

		checkluatype(auto_advance, TYPE_BOOL)

		anim.auto = auto_advance
	end

	--- Sets the animation bounce
	-- @client
	-- @param boolean bounce Should the animation bounce instead of loop?
	function player_methods:setAnimationBounce(bounce)
		local ply = getply(self)
		if instance.owner ~= ply then checkpermission(instance, ply, "entities.setRenderProperty") end

		local anim = playerAnimGet(ply)
		if not anim then SF.Throw("No animation is playing.", 2) end

		checkluatype(bounce, TYPE_BOOL)

		anim.bounce = bounce
	end

	--- Sets the animation loop
	-- @client
	-- @param boolean loop Should the animation loop?
	function player_methods:setAnimationLoop(loop)
		local ply = getply(self)
		if instance.owner ~= ply then checkpermission(instance, ply, "entities.setRenderProperty") end

		local anim = playerAnimGet(ply)
		if not anim then SF.Throw("No animation is playing.", 2) end

		checkluatype(loop, TYPE_BOOL)

		anim.loop = loop
	end

	--- Sets the animation range
	-- @client
	-- @param number min Min. Ranging from 0-1
	-- @param number max Max. Ranging from 0-1
	function player_methods:setAnimationRange(min, max)
		local ply = getply(self)
		if instance.owner ~= ply then checkpermission(instance, ply, "entities.setRenderProperty") end

		local anim = playerAnimGet(ply)
		if not anim then SF.Throw("No animation is playing.", 2) end

		checkvalidnumber(min)
		checkvalidnumber(max)

		anim.min = math.max(min, 0)
		anim.max = math.min(max, 1)
		anim.range = anim.max - anim.min
	end

	--- Gets whether a animation is playing
	-- @client
	-- @return boolean If an animation is playing
	function player_methods:isPlayingAnimation()
		local ply = getply(self)
		return playerAnimGet(ply) ~= nil
	end

	--- Gets the progress of the animation ranging 0-1
	-- @client
	-- @return number Progress ranging 0-1
	function player_methods:getAnimationProgress()
		local ply = getply(self)
		local anim = playerAnimGet(ply)

		if not anim then return 0 end
		return anim.progress
	end

	--- Gets the animation time
	-- @client
	-- @return number Time in seconds
	function player_methods:getAnimationTime()
		local ply = getply(self)
		local anim = playerAnimGet(ply)

		if not anim then return 0 end
		return (anim.progress * anim.range + anim.min) * anim.duration
	end

	--- Returns whether the player's player model will be drawn at the time the function is called.
	-- @client
	-- @return boolean True if the player's playermodel is visible
	function player_methods:shouldDrawLocalPlayer()
		return Ply_ShouldDrawLocalPlayer(getply(self))
	end
end

end
