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

// The scene's cover remains static and editable. Its replicated fracture mask
// belongs to the existing session so late joiners also see missing collision.
public sealed partial class MultiplayerSession
{
    [Sync(SyncFlags.FromHost)] public string ConcreteFractures {get;private set;}="";
    internal int ConcreteMask(int id)
    {
        var values=ConcreteFractures.Split(',');
        return id>=0 && id<values.Length && int.TryParse(values[id],out int mask) ? mask : 0;
    }
    internal void BreakConcrete(int id,int piece)
    {
        if(!Authority || !IsOverdrive || !InRound || id<0 || id>=12 || piece<0 || piece>=14)return;
        var values=Enumerable.Range(0,12).Select(ConcreteMask).ToArray();values[id]|=1<<piece;
        ConcreteFractures=string.Join(",",values);
    }
}

public sealed class ConcreteDestruction : Component
{
    public const string Folder="models/foundry/foundry_concrete_barrier/fracture/";
    private bool installed;
    protected override void OnUpdate()
    {
        if(installed)return;installed=true;
        foreach(var cover in Scene.GetAllComponents<CoverSurface>().ToArray())
            if(cover.GameObject.Name.StartsWith("Concrete cover ") && int.TryParse(cover.GameObject.Name[15..],out int id))
            {
                var component=cover.Components.Get<BreakableConcrete>() ?? cover.Components.Create<BreakableConcrete>();component.CoverId=id;
            }
    }
}

public sealed class BreakableConcrete : Component
{
    public int CoverId {get;set;}
    public int Mask {get;private set;}
    internal GameObject Core {get;private set;}
    internal List<ConcreteChip> Chips {get;}=new();
    private ModelRenderer original;
    private ModelCollider collision;
    private int round=-1;
    protected override void OnStart()
    {
        original=Components.Get<ModelRenderer>();collision=Components.Get<ModelCollider>();
        var layout=Json.Deserialize<ShieldBreakEffect.Layout>(FileSystem.Mounted.ReadAllText(ConcreteDestruction.Folder+"layout.json"));
        Core=Create("Fractured concrete core",ConcreteDestruction.Folder+"core.vmdl",Vector3.Zero);
        for(int i=0;i<layout.Pieces.Count;i++)
        {
            var piece=layout.Pieces[i];var chip=Create($"Concrete edge {i}",piece.Model,new Vector3(piece.Center[0],piece.Center[1],piece.Center[2])).Components.Create<ConcreteChip>();
            chip.Cover=this;chip.Index=i;chip.ModelPath=piece.Model;Chips.Add(chip);
        }
    }
    private GameObject Create(string name,string model,Vector3 center)
    {
        var go=new GameObject(GameObject,true,name){NetworkMode=NetworkMode.Never,LocalPosition=center};
        var asset=Model.Load(model);go.Components.Create<ModelRenderer>().Model=asset;
        var collider=go.Components.Create<ModelCollider>();collider.Model=asset;collider.Static=true;
        return go;
    }
    protected override void OnUpdate()
    {
        var s=MultiplayerSession.Find(Scene);bool active=s?.IsOverdrive==true;
        if(s is not null && round!=s.RoundId){round=s.RoundId;foreach(var chip in Chips)chip.ResetDamage();}
        Mask=active ? s.ConcreteMask(CoverId) : 0;
        if(original.IsValid())original.Enabled=!active;
        if(collision.IsValid())collision.Enabled=!active;
        if(Core.IsValid())Core.Enabled=active;
        foreach(var chip in Chips)chip.GameObject.Enabled=active && (Mask&(1<<chip.Index))==0;
    }
    protected override void OnDestroy()
    {
        if(Core.IsValid())Core.Destroy();foreach(var chip in Chips)if(chip.IsValid())chip.GameObject.Destroy();
        if(original.IsValid())original.Enabled=true;if(collision.IsValid())collision.Enabled=true;
    }
}

public sealed class ConcreteChip : Component
{
    internal BreakableConcrete Cover {get;set;}
    internal int Index {get;set;}
    internal string ModelPath {get;set;}
    private int damage;
    internal void ResetDamage()=>damage=0;
    internal void Hit(int amount,Vector3 direction,PaintballCombatant shooter)
    {
        var s=MultiplayerSession.Find(Scene);
        if(!MultiplayerSession.Authority || s?.IsOverdrive!=true || !s.InRound || (s.ConcreteMask(Cover.CoverId)&(1<<Index))!=0)return;
        damage+=amount;if(damage<22)return;
        s.BreakConcrete(Cover.CoverId,Index);
        ConcreteFragment.Spawn(this,direction,shooter,s.RoundId);
        GameObject.Enabled=false;
    }
}

/// <summary>Swept host-owned rubble. Dust is cosmetic; only a fast real chunk can knock out Terry.</summary>
public sealed class ConcreteFragment : Component
{
    [Sync(SyncFlags.FromHost)] public int Piece {get;set;}
    [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 HeadKnockouts {get;private set;}
    private float age;
    private bool hitOnce;
    private ModelRenderer renderer;
    internal static void Spawn(ConcreteChip chip,Vector3 direction,PaintballCombatant shooter,int round)
    {
        foreach(var old in chip.Scene.GetAllComponents<ConcreteFragment>().OrderByDescending(x=>x.age).SkipLast(17).ToArray())old.GameObject.Destroy();
        var go=new GameObject(false,"Flying concrete chunk"){WorldTransform=chip.WorldTransform};go.Tags.Add("paintball_debris");
        var p=go.Components.Create<ConcreteFragment>();p.Piece=chip.Index;p.Round=round;p.Shooter=shooter?.GameObject;
        p.Velocity=direction.Normal*430+Vector3.Up*175;go.Enabled=true;
        if(MultiplayerSession.Online){go.NetworkMode=NetworkMode.Object;go.Network.SetOwnerTransfer(OwnerTransfer.Fixed);go.Network.Interpolation=false;go.NetworkSpawn();}
        BreakEffect(go.WorldPosition,round);
    }
    [Rpc.Broadcast(NetFlags.HostOnly)] private static async void BreakEffect(Vector3 point,int round)
    {
        var scene=Game.ActiveScene;await GameTask.MainThread();
        if(!scene.IsValid() || Game.ActiveScene!=scene || MultiplayerSession.Find(scene) is not {IsOverdrive:true} s || s.RoundId!=round)return;
        KitchenCombatAudio.Play("concrete",point);
        var dust=GameObject.Clone("prefabs/surface/default-bullet.prefab",new Transform(point,Rotation.LookAt(Vector3.Up)),startEnabled:false);
        dust.NetworkMode=NetworkMode.Never;dust.WorldScale=new Vector3(.65f);dust.Enabled=true;
    }
    protected override void OnStart(){renderer=Components.Create<ModelRenderer>();renderer.Model=Model.Load(ConcreteDestruction.Folder+$"chip_{Piece:00}.vmdl");}
    protected override void OnUpdate(){age+=Time.Delta;if(renderer.IsValid())renderer.Tint=Color.White.WithAlpha(((3.2f-age)/.4f).Clamp(0,1));}
    protected override void OnFixedUpdate()
    {
        if(!MultiplayerSession.Authority)return;
        var s=MultiplayerSession.Find(Scene);
        if(s?.IsOverdrive!=true || !s.InRound || s.RoundId!=Round || age>3.2f){GameObject.Destroy();return;}
        float dt=Time.Delta;Velocity+=Vector3.Down*500*dt;
        var end=WorldPosition+Velocity*dt;
        var hit=Scene.Trace.Sphere(2.2f,WorldPosition,end).WithSurfaceMeshes().WithoutTags("paintball_debris","paintball_actor").Run();
        if(!hit.Hit){WorldPosition=end;WorldRotation*=Rotation.From(new Angles(180,250,110)*dt);return;}
        var enemy=OverdriveEnemy.Find(hit.GameObject);
        if(!hitOnce && enemy.IsValid() && enemy.Zone(hit.HitPosition)==1 && Velocity.Length>=140)
        {
            var actor=Shooter.IsValid() ? Shooter.Components.Get<PaintballCombatant>() : null;
            if(enemy.KnockOut(Velocity.Normal,actor)){hitOnce=true;HeadKnockouts++;SausageBolt.Cue(hit.HitPosition,"bonk",Round,null);}
        }
        WorldPosition=hit.EndPosition+hit.Normal*.6f;
        Velocity=(Velocity-2*Vector3.Dot(Velocity,hit.Normal)*hit.Normal)*.22f;
        if(Velocity.Length<30)Velocity=Vector3.Zero;
    }
}