Weapons/HandGrenadeWeapon.cs
using Sandbox.Rendering;
public sealed class HandGrenadeWeapon : BaseWeapon
{
[Property]
public GameObject Prefab { get; set; }
[Property]
public float ThrowPower { get; set; } = 1200f;
/// <summary>
/// How long does this explosive last until exploding
/// </summary>
[Property]
public float Lifetime { get; set; } = 3f;
/// <summary>
/// Damage radius
/// </summary>
[Property]
public float Radius { get; set; }
/// <summary>
/// Max damage, respecting <see cref="DamageFalloff"/>
/// </summary>
[Property]
public float MaxDamage { get; set; } = 125;
/// <summary>
/// A falloff curve to dictate damage inflicted to damageables in the area.
/// </summary>
[Property]
public Curve DamageFalloff { get; set; } = new Curve( new Curve.Frame( 1.0f, 1.0f ), new Curve.Frame( 0.0f, 0.0f ) );
[Sync]
TimeSince TimeSinceCooked { get; set; }
[Sync]
bool IsThrowing { get; set; } = false;
[Sync]
bool IsCooking { get; set; } = false;
[Sync]
TimeUntil TimeUntilThrown { get; set; }
public override void OnControl( Player player )
{
if ( IsThrowing )
{
if ( TimeUntilThrown )
{
if ( player.GetAmmoCount( AmmoResource ) < 1 )
{
GameObject.Destroy();
// Delete weapon, switch to best
SwitchAway();
}
else
{
IsThrowing = false;
}
}
return;
}
if ( Input.Pressed( "Attack1" ) )
{
IsCooking = true;
TimeSinceCooked = 0;
StartAttack();
}
if ( Input.Down( "Attack1" ) && TimeSinceCooked > Lifetime )
{
IsCooking = false;
Explode( WorldPosition );
if ( !TakeAmmo( 1 ) )
{
DestroyGameObject();
}
return;
}
if ( Input.Released( "Attack1" ) )
{
IsCooking = false;
Throw( player, player.EyeTransform.Rotation.Forward );
if ( !HasAmmo() )
{
SwitchAway();
GameObject.Destroy();
}
StopAttack();
}
}
public override bool IsInUse() => IsCooking;
public override void OnPlayerDeath( IPlayerEvent.DiedParams args )
{
if ( !IsCooking ) return;
if ( Owner.IsValid() )
{
Throw( Owner, Vector3.Down, 0.2f );
}
}
protected override void OnEnabled()
{
base.OnEnabled();
AddShootDelay( 0.5f );
}
[Rpc.Host]
private void TryThrow( Player player, Vector3 startPos, Vector3 direction, float powerScale = 1f )
{
if ( !player.IsValid() )
return;
var go = Prefab.Clone( startPos );
var explosive = go.GetComponent<Explosive>();
explosive.Instigator = player.PlayerData;
explosive.Lifetime = Lifetime - TimeSinceCooked;
explosive.MaxDamage = MaxDamage;
explosive.DamageFalloff = DamageFalloff;
explosive.Radius = Radius;
var rb = go.GetComponent<Rigidbody>();
var baseVelocity = player.GetComponent<PlayerController>().Velocity;
rb.AngularVelocity = go.WorldRotation.Right * 10f;
rb.Velocity = baseVelocity + direction * (ThrowPower * powerScale) + Vector3.Up * 100f;
go.NetworkSpawn();
}
public void Throw( Player player, Vector3 direction, float powerScale = 1f )
{
if ( !CanShoot() )
return;
if ( !TakeAmmo( 1 ) )
{
DestroyGameObject();
// Delete weapon, switch to best
SwitchAway();
return;
}
var ev = new IWeaponEvent.AttackEvent( ViewModel.IsValid() );
IWeaponEvent.PostToGameObject( GameObject.Root, x => x.OnAttack( ev ) );
AddShootDelay( 1f );
IsThrowing = true;
TimeUntilThrown = 0.25f;
var forward = direction;
var right = player.EyeTransform.Rotation.Right;
var initialPos = player.EyeTransform.ForwardRay.Position + (forward * 18.0f) + (right * 8.0f);
initialPos = CheckThrowPosition( player, player.EyeTransform.Position, initialPos );
TryThrow( player, initialPos, direction, powerScale );
if ( !player.Controller.ThirdPerson && player.IsLocalPlayer )
{
new Sandbox.CameraNoise.Punch( new Vector3( Random.Shared.Float( -10, -15 ), Random.Shared.Float( -10, 0 ), 0 ), 1.0f, 3, 0.5f );
new Sandbox.CameraNoise.Shake( 0.3f, 1.2f );
}
}
private Vector3 CheckThrowPosition( Player player, Vector3 eyePosition, Vector3 grenadePosition )
{
var tr = Scene.Trace.Box( BBox.FromPositionAndSize( Vector3.Zero, 8.0f ), eyePosition, grenadePosition )
.WithoutTags( "trigger", "ragdoll" )
.IgnoreGameObjectHierarchy( player.GameObject )
.Run();
if ( tr.Hit )
{
return tr.EndPosition;
}
return grenadePosition;
}
public override void DrawCrosshair( HudPainter hud, Vector2 center )
{
var gap = 6;
var len = 4;
var w = 2;
Color color = !CanShoot() ? UI.CrosshairInactive : UI.CrosshairActive;
hud.DrawCircle( center, len, color );
hud.DrawLine( center + Vector2.Left * (len + gap) * 2, center + Vector2.Left * gap * 2, w, color );
hud.DrawLine( center - Vector2.Left * (len + gap) * 2, center - Vector2.Left * gap * 2, w, color );
center += new Vector2( 0, 10 );
gap -= 1;
hud.DrawLine( center + Vector2.Left * (len + gap) * 2, center + Vector2.Left * gap * 2, w, color );
hud.DrawLine( center - Vector2.Left * (len + gap) * 2, center - Vector2.Left * gap * 2, w, color );
center += new Vector2( 0, 10 );
gap -= 1;
hud.DrawLine( center + Vector2.Left * (len + gap) * 2, center + Vector2.Left * gap * 2, w, color );
hud.DrawLine( center - Vector2.Left * (len + gap) * 2, center - Vector2.Left * gap * 2, w, color );
center += new Vector2( 0, 10 );
gap -= 1;
hud.DrawLine( center + Vector2.Left * (len + gap) * 2, center + Vector2.Left * gap * 2, w, color );
hud.DrawLine( center - Vector2.Left * (len + gap) * 2, center - Vector2.Left * gap * 2, w, color );
}
[Rpc.Broadcast]
private void CreateEffects()
{
if ( Application.IsDedicatedServer ) return;
Effects.SpawnScorch( WorldPosition, Vector3.Up, GameObject );
Effects.SpawnExplosion( WorldPosition );
}
[Rpc.Host]
public void Explode( Vector3 pos )
{
TagSet tags = [DamageTags.Explosion];
Damage.Radius( pos, Radius, MaxDamage, tags, GameObject, GameObject, DamageFalloff );
CreateEffects();
}
}