770029f207
Two defects that together made the club's 17 owned consumables invisible while club/stats still counted them — the count gate promised 17, the item route served 0. 1. The catalog omitted `card_asset_id`, `amount`, `contract` and `rating` for non-player definitions. Without an art id the adapter refuses to emit the card (it would draw the notfound box), and the families that read `amount`/`contract` would render "-1" or grant nothing. All four values are present in the source wire and were simply dropped on the way out. 2. Owned rows were imported without a `content_kind`, and Core defaults an unstated row to `player` — durably recording a fitness coach and a contract card as players in the ownership authority, even though the catalog-driven wire looked right. These are definition-level fields, so every owned copy must agree; a group that disagrees is deferred rather than resolved by taking the first copy's value. Measured on the real profile: observed `amount` equals the `fcc_*` table row for every consumable that carries one (1, 2, 4, 5, 10, 15), and a wire omission corresponds to a table amount of 0. It is therefore NOT a stack count — two copies of 5003068 are two instances — so the import states no `quantity` at all.
122 lines
4.0 KiB
Rust
122 lines
4.0 KiB
Rust
//! Serde model of the real FIFA 17 Python profile (`fifa17_profile.json`).
|
|
//! Only the fields the importer reasons about are modelled; every field is
|
|
//! permissive (`Option`/`default`) because the source is external and unknown
|
|
//! fields are ignored. Nothing here is written back.
|
|
|
|
use anyhow::{Context, Result};
|
|
use serde::Deserialize;
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
pub struct Profile {
|
|
#[serde(rename = "personaId", default)]
|
|
pub persona_id: i64,
|
|
#[serde(rename = "personaName", default)]
|
|
pub persona_name: String,
|
|
#[serde(rename = "clubName", default)]
|
|
pub club_name: String,
|
|
#[serde(rename = "clubAbbr", default)]
|
|
pub club_abbr: String,
|
|
#[serde(default)]
|
|
pub coins: i64,
|
|
/// Next wire id to issue (issue-then-increment: `fut_store.py` assigns this
|
|
/// value then increments). The allocation watermark to preserve.
|
|
#[serde(rename = "nextItemId", default)]
|
|
pub next_item_id: i64,
|
|
#[serde(default)]
|
|
pub items: Vec<Item>,
|
|
#[serde(default)]
|
|
pub squads: Vec<Squad>,
|
|
/// Unconsumed pack entitlements (`unopenedPackIds`), seeded into Core as
|
|
/// generic entitlements on import.
|
|
#[serde(rename = "unopenedPackIds", default)]
|
|
pub unopened_pack_ids: Vec<i64>,
|
|
}
|
|
|
|
impl Profile {
|
|
pub fn from_json_str(raw: &str) -> Result<Self> {
|
|
serde_json::from_str(raw).context("parsing fifa17_profile.json")
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
pub struct Item {
|
|
/// The per-INSTANCE wire id (preserved on import).
|
|
pub id: i64,
|
|
/// The per-DEFINITION versioned id: `resourceId = (version << 24) | assetId`.
|
|
#[serde(rename = "resourceId", default)]
|
|
pub resource_id: i64,
|
|
#[serde(rename = "assetId", default)]
|
|
pub asset_id: Option<i64>,
|
|
#[serde(rename = "itemType", default)]
|
|
pub item_type: String,
|
|
#[serde(default)]
|
|
pub rareflag: Option<i64>,
|
|
#[serde(default)]
|
|
pub rating: Option<i64>,
|
|
#[serde(rename = "preferredPosition", default)]
|
|
pub preferred_position: Option<String>,
|
|
#[serde(default)]
|
|
pub nation: Option<i64>,
|
|
#[serde(default)]
|
|
pub teamid: Option<i64>,
|
|
#[serde(rename = "leagueId", default)]
|
|
pub league_id: Option<i64>,
|
|
#[serde(rename = "attributeList", default)]
|
|
pub attribute_list: Option<Vec<Attr>>,
|
|
/// FIFA `cardsubtypeid` — the consumable family / staff role selector. Absent
|
|
/// for player cards; present for consumables and staff.
|
|
#[serde(default)]
|
|
pub cardsubtypeid: Option<i64>,
|
|
/// Consumable ART id (small id), distinct from `resourceId`. Permissive.
|
|
#[serde(default)]
|
|
pub cardassetid: Option<i64>,
|
|
/// Consumable effect magnitude (`amount`) — NOT a stack size: every owned
|
|
/// copy is its own instance and carries its definition's value, which is
|
|
/// exactly the `fcc_*` table's `amount` column. Permissive.
|
|
#[serde(default)]
|
|
pub amount: Option<i64>,
|
|
/// Staff/contract `contract` count. Permissive.
|
|
#[serde(default)]
|
|
pub contract: Option<i64>,
|
|
/// Owned-item lifecycle state (`activeHomeKit` / `activeAwayKit` for kits).
|
|
#[serde(rename = "itemState", default)]
|
|
pub item_state: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
pub struct Attr {
|
|
pub index: i64,
|
|
pub value: i64,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
pub struct Squad {
|
|
#[serde(default)]
|
|
pub formation: String,
|
|
#[serde(rename = "squadName", default)]
|
|
pub squad_name: String,
|
|
#[serde(default)]
|
|
pub captain: Option<i64>,
|
|
/// Opaque staff refs; each is `{ "id": <wire id>, ... }`.
|
|
#[serde(default)]
|
|
pub manager: Vec<serde_json::Value>,
|
|
#[serde(default)]
|
|
pub players: Vec<SquadSlot>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
pub struct SquadSlot {
|
|
pub index: i64,
|
|
#[serde(rename = "itemData", default)]
|
|
pub item_data: SlotItem,
|
|
#[serde(rename = "kitNumber", default)]
|
|
pub kit_number: Option<i64>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default, Deserialize)]
|
|
pub struct SlotItem {
|
|
/// Wire id of the owned instance in this slot; `0` = empty slot.
|
|
#[serde(default)]
|
|
pub id: i64,
|
|
}
|