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

// Replicated fuse and bone attachment. Only the host resolves the struck part;
// unlike the grenade this never deals area damage to neighbouring enemies.
public sealed class SausageExplosiveBolt : Component
{
    [Sync(SyncFlags.FromHost)] public GameObject Enemy {get;set;}
    [Sync(SyncFlags.FromHost)] public GameObject Shooter {get;set;}
    [Sync(SyncFlags.FromHost)] public string Bone {get;set;}
    [Sync(SyncFlags.FromHost)] public Vector3 BonePoint {get;set;}
    [Sync(SyncFlags.FromHost)] public Rotation BoneRotation {get;set;}
    [Sync(SyncFlags.FromHost)] public int Part {get;set;}
    [Sync(SyncFlags.FromHost)] public int Round {get;set;}
    [Sync(SyncFlags.FromHost)] public float Fuse {get;set;}=2;
    private GameObject visual;
    private float beep;
    private bool resolved;
    public static int Detonations {get;private set;}
    public static void Attach(OverdriveEnemy enemy,PaintballCombatant shooter,Vector3 point,Vector3 direction,string bone)
    {
        if(!MultiplayerSession.Authority || enemy.Dead)return;
        int part=enemy.Zone(point,bone);
        if(string.IsNullOrEmpty(bone))bone=part switch {1=>"head",2=>"arm_upper_L",4=>"arm_upper_R",8=>"leg_upper_L",16=>"leg_upper_R",_=>"spine_1"};
        var pose=enemy.Components.Get<OverdriveRobotRig>().IntactPose(bone);
        var go=new GameObject(false,"Embedded explosive bolt"){WorldPosition=point};go.Tags.Add("paintball_debris");
        var bolt=go.Components.Create<SausageExplosiveBolt>();bolt.Enemy=enemy.GameObject;bolt.Shooter=shooter?.GameObject;
        bolt.Bone=bone;bolt.Part=part;bolt.Round=enemy.Round;
        bolt.BonePoint=pose.PointToLocal(point+direction*2);bolt.BoneRotation=pose.Rotation.Inverse*Rotation.LookAt(direction);
        go.Enabled=true;
        if(MultiplayerSession.Online){go.NetworkMode=NetworkMode.Object;go.Network.SetOwnerTransfer(OwnerTransfer.Fixed);go.NetworkSpawn();}
    }
    protected override void OnStart(){visual=SausageBolt.Visual(GameObject);}
    private Transform Pose(OverdriveEnemy enemy)
    {
        var rig=enemy.Components.Get<OverdriveRobotRig>();
        var pose=rig.IsValid() ? rig.IntactPose(Bone) : enemy.WorldTransform;
        if(enemy.Dead && Scene.GetAllComponents<OverdriveCorpse>().FirstOrDefault(c=>c.SourceEnemy==Enemy)?.VisibleBody is {} corpse
            && corpse.IsValid() && corpse.TryGetBoneTransform(Bone,out var fallen))pose=fallen;
        return pose;
    }
    protected override void OnUpdate()
    {
        var enemy=Enemy.IsValid() ? Enemy.Components.Get<OverdriveEnemy>() : null;
        var s=MultiplayerSession.Find(Scene);
        if(!enemy.IsValid() || s is not {IsOverdrive:true,InRound:true} || s.RoundId!=Round)
        {if(MultiplayerSession.Authority)GameObject.Destroy();return;}
        if((enemy.MissingParts&Part)!=0){if(MultiplayerSession.Authority)GameObject.Destroy();return;}
        var pose=Pose(enemy);
        if(MultiplayerSession.Authority)
        {
            WorldPosition=pose.PointToWorld(BonePoint);WorldRotation=pose.Rotation*BoneRotation;
            Fuse=MathF.Max(0,Fuse-Time.Delta);
            if(Fuse<=0 && !resolved)
            {
                resolved=true;Detonations++;
                var shooter=Shooter.IsValid() ? Shooter.Components.Get<PaintballCombatant>() : null;
                bool hit=false;
                if(!enemy.Dead)
                {
                    hit=enemy.ExplodeBoltPart(Part,WorldRotation.Forward,shooter,WorldPosition);
                }
                Explosion(WorldPosition,Round);
                if(hit && shooter.IsValid())HitConfirm.Send(shooter.GameObject,enemy.Dead,WorldPosition,Round);
                GameObject.Destroy();return;
            }
        }
        beep-=Time.Delta;
        if(beep<=0 && Fuse>0)
        {
            var sound=Sound.Play("sounds/paintball/countdown_tick.sound",pose.PointToWorld(BonePoint));
            if(sound is not null){sound.Volume=.3f;sound.Pitch=1.35f;}
            beep=Fuse>1 ? .65f : .24f;
        }
    }
    protected override void OnPreRender()
    {
        var enemy=Enemy.IsValid() ? Enemy.Components.Get<OverdriveEnemy>() : null;
        if(!enemy.IsValid() || !visual.IsValid())return;
        visual.Enabled=(enemy.MissingParts&Part)==0;
        var pose=Pose(enemy);var rotation=pose.Rotation*BoneRotation;
        visual.WorldRotation=rotation;
        visual.WorldPosition=pose.PointToWorld(BonePoint)+rotation*new Vector3(-28.4036f*.60f,0,0);
    }
    [Rpc.Broadcast(NetFlags.HostOnly)] private static async void Explosion(Vector3 point,int round)
    {
        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;
        SausageExplosionEffect.Spawn(scene,point,false);
    }
}