ArenaSurfaceFinish.cs
using Sandbox;
using System;
namespace PaintballBaddies;
/// <summary>Shared local material finish for both arenas; native decals, bounded and deterministic.</summary>
public sealed class ArenaSurfaceFinish : Component
{
public int WetMarks {get;private set;}
public ArenaSurfaceMaterials SurfaceMaterials {get;private set;}
private GameObject overlay;
private Texture roughness;
private float ready;
private bool building;
private bool appliedOverdrive;
protected override void OnUpdate()
{
if(ArenaSelection.Current(Scene)==ArenaSelection.Restaurant)return;
bool overdrive=MultiplayerSession.Find(Scene)?.IsOverdrive==true;
if(overlay.IsValid())overlay.Enabled=!overdrive;
if(SurfaceMaterials is not null && appliedOverdrive!=overdrive)RefreshMaterials();
if(overlay.IsValid() || building)return;
ready+=Time.Delta;if(ready<1.5f || Scene.GetAllComponents<MapInstance>().Any(m=>m.Active && !m.IsLoaded))return;
Build();
}
public void RefreshMaterials(){SurfaceMaterials?.Restore();SurfaceMaterials=new ArenaSurfaceMaterials();SurfaceMaterials.Apply(Scene);appliedOverdrive=MultiplayerSession.Find(Scene)?.IsOverdrive==true;}
private void Build()
{
building=true;
// All matched finish textures now ship under the portable project.
// No extra cloud download is needed before the floor can be rendered.
if(!this.IsValid())return;
SurfaceMaterials=new ArenaSurfaceMaterials();
SurfaceMaterials.Apply(Scene);
appliedOverdrive=MultiplayerSession.Find(Scene)?.IsOverdrive==true;
overlay=new GameObject(GameObject,true,"Fresh glossy arena paint"){NetworkMode=NetworkMode.Never};
overlay.Enabled=!appliedOverdrive;
using(var b=new Bitmap(1,1)){b.Clear(new Color(.19f,0,1,1));roughness=b.ToTexture();}
var points=ArenaMap.PatrolPoints(Scene);
if(points.Length==0)points=ArenaOpponent.CreatePatrolRoute();
var random=new Random(9192026);
float R(float a,float b)=>a+(float)random.NextDouble()*(b-a);
var colors=new[]{new Color(.08f,.52f,.6f),new Color(.65f,.055f,.26f),new Color(.83f,.48f,.045f)};
foreach(var p in points)
{
for(int i=0;i<3 && WetMarks<96;i++)
{
var from=p+new Vector3(R(-115,115),R(-115,115),50);
var hit=Scene.Trace.Ray(from,from-Vector3.Up*110).WithoutTags("paintball_actor","paintball_debris","pop_target").Run();
if(!hit.Hit || hit.Normal.z<.8f)continue;
var go=new GameObject(overlay,true,"Recent paint pool")
{LocalScale=Vector3.One,WorldPosition=hit.HitPosition+hit.Normal*.2f,WorldRotation=Rotation.LookAt(-hit.Normal)};
var decal=go.Components.Create<Decal>();
decal.Decals=[new(){ColorTexture=PaintImpactSystem.SplashTexture,RoughMetalOcclusionTexture=roughness,Width=1,Height=1,ColorMix=1}];
float size=R(32,66);decal.Size=new Vector2(size,size*R(.75f,1.25f));
decal.Depth=.7f;decal.Rotation=R(0,360);decal.ColorTint=colors[WetMarks++%colors.Length];
decal.AttenuationAngle=.4f;decal.LifeTime=0;decal.Transient=false;
}
}
Log.Info($"ARENA_SURFACE_FINISH arena={ArenaMap.DisplayTitle(Scene)} wet_marks={WetMarks} reflections=SSR");
}
protected override void OnDestroy(){SurfaceMaterials?.Restore();overlay?.Destroy();roughness?.Dispose();}
}