Weapons/MeleeWeapon.cs
using Opium;
using System.Diagnostics.Metrics;
public sealed class MeleeWeapon : BaseWeapon
{
/// <summary>
/// The trace radius for the arc.
/// </summary>
[Property] public float ArcTraceRadius { get; set; } = 1f;
/// <summary>
/// Durability of the weapon
/// </summary>
[Property] public int Durability { get; set; } = 100;
/// <summary>
/// How much durability should we take on hit?
/// </summary>
[Property] public int DurabilityTakenOnHit { get; set; } = 10;
/// <summary>
/// The cooldown between aiming.
/// </summary>
[Property] public float AimCooldown { get; set; } = 1.5f;
[Property] public float AttackCooldown { get; set; } = 1f;
/// <summary>
/// Does the arc open doors?
/// </summary>
[Property] public bool HitOpenDoor { get; set; } = true;
public bool IsBroken => Durability <= 0;
/// <summary>
/// This is a bit shit.
/// </summary>
public WeaponArcOrigin ArcOrigin => Actor.CameraObject.Components.Get<WeaponArcOrigin>( FindMode.EverythingInSelfAndDescendants );
/// <summary>
/// Accessor for attacks
/// </summary>
public IEnumerable<MeleeWeaponAttack> Attacks => this.IsValid() ?
Components.GetAll<MeleeWeaponAttack>( FindMode.EverythingInSelfAndDescendants ) : new List<MeleeWeaponAttack>();
public MeleeWeaponAttack CurrentAttack => Attacks.FirstOrDefault( x => x.IsValid() && x.IsActive );
public int CurrentIndex = 0;
[Property] public List<MeleeWeaponAttack> MainAttacks { get; set; }
[Property, ReadOnly] public MeleeWeaponAttack MainAttack { get; set; }
[Property] public MeleeWeaponAttack BlockAttack { get; set; }
[Property] public float BlockDamageFactor { get; set; } = 0f;
/// <summary>
/// How much posture damage does this weapon do when hitting someone?
/// </summary>
[Property, Group( "Posture" )] public float Posture { get; set; } = 25f;
protected override void OnEnabled()
{
MainAttack = MainAttacks.FirstOrDefault();
CurrentIndex = 0;
}
public override void Shoot()
{
var idx = CurrentIndex;
if ( idx > MainAttacks.Count - 1 )
{
idx = 0;
CurrentIndex = 0;
}
Actor.TriggerEvent( "shoot", this );
var attack = MainAttacks[idx];
MainAttack = attack;
attack?.Activate();
CurrentIndex++;
}
public override void Aim()
{
base.Aim();
Actor.TriggerEvent( "aim", this );
BlockAttack?.Activate();
}
/// <summary>
/// Can we aim? In this case, it's probably a block attack. That or it's an alternate attack..
/// </summary>
/// <returns></returns>
public override bool CanAim()
{
if ( !Actor.CanAim( this ) ) return false;
if ( CurrentAttack is not null ) return false;
if ( TimeSinceShoot < AttackCooldown ) return false;
if ( Attacks.Any( x => x.IsActive ) ) return false;
return TimeSinceAim > AimCooldown;
}
public bool TestHit( Vector3 arcPosition, out SceneTraceResult tr, float delta = 1f )
{
tr = Scene.Trace.Ray( ArcOrigin.Transform.World.Position, arcPosition )
.IgnoreGameObjectHierarchy( GameObject.Root )
.UseHitboxes( true )
.Size( ArcTraceRadius * delta )
.Run();
if ( tr.Hit && tr.GameObject is not null )
{
return true;
}
return false;
}
public void DoMeleeHit( SceneTraceResult tr )
{
// Inflict damage on whatever we find.
var damageInfo = Opium.DamageInfo.Generic( BaseDamage, Actor?.GameObject ?? GameObject, GameObject, "melee", HitOpenDoor ? "open_door" : "" );
CalculateDamage( damageInfo );
tr.GameObject.TakeDamage( damageInfo );
bool isRicochet = tr.GameObject.Components.Get<Actor>( FindMode.EverythingInSelfAndAncestors ) is not null && damageInfo.Damage == 0;
var metal = ResourceLibrary.Get<Surface>( "surface/world/op_metal.surface" );
Surface surface = isRicochet ? metal : GetSurfaceFromTrace( tr );
// If it's an actor and the damage is zero, do nothing
var rb = tr.GameObject.Components.Get<Rigidbody>( FindMode.EnabledInSelfAndChildren );
if ( rb is not null )
{
rb.ApplyImpulseAt( tr.EndPosition, tr.Normal * 10000f );
}
var skinnedModelRenderer = tr.GameObject.Components.Get<SkinnedModelRenderer>();
if ( skinnedModelRenderer is not null && tr.Hitbox is not null && tr.Hitbox.Bone is not null )
{
CreateImpactEffects( skinnedModelRenderer.GetBoneObject( tr.Hitbox.Bone ), surface, tr.EndPosition, tr.Normal );
}
else
CreateImpactEffects( tr.GameObject, surface, tr.EndPosition, tr.Normal );
// Only take durability if we hit an actor
if ( tr.GameObject.Tags.Has( "actor" ) )
{
Durability -= DurabilityTakenOnHit;
Actor.TriggerEvent( "durability_loss", Durability );
}
if ( Durability <= 0 )
{
DropAsync();
}
}
// TODO: This fucking sucks!!!!!
async void DropAsync()
{
await GameTask.DelaySeconds( 0.25f );
if ( !this.IsValid() )
return;
if ( Actor.IsValid() )
return;
var wpn = Actor.DropWeapon( this );
if ( !wpn.IsValid() )
return;
var rigidbody = wpn.Components.Get<Rigidbody>( FindMode.EverythingInSelfAndChildren );
if ( !rigidbody.IsValid() )
return;
rigidbody.ApplyImpulse( Vector3.Random * 10000f );
}
/// <summary>
/// Overriden.
/// </summary>
/// <returns></returns>
public override bool CanShoot()
{
if ( !base.CanShoot() ) return false;
if ( TimeSinceShoot < AttackCooldown ) return false;
if ( Durability <= 0 ) return false;
// Can only run primary attack if one is not active.
return !Attacks.Any( x => x.IsActive );
}
}