From 4995190199d681ead794bb8b3764134682e041ed Mon Sep 17 00:00:00 2001 From: Lucas Schwiderski Date: Thu, 9 Mar 2023 11:52:52 +0100 Subject: [PATCH] feat(dtmm): Check mod order Closes #13. --- CHANGELOG.adoc | 2 + crates/dtmm/src/controller/app.rs | 63 +++++++++++++++++++++++++++++- crates/dtmm/src/controller/game.rs | 11 ++---- crates/dtmm/src/state/data.rs | 32 +++++++++++++++ lib/dtmt-shared/src/lib.rs | 21 ++++++++-- 5 files changed, 117 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index f61467b..ecfed2b 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -10,6 +10,8 @@ - dtmm: check for Steam game update before deployment - dtmm: remove unused bundles from previous deployment - dtmm: show dialog for critical errors +- dtmm: check mod order before deployment +- dtmt: add mod dependencies to config === Fixed diff --git a/crates/dtmm/src/controller/app.rs b/crates/dtmm/src/controller/app.rs index ce8f2a2..c36e7f2 100644 --- a/crates/dtmm/src/controller/app.rs +++ b/crates/dtmm/src/controller/app.rs @@ -14,7 +14,7 @@ use tokio_stream::wrappers::ReadDirStream; use tokio_stream::StreamExt; use zip::ZipArchive; -use crate::state::{ActionState, ModInfo, PackageInfo}; +use crate::state::{ActionState, ModInfo, ModOrder, PackageInfo}; use crate::util::config::{ConfigSerialize, LoadOrderEntry}; use super::read_sjson_file; @@ -216,3 +216,64 @@ where Ok::<_, color_eyre::Report>(mods) }) } + +pub(crate) fn check_mod_order(state: &ActionState) -> Result<()> { + { + let first = state.mods.get(0); + if first.is_none() || !(first.unwrap().id == "dml" && first.unwrap().enabled) { + // TODO: Add a suggestion where to get it, once that's published + eyre::bail!("'Darktide Mod Loader' needs to be installed, enabled and at the top of the load order"); + } + } + + state + .mods + .iter() + .filter(|i| i.enabled) + .enumerate() + .for_each(|(i, info)| tracing::debug!(i, ?info)); + + for (i, mod_info) in state.mods.iter().filter(|i| i.enabled).enumerate() { + for dep in &mod_info.depends { + let dep_info = state.mods.iter().enumerate().find(|(_, m)| m.id == dep.id); + + match dep_info { + Some((_, dep_info)) if !dep_info.enabled => { + eyre::bail!( + "Dependency '{}' ({}) must be enabled.", + dep_info.name, + dep.id + ); + } + Some((j, dep_info)) if dep.order == ModOrder::Before && j >= i => { + eyre::bail!( + "Dependency '{}' ({}) must be loaded before '{}'", + dep_info.name, + dep.id, + mod_info.name + ); + } + Some((j, dep_info)) if dep.order == ModOrder::After && j <= i => { + eyre::bail!( + "Dependency '{}' ({}) must be loaded after '{}'", + dep_info.name, + dep.id, + mod_info.name + ); + } + None => { + eyre::bail!( + "Missing dependency '{}' for mod '{}'", + dep.id, + mod_info.name + ); + } + Some(_) => { + // All good + } + } + } + } + + Ok(()) +} diff --git a/crates/dtmm/src/controller/game.rs b/crates/dtmm/src/controller/game.rs index c054688..68f9269 100644 --- a/crates/dtmm/src/controller/game.rs +++ b/crates/dtmm/src/controller/game.rs @@ -21,6 +21,7 @@ use tokio::io::AsyncWriteExt; use tracing::Instrument; use super::read_sjson_file; +use crate::controller::app::check_mod_order; use crate::state::{ActionState, PackageInfo}; const MOD_BUNDLE_NAME: &str = "packages/mods"; @@ -525,14 +526,6 @@ where pub(crate) async fn deploy_mods(state: ActionState) -> Result<()> { let state = Arc::new(state); - { - let first = state.mods.get(0); - if first.is_none() || !(first.unwrap().id == "dml" && first.unwrap().enabled) { - // TODO: Add a suggestion where to get it, once that's published - eyre::bail!("'Darktide Mod Loader' needs to be installed, enabled and at the top of the load order"); - } - } - let (_, game_info, deployment_info) = tokio::try_join!( async { let path = state.game_dir.join("bundle"); @@ -576,6 +569,8 @@ pub(crate) async fn deploy_mods(state: ActionState) -> Result<()> { } } + check_mod_order(&state)?; + tracing::info!( "Deploying {} mods to {}", state.mods.iter().filter(|i| i.enabled).count(), diff --git a/crates/dtmm/src/state/data.rs b/crates/dtmm/src/state/data.rs index 8021840..779b67c 100644 --- a/crates/dtmm/src/state/data.rs +++ b/crates/dtmm/src/state/data.rs @@ -39,6 +39,36 @@ pub(crate) struct ModResourceInfo { pub localization: Option, } +#[derive(Clone, Data, Debug, PartialEq)] +pub(crate) enum ModOrder { + Before, + After, +} + +#[derive(Clone, Data, Debug, PartialEq)] +pub(crate) struct ModDependency { + pub id: String, + pub order: ModOrder, +} + +impl From for ModDependency { + fn from(value: dtmt_shared::ModDependency) -> Self { + match value { + dtmt_shared::ModDependency::ID(id) => ModDependency { + id, + order: ModOrder::Before, + }, + dtmt_shared::ModDependency::Config { id, order } => ModDependency { + id, + order: match order { + dtmt_shared::ModOrder::Before => ModOrder::Before, + dtmt_shared::ModOrder::After => ModOrder::After, + }, + }, + } + } +} + #[derive(Clone, Data, Debug, Lens, PartialEq)] pub(crate) struct ModInfo { pub id: String, @@ -51,6 +81,7 @@ pub(crate) struct ModInfo { #[lens(ignore)] #[data(ignore)] pub resources: ModResourceInfo, + pub depends: Vector, } impl ModInfo { @@ -66,6 +97,7 @@ impl ModInfo { data: cfg.resources.data, localization: cfg.resources.localization, }, + depends: cfg.depends.into_iter().map(ModDependency::from).collect(), } } } diff --git a/lib/dtmt-shared/src/lib.rs b/lib/dtmt-shared/src/lib.rs index f15e4e7..17b8d92 100644 --- a/lib/dtmt-shared/src/lib.rs +++ b/lib/dtmt-shared/src/lib.rs @@ -3,10 +3,11 @@ use std::path::PathBuf; mod log; pub use log::*; +use serde::Deserialize; use steamlocate::SteamDir; use time::OffsetDateTime; -#[derive(Clone, Debug, Default, serde::Deserialize)] +#[derive(Clone, Debug, Default, Deserialize)] pub struct ModConfigResources { pub init: PathBuf, #[serde(default)] @@ -15,7 +16,21 @@ pub struct ModConfigResources { pub localization: Option, } -#[derive(Clone, Debug, Default, serde::Deserialize)] +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ModOrder { + Before, + After, +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(untagged)] +pub enum ModDependency { + ID(String), + Config { id: String, order: ModOrder }, +} + +#[derive(Clone, Debug, Default, Deserialize)] pub struct ModConfig { #[serde(skip)] pub dir: std::path::PathBuf, @@ -26,7 +41,7 @@ pub struct ModConfig { pub packages: Vec, pub resources: ModConfigResources, #[serde(default)] - pub depends: Vec, + pub depends: Vec, } pub const STEAMAPP_ID: u32 = 1361210;