Source code for fmsave.models.players

"""Player records: identity, ability, reputation, club, positions, attributes and traits.

Person fields (name, birth date, nationality, home-grown ids, personality and traits) come
from each player's person block; they stay None or empty when no block validates. Adjusted
and raw attributes share one coverage row per attribute, so one Attributes class serves both
`attributes` (the 1 to 20 display scale) and `raw_attributes` (the 1 to 100 raw scale).
"""

from __future__ import annotations

from dataclasses import dataclass
from datetime import date
from enum import IntEnum

from fmsave._status import register_field_statuses
from fmsave.models.common import CodedValue, TransferValueState
from fmsave.models.contracts import Contract
from fmsave.models.suspensions import PlayerSuspension


[docs] @dataclass(frozen=True, slots=True) class Ability: """A player's current and potential ability. Attributes: current: Current ability (unconfirmed). potential: Potential ability, or None when the save stores a range code instead (unconfirmed). potential_range_code: The stored negative code when potential is unknown, else None (unconfirmed). """ current: int potential: int | None potential_range_code: int | None
[docs] @dataclass(frozen=True, slots=True) class Reputation: """A player's reputation figures. Never threshold on bucket: it is a coarse, lossy bucket that mis-orders players. Use current to rank players instead. Attributes: bucket: Coarse reputation bucket that loses precision (unconfirmed). home: Home reputation. current: Current reputation. world: World reputation. """ bucket: int home: int current: int world: int
[docs] @dataclass(frozen=True, slots=True) class Attributes: """52 non-foot player attributes, shared by the display and raw scales. Attributes: crossing: Crossing. dribbling: Dribbling. finishing: Finishing. heading: Heading. long_shots: Long shots. marking: Marking. off_the_ball: Off the ball movement. passing: Passing. penalty_taking: Penalty taking. tackling: Tackling. vision: Vision. handling: Goalkeeping handling. aerial_reach: Goalkeeping aerial reach. command_of_area: Goalkeeping command of area. communication: Goalkeeping communication. kicking: Goalkeeping kicking. throwing: Goalkeeping throwing. anticipation: Anticipation. decisions: Decisions. one_on_ones: Goalkeeping one-on-ones. positioning: Positioning (an attribute, not a position rating). reflexes: Goalkeeping reflexes. first_touch: First touch. technique: Technique. flair: Flair. corners: Corners. teamwork: Teamwork. work_rate: Work rate. long_throws: Long throws. eccentricity: Goalkeeping eccentricity. rushing_out: Goalkeeping tendency to rush out. punching: Goalkeeping tendency to punch crosses. acceleration: Acceleration. free_kick_taking: Free kick taking. strength: Strength. stamina: Stamina. pace: Pace. jumping_reach: Jumping reach. leadership: Leadership. dirtiness: Dirtiness, a hidden attribute. balance: Balance. bravery: Bravery. consistency: Consistency, a hidden attribute. aggression: Aggression. agility: Agility. important_matches: Big-match temperament, a hidden attribute. injury_proneness: Injury proneness, a hidden attribute. versatility: Versatility, a hidden attribute. natural_fitness: Natural fitness. determination: Determination. composure: Composure. concentration: Concentration. """ crossing: int dribbling: int finishing: int heading: int long_shots: int marking: int off_the_ball: int passing: int penalty_taking: int tackling: int vision: int handling: int aerial_reach: int command_of_area: int communication: int kicking: int throwing: int anticipation: int decisions: int one_on_ones: int positioning: int reflexes: int first_touch: int technique: int flair: int corners: int teamwork: int work_rate: int long_throws: int eccentricity: int rushing_out: int punching: int acceleration: int free_kick_taking: int strength: int stamina: int pace: int jumping_reach: int leadership: int dirtiness: int balance: int bravery: int consistency: int aggression: int agility: int important_matches: int injury_proneness: int versatility: int natural_fitness: int determination: int composure: int concentration: int
[docs] @dataclass(frozen=True, slots=True) class Positions: """The 15 position ratings, in the player rating array order. This is not the tactic or per-match position mask order. Attributes: gk: Goalkeeper. sw: Sweeper. dl: Left back. dc: Centre back. dr: Right back. dm: Defensive midfielder. ml: Left midfielder. mc: Central midfielder. mr: Right midfielder. aml: Attacking midfielder, left. amc: Attacking midfielder, centre. amr: Attacking midfielder, right. stc: Striker. wbl: Left wing back. wbr: Right wing back. """ gk: int sw: int dl: int dc: int dr: int dm: int ml: int mc: int mr: int aml: int amc: int amr: int stc: int wbl: int wbr: int
[docs] @dataclass(frozen=True, slots=True) class Personality: """A player's personality profile. Attributes: adaptability: Adaptability. ambition: Ambition. loyalty: Loyalty. pressure: Pressure. professionalism: Professionalism. sportsmanship: Sportsmanship. temperament: Temperament. controversy: Controversy. """ adaptability: int ambition: int loyalty: int pressure: int professionalism: int sportsmanship: int temperament: int controversy: int
[docs] class Trait(IntEnum): """A named player trait; UNKNOWN keeps the bit number in CodedValue.raw. A bit is named only where a trait the game itself displayed pins that exact bit. Two readings do that. Six bits come from player profiles, which list a player's traits in ascending bit order, and that order is what ties a displayed trait to a bit. The other ten come from in-game trait panels read on 2026-09-16, one player per bit, each player's bitmask holding exactly one bit no earlier reading had reached, so the one unaccounted trait his panel showed is that bit. A name an outside name table would supply is not evidence for one, so every bit no displayed trait reaches is UNKNOWN and keeps its bit number. Neither is a name that merely looks likely from the bits around it, even though two such guesses were later borne out by a panel: what named those bits is the panel, not the guess. """ UNKNOWN = -1 RUNS_WITH_BALL_DOWN_LEFT = 0 RUNS_WITH_BALL_DOWN_RIGHT = 1 RUNS_WITH_BALL_THROUGH_CENTRE = 2 MOVES_INTO_CHANNELS = 4 GETS_FORWARD_WHENEVER_POSSIBLE = 5 TRIES_KILLER_BALLS_OFTEN = 7 SHOOTS_FROM_DISTANCE = 8 LIKES_TO_TRY_TO_BEAT_OFFSIDE_TRAP = 13 COMES_DEEP_TO_GET_BALL = 19 DICTATES_TEMPO = 22 KNOCKS_BALL_PAST_OPPONENT = 27 AVOIDS_USING_WEAKER_FOOT = 34 DIVES_INTO_TACKLES = 37 TRIES_LONG_RANGE_PASSES = 43 RUNS_WITH_BALL_OFTEN = 51 CROSSES_EARLY = 59
[docs] @dataclass(frozen=True, slots=True) class Player: """A player record from the save's game database. Person fields (name, birth date, nationality, home-grown ids, personality and traits) come from the player's person block; they stay None or empty when no block validates. Attributes: uid: The player's id in the game database (unconfirmed). name: Display name: common name first, else first plus last name, else legal name (unconfirmed). first_name: First name (unconfirmed). last_name: Last name (unconfirmed). common_name: Common (nickname) name, when the save stores one (unconfirmed). full_name: First name plus last name, or None when either is missing (unconfirmed). legal_name: The save's stored legal name, or None when the save stores none (unconfirmed). birth_date: Date of birth (unconfirmed). age: Age at the save's in-game clock date, or None when birth_date or the clock date is unreadable (unconfirmed). nation_id: Id of the player's primary nation (unconfirmed). second_nation_ids: Ids of the player's other eligible nations. home_grown_nation_ids: Ids of nations the player is considered home grown for. home_grown_club_uids: Uids of clubs the player is considered home grown for, in relation-list order; an entry is None when its club index does not resolve, but still keeps its position. They are Club.uid values and carry its status (unconfirmed). home_grown_club_names: Denormalised names for home_grown_club_uids, in the same order; None wherever home_grown_club_uids is None (unconfirmed). height_cm: Height in centimetres (unconfirmed). ability: Current and potential ability. reputation: Reputation figures; never threshold on reputation.bucket. club_uid: Uid of the club the player belongs to: the club fielding his registered team, which for a team an affiliate club stores is the club controlling it. None for a free agent or an unresolved team (unconfirmed). club_name: Denormalised full name of club_uid (unconfirmed). club_short_name: Denormalised short name of club_uid (unconfirmed). club_nation_id: Denormalised league nation id of club_uid (unconfirmed). club_fa_nation_id: Denormalised FA nation id of club_uid (unconfirmed). club_reputation: Denormalised reputation of club_uid. club_last_league_position: Denormalised last completed season league position of club_uid. team_id: Id of the player's registered team, or None for a free agent (unconfirmed). team_slot: The team's slot in club_uid's team list, which counts that club's own slots first and then the teams it controls at affiliate clubs; None when team_id does not resolve to a club (unconfirmed). team_club_uid: Uid of the club whose record stores the player's registered team, when another club controls that team, else None (unconfirmed). team_club_name: Denormalised full name of team_club_uid; None wherever team_club_uid is (unconfirmed). club_join_date: Date the player joined his current club; it disagrees with the contract start date for about half of players, so it is not a substitute for it (unconfirmed). natural_positions: Position codes rated at least 18, best first. accomplished_positions: Position codes rated 15 to 17, best first. This is an fmsave classifier label, not text the game itself shows. personality: Personality profile, or None when no person block validates. attributes: The 52 non-foot attributes, on the 1 to 20 display scale. raw_attributes: The same 52 attributes, on the 1 to 100 raw scale. left_foot: Left foot strength, on the 1 to 20 display scale. right_foot: Right foot strength, on the 1 to 20 display scale. raw_left_foot: Left foot strength, on the 1 to 100 raw scale. raw_right_foot: Right foot strength, on the 1 to 100 raw scale. positions: The 15 position ratings. transfer_value: Transfer value in the save's base currency, set only when transfer_value_state is OK. transfer_value_state: How transfer_value was read from the save (unconfirmed). raw_condition: Condition, on the 0 to 10000 scale the save stores it on. The game shows condition as a percentage, which is this divided by 100. raw_match_sharpness: Match sharpness, on the 0 to 10000 scale the save stores it on. The game shows it as a percentage, which is this divided by 100. traits: Named player traits; an unnamed bit is Trait.UNKNOWN with raw set to the bit number. trait_bits: The raw trait bitmask, or None when no person block validates. on_loan: Whether the player is on loan from the club of his contract in effect: True when he is registered with another club's team and the save holds a loan for him there that has not ended, False when it does not, and None when his club or his contract in effect is unknown. A player registered with a team his own club controls, such as a B team, is a player of that club, not on loan. loan_parent_club_uid: Uid of the club of the contract in effect when on_loan is True, else None. It is the contract's club_uid under another name, so it carries the same status (unconfirmed). loan_parent_club_name: Denormalised name of loan_parent_club_uid (unconfirmed). loan_start: Date the loan began when on_loan is True, else None. It is also None when the loan's stored start does not decode as a game date, which keeps an unreadable start from costing the player his loan; no save read so far holds such a loan. loan_end: Date the loan ends when on_loan is True, else None. contract: The player's assembled contract, or None when no chain record and no fallback dates were found. suspensions: The player's unserved suspensions, in the order the save stores them, including bans the game no longer displays; () when the player has none. """ uid: int name: str | None first_name: str | None last_name: str | None common_name: str | None full_name: str | None legal_name: str | None birth_date: date | None age: int | None nation_id: int | None second_nation_ids: tuple[int, ...] home_grown_nation_ids: tuple[int, ...] home_grown_club_uids: tuple[int | None, ...] home_grown_club_names: tuple[str | None, ...] height_cm: int ability: Ability reputation: Reputation club_uid: int | None club_name: str | None club_short_name: str | None club_nation_id: int | None club_fa_nation_id: int | None club_reputation: int | None club_last_league_position: int | None team_id: int | None team_slot: int | None team_club_uid: int | None team_club_name: str | None club_join_date: date | None natural_positions: tuple[str, ...] accomplished_positions: tuple[str, ...] personality: Personality | None attributes: Attributes raw_attributes: Attributes left_foot: int right_foot: int raw_left_foot: int raw_right_foot: int positions: Positions transfer_value: int | None transfer_value_state: TransferValueState raw_condition: int raw_match_sharpness: int traits: tuple[CodedValue[Trait], ...] trait_bits: int | None on_loan: bool | None loan_parent_club_uid: int | None loan_parent_club_name: str | None loan_start: date | None loan_end: date | None contract: Contract | None suspensions: tuple[PlayerSuspension, ...]
register_field_statuses(Ability, unconfirmed=("current", "potential", "potential_range_code")) register_field_statuses(Reputation, verified=("home", "current", "world"), unconfirmed=("bucket",)) register_field_statuses( Attributes, verified=( "crossing", "dribbling", "finishing", "heading", "long_shots", "marking", "off_the_ball", "passing", "penalty_taking", "tackling", "vision", "handling", "aerial_reach", "command_of_area", "communication", "kicking", "throwing", "anticipation", "decisions", "one_on_ones", "positioning", "reflexes", "first_touch", "technique", "flair", "corners", "teamwork", "work_rate", "long_throws", "eccentricity", "rushing_out", "punching", "acceleration", "free_kick_taking", "strength", "stamina", "pace", "jumping_reach", "leadership", "dirtiness", "balance", "bravery", "consistency", "aggression", "agility", "important_matches", "injury_proneness", "versatility", "natural_fitness", "determination", "composure", "concentration", ), ) register_field_statuses( Positions, verified=( "gk", "sw", "dl", "dc", "dr", "dm", "ml", "mc", "mr", "aml", "amc", "amr", "stc", "wbl", "wbr", ), ) register_field_statuses( Personality, verified=( "adaptability", "ambition", "loyalty", "pressure", "professionalism", "sportsmanship", "temperament", "controversy", ), ) # A field taken from another record carries that record's status: the home-grown club uids and # names are Club.uid and Club.name, and the loan parent club is the contract's club. register_field_statuses( Player, verified=( "second_nation_ids", "home_grown_nation_ids", "club_reputation", "club_last_league_position", "natural_positions", "accomplished_positions", "left_foot", "right_foot", "raw_left_foot", "raw_right_foot", "transfer_value", "raw_condition", "raw_match_sharpness", "traits", "trait_bits", "on_loan", "loan_start", "loan_end", "suspensions", ), unconfirmed=( "uid", "name", "first_name", "last_name", "common_name", "full_name", "legal_name", "birth_date", "age", "nation_id", "height_cm", "club_uid", "club_name", "club_short_name", "club_nation_id", "club_fa_nation_id", "team_id", "team_slot", "team_club_uid", "team_club_name", "club_join_date", "transfer_value_state", "home_grown_club_uids", "home_grown_club_names", "loan_parent_club_uid", "loan_parent_club_name", ), )