Weapons/ViewModel.cs

ViewModel component for a weapon view model. It reads owner and equipment state, updates skinned model parameters (deploy, jump, movement, inertia), applies bone transform to the camera, and smooths local position/rotation and field of view offsets each frame.

Native Interop

namespace KOTH;

public partial class ViewModel : Component, IEquipment
{
	public Equipment Equipment { get; set; }
	PlayerPawn Owner => Equipment.IsValid() ? Equipment.Owner : null;

	[Property, Group("Components")] public SkinnedModelRenderer Arms { get; set; }
	[Property, Group("GameObjects")] public GameObject Muzzle { get; set; }
	[Property, Group("GameObjects")] public GameObject EjectionPort { get; set; }
	[Property, Group("Components")] public SkinnedModelRenderer ModelRenderer { get; set; }
	[Property, Group("Configuration")] public bool UseMovementInertia { get; set; } = true;

	private float YawInertiaScale => 2f;
	private float PitchInertiaScale => 2f;
	private bool activateInertia = false;
	private float lastPitch;
	private float lastYaw;
	private float YawInertia;
	private float PitchInertia;

	protected override void OnAwake()
	{
		ModelRenderer?.Set("b_deploy_skip", true);
	}

	protected override void OnStart()
	{
		if (Owner.IsValid())
		{
			Owner.OnJump += OnPlayerJumped;
		}
	}

	void OnPlayerJumped()
	{
		ModelRenderer?.Set("b_jump", true);
	}

	void ApplyAnimationTransform()
	{
		if (!ModelRenderer.IsValid()) return;
		if (!ModelRenderer.Enabled) return;

		var bone = ModelRenderer.SceneModel.GetBoneLocalTransform("camera");
		var camera = Equipment.Owner.Camera.GameObject;

		camera.LocalPosition += bone.Position;
		camera.LocalRotation *= bone.Rotation;
	}

	void ApplyInertia()
	{
		var PlayerCameraObject = Equipment.Owner.Camera.GameObject;
		var inRot = PlayerCameraObject.WorldRotation;

		// Need to fetch data from the camera for the first frame
		if (!activateInertia)
		{
			lastPitch = inRot.Pitch();
			lastYaw = inRot.Yaw();
			YawInertia = 0;
			PitchInertia = 0;
			activateInertia = true;
		}

		var newPitch = PlayerCameraObject.WorldRotation.Pitch();
		var newYaw = PlayerCameraObject.WorldRotation.Yaw();

		PitchInertia = Angles.NormalizeAngle(newPitch - lastPitch);
		YawInertia = Angles.NormalizeAngle(lastYaw - newYaw);

		lastPitch = newPitch;
		lastYaw = newYaw;
	}

	private Vector3 lerpedWishMove;

	private Vector3 localPosition;
	private Rotation localRotation;

	private Vector3 lerpedLocalPosition;
	private Rotation lerpedlocalRotation;

	protected void ApplyVelocity()
	{
		var moveVel = Owner.CharacterController.Velocity;
		var moveLen = moveVel.Length;

		var wishMove = Owner.WishMove.Normal * 1f;

		if (Owner.IsCrouching) moveLen *= 0.5f;

		lerpedWishMove = lerpedWishMove.LerpTo(wishMove, Time.Delta * 7.0f);
		ModelRenderer?.Set("move_bob", moveLen.Remap(0, 300, 0, 1, true));

		if (UseMovementInertia)
			YawInertia += lerpedWishMove.y * 10f;

		ModelRenderer?.Set("aim_yaw_inertia", YawInertia * YawInertiaScale);
		ModelRenderer?.Set("aim_pitch_inertia", PitchInertia * PitchInertiaScale);
	}

	private float FieldOfViewOffset = 0f;
	private float TargetFieldOfView = 90f;

	void ApplyAnimationParameters()
	{
		ModelRenderer.Set("b_grounded", Owner.IsGrounded);

		// Handedness
		ModelRenderer.Set("b_twohanded", true);

		//// Weapon state
		//ModelRenderer.Set("b_empty", !Equipment.Components.Get<AmmoComponent>(FindMode.EnabledInSelfAndDescendants)?.HasAmmo ?? false);
	}

	public bool PlayDeployEffects
	{
		set
		{
			ModelRenderer?.Set("b_deploy", value);
			ModelRenderer?.Set("b_deploy_skip", !value);
		}
	}

	protected override void OnUpdate()
	{
		// Reset every frame
		localRotation = Rotation.Identity;
		localPosition = Vector3.Zero;

		if (!Owner.IsValid() || !Owner.CharacterController.IsValid() || !Owner.IsAlive)
			return;

		ApplyAnimationParameters();

		ApplyVelocity();
		ApplyAnimationTransform();
		ApplyInertia();

		TargetFieldOfView = TargetFieldOfView.LerpTo(90 + FieldOfViewOffset, Time.Delta * 10f);
		FieldOfViewOffset = 0;

		lerpedlocalRotation = Rotation.Lerp(lerpedlocalRotation, localRotation, Time.Delta * 10f);
		lerpedLocalPosition = lerpedLocalPosition.LerpTo(localPosition, Time.Delta * 10f);

		LocalRotation = lerpedlocalRotation;
		LocalPosition = lerpedLocalPosition;
	}
}