This class manages rare dialogue events during match simulation. It handles self-reproach for player mistakes, quiet observations from defenders during downtime, and teammate goal jokes.
using System;
using System.Linq;
namespace Socer;
public sealed partial class MatchSimulation
{
float nextSelfReproachAt,nextDefenderIdleAt=35,nextQuietSampleAt,nextGoalCommentAt;
readonly float[] defenderQuietSince=Enumerable.Repeat(-1f,22).ToArray();
bool suppressGoalDialogueReply;
int lastGoalCommentTicket=-1;
public int SelfReproachLines,DefenderIdleConversations,GoalTeammateJokes;
PlayerSpeechCue RareSelfCue(Footballer p,PlayerSpeechCue cue)
{
if(cue is not (PlayerSpeechCue.Miss or PlayerSpeechCue.ControlMistake) || Seconds<nextSelfReproachAt)return cue;
uint roll=PlayerDialogue.StableHash(p.Profile.CatalogId+":self")^(uint)(Seed*397+p.Id*53+p.SpeechSerial*109);
return roll%100<35?PlayerSpeechCue.SelfReproach:cue;
}
void RareDialogueSpoken(PlayerSpeechCue cue)
{
if(cue==PlayerSpeechCue.SelfReproach){SelfReproachLines++;nextSelfReproachAt=Seconds+28;}
if(cue==PlayerSpeechCue.DefenderIdle)
{
DefenderIdleConversations++;
nextDefenderIdleAt=Seconds+60+(PlayerDialogue.StableHash(Seed+":quiet:"+DefenderIdleConversations)%16);
}
}
bool QuietDefender(Footballer p)
{
if(!p.InPlay || p.Keeper || p.Velocity.Length>=2 || p.DirectOrder!=DirectOrderKind.None ||
Owner<0 || Players[Owner].Team!=p.Team || World(Ball,p.Team).X<62 || (p.Position-Ball).Length<28)return false;
string role=p.Profile.SlotRole;
if(!(role is "CB" or "LB" or "RB" or "LWB" or "RWB") && !(string.IsNullOrEmpty(role) && p.Id%11 is >=1 and <=4))return false;
foreach(var q in Players)if(q.InPlay && q.Team!=p.Team && (q.Position-p.Position).Length<12)return false;
return true;
}
void ObserveRareDialogue()
{
if(!DialogueEnabled || Finished || MedicalActive || ShootoutActive || RehearsingStrategy ||
GoalHoldRemaining>0 || RestartRemaining>0 || IncidentRemaining>0 || PeriodWalkRemaining>0 || ShotInFlight)
{Array.Fill(defenderQuietSince,-1);return;}
if(Seconds<nextQuietSampleAt)return;
nextQuietSampleAt=Seconds+1.5f;
// Background chatter waits for a sustained quiet spell and yields to football calls.
foreach(var p in Players)
{
if(!QuietDefender(p)){defenderQuietSince[p.Id]=-1;continue;}
if(defenderQuietSince[p.Id]<0)defenderQuietSince[p.Id]=Seconds;
}
if(Seconds<nextDefenderIdleAt || pendingDialogueReply!=null || Players.Any(p=>p.SpeechUntil>Seconds))return;
foreach(var p in Players)
{
if(defenderQuietSince[p.Id]<0 || Seconds-defenderQuietSince[p.Id]<4.5f)continue;
uint roll=PlayerDialogue.StableHash(Seed+":idle:"+p.Id)^(uint)((int)(Seconds/1.5f)*193);
if(roll%100<18 && SpeakPlayer(p,PlayerSpeechCue.DefenderIdle))break;
}
}
bool TryTeammateGoalJoke(Footballer scorer,bool assisted,int passer)
{
if(!DialogueEnabled || !scorer.InPlay)return false;
int ticket=Goals[0]+Goals[1];
if(ticket==lastGoalCommentTicket)return false;
lastGoalCommentTicket=ticket;
if(Seconds<nextGoalCommentAt)return false;
uint roll=PlayerDialogue.StableHash(scorer.Profile.CatalogId+":goal joke")^(uint)(Seed*193+ticket*397);
if(roll%100>=28)return false;
var friend=Players.Where(p=>p.InPlay && !p.Keeper && p.Team==scorer.Team && p.Id!=scorer.Id &&
(p.Position-scorer.Position).Length<=18)
.OrderBy(p=>assisted && p.Id==passer?0:1).ThenBy(p=>(p.Position-scorer.Position).Length).FirstOrDefault();
if(friend==null)return false;
friend.SpeechCooldown=0;friend.LastSpeechAt=-10;
if(!SpeakPlayer(friend,PlayerSpeechCue.GoalTeammateJoke,true,true,scorer.Id))return false;
nextGoalCommentAt=Seconds+70;GoalTeammateJokes++;
return true;
}
}