Weapons/WeaponModel/ViewModel.cs
using static BaseWeapon;
public sealed partial class ViewModel : WeaponModel, IWeaponEvent, ICameraSetup, PlayerController.IEvents
{
[ConVar( "sbdm.hideviewmodel", ConVarFlags.Cheat )]
private static bool HideViewModel { get; set; } = false;
/// <summary>
/// Turns on incremental reloading parameters.
/// </summary>
[Property, Group( "Animation" )]
public bool IsIncremental { get; set; } = false;
/// <summary>
/// Animation speed in general.
/// </summary>
[Property, Group( "Animation" )]
public float AnimationSpeed { get; set; } = 1.0f;
/// <summary>
/// Animation speed for incremental reload sections.
/// </summary>
[Property, Group( "Animation" )]
public float IncrementalAnimationSpeed { get; set; } = 3.0f;
[Property] public bool UseFastAnimations { get; set; } = false;
/// <summary>
/// How much inertia should this weapon have?
/// </summary>
[Property, Group( "Inertia" )]
Vector2 InertiaScale { get; set; } = new Vector2( 2, 2 );
bool IsAttacking;
TimeSince AttackDuration;
Vector2 lastInertia;
Vector2 currentInertia;
bool isFirstUpdate = true;
protected override void OnStart()
{
foreach ( var renderer in GetComponentsInChildren<ModelRenderer>() )
{
// Don't render shadows for viewmodels
renderer.RenderType = ModelRenderer.ShadowRenderType.Off;
}
}
protected override void OnUpdate()
{
UpdateAnimation();
}
void ApplyInertia()
{
// Need to fetch data from the camera for the first frame
if ( isFirstUpdate )
{
var rot = Scene.Camera.WorldRotation;
lastInertia = new Vector2( rot.Pitch(), rot.Yaw() );
currentInertia = Vector2.Zero;
isFirstUpdate = false;
}
var newPitch = Scene.Camera.WorldRotation.Pitch();
var newYaw = Scene.Camera.WorldRotation.Yaw();
currentInertia = new Vector2( Angles.NormalizeAngle( newPitch - lastInertia.x ), Angles.NormalizeAngle( lastInertia.y - newYaw ) );
lastInertia = new( newPitch, newYaw );
}
void ICameraSetup.Setup( CameraComponent cc )
{
Renderer.Enabled = !HideViewModel;
WorldPosition = cc.WorldPosition;
WorldRotation = cc.WorldRotation;
ApplyInertia();
ApplyAnimationTransform( cc );
}
void ApplyAnimationTransform( CameraComponent cc )
{
if ( !Renderer.IsValid() ) return;
var bone = Renderer.GetBoneObject( "camera" );
if ( !bone.IsValid() )
{
return;
}
var scale = 1;
cc.LocalRotation *= bone.LocalRotation * scale;
cc.LocalPosition += cc.WorldRotation * bone.LocalPosition * scale;
}
void UpdateAnimation()
{
var playerController = GetComponentInParent<PlayerController>();
if ( !playerController.IsValid() ) return;
Renderer.Set( "b_twohanded", true );
Renderer.Set( "b_grounded", playerController.IsOnGround );
Renderer.Set( "move_bob", GamePreferences.ViewBobbing ? playerController.Velocity.Length.Remap( 0, playerController.RunSpeed * 2f ) : 0 );
Renderer.Set( "aim_pitch_inertia", currentInertia.x * InertiaScale.x );
Renderer.Set( "aim_yaw_inertia", currentInertia.y * InertiaScale.y );
Renderer.Set( "attack_hold", IsAttacking ? AttackDuration.Relative.Clamp( 0f, 1f ) : 0f );
Renderer.Set( "deploy_type", UseFastAnimations ? 1 : 0 );
Renderer.Set( "reload_type", UseFastAnimations ? 1 : 0 );
var hovered = playerController.Hovered;
var isHovering = hovered.IsValid() && hovered.WorldPosition.DistanceSquared( WorldPosition ) < 16384;
Renderer.Set( "b_grab", isHovering );
}
void IWeaponEvent.OnAttack( IWeaponEvent.AttackEvent e )
{
Renderer?.Set( "b_attack", true );
DoMuzzleEffect();
DoEjectBrass();
if ( IsThrowable )
{
Renderer?.Set( "b_throw", true );
Invoke( 0.5f, () =>
{
Renderer?.Set( "b_deploy_new", true );
Renderer?.Set( "b_pull", false );
} );
}
}
void IWeaponEvent.OnAttackHit( IWeaponEvent.AttackEvent e )
{
Renderer?.Set( "b_attack_has_hit", true );
}
void IWeaponEvent.CreateRangedEffects( BaseWeapon weapon, Vector3 hitPoint, Vector3? origin )
{
DoTracerEffect( hitPoint, origin );
}
void PlayerController.IEvents.StartPressing( Component target )
{
var grabAction = target.GetComponent<GrabAction>();
var actionStyle = grabAction.IsValid() ? (int)grabAction.Style : 1;
Renderer.Set( "grab_action", actionStyle );
}
/// <summary>
/// Called when starting to reload a weapon.
/// </summary>
void IWeaponEvent.OnReloadStart()
{
Renderer?.Set( "speed_reload", AnimationSpeed );
Renderer?.Set( IsIncremental ? "b_reloading" : "b_reload", true );
}
/// <summary>
/// Called when incrementally reloading a weapon.
/// </summary>
void IWeaponEvent.OnIncrementalReload()
{
Renderer?.Set( "speed_reload", IncrementalAnimationSpeed );
Renderer?.Set( "b_reloading_shell", true );
}
const float IncrementalReloadDelay = 0.5f;
void IWeaponEvent.OnReloadFinish()
{
if ( IsIncremental )
{
//
// Stops the reload after a little delay so it's not immediately cancelling the animation.
//
Invoke( IncrementalReloadDelay, () =>
{
Renderer?.Set( "speed_reload", AnimationSpeed );
Renderer?.Set( "b_reloading", false );
} );
}
else
{
Renderer?.Set( "b_reload", false );
}
}
}