PlayerCatalogExpansion.cs

Static catalog data for the soccer game's player cards. Defines curated ratings, ages, heights, preferred foot and portrait values keyed by player name, and appends a batch of professional players (ids 25-73) to the catalog with generated details.

Rough Code
using System;
using System.Collections.Generic;
using System.Linq;
namespace Socer;

public static partial class PlayerCatalog
{
    // Original approximate game profiles informed by EA's role/stat structure.
    // These values are deliberately authored rather than represented as a live licensed database.
    static Dictionary<string,string> faceData;
    static Dictionary<string,string> Faces=>faceData??=new(StringComparer.Ordinal){
        ["Gianluigi Donnarumma"]="90,84,73,91,52,87|27|196|Right",
        ["Alisson Becker"]="86,85,85,87,56,89|33|193|Right",
        ["Achraf Hakimi"]="93,76,82,82,79,78|27|181|Right",
        ["Trent Alexander-Arnold"]="75,72,90,80,78,73|27|175|Right",
        ["Virgil van Dijk"]="77,60,71,72,90,86|35|193|Right",
        ["Antonio Rüdiger"]="81,54,68,65,87,86|33|190|Right",
        ["Jude Bellingham"]="80,86,83,88,78,84|23|186|Right",
        ["Rodri"]="66,80,86,84,87,85|30|191|Right",
        ["Mohamed Salah"]="89,88,83,88,45,76|34|175|Left",
        ["Bukayo Saka"]="85,83,83,88,60,73|25|178|Left",
        ["Harry Kane"]="64,91,84,83,49,82|33|188|Right",
        ["Erling Haaland"]="87,92,71,80,47,89|26|195|Left",
        ["Thibaut Courtois"]="89,89,76,90,46,89|34|200|Left",
        ["Manuel Neuer"]="83,80,88,82,52,85|40|193|Right",
        ["Theo Hernández"]="91,71,79,82,78,83|28|184|Left",
        ["Alphonso Davies"]="95,65,77,84,75,74|25|185|Left",
        ["Rúben Dias"]="62,39,66,68,89,87|29|187|Right",
        ["William Saliba"]="79,39,70,72,88,83|25|192|Right",
        ["Kevin De Bruyne"]="67,86,92,86,63,75|35|181|Right",
        ["Pedri"]="77,70,86,90,67,64|23|174|Right",
        ["Vinícius Júnior"]="95,84,81,91,29,69|26|176|Right",
        ["Lamine Yamal"]="85,80,83,90,23,52|19|180|Left",
        ["Lionel Messi"]="76,86,90,91,33,63|39|170|Left",
        ["Kylian Mbappé"]="96,91,80,92,29,76|27|178|Right",
        ["Jan Oblak"]="84,88,74,85,43,87|33|188|Right",
        ["Marc-André ter Stegen"]="86,84,89,85,47,85|34|187|Right",
        ["Emiliano Martínez"]="83,84,76,85,45,84|34|195|Right",
        ["David Raya"]="82,81,88,84,50,82|31|183|Right",
        ["Diogo Costa"]="82,80,83,85,51,81|27|186|Right",
        ["Unai Simón"]="81,83,78,83,48,83|29|190|Right",
        ["Andy Robertson"]="77,58,81,77,81,75|32|178|Left",
        ["Federico Dimarco"]="79,75,86,80,76,74|28|175|Left",
        ["Jeremie Frimpong"]="94,67,76,84,72,61|25|171|Right",
        ["Dani Carvajal"]="77,56,75,77,83,80|34|173|Right",
        ["Gabriel Magalhães"]="68,38,61,58,85,85|28|190|Left",
        ["Alessandro Bastoni"]="73,43,77,73,86,82|27|190|Left",
        ["Marquinhos"]="75,54,74,70,87,79|32|183|Right",
        ["Matthijs de Ligt"]="64,51,64,60,83,86|27|189|Right",
        ["Nico Schlotterbeck"]="75,55,71,65,82,82|26|191|Left",
        ["Declan Rice"]="71,65,77,77,85,85|27|188|Right",
        ["Federico Valverde"]="89,81,84,81,78,83|28|182|Right",
        ["Bruno Fernandes"]="70,83,89,82,67,71|32|179|Right",
        ["Martin Ødegaard"]="70,78,89,89,63,65|27|178|Left",
        ["Florian Wirtz"]="81,80,86,89,50,60|23|177|Right",
        ["Jamal Musiala"]="84,81,80,92,58,64|23|184|Right",
        ["Cole Palmer"]="75,85,83,86,45,63|24|189|Left",
        ["Raphinha"]="89,83,82,86,52,68|29|176|Left",
        ["Nico Williams"]="93,76,78,85,37,64|24|181|Right",
        ["Ousmane Dembélé"]="92,84,84,90,36,59|29|178|Left",
        ["Lautaro Martínez"]="81,87,75,85,48,85|29|174|Right",
        ["Alexander Isak"]="85,85,72,84,35,77|27|192|Right",
        ["Victor Osimhen"]="87,84,65,77,41,82|27|186|Right",
        ["Cristiano Ronaldo"]="77,87,75,80,34,77|41|187|Right",
        // Famous expansion: authored football archetypes on the same 99-point
        // game scale. Identity/role references are recorded in Art/Portraits/
        // famous-expansion-v3.json; these are not EA or live-season ratings.
        ["Ederson"]="85,82,93,86,64,84|33|188|Left",
        ["Sergio Ramos"]="70,68,72,66,86,85|40|184|Right",
        ["Jules Koundé"]="82,44,76,78,86,80|27|181|Right",
        ["João Cancelo"]="84,70,87,87,82,72|32|182|Right",
        ["Joshua Kimmich"]="64,70,90,83,85,76|31|177|Right",
        ["Luka Modrić"]="70,74,94,91,68,61|41|172|Right",
        ["N'Golo Kanté"]="78,56,80,80,90,78|35|168|Right",
        ["Frenkie de Jong"]="78,69,87,87,77,77|29|181|Right",
        ["Neymar"]="85,83,86,94,36,59|34|175|Right",
        ["Son Heung-min"]="88,89,81,86,43,71|34|183|Right",
        ["Rafael Leão"]="94,82,78,87,31,74|27|188|Right",
        ["Bernardo Silva"]="73,76,88,92,60,62|32|171|Left",
        ["Robert Lewandowski"]="70,91,77,82,44,83|38|185|Right",
        ["Karim Benzema"]="72,89,85,86,38,79|38|185|Right",
        ["Luis Suárez"]="68,88,79,81,45,80|39|175|Right",
        ["Antoine Griezmann"]="77,87,86,87,58,69|35|176|Left",
    };
    static void CuratedFace(PlayerCard card)
    {
        if(!Faces.TryGetValue(card.Name,out string data))return;
        var pieces=data.Split('|');var a=pieces[0].Split(',').Select(int.Parse).ToArray();
        card.Age=int.Parse(pieces[1]);card.HeightCm=int.Parse(pieces[2]);card.Foot=pieces[3];
        if(PlayerPositions.Card(card)=="GK")
        {
            card.Pace=a[4];card.Keeping=(int)Math.Round((a[0]+a[1]+a[3]+a[5])/4d);
            card.Passing=a[2];card.Shooting=14;card.Dribbling=29;card.Defence=19;card.Physical=68;card.Endurance=47;
        }
        else
        {
            card.Pace=a[0];card.Shooting=a[1];card.Passing=a[2];card.Dribbling=a[3];card.Defence=a[4];card.Physical=a[5];
            card.Endurance=Math.Clamp(card.Physical+8,45,94);card.Keeping=12;
        }
        card.Composure=Math.Clamp(card.Overall+2,60,95);card.Aggression=Math.Clamp(card.Physical-1,40,92);
    }
    public static void KeeperReference(PlayerCard card,PlayerDetailStats stats)
    {
        if(PlayerPositions.Card(card)!="GK" || !Faces.TryGetValue(card.Name,out string data))return;
        var a=data.Split('|')[0].Split(',').Select(int.Parse).ToArray();
        var original=Find(card.CatalogId);
        int development=original==null?0:card.Keeping-original.Keeping;
        int C(int n)=>Math.Clamp(n+development,1,99);
        stats.GkDiving=C(a[0]);stats.GkHandling=C(a[1]);stats.GkKicking=Math.Clamp(a[2]+(original==null?0:card.Passing-original.Passing),1,99);
        stats.GkReflexes=C(a[3]);stats.GkPositioning=C(a[5]);
    }
    static void AddMoreProfessionals(List<PlayerCard> result)
    {
        Add("Jan Oblak","Slovenia","SI","GK",25);
        Add("Marc-André ter Stegen","Germany","DE","GK",26);
        Add("Emiliano Martínez","Argentina","AR","GK",27);
        Add("David Raya","Spain","ES","GK",28);
        Add("Diogo Costa","Portugal","PT","GK",29);
        Add("Unai Simón","Spain","ES","GK",30);
        Add("Andy Robertson","Scotland","SCT","LB",31);
        Add("Federico Dimarco","Italy","IT","LB",32);
        Add("Jeremie Frimpong","Netherlands","NL","RB",33);
        Add("Dani Carvajal","Spain","ES","RB",34);
        Add("Gabriel Magalhães","Brazil","BR","CB",35);
        Add("Alessandro Bastoni","Italy","IT","CB",36);
        Add("Marquinhos","Brazil","BR","CB",37);
        Add("Matthijs de Ligt","Netherlands","NL","CB",38);
        Add("Nico Schlotterbeck","Germany","DE","CB",39);
        Add("Declan Rice","England","GB","CDM",40);
        Add("Federico Valverde","Uruguay","UY","CM",41);
        Add("Bruno Fernandes","Portugal","PT","CAM",42);
        Add("Martin Ødegaard","Norway","NO","CAM",43);
        Add("Florian Wirtz","Germany","DE","CAM",44);
        Add("Jamal Musiala","Germany","DE","CAM",45);
        Add("Cole Palmer","England","GB","CAM",46);
        Add("Raphinha","Brazil","BR","RW",47);
        Add("Nico Williams","Spain","ES","LW",48);
        Add("Ousmane Dembélé","France","FR","RW",49);
        Add("Lautaro Martínez","Argentina","AR","ST",50);
        Add("Alexander Isak","Sweden","SE","ST",51);
        Add("Victor Osimhen","Nigeria","NG","ST",52);
        Add("Cristiano Ronaldo","Portugal","PT","ST",53);
        // 54-57 are the historical sources of the existing red editions.
        // Append normal identities without renumbering any owned player.
        Add("Ederson","Brazil","BR","GK",58);
        Add("Sergio Ramos","Spain","ES","CB",59);
        Add("Jules Koundé","France","FR","RB",60);
        Add("João Cancelo","Portugal","PT","LB",61);
        Add("Joshua Kimmich","Germany","DE","CDM",62);
        Add("Luka Modrić","Croatia","HR","CM",63);
        Add("N'Golo Kanté","France","FR","CDM",64);
        Add("Frenkie de Jong","Netherlands","NL","CM",65);
        Add("Neymar","Brazil","BR","LW",66);
        Add("Son Heung-min","South Korea","KR","LW",67);
        Add("Rafael Leão","Portugal","PT","LW",68);
        Add("Bernardo Silva","Portugal","PT","CAM",69);
        Add("Robert Lewandowski","Poland","PL","ST",70);
        Add("Karim Benzema","France","FR","ST",71);
        Add("Luis Suárez","Uruguay","UY","ST",72);
        Add("Antoine Griezmann","France","FR","ST",73);
        void Add(string name,string country,string code,string pos,int id)
        {
            var card=new PlayerCard{CatalogId=$"PRO-{id:00}",Name=name,Country=country,CountryCode=code,
                Position=pos=="GK"?"ВР":pos=="CB"?"ЦЗ":pos is "LB" or "RB"?"КЗ":pos is "LW" or "RW"?"КП":pos=="ST"?"НП":"ЦП",
                PreferredPosition=pos,Pace=70,Shooting=70,Passing=70,Dribbling=70,Defence=60,Physical=70,Keeping=12,
                PortraitSkin=2,PortraitHair=0,PortraitStyle=id%5,PortraitBeard=2,WeakFoot=3,SkillMoves=pos=="GK"?1:pos is "LW" or "RW" or "CAM"?4:2};
            CuratedFace(card);
            if(id==53){card.WeakFoot=4;card.SkillMoves=5;card.PortraitSkin=1;}
            if(id>=58)FamousPlayerIdentity(card,id);
            CardDevelopment.Initialize(card);card.Details=PlayerCardDetails.Generate(card);result.Add(card);
        }
    }
    static void FamousPlayerIdentity(PlayerCard card,int id)
    {
        // Stamina, temperament and technique retain different play patterns:
        // ball-winners can repeat recoveries, creators control rather than race,
        // and older finishers rely on position and composure.
        (card.Endurance,card.Aggression,card.Composure,card.WeakFoot,card.SkillMoves)=id switch
        {
            58=>(55,49,90,3,1),59=>(76,92,87,3,2),60=>(86,79,85,3,3),61=>(81,67,86,4,4),
            62=>(88,78,89,4,3),63=>(70,56,93,4,4),64=>(92,81,84,3,2),65=>(84,66,88,4,4),
            66=>(68,58,90,5,5),67=>(84,54,88,5,4),68=>(78,45,81,4,4),69=>(91,60,90,3,4),
            70=>(76,72,93,4,3),71=>(72,58,93,4,4),72=>(69,86,89,4,3),73=>(85,68,90,3,4),
            _=>(card.Endurance,card.Aggression,card.Composure,card.WeakFoot,card.SkillMoves)
        };
        (card.PortraitSkin,card.PortraitHair,card.PortraitBeard)=id switch
        {
            58=>(2,0,2),59=>(1,1,0),60=>(4,0,2),61=>(2,0,0),62=>(0,3,2),63=>(1,1,2),
            64=>(5,0,2),65=>(0,3,2),66=>(2,0,0),67=>(1,0,2),68=>(4,0,2),69=>(1,1,0),
            70=>(0,0,2),71=>(2,0,0),72=>(2,0,0),73=>(0,1,0),_ => (2,0,2)
        };
        card.Flag=card.CountryCode switch {
            "BR"=>"🇧🇷","ES"=>"🇪🇸","FR"=>"🇫🇷","PT"=>"🇵🇹","DE"=>"🇩🇪","NL"=>"🇳🇱",
            "HR"=>"🇭🇷","KR"=>"🇰🇷","PL"=>"🇵🇱","UY"=>"🇺🇾",_=>card.Flag};
    }
}