Partial ClubCareer class for a football career mode. Stores league season history, club achievements and per-season challenges, and resolves/unlocks them from completed league and cup results, including claiming coin and training card rewards.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
namespace Socer;
public sealed class LeagueSeasonResult
{
public int Season {get;set;}
public int Division {get;set;}
public int Place {get;set;}
public int Played {get;set;}
public int Points {get;set;}
public int Wins {get;set;}
public int Draws {get;set;}
public int Losses {get;set;}
public int GoalsFor {get;set;}
public int GoalsAgainst {get;set;}
}
public sealed class ClubAchievement
{
public string Id {get;set;}="";
public int EarnedSeason {get;set;}
public bool Claimed {get;set;}
public int CoinReward {get;set;}
public int TrainingGrade {get;set;}
public int TrainingCount {get;set;}
[JsonIgnore] public bool RewardAvailable=>!Claimed && (CoinReward>0 || TrainingCount>0);
}
public sealed class CareerSeasonChallenge
{
public string Id {get;set;}="";
public int Season {get;set;}
public string Objective {get;set;}="";
public int Target {get;set;}
public int Progress {get;set;}
public bool Resolved {get;set;}
public bool Earned {get;set;}
public bool Claimed {get;set;}
public int CoinReward {get;set;}=400;
public int TrainingGrade {get;set;}=2;
public int TrainingCount {get;set;}=1;
}
public sealed partial class ClubCareer
{
public int AchievementVersion {get;set;}
public List<LeagueSeasonResult> LeagueHistory {get;set;}=new();
public List<ClubAchievement> Achievements {get;set;}=new();
public List<CareerSeasonChallenge> SeasonChallenges {get;set;}=new();
[JsonIgnore] public bool PremierChampion=>PremierTitleCount>0;
[JsonIgnore] public int FirstPremierTitleSeason=>(LeagueHistory??new()).Where(r=>r.Division==5 && r.Place==1).Select(r=>r.Season).DefaultIfEmpty().Min();
[JsonIgnore] public int PremierTitleCount=>(LeagueHistory??new()).Count(r=>r.Division==5 && r.Place==1);
[JsonIgnore] public CareerSeasonChallenge CurrentSeasonChallenge=>SeasonChallenges?.FirstOrDefault(c=>c.Season==Season);
[JsonIgnore] public int ClaimableCareerRewards=>(Achievements??new()).Count(a=>a.RewardAvailable)+(SeasonChallenges??new()).Count(c=>c.Earned && !c.Claimed);
// The complete current table is evidence. Season number, division, coins or
// cumulative wins cannot prove old titles and never create historical ones.
public void NormalizeCareerAchievements()
{
LeagueHistory??=new();Achievements??=new();SeasonChallenges??=new();
LeagueHistory=LeagueHistory.Where(r=>r!=null && r.Season>0 && r.Division is >=1 and <=5 && r.Place is >=1 and <=8 && r.Played>0)
.GroupBy(r=>r.Season).Select(g=>g.First()).OrderBy(r=>r.Season).ToList();
Achievements=Achievements.Where(a=>a!=null && !string.IsNullOrWhiteSpace(a.Id)).GroupBy(a=>a.Id).Select(g=>g.First()).ToList();
SeasonChallenges=SeasonChallenges.Where(c=>c!=null && c.Season>0 && !string.IsNullOrWhiteSpace(c.Id)).GroupBy(c=>c.Season).Select(g=>g.First()).ToList();
AchievementVersion=1;
ResolveCareerAchievements();
EnsureSeasonChallenge();
}
public void ResolveCareerAchievements()
{
LeagueHistory??=new();Achievements??=new();SeasonChallenges??=new();
if(LeagueComplete && !LeagueHistory.Any(r=>r.Season==Season))
{
var table=Standings();var own=table.First(r=>r.Team==0);
LeagueHistory.Add(new(){Season=Season,Division=Division,Place=table.FindIndex(r=>r.Team==0)+1,
Played=own.Played,Points=own.Points,Wins=own.Wins,Draws=own.Draws,Losses=own.Losses,GoalsFor=own.For,GoalsAgainst=own.Against});
}
foreach(var result in LeagueHistory.Where(r=>r.Place==1).OrderBy(r=>r.Season))
{
UnlockAchievement("first-league-title",result.Season);
if(result.Division!=5)continue;
UnlockAchievement("premier-champion",result.Season,1000,3,1);
if(result.Losses==0)UnlockAchievement("unbeaten-champion",result.Season);
if(CupResults.Any(c=>c.Season==result.Season && c.PlaceFrom==1 && c.PlaceTo==1))UnlockAchievement("domestic-double",result.Season);
}
foreach(var cup in CupResults.Where(c=>c.PlaceFrom==1 && c.PlaceTo==1).OrderBy(c=>c.Season))UnlockAchievement("cup-winner",cup.Season);
ResolveSeasonChallenge();
}
void UnlockAchievement(string id,int season,int coins=0,int grade=0,int training=0)
{
if(Achievements.Any(a=>a.Id==id))return;
Achievements.Add(new(){Id=id,EarnedSeason=season,CoinReward=coins,TrainingGrade=grade,TrainingCount=training,Claimed=coins==0 && training==0});
}
void EnsureSeasonChallenge()
{
if(!PremierChampion || Season<=FirstPremierTitleSeason || CurrentSeasonChallenge!=null)return;
// A new challenge starts with a new season. Do not backfill a loaded
// partially played season with retroactive progress or extra prizes.
if(PlayedLeagueRounds>0 || Tournaments.Any(t=>t.Started || t.Finished))return;
string[] objectives={"defend-title","strong-season","double","clean-sheets"};
string objective=Division<5?(Division==4?"return-premier":"strong-season"):objectives[(int)((PlayerCatalog.Hash($"champion-{ClubSeed}")+(uint)Season)%4)];
int games=League.Count(f=>f.Home==0 || f.Away==0);
int target=objective=="strong-season"?(int)Math.Ceiling(games*3*.72):objective=="clean-sheets"?Math.Max(1,(int)Math.Ceiling(games*.4)):1;
SeasonChallenges.Add(new(){Id=$"season-{Season}-{objective}",Season=Season,Objective=objective,Target=target});
}
void ResolveSeasonChallenge()
{
var challenge=CurrentSeasonChallenge;if(challenge==null || challenge.Resolved)return;
var own=Standings().First(r=>r.Team==0);
var result=LeagueHistory.FirstOrDefault(r=>r.Season==Season);
bool champion=result?.Division==5 && result.Place==1;
var games=League.Where(f=>f.Played && (f.Home==0 || f.Away==0));
challenge.Progress=challenge.Objective switch
{
"defend-title"=>champion?1:0,
"double"=>champion && CupResults.Any(c=>c.Season==Season && c.PlaceFrom==1 && c.PlaceTo==1)?1:0,
"return-premier"=>result?.Division==4 && result.Place<=2?1:0,
"strong-season"=>Math.Min(challenge.Target,own.Points),
"clean-sheets"=>Math.Min(challenge.Target,games.Count(f=>(f.Home==0?f.AwayGoals:f.HomeGoals)==0)),
_=>0
};
if(challenge.Progress>=challenge.Target){challenge.Earned=true;challenge.Resolved=true;}
else if(SeasonComplete)challenge.Resolved=true;
}
public bool ClaimCareerReward(string id)
{
if(Pending!=null || string.IsNullOrWhiteSpace(id))return false;
var achievement=Achievements?.FirstOrDefault(a=>a.Id==id);
var challenge=SeasonChallenges?.FirstOrDefault(c=>c.Id==id);
int coins,grade,count;
if(achievement?.RewardAvailable==true)
{
coins=achievement.CoinReward;grade=achievement.TrainingGrade;count=achievement.TrainingCount;
}
else if(challenge is {Earned:true,Claimed:false})
{
coins=challenge.CoinReward;grade=challenge.TrainingGrade;count=challenge.TrainingCount;
}
else return false;
if(!CanReceiveTrainingCards(count))return false;
if(achievement?.RewardAvailable==true)achievement.Claimed=true;else challenge.Claimed=true;
Coins=(int)Math.Min(int.MaxValue,(long)Coins+Math.Max(0,coins));
TrainingInventory??=new();NextTrainingCardId=Math.Max(NextTrainingCardId,TrainingInventory.Select(c=>c.Id).DefaultIfEmpty().Max()+1);
for(int i=0;i<count;i++)TrainingInventory.Add(new(){Id=NextTrainingCardId++,Grade=Math.Clamp(grade,1,3),Focus=0,Xp=new[]{45,90,150}[Math.Clamp(grade,1,3)-1]});
return true;
}
}