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

// A one-way enemy entrance. Native collision rules block the squad while
// allowing sausages out; the opaque swinging leaf conceals their arrival.
public sealed class RestaurantBathroomEntrance : Component
{
    public const string BarrierTag="sausage_entry_clip";
    public static readonly Vector3 Threshold=new(448,793,33);
    private GameObject root,hinge;
    private float open,linger;
    public float OpenAmount=>open;
    protected override void OnStart()
    {
        root=new GameObject(true,"Restroom enemy entrance"){NetworkMode=NetworkMode.Never};
        Scene.NavMesh.ExcludedBodies.Add(BarrierTag);
        var barrier=new GameObject(root,false,"Restroom exit-only player barrier")
        {NetworkMode=NetworkMode.Never,WorldPosition=Threshold+Vector3.Up*56};
        barrier.Tags.Add(BarrierTag);
        var clip=barrier.Components.Create<BoxCollider>();clip.Scale=new Vector3(12,64,116);clip.Static=true;
        // This is a physical player clip, never invisible cover or bullet cover.
        clip.ColliderFlags=ColliderFlags.IgnoreTraces;barrier.Enabled=true;
        hinge=new GameObject(root,true,"Restroom outward door hinge")
        {NetworkMode=NetworkMode.Never,WorldPosition=new Vector3(447,817,33)};
        var wood="models/foundry/foundry_factory_door/foundry_factory_door.vmat";
        Panel(hinge,"Restroom door leaf",new Vector3(0,-24,51),new Vector3(3.5f,48,102),new Color(.27f,.21f,.14f),wood);
        foreach(float y in new[]{-43f,-5f})Panel(hinge,"Restroom door raised stile",new Vector3(-2.2f,y,51),new Vector3(1.4f,3.5f,96),new Color(.20f,.15f,.1f),wood);
        foreach(float z in new[]{7f,51f,96f})Panel(hinge,"Restroom door raised rail",new Vector3(-2.2f,-24,z),new Vector3(1.4f,39,4),new Color(.2f,.15f,.1f),wood);
        Panel(hinge,"Restroom push plate",new Vector3(-3,-39,44),new Vector3(.9f,6,14),new Color(.46f,.43f,.35f),"materials/foundry/metalplates006/metalplates006.vmat");
        Label(hinge,"EXIT ONLY",new Vector3(-3.4f,-24,74),.13f,new Color(1,.81f,.43f));
        Label(hinge,"NO ENTRY",new Vector3(-3.4f,-24,63),.115f,new Color(1,.81f,.43f));
        Panel(root,"Restroom out of order sign",new Vector3(436,793,150),new Vector3(2,92,18),new Color(.14f,.1f,.075f),"materials/foundry/metalplates006/metalplates006.vmat");
        Label(root,"RESTROOM · OUT OF ORDER",new Vector3(434.5f,793,151),.12f,new Color(1,.8f,.45f));
        Scene.NavMesh.SetDirty();
    }
    protected override void OnUpdate()
    {
        if(!hinge.IsValid())return;
        bool exiting=Scene.GetAllComponents<OverdriveEnemy>().Any(e=>!e.Dead && e.GameObject.Active
            && e.WorldPosition.x>380 && e.WorldPosition.x<570 && MathF.Abs(e.WorldPosition.y-793)<72);
        if(exiting)linger=.5f;else linger=MathF.Max(0,linger-Time.Delta);
        float target=linger>0 ? 1 : 0;
        open+=(target-open)*(1-MathF.Exp(-10*Time.Delta));
        hinge.WorldRotation=Rotation.FromYaw(-100*open);
    }
    private static GameObject Panel(GameObject parent,string name,Vector3 at,Vector3 size,Color tint,string material)
    {
        var model=Model.Load("models/dev/box.vmdl");
        var go=new GameObject(parent,true,name){NetworkMode=NetworkMode.Never,LocalPosition=at};
        go.LocalScale=size/model.Bounds.Size;go.LocalPosition-=model.Bounds.Center*go.LocalScale;
        var render=go.Components.Create<ModelRenderer>();render.Model=model;render.Tint=tint;render.MaterialOverride=Material.Load(material);
        return go;
    }
    private static void Label(GameObject parent,string text,Vector3 at,float scale,Color color)
    {
        var go=new GameObject(parent,true,"Restroom entrance notice"){NetworkMode=NetworkMode.Never,LocalPosition=at};
        var label=go.Components.Create<TextRenderer>();label.Text=text;label.FontFamily="Arial";label.FontWeight=800;label.FontSize=36;label.Scale=scale;
        label.Color=color;label.HorizontalAlignment=TextRenderer.HAlignment.Center;label.VerticalAlignment=TextRenderer.VAlignment.Center;
    }
    protected override void OnDestroy(){if(root.IsValid())root.Destroy();}
}