Razor panel for the injury substitution prompt in a soccer match. Shows the injured player's portrait and name, lists eligible replacement cards sorted by suitability with keyboard navigation, and confirms either the substitution or continuing short-handed when no reserve is available.
@using Sandbox
@using Sandbox.UI
@using Socer
@using System
@using System.Linq
@inherits Panel
<div class="injury-choice localized-text">
<label class="injury-kicker">@Text("MEDICAL STOPPAGE", "МЕДИЦИНСКАЯ ОСТАНОВКА")</label>
<label class="injury-title">@Text("A substitution is required", "Нужна замена")</label>
<div class="injured-player">
@if(Outgoing!=null){<img src="@PlayerAppearance.Portrait(Outgoing)" />}
<div><label>@(Outgoing?.Name??Game.InjuredMatchPlayer?.Profile.Name)</label><label class="injury-copy">@Text("Cannot continue this match. Taken off on a stretcher.", "Не может продолжить матч. Унесён с поля на носилках.")</label></div>
</div>
@if(Candidates.Length>0)
{
<label class="injury-copy">@(Game.InjuryNeedsEmergencyKeeper?Text("No reserve goalkeeper. Choose an outfield player to take the gloves.", "Запасного вратаря нет. Выберите полевого игрока, который встанет в ворота."):Text("Choose who comes on. Players are ranked by suitability for this position.", "Выберите, кто выйдет. Запасные отсортированы по пригодности для этой позиции."))</label>
<div class="injury-candidates">
@foreach(var card in Candidates)
{
<button class="injury-candidate @(Selected==card.Id?"chosen":"")" onclick=@(()=>Selected=card.Id)>
<img src="@PlayerAppearance.Portrait(card)" />
<div><label>@card.Name</label><label class="candidate-role">@(PlayerPositions.Card(card)+" · "+card.Overall)</label>
@if(card.Id==Candidates[0].Id){<label class="candidate-best">@Text("Best fit", "Лучше подходит")</label>}
</div>
</button>
}
</div>
<div class="injury-footer"><label>@($"{Game.SubstitutionsUsed}/{MatchController.SubstitutionLimit} "+Text("substitutions used", "замен использовано"))</label><button class="primary injury-confirm" disabled="@(!Candidates.Any(c=>c.Id==Selected))" onclick=@Confirm>@Text("Bring on →", "Выпустить →")</button></div>
}
else
{
<label class="injury-copy">@(Game.SubstitutionsUsed>=MatchController.SubstitutionLimit?Text("All five substitutions have been used. The team will continue short-handed.", "Все пять замен использованы. Команда продолжит в меньшинстве."):Text("There is no eligible reserve. The team will continue short-handed.", "Подходящих запасных нет. Команда продолжит в меньшинстве."))</label>
<button class="primary injury-confirm" onclick=@(()=>Game.ContinueAfterInjuryWithoutReserve())>@Text("Continue match →", "Продолжить матч →")</button>
}
</div>
@code {
[Parameter] public MatchController Game {get;set;}
int Selected=-1;
PlayerCard Outgoing=>Game.MatchCard(Game.InjuredMatchSlot);
PlayerCard[] Candidates=>Game.InjuryReplacementCandidates.ToArray();
static string Text(string en,string ru)=>GamePreferences.Russian?ru:en;
void Confirm(){if(Game.ReplaceInjuredPlayer(Selected))Selected=-1;}
public bool HandleHotkey(string id)
{
var candidates=Candidates;
if(id=="app.confirm")
{
if(candidates.Length==0)return Game.ContinueAfterInjuryWithoutReserve();
if(!candidates.Any(c=>c.Id==Selected))return false;
Confirm();return true;
}
if(id is not ("player.previous" or "player.next") || candidates.Length==0)return false;
int step=id=="player.previous"?-1:1,index=Array.FindIndex(candidates,c=>c.Id==Selected);
Selected=candidates[index<0?(step>0?0:candidates.Length-1):(index+step+candidates.Length)%candidates.Length].Id;return true;
}
// The compulsory choice freezes the match: avoid frame-driven rebuilds
// which would repeatedly replace already-localized labels.
protected override int BuildHash()=>HashCode.Combine(Game?.Match,Game?.InjuredMatchSlot,Game?.SubstitutionsUsed,Selected,GamePreferences.Revision);
}