Skip to main content

lighty_version/
lighty_builder.rs

1use std::path::{Path, PathBuf};
2
3use lighty_core::AppState;
4use lighty_loaders::types::{Loader, VersionInfo};
5use lighty_modsloader::{ModRequest, WithMods};
6
7#[cfg(any(feature = "modrinth", feature = "curseforge"))]
8use lighty_modsloader::ModpackSource;
9
10/// Builder for LightyUpdater-managed instances.
11///
12/// Unlike [`super::VersionBuilder`], `loader_version` here holds the
13/// LightyUpdater server URL: the actual loader and Minecraft version
14/// are fetched from that server at install time. Default paths come
15/// from the global [`AppState`] (call [`AppState::init`] first).
16///
17/// User-attached Modrinth / CurseForge mods can be layered on top of
18/// the server-pushed metadata via [`with_mod`]. Modpacks are
19/// intentionally not supported here — LightyUpdater is itself the
20/// modpack source of truth, mixing in a second modpack would conflict
21/// on the loader / Minecraft version.
22///
23/// [`with_mod`]: LightyVersionBuilder::with_mod
24#[derive(Debug, Clone)]
25pub struct LightyVersionBuilder {
26    pub name: String,
27    pub server_url: String,
28    pub minecraft_version: Option<String>,
29    pub loader: Option<Loader>,
30    pub game_dirs: PathBuf,
31    pub java_dirs: PathBuf,
32    pub mod_requests: Vec<ModRequest>,
33}
34
35impl LightyVersionBuilder {
36    /// Creates a new `LightyVersionBuilder`.
37    ///
38    /// `server_url` is the LightyUpdater server endpoint; the loader
39    /// and Minecraft version are resolved from its response at install
40    /// time. Panics if [`AppState::init`] hasn't been called.
41    ///
42    /// # Example
43    /// ```rust,no_run
44    /// use lighty_core::AppState;
45    /// use lighty_version::LightyVersionBuilder;
46    ///
47    /// AppState::init("MyLauncher").unwrap();
48    /// let builder = LightyVersionBuilder::new("my-server", "https://updater.example.com");
49    /// ```
50    pub fn new(name: &str, server_url: &str) -> Self {
51        Self {
52            name: name.to_string(),
53            server_url: server_url.to_string(),
54            minecraft_version: None,
55            loader: None,
56            game_dirs: AppState::data_dir().join(name),
57            java_dirs: AppState::config_dir().join("jre"),
58            mod_requests: Vec::new(),
59        }
60    }
61
62    /// Opens the user-mods sub-builder. Modpacks are not exposed on
63    /// purpose — see the struct doc.
64    ///
65    /// # Example
66    /// ```rust,no_run
67    /// # use lighty_core::AppState;
68    /// # use lighty_version::LightyVersionBuilder;
69    /// # AppState::init("MyLauncher").unwrap();
70    /// let builder = LightyVersionBuilder::new("server", "https://updater.example.com")
71    ///     .with_mod()
72    ///         .done();
73    /// ```
74    pub fn with_mod(self) -> LightyModSourcesBuilder {
75        LightyModSourcesBuilder {
76            parent: self,
77            pending: Vec::new(),
78        }
79    }
80}
81
82impl VersionInfo for LightyVersionBuilder {
83    type LoaderType = Loader;
84
85    fn name(&self) -> &str {
86        &self.name
87    }
88
89    fn loader_version(&self) -> &str {
90        &self.server_url
91    }
92
93    fn minecraft_version(&self) -> &str {
94        self.minecraft_version.as_ref().map_or("", String::as_str)
95    }
96
97    fn game_dirs(&self) -> &Path {
98        &self.game_dirs
99    }
100
101    fn java_dirs(&self) -> &Path {
102        &self.java_dirs
103    }
104
105    fn loader(&self) -> &Self::LoaderType {
106        self.loader.as_ref().unwrap_or(&Loader::LightyUpdater)
107    }
108}
109
110impl<'b> VersionInfo for &'b LightyVersionBuilder {
111    type LoaderType = Loader;
112
113    fn name(&self) -> &str {
114        &self.name
115    }
116
117    fn loader_version(&self) -> &str {
118        &self.server_url
119    }
120
121    fn minecraft_version(&self) -> &str {
122        self.minecraft_version.as_ref().map_or("", String::as_str)
123    }
124
125    fn game_dirs(&self) -> &Path {
126        &self.game_dirs
127    }
128
129    fn java_dirs(&self) -> &Path {
130        &self.java_dirs
131    }
132
133    fn loader(&self) -> &Self::LoaderType {
134        self.loader.as_ref().unwrap_or(&Loader::LightyUpdater)
135    }
136}
137
138impl WithMods for LightyVersionBuilder {
139    fn mod_requests(&self) -> &[ModRequest] {
140        &self.mod_requests
141    }
142
143    #[cfg(any(feature = "modrinth", feature = "curseforge"))]
144    fn modpack(&self) -> Option<&ModpackSource> {
145        None
146    }
147}
148
149impl<'b> WithMods for &'b LightyVersionBuilder {
150    fn mod_requests(&self) -> &[ModRequest] {
151        &self.mod_requests
152    }
153
154    #[cfg(any(feature = "modrinth", feature = "curseforge"))]
155    fn modpack(&self) -> Option<&ModpackSource> {
156        None
157    }
158}
159
160/// Sub-builder accumulating user-attached Modrinth / CurseForge mods
161/// on a [`LightyVersionBuilder`]. No modpack methods — LightyUpdater
162/// is already the modpack source.
163pub struct LightyModSourcesBuilder {
164    parent: LightyVersionBuilder,
165    pending: Vec<ModRequest>,
166}
167
168impl LightyModSourcesBuilder {
169    /// Adds Modrinth mod requests on top of the LightyUpdater-pushed metadata.
170    ///
171    /// Each tuple is `(project-slug-or-id, optional-mod-version-id)`.
172    ///
173    /// # Example
174    /// ```rust,no_run
175    /// # use lighty_core::AppState;
176    /// # use lighty_version::LightyVersionBuilder;
177    /// # AppState::init("MyLauncher").unwrap();
178    /// let builder = LightyVersionBuilder::new("server", "https://updater.example.com")
179    ///     .with_mod()
180    ///         .with_modrinth_mods(vec![
181    ///             ("sodium-extra", None),
182    ///             ("iris", None),
183    ///         ])
184    ///         .done();
185    /// ```
186    #[cfg(feature = "modrinth")]
187    pub fn with_modrinth_mods<S>(mut self, list: Vec<(S, Option<String>)>) -> Self
188    where
189        S: Into<String>,
190    {
191        for (id_or_slug, version) in list {
192            self.pending.push(ModRequest::Modrinth {
193                id_or_slug: id_or_slug.into(),
194                version,
195            });
196        }
197        self
198    }
199
200    /// Adds CurseForge mod requests on top of the LightyUpdater-pushed metadata.
201    ///
202    /// Requires [`lighty_modsloader::curseforge::set_api_key`] to have
203    /// been called before `.run()`.
204    ///
205    /// # Example
206    /// ```rust,no_run
207    /// # use lighty_core::AppState;
208    /// # use lighty_version::LightyVersionBuilder;
209    /// # AppState::init("MyLauncher").unwrap();
210    /// let builder = LightyVersionBuilder::new("server", "https://updater.example.com")
211    ///     .with_mod()
212    ///         .with_curseforge_mods(vec![(238222, None)])
213    ///         .done();
214    /// ```
215    #[cfg(feature = "curseforge")]
216    pub fn with_curseforge_mods(mut self, list: Vec<(u32, Option<u32>)>) -> Self {
217        for (mod_id, file_id) in list {
218            self.pending.push(ModRequest::CurseForge { mod_id, file_id });
219        }
220        self
221    }
222
223    /// Threads the accumulated mod requests back into the parent builder.
224    pub fn done(self) -> LightyVersionBuilder {
225        let mut parent = self.parent;
226        let mut pending = self.pending;
227        parent.mod_requests.append(&mut pending);
228        parent
229    }
230}