PlayerCardView.razor

The component renders a player card UI. It displays player attributes like name, rarity, position, skills, and statistics based on provided data. It manages drag and click interactions for the card element.

@using Sandbox
@using Sandbox.UI
@using Socer
@using System
@using System.Linq
@inherits Panel
@{ var cardPortrait=PortraitOverride??PlayerAppearance.Portrait(Card); var equippedSkills=Card?.SkillLoadout?.Where(s=>s!=null && UniqueSkills.Get(s.Skill)!=null).Take(UniqueSkills.MaxSlots(Card)).ToArray()??Array.Empty<SkillInstance>(); }

<button @ref="CardButton" class="football-card [email protected]() @(Card.Rarity is "BRONZE" or "SILVER" or "GOLD" or "ELITE" or "LEGENDARY"?"faceted-card":"") @CssClass @(cardPortrait?.EndsWith("/messi-cartoon-test-v1.png")==true?"portrait-messi-cartoon":"") @(Card.Rarity==CardRatings.Legendary?"legendary-"+Card.CatalogId.ToLowerInvariant():"") @(dragging?"drag-source":"")" style="@InlineStyle" onclick=@ClickCard onmousedown=@(e=>CardMouseDown(e)) onmouseup=@(e=>CardMouseUp(e))>
    <div class="card-frame-highlight"></div>
    <div class="card-grain"></div><div class="card-light"></div><div class="card-engraving"></div>
    <div class="card-top"><label class="ovr">@Card.Overall</label><label class="role">@PlayerPositions.Card(Card)</label></div>
    @if(Card.IsAcademyLoan){<label class="card-academy-loan localized-text" title="@(GamePreferences.Russian?"Академия · временный игрок":"Academy · temporary player")">@(GamePreferences.Russian?"ВРЕМ.":"TEMP")</label>}
    @if(equippedSkills.Length>0){<div @ref="SkillsStrip" class="card-equipped-skills [email protected] localized-text">@foreach(var skill in equippedSkills){<SkillBadgeView Skill="@skill.Skill" Grade="@skill.Grade" CssClass="@($"card-skill-emblem skill-{skill.Skill.ToString().ToLowerInvariant()} {(skill.Skill==HighlightSkill?"skill-just-granted":"")}")" />}</div>}
    <div class="card-portrait-window">
        @if(cardPortrait is string portrait){<div class="portrait generated-portrait" style="@($"background-image:url('{portrait}');")"></div>}
        else{<div class="portrait">@foreach(var part in PlayerPortrait.Parts(ClubSeed,Card)){<div class="portrait-part" style="@part"></div>}</div>}
    </div>
    <div class="card-data-panel">
    <div class="surname @(Card.Name.Split(' ').Last().Length>8?"name-long":"")">@(Card.Name.Split(' ').Last())</div>
    <div class="card-bottom">
        <div class="card-stats">@foreach(var stat in PlayerCardDetails.Summary(Card)){<div class="card-stat"><label>@stat.Name</label><b>@stat.Value</b></div>}</div>
        <div class="card-insignia"><img class="country-flag" src="@($"/flags/{Card.CountryCode.ToLowerInvariant()}.svg")" title="@Card.Country" /></div>
    </div>
    </div>
</button>

@code {
    [Parameter] public PlayerCard Card { get; set; }
    [Parameter] public int ClubSeed { get; set; }
    [Parameter] public string CssClass { get; set; }="";
    [Parameter] public string InlineStyle { get; set; }="";
    [Parameter] public string PortraitOverride { get; set; }
    // Old saved panels may still pass this parameter through hotload. It no longer renders progression.
    [Parameter] public bool ShowLevel { get; set; }
    [Parameter] public UniqueSkill HighlightSkill {get;set;}=UniqueSkill.None;
    [Parameter] public Action OnSelected { get; set; }
    [Parameter] public bool Draggable { get; set; }
    [Parameter] public Action OnDragStarted { get; set; }
    [Parameter] public Action OnDragMoved { get; set; }
    [Parameter] public Action OnDragEnded { get; set; }
    Button CardButton {get;set;}
    Panel SkillsStrip {get;set;}
    bool pressed,dragging,suppressClick;
    Vector2 down;
    void ClickCard(){SkillHoverOverlay.Forget(CardButton);if(suppressClick){suppressClick=false;return;}OnSelected?.Invoke();}
    void CardMouseDown(PanelEvent e)
    {
        if(e is not MousePanelEvent mouse || mouse.MouseButton!=MouseButtons.Left)return;
        SkillHoverOverlay.Interaction(CardButton,true);
        if(!Draggable)return;
        // The scrollable bench starts its own drag at 5 px and sends an early
        // mouseup, before our 8 px card drag can begin. Keep this gesture here.
        e.StopPropagation();
        pressed=true;dragging=false;suppressClick=false;down=CardButton.MousePosition;
        // UI already routes release to the pressed button. Mouse capture would
        // lock the OS cursor and return it to the press position after the drop.
        if(CardButton.HasMouseCapture)CardButton.SetMouseCapture(false);
    }
    void CardMouseUp(PanelEvent e)
    {
        if(e is not MousePanelEvent mouse || mouse.MouseButton!=MouseButtons.Left)return;
        SkillHoverOverlay.Interaction(CardButton,false);
        if(!pressed)return;
        if(dragging){OnDragEnded?.Invoke();suppressClick=true;}
        pressed=false;dragging=false;
        if(CardButton.HasMouseCapture)CardButton.SetMouseCapture(false);
    }
    public override void Tick()
    {
        base.Tick();
        if(CardButton!=null && !pressed && !dragging && !(CssClass??"").Contains("display-only"))
            SkillHoverOverlay.Track(CardButton,SkillsStrip);
        else SkillHoverOverlay.Leave(CardButton);
        if(!pressed)return;
        if(!Draggable || CardButton==null){if(dragging)OnDragEnded?.Invoke();pressed=false;dragging=false;SkillHoverOverlay.Interaction(CardButton,false);if(CardButton?.HasMouseCapture==true)CardButton.SetMouseCapture(false);return;}
        if(!dragging && (CardButton.MousePosition-down).Length>=8){dragging=true;OnDragStarted?.Invoke();}
        if(dragging)OnDragMoved?.Invoke();
    }
    public override void OnDeleted(){SkillHoverOverlay.Forget(CardButton);base.OnDeleted();}
    protected override int BuildHash()
    {
        if(Card==null)return 0;
        // Owned cards are mutated in place. The object reference alone cannot
        // invalidate this child after filling or replacing a skill slot.
        int skills=HashCode.Combine(Card.SkillRerolls,Card.SkillLoadout?.Count??0);
        if(Card.SkillLoadout!=null)
            foreach(var skill in Card.SkillLoadout)
                skills=HashCode.Combine(skills,skill?.Skill,skill?.Grade);
        var d=Card.Details;
        return HashCode.Combine(
            HashCode.Combine(Card.Id,Card.CardKey,Card.Name,Card.CountryCode,Card.Overall,Card.Position,Card.PreferredPosition,skills),
            HashCode.Combine(Card.Pace,Card.Shooting,Card.Passing,Card.Dribbling,Card.Defence,Card.Physical,Card.Endurance,Card.Keeping),
            HashCode.Combine(Card.Aggression,Card.Composure,Card.PortraitSkin,Card.PortraitHair,Card.PortraitStyle,Card.PortraitBeard,Card.HeightCm),
            HashCode.Combine(d?.GkDiving,d?.GkHandling,d?.GkKicking,d?.GkReflexes,d?.GkPositioning),
            HashCode.Combine(CssClass,InlineStyle,PortraitOverride,ClubSeed,Draggable,dragging,GamePreferences.Russian))^HashCode.Combine(HighlightSkill,Card.IsAcademyLoan);
    }
}