FootballOffsideGraphics.cs

Partial class for the football match world panel. Draws the offside decision as two feathered vertical pitch lines and exposes the current offside values in a report object.

Rough Code
using Sandbox;
using Socer;
using System;

public sealed partial class FootballWorldPanel
{
    SceneDynamicObject offsideDecisionObject;
    readonly Vertex[] offsideDecisionVertices=new Vertex[36];
    float offsideStrokeWidth;
    bool offsideDecisionVisible;

    void DrawOffsideDecision(MatchSimulation match,bool replay)
    {
        // Remove the former box even from a panel surviving hotload. The new
        // flat, feathered lines have exact touchline endpoints and cast no shadow.
        if(offside!=null){offside.GameObject.Destroy();offside=null;}
        offsideDecisionVisible=!replay && match.OffsideDecisionTeam>=0 && match.OffsideLineUntil>match.Seconds;
        if(!offsideDecisionVisible)
        {if(offsideDecisionObject!=null)offsideDecisionObject.RenderingEnabled=false;return;}
        offsideDecisionObject??=GroundObject();offsideDecisionObject.RenderingEnabled=true;
        int count=0;
        void Line(float x,string color)
        {
            x=Math.Clamp(x,0,105);
            float pixel=GroundPixelSize(new Point(x,34));
            float half=Math.Max(.028f,pixel),fringe=pixel*.85f;
            offsideStrokeWidth=half*2;
            var ink=Ink(color);
            Vertex V(float y,int band)
            {
                float offset=band switch{0=>-half-fringe,1=>-half,2=>half,_=>half+fringe};
                return new Vertex(WorldPoint(new Point(Math.Clamp(x+offset,0,105),y),.016f),
                    new Color32((byte)(ink.r*255),(byte)(ink.g*255),(byte)(ink.b*255),(byte)(band is 0 or 3?0:230)));
            }
            for(int band=0;band<3;band++)
            {
                var a=V(0,band);var b=V(68,band);var c=V(68,band+1);var d=V(0,band+1);
                offsideDecisionVertices[count++]=a;offsideDecisionVertices[count++]=b;offsideDecisionVertices[count++]=c;
                offsideDecisionVertices[count++]=a;offsideDecisionVertices[count++]=c;offsideDecisionVertices[count++]=d;
            }
        }
        Line(match.OffsideLineX,"#ebd477");
        Line(match.OffsideAttackerX,"#ed796f");
        offsideDecisionObject.Init(Graphics.PrimitiveType.Triangles);
        offsideDecisionObject.AddVertex(offsideDecisionVertices.AsSpan(0,count));
    }
    public object OffsideGraphicsReport()=>new{Visible=offsideDecisionVisible,StrokeMetres=offsideStrokeWidth,
        Decision=Game.Match.OffsideDecisionTeam,LimitX=Game.Match.OffsideLineX,AttackerX=Game.Match.OffsideAttackerX,
        PassBall=new{Game.Match.OffsidePassBall.X,Game.Match.OffsidePassBall.Y},Game.Match.OffsideLineUntil,Game.Match.Seconds,
        Flat=true,Feathered=true,Vertices=offsideDecisionVisible?36:0,LegacyBox=offside!=null};
}