ArtilleryShell is a FallingObject that represents a spawned artillery projectile. It handles start/updating visuals and rotation, decides on impact behavior (drop loot or create an explosion using Manager RPCs), and spawns VFX and sound via a broadcast RPC.
using Sandbox;
using System;
public class ArtilleryShell : FallingObject
{
[Property] public ModelRenderer ModelRenderer { get; set; }
public const float EXPLOSION_RADIUS = 52f;
public const float EXPLOSION_DAMAGE = 10f;
private float _rotationSpeed;
protected override void OnStart()
{
base.OnStart();
TimeSinceSpawn = 0f;
ModelRenderer.Tint = Color.White.WithAlpha( 0f );
if ( IsProxy )
return;
_startingHeight = 1024f;
Lifetime = Game.Random.Float( 1.75f, 2f );
_rotationSpeed = Game.Random.Float( 100f, 600f );
}
protected override void OnUpdate()
{
base.OnUpdate();
ModelRenderer.Tint = Color.White.WithAlpha( Utils.Map( TimeSinceSpawn, 0f, 0.5f, 0f, 1f ) );
if ( IsProxy )
return;
// todo: move with wind force
WorldRotation = WorldRotation.RotateAroundAxis( Vector3.Forward, _rotationSpeed * Time.Delta );
}
public override void HitGround()
{
var itemChance = Shooter.IsValid() ? Shooter.Stats[PlayerStat.ArtillerySupplyChance] : 0f;
if( Game.Random.Float( 0f, 1f ) < itemChance)
{
DropLoot( Shooter, (Vector2)WorldPosition );
}
else
{
float damage = EXPLOSION_DAMAGE * (Shooter.IsValid() ? Shooter.Stats[PlayerStat.ExplosionDamageMultiplier] : 1f);
var radius = EXPLOSION_RADIUS * (Shooter.IsValid() ? Shooter.Stats[PlayerStat.ExplosionSizeMultiplier] * Shooter.Stats[PlayerStat.RadiusMultiplier] : 1f);
Manager.Instance.CreateExplosionRpc( (Vector2)WorldPosition, radius, damage, repelRadius: radius * 1.15f, repelForce: damage * 20f, playerSource: Shooter, enemySource: null, enemyType: EnemyType.None, Color.Red );
}
GameObject.Destroy();
}
protected void DropLoot( Player player, Vector2 impactPos )
{
LootVfx( impactPos );
float rand = Game.Random.Float( 0f, 1f );
var pos = impactPos + Utils.GetRandomVector() * Game.Random.Float( 1f, 3f );
var dir = (pos - impactPos).Normal;
var numDeadPlayers = Manager.Instance.Players.Count - Manager.Instance.AlivePlayers.Count;
if ( rand < 0.17f )
Manager.Instance.SpawnItemRpc( "health_pack", pos, dir );
else if ( rand < 0.20f )
Manager.Instance.SpawnItemRpc( "magnet", pos, dir );
else if ( rand < 0.27f )
Manager.Instance.SpawnItemRpc( "bomb", pos, dir );
else if ( rand < 0.4f )
Manager.Instance.SpawnItemRpc( "reroll_item", pos, dir );
else if ( rand < 0.60f )
Manager.Instance.SpawnItemRpc( "banish_item", pos, dir );
else if ( rand < 0.85f )
Manager.Instance.SpawnItemRpc( "armor_item", pos, dir );
else if ( rand < 0.88f && player.IsValid() )
player.GiveRandomPerkItemRpc( pos, dir, Rarity.None );
else if ( rand < 0.95f && numDeadPlayers > 0f )
Manager.Instance.SpawnItemRpc( "revive_soul", pos, dir );
else
Manager.Instance.SpawnCoinRpc( pos, value: Game.Random.Int( 1, 2 ), dir );
}
[Rpc.Broadcast]
public void LootVfx( Vector2 pos )
{
GameObject.Clone( "prefabs/effects/cloud.prefab", new CloneConfig { StartEnabled = true, Transform = new Transform( new Vector3( pos.x, pos.y, 10f ) ) } );
// todo: better sfx
Manager.Instance.PlaySfxNearby( "enemy.explode", pos, pitch: Game.Random.Float( 1.8f, 2.2f ), volume: 0.7f, maxDist: 550f );
}
}