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

/// <summary>Host-swept bolts. No client hit claims and no friendly-fire damage.</summary>
public sealed class SausageBolt : Component
{
    [Sync(SyncFlags.FromHost)] public Vector3 Velocity {get;set;}
    [Sync(SyncFlags.FromHost)] public GameObject Shooter {get;set;}
    [Sync(SyncFlags.FromHost)] public int Round {get;set;}
    public static int Hits {get;private set;}
    private float age;
    private bool resolved;
    private LineRenderer flightTrail;
    public static void Fire(PaintballCombatant actor,Vector3 origin,Vector3 aim)
    {
        if(!MultiplayerSession.Authority)return;
        var go=new GameObject(false,"Flying crossbow bolt"){WorldPosition=origin};go.Tags.Add("paintball_debris");
        var bolt=go.Components.Create<SausageBolt>();bolt.Shooter=actor.GameObject;bolt.Round=MultiplayerSession.Find(actor.Scene).RoundId;
        bolt.Velocity=(aim-origin).Normal*2400;go.WorldRotation=Rotation.LookAt(bolt.Velocity.Normal);go.Enabled=true;
        if(MultiplayerSession.Online){go.NetworkMode=NetworkMode.Object;go.Network.SetOwnerTransfer(OwnerTransfer.Fixed);go.Network.Interpolation=false;go.NetworkSpawn();}
        Cue(origin,"bow",bolt.Round,actor.GameObject);
    }
    internal static GameObject Visual(GameObject parent)
    {
        // Keep the tip at the physical hit point when enlarging the native bolt.
        var go=new GameObject(parent,true,"Crossbow bolt mesh"){NetworkMode=NetworkMode.Never,LocalScale=new Vector3(.60f,.92f,.92f),LocalPosition=new Vector3(-28.4036f*.60f,0,0)};
        var renderer=go.Components.Create<ModelRenderer>();renderer.Model=Model.Load(OverdriveAssets.BoltPath);
        renderer.MaterialOverride=Material.Load("materials/weapons/readable_bolt.vmat");return go;
    }
    protected override void OnStart()
    {
        Visual(GameObject);
        // Armed in flight; the red tip goes dark on penetration while the fuse beeps.
        PopRiotTarget.Sphere(GameObject,Vector3.Zero,.48f,new Color(1,.025f,.015f),true);
        var trail=new GameObject(GameObject,true,"Crossbow flight ribbon"){NetworkMode=NetworkMode.Never};
        flightTrail=trail.Components.Create<LineRenderer>();flightTrail.UseVectorPoints=true;
        flightTrail.Face=SceneLineObject.FaceMode.Camera;flightTrail.Lighting=false;flightTrail.CastShadows=false;
        flightTrail.Opaque=false;flightTrail.Additive=false;
        flightTrail.Width=new Curve(new Curve.Frame(0,.04f),new Curve.Frame(1,.32f));
        flightTrail.Color=Gradient.FromColors(new Color(.8f,.8f,.76f,0),new Color(.86f,.86f,.8f,.28f));
    }
    protected override void OnUpdate()
    {
        age+=Time.Delta;
        if(flightTrail.IsValid())flightTrail.VectorPoints=[WorldPosition-Velocity.Normal*MathF.Min(30,age*Velocity.Length),WorldPosition];
    }
    protected override void OnFixedUpdate()
    {
        if(!MultiplayerSession.Authority || resolved)return;
        var session=MultiplayerSession.Find(Scene);
        if(session?.IsOverdrive!=true || !session.InRound || Round!=session.RoundId || age>1.3f){GameObject.Destroy();return;}
        var actor=Shooter.IsValid() ? Shooter.Components.Get<PaintballCombatant>() : null;
        var end=WorldPosition+Velocity*Time.Delta;
        var ray=Scene.Trace.Sphere(.5f,WorldPosition,end).WithSurfaceMeshes().WithoutTags("paintball_actor","paintball_debris","overdrive_enemy");
        foreach(var shield in Scene.GetAllComponents<ArenaShield>())ray=ray.IgnoreGameObjectHierarchy(shield.GameObject);
        var hit=ray.Run();
        // Native animated Citizen hitboxes avoid sticking arrows to the outer
        // navigation box, which can leave them floating above a moving head.
        var flesh=OverdriveEnemy.TraceIntact(Scene.Trace.Sphere(.5f,WorldPosition,end).UsePhysicsWorld(false).UseHitboxes(true).WithTag("overdrive_enemy"));
        if(flesh.Hit && (!hit.Hit || flesh.Fraction<hit.Fraction))hit=flesh;
        float distance=hit.Hit ? (hit.HitPosition-WorldPosition).Length : (end-WorldPosition).Length;
        if(OverdriveEnemy.TraceStack(Scene,WorldPosition,Velocity.Normal,distance,out var stackPoint) is {} carrier && carrier.ShootStack(actor,stackPoint) is int kind && kind>0)
        {
            if(actor.IsValid())HitConfirm.Callout(actor.GameObject,OverdriveEnemy.StackCallout(kind),Round);
            resolved=true;GameObject.Destroy();return;
        }
        if(SausageProjectile.TryIntercept(Scene,WorldPosition,Velocity.Normal,distance,actor,out _)){resolved=true;GameObject.Destroy();return;}
        if(!hit.Hit){WorldPosition=end;return;}
        resolved=true;
        var enemy=OverdriveEnemy.Find(hit.GameObject);
        if(enemy.IsValid() && !enemy.Dead)
        {
            SausageExplosiveBolt.Attach(enemy,actor,hit.HitPosition,Velocity.Normal,hit.Hitbox?.Bone?.Name);
            Hits++;Cue(hit.HitPosition,"bolt",Round,null);
            if(actor.IsValid())HitConfirm.Send(actor.GameObject,false,hit.HitPosition,Round);
        }
        else if(RestaurantBreakable.Find(hit.GameObject) is {} dish)
        {
            dish.Hit(dish.Kind=="chair" ? 8 : 48,hit.HitPosition,actor);
            if(dish.IsValid() && !dish.BrokenState)Surface(hit.HitPosition,hit.Normal,Velocity.Normal,Round,dish.GameObject);
            else Cue(hit.HitPosition,"bolt",Round,null);
        }
        else Surface(hit.HitPosition,hit.Normal,Velocity.Normal,Round,null);
        GameObject.Destroy();
    }
    [Rpc.Broadcast(NetFlags.HostOnly)] internal static async void Cue(Vector3 point,string kind,int round,GameObject shooter)
    {
        var scene=Game.ActiveScene;await GameTask.MainThread();
        if(!scene.IsValid() || scene!=Game.ActiveScene || MultiplayerSession.Find(scene) is not {IsOverdrive:true} s || s.RoundId!=round)return;
        KitchenCombatAudio.Play(kind,point);
        if(shooter.IsValid())
        {
            var body=shooter.Components.Get<CitizenPlayerPresentation>()?.FootstepRenderer ?? shooter.Components.Get<CitizenOpponentPresentation>()?.FootstepRenderer;
            if(body.IsValid())body.Set("b_attack",true);
        }
    }
    [Rpc.Broadcast(NetFlags.HostOnly)] private static async void Surface(Vector3 point,Vector3 normal,Vector3 direction,int round,GameObject receiver)
    {
        var scene=Game.ActiveScene;await GameTask.MainThread();
        if(!scene.IsValid() || scene!=Game.ActiveScene || MultiplayerSession.Find(scene) is not {IsOverdrive:true} s || s.RoundId!=round)return;
        KitchenCombatAudio.Play("bolt",point);
        var go=new GameObject(true,"Bolt stuck in cover"){NetworkMode=NetworkMode.Never,WorldPosition=point+direction*1.5f,WorldRotation=Rotation.LookAt(direction)};
        if(receiver.IsValid())go.Parent=receiver;
        go.Tags.Add("paintball_debris");Visual(go);go.Components.Create<EmbeddedBoltLifetime>();
    }
}

public sealed class SausageEmbeddedBolts : Component
{
    public sealed class Record
    {
        public string Bone {get;set;}
        public Vector3 Point {get;set;}
        public Rotation Rotation {get;set;}
        public int Part {get;set;}
    }
    // Keep history on the authoritative enemy's initial network snapshot;
    // this component only presents the replicated records.
    public string History=>Components.Get<OverdriveEnemy>()?.EmbeddedBoltState ?? "";
    private string observed;
    private readonly List<(Record Data,GameObject Visual)> bolts=new();
    public int VisibleCount=>bolts.Count(b=>b.Visual.IsValid() && b.Visual.Enabled);
    internal void Stick(Vector3 point,Vector3 direction,string hitBone=null)
    {
        if(!MultiplayerSession.Authority)return;
        var enemy=Components.Get<OverdriveEnemy>();var rig=Components.Get<OverdriveRobotRig>();
        if(enemy?.Dead!=false || rig?.Body.IsValid()!=true)return;
        int part=string.IsNullOrEmpty(hitBone) ? enemy.Zone(point) : OverdriveRobotRig.Part(hitBone);
        string[] candidates=part switch {1=>["head"],8=>["leg_upper_L","leg_lower_L"],16=>["leg_upper_R","leg_lower_R"],2=>["arm_upper_L","arm_lower_L"],4=>["arm_upper_R","arm_lower_R"],_=>["spine_0","spine_1","spine_2"]};
        string bone=string.IsNullOrEmpty(hitBone) ? candidates.OrderBy(n=>(rig.IntactPose(n).Position-point).Length).First() : hitBone;
        var pose=rig.IntactPose(bone);
        var list=string.IsNullOrEmpty(History) ? new List<Record>() : Json.Deserialize<List<Record>>(History);
        if(list.Count>=4)list.RemoveAt(0);
        list.Add(new(){Bone=bone,Point=pose.PointToLocal(point+direction*2),Rotation=pose.Rotation.Inverse*Rotation.LookAt(direction),Part=part});
        enemy.EmbeddedBoltState=Json.Serialize(list);
    }
    protected override void OnPreRender()
    {
        var rig=Components.Get<OverdriveRobotRig>();var enemy=Components.Get<OverdriveEnemy>();
        if(!enemy.IsValid() || rig?.Body.IsValid()!=true)return;
        if(observed!=History)
        {
            foreach(var item in bolts)if(item.Visual.IsValid())item.Visual.Destroy();bolts.Clear();observed=History;
            if(!string.IsNullOrEmpty(History))foreach(var record in Json.Deserialize<List<Record>>(History))
            {
                var pivot=new GameObject(GameObject,true,"Embedded sausage bolt"){NetworkMode=NetworkMode.Never};pivot.Tags.Add("paintball_debris");
                SausageBolt.Visual(pivot);bolts.Add((record,pivot));
            }
        }
        if(bolts.Count==0)return;
        var corpse=enemy.Dead ? Scene.GetAllComponents<OverdriveCorpse>().FirstOrDefault(c=>c.SourceEnemy==GameObject) : null;
        foreach(var item in bolts)
        {
            if(!item.Visual.IsValid())continue;
            item.Visual.Enabled=(enemy.MissingParts&item.Data.Part)==0 && (!enemy.Dead || corpse.IsValid());
            var pose=rig.IntactPose(item.Data.Bone);
            if(corpse?.VisibleBody.IsValid()==true && corpse.VisibleBody.TryGetBoneTransform(item.Data.Bone,out var fallen))pose=fallen;
            item.Visual.WorldPosition=pose.PointToWorld(item.Data.Point);item.Visual.WorldRotation=pose.Rotation*item.Data.Rotation;
        }
    }
}

public sealed class EmbeddedBoltLifetime : Component
{
    private float age;
    protected override void OnStart(){foreach(var old in Scene.GetAllComponents<EmbeddedBoltLifetime>().OrderByDescending(b=>b.age).SkipLast(23).ToArray())old.GameObject.Destroy();}
    protected override void OnUpdate(){age+=Time.Delta;if(age>12)GameObject.Destroy();}
}