Partial class for the football world panel. Builds a shared crossed-ribbon mesh and positions particle objects for sprint dust and ball flight trails each frame.
using Sandbox;
using Socer;
using System.Collections.Generic;
public sealed partial class FootballWorldPanel
{
Model matchWakeModel;
Model MatchWakeModel
{
get
{
if(matchWakeModel!=null)return matchWakeModel;
// Shared, feathered crossed ribbons, built once. Keep the material
// strongly owned by DynamicGeometryMaterial for the panel lifetime.
var vertices=new List<Vertex>(72);
void Quad(Vector3 a,Vector3 b,Vector3 c,Vector3 d,byte edgeA,byte edgeB)
{
Vertex V(Vector3 p,byte alpha)=>new(p,new Color32(255,255,255,alpha));
vertices.Add(V(a,edgeA));vertices.Add(V(b,edgeA));vertices.Add(V(c,edgeB));
vertices.Add(V(a,edgeA));vertices.Add(V(c,edgeB));vertices.Add(V(d,edgeB));
}
for(int plane=0;plane<2;plane++)for(int band=0;band<3;band++)
{
float Edge(int b)=>b switch{0=>-.5f,1=>-.16f,2=>.16f,_=>.5f};
Vector3 P(float x,float y)=>plane==0?new(x,y,0):new(x,0,y);
float lo=Edge(band),hi=Edge(band+1);
Quad(P(-.5f,lo),P(.5f,lo),P(.5f,hi),P(-.5f,hi),
(byte)(band==0?0:255),(byte)(band==2?0:255));
}
var bounds=new BBox(new Vector3(-.51f),new Vector3(.51f));
var mesh=new Mesh(DynamicGeometryMaterial);
mesh.CreateVertexBuffer(vertices.Count,vertices);mesh.Bounds=bounds;
return matchWakeModel=Model.Builder.AddMesh(mesh).WithViewBounds(bounds).Create();
}
}
void DrawMatchEffects(IReadOnlyList<SprintDust.Puff> puffs,float seconds,IReadOnlyList<FlightMark> trail,
Point ballPosition,float ballHeight,float speed,bool free,UniqueSkill skill,int grade,LegendaryAbility legendary)
{
for(int i=0;i<dustObjects.Length;i++)
{
var obj=dustObjects[i];
if(obj==null)continue;
obj.Enabled=i<puffs.Count;
if(!obj.Enabled)continue;
var p=puffs[i];var sample=SprintDust.Sample(p,seconds);
obj.Enabled=sample.Visible;
if(!sample.Visible)continue;
bool contact=p.Kind is SprintDust.ParticleKind.Glove or SprintDust.ParticleKind.Frame;
var model=contact?MatchWakeModel:sphere;
if(obj.Model!=model)obj.Model=model;
SetPart(obj,sample.A,sample.ZA,sample.B,sample.ZB,sample.Width,sample.Color);
if(contact)obj.Tint=obj.Tint.WithAlpha(sample.Opacity);
else
{
var transform=obj.WorldTransform;var scale=transform.Scale;
scale.z*=p.Kind==SprintDust.ParticleKind.Grass?.32f:.65f;
obj.WorldTransform=new Transform(transform.Position,transform.Rotation,scale);
}
}
for(int i=0;i<flightObjects.Length;i++)
{
var obj=flightObjects[i];
if(obj==null)continue;
// The restrained double filament replaces the ordinary white wake.
// Authored skill colours remain their own explicitly selected FX.
if(skill==UniqueSkill.None && legendary==LegendaryAbility.None){obj.Enabled=false;continue;}
var stroke=FootballMatchEffects.Flight(trail,i,ballPosition,ballHeight,speed,free,skill,grade,legendary);
obj.Enabled=stroke.Visible;
if(!stroke.Visible)continue;
if(obj.Model!=MatchWakeModel)obj.Model=MatchWakeModel;
SetPart(obj,stroke.A,stroke.ZA,stroke.B,stroke.ZB,stroke.Width,stroke.Color);
obj.Tint=obj.Tint.WithAlpha(stroke.Opacity);
}
}
}