Weapons/Base/ViewModel.cs
using Opium;
using Sandbox.Utility;
public sealed class ViewModel : Component, Actor.IEventListener
{
BaseWeapon weapon;
[Property] public BaseWeapon Weapon
{
get => weapon;
set
{
if ( weapon == value ) return;
weapon = value;
if ( weapon is not null )
{
weapon.EventListener += OnWeaponEvent;
}
}
}
[Property] public SkinnedModelRenderer ModelRenderer { get; set; }
[Property] public CodeAnimations CodeAnimationFlags { get; set; }
[Property] public List<GameObject> AmmoStack { get; set; }
[Property, Group( "Avoidance" )] public float WeaponLength { get; set; } = 50f;
[Property, Group( "Avoidance" )] public Vector3 WeaponAvoidanceOffset { get; set; } = new( 0.1f, 0.05f, 0.05f );
[Property, Group( "Avoidance" )] public Angles WeaponAvoidanceAngles { get; set; } = new( 50f, 0, 0 );
public int CurrentAmmo = 999;
void TryUpdateAmmoStack()
{
if ( Weapon is RangedWeapon range )
{
if ( range.CurrentAmmo != CurrentAmmo )
{
CurrentAmmo = range.CurrentAmmo;
UpdateAmmoStack();
}
CurrentAmmo = range.CurrentAmmo;
}
}
void UpdateAmmoStack()
{
if ( AmmoStack is null ) return;
for ( int i = 0; i < AmmoStack.Count; i++ )
{
var obj = AmmoStack[i];
if ( CurrentAmmo >= i + 1 )
{
obj.Enabled = true;
}
else
{
obj.Enabled = false;
}
}
}
[Flags]
public enum CodeAnimations
{
None = 1 << 0,
Swing = 1 << 1,
Blocking = 1 << 2,
EquipState = 1 << 3
};
protected override void OnDisabled()
{
if ( Weapon is not null )
{
Weapon.EventListener -= OnWeaponEvent;
}
}
protected override void OnEnabled()
{
DoCodeAnimations();
}
void OnWeaponEvent( string eventName )
{
if ( eventName == "shoot" )
{
DoShootEffects();
}
if ( eventName == "attack" )
{
ModelRenderer.Set( "b_attack", true );
}
if ( eventName == "use" )
{
ModelRenderer.Set( "b_grab", true );
}
if ( eventName == "ammocheck" )
{
ModelRenderer.Set( "b_ammocheck", true );
}
}
void DoShootEffects()
{
if ( Weapon is RangedWeapon rangedWeapon && rangedWeapon.MuzzleFlashPrefab is not null )
{
var muzzleGameObject = GameObject.GetAllObjects( true ).FirstOrDefault( x => x.Tags.Has( "muzzle" ) );
if ( muzzleGameObject is not null )
{
var instance = rangedWeapon.MuzzleFlashPrefab.Clone();
instance.Enabled = true;
instance.SetParent( muzzleGameObject, false );
instance.Transform.Position = muzzleGameObject.Transform.Position;
instance.Transform.Rotation = muzzleGameObject.Transform.Rotation;
}
var ejectGameObject = GameObject.GetAllObjects( true ).FirstOrDefault( x => x.Tags.Has( "eject" ) );
if( ejectGameObject is not null )
{
var instance = rangedWeapon.EjectPrefab.Clone();
instance.Enabled = true;
instance.Transform.Position = ejectGameObject.Transform.Position;
instance.Transform.Rotation = ejectGameObject.Transform.Rotation;
var rb = instance.Components.Get<Rigidbody>();
rb.Velocity = ejectGameObject.Transform.Rotation.Forward * 100f;
}
}
}
/// <summary>
/// Accessor for the player controller.
/// This cast is fine, since viewmodels are only made on players.
/// </summary>
public Opium.PlayerController PlayerController => Weapon?.Actor as Opium.PlayerController;
void SetMeleeWeaponAnimState( string stateName )
{
/// this sucks
var stateToId = stateName switch
{
"windup" => 1,
"attack" => 2,
"hit" => 3,
"block" => 4,
"blockhit" => 5,
_ => 0
};
ModelRenderer.Set( "state", stateToId );
}
void SetVariationState( string stateName )
{
/// this sucks
var stateToId = stateName switch
{
"left_handed" => 1,
_ => 0
};
ModelRenderer.Set( "variation_state", stateToId );
}
void SetRangedWeaponAnimState( string stateName )
{
/// this sucks
var stateToId = stateName switch
{
"attack" => 1,
"reload" => 2,
"hit" => 3,
_ => 0
};
ModelRenderer.Set( "state", stateToId );
}
void ApplyAnimGraphVariables()
{
if ( Weapon is MeleeWeapon melee && melee.IsValid() )
{
var attack = melee.CurrentAttack;
if ( attack.IsValid() && !string.IsNullOrEmpty( attack.CurrentStateInfo.AnimationState ) )
{
ModelRenderer.Set( "b_hover", false );
SetMeleeWeaponAnimState( attack.CurrentStateInfo.AnimationState );
SetVariationState( attack.CurrentStateInfo.VariationState );
}
else
{
// Bit shitty, but whatever.
var interactUse = PlayerController.Components.Get<InteractUse>();
if ( interactUse is not null && interactUse.InteractingObject is not null )
{
ModelRenderer.Set( "b_hover", true );
return;
}
else
{
ModelRenderer.Set( "b_hover", false );
}
SetMeleeWeaponAnimState( "default" );
}
}
else if ( Weapon is RangedWeapon range && range.IsValid() )
{
// Bit dumb
var isFiring = Weapon.TimeSinceShoot <= 0.5f;
var interactUse = PlayerController.Components.Get<InteractUse>();
var isInteracting = interactUse is not null && interactUse.LookingAtObject is not null;
ModelRenderer.Set( "b_hover", isInteracting && !isFiring && !range.IsAiming );
SetRangedWeaponAnimState( isFiring ? "attack" : "idle" );
}
}
TimeSince timeSinceLastAction = 5;
bool shouldDoLowering = false;
Vector3 lowerOffset = Vector3.Zero;
Rotation lowerOffsetRot = Rotation.Identity;
public void DoCodeAnimations()
{
if ( !PlayerController.IsValid() )
{
Log.Info( $"No player controller. {Weapon}" );
return;
}
// This can happen somehow?
if ( !weapon.IsValid() )
return;
var inventory = PlayerController.Inventory;
var position = Vector3.Zero;
position += Transform.Rotation.Forward * 10f;
position += Transform.Rotation.Down * 20f;
position += Transform.Rotation.Right * 10f;
var rotation = Rotation.Identity;
rotation *= Rotation.FromPitch( 25 );
rotation *= Rotation.FromYaw( 45 );
// Wow this is fucking horrible
shouldDoLowering = weapon.GameObject.Name == "fists";
if ( inventory.EquipState != PlayerInventory.EquippedState.Ready )
{
var timeSince = inventory.EquipState == PlayerInventory.EquippedState.Deploying ? inventory.TimeSinceDeployed : inventory.TimeSinceHolstered;
var delta = timeSince / inventory.EquipTime;
bool reverse = inventory.EquipState == PlayerInventory.EquippedState.Deploying ? true : false;
delta = reverse ? 1 - delta : delta;
delta = Easing.EaseIn( delta );
Transform.LocalPosition = position * delta;
Transform.LocalRotation = rotation * delta;
}
else
{
Transform.LocalPosition = Vector3.Zero;
Transform.LocalRotation = Rotation.Identity;
}
Vector3 posOffset = Vector3.Down * 25f;
var interactUse = PlayerController.Components.Get<PlayerInteractor>();
var shouldLower = shouldDoLowering && timeSinceLastAction > 5f || !PlayerController.IsAlive || interactUse.currentlyCarriedObject.IsValid();
lowerOffsetRot = Rotation.Lerp( lowerOffsetRot, shouldLower ? Rotation.FromPitch( -25 ) : Rotation.Identity, Time.Delta * 3f );
lowerOffset = lowerOffset.LerpTo( shouldLower ? posOffset : 0, Time.Delta * 4f );
Transform.LocalPosition += lowerOffset;
Transform.LocalRotation *= lowerOffsetRot;
}
protected override void OnUpdate()
{
ApplyAnimGraphVariables();
TryUpdateAmmoStack();
DoCodeAnimations();
}
public void OnEvent( Actor actor, string eventName, params object[] obj )
{
if ( actor.ActiveWeapon == weapon && ( eventName == "shoot" || eventName == "aim" ) )
{
timeSinceLastAction = 0;
}
}
}