using System;
using System.Globalization;
using System.Runtime.CompilerServices;
using KinematicCharacterController;
using RoR2.ConVar;
using Unity;
using UnityEngine;
using UnityEngine.Networking;

namespace RoR2;

[RequireComponent(typeof(CharacterBody))]
public class CharacterMotor : BaseCharacterController, IPhysMotor, ILifeBehavior, IDisplacementReceiver, ICharacterGravityParameterProvider, ICharacterFlightParameterProvider
{
	[Serializable]
	public struct HitGroundInfo
	{
		public Vector3 velocity;

		public Vector3 position;

		public bool isValidForEffect;

		public override string ToString()
		{
			return $"velocity={velocity} position={position}";
		}
	}

	public delegate void HitGroundDelegate(ref HitGroundInfo hitGroundInfo);

	public struct MovementHitInfo
	{
		public Vector3 velocity;

		public Collider hitCollider;
	}

	public delegate void MovementHitDelegate(ref MovementHitInfo movementHitInfo);

	[HideInInspector]
	public float walkSpeedPenaltyCoefficient = 1f;

	[Tooltip("The character direction component to supply a move vector to.")]
	public CharacterDirection characterDirection;

	[Tooltip("Whether or not a move vector supplied to this component can cause movement. Use this when the object is driven by root motion.")]
	public bool muteWalkMotion;

	[Tooltip("The mass of this character.")]
	public float mass = 1f;

	[Tooltip("The air control value of this character as a fraction of ground control.")]
	public float airControl = 0.25f;

	[Tooltip("Disables Air Control for things like jumppads")]
	public bool disableAirControlUntilCollision;

	[Tooltip("Auto-assigns parameters skin width, slope angle, and step offset as a function of the Character Motor's radius and height")]
	public bool generateParametersOnAwake = true;

	[Tooltip("Ignores JumpVolumes")]
	public bool doNotTriggerJumpVolumes;

	private NetworkIdentity networkIdentity;

	private CharacterBody body;

	private CapsuleCollider capsuleCollider;

	private static readonly bool enableMotorWithoutAuthority;

	private bool alive = true;

	private const float restDuration = 1f;

	private const float restVelocityThreshold = 0.025f;

	private const float restVelocityThresholdSqr = 0.00062500004f;

	public const float slipStartAngle = 70f;

	public const float slipEndAngle = 55f;

	private float restStopwatch;

	private Vector3 previousPosition;

	private bool isAirControlForced;

	[NonSerialized]
	public int jumpCount;

	[NonSerialized]
	public bool netIsGrounded;

	[NonSerialized]
	public Vector3 netGroundNormal;

	[NonSerialized]
	public Vector3 velocity;

	private Vector3 lastVelocity;

	[NonSerialized]
	public Vector3 rootMotion;

	private Vector3 _moveDirection;

	private static readonly FloatConVar cvCMotorSafeCollisionStepThreshold;

	private int _safeCollisionEnableCount;

	[SerializeField]
	[Tooltip("Determins how gravity affects this character.")]
	private CharacterGravityParameters _gravityParameters;

	[Tooltip("Determines whether this character has three-dimensional or two-dimensional movement capabilities.")]
	[SerializeField]
	private CharacterFlightParameters _flightParameters;

	private static int kRpcRpcApplyForceImpulse;

	private static int kRpcRpcOnHitGroundSFX;

	private static int kCmdCmdReportHitGround;

	public float walkSpeed => body.moveSpeed * walkSpeedPenaltyCoefficient;

	public float acceleration => body.acceleration;

	public bool atRest => restStopwatch > 1f;

	public bool hasEffectiveAuthority { get; private set; }

	public Vector3 estimatedGroundNormal
	{
		get
		{
			if (!hasEffectiveAuthority)
			{
				return netGroundNormal;
			}
			return base.Motor.GroundingStatus.GroundNormal;
		}
	}

	private bool canWalk
	{
		get
		{
			if (!muteWalkMotion)
			{
				return alive;
			}
			return false;
		}
	}

	public bool isGrounded
	{
		get
		{
			if (!hasEffectiveAuthority)
			{
				return netIsGrounded;
			}
			return base.Motor.GroundingStatus.IsStableOnGround;
		}
	}

	float IPhysMotor.mass => mass;

	Vector3 IPhysMotor.velocity => velocity;

	Vector3 IPhysMotor.velocityAuthority
	{
		get
		{
			return velocity;
		}
		set
		{
			velocity = value;
		}
	}

	public Vector3 moveDirection
	{
		get
		{
			return _moveDirection;
		}
		set
		{
			_moveDirection = value;
		}
	}

	private float slopeLimit
	{
		get
		{
			return base.Motor.MaxStableSlopeAngle;
		}
		set
		{
			base.Motor.MaxStableSlopeAngle = value;
		}
	}

	public float stepOffset
	{
		get
		{
			return base.Motor.MaxStepHeight;
		}
		set
		{
			base.Motor.MaxStepHeight = value;
		}
	}

	public float capsuleHeight => capsuleCollider.height;

	public float capsuleRadius => capsuleCollider.radius;

	public StepHandlingMethod stepHandlingMethod
	{
		get
		{
			return base.Motor.StepHandling = StepHandlingMethod.None;
		}
		set
		{
			base.Motor.StepHandling = value;
		}
	}

	public bool ledgeHandling
	{
		get
		{
			return base.Motor.LedgeAndDenivelationHandling;
		}
		set
		{
			base.Motor.LedgeAndDenivelationHandling = value;
		}
	}

	public bool interactiveRigidbodyHandling
	{
		get
		{
			return base.Motor.InteractiveRigidbodyHandling;
		}
		set
		{
			base.Motor.InteractiveRigidbodyHandling = value;
		}
	}

	public Run.FixedTimeStamp lastGroundedTime { get; private set; } = Run.FixedTimeStamp.negativeInfinity;


	public CharacterGravityParameters gravityParameters
	{
		get
		{
			return _gravityParameters;
		}
		set
		{
			if (!_gravityParameters.Equals(value))
			{
				_gravityParameters = value;
				useGravity = _gravityParameters.CheckShouldUseGravity();
			}
		}
	}

	public bool useGravity { get; private set; }

	public CharacterFlightParameters flightParameters
	{
		get
		{
			return _flightParameters;
		}
		set
		{
			if (!_flightParameters.Equals(value))
			{
				_flightParameters = value;
				isFlying = _flightParameters.CheckShouldUseFlight();
			}
		}
	}

	public bool isFlying { get; private set; }

	[Obsolete("Use '.onHitGroundAuthority' instead, which this is just a backwards-compatibility wrapper for. Or, use '.onHitGroundAuthority' if that is more appropriate to your use case.", false)]
	public event HitGroundDelegate onHitGround
	{
		add
		{
			onHitGroundAuthority += value;
		}
		remove
		{
			onHitGroundAuthority -= value;
		}
	}

	public event HitGroundDelegate onHitGroundAuthority;

	public event Action<CharacterBody> onMotorStart;

	public event MovementHitDelegate onMovementHit;

	private void UpdateInnerMotorEnabled()
	{
		base.Motor.enabled = enableMotorWithoutAuthority || hasEffectiveAuthority;
	}

	private void UpdateAuthority()
	{
		hasEffectiveAuthority = Util.HasEffectiveAuthority(base.gameObject);
		UpdateInnerMotorEnabled();
	}

	private void Awake()
	{
		networkIdentity = GetComponent<NetworkIdentity>();
		body = GetComponent<CharacterBody>();
		capsuleCollider = GetComponent<CapsuleCollider>();
	}

	private void Start()
	{
		previousPosition = base.transform.position;
		if (base.Motor == null)
		{
			SetupCharacterMotor(GetComponent<KinematicCharacterMotor>());
		}
		if (base.Motor.AttachedRigidbody != null)
		{
			base.Motor.AttachedRigidbody.mass = mass;
		}
		base.Motor.MaxStableSlopeAngle = 70f;
		base.Motor.MaxStableDenivelationAngle = 55f;
		base.Motor.RebuildCollidableLayers();
		if (generateParametersOnAwake)
		{
			GenerateParameters();
		}
		useGravity = gravityParameters.CheckShouldUseGravity();
		isFlying = flightParameters.CheckShouldUseFlight();
		UpdateAuthority();
		this.onMotorStart?.Invoke(body);
	}

	public override void OnStartAuthority()
	{
		UpdateAuthority();
	}

	public override void OnStopAuthority()
	{
		UpdateAuthority();
	}

	private void OnEnable()
	{
		UpdateInnerMotorEnabled();
	}

	private void OnDisable()
	{
		base.Motor.enabled = false;
	}

	private void PreMove(float deltaTime)
	{
		if (!hasEffectiveAuthority)
		{
			return;
		}
		float num = acceleration;
		if (isAirControlForced || !isGrounded)
		{
			num *= (disableAirControlUntilCollision ? 0f : airControl);
		}
		Vector3 vector = moveDirection;
		if (!isFlying)
		{
			vector.y = 0f;
		}
		if (body.isSprinting)
		{
			float magnitude = vector.magnitude;
			if (magnitude < 1f && magnitude > 0f)
			{
				float num2 = 1f / vector.magnitude;
				vector *= num2;
			}
		}
		Vector3 target = vector * walkSpeed;
		if (!isFlying)
		{
			target.y = velocity.y;
		}
		velocity = Vector3.MoveTowards(velocity, target, num * deltaTime);
		if (useGravity)
		{
			ref float y = ref velocity.y;
			y += Physics.gravity.y * deltaTime;
			if (isGrounded)
			{
				y = Mathf.Max(y, 0f);
			}
		}
	}

	public void OnDeathStart()
	{
		alive = false;
	}

	private void FixedUpdate()
	{
		float fixedDeltaTime = Time.fixedDeltaTime;
		if (fixedDeltaTime != 0f)
		{
			Vector3 position = base.transform.position;
			if ((previousPosition - position).sqrMagnitude < 0.00062500004f * fixedDeltaTime)
			{
				restStopwatch += fixedDeltaTime;
			}
			else
			{
				restStopwatch = 0f;
			}
			previousPosition = position;
			if (netIsGrounded)
			{
				lastGroundedTime = Run.FixedTimeStamp.now;
			}
		}
	}

	private void GenerateParameters()
	{
		slopeLimit = 70f;
		stepOffset = Mathf.Min(capsuleHeight * 0.1f, 0.2f);
		stepHandlingMethod = StepHandlingMethod.None;
		ledgeHandling = false;
		interactiveRigidbodyHandling = true;
	}

	public void ApplyForce(Vector3 force, bool alwaysApply = false, bool disableAirControlUntilCollision = false)
	{
		PhysForceInfo forceInfo = PhysForceInfo.Create();
		forceInfo.force = force;
		forceInfo.ignoreGroundStick = alwaysApply;
		forceInfo.disableAirControlUntilCollision = disableAirControlUntilCollision;
		forceInfo.massIsOne = false;
		ApplyForceImpulse(in forceInfo);
	}

	public void ApplyForceImpulse(in PhysForceInfo forceInfo)
	{
		if (NetworkServer.active && !hasEffectiveAuthority)
		{
			CallRpcApplyForceImpulse(forceInfo);
			return;
		}
		Vector3 force = forceInfo.force;
		if (!forceInfo.massIsOne)
		{
			_ = force.magnitude;
			force *= 1f / mass;
		}
		if (mass != 0f)
		{
			if (force.y < 6f && isGrounded && !forceInfo.ignoreGroundStick)
			{
				force.y = 0f;
			}
			if (force.y > 0f)
			{
				base.Motor.ForceUnground();
			}
			velocity += force;
			if (forceInfo.disableAirControlUntilCollision)
			{
				disableAirControlUntilCollision = true;
			}
		}
	}

	[ClientRpc]
	private void RpcApplyForceImpulse(PhysForceInfo physForceInfo)
	{
		if (!NetworkServer.active)
		{
			ApplyForceImpulse(in physForceInfo);
		}
	}

	public override void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
	{
		currentRotation = Quaternion.identity;
	}

	public override void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
	{
		currentVelocity = velocity;
	}

	public override void BeforeCharacterUpdate(float deltaTime)
	{
		_ = cvCMotorSafeCollisionStepThreshold.value;
		_ = cvCMotorSafeCollisionStepThreshold.value;
		if (rootMotion != Vector3.zero)
		{
			Vector3 vector = rootMotion;
			rootMotion = Vector3.zero;
			base.Motor.MoveCharacter(base.transform.position + vector);
		}
		PreMove(deltaTime);
		_ = (velocity * deltaTime).sqrMagnitude;
	}

	public override void PostGroundingUpdate(float deltaTime)
	{
		if (base.Motor.GroundingStatus.IsStableOnGround != base.Motor.LastGroundingStatus.IsStableOnGround)
		{
			netIsGrounded = base.Motor.GroundingStatus.IsStableOnGround;
			if (base.Motor.GroundingStatus.IsStableOnGround)
			{
				OnLanded();
			}
			else
			{
				OnLeaveStableGround();
			}
		}
	}

	private void OnLanded()
	{
		jumpCount = 0;
		HitGroundInfo hitGroundInfo = default(HitGroundInfo);
		hitGroundInfo.velocity = lastVelocity;
		hitGroundInfo.position = base.Motor.GroundingStatus.GroundPoint;
		hitGroundInfo.isValidForEffect = Run.FixedTimeStamp.now - lastGroundedTime > 0.2f;
		HitGroundInfo hitGroundInfo2 = hitGroundInfo;
		if (hasEffectiveAuthority)
		{
			try
			{
				this.onHitGroundAuthority?.Invoke(ref hitGroundInfo2);
			}
			catch (Exception message)
			{
				Debug.LogError(message);
			}
			if (NetworkServer.active)
			{
				GlobalEventManager.instance.OnCharacterHitGroundServer(body, hitGroundInfo2);
			}
			else
			{
				CallCmdReportHitGround(hitGroundInfo2);
			}
		}
	}

	[ClientRpc]
	public void RpcOnHitGroundSFX(HitGroundInfo hitGroundInfo, bool tookFallDamage)
	{
		if (!NetworkServer.active)
		{
			GlobalEventManager.instance.OnCharacterHitGroundSFX(body, hitGroundInfo, tookFallDamage);
		}
	}

	[Command]
	private void CmdReportHitGround(HitGroundInfo hitGroundInfo)
	{
		GlobalEventManager.instance.OnCharacterHitGroundServer(body, hitGroundInfo);
	}

	private void OnLeaveStableGround()
	{
		if (jumpCount < 1)
		{
			jumpCount = 1;
		}
		body.SetInLava(b: false);
	}

	public override void AfterCharacterUpdate(float deltaTime)
	{
		lastVelocity = velocity;
		velocity = base.Motor.BaseVelocity;
	}

	[MethodImpl(MethodImplOptions.AggressiveInlining)]
	public override bool IsColliderValidForCollisions(Collider coll)
	{
		if (!coll.isTrigger)
		{
			return coll != base.Motor.Capsule;
		}
		return false;
	}

	public override void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport)
	{
		isAirControlForced = false;
		SurfaceDef objectSurfaceDef = SurfaceDefProvider.GetObjectSurfaceDef(hitCollider, hitPoint);
		if ((bool)objectSurfaceDef)
		{
			isAirControlForced = objectSurfaceDef.isSlippery;
			body.SetInLava(objectSurfaceDef.isLava);
		}
	}

	public override void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport)
	{
		disableAirControlUntilCollision = false;
		if (this.onMovementHit != null)
		{
			MovementHitInfo movementHitInfo = default(MovementHitInfo);
			movementHitInfo.velocity = velocity;
			movementHitInfo.hitCollider = hitCollider;
			MovementHitInfo movementHitInfo2 = movementHitInfo;
			this.onMovementHit(ref movementHitInfo2);
			SurfaceDef objectSurfaceDef = SurfaceDefProvider.GetObjectSurfaceDef(hitCollider, hitPoint);
			if ((bool)objectSurfaceDef)
			{
				body.SetInLava(objectSurfaceDef.isLava);
			}
		}
	}

	public override void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
	{
	}

	public void Jump(float horizontalMultiplier, float verticalMultiplier, bool vault = false)
	{
		Vector3 vector = moveDirection;
		if (vault)
		{
			velocity = vector;
		}
		else
		{
			vector.y = 0f;
			float magnitude = vector.magnitude;
			if (magnitude > 0f)
			{
				vector /= magnitude;
			}
			Vector3 vector2 = vector * body.moveSpeed * horizontalMultiplier;
			vector2.y = body.jumpPower * verticalMultiplier;
			velocity = vector2;
		}
		base.Motor.ForceUnground();
	}

	public void AddDisplacement(Vector3 displacement)
	{
		rootMotion += displacement;
	}

	static CharacterMotor()
	{
		enableMotorWithoutAuthority = false;
		cvCMotorSafeCollisionStepThreshold = new FloatConVar("cmotor_safe_collision_step_threshold", ConVarFlags.Cheat, 1.0833334f.ToString(CultureInfo.InvariantCulture), "How large of a movement in meters/fixedTimeStep is needed to trigger more expensive \"safe\" collisions to prevent tunneling.");
		kCmdCmdReportHitGround = 1796547162;
		NetworkBehaviour.RegisterCommandDelegate(typeof(CharacterMotor), kCmdCmdReportHitGround, InvokeCmdCmdReportHitGround);
		kRpcRpcApplyForceImpulse = 1042934326;
		NetworkBehaviour.RegisterRpcDelegate(typeof(CharacterMotor), kRpcRpcApplyForceImpulse, InvokeRpcRpcApplyForceImpulse);
		kRpcRpcOnHitGroundSFX = -1661136052;
		NetworkBehaviour.RegisterRpcDelegate(typeof(CharacterMotor), kRpcRpcOnHitGroundSFX, InvokeRpcRpcOnHitGroundSFX);
		NetworkCRC.RegisterBehaviour("CharacterMotor", 0);
	}

	void IPhysMotor.ApplyForceImpulse(in PhysForceInfo physForceInfo)
	{
		ApplyForceImpulse(in physForceInfo);
	}

	private void UNetVersion()
	{
	}

	protected static void InvokeCmdCmdReportHitGround(NetworkBehaviour obj, NetworkReader reader)
	{
		if (!NetworkServer.active)
		{
			Debug.LogError("Command CmdReportHitGround called on client.");
		}
		else
		{
			((CharacterMotor)obj).CmdReportHitGround(GeneratedNetworkCode._ReadHitGroundInfo_CharacterMotor(reader));
		}
	}

	public void CallCmdReportHitGround(HitGroundInfo hitGroundInfo)
	{
		if (!NetworkClient.active)
		{
			Debug.LogError("Command function CmdReportHitGround called on server.");
			return;
		}
		if (base.isServer)
		{
			CmdReportHitGround(hitGroundInfo);
			return;
		}
		NetworkWriter networkWriter = new NetworkWriter();
		networkWriter.Write((short)0);
		networkWriter.Write((short)5);
		networkWriter.WritePackedUInt32((uint)kCmdCmdReportHitGround);
		networkWriter.Write(GetComponent<NetworkIdentity>().netId);
		GeneratedNetworkCode._WriteHitGroundInfo_CharacterMotor(networkWriter, hitGroundInfo);
		SendCommandInternal(networkWriter, 0, "CmdReportHitGround");
	}

	protected static void InvokeRpcRpcApplyForceImpulse(NetworkBehaviour obj, NetworkReader reader)
	{
		if (!NetworkClient.active)
		{
			Debug.LogError("RPC RpcApplyForceImpulse called on server.");
		}
		else
		{
			((CharacterMotor)obj).RpcApplyForceImpulse(GeneratedNetworkCode._ReadPhysForceInfo_None(reader));
		}
	}

	protected static void InvokeRpcRpcOnHitGroundSFX(NetworkBehaviour obj, NetworkReader reader)
	{
		if (!NetworkClient.active)
		{
			Debug.LogError("RPC RpcOnHitGroundSFX called on server.");
		}
		else
		{
			((CharacterMotor)obj).RpcOnHitGroundSFX(GeneratedNetworkCode._ReadHitGroundInfo_CharacterMotor(reader), reader.ReadBoolean());
		}
	}

	public void CallRpcApplyForceImpulse(PhysForceInfo physForceInfo)
	{
		if (!NetworkServer.active)
		{
			Debug.LogError("RPC Function RpcApplyForceImpulse called on client.");
			return;
		}
		NetworkWriter networkWriter = new NetworkWriter();
		networkWriter.Write((short)0);
		networkWriter.Write((short)2);
		networkWriter.WritePackedUInt32((uint)kRpcRpcApplyForceImpulse);
		networkWriter.Write(GetComponent<NetworkIdentity>().netId);
		GeneratedNetworkCode._WritePhysForceInfo_None(networkWriter, physForceInfo);
		SendRPCInternal(networkWriter, 0, "RpcApplyForceImpulse");
	}

	public void CallRpcOnHitGroundSFX(HitGroundInfo hitGroundInfo, bool tookFallDamage)
	{
		if (!NetworkServer.active)
		{
			Debug.LogError("RPC Function RpcOnHitGroundSFX called on client.");
			return;
		}
		NetworkWriter networkWriter = new NetworkWriter();
		networkWriter.Write((short)0);
		networkWriter.Write((short)2);
		networkWriter.WritePackedUInt32((uint)kRpcRpcOnHitGroundSFX);
		networkWriter.Write(GetComponent<NetworkIdentity>().netId);
		GeneratedNetworkCode._WriteHitGroundInfo_CharacterMotor(networkWriter, hitGroundInfo);
		networkWriter.Write(tookFallDamage);
		SendRPCInternal(networkWriter, 0, "RpcOnHitGroundSFX");
	}

	public override bool OnSerialize(NetworkWriter writer, bool forceAll)
	{
		bool flag = base.OnSerialize(writer, forceAll);
		bool flag2 = default(bool);
		return flag2 || flag;
	}

	public override void OnDeserialize(NetworkReader reader, bool initialState)
	{
		base.OnDeserialize(reader, initialState);
	}

	public override void PreStartClient()
	{
		base.PreStartClient();
	}
}
