FootballLabPanel.razor

The Football lab panel for the football match UI. Lists training scenarios by category, lets the player adjust attacker and defender ratings, restart or reseed the trial, change playback speed and pause, and shows telemetry for the current run. Labels switch between English and Russian.

Rough Code
@using Sandbox
@using Sandbox.UI
@using Socer
@using System
@using System.Linq
@inherits Panel

<div class="football-lab-panel">
    <div class="football-lab-head">
        <div><label class="lab-eyebrow">@(GamePreferences.Russian?"ТРЕНИРОВОЧНАЯ ПЛОЩАДКА":"TRAINING GROUND")</label><label class="lab-title">@(GamePreferences.Russian?"Лаборатория":"Football lab")</label></div>
        <button class="lab-exit" onclick=@(()=>Game.CloseFootballLab()) title="Back">×</button>
    </div>
    <label class="lab-help">@(GamePreferences.Russian?"Повторить — те же условия для сравнения. Новый вариант — другая расстановка и решения защитника.":"Repeat keeps the same setup for comparison. New variation changes the positions and defender decisions.")</label>
    @if(Game.FootballLabTrialComplete){<label class="lab-complete">@(GamePreferences.Russian?"ЭПИЗОД ЗАВЕРШЁН · ПОВТОРИТЕ ИЛИ ВЫБЕРИТЕ ДРУГОЙ":"MOMENT COMPLETE · REPEAT OR CHOOSE ANOTHER")</label>}
    <div class="lab-rating-row">
        <div><label>@(GamePreferences.Russian?"АТАКА":"ATTACK")</label><div><button onclick=@(()=>Game.RunFootballLab(Game.FootballLabScenario,Game.FootballLabAttackerRating-5))>−</button><b>@Game.FootballLabAttackerRating</b><button onclick=@(()=>Game.RunFootballLab(Game.FootballLabScenario,Game.FootballLabAttackerRating+5))>+</button></div></div>
        <div><label>@(GamePreferences.Russian?"ЗАЩИТА":"DEFENCE")</label><div><button onclick=@(()=>Game.RunFootballLab(Game.FootballLabScenario,-1,Game.FootballLabDefenderRating-5))>−</button><b>@Game.FootballLabDefenderRating</b><button onclick=@(()=>Game.RunFootballLab(Game.FootballLabScenario,-1,Game.FootballLabDefenderRating+5))>+</button></div></div>
    </div>
    <div class="lab-actions">
        <button class="lab-repeat" onclick=@(()=>Game.RestartFootballLab())>@(GamePreferences.Russian?"Повторить":"Repeat")</button>
        <button class="lab-new-seed" onclick=@(()=>Game.RestartFootballLab(true))>@(GamePreferences.Russian?"Новый вариант":"New variation")</button>
    </div>
    <div class="lab-actions lab-playback">
        @if(!Game.FootballLabTrialComplete){<button onclick=@(()=>Game.Paused=!Game.Paused)>@(Game.Paused?GamePreferences.Russian?"▶ Продолжить":"▶ Resume":GamePreferences.Russian?"Ⅱ Пауза":"Ⅱ Pause")</button>}
        @foreach(var speed in new[]{.5f,1f,1.5f}){float value=speed;<button class="@(Math.Abs(Game.Speed-value)<.01f?"chosen":"")" onclick=@(()=>Game.Speed=value)>@($"{speed:0.0}×")</button>}
    </div>
    <div class="lab-categories">@foreach(var group in FootballLabScenarios.All.Select(s=>s.Group).Distinct()){var name=group;<button class="lab-category lab-category-@(name.ToLowerInvariant().Replace(" ","-")) @(Category==name?"chosen":"")" onclick=@(()=>Category=name)>@GroupName(name)</button>}</div>
    <div class="lab-scenarios"><div class="lab-tile-grid">@foreach(var scenario in FootballLabScenarios.All.Where(s=>s.Group==Category)){var id=scenario.Id;<button class="lab-tile @(Game.FootballLabScenario==id?"chosen":"") lab-scene-@id" onclick=@(()=>Game.RunFootballLab(id,nextSeed:true))>@ScenarioName(id)</button>}</div></div>
    <div class="lab-telemetry">
        <label class="lab-group">@(GamePreferences.Russian?"ЭТОТ ПРОГОН":"THIS RUN") · @Game.FootballLabSeed</label>
        <div><label>@(GamePreferences.Russian?"Атакующий":"Attacker")</label><b>@(GamePreferences.Russian?M.Players[9].Action:UiEnglish.Text(M.Players[9].Action))</b></div>
        <div><label>@(GamePreferences.Russian?"Защитник":"Defender")</label><b>@(GamePreferences.Russian?M.Players[13].Action:UiEnglish.Text(M.Players[13].Action))</b></div>
        <div><label>@(GamePreferences.Russian?"Касания":"Touches")</label><b>@M.DribbleTouches</b></div>
        <div><label>@(GamePreferences.Russian?"Финты":"Moves")</label><b>@M.SkillMoveStarts.Sum()</b></div>
        <div><label>@(GamePreferences.Russian?"Между ног":"Nutmegs")</label><b>@($"{M.NutmegsCompleted}/{M.NutmegAttempts}")</b></div>
        <div><label>@(GamePreferences.Russian?"Отборы":"Tackles")</label><b>@($"{M.Tackles[0]+M.Tackles[1]}/{M.TackleAttempts[0]+M.TackleAttempts[1]}")</b></div>
        <div><label>@(GamePreferences.Russian?"Нога / сбоку / блок":"Poke / hook / block")</label><b>@string.Join(" / ",M.StandingTackleVariantStarts)</b></div>
        <div><label>@(GamePreferences.Russian?"Подкат / корпус":"Slide / shoulder")</label><b>@($"{M.SlideAttempts} / {M.BodyAttempts}")</b></div>
        <div><label>@(GamePreferences.Russian?"Рывки вдогонку":"Recovery sprints")</label><b>@M.DefensiveRecoverySprints</b></div>
        <div><label>@(GamePreferences.Russian?"Плечо: победы / контакт":"Shoulder: wins / contact")</label><b>@($"{M.BodyWins} / {M.BodyPressureSeconds:0.0}s")</b></div>
        <div><label>@(GamePreferences.Russian?"Удары":"Shots")</label><b>@($"{M.Shots[0]} : {M.Shots[1]}")</b></div>
    </div>
</div>

@code {
    [Parameter] public MatchController Game {get;set;}
    string Category="DUELS";
    MatchSimulation M=>Game.Match;
    protected override int BuildHash()=>Game?.FootballLabActive==true?
        HashCode.Combine(Game.Revision,Game.FootballLabScenario,Game.FootballLabAttackerRating,Game.FootballLabDefenderRating,Category):0;
    string GroupName(string group)=>!GamePreferences.Russian?group:group switch
    {"DUELS"=>"ЕДИНОБОРСТВА","BALL CONTROL"=>"КОНТРОЛЬ МЯЧА","ATTACK"=>"АТАКА","SET PIECES"=>"СТАНДАРТЫ","SKILL MOVES"=>"ФИНТЫ",_=>"МАТЧ"};
    string ScenarioName(string id)
    {
        if(FootballLabScenarios.TrySkill(id,out var spec))return !GamePreferences.Russian?spec.Name:RussianSkill(spec.Kind);
        return !GamePreferences.Russian?FootballLabScenarios.All.First(s=>s.Id==id).Name:id switch
        {"dribble"=>"Обводка 1 на 1","nutmeg"=>"Между ног","tackle"=>"Отбор мяча","tackle-front"=>"Отбор спереди","tackle-side"=>"Отбор сбоку","tackle-recovery"=>"Вдогонку","tackle-slide"=>"Подкат","shoulder"=>"Борьба корпусом","sprint"=>"Бег с мячом","first-touch"=>"Первое касание","pass"=>"Передача и приём","cross"=>"Навес с фланга","one-on-one"=>"Выход к вратарю","shot"=>"Дальний удар","free-kick"=>"Штрафной","corner"=>"Угловой","penalty"=>"Пенальти",_=>"Полный матч"};
    }
    string RussianSkill(SkillMoveKind kind)=>kind switch
    {
        SkillMoveKind.BodyFeint=>"Ложное движение",SkillMoveKind.Stepover=>"Переступание",SkillMoveKind.Scissors=>"Ножницы",
        SkillMoveKind.BallRoll=>"Прокат мяча",SkillMoveKind.DragBack=>"Оттяжка",SkillMoveKind.CruyffTurn=>"Разворот Кройфа",
        SkillMoveKind.Roulette=>"Рулетка",SkillMoveKind.Elastico=>"Эластико",SkillMoveKind.ReverseElastico=>"Обратное эластико",
        SkillMoveKind.HeelToHeel=>"Пятка к пятке",SkillMoveKind.RainbowFlick=>"Радуга",SkillMoveKind.LaCroqueta=>"Ла крокета",
        SkillMoveKind.FakeShot=>"Ложный удар",SkillMoveKind.McGeadySpin=>"Разворот Макгиди",SkillMoveKind.ScoopTurn=>"Подсечка",
        SkillMoveKind.InsideChop=>"Внутренний срез",SkillMoveKind.OutsideCut=>"Внешний срез",SkillMoveKind.StopAndGo=>"Стоп и рывок",
        SkillMoveKind.SoleVTurn=>"V-разворот подошвой",_=>"Между ног"
    };
}