Skip to main content

launcher/pages/mods/
types.rs

1use std::collections::HashMap;
2
3use eframe::egui;
4use serde_json::Value;
5use supreme_hook_config::ModConfig;
6
7use super::account::{AvatarState, LoginPopup, LoginTask};
8use super::tournament::{
9    OpenPopup, PendingSelection, PopupValue, RemoveConfirm, RoomCache, RoomTaskResult,
10};
11use super::watch::ModsWatcher;
12use crate::launcher_config::AccountConfig;
13use crate::tasks::TaskHandle;
14use supreme_fs::AccountDto;
15
16/// One mod discovered under `.supreme/mods/`.
17pub struct LoadedMod {
18    /// The folder name under `.supreme/mods/`, the stable identifier used
19    /// as an entry in `mods.json`'s active configuration
20    /// (`order`/`enabled`/`configuration`), and as the key into
21    /// [`ModsPageState::selected`].
22    pub dir_name: String,
23    /// This mod's author-owned, static definition, name/metadata, rules,
24    /// parameters, and default values for them (`config.configuration`).
25    /// Defaults are author-set, not per-user, the actual per-user state
26    /// is the sibling `configuration` field below, kept deliberately
27    /// separate so editing a setting never touches this mod's `mod.json`.
28    pub config: ModConfig,
29    /// This mod's current parameter *overrides*, only what a person has
30    /// actually changed via the launcher, loaded from (and, on any edit,
31    /// persisted back to) the active configuration in the shared
32    /// `mods.json`. A parameter with no entry here falls back to
33    /// `config.configuration`'s default for it, then to fully unset.
34    pub configuration: HashMap<String, Value>,
35    pub enabled: bool,
36    /// Set if this mod's `mod.json` failed to parse, or parsed but didn't
37    /// pass [`ModConfig::validate`]. The row still shows, with this
38    /// message instead of a configuration editor, rather than the mod
39    /// silently vanishing from the list.
40    pub load_error: Option<String>,
41    /// Non-fatal warnings discovered while loading this mod's `mod.json`
42    /// typically unknown keys that suggest the mod uses features not yet
43    /// supported by this launcher version. Displayed subtly in the UI
44    /// rather than blocking the mod from loading.
45    pub warnings: Vec<String>,
46}
47
48#[derive(Default)]
49pub struct ModsPageState {
50    pub mods: Vec<LoadedMod>,
51    /// `dir_name` of the mod currently shown in the right pane.
52    pub selected: Option<String>,
53
54    // Lightweight initialization tracking, same pattern as the Snowboards
55    // and Riders pages.
56    pub(super) is_initialized: bool,
57
58    /// Background filesystem watcher on `.supreme/mods/`, started on first
59    /// init alongside the initial [`super::tasks::discover_mods`] call.
60    /// `None` if it failed to start (e.g. the directory couldn't be
61    /// created), live-reload is a convenience, so the page still works
62    /// without it, just without picking up out-of-band changes.
63    pub(super) watcher: Option<ModsWatcher>,
64
65    // ---------------------------------------------------------------------------
66    // Tournament UI state, managed by the
67    // sibling `tournament` module; see its docs for each field's role.
68    // ---------------------------------------------------------------------------
69    /// egui pass number this page last rendered in, used to detect the
70    /// page (re)becoming active, which refreshes the room cache.
71    pub(super) last_seen_pass: Option<u64>,
72    /// Cached `list_rooms` result: `{room_count}` placeholders and the
73    /// room_list popup read from here.
74    pub(super) room_cache: RoomCache,
75    /// In-flight `list_rooms` task.
76    pub(super) list_task: Option<TaskHandle<RoomTaskResult>>,
77    /// In-flight `create_room`/`join_room` task (one at a time).
78    pub(super) action_task: Option<TaskHandle<RoomTaskResult>>,
79    /// What to persist when the create/join task succeeds.
80    pub(super) pending_selection: Option<PendingSelection>,
81    /// Currently open popup (keyed per mod + button).
82    pub(super) open_popup: Option<OpenPopup>,
83    /// A room removal awaiting its confirmation popup's verdict (the
84    /// popup renders above everything else, the removal request only
85    /// starts on the explicit Remove click).
86    pub(super) remove_confirm: Option<RemoveConfirm>,
87    /// monotonically increasing popup-open counter, each open gets
88    /// a fresh egui window id so an auto-sized popup re-hugs its contents
89    /// instead of keeping the widest geometry it ever grew to.
90    pub(super) popup_serial: u64,
91    /// Live popup field values, keyed `(mod dir, button id, field id)`.
92    pub(super) popup_values: HashMap<(String, String, String), PopupValue>,
93    /// Last tournament error (server message, disk failure, …) shown in
94    /// a popup until dismissed.
95    pub(super) tournament_error: Option<String>,
96    /// The mod parameter a refused create/join pointed at
97    /// `(dir_name, param_id)`: the mod page highlights that control
98    /// (error stroke + reason) until its value validates. Set when the
99    /// `tournament_player` parameter is empty/invalid at action time.
100    pub(super) tournament_param_error: Option<(String, String)>,
101    /// The logged-in tournament account (`None` = guest mode), adopted
102    /// from the launcher's own config on page init and kept in sync by
103    /// the sibling `account` module (see its docs; a change flags
104    /// `account_dirty`).
105    pub(super) account: Option<AccountConfig>,
106    /// Set when the account record changed this frame and needs
107    /// flushing into the launcher's own config file.
108    pub(super) account_dirty: bool,
109    /// The account popup, when open (the Waiting state doubles as the
110    /// start signal for a link flow).
111    pub(super) login_popup: Option<LoginPopup>,
112    /// The running link flow, when any (one at a time, guests only).
113    pub(super) login_task: Option<LoginTask>,
114    /// The one-per-run account refresh (`auth_account` through the
115    /// device key), when in flight. The refresh follows a nickname or
116    /// avatar change without a re-link; see the `account` module's
117    /// docs.
118    pub(super) account_refresh_task: Option<TaskHandle<AccountDto>>,
119    /// Whether this run's refresh already ran (succeeded, failed, or
120    /// had nothing to refresh) - it never re-spawns within one run; a
121    /// fresh link carries fresh account data and needs no refresh.
122    pub(super) account_refreshed: bool,
123    /// The running avatar fetch, when any (the sha-keyed cache file is
124    /// cold).
125    pub(super) avatar_task: Option<TaskHandle<()>>,
126    /// The account avatar's load state (see `AvatarState`; `Guest` =
127    /// nothing to show).
128    pub(super) avatar: AvatarState,
129    /// the room-card background textures, keyed `env/difficulty`.
130    /// Loaded lazily from the game's
131    /// `Data/Levels/<env>/Tracks/<diff>/Menu_Texture_Normal.tga`; `None`
132    /// = tried and failed (the tinted fallback card is drawn instead).
133    pub(super) room_card_textures: HashMap<String, Option<egui::TextureHandle>>,
134}