The static class manages dialogue for a football game. It defines several sets of humorous, vulgar, and localized phrases categorized by game events and selects dialogue using logic based on speaker temperament and dialogue history.
using System;
using System.Linq;
using System.Collections.Generic;
namespace Socer;
// Authored alternatives retain the football meaning in both languages and clean mode.
public static class PlayerDialogueFlavor
{
public readonly record struct Line(PlayerSpeechCue Cue,PlayerDialogue.Phrase Phrase);
public sealed record Exchange(int Id,PlayerDialogue.Phrase Opening,PlayerDialogue.Phrase Reply,string Language="",PlayerSpeechCue? Cue=null);
static Line Fun(PlayerSpeechCue cue,string ru,string en)=>new(cue,new(1000+(int)cue,ru,en));
static Line Rough(PlayerSpeechCue cue,string ru,string en,string cleanRu,string cleanEn)=>new(cue,new(2000+(int)cue,ru,en,cleanRu,cleanEn));
public static readonly Line[] Funny={
Fun(PlayerSpeechCue.Request,"Пас сюда, я не декорация!","Pass here, I'm not scenery!"),
Fun(PlayerSpeechCue.Through,"На ход! У меня лапки горят!","Through! My little legs are on fire!"),
Fun(PlayerSpeechCue.Support,"Дай назад, я сегодня страховка.","Back to me. I'm the insurance today."),
Fun(PlayerSpeechCue.Pressure,"Сзади! У меня тут очередь!","Man on! There's a queue behind me!"),
Fun(PlayerSpeechCue.Shield,"Держу! Но это не ипотека.","I've got it! It's not a mortgage."),
Fun(PlayerSpeechCue.CrossNear,"На ближнюю! Я тут прописался!","Near post! I live here now!"),
Fun(PlayerSpeechCue.CrossFar,"На дальнюю! Я там как мебель!","Far post! I'm part of the furniture!"),
Fun(PlayerSpeechCue.LineDown,"Назад! Вратарь уже скучает!","Drop back! The keeper's getting lonely!"),
Fun(PlayerSpeechCue.LineUp,"Выше! Этот газон уже видели!","Push up! We've seen this bit of grass!"),
Fun(PlayerSpeechCue.Distribution,"Ловите! Доставка без пакета!","Catch! Delivery, no bag included!"),
Fun(PlayerSpeechCue.Wall,"Стенка! Кто дырка — тот платит!","Wall! Whoever leaves a gap pays!"),
Fun(PlayerSpeechCue.ShortRestart,"Коротко! Длинное я не вывожу.","Short one! I can't do long distance."),
Fun(PlayerSpeechCue.Finish,"Бью! Молитесь по тарифу!","Shooting! Prayers cost extra!"),
Fun(PlayerSpeechCue.Goal,"Мам, я не зря в шортах!","Mum, the shorts were worth it!"),
Fun(PlayerSpeechCue.Miss,"Это не промах. Газон съехал.","I didn't miss. The pitch moved."),
Fun(PlayerSpeechCue.IgnoredPass,"Я открыт как круглосуточный!","I'm open like a twenty-four-hour shop!"),
Fun(PlayerSpeechCue.TeamRally,"Собрались! Мы пока не салат!","Get together! We're not a salad yet!"),
Fun(PlayerSpeechCue.OpponentTaunt,"Ты прессингуешь или обнимаешься?","Are you pressing or asking for a hug?"),
Fun(PlayerSpeechCue.FoulReaction,"По мячу! Я не груша!","The ball! I'm not a punching bag!"),
Fun(PlayerSpeechCue.Scuffle,"Это футбол, а не очередь в МФЦ!","It's football, not a queue at city hall!"),
Fun(PlayerSpeechCue.ScuffleReply,"Отцепись, я не твоя зарплата!","Let go, I'm not your paycheck!"),
Fun(PlayerSpeechCue.RefereeComplaint,"Свисток есть, инструкции не дали?","Got the whistle, lost the manual?"),
Fun(PlayerSpeechCue.CoachAcknowledge,"Понял! Не всё, но уверенно.","Got it! Not all of it, but confidently."),
Fun(PlayerSpeechCue.Scheme,"По плану! План кто-нибудь видел?","Stick to the plan! Anyone seen it?"),
Fun(PlayerSpeechCue.ControlMistake,"Мяч круглый, вопросы к нему!","The ball's round. Complain to the ball!"),
Fun(PlayerSpeechCue.Claim,"Мой! Чек у меня!","Mine! I've got the receipt!"),
Fun(PlayerSpeechCue.PassPraise,"Пас огонь! Я аж человеком стал.","What a pass! I feel human again."),
Fun(PlayerSpeechCue.DefensivePraise,"Отобрал! Теперь верни сдачу.","Won it! Now give him his change."),
Fun(PlayerSpeechCue.DribbleTaunt,"Ты где? Я уже за углом!","Where are you? I'm round the corner!"),
Fun(PlayerSpeechCue.AssistThanks,"Тебе спасибо, мне статистика!","Thanks to you, stats for me!"),
Fun(PlayerSpeechCue.InjuryPain,"Нога в отпуск. Я пока тут.","My leg's on leave. I'm still here.")
};
public static readonly Line[] Swearing={
Rough(PlayerSpeechCue.Request,"Да пасни уже, бля!","Just pass it, fuck!","Да пасни уже!","Just pass it!"),
Rough(PlayerSpeechCue.Pressure,"Сзади, блядь! Я не танк!","Man on, shit! I'm not a tank!","Сзади! Я не танк!","Man on! I'm not a tank!"),
Rough(PlayerSpeechCue.Finish,"Ебать, сейчас влеплю!","Fuck it, I'm hitting this!","Сейчас как влеплю!","I'm smashing this!"),
Rough(PlayerSpeechCue.Goal,"Ебать, залетело!","Fuck yes, it went in!","Вот это залетело!","Yes, it went in!"),
Rough(PlayerSpeechCue.Miss,"Бля, ворота что ли переехали?!","Fuck, did the goal move?!","Да ворота что ли переехали?!","Did the goal move?!"),
Rough(PlayerSpeechCue.IgnoredPass,"Ты пас зажал, сука, как зарплату!","Hoarding passes like a fucking paycheck!","Ты пас зажал как зарплату!","Hoarding passes like a paycheck!"),
Rough(PlayerSpeechCue.TeamRally,"Хватит хуйнёй страдать, играем!","Enough fucking about, play!","Хватит валять дурака, играем!","Enough messing about, play!"),
Rough(PlayerSpeechCue.OpponentTaunt,"Заебал ноги искать, мяч вон он!","Stop chasing my fucking legs, there's the ball!","Хватит ноги искать, мяч вон он!","Stop chasing my legs, there's the ball!"),
Rough(PlayerSpeechCue.FoulReaction,"Ты охуел? Это моя нога!","What the fuck? That's my leg!","Ты чего? Это моя нога!","What was that? That's my leg!"),
Rough(PlayerSpeechCue.Scuffle,"Руки убрал, мудак!","Hands off, asshole!","Руки убрал, герой!","Hands off, hero!"),
Rough(PlayerSpeechCue.ScuffleReply,"Сам-то не пизди, толкнул первый!","Cut the bullshit, you shoved first!","Не выдумывай, толкнул первый!","Don't make it up, you shoved first!"),
Rough(PlayerSpeechCue.RefereeComplaint,"Судья, ну это пиздец уже!","Ref, this is fucking ridiculous!","Судья, ну это уже перебор!","Ref, this is getting ridiculous!"),
Rough(PlayerSpeechCue.CoachAcknowledge,"Понял, бля. Теперь бы исполнить.","Got it, shit. Now to actually do it.","Понял. Теперь бы исполнить.","Got it. Now to actually do it."),
Rough(PlayerSpeechCue.ControlMistake,"Бля, у мяча сегодня характер!","Shit, the ball's got an attitude!","Да у мяча сегодня характер!","The ball's got an attitude!"),
Rough(PlayerSpeechCue.PassPraise,"Охуенный пас, ещё такой давай!","Fucking great pass, do that again!","Шикарный пас, ещё такой давай!","Brilliant pass, do that again!"),
Rough(PlayerSpeechCue.DribbleTaunt,"Нахуй ты туда побежал? Я тут!","Where the fuck are you going? I'm here!","Куда ты туда побежал? Я тут!","Where are you going? I'm here!"),
Rough(PlayerSpeechCue.AssistThanks,"Вот это пас, ебать! Уважаю!","What a fucking pass! Respect!","Вот это пас! Уважаю!","What a pass! Respect!"),
Rough(PlayerSpeechCue.InjuryPain,"Ай, бля! Нога не казённая!","Ow, fuck! I only get two legs!","Ай! Нога не казённая!","Ow! I only get two legs!")
};
static PlayerDialogue.Phrase P(int id,string ru,string en,string cleanRu="",string cleanEn="")=>new(id,ru,en,cleanRu,cleanEn);
public static readonly Exchange[] Spanish={
new(4000,P(4000,"¡Estoy solo, pásala!","¡Estoy solo, pásala!"),P(4001,"Я только «сало» понял. Пас куда?","I only understood 'solo'. Solo where?"),"es"),
new(4010,P(4010,"¡Vamos, vamos, al pie!","¡Vamos, vamos, al pie!"),P(4011,"За «вамос» я тоже. А бежать куда?","I'm all for 'vamos'. Which way though?"),"es"),
new(4020,P(4020,"¡Pásala, coño!","¡Pásala, coño!","¡Pásala, compañero!","¡Pásala, compañero!"),P(4021,"Да-да. Я ни хера не понял.","Yeah, sure. Didn't understand shit.","Да-да. Я вообще ничего не понял.","Yeah, sure. Didn't understand a thing."),"es"),
new(4030,P(4030,"¡Vamos, joder!","¡Vamos, joder!","¡Vamos, compañero!","¡Vamos, compañero!"),P(4031,"Брат, я без субтитров не вывожу.","Mate, I need subtitles for this."),"es")
};
public static readonly Exchange[] Teammates={
new(5000,P(5000,"Пасни, я тут как мебель!","Pass it, I'm part of the furniture!"),P(5001,"Мебель хотя бы не в офсайде.","Furniture usually stays onside.")),
new(5010,P(5010,"Дай в ноги, не в космос!","To feet, not into space!"),P(5011,"Космос ближе твоей позиции.","Space is closer than your position.")),
new(5020,P(5020,"Я открыт. Ты меня вообще видишь?","I'm open. Can you even see me?"),P(5021,"Вижу. Делаю вид, что это тактика.","I can. Pretending it's tactical.")),
new(5030,P(5030,"Пас зажал, бля? Он бесплатный!","Hoarding the fucking ball? Passes are free!","Пас зажал? Он бесплатный!","Hoarding the ball? Passes are free!"),P(5031,"Я коплю на красивую передачу.","I'm saving up for a pretty pass."))
};
public static readonly Exchange[] Opponents={
new(5100,P(5100,"Ты мяч-то видел?","Have you actually seen the ball?"),P(5101,"Видел. Он от тебя тоже в шоке.","Yes. It's confused by you too.")),
new(5110,P(5110,"Догоняй, у меня сегодня турбо!","Catch me! I've got turbo today!"),P(5111,"Турбо у него. Поворотник включи.","Turbo, sure. Try using an indicator."))
};
public static IEnumerable<PlayerDialogue.Phrase> All=>Funny.Concat(Swearing).Select(l=>l.Phrase).Concat(Spanish.Concat(Teammates).Concat(Opponents).SelectMany(e=>new[]{e.Opening,e.Reply}));
public static int PhraseCount=>Funny.Length+Swearing.Length+(Spanish.Length+Teammates.Length+Opponents.Length)*2;
public static bool SpanishSpeaker(Footballer p)=>p.Profile.CountryCode is "ES" or "AR" or "MX" or "UY" or "CL" or "CO";
public static bool Recent(Footballer p,int id)=>Array.IndexOf(p.RecentSpeechIds,id)>=0;
public static bool TryPhrase(PlayerSpeechCue cue,Footballer p,uint hash,out PlayerDialogue.Phrase phrase,DialogueMemory memory=null)
{
int roughChance=PlayerDialogue.Temperament(p)==PlayerTemperament.Fiery?12:6;
var bank=hash%100<roughChance?Swearing:Array.Empty<Line>();
foreach(var entry in bank)if(entry.Cue==cue && !Recent(p,entry.Phrase.Id) && !(memory?.Recent(entry.Phrase.Id)??false)){phrase=entry.Phrase;return true;}
phrase=default;return false;
}
public static Exchange[] ExchangePool(Footballer speaker,Footballer listener,PlayerSpeechCue cue)
{
bool opponents=speaker.Team!=listener.Team;
bool opponentCue=PlayerDialogue.Audience(cue)==SpeechAudience.Opponent;
if(opponents!=opponentCue || cue==PlayerSpeechCue.InjurySerious)return Array.Empty<Exchange>();
var bank=PlayerDialogueCatalog.Exchanges.Where(e=>e.Cue==cue);
if(opponents && cue is PlayerSpeechCue.OpponentTaunt or PlayerSpeechCue.DribbleTaunt)bank=bank.Concat(Opponents);
if(!opponents && cue is PlayerSpeechCue.Request or PlayerSpeechCue.IgnoredPass)bank=bank.Concat(Teammates);
if(!opponents && SpanishSpeaker(speaker) && !SpanishSpeaker(listener))
{
var foreign=PlayerDialogueCatalog.SpanishExtras.Where(e=>e.Cue==cue);
if(cue is PlayerSpeechCue.Request or PlayerSpeechCue.Support or PlayerSpeechCue.IgnoredPass)foreign=foreign.Concat(Spanish);
bank=foreign.Concat(bank);
}
return bank.ToArray();
}
public static Exchange PickExchange(Footballer speaker,Footballer listener,PlayerSpeechCue cue,uint hash,DialogueMemory memory=null)
{
var bank=ExchangePool(speaker,listener,cue);Exchange best=null;int bestCount=int.MaxValue,bestLast=int.MaxValue,bestAge=-1;
for(int i=0;i<bank.Length;i++)
{
var exchange=bank[(int)((hash+(uint)i)%(uint)bank.Length)];
if(Recent(speaker,exchange.Opening.Id) || Recent(listener,exchange.Reply.Id) ||
(memory?.Recent(exchange.Opening.Id,64)??false) || (memory?.Recent(exchange.Reply.Id,64)??false))continue;
int count=Math.Max(memory?.Uses(exchange.Opening.Id)??0,memory?.Uses(exchange.Reply.Id)??0);
int last=Math.Max(memory?.Last(exchange.Opening.Id)??-1000000,memory?.Last(exchange.Reply.Id)??-1000000);
int age=Math.Min(DialogueMemory.PersonalAge(speaker,exchange.Opening.Id),DialogueMemory.PersonalAge(listener,exchange.Reply.Id));
if(count<bestCount || count==bestCount && (last<bestLast || last==bestLast && age>bestAge))
{best=exchange;bestCount=count;bestLast=last;bestAge=age;}
}
return best;
}
}