From 85948bbb37cc94ad872ebe003e667a2bbf013896 Mon Sep 17 00:00:00 2001 From: LtPeriwinkle Date: Sat, 30 Mar 2024 15:15:22 -0700 Subject: [PATCH] add mist export plugin --- Cargo.lock | 27 ++++++ compiler-core/Cargo.toml | 1 + .../src/plugin/native/export_livesplit.rs | 15 +-- .../src/plugin/native/export_mist.rs | 96 +++++++++++++++++++ .../src/plugin/native/export_mist.yaml | 2 + compiler-core/src/plugin/native/mod.rs | 20 ++++ docs/src/.vitepress/nav/plugin.ts | 1 + docs/src/plugin/export-mist.md | 34 +++++++ web-client/src/core/doc/export.ts | 2 +- 9 files changed, 184 insertions(+), 14 deletions(-) create mode 100644 compiler-core/src/plugin/native/export_mist.rs create mode 100644 compiler-core/src/plugin/native/export_mist.yaml create mode 100644 docs/src/plugin/export-mist.md diff --git a/Cargo.lock b/Cargo.lock index 830fbe70..0a87ad34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -284,6 +284,9 @@ name = "bitflags" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +dependencies = [ + "serde", +] [[package]] name = "bitvec" @@ -509,6 +512,7 @@ dependencies = [ "livesplit-core", "log", "map-macro", + "mist-core", "roman", "serde", "serde_json", @@ -1354,6 +1358,17 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "mist-core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0808249a2eef73d748c076f04f245d8a80863d7e3ba23e2ff1a87d54116fa5" +dependencies = [ + "ron", + "serde", + "thiserror", +] + [[package]] name = "nix" version = "0.23.2" @@ -1691,6 +1706,18 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2c543f0c827ae24df93159810fd4bce2d0abe3785bb4c4d68fae3c467d58d9b" +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64", + "bitflags 2.4.0", + "serde", + "serde_derive", +] + [[package]] name = "rustc-demangle" version = "0.1.23" diff --git a/compiler-core/Cargo.toml b/compiler-core/Cargo.toml index 275bc592..b3e7acf1 100644 --- a/compiler-core/Cargo.toml +++ b/compiler-core/Cargo.toml @@ -25,6 +25,7 @@ roman = "0.1.6" flate2 = "1.0.28" base64 = "0.21.7" livesplit-core = "0.13.0" +mist-core = { version = "2.0", default-features = false, features = ["ser"]} [dev-dependencies] map-macro = "0.2.6" diff --git a/compiler-core/src/plugin/native/export_livesplit.rs b/compiler-core/src/plugin/native/export_livesplit.rs index da93d090..6704fec2 100644 --- a/compiler-core/src/plugin/native/export_livesplit.rs +++ b/compiler-core/src/plugin/native/export_livesplit.rs @@ -15,6 +15,8 @@ use crate::macros::async_trait; use crate::plugin::{PluginResult, Runtime}; use crate::res::ResPath; +use super::should_split_on; + pub struct ExportLiveSplit; #[async_trait(auto)] @@ -148,19 +150,6 @@ impl Runtime for ExportLiveSplit { } } -fn should_split_on(line: &CompLine, split_types: &BTreeSet) -> bool { - let counter = match &line.counter_text { - Some(counter) => counter, - None => return false, - }; - let tag = match &counter.tag { - Some(tag) => tag, - None => return false, - }; - - split_types.contains(tag) -} - async fn build_icon_cache( doc: &CompDoc<'_>, split_sections: &[(&CompSection, Vec<&CompLine>)], diff --git a/compiler-core/src/plugin/native/export_mist.rs b/compiler-core/src/plugin/native/export_mist.rs new file mode 100644 index 00000000..85f787eb --- /dev/null +++ b/compiler-core/src/plugin/native/export_mist.rs @@ -0,0 +1,96 @@ +//! Exporter plugin for mist split files + +use std::borrow::Cow; +use std::collections::BTreeSet; + +use mist_core::timer::Run; + +use serde_json::Value; + +use super::should_split_on; + +use crate::comp::CompDoc; +use crate::expo::{ExpoBlob, ExpoDoc, ExportIcon, ExportMetadata}; +use crate::export_error; +use crate::json::Coerce; +use crate::macros::async_trait; +use crate::plugin::{PluginResult, Runtime}; + +pub struct ExportMist; + +#[async_trait(auto)] +impl Runtime for ExportMist { + fn get_id(&self) -> Cow<'static, str> { + Cow::Owned(super::Native::ExportMist.id()) + } + + async fn on_prepare_export(&mut self) -> PluginResult>> { + let meta = ExportMetadata { + plugin_id: self.get_id().into_owned(), + name: "mist".into(), + description: "Export to a mist split file".into(), + icon: ExportIcon::Data, + extension: Some("msf".into()), + export_id: None, + example_config: Some(include_str!("./export_mist.yaml").into()), + learn_more: Some("/docs/plugin/export-mist#export-mist".into()), + }; + Ok(Some(vec![meta])) + } + + async fn on_export_comp_doc<'p>( + &mut self, + _: &str, + payload: &Value, + doc: &CompDoc<'p>, + ) -> PluginResult> { + let payload = match payload.as_object() { + Some(payload) => payload, + None => return export_error!("Invalid payload"), + }; + let mut split_types = BTreeSet::new(); + if let Some(x) = payload.get("split-types") { + let x = match x.as_array() { + Some(x) => x, + _ => return export_error!("Invalid split types"), + }; + let names: BTreeSet = x.iter().map(|x| x.coerce_to_string()).collect(); + for (tag_name, tag) in doc.config.tags.iter() { + if let Some(split_type) = &tag.split_type { + if names.contains(split_type) { + split_types.insert(tag_name.clone()); + } + } + } + } + + if split_types.is_empty() { + return export_error!("No splits to export. Make sure you selected at least one split type in the settings."); + } + + let mut run = Run::empty(); + let mut splits = vec![]; + for section in &doc.route { + for line in section.lines.iter() { + if should_split_on(line, &split_types) { + splits.push(line.split_name.as_ref().unwrap_or(&line.text).to_string()) + } + } + } + + if splits.is_empty() { + return export_error!("No splits to export. Make sure you selected at least one split type in the settings."); + } + + run.set_splits(&splits); + let content = run.to_string(); + if content.is_err() { + return export_error!("Failed to serialize split file"); + } + + Ok(Some(ExpoDoc::Success { + file_name: format!("{}.msf", doc.config.meta.title), + file_content: ExpoBlob::from_utf8(content.unwrap()), + })) + } +} diff --git a/compiler-core/src/plugin/native/export_mist.yaml b/compiler-core/src/plugin/native/export_mist.yaml new file mode 100644 index 00000000..c8c0fbda --- /dev/null +++ b/compiler-core/src/plugin/native/export_mist.yaml @@ -0,0 +1,2 @@ +# Keep this as-is to use the splits configured in the settings +split-types: null diff --git a/compiler-core/src/plugin/native/mod.rs b/compiler-core/src/plugin/native/mod.rs index a559d029..1bc11698 100644 --- a/compiler-core/src/plugin/native/mod.rs +++ b/compiler-core/src/plugin/native/mod.rs @@ -2,15 +2,19 @@ //! //! Built-in plugins are implemented in Rust and directly included in the compiler. +use std::collections::BTreeSet; + use serde::{Deserialize, Serialize}; use serde_json::Value; +use crate::comp::CompLine; use crate::pack::CompileContext; use super::{BoxedEarlyRuntime, BoxedRuntime, PluginResult}; mod botw_unstable; mod export_livesplit; +mod export_mist; mod link; mod metrics; mod split_format; @@ -22,6 +26,8 @@ pub enum Native { BotwAbilityUnstable, // TODO #24: remove this #[serde(rename = "export-livesplit")] ExportLiveSplit, + #[serde(rename = "export-mist")] + ExportMist, Link, Metrics, SplitFormat, @@ -43,6 +49,7 @@ impl Native { botw_unstable::BotwAbilityUnstable::from_props(props), )), Self::ExportLiveSplit => Ok(Box::new(export_livesplit::ExportLiveSplit)), + Self::ExportMist => Ok(Box::new(export_mist::ExportMist)), Self::Link => Ok(Box::new(link::Link)), Self::Metrics => Ok(Box::new(metrics::Metrics::from_props( props, @@ -59,3 +66,16 @@ impl Native { .unwrap_or_default() } } + +fn should_split_on(line: &CompLine, split_types: &BTreeSet) -> bool { + let counter = match &line.counter_text { + Some(counter) => counter, + None => return false, + }; + let tag = match &counter.tag { + Some(tag) => tag, + None => return false, + }; + + split_types.contains(tag) +} diff --git a/docs/src/.vitepress/nav/plugin.ts b/docs/src/.vitepress/nav/plugin.ts index 4e467e6e..4b86e4c6 100644 --- a/docs/src/.vitepress/nav/plugin.ts +++ b/docs/src/.vitepress/nav/plugin.ts @@ -25,6 +25,7 @@ export const pluginsSideBar = { text: "Built-in Exporter Plugins", items: [ { text: "Export LiveSplit", link: "/plugin/export-livesplit" }, + { text: "Export mist", link: "/plugin/export-mist" }, ], }, { diff --git a/docs/src/plugin/export-mist.md b/docs/src/plugin/export-mist.md new file mode 100644 index 00000000..f6d47627 --- /dev/null +++ b/docs/src/plugin/export-mist.md @@ -0,0 +1,34 @@ + +# Export mist +The `export-mist` plugin lets you export the route to a split file (.msf) for mist. + +This plugin comes pre-configured in the web app: + +1. Click on `Settings`. +2. Select the `Plugins` category. +3. Under `App Plugins`, make sure `Export split files` is checked. + +Alternatively, you can add it to the route configuration: +```yaml +config: +- plugins: + - use: export-mist +``` + +## Extra Options +The plugin provides extra configuration when exporting. + +### Split Types +The recommended way to configure which split types are exported is through [Split Settings](../doc#splits) + +By having `split-types: null`, Celer will automatically add the split settings for you. +You can also override it by putting an array of split types. For example: +```yaml +split-types: +- Lightroots +- Shrines +- Tears +``` +:::tip +The split type names should match exactly with the checkbox labels in Split Settings. Case matters. +::: diff --git a/web-client/src/core/doc/export.ts b/web-client/src/core/doc/export.ts index def2b6b4..dca55d19 100644 --- a/web-client/src/core/doc/export.ts +++ b/web-client/src/core/doc/export.ts @@ -61,7 +61,7 @@ export function createExportRequest( /// Get the plugin configs when the "Export Split" option is enabled export function getSplitExportPluginConfigs() { - return [{ use: "export-livesplit" }]; + return [{ use: "export-livesplit" }, { use: "export-mist" }]; } export function injectSplitTypesIntoRequest(