use std::collections::HashMap;
use anyhow::bail;
use mcvm_shared::id::ProfileID;
use mcvm_shared::Side;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use mcvm_shared::modifications::{ClientType, Modloader, Proxy, ServerType};
use super::instance::{merge_instance_configs, InstanceConfig};
use super::package::PackageConfigDeser;
#[derive(Deserialize, Serialize, Clone)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct ProfileConfig {
#[serde(flatten)]
pub instance: InstanceConfig,
#[serde(default)]
pub packages: ProfilePackageConfiguration,
}
impl ProfileConfig {
pub fn merge(&mut self, other: Self) {
self.instance = merge_instance_configs(&self.instance, other.instance);
}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(untagged)]
pub enum ProfilePackageConfiguration {
Simple(Vec<PackageConfigDeser>),
Full {
#[serde(default)]
global: Vec<PackageConfigDeser>,
#[serde(default)]
client: Vec<PackageConfigDeser>,
#[serde(default)]
server: Vec<PackageConfigDeser>,
},
}
impl Default for ProfilePackageConfiguration {
fn default() -> Self {
Self::Simple(Vec::new())
}
}
impl ProfilePackageConfiguration {
pub fn validate(&self) -> anyhow::Result<()> {
match &self {
Self::Simple(global) => {
for pkg in global {
pkg.validate()?;
}
}
Self::Full {
global,
client,
server,
} => {
for pkg in global.iter().chain(client.iter()).chain(server.iter()) {
pkg.validate()?;
}
}
}
Ok(())
}
pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PackageConfigDeser> + 'a> {
match &self {
Self::Simple(global) => Box::new(global.iter()),
Self::Full {
global,
client,
server,
} => Box::new(global.iter().chain(client.iter()).chain(server.iter())),
}
}
pub fn iter_global(&self) -> impl Iterator<Item = &PackageConfigDeser> {
match &self {
Self::Simple(global) => global,
Self::Full { global, .. } => global,
}
.iter()
}
pub fn iter_side(&self, side: Side) -> impl Iterator<Item = &PackageConfigDeser> {
match &self {
Self::Simple(..) => [].iter(),
Self::Full { client, server, .. } => match side {
Side::Client => client.iter(),
Side::Server => server.iter(),
},
}
}
pub fn add_global_package(&mut self, pkg: PackageConfigDeser) {
match self {
Self::Simple(global) => global.push(pkg),
Self::Full { global, .. } => global.push(pkg),
}
}
pub fn add_client_package(&mut self, pkg: PackageConfigDeser) {
match self {
Self::Simple(global) => {
*self = Self::Full {
global: global.clone(),
client: vec![pkg],
server: Vec::new(),
}
}
Self::Full { client, .. } => client.push(pkg),
}
}
pub fn add_server_package(&mut self, pkg: PackageConfigDeser) {
match self {
Self::Simple(global) => {
*self = Self::Full {
global: global.clone(),
client: Vec::new(),
server: vec![pkg],
}
}
Self::Full { server, .. } => server.push(pkg),
}
}
}
pub fn consolidate_profile_configs(
profiles: HashMap<ProfileID, ProfileConfig>,
global_profile: Option<&ProfileConfig>,
) -> anyhow::Result<HashMap<ProfileID, ProfileConfig>> {
let mut out: HashMap<_, ProfileConfig> = HashMap::with_capacity(profiles.len());
let max_iterations = 10000;
let mut i = 0;
while out.len() != profiles.len() {
for (id, profile) in &profiles {
if out.contains_key(id) {
continue;
}
if profile.instance.common.from.is_empty() {
let mut profile = profile.clone();
if let Some(global_profile) = global_profile {
let overlay = profile;
profile = global_profile.clone();
profile.merge(overlay);
}
out.insert(id.clone(), profile);
} else {
for parent in profile.instance.common.from.iter() {
if let Some(parent) = out.get(&ProfileID::from(parent.clone())) {
let mut new = parent.clone();
new.merge(profile.clone());
out.insert(id.clone(), new);
} else {
bail!("Parent profile '{parent}' does not exist");
}
}
}
}
i += 1;
if i > max_iterations {
panic!("Max iterations exceeded while resolving profiles. This is a bug in MCVM.");
}
}
Ok(out)
}
#[derive(Clone, Debug)]
pub struct GameModifications {
modloader: Modloader,
client_type: ClientType,
server_type: ServerType,
}
impl GameModifications {
pub fn new(modloader: Modloader, client_type: ClientType, server_type: ServerType) -> Self {
Self {
modloader,
client_type,
server_type,
}
}
pub fn client_type(&self) -> ClientType {
if let ClientType::None = self.client_type {
match &self.modloader {
Modloader::Vanilla => ClientType::Vanilla,
Modloader::Forge => ClientType::Forge,
Modloader::NeoForged => ClientType::NeoForged,
Modloader::Fabric => ClientType::Fabric,
Modloader::Quilt => ClientType::Quilt,
Modloader::LiteLoader => ClientType::LiteLoader,
Modloader::Risugamis => ClientType::Risugamis,
Modloader::Rift => ClientType::Rift,
Modloader::Unknown(modloader) => ClientType::Unknown(modloader.clone()),
}
} else {
self.client_type.clone()
}
}
pub fn server_type(&self) -> ServerType {
if let ServerType::None = self.server_type {
match &self.modloader {
Modloader::Vanilla => ServerType::Vanilla,
Modloader::Forge => ServerType::Forge,
Modloader::NeoForged => ServerType::NeoForged,
Modloader::Fabric => ServerType::Fabric,
Modloader::Quilt => ServerType::Quilt,
Modloader::LiteLoader => ServerType::Unknown("liteloader".into()),
Modloader::Risugamis => ServerType::Risugamis,
Modloader::Rift => ServerType::Rift,
Modloader::Unknown(modloader) => ServerType::Unknown(modloader.clone()),
}
} else {
self.server_type.clone()
}
}
pub fn get_modloader(&self, side: Side) -> Modloader {
match side {
Side::Client => match self.client_type {
ClientType::None => self.modloader.clone(),
ClientType::Vanilla => Modloader::Vanilla,
ClientType::Forge => Modloader::Forge,
ClientType::NeoForged => Modloader::NeoForged,
ClientType::Fabric => Modloader::Fabric,
ClientType::Quilt => Modloader::Quilt,
ClientType::LiteLoader => Modloader::LiteLoader,
ClientType::Risugamis => Modloader::Risugamis,
ClientType::Rift => Modloader::Rift,
_ => Modloader::Vanilla,
},
Side::Server => match self.server_type {
ServerType::None => self.modloader.clone(),
ServerType::Forge | ServerType::SpongeForge => Modloader::Forge,
ServerType::NeoForged => Modloader::NeoForged,
ServerType::Fabric => Modloader::Fabric,
ServerType::Quilt => Modloader::Quilt,
ServerType::Risugamis => Modloader::Risugamis,
ServerType::Rift => Modloader::Rift,
_ => Modloader::Vanilla,
},
}
}
pub fn common_modloader(&self) -> bool {
matches!(
(&self.client_type, &self.server_type),
(ClientType::None, ServerType::None)
| (ClientType::Vanilla, ServerType::Vanilla)
| (ClientType::Forge, ServerType::Forge)
| (ClientType::NeoForged, ServerType::NeoForged)
| (ClientType::Fabric, ServerType::Fabric)
| (ClientType::Quilt, ServerType::Quilt)
| (ClientType::Risugamis, ServerType::Risugamis)
| (ClientType::Rift, ServerType::Rift)
)
}
}
pub fn can_install_client_type(client_type: &ClientType) -> bool {
matches!(client_type, ClientType::None | ClientType::Vanilla)
}
pub fn can_install_server_type(server_type: &ServerType) -> bool {
matches!(
server_type,
ServerType::None
| ServerType::Vanilla
| ServerType::Paper
| ServerType::Folia
| ServerType::Sponge
)
}
pub fn can_install_proxy(proxy: Proxy) -> bool {
matches!(proxy, Proxy::None)
}