MatchResult.razor

Razor panel for the post-match screen in a soccer career game. Shows the result (win/draw/loss, score, penalties), a match statistics tab fed from MatchController.Match counters, a rewards tab with animated coin and cup token counters, and buttons to continue to the club or retry career round calculation. Includes English/Russian string lookup.

@using Sandbox
@using Sandbox.UI
@using Socer
@using System
@using System.Globalization
@inherits Panel

<root class="localized-text @OutcomeClass @(Ready?"ready":"entering")">
    <div class="result-stage">
        @if(Game.FinalCupVictoryMatch)
        {
            <div class="career-finale-victory"><img src="/ui-icons/ui-bold-nav-cup.svg" /><div><label>@T("YOU WIN SOCER","ВЫ ПОБЕДИЛИ В SOCER")</label><label>@T("Champions Cup winners","Обладатели Кубка чемпионов")</label><label>@T("Five leagues. One final trophy. Your club made it.","Пять лиг. Главный трофей. Ваш клуб прошёл весь путь.")</label></div></div>
        }
        <div class="result-heading"><label class="result-kicker">@T("SOCER / FULL TIME")</label><label class="result-outcome">@Outcome</label></div>
        <div class="result-scoreline"><img class="result-crest" src="@Socer.ClubIdentity.Crest(Game.Match.HomeName)" /><label>@T(Game.Match.HomeName)</label><label class="result-score">@Game.Match.Goals[0] : @Game.Match.Goals[1]</label><label>@T(Game.Match.AwayName)</label><img class="result-crest" src="@Socer.ClubIdentity.Crest(Game.Match.AwayName)" /></div>
        @if(Game.Match.ShootoutComplete){<label class="result-penalties">@($"{T("Penalties","Серия пенальти")} {Game.Match.ShootoutScores[0]} : {Game.Match.ShootoutScores[1]}")</label>}
        <div class="result-tabs"><button class="@(!Rewards?"active":"")" onclick=@(()=>Rewards=false)>@T("Match")</button><button class="@(Rewards?"active":"")" disabled="@Game.AwaitingCareerRoundResults" onclick=@(()=>Rewards=true)>@T("Rewards")</button></div>
        @if(!Rewards)
        {
            <div class="result-content match-statistics">
                <div class="stats-head"><label>@T(Game.Match.HomeName)</label><label>@T("MATCH STATISTICS")</label><label>@T(Game.Match.AwayName)</label></div>
                @foreach(var stat in MatchStats)
                {
                    <div class="result-stat-row"><label class="result-value">@($"{stat.Home}{stat.Suffix}")</label><div class="stat-center"><label>@T(stat.Name)</label><div class="comparison-track"><div style="@StatWidth(stat.Home,stat.Away)"></div></div></div><label class="result-value">@($"{stat.Away}{stat.Suffix}")</label></div>
                }
                @if(!string.IsNullOrEmpty(Game.MatchMedicalSummary)){<label class="result-caption result-medical-summary">@T(Game.MatchMedicalSummary)</label>}
            </div>
        }
        else
        {
            <div class="result-content rewards-content">
                <div class="reward-tile"><label class="reward-symbol">◈</label><label class="result-caption">@T("CLUB BUDGET")</label><label class="reward-value">+@CoinsShown</label><label>@T(Receipt!=null?"Awarded, including competition bonuses":"Friendly match · no career rewards")</label></div>
                @if(Receipt?.CupTokens>0){<div class="reward-tile cup-currency-reward"><div class="reward-cup-icon"></div><label class="result-caption">@T("CUP TOKENS","КУБКИ")</label><label class="reward-value">+@CupTokensShown</label><label>@T("For a cup win or first place in the league","За кубковую победу или первое место в лиге")</label></div>}
            </div>
        }
        <div class="result-bottom">
            @if(Game.AwaitingCareerRoundResults)
            {
                <label class="result-round-status" style="white-space: normal;">@(Game.CareerRoundCalculationFailed?T("Could not finish the round. Your score is kept in this session; retry the other matches to collect your rewards.","Не удалось рассчитать тур. Счёт вашего матча сохранён в этой сессии; повторите расчёт остальных встреч, чтобы получить награды."):T("Finishing the other matches…","Считаем остальные матчи тура…"))</label>
                @if(Game.CareerRoundCalculationFailed){<div><button class="result-continue retry-round-results" disabled="@(!Game.CanRetryCareerRoundResults)" onclick=@(()=>Game.RetryCareerRoundResults())>@T("Retry calculation","Повторить расчёт")</button></div>}
            }
            else
            {
                <label>@ReceiptSummary</label><div><button class="result-continue" onclick=@(()=>{if(!Rewards)Rewards=true;else Game.ExitFinishedMatch();})>@T(Rewards?"To club →":"To rewards →")</button>@if(!Rewards){<button class="result-exit" onclick=@(()=>Game.ExitFinishedMatch())>@T("Go straight to club")</button>}</div>
            }
        </div>
    </div>
</root>

@code {
    public MatchController Game {get;set;}
    public MatchController.MatchSettlement PreviewReceipt {get;set;}
    public bool HandleHotkey(string id)
    {
        if(id!="app.confirm" || !Ready || Game.AwaitingCareerRoundResults || Game.CareerRoundCalculationFailed)return false;
        if(!Rewards)Rewards=true;else Game.ExitFinishedMatch();return true;
    }
    bool rewards;
    float rewardsAt;
    bool Rewards { get=>rewards; set { if(value && !rewards)rewardsAt=Time.Now;rewards=value; } }
    float opened=Time.Now;
    bool Ready=>Time.Now-opened>.05f;
    MatchController.MatchSettlement Receipt=>Game.IsRulesPreview?PreviewReceipt:Game.Settlement?.Match==Game.Match?Game.Settlement:null;
    string OutcomeKey=>Receipt!=null?(Receipt.Won?"WIN":Receipt.Draw?"DRAW":"LOSS"):Game.Match.ShootoutComplete?(Game.Match.ShootoutWinner==0?"WIN":"LOSS"):Game.Match.Goals[0]>Game.Match.Goals[1]?"WIN":Game.Match.Goals[0]==Game.Match.Goals[1]?"DRAW":"LOSS";
    string Outcome=>T(OutcomeKey);
    string OutcomeClass=>OutcomeKey switch {"WIN"=>"outcome-win","DRAW"=>"outcome-draw",_=>"outcome-loss"};
    string ReceiptSummary=>string.IsNullOrWhiteSpace(Receipt?.Summary)?T("Match complete","Матч завершён"):T(UiEnglish.Text(Receipt.Summary,"Match complete"));
    // This child rerenders independently while the reward counters animate.
    // Resolve language here so a parent localization pass never races the render.
    static string T(string text,string russianFallback=null)
    {
        if(!GamePreferences.Russian)return text;
        string translated=UiRussian.Translate(text);
        return translated==text && russianFallback!=null?russianFallback:translated;
    }
    float RewardProgress=>1-MathF.Pow(1-Math.Clamp((Time.Now-rewardsAt)/.65f,0,1),3);
    int CoinsShown=>(int)MathF.Round((Receipt?.Coins??0)*RewardProgress);
    int CupTokensShown=>(int)MathF.Round((Receipt?.CupTokens??0)*RewardProgress);
    (string Name,int Home,int Away,string Suffix)[] MatchStats
    {
        get
        {
            var m=Game.Match;float possession=m.Possession[0]+m.Possession[1];
            int home=possession>0?(int)MathF.Round(m.Possession[0]*100/possession):50;
            int Accuracy(int team)=>m.Passes[team]>0?(int)MathF.Round(m.Completed[team]*100f/m.Passes[team]):0;
            return new[]{
                ("Possession",home,100-home,"%"),
                ("Shots",m.Shots[0],m.Shots[1],""),
                ("Shots on target",m.Goals[0]+m.Saves[1],m.Goals[1]+m.Saves[0],""),
                ("Shots off target",m.ShotsWide[0]+m.ShotsOver[0],m.ShotsWide[1]+m.ShotsOver[1],""),
                ("Passes",m.Passes[0],m.Passes[1],""),
                ("Pass accuracy",Accuracy(0),Accuracy(1),"%"),
                ("Through balls",m.ThroughBalls[0],m.ThroughBalls[1],""),
                ("Wall passes",m.WallPassesCompleted[0],m.WallPassesCompleted[1],""),
                ("Third-man combinations",m.ThirdManPassesCompleted[0],m.ThirdManPassesCompleted[1],""),
                ("Coordinated runs",m.CoordinatedRuns[0],m.CoordinatedRuns[1],""),
                ("Dribbles won",m.DribblesWonByTeam[0],m.DribblesWonByTeam[1],""),
                ("Possessions lost",m.PossessionLosses[0],m.PossessionLosses[1],""),
                ("Corners",m.Corners[0],m.Corners[1],""),
                ("Set-piece shots",m.FreeKickShots[0],m.FreeKickShots[1],""),
                ("Set-piece crosses",m.FreeKickCrosses[0]+m.CornerNear[0]+m.CornerFar[0],m.FreeKickCrosses[1]+m.CornerNear[1]+m.CornerFar[1],""),
                ("Short set pieces",m.FreeKickShort[0]+m.CornerShort[0],m.FreeKickShort[1]+m.CornerShort[1],""),
                ("Penalties awarded",m.PenaltiesAwarded[0],m.PenaltiesAwarded[1],""),
                ("Tackles",m.Tackles[0],m.Tackles[1],""),
                ("Saves",m.Saves[0],m.Saves[1],""),
                ("Fouls",m.Fouls[0],m.Fouls[1],""),
                ("Offsides",m.Offsides[0],m.Offsides[1],""),
                ("Yellow cards",m.YellowCards[0],m.YellowCards[1],""),
                ("Red cards",m.RedCards[0],m.RedCards[1],"")
            };
        }
    }
    static string StatWidth(int home,int away)=>FormattableString.Invariant($"width:{(home+away>0?home*100f/(home+away):50):0.#}%;");
    protected override int BuildHash()=>HashCode.Combine(Ready,Rewards,Receipt,Game.Match.Goals[0],Game.Match.Goals[1],CoinsShown,CupTokensShown,GamePreferences.Revision)^HashCode.Combine(Game.AwaitingCareerRoundResults,Game.CareerRoundCalculationFailed,Game.CanRetryCareerRoundResults,Game.FinalCupVictoryMatch);
}