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}