using SWB.Shared; using System; using System.Collections.Generic; namespace SWB.Base; public partial class Weapon { /// /// Checks if the weapon can do the provided attack /// /// Attack information /// Time since this attack /// The input button for this attack /// 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 ); } /// /// Checks if weapon can do the primary attack /// public virtual bool CanPrimaryShoot() { return CanShoot( Primary, TimeSincePrimaryShoot, InputButtonHelper.PrimaryAttack ); } /// /// Checks if weapon can do the secondary attack /// 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 ); } /// A single bullet trace from start to end with a certain radius. public virtual SceneTraceResult TraceBullet( Vector3 start, Vector3 end, float radius = 2.0f ) { var startsInWater = SurfaceUtil.IsPointWater( start ); List 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(); } } /// Create a bullet impact effect 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( 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.Material = decalEntry.Material; decalRenderer.Size = new( decalEntry.Height.GetValue(), decalEntry.Height.GetValue(), decalEntry.Depth.GetValue() ); gameObject.DestroyAsync( 30f ); } } } /// Create a weapon particle public virtual void CreateParticle( ParticleSystem particle, string attachment, float scale, Action 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 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 ); } }