1use std::collections::HashMap;
2
3use anyhow::bail;
4use mcvm_shared::id::ProfileID;
5use mcvm_shared::Side;
6#[cfg(feature = "schema")]
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9
10use mcvm_shared::modifications::{ClientType, Modloader, Proxy, ServerType};
11
12use super::instance::{merge_instance_configs, InstanceConfig};
13use super::package::PackageConfigDeser;
14
15#[derive(Deserialize, Serialize, Clone)]
17#[cfg_attr(feature = "schema", derive(JsonSchema))]
18pub struct ProfileConfig {
19 #[serde(flatten)]
21 pub instance: InstanceConfig,
22 #[serde(default)]
24 pub packages: ProfilePackageConfiguration,
25}
26
27impl ProfileConfig {
28 pub fn merge(&mut self, other: Self) {
30 self.instance = merge_instance_configs(&self.instance, other.instance);
31 }
32}
33
34#[derive(Deserialize, Serialize, Debug, Clone)]
36#[cfg_attr(feature = "schema", derive(JsonSchema))]
37#[serde(untagged)]
38pub enum ProfilePackageConfiguration {
39 Simple(Vec<PackageConfigDeser>),
41 Full {
43 #[serde(default)]
45 global: Vec<PackageConfigDeser>,
46 #[serde(default)]
48 client: Vec<PackageConfigDeser>,
49 #[serde(default)]
51 server: Vec<PackageConfigDeser>,
52 },
53}
54
55impl Default for ProfilePackageConfiguration {
56 fn default() -> Self {
57 Self::Simple(Vec::new())
58 }
59}
60
61impl ProfilePackageConfiguration {
62 pub fn validate(&self) -> anyhow::Result<()> {
64 match &self {
65 Self::Simple(global) => {
66 for pkg in global {
67 pkg.validate()?;
68 }
69 }
70 Self::Full {
71 global,
72 client,
73 server,
74 } => {
75 for pkg in global.iter().chain(client.iter()).chain(server.iter()) {
76 pkg.validate()?;
77 }
78 }
79 }
80
81 Ok(())
82 }
83
84 pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PackageConfigDeser> + 'a> {
86 match &self {
87 Self::Simple(global) => Box::new(global.iter()),
88 Self::Full {
89 global,
90 client,
91 server,
92 } => Box::new(global.iter().chain(client.iter()).chain(server.iter())),
93 }
94 }
95
96 pub fn iter_global(&self) -> impl Iterator<Item = &PackageConfigDeser> {
98 match &self {
99 Self::Simple(global) => global,
100 Self::Full { global, .. } => global,
101 }
102 .iter()
103 }
104
105 pub fn iter_side(&self, side: Side) -> impl Iterator<Item = &PackageConfigDeser> {
107 match &self {
108 Self::Simple(..) => [].iter(),
109 Self::Full { client, server, .. } => match side {
110 Side::Client => client.iter(),
111 Side::Server => server.iter(),
112 },
113 }
114 }
115
116 pub fn add_global_package(&mut self, pkg: PackageConfigDeser) {
118 match self {
119 Self::Simple(global) => global.push(pkg),
120 Self::Full { global, .. } => global.push(pkg),
121 }
122 }
123
124 pub fn add_client_package(&mut self, pkg: PackageConfigDeser) {
126 match self {
127 Self::Simple(global) => {
128 *self = Self::Full {
129 global: global.clone(),
130 client: vec![pkg],
131 server: Vec::new(),
132 }
133 }
134 Self::Full { client, .. } => client.push(pkg),
135 }
136 }
137
138 pub fn add_server_package(&mut self, pkg: PackageConfigDeser) {
140 match self {
141 Self::Simple(global) => {
142 *self = Self::Full {
143 global: global.clone(),
144 client: Vec::new(),
145 server: vec![pkg],
146 }
147 }
148 Self::Full { server, .. } => server.push(pkg),
149 }
150 }
151}
152
153pub fn consolidate_profile_configs(
155 profiles: HashMap<ProfileID, ProfileConfig>,
156 global_profile: Option<&ProfileConfig>,
157) -> anyhow::Result<HashMap<ProfileID, ProfileConfig>> {
158 let mut out: HashMap<_, ProfileConfig> = HashMap::with_capacity(profiles.len());
159
160 let max_iterations = 10000;
161
162 let mut i = 0;
164 while out.len() != profiles.len() {
165 for (id, profile) in &profiles {
166 if out.contains_key(id) {
168 continue;
169 }
170
171 if profile.instance.common.from.is_empty() {
172 let mut profile = profile.clone();
174 if let Some(global_profile) = global_profile {
175 let overlay = profile;
176 profile = global_profile.clone();
177 profile.merge(overlay);
178 }
179 out.insert(id.clone(), profile);
180 } else {
181 for parent in profile.instance.common.from.iter() {
182 if let Some(parent) = out.get(&ProfileID::from(parent.clone())) {
184 let mut new = parent.clone();
185 new.merge(profile.clone());
186 out.insert(id.clone(), new);
187 } else {
188 bail!("Parent profile '{parent}' does not exist");
189 }
190 }
191 }
192 }
193
194 i += 1;
195 if i > max_iterations {
196 panic!("Max iterations exceeded while resolving profiles. This is a bug in MCVM.");
197 }
198 }
199
200 Ok(out)
201}
202
203#[derive(Clone, Debug)]
205pub struct GameModifications {
206 modloader: Modloader,
207 client_type: ClientType,
209 server_type: ServerType,
211}
212
213impl GameModifications {
214 pub fn new(modloader: Modloader, client_type: ClientType, server_type: ServerType) -> Self {
216 Self {
217 modloader,
218 client_type,
219 server_type,
220 }
221 }
222
223 pub fn client_type(&self) -> ClientType {
225 if let ClientType::None = self.client_type {
226 match &self.modloader {
227 Modloader::Vanilla => ClientType::Vanilla,
228 Modloader::Forge => ClientType::Forge,
229 Modloader::NeoForged => ClientType::NeoForged,
230 Modloader::Fabric => ClientType::Fabric,
231 Modloader::Quilt => ClientType::Quilt,
232 Modloader::LiteLoader => ClientType::LiteLoader,
233 Modloader::Risugamis => ClientType::Risugamis,
234 Modloader::Rift => ClientType::Rift,
235 Modloader::Unknown(modloader) => ClientType::Unknown(modloader.clone()),
236 }
237 } else {
238 self.client_type.clone()
239 }
240 }
241
242 pub fn server_type(&self) -> ServerType {
244 if let ServerType::None = self.server_type {
245 match &self.modloader {
246 Modloader::Vanilla => ServerType::Vanilla,
247 Modloader::Forge => ServerType::Forge,
248 Modloader::NeoForged => ServerType::NeoForged,
249 Modloader::Fabric => ServerType::Fabric,
250 Modloader::Quilt => ServerType::Quilt,
251 Modloader::LiteLoader => ServerType::Unknown("liteloader".into()),
252 Modloader::Risugamis => ServerType::Risugamis,
253 Modloader::Rift => ServerType::Rift,
254 Modloader::Unknown(modloader) => ServerType::Unknown(modloader.clone()),
255 }
256 } else {
257 self.server_type.clone()
258 }
259 }
260
261 pub fn get_modloader(&self, side: Side) -> Modloader {
263 match side {
264 Side::Client => match self.client_type {
265 ClientType::None => self.modloader.clone(),
266 ClientType::Vanilla => Modloader::Vanilla,
267 ClientType::Forge => Modloader::Forge,
268 ClientType::NeoForged => Modloader::NeoForged,
269 ClientType::Fabric => Modloader::Fabric,
270 ClientType::Quilt => Modloader::Quilt,
271 ClientType::LiteLoader => Modloader::LiteLoader,
272 ClientType::Risugamis => Modloader::Risugamis,
273 ClientType::Rift => Modloader::Rift,
274 _ => Modloader::Vanilla,
275 },
276 Side::Server => match self.server_type {
277 ServerType::None => self.modloader.clone(),
278 ServerType::Forge | ServerType::SpongeForge => Modloader::Forge,
279 ServerType::NeoForged => Modloader::NeoForged,
280 ServerType::Fabric => Modloader::Fabric,
281 ServerType::Quilt => Modloader::Quilt,
282 ServerType::Risugamis => Modloader::Risugamis,
283 ServerType::Rift => Modloader::Rift,
284 _ => Modloader::Vanilla,
285 },
286 }
287 }
288
289 pub fn common_modloader(&self) -> bool {
291 matches!(
292 (&self.client_type, &self.server_type),
293 (ClientType::None, ServerType::None)
294 | (ClientType::Vanilla, ServerType::Vanilla)
295 | (ClientType::Forge, ServerType::Forge)
296 | (ClientType::NeoForged, ServerType::NeoForged)
297 | (ClientType::Fabric, ServerType::Fabric)
298 | (ClientType::Quilt, ServerType::Quilt)
299 | (ClientType::Risugamis, ServerType::Risugamis)
300 | (ClientType::Rift, ServerType::Rift)
301 )
302 }
303}
304
305pub fn can_install_client_type(client_type: &ClientType) -> bool {
307 matches!(client_type, ClientType::None | ClientType::Vanilla)
308}
309
310pub fn can_install_server_type(server_type: &ServerType) -> bool {
312 matches!(
313 server_type,
314 ServerType::None
315 | ServerType::Vanilla
316 | ServerType::Paper
317 | ServerType::Folia
318 | ServerType::Sponge
319 )
320}
321
322pub fn can_install_proxy(proxy: Proxy) -> bool {
324 matches!(proxy, Proxy::None)
326}