diff --git a/src/entities/mock.rs b/src/entities/mock.rs index 5c1d206..97a79f1 100644 --- a/src/entities/mock.rs +++ b/src/entities/mock.rs @@ -45,9 +45,7 @@ impl Dashboard { quest_book: generate_mock_questbook(), notes: generate_mock_notebook(), people: generate_mock_contact_book(), - settings: Settings { - - } + settings: Settings::default(), } } } diff --git a/src/entities/mod.rs b/src/entities/mod.rs index e340131..a035ed5 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -9,12 +9,14 @@ //! //! The model is deliberately "wide": it covers the campaign header, the //! player character, the party's common stash, quests, notes, contacts and -//! settings. All of it currently comes from hardcoded mock data (the `mock` -//! module); nothing is persisted yet (see ROADMAP phase 1). +//! settings. `mock` provides demo data, `Dashboard::blank` an empty slate; +//! persistence (localStorage autosave, JSON export/import) lives in +//! `crate::state::persist`. use std::fmt::Display; use chrono::{ NaiveDate, NaiveDateTime }; use reactive_stores::Store; +use serde::{ Deserialize, Serialize }; use crate::prelude::*; @@ -66,7 +68,7 @@ pub struct ItemReferenceMap (pub HashMap); /// party's common inventory, quests, notes, contacts and settings. The /// whole tree derives `Store`, so every nested field is independently /// reactive. `Dashboard::mock()` provides the initial state. -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub struct Dashboard { pub campaign: String, pub campaign_image: String, @@ -80,10 +82,49 @@ pub struct Dashboard { pub settings: Settings } +impl Dashboard { + /// An empty dashboard: the blank slate after Clear. + /// + /// No campaign name, no players, empty books, zeroed numbers. + pub fn blank () -> Self { + Self { + campaign: String::new(), + campaign_image: String::new(), + session: 0, + date: chrono::Local::now().date_naive(), + player: PlayerData { + name: Name::default(), + class: String::new(), + image: String::new(), + level: 0, + xp: 0, + temp_hp: 0, + hp: 0, + max_hp: 0, + balance: Balance::default(), + spell_slots: SpellSlots (core::array::from_fn(|_| SpellSlotLevel { used: 0, total: 0 })), + inventory: Inventory::default(), + attunements: Attunements (core::array::from_fn(|_| None)), + prepared: PreparedSpells (Vec::new()), + hit_dice: HitDice { used: 0, kind: 8 }, + death_save_throws: DeathSaveThrows { failed: 0, succeeded: 0 }, + }, + common: CommonData { + balance: Balance::default(), + inventory: Inventory::default(), + }, + quest_book: QuestBook (Vec::new()), + notes: NoteBook (Vec::new()), + people: ContactBook (HashMap::new()), + settings: Settings::default(), + } + } +} + /// The player character: identity, class, level, XP, hit points, money, /// spell slots, inventory, attunements, prepared spells, hit dice and /// death saves. This is the heart of the character sheet. -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub struct PlayerData { pub name: Name, pub class: String, @@ -105,7 +146,7 @@ pub struct PlayerData { /// The party's shared stash: money and items usable by everyone. -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub struct CommonData { pub balance: Balance, pub inventory: Inventory, @@ -113,7 +154,7 @@ pub struct CommonData { /// Currency counts per denomination, platinum through copper. /// Edited with `Numput` on the inventories page. -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize, Default)] pub struct Balance { pub platinum: u32, pub gold: u32, @@ -126,14 +167,14 @@ pub struct Balance { /// /// `#[store(key)]` gives every entry a stable reactive key (its `id`), /// which keeps `For`/`ForEnumerate` updates cheap. -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize, Default)] pub struct Inventory { #[store(key: u64 = |row| row.id)] pub rows: Vec } /// One inventory row: a quantity of a single item. -#[derive(Clone, Debug, Store, Default)] +#[derive(Clone, Debug, Store, Serialize, Deserialize, Default)] pub struct InventoryEntry { pub id: u64, pub quantity: u32, @@ -155,7 +196,7 @@ impl InventoryEntry { /// An item inside an inventory: name, weight (basis points), optional /// description, and free-form tags (e.g. `type`, `rarity`). -#[derive(Clone, Debug, Store, Default)] +#[derive(Clone, Debug, Store, Serialize, Deserialize, Default)] pub struct InventoryItem { pub name: String, pub weight: u32, @@ -164,12 +205,12 @@ pub struct InventoryItem { } /// The party's quest list. No UI yet — see ROADMAP phase 4. -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub struct QuestBook (pub Vec); /// One quest: title, locations, dates, description, giver and reward. /// Note: there is no completion state yet (ROADMAP phase 4 adds one). -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub struct Quest { pub title: String, pub location_taken: Option, @@ -182,12 +223,12 @@ pub struct Quest { } /// The party's session notes. No UI yet — see ROADMAP phase 5. -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub struct NoteBook (pub Vec); /// One note: content, in-game date, real-world timestamp, session number /// and tags. -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub struct Note { pub content: String, pub date_ingame: NaiveDate, @@ -197,28 +238,28 @@ pub struct Note { } /// Spell slots for the nine spell levels (index 0 = 1st level). -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub struct SpellSlots (pub [SpellSlotLevel; 9]); /// Used/total slots for one spell level. -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub struct SpellSlotLevel { pub used: u8, pub total: u8, } /// Three attunement slots (D&D rules allow three attuned items). -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub struct Attunements (pub [Option; 3]); /// The spell list the character has prepared (or is learning). -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub struct PreparedSpells (pub Vec); /// One prepared-spell slot: vacant or holding a spell. -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub enum PreparedSpell { Vacant, Occupied (Spell) @@ -227,13 +268,13 @@ pub enum PreparedSpell { /// A spell. Only the name exists so far; the rest is a `@TODO` /// (see ROADMAP phase 2). // @TODO -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub struct Spell { pub name: String } /// Hit dice: how many of the current die kind are already spent. -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub struct HitDice { pub used: u8, pub kind: u8 @@ -242,7 +283,7 @@ pub struct HitDice { /// Death-saving-throw counters: three failures kill, three successes /// stabilize. -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub struct DeathSaveThrows { pub failed: u8, pub succeeded: u8, @@ -257,11 +298,11 @@ impl DeathSaveThrows { } /// Known NPCs keyed by name. No UI yet — see ROADMAP phase 6. -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub struct ContactBook (pub HashMap); /// One NPC contact: name, optional image, status, location and notes. -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub struct Contact { pub name: Name, pub image: Option, @@ -271,7 +312,7 @@ pub struct Contact { } /// Life status of a contact, used for filtering. -#[derive(Clone, Debug, Store)] +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub enum ContactStatus { Unknown, DeadBygone, @@ -280,7 +321,7 @@ pub enum ContactStatus { } /// A person's name: first, last, and an optional alias (nickname). -#[derive(Clone, Debug, Store, Default)] +#[derive(Clone, Debug, Store, Serialize, Deserialize, Default)] pub struct Name { pub first: String, pub last: String, @@ -298,15 +339,28 @@ impl Name { } } -/// App settings. Empty today; will carry e.g. a persisted-format version. -#[derive(Clone, Debug, Store, Default)] +/// App settings: currently only the saved-format version. +#[derive(Clone, Debug, Store, Serialize, Deserialize)] pub struct Settings { + /// Saved-format version; bumped on breaking JSON changes. + pub version: u32 +} +impl Settings { + /// The current saved-format version. + pub const VERSION: u32 = 1; +} + +impl Default for Settings { + /// Defaults carry the current format version. + fn default () -> Self { + Self { version: Self::VERSION } + } } /// Which side of the party an inventory belongs to. -#[derive(Clone, Copy, Debug, Store)] +#[derive(Clone, Copy, Debug, Store, Serialize, Deserialize)] pub enum InventoryKind { Personal, Common