ShotgunShellEjection.cs
using Sandbox;
using System;
namespace PaintballBaddies;

/// <summary>Small local cosmetic casings; no network bodies or gameplay collision.</summary>
public sealed class ShotgunShellEjection : Component
{
    private Vector3 velocity;
    private float age;
    private int bounces;
    public static int Ejected {get;private set;}
    public static void Spawn(Scene scene,GameObject shooter,Vector3 muzzle,Vector3 direction)
    {
        var model=Model.Load(OverdriveAssets.ShellPath);if(model.IsError)return;
        foreach(var old in scene.GetAllComponents<ShotgunShellEjection>().OrderByDescending(e=>e.age).SkipLast(23).ToArray())old.GameObject.Destroy();
        var anchor=shooter.IsValid() ? shooter.Components.Get<PaintballMarker>()?.PresentationAnchor ?? shooter.Components.Get<ArenaOpponent>()?.PresentationAnchor : null;
        var rotation=Rotation.LookAt(direction);
        var port=anchor.IsValid() ? anchor.WorldTransform.PointToWorld((shooter.Components.Get<OverdriveLoadout>()?.FittedOffset ?? OverdriveLoadout.ShotgunOffset)+new Vector3(2.5f,-1.4f,4)) : muzzle-direction*17;
        var go=new GameObject(false,"Ejected shotgun shell"){NetworkMode=NetworkMode.Never,WorldPosition=port,WorldRotation=rotation,WorldScale=new Vector3(1.25f)};
        go.Tags.Add("paintball_debris");go.Components.Create<ModelRenderer>().Model=model;
        go.Components.Create<ShotgunShellEjection>().velocity=rotation.Right*95+Vector3.Up*70+direction*12;go.Enabled=true;Ejected++;
    }
    protected override void OnUpdate()
    {
        age+=Time.Delta;if(age>2.2f){GameObject.Destroy();return;}
        if(bounces>=2)return;
        float dt=MathF.Min(Time.Delta,.04f);velocity+=Vector3.Down*600*dt;
        var end=WorldPosition+velocity*dt;
        var hit=Scene.Trace.Ray(WorldPosition,end).WithoutTags("paintball_actor","overdrive_enemy","paintball_debris").Run();
        WorldPosition=hit.Hit ? hit.HitPosition+hit.Normal*.5f : end;
        if(hit.Hit){velocity=(velocity-2*Vector3.Dot(velocity,hit.Normal)*hit.Normal)*.32f;bounces++;}
        WorldRotation*=Rotation.From(new Angles(440*dt,300*dt,160*dt));
    }
}