CharacterMarkerSound.cs
using Sandbox;
using System;
namespace PaintballBaddies;
/// <summary>One marker voice per seat, reshuffled by the round authority.</summary>
public static class CharacterMarkerSound
{
public const int Count=5;
public static string For(int profile)=>$"sounds/paintball/selected/shot_{Math.Clamp(profile,0,Count-1)}.sound";
public static int Profile(int assignments,int seat)=>assignments<0 ? Math.Clamp(seat,0,5)%Count
: Math.Clamp((assignments>>(Math.Clamp(seat,0,5)*3))&7,0,Count-1);
public static int Roll(int previous=-1)
{
// Six seats, five sounds: every sound is represented, only one repeats.
// Reject assignments that give any seat its previous round's sound.
for(int attempt=0;attempt<128;attempt++)
{
var bag=new[]{0,1,2,3,4,Game.Random.Int(0,Count-1)};
for(int i=bag.Length-1;i>0;i--){int j=Game.Random.Int(0,i);(bag[i],bag[j])=(bag[j],bag[i]);}
if(previous>=0 && Enumerable.Range(0,6).Any(i=>bag[i]==Profile(previous,i)))continue;
int packed=0;for(int i=0;i<6;i++)packed|=bag[i]<<(i*3);
return packed;
}
// Bounded fallback preserves both coverage and the no-repeat guarantee.
int fallback=0;for(int i=0;i<6;i++)fallback|=((Profile(previous,i)+1)%Count)<<(i*3);
return fallback;
}
public static string For(GameObject actor)
{
int seat=actor.Components.Get<NetworkPawn>()?.Seat ?? actor.Components.Get<NetworkBot>()?.Seat
?? actor.Components.Get<ArenaOpponent>()?.CharacterIndex ?? actor.Components.Get<RosterSelection>()?.Selected ?? 0;
int assignments=MultiplayerSession.Online ? MultiplayerSession.Find(actor.Scene)?.MarkerSoundAssignments ?? -1
: actor.Scene.GetAllComponents<ArenaMatch>().FirstOrDefault(x=>!x.IsProxy)?.MarkerSoundAssignments ?? -1;
return For(Profile(assignments,seat));
}
}