294 lines
8.4 KiB
C#
294 lines
8.4 KiB
C#
|
using SWB.Shared;
|
|||
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
|
|||
|
namespace SWB.Base;
|
|||
|
|
|||
|
public partial class Weapon
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// Checks if the weapon can do the provided attack
|
|||
|
/// </summary>
|
|||
|
/// <param name="shootInfo">Attack information</param>
|
|||
|
/// <param name="lastAttackTime">Time since this attack</param>
|
|||
|
/// <param name="inputButton">The input button for this attack</param>
|
|||
|
/// <returns></returns>
|
|||
|
public virtual bool CanShoot( ShootInfo shootInfo, TimeSince lastAttackTime, string inputButton )
|
|||
|
{
|
|||
|
if ( (IsReloading && !ShellReloading) || (IsReloading && ShellReloading && !ShellReloadingShootCancel) || InBoltBack ) return false;
|
|||
|
if ( !Owner.IsValid() || !Input.Down( inputButton ) ) return false; // || (Secondary is null) shootInfo is null ||
|
|||
|
if ( !HasAmmo() )
|
|||
|
{
|
|||
|
if ( Input.Pressed( inputButton ) )
|
|||
|
{
|
|||
|
// Check for auto reloading
|
|||
|
if ( Settings.AutoReload && lastAttackTime > GetRealRPM( shootInfo.RPM ) )
|
|||
|
{
|
|||
|
TimeSincePrimaryShoot = 999;
|
|||
|
TimeSinceSecondaryShoot = 999;
|
|||
|
|
|||
|
if ( ShellReloading )
|
|||
|
OnShellReload();
|
|||
|
else
|
|||
|
Reload();
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
// Dry fire
|
|||
|
if ( shootInfo.DryShootSound is not null )
|
|||
|
PlaySound( shootInfo.DryShootSound.ResourceId );
|
|||
|
}
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
if ( shootInfo.FiringType == FiringType.semi && !Input.Pressed( inputButton ) ) return false;
|
|||
|
if ( shootInfo.FiringType == FiringType.burst )
|
|||
|
{
|
|||
|
if ( burstCount > 2 ) return false;
|
|||
|
|
|||
|
if ( Input.Down( inputButton ) && lastAttackTime > GetRealRPM( shootInfo.RPM ) )
|
|||
|
{
|
|||
|
burstCount++;
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
return false;
|
|||
|
};
|
|||
|
|
|||
|
if ( shootInfo.RPM <= 0 ) return true;
|
|||
|
|
|||
|
return lastAttackTime > GetRealRPM( shootInfo.RPM );
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Checks if weapon can do the primary attack
|
|||
|
/// </summary>
|
|||
|
public virtual bool CanPrimaryShoot()
|
|||
|
{
|
|||
|
return CanShoot( Primary, TimeSincePrimaryShoot, InputButtonHelper.PrimaryAttack );
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Checks if weapon can do the secondary attack
|
|||
|
/// </summary>
|
|||
|
public virtual bool CanSecondaryShoot()
|
|||
|
{
|
|||
|
return false; // CanShoot( Secondary, TimeSinceSecondaryShoot, InputButtonHelper.SecondaryAttack );
|
|||
|
}
|
|||
|
|
|||
|
public virtual void Shoot( ShootInfo shootInfo, bool isPrimary )
|
|||
|
{
|
|||
|
Log.Info(shootInfo);
|
|||
|
// Ammo
|
|||
|
shootInfo.Ammo -= 1;
|
|||
|
|
|||
|
// Animations
|
|||
|
var shootAnim = GetShootAnimation( shootInfo );
|
|||
|
if ( !string.IsNullOrEmpty( shootAnim ) )
|
|||
|
WorldModelRenderer.Set( shootAnim, true );
|
|||
|
|
|||
|
// Sound
|
|||
|
if ( shootInfo.ShootSound is not null )
|
|||
|
PlaySound( shootInfo.ShootSound.ResourceId );
|
|||
|
|
|||
|
// Particles
|
|||
|
HandleShootEffects( isPrimary );
|
|||
|
|
|||
|
// Barrel smoke
|
|||
|
barrelHeat += 1;
|
|||
|
|
|||
|
// Recoil
|
|||
|
// Owner.EyeAnglesOffset += GetRecoilAngles( shootInfo );
|
|||
|
|
|||
|
// Screenshake
|
|||
|
// if ( shootInfo.ScreenShake is not null )
|
|||
|
// Owner.ShakeScreen( shootInfo.ScreenShake );
|
|||
|
|
|||
|
// UI
|
|||
|
BroadcastUIEvent( "shoot", GetRealRPM( shootInfo.RPM ) );
|
|||
|
|
|||
|
// Bullet
|
|||
|
for ( int i = 0; i < shootInfo.Bullets; i++ )
|
|||
|
{
|
|||
|
var realSpread = IsScoping ? 0 : GetRealSpread( shootInfo.Spread );
|
|||
|
var spreadOffset = shootInfo.BulletType.GetRandomSpread( realSpread );
|
|||
|
ShootBullet( isPrimary, spreadOffset );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
[Broadcast]
|
|||
|
public virtual void ShootBullet( bool isPrimary, Vector3 spreadOffset )
|
|||
|
{
|
|||
|
if ( !IsValid ) return;
|
|||
|
var shootInfo = GetShootInfo( isPrimary );
|
|||
|
shootInfo?.BulletType?.Shoot( this, shootInfo, spreadOffset );
|
|||
|
}
|
|||
|
|
|||
|
/// <summary> A single bullet trace from start to end with a certain radius.</summary>
|
|||
|
public virtual SceneTraceResult TraceBullet( Vector3 start, Vector3 end, float radius = 2.0f )
|
|||
|
{
|
|||
|
var startsInWater = SurfaceUtil.IsPointWater( start );
|
|||
|
List<string> withoutTags = new() { TagsHelper.Trigger, TagsHelper.PlayerClip, TagsHelper.PassBullets, TagsHelper.ViewModel };
|
|||
|
|
|||
|
if ( startsInWater )
|
|||
|
withoutTags.Add( TagsHelper.Water );
|
|||
|
|
|||
|
var tr = Scene.Trace.Ray( start, end )
|
|||
|
.UseHitboxes()
|
|||
|
.WithoutTags( withoutTags.ToArray() )
|
|||
|
.Size( radius )
|
|||
|
.IgnoreGameObjectHierarchy( Owner.GameObject )
|
|||
|
.Run();
|
|||
|
|
|||
|
// Log.Info( tr.GameObject );
|
|||
|
|
|||
|
return tr;
|
|||
|
}
|
|||
|
|
|||
|
[Broadcast]
|
|||
|
public virtual void HandleShootEffects( bool isPrimary )
|
|||
|
{
|
|||
|
if ( !IsValid || Owner is null ) return;
|
|||
|
|
|||
|
// Player
|
|||
|
Owner.BodyRenderer.Set( "b_attack", true );
|
|||
|
|
|||
|
// Weapon
|
|||
|
var shootInfo = GetShootInfo( isPrimary );
|
|||
|
if ( shootInfo is null ) return;
|
|||
|
|
|||
|
var scale = shootInfo.WMParticleScale;
|
|||
|
var muzzleTransform = GetMuzzleTransform();
|
|||
|
|
|||
|
// Bullet eject
|
|||
|
if ( shootInfo.BulletEjectParticle is not null )
|
|||
|
{
|
|||
|
if ( !BoltBack )
|
|||
|
{
|
|||
|
if ( !ShellReloading || (ShellReloading && ShellEjectDelay == 0) )
|
|||
|
{
|
|||
|
CreateParticle( shootInfo.BulletEjectParticle, "ejection_point", scale );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
var delayedEject = async () =>
|
|||
|
{
|
|||
|
await GameTask.DelaySeconds( ShellEjectDelay );
|
|||
|
if ( !IsValid ) return;
|
|||
|
CreateParticle( shootInfo.BulletEjectParticle, "ejection_point", scale );
|
|||
|
};
|
|||
|
delayedEject();
|
|||
|
}
|
|||
|
}
|
|||
|
else if ( shootInfo.Ammo > 0 )
|
|||
|
{
|
|||
|
AsyncBoltBack( GetRealRPM( shootInfo.RPM ) );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if ( !muzzleTransform.HasValue ) return;
|
|||
|
|
|||
|
// Muzzle flash
|
|||
|
if ( shootInfo.MuzzleFlashParticle is not null )
|
|||
|
CreateParticle( shootInfo.MuzzleFlashParticle, muzzleTransform.Value, scale, ( particles ) => ParticleToMuzzlePos( particles ) );
|
|||
|
|
|||
|
// Barrel smoke
|
|||
|
if ( !IsProxy && shootInfo.BarrelSmokeParticle is not null && barrelHeat >= shootInfo.ClipSize * 0.75 )
|
|||
|
CreateParticle( shootInfo.BarrelSmokeParticle, muzzleTransform.Value, shootInfo.VMParticleScale, ( particles ) => ParticleToMuzzlePos( particles ) );
|
|||
|
}
|
|||
|
|
|||
|
void ParticleToMuzzlePos( SceneParticles particles )
|
|||
|
{
|
|||
|
var transform = GetMuzzleTransform();
|
|||
|
|
|||
|
if ( transform.HasValue )
|
|||
|
{
|
|||
|
// Apply velocity to prevent muzzle shift when moving fast
|
|||
|
particles?.SetControlPoint( 0, transform.Value.Position ); //+ Owner.Velocity * 0.03f
|
|||
|
particles?.SetControlPoint( 0, transform.Value.Rotation );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
particles?.Delete();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>Create a bullet impact effect</summary>
|
|||
|
public virtual void CreateBulletImpact( SceneTraceResult tr )
|
|||
|
{
|
|||
|
// Sound
|
|||
|
tr.Surface.PlayCollisionSound( tr.HitPosition );
|
|||
|
|
|||
|
// Particles
|
|||
|
if ( tr.Surface.ImpactEffects.Bullet is not null )
|
|||
|
{
|
|||
|
var effectPath = Game.Random.FromList( tr.Surface.ImpactEffects.Bullet, "particles/impact.generic.smokepuff.vpcf" );
|
|||
|
|
|||
|
if ( effectPath is not null )
|
|||
|
{
|
|||
|
// Surface def for flesh has wrong blood particle linked
|
|||
|
if ( effectPath.Contains( "impact.flesh" ) )
|
|||
|
{
|
|||
|
effectPath = "particles/impact.flesh.bloodpuff.vpcf";
|
|||
|
}
|
|||
|
else if ( effectPath.Contains( "impact.wood" ) )
|
|||
|
{
|
|||
|
effectPath = "particles/impact.generic.smokepuff.vpcf";
|
|||
|
}
|
|||
|
|
|||
|
var p = new SceneParticles( Scene.SceneWorld, effectPath );
|
|||
|
p.SetControlPoint( 0, tr.HitPosition );
|
|||
|
p.SetControlPoint( 0, Rotation.LookAt( tr.Normal ) );
|
|||
|
p.PlayUntilFinished( TaskSource.Create() );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Decal
|
|||
|
if ( tr.Surface.ImpactEffects.BulletDecal is not null )
|
|||
|
{
|
|||
|
var decalPath = Game.Random.FromList( tr.Surface.ImpactEffects.BulletDecal, "decals/bullethole.decal" );
|
|||
|
|
|||
|
if ( ResourceLibrary.TryGet<DecalDefinition>( decalPath, out var decalDef ) )
|
|||
|
{
|
|||
|
var decalEntry = Game.Random.FromList( decalDef.Decals );
|
|||
|
|
|||
|
var gameObject = Scene.CreateObject();
|
|||
|
//gameObject.SetParent( tr.GameObject, false );
|
|||
|
gameObject.WorldPosition = tr.HitPosition;
|
|||
|
gameObject.WorldRotation = Rotation.LookAt( -tr.Normal );
|
|||
|
|
|||
|
var decalRenderer = gameObject.Components.Create<DecalRenderer>();
|
|||
|
decalRenderer.Material = decalEntry.Material;
|
|||
|
decalRenderer.Size = new( decalEntry.Height.GetValue(), decalEntry.Height.GetValue(), decalEntry.Depth.GetValue() );
|
|||
|
gameObject.DestroyAsync( 30f );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>Create a weapon particle</summary>
|
|||
|
public virtual void CreateParticle( ParticleSystem particle, string attachment, float scale, Action<SceneParticles> OnFrame = null )
|
|||
|
{
|
|||
|
var effectRenderer = GetEffectRenderer();
|
|||
|
|
|||
|
if ( effectRenderer is null || effectRenderer.SceneModel is null ) return;
|
|||
|
|
|||
|
var transform = effectRenderer.SceneModel.GetAttachment( attachment );
|
|||
|
|
|||
|
if ( !transform.HasValue ) return;
|
|||
|
|
|||
|
CreateParticle( particle, transform.Value, scale, OnFrame );
|
|||
|
}
|
|||
|
|
|||
|
public virtual void CreateParticle( ParticleSystem particle, Transform transform, float scale, Action<SceneParticles> OnFrame = null )
|
|||
|
{
|
|||
|
SceneParticles particles = new( Scene.SceneWorld, particle );
|
|||
|
particles?.SetControlPoint( 0, transform.Position );
|
|||
|
particles?.SetControlPoint( 0, transform.Rotation );
|
|||
|
particles?.SetNamedValue( "scale", scale );
|
|||
|
|
|||
|
particles?.PlayUntilFinished( Task, OnFrame );
|
|||
|
}
|
|||
|
}
|