Weapons/GluonGun/GluonWeapon.cs
using Sandbox.Rendering;
using Sandbox.Utility;
public sealed class GluonWeapon : BaseBulletWeapon, BaseWeapon.IWeaponEvent
{
[Property] public float BeamDamage { get; set; } = 10;
[Property] public float BeamRange { get; set; } = 1000;
[Property] public float BeamPulseInterval { get; set; } = 0.2f;
[Property] public float BeamAmmoDrainFrequency { get; set; } = 0.2f;
[Property] public SoundEvent StartSound { get; set; }
[Property] public SoundEvent StopSound { get; set; }
[Property] public SoundEvent LoopSound { get; set; }
[Property] public GameObject BeamEffect { get; set; }
[Sync]
private GameObject LockTargetGameObject { get; set; }
[Sync]
private Vector3 beamTarget { get; set; }
IDamageable lockTarget;
BeamComponent beam;
SoundHandle loopSound;
Vector3 hitPosition;
TimeSince timeSinceAmmoTick;
TimeSince timeSinceFireTick;
public override void OnHolstered( Player player )
{
base.OnHolstered( player );
StopAttack();
}
public override void OnPlayerUpdate( Player player )
{
base.OnPlayerUpdate( player );
if ( player.IsLocalPlayer )
{
UpdateShootEffects( player );
}
}
private Player FindLockTargetWithRadius( Player player, float radius )
{
var tr = GetBulletTrace( player, radius ).Run();
if ( !tr.Hit || tr.GameObject.GetComponentInParent<Player>() is null )
{
return null;
}
return tr.GameObject.GetComponentInParent<Player>();
}
private Player FindLockTarget( Player player )
{
return FindLockTargetWithRadius( player, 1f ) ?? FindLockTargetWithRadius( player, 15f ) ?? FindLockTargetWithRadius( player, 50f ) ?? FindLockTargetWithRadius( player, 100 );
}
private void UpdateBeamTarget( Vector3 target )
{
if ( !Owner.IsLocalPlayer )
{
// No interpolation
beamTarget = target;
return;
}
if ( beam.IsValid() )
{
// Don't lerp if the distance is too far away
if ( beamTarget.DistanceSquared( target ) > 62500f )
{
beamTarget = target;
}
else
{
beamTarget = beamTarget.LerpTo( target, Time.Delta * 15f );
}
}
}
protected override void OnUpdate()
{
base.OnUpdate();
if ( !beam.IsValid() )
{
return;
}
if ( LockTargetGameObject.IsValid() )
{
var target = LockTargetGameObject.GetBounds().Center;
UpdateBeamTarget( target );
beam.SetMiddlePoint( Owner.EyeTransform.Forward, 2.5f );
}
beam.SetBeam( beamTarget );
}
public override void OnControl( Player player )
{
base.OnControl( player );
lockTarget = FindLockTarget( player );
if ( Input.Pressed( "Attack1" ) )
{
if ( !TakeAmmo( 1 ) )
{
DryFire();
AddShootDelay( 0.1f );
SwitchAway();
}
else if ( CanShoot() )
{
timeSinceFireTick = BeamPulseInterval;
beamTarget = hitPosition;
StartAttack();
}
}
if ( Input.Released( "Attack1" ) )
{
StopAttack();
}
if ( beam.IsValid() )
{
if ( timeSinceAmmoTick > BeamAmmoDrainFrequency )
{
timeSinceAmmoTick = 0;
if ( !CanShoot() || !TakeAmmo( 2 ) )
{
AddShootDelay( 0.1f );
StopAttack();
return;
}
}
Shoot( player );
if ( !player.Controller.ThirdPerson && player.IsLocalPlayer )
{
new Sandbox.CameraNoise.Shake( 0.08f, 0.1f );
}
}
}
public override bool IsInUse() => beam.IsValid();
public void Shoot( Player player )
{
float damageScale = timeSinceFireTick / BeamPulseInterval;
if ( timeSinceFireTick > BeamPulseInterval )
{
SceneTraceResult tr = default;
if ( lockTarget is not null )
{
var start = player.EyeTransform.Position;
var rot = Rotation.LookAt( player.EyeTransform.Forward );
var component = lockTarget as Component;
var target = component.GameObject.GetBounds().Center;
var direction = (target - start);
tr = Scene.Trace.Ray( start, start + direction * BeamRange )
.IgnoreGameObjectHierarchy( player.GameObject )
.WithCollisionRules( "bullet" )
.UseHitboxes()
.Size( 1f )
.Run();
LockTargetGameObject = tr.GameObject;
}
else
{
LockTargetGameObject = null;
tr = GetBulletTrace( player, 5 ).Run();
}
hitPosition = tr.EndPosition;
TraceAttack( TraceAttackInfo.From( tr, BeamDamage * damageScale, [DamageTags.GibAlways, DamageTags.Shock] ) );
SplashDamage( TraceAttackInfo.From( tr, BeamDamage / 4.0f * damageScale, [DamageTags.GibAlways, DamageTags.Shock] ) );
if ( player.IsLocalPlayer )
{
HitMarker.CreateFromTrace( tr );
}
UpdateShootEffects( player );
timeSinceFireTick = 0;
}
TimeSinceShoot = 0;
}
[Rpc.Host]
void SplashDamage( TraceAttackInfo attack )
{
if ( !Owner.IsValid() ) return;
Damage.Radius( attack.Position, 128, attack.Damage, attack.Tags, Owner.GameObject, GameObject, ignore: Owner.GameObject );
}
/// <summary>
/// Constructs a trace and returns it, doesn't run it yet!
/// </summary>
SceneTrace GetBulletTrace( Player player, float radius )
{
var start = player.EyeTransform.Position;
var rot = Rotation.LookAt( player.EyeTransform.Forward );
return Scene.Trace.Ray( start, start + rot.Forward * BeamRange )
.IgnoreGameObjectHierarchy( player.GameObject )
.WithCollisionRules( "bullet" )
.UseHitboxes()
.Size( radius );
}
public override void DrawCrosshair( HudPainter hud, Vector2 center )
{
var tss = TimeSinceShoot.Relative.Remap( 0, 0.4f, 0.5f, 0 );
var len = 40 + Easing.BounceIn( tss ) * 128;
Color color = !CanShoot() ? UI.CrosshairInactive : UI.CrosshairActive;
var rect = new Rect( center - len * 0.5f, len );
hud.DrawRect( rect, Color.Transparent, cornerRadius: new Vector4( 512 ), borderWidth: new Vector4( 2 ), borderColor: color );
if ( lockTarget is Component target && target.IsValid() )
{
var gap = 48f;
var w = 2f;
len = 16;
hud.DrawLine( center + Vector2.Left * (len + gap), center + Vector2.Left * gap, w, color );
hud.DrawLine( center - Vector2.Left * (len + gap), center - Vector2.Left * gap, w, color );
hud.DrawLine( center + Vector2.Up * (len + gap), center + Vector2.Up * gap, w, color );
hud.DrawLine( center - Vector2.Up * (len + gap), center - Vector2.Up * gap, w, color );
// Define the size of the square
var squareSize = 64f;
var go = target.GameObject;
center = Scene.Camera.PointToScreenPixels( go.GetBounds().Center );
// Draw the four edges of the square
hud.DrawLine( center + new Vector2( -squareSize / 2, -squareSize / 2 ), center + new Vector2( squareSize / 2, -squareSize / 2 ), w, color ); // Top edge
hud.DrawLine( center + new Vector2( squareSize / 2, -squareSize / 2 ), center + new Vector2( squareSize / 2, squareSize / 2 ), w, color ); // Right edge
hud.DrawLine( center + new Vector2( squareSize / 2, squareSize / 2 ), center + new Vector2( -squareSize / 2, squareSize / 2 ), w, color ); // Bottom edge
hud.DrawLine( center + new Vector2( -squareSize / 2, squareSize / 2 ), center + new Vector2( -squareSize / 2, -squareSize / 2 ), w, color ); // Left edge
}
}
void IWeaponEvent.OnAttackStart( IWeaponEvent.AttackEvent e )
{
StartShootEffects();
}
void IWeaponEvent.OnAttackStop()
{
StopShootEffects();
}
private void StartShootEffects()
{
if ( Application.IsDedicatedServer ) return;
GameObject.PlaySound( StartSound );
loopSound?.Stop();
if ( LoopSound.IsValid() )
{
loopSound = GameObject.PlaySound( LoopSound );
}
// If player dies this can be invalid
if ( WeaponModel.IsValid() )
{
var effect = BeamEffect.Clone( new CloneConfig { Parent = WeaponModel.MuzzleTransform, Transform = global::Transform.Zero, StartEnabled = true } );
beam = effect.GetComponent<BeamComponent>( true );
}
}
private void UpdateShootEffects( Player player )
{
if ( !beam.IsValid() )
return;
if ( !LockTargetGameObject.IsValid() )
{
var tr = GetBulletTrace( player, 5 ).Run();
UpdateBeamTarget( tr.EndPosition );
beam.SetMiddlePoint( Owner.EyeTransform.Forward, 0f );
}
}
private void StopShootEffects()
{
if ( Application.IsDedicatedServer ) return;
if ( loopSound.IsValid() )
{
loopSound.Stop();
loopSound = null;
if ( StopSound.IsValid() )
{
GameObject.PlaySound( StopSound );
}
}
if ( beam.IsValid() )
{
beam.DestroyGameObject();
}
}
}