diff --git a/Cargo.lock b/Cargo.lock index 4ea00a7..409710c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1538,6 +1538,10 @@ dependencies = [ "uuid", ] +[[package]] +name = "headquarters-mms" +version = "0.1.0" + [[package]] name = "heck" version = "0.5.0" diff --git a/Cargo.toml b/Cargo.toml index 34e8954..0302b02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,10 @@ -# The workspace spans the Rust services under services/. One binary today; -# M5 adds the proxy, sharing a library crate. Crate names are prefixed -# (headquarters-*) so a binary's name is unambiguous outside this repo. +# The workspace spans the Rust services under services/, and the libraries +# they share under crates/. One binary today; M5 adds the proxy. Crate names +# are prefixed (headquarters-*) so a binary's name is unambiguous outside this +# repo. [workspace] resolver = "3" -members = ["services/api"] +members = ["services/api", "crates/mms"] [workspace.package] version = "0.1.0" diff --git a/README.md b/README.md index 830868a..3695c39 100644 --- a/README.md +++ b/README.md @@ -267,7 +267,9 @@ scenario preview), `web/src/camo/backgrounds/` (four terrain tiles), from the same tree under the same licence, credited in `web/src/content/about.json`. The faction palettes in `web/src/camo/palette.ts` are measured off those camo files by the same script, so they are derived from -the art and covered by the same terms. That obligates us to three +the art and covered by the same terms. `crates/mms/tests/corpus/` holds the 48 +scenario files from the same release, copied verbatim, and is what says the +`.mms` reader reads the real format rather than a subset of it. That obligates us to three things, all of which already hold: stay non-commercial, attribute MegaMek (done in the editor UI and here), and share alike. Note this is an asset licence and not a code copyleft — diff --git a/crates/mms/Cargo.toml b/crates/mms/Cargo.toml new file mode 100644 index 0000000..a09058d --- /dev/null +++ b/crates/mms/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "headquarters-mms" +version.workspace = true +edition.workspace = true +publish.workspace = true + +# No dependencies on purpose. The format is lines of `key=value`, and the +# thing that makes a parser of it correct is matching MegaMek's own reader +# quirk for quirk - not the convenience of a crate. +[dependencies] diff --git a/crates/mms/src/document.rs b/crates/mms/src/document.rs new file mode 100644 index 0000000..3263265 --- /dev/null +++ b/crates/mms/src/document.rs @@ -0,0 +1,256 @@ +//! The lexical layer: a `.mms` file as the ordered list of `key=value` lines +//! MegaMek reads it as. +//! +//! This is deliberately not a struct of known fields. `ScenarioV1.load()` puts +//! every line into a multimap keyed by the text left of the first `=`, and +//! everything above it — factions, units, per-unit damage — is that multimap +//! queried by constructed key names. A parser that skipped this layer would +//! have to decide what a key means before it has read the `Factions=` line +//! that says, and would lose the lines it did not recognise. +//! +//! Reproduced from `megamek/common/scenario/ScenarioV1.java` (0.51.0), which +//! is the only specification there is. + +/// One `key=value` line, in file order. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Entry { + pub key: String, + pub value: String, + /// 1-based line number in the source, for error messages. + pub line: usize, +} + +/// A line MegaMek reads and we could not make sense of. It logs and carries +/// on, so these are never fatal — but a generator wants to know. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Warning { + pub line: usize, + pub text: String, + pub message: &'static str, +} + +/// A parsed `.mms` file, still purely lexical. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct Document { + pub entries: Vec, + pub warnings: Vec, +} + +impl Document { + /// Read a file the way `ScenarioV1.load()` does. + /// + /// Blank lines and comment lines are dropped, where a comment is a line + /// whose *trimmed* form starts with `#` — so an indented comment counts, + /// and a `#` after any other character does not start one. A line with no + /// `=` is an error MegaMek logs and skips; it becomes a warning here. Key + /// and value are both trimmed, and the split is on the first `=` only, so + /// a value may contain any number more. + /// + /// A key may repeat. MegaMek keeps every value in order and joins them + /// with `,` on read, which is load-bearing: `Damage` on one unit is + /// written as repeated lines. + pub fn parse(text: &str) -> Document { + let mut doc = Document::default(); + for (i, raw) in text.lines().enumerate() { + let line = i + 1; + // MegaMek strips a UTF-8 BOM nowhere, but Files.readAllLines + // decodes it into the first line's leading char, where it would + // become part of the first key. Dropping it is the difference + // between reading MMSVersion and not. + let raw = if line == 1 { + raw.strip_prefix('\u{feff}').unwrap_or(raw) + } else { + raw + }; + // Java's String.isBlank() and our trim() agree on whitespace for + // every character these files contain. + let trimmed = raw.trim(); + if trimmed.is_empty() || trimmed.starts_with('#') { + continue; + } + match raw.split_once('=') { + Some((key, value)) => doc.entries.push(Entry { + key: key.trim().to_string(), + value: value.trim().to_string(), + line, + }), + None => doc.warnings.push(Warning { + line, + text: trimmed.to_string(), + message: "no '=' in the line; MegaMek logs this and skips it", + }), + } + } + doc + } + + /// Every value for a key, in file order. + pub fn values(&self, key: &str) -> Vec<&str> { + self.entries + .iter() + .filter(|e| e.key == key) + .map(|e| e.value.as_str()) + .collect() + } + + /// What `getString(key)` returns: the values joined with `,`, or `None` + /// when the key is absent. + /// + /// The join is why `Factions=A,B` and two `Factions=` lines are the same + /// thing to MegaMek, and why a repeated key is not an error. + pub fn get(&self, key: &str) -> Option { + let values = self.values(key); + if values.is_empty() { + None + } else { + Some(values.join(",")) + } + } + + /// `getString(key, separator)`, for the one caller that wants a different + /// join: pilot advantages are space-separated. + pub fn get_joined(&self, key: &str, separator: &str) -> Option { + let values = self.values(key); + if values.is_empty() { + None + } else { + Some(values.join(separator)) + } + } + + /// `parseInt(key, default)`. MegaMek's `MathUtility.parseInt` swallows a + /// bad number and returns the default, so a typo reads as "not set" + /// rather than as an error. + pub fn get_int(&self, key: &str, default: i32) -> i32 { + self.get(key) + .and_then(|v| v.trim().parse::().ok()) + .unwrap_or(default) + } + + /// `parseBoolean`: absent means the default; present means `true`, `on` + /// or `1`, case-insensitively, and anything else is false. + pub fn get_bool(&self, key: &str, default: bool) -> bool { + match self.get(key) { + None => default, + Some(v) => { + let v = v.trim(); + v.eq_ignore_ascii_case("true") + || v.eq_ignore_ascii_case("on") + || v.eq_ignore_ascii_case("1") + } + } + } + + pub fn contains(&self, key: &str) -> bool { + self.entries.iter().any(|e| e.key == key) + } + + /// Render back to `.mms` text: one `key=value` per line, in order. + /// + /// Not a byte-for-byte round trip of a hand-written file — comments and + /// blank lines are gone, and spacing around `=` is normalised — but a + /// re-parse of the output is equal to the input, which is the property + /// that matters and the one `corpus.rs` checks. + pub fn render(&self) -> String { + let mut out = String::new(); + for entry in &self.entries { + out.push_str(&entry.key); + out.push('='); + out.push_str(&entry.value); + out.push('\n'); + } + out + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn splits_on_the_first_equals_only() { + let doc = Document::parse("Description=a=b=c\n"); + assert_eq!(doc.get("Description").as_deref(), Some("a=b=c")); + } + + #[test] + fn trims_around_the_separator() { + let doc = Document::parse(" Name = First Run \n"); + assert_eq!(doc.get("Name").as_deref(), Some("First Run")); + } + + #[test] + fn drops_blank_and_comment_lines_including_indented_ones() { + let doc = Document::parse("# a\n\n # b\nName=x\n"); + assert_eq!(doc.entries.len(), 1); + assert!(doc.warnings.is_empty()); + } + + #[test] + fn a_hash_that_is_not_first_does_not_start_a_comment() { + let doc = Document::parse("Name=Trial #3\n"); + assert_eq!(doc.get("Name").as_deref(), Some("Trial #3")); + } + + #[test] + fn a_line_without_an_equals_warns_and_is_skipped() { + let doc = Document::parse("Name=x\nnonsense\n"); + assert_eq!(doc.entries.len(), 1); + assert_eq!(doc.warnings.len(), 1); + assert_eq!(doc.warnings[0].line, 2); + } + + #[test] + fn repeated_keys_join_with_a_comma() { + let doc = Document::parse("Factions=A\nFactions=B\n"); + assert_eq!(doc.get("Factions").as_deref(), Some("A,B")); + assert_eq!(doc.values("Factions"), vec!["A", "B"]); + } + + #[test] + fn a_bom_does_not_become_part_of_the_first_key() { + let doc = Document::parse("\u{feff}MMSVersion=1\n"); + assert_eq!(doc.get("MMSVersion").as_deref(), Some("1")); + } + + #[test] + fn an_empty_value_is_present_and_empty() { + let doc = Document::parse("Maps=\n"); + assert_eq!(doc.get("Maps").as_deref(), Some("")); + assert!(doc.contains("Maps")); + } + + #[test] + fn booleans_follow_megameks_vocabulary() { + assert!(Document::parse("S=true\n").get_bool("S", false)); + assert!(Document::parse("S=ON\n").get_bool("S", false)); + assert!(Document::parse("S=1\n").get_bool("S", false)); + assert!(!Document::parse("S=yes\n").get_bool("S", true)); + assert!(Document::parse("Other=1\n").get_bool("S", true)); + } + + #[test] + fn a_bad_integer_reads_as_the_default() { + assert_eq!( + Document::parse("BoardWidth=wide\n").get_int("BoardWidth", 1), + 1 + ); + } + + #[test] + fn render_round_trips_through_a_reparse() { + // Keys and values, not line numbers: rendering drops the comments and + // blank lines, so everything after one moves up. That is the whole + // difference, and it is why `line` is source position rather than + // part of the content. + fn pairs(doc: &Document) -> Vec<(&str, &str)> { + doc.entries + .iter() + .map(|e| (e.key.as_str(), e.value.as_str())) + .collect() + } + let doc = Document::parse("# c\nName=x\nFactions=A\nFactions=B\n"); + let again = Document::parse(&doc.render()); + assert_eq!(pairs(&again), pairs(&doc)); + } +} diff --git a/crates/mms/src/lib.rs b/crates/mms/src/lib.rs new file mode 100644 index 0000000..7264c5f --- /dev/null +++ b/crates/mms/src/lib.rs @@ -0,0 +1,173 @@ +//! MegaMek scenario files (`.mms`), read and written. +//! +//! There is no published specification for this format. What there is, +//! and what this crate follows line for line, is +//! `megamek/common/scenario/ScenarioV1.java` and `ScenarioLoader.java` in +//! MegaMek 0.51.0 — the version arena's image pins. Where MegaMek is +//! surprising, this is surprising the same way, and says so: the server that +//! runs the match is the authority, and a parser that read a file more +//! sensibly than the server does would be wrong in the one situation that +//! matters. +//! +//! Two formats share the extension. `MMSVersion=1` is the properties-style +//! one every scenario a match can be launched into uses; it is read and +//! written here in full ([`v1`]). `MMSVersion: 2` is YAML and much larger, and +//! is read only as far as its top-level fields ([`v2`]) — enough to identify +//! and list one, which is all anything here needs. + +pub mod document; +pub mod v1; +pub mod v2; + +pub use document::Document; +pub use v1::ScenarioV1; +pub use v2::HeaderV2; + +/// Which of the two formats a file is written in. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Version { + V1, + V2, +} + +/// A file read as whichever format it declares. +#[derive(Debug, Clone)] +pub enum Scenario { + V1(ScenarioV1), + V2(HeaderV2), +} + +impl Scenario { + /// The scenario's title, however the file spells it. + pub fn name(&self) -> Option<&str> { + match self { + Scenario::V1(s) => s.name.as_deref(), + Scenario::V2(h) => h.name.as_deref(), + } + } + + pub fn description(&self) -> Option<&str> { + match self { + Scenario::V1(s) => s.description.as_deref(), + Scenario::V2(h) => h.description.as_deref(), + } + } + + pub fn version(&self) -> Version { + match self { + Scenario::V1(_) => Version::V1, + Scenario::V2(_) => Version::V2, + } + } +} + +/// Why a file could not be read. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Error { + /// No `MMSVersion` line, or one naming a version that is not 1 or 2. + /// MegaMek's own message for this is "The scenario file lacks scenario + /// version info!", and it is the same refusal either way. + NoVersion, + /// A version 1 file that does not parse. + V1(v1::ParseError), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::NoVersion => f.write_str("the scenario file lacks scenario version info!"), + Error::V1(e) => write!(f, "{e}"), + } + } +} + +impl std::error::Error for Error {} + +/// Which version a file declares, by MegaMek's own rule. +/// +/// `ScenarioLoader.findMmsVersion` scans for the first non-comment line +/// matching `^\s*MMSVersion\s*[:=]\s*(\d)` and takes one digit. So `=` and `:` +/// are both accepted whichever format the file turns out to be, the line need +/// not be first, and a two-digit version would read as its first digit — which +/// is upstream's problem to have, not ours to improve on. +pub fn detect_version(text: &str) -> Option { + for line in text.lines() { + if line.trim_start().starts_with('#') { + continue; + } + let rest = line.trim_start(); + let Some(rest) = strip_prefix_ignore_ascii_case(rest, "MMSVersion") else { + continue; + }; + let rest = rest.trim_start(); + let Some(rest) = rest.strip_prefix([':', '=']) else { + continue; + }; + let rest = rest.trim_start(); + return match rest.bytes().next() { + Some(b'1') => Some(Version::V1), + Some(b'2') => Some(Version::V2), + _ => None, + }; + } + None +} + +/// Read a scenario, whichever version it is. +pub fn parse(text: &str) -> Result { + match detect_version(text) { + Some(Version::V1) => ScenarioV1::parse(text).map(Scenario::V1).map_err(Error::V1), + Some(Version::V2) => Ok(Scenario::V2(HeaderV2::parse(text))), + None => Err(Error::NoVersion), + } +} + +/// Java's `Pattern.compile("MMSVersion")` is case-sensitive, so this is too — +/// the helper exists to keep the comparison explicit rather than to relax it. +fn strip_prefix_ignore_ascii_case<'a>(value: &'a str, prefix: &str) -> Option<&'a str> { + value.strip_prefix(prefix) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn detects_both_spellings_of_the_version_line() { + assert_eq!(detect_version("MMSVersion=1\n"), Some(Version::V1)); + assert_eq!(detect_version("MMSVersion: 2\n"), Some(Version::V2)); + assert_eq!(detect_version(" MMSVersion = 1\n"), Some(Version::V1)); + } + + #[test] + fn the_version_line_need_not_be_first() { + let text = "# a long licence header\n#\nName=x\nMMSVersion=1\n"; + assert_eq!(detect_version(text), Some(Version::V1)); + } + + #[test] + fn a_commented_out_version_does_not_count() { + assert_eq!(detect_version("# MMSVersion=1\n"), None); + } + + #[test] + fn no_version_line_is_refused() { + assert_eq!(parse("Factions=A,B\n").unwrap_err(), Error::NoVersion); + } + + #[test] + fn an_unknown_version_is_refused() { + assert_eq!(detect_version("MMSVersion=3\n"), None); + } + + #[test] + fn reads_each_version_as_itself() { + let v1 = parse("MMSVersion=1\nName=A\nFactions=X\nUnit_X_1=Wasp WSP-1A,P,4,5\n").unwrap(); + assert_eq!(v1.version(), Version::V1); + assert_eq!(v1.name(), Some("A")); + + let v2 = parse("MMSVersion: 2\nname: B\n").unwrap(); + assert_eq!(v2.version(), Version::V2); + assert_eq!(v2.name(), Some("B")); + } +} diff --git a/crates/mms/src/v1.rs b/crates/mms/src/v1.rs new file mode 100644 index 0000000..a21c4fb --- /dev/null +++ b/crates/mms/src/v1.rs @@ -0,0 +1,701 @@ +//! `MMSVersion=1`: the properties-style scenario format, read and written. +//! +//! Everything here follows `megamek/common/scenario/ScenarioV1.java` (0.51.0). +//! Where MegaMek does something surprising, this does the same thing and says +//! so — a parser that quietly improved on it would disagree with the server +//! that actually runs the match, which is the one way this can be worse than +//! useless. + +use crate::document::Document; + +/// A scenario, as much of one as V1 can express. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct ScenarioV1 { + pub name: Option, + pub description: Option, + pub board: Board, + pub factions: Vec, + /// `SinglePlayer=true`: the first faction is the human and the rest are + /// Princess. arena decides this from the manifest instead, so it is read + /// and carried rather than obeyed. + pub single_player: bool, + pub fixed_game_options: bool, + pub fixed_planetary_conditions: bool, + /// Keys that are not a faction's, a unit's, or one of the handful of + /// scenario-level ones — kept so a rewrite does not drop them. + pub other: Vec<(String, String)>, + /// Things MegaMek will not do with this file that its author probably + /// meant it to. Never fatal; see [`Quirk`]. + pub quirks: Vec, +} + +/// The board: `n` map sheets of `w`×`h` hexes, named by `Maps`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Board { + /// Hexes across one map sheet. MegaMek's default when unset. + pub map_width: i32, + pub map_height: i32, + /// Sheets across and down. + pub board_width: i32, + pub board_height: i32, + /// One entry per sheet, in column-major order — `(x, y)` is index + /// `y * board_width + x`. `RANDOM` picks any board in scope, and a + /// `rotate:` prefix flips the sheet. + pub maps: Vec, + /// Subdirectories of `data/boards` a `RANDOM` sheet may be drawn from. + pub random_dirs: Vec, + pub bridge_cf: Option, +} + +impl Default for Board { + fn default() -> Self { + // ScenarioV1.createBoard's own fallbacks. + Board { + map_width: 16, + map_height: 17, + board_width: 1, + board_height: 1, + maps: Vec::new(), + random_dirs: Vec::new(), + bridge_cf: None, + } + } +} + +/// One side. The name is the join key everything else in the file is built +/// from, and the one arena's manifest calls a slot. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Faction { + pub name: String, + /// `Location_`, a starting edge; "Any" when the file does not say. + pub location: String, + /// `team_` — lower case, because that is the key MegaMek looks up. + /// See [`Quirk::UnreadFactionKey`]. + pub team: Option, + /// `camo_`, as the file spells it: `category,filename`. + pub camo: Option, + pub minefields: Option, + pub units: Vec, +} + +/// One unit. `id` is the `` of `Unit__` — any string without an +/// underscore, not necessarily a number. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Unit { + pub id: String, + /// The unit as `MekSummaryCache` knows it, e.g. `Wasp WSP-1A`. + pub chassis: String, + pub pilot: String, + pub gunnery: i32, + pub piloting: i32, + /// Present only in the six-field pilot form. + pub gender: Option, + pub clan: Option, + /// The facing token exactly as the file writes it, present whenever a + /// position is. MegaMek only acts on `N`, `NE`, `SE`, `S`, `SW` and `NW`, + /// case-insensitively, and silently ignores anything else — several + /// shipped scenarios say `E` or `W`, and those units face north. Keep the + /// token; ask [`Unit::facing_applied`] what the game will do with it. + pub facing: Option, + /// As written in the file: 1-based hex column and row. MegaMek subtracts + /// one on load; keeping the file's own numbers means a re-render is the + /// same file. + pub position: Option<(i32, i32)>, + /// `Unit___` lines, in file order: `Damage`, + /// `AutoEject`, `Advantages` and the rest. + pub data: Vec<(String, String)>, +} + +/// Something MegaMek does not read, or reads differently than it looks. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Quirk { + /// A `Team_X` or `Camo_X` line. MegaMek builds those keys as `team_X` + /// and `camo_X` and looks them up in a case-sensitive map, so a + /// capitalised one is never read — the faction gets the default team + /// (one per faction, in file order) instead of the one the file names. + /// Most of the shipped scenarios that set teams set them this way. + UnreadFactionKey { key: String, read_as: String }, + /// A `Unit_..._` line whose key MegaMek's switch has no case for, + /// which it logs as "Unknown unit data key" and drops. `CritHit` is the + /// one that matters: every shipped scenario spells it that way and + /// ScenarioV1 looks for `CriticalHit`. + UnknownUnitDataKey { key: String }, + /// A `Unit_..._camo` or `..._altitude` line. Both are real settings, but + /// the pattern that finds unit data requires the suffix to start with a + /// capital, so neither is ever matched. + UnmatchableUnitDataKey { key: String }, + /// A facing MegaMek's switch has no case for — `E` and `W` are the ones + /// the shipped scenarios use. The unit is placed where the file says and + /// faces north regardless. + UnusedFacing { unit: String, facing: String }, +} + +/// A file that does not parse at all. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ParseError { + pub message: String, +} + +impl std::fmt::Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.message) + } +} + +impl std::error::Error for ParseError {} + +fn err(message: impl Into) -> Result { + Err(ParseError { + message: message.into(), + }) +} + +impl ScenarioV1 { + /// Parse an already-lexed document. + pub fn from_document(doc: &Document) -> Result { + // ScenarioV1.createPlayers: no Factions line, or an empty one, is the + // one thing it refuses outright. + let Some(raw_factions) = doc.get("Factions") else { + return err("the scenario does not contain any factions (players)!"); + }; + if raw_factions.is_empty() { + return err("the scenario does not contain any factions (players)!"); + } + let faction_names: Vec = raw_factions.split(',').map(str::to_string).collect(); + + let mut scenario = ScenarioV1 { + name: doc.get("Name"), + description: doc.get("Description"), + board: Board { + map_width: doc.get_int("MapWidth", 16), + map_height: doc.get_int("MapHeight", 17), + board_width: doc.get_int("BoardWidth", 1), + board_height: doc.get_int("BoardHeight", 1), + maps: doc + .get("Maps") + .map(|m| m.split(',').map(str::to_string).collect()) + .unwrap_or_default(), + random_dirs: doc + .get("RandomDirs") + .map(|m| m.split(',').map(str::to_string).collect()) + .unwrap_or_default(), + bridge_cf: doc.get("BridgeCF").and_then(|v| v.trim().parse().ok()), + }, + single_player: doc.get_bool("SinglePlayer", false), + fixed_game_options: doc.get_bool("FixedGameOptions", false), + fixed_planetary_conditions: doc.get_bool("FixedPlanetaryConditions", false), + ..Default::default() + }; + + // Which keys belong to a faction or one of its units. Everything left + // over is scenario-level and is carried through untouched. + let mut claimed: Vec = Vec::new(); + + for name in &faction_names { + let mut faction = Faction { + name: name.clone(), + location: doc + .get(&format!("Location_{name}")) + .unwrap_or_else(|| "Any".to_string()), + // Lower case: getFactionParam builds "team_" + faction. + team: doc + .get(&format!("team_{name}")) + .and_then(|v| v.trim().parse().ok()), + camo: doc.get(&format!("camo_{name}")), + minefields: doc.get(&format!("Minefields_{name}")), + units: Vec::new(), + }; + claimed.push(format!("Location_{name}")); + claimed.push(format!("team_{name}")); + claimed.push(format!("camo_{name}")); + claimed.push(format!("Minefields_{name}")); + + // The capitalised spellings, which MegaMek does not read. Worth + // saying out loud rather than silently doing what it does — and + // deliberately not claimed, so they fall through to `other` and + // survive a rewrite. Dropping a line because the game ignores it + // would edit somebody's scenario on their behalf. + for (spelled, read_as) in [("Team", "team"), ("Camo", "camo")] { + let key = format!("{spelled}_{name}"); + if doc.contains(&key) { + scenario.quirks.push(Quirk::UnreadFactionKey { + key, + read_as: format!("{read_as}_{name}"), + }); + } + } + + let prefix = format!("Unit_{name}_"); + // Two passes, because a data line names the unit it belongs to + // and MegaMek drops one whose unit was never declared. + for entry in &doc.entries { + let Some(rest) = entry.key.strip_prefix(&prefix) else { + continue; + }; + if rest.contains('_') { + continue; + } + let unit = parse_entity_line(rest, &entry.value)?; + if unit.facing.is_some() && unit.facing_applied().is_none() { + scenario.quirks.push(Quirk::UnusedFacing { + unit: entry.key.clone(), + facing: unit.facing.clone().unwrap_or_default(), + }); + } + faction.units.push(unit); + claimed.push(entry.key.clone()); + } + for entry in &doc.entries { + let Some(rest) = entry.key.strip_prefix(&prefix) else { + continue; + }; + let mut parts = rest.split('_'); + let (Some(id), Some(data_key), None) = (parts.next(), parts.next(), parts.next()) + else { + continue; + }; + if !starts_upper_ascii(data_key) { + // The pattern requires [A-Z]; `camo` and `altitude` are + // real settings that can never match it. Not claimed, for + // the same reason Team_ is not: it stays in the file. + if matches!(data_key, "camo" | "altitude") { + scenario.quirks.push(Quirk::UnmatchableUnitDataKey { + key: entry.key.clone(), + }); + } + continue; + } + let Some(unit) = faction.units.iter_mut().find(|u| u.id == id) else { + // "Data for undeclared unit encountered, ignoring". + continue; + }; + if !KNOWN_UNIT_DATA.contains(&data_key) { + scenario.quirks.push(Quirk::UnknownUnitDataKey { + key: entry.key.clone(), + }); + } + unit.data.push((data_key.to_string(), entry.value.clone())); + claimed.push(entry.key.clone()); + } + + scenario.factions.push(faction); + } + + for entry in &doc.entries { + if SCENARIO_KEYS.contains(&entry.key.as_str()) || claimed.contains(&entry.key) { + continue; + } + scenario + .other + .push((entry.key.clone(), entry.value.clone())); + } + + Ok(scenario) + } + + /// Parse from text. + pub fn parse(text: &str) -> Result { + ScenarioV1::from_document(&Document::parse(text)) + } + + /// Render to `.mms` text. + /// + /// Field order follows the shipped scenarios: what the file is, then the + /// board, then the factions, then each faction's units and their data. + /// A parse of the output equals the scenario that produced it. + pub fn render(&self) -> String { + let mut out = String::new(); + out.push_str("MMSVersion=1\n"); + if let Some(name) = &self.name { + out.push_str(&format!("Name={name}\n")); + } + if let Some(description) = &self.description { + out.push_str(&format!("Description={description}\n")); + } + if self.single_player { + out.push_str("SinglePlayer=true\n"); + } + if self.fixed_game_options { + out.push_str("FixedGameOptions=true\n"); + } + if self.fixed_planetary_conditions { + out.push_str("FixedPlanetaryConditions=true\n"); + } + + let default = Board::default(); + if self.board.map_width != default.map_width { + out.push_str(&format!("MapWidth={}\n", self.board.map_width)); + } + if self.board.map_height != default.map_height { + out.push_str(&format!("MapHeight={}\n", self.board.map_height)); + } + out.push_str(&format!("BoardWidth={}\n", self.board.board_width)); + out.push_str(&format!("BoardHeight={}\n", self.board.board_height)); + if !self.board.maps.is_empty() { + out.push_str(&format!("Maps={}\n", self.board.maps.join(","))); + } + if !self.board.random_dirs.is_empty() { + out.push_str(&format!( + "RandomDirs={}\n", + self.board.random_dirs.join(",") + )); + } + if let Some(cf) = self.board.bridge_cf { + out.push_str(&format!("BridgeCF={cf}\n")); + } + + for (key, value) in &self.other { + out.push_str(&format!("{key}={value}\n")); + } + + let names: Vec<&str> = self.factions.iter().map(|f| f.name.as_str()).collect(); + out.push_str(&format!("Factions={}\n", names.join(","))); + + for faction in &self.factions { + let name = &faction.name; + out.push_str(&format!("Location_{name}={}\n", faction.location)); + // Lower case, so that MegaMek reads it. Writing `Team_` would + // look right and do nothing. + if let Some(team) = faction.team { + out.push_str(&format!("team_{name}={team}\n")); + } + if let Some(camo) = &faction.camo { + out.push_str(&format!("camo_{name}={camo}\n")); + } + if let Some(minefields) = &faction.minefields { + out.push_str(&format!("Minefields_{name}={minefields}\n")); + } + for unit in &faction.units { + out.push_str(&format!("Unit_{name}_{}={}\n", unit.id, unit.entity_line())); + for (key, value) in &unit.data { + out.push_str(&format!("Unit_{name}_{}_{key}={value}\n", unit.id)); + } + } + } + out + } +} + +/// The six tokens MegaMek's facing switch has a case for. +pub const FACINGS: [&str; 6] = ["N", "NE", "SE", "S", "SW", "NW"]; + +impl Unit { + /// The facing the game will actually apply, or `None` when the file's + /// token is one MegaMek does not recognise and the unit therefore faces + /// north. + pub fn facing_applied(&self) -> Option { + let raw = self.facing.as_ref()?; + let upper = raw.to_ascii_uppercase(); + FACINGS.contains(&upper.as_str()).then_some(upper) + } + + /// The right-hand side of a `Unit_` line, in the form it was read from. + pub fn entity_line(&self) -> String { + let mut parts = vec![ + self.chassis.clone(), + self.pilot.clone(), + self.gunnery.to_string(), + self.piloting.to_string(), + ]; + if let (Some(gender), Some(clan)) = (self.gender, self.clan) { + parts.push(gender.to_string()); + parts.push(clan.to_string()); + } + if let Some((x, y)) = self.position { + parts.push(self.facing.clone().unwrap_or_else(|| "N".to_string())); + parts.push(x.to_string()); + parts.push(y.to_string()); + } + parts.join(",") + } +} + +/// `parseEntityLine`, quirks included. +fn parse_entity_line(id: &str, line: &str) -> Result { + let parts: Vec<&str> = line.split(',').collect(); + if parts.len() < 4 { + return err(format!("Unparsable entity line: {line}")); + } + // The gender form is detected by part 4 being a number, because a facing + // never is. + let gendered = parts.len() > 4 && is_java_int(parts[4]); + let (gender, clan, mut i) = if gendered { + if parts.len() < 6 { + return err(format!("Unparsable entity line: {line}")); + } + ( + Some(parse_int(parts[4], line)?), + // Boolean.parseBoolean: "true" in any case is true, everything + // else — including nonsense — is false, and never an error. + Some(parts[5].eq_ignore_ascii_case("true")), + 6usize, + ) + } else { + (None, None, 4usize) + }; + + let mut facing = None; + let mut position = None; + // The length test is on the whole line, not on what is left after the + // pilot, so a gendered line with exactly seven fields walks off the end + // and is rejected. Reproduced rather than fixed. + if parts.len() >= 7 { + let facing_raw = parts.get(i).ok_or_else(|| ParseError { + message: format!("Unparsable entity line: {line}"), + })?; + i += 1; + let x = parse_int( + parts.get(i).ok_or_else(|| ParseError { + message: format!("Unparsable entity line: {line}"), + })?, + line, + )?; + i += 1; + let y = parse_int( + parts.get(i).ok_or_else(|| ParseError { + message: format!("Unparsable entity line: {line}"), + })?, + line, + )?; + facing = Some((*facing_raw).to_string()); + position = Some((x, y)); + } + + Ok(Unit { + id: id.to_string(), + chassis: parts[0].to_string(), + pilot: parts[1].to_string(), + gunnery: parse_int(parts[2], line)?, + piloting: parse_int(parts[3], line)?, + gender, + clan, + facing, + position, + data: Vec::new(), + }) +} + +/// `Integer.parseInt`: strict, and an optional leading sign. +fn parse_int(value: &str, line: &str) -> Result { + value.parse::().map_err(|_| ParseError { + message: format!("Unparsable entity line: {line}"), + }) +} + +/// The `-?\d+` test that picks the gendered form. +fn is_java_int(value: &str) -> bool { + let digits = value.strip_prefix('-').unwrap_or(value); + !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit()) +} + +fn starts_upper_ascii(value: &str) -> bool { + value.bytes().next().is_some_and(|b| b.is_ascii_uppercase()) +} + +/// Unit data keys ScenarioV1's switch actually has a case for. `CritHit` is +/// absent on purpose: the shipped files use it and ScenarioV1 looks for +/// `CriticalHit`, so it is dropped. +const KNOWN_UNIT_DATA: &[&str] = &[ + "Damage", + "DamageSpecific", + "CriticalHit", + "SetAmmoTo", + "SetAmmoType", + "PilotHits", + "ExternalID", + "Advantages", + "AutoEject", + "Commander", + "DeploymentRound", +]; + +/// Keys read off the scenario itself, so not carried in `other`. +const SCENARIO_KEYS: &[&str] = &[ + "MMSVersion", + "Name", + "Description", + "Factions", + "SinglePlayer", + "FixedGameOptions", + "FixedPlanetaryConditions", + "MapWidth", + "MapHeight", + "BoardWidth", + "BoardHeight", + "Maps", + "RandomDirs", + "BridgeCF", +]; + +#[cfg(test)] +mod tests { + use super::*; + + const FIRST_RUN: &str = "\ +MMSVersion=1 +Name=First Run +Description=Training scenario +BoardWidth=2 +BoardHeight=1 +Maps=Map Set 2/16x17 BattleTech,Map Set 2/16x17 BattleTech +Factions=TraineeA,TraineeB +Location_TraineeA=S +Location_TraineeB=N +Unit_TraineeA_1=Wasp WSP-1A,Chuck,4,5,N,3,2 +Unit_TraineeB_1=Stinger STG-3R,Dale,4,5 +"; + + #[test] + fn reads_the_shape_of_a_scenario() { + let s = ScenarioV1::parse(FIRST_RUN).unwrap(); + assert_eq!(s.name.as_deref(), Some("First Run")); + assert_eq!(s.factions.len(), 2); + assert_eq!(s.factions[0].location, "S"); + assert_eq!(s.board.board_width, 2); + assert_eq!(s.board.maps.len(), 2); + } + + #[test] + fn a_placed_unit_keeps_the_files_own_coordinates() { + let s = ScenarioV1::parse(FIRST_RUN).unwrap(); + let unit = &s.factions[0].units[0]; + assert_eq!(unit.chassis, "Wasp WSP-1A"); + assert_eq!(unit.pilot, "Chuck"); + assert_eq!((unit.gunnery, unit.piloting), (4, 5)); + assert_eq!(unit.facing.as_deref(), Some("N")); + assert_eq!(unit.position, Some((3, 2))); + } + + #[test] + fn an_unplaced_unit_has_no_position() { + let s = ScenarioV1::parse(FIRST_RUN).unwrap(); + assert_eq!(s.factions[1].units[0].position, None); + } + + #[test] + fn a_faction_with_no_location_defaults_to_any() { + let s = ScenarioV1::parse("Factions=A\nUnit_A_1=Wasp WSP-1A,P,4,5\n").unwrap(); + assert_eq!(s.factions[0].location, "Any"); + } + + #[test] + fn no_factions_line_is_the_one_fatal_error() { + assert!(ScenarioV1::parse("Name=x\n").is_err()); + assert!(ScenarioV1::parse("Factions=\n").is_err()); + } + + #[test] + fn a_faction_name_may_contain_underscores() { + // Jade_Falcon and Jade_Falcon_ToP are both factions in + // TrialOfPosition.mms, and only the quoted-name match tells their + // unit keys apart. + let text = "Factions=Jade_Falcon,Jade_Falcon_ToP\n\ + Unit_Jade_Falcon_1=Wasp WSP-1A,P,4,5\n\ + Unit_Jade_Falcon_ToP_1=Stinger STG-3R,Q,4,5\n\ + Unit_Jade_Falcon_ToP_1_Damage=2\n"; + let s = ScenarioV1::parse(text).unwrap(); + assert_eq!(s.factions[0].units.len(), 1); + assert_eq!(s.factions[0].units[0].chassis, "Wasp WSP-1A"); + assert_eq!(s.factions[1].units.len(), 1); + assert_eq!( + s.factions[1].units[0].data, + vec![("Damage".into(), "2".into())] + ); + } + + #[test] + fn the_gendered_pilot_form_is_detected_by_a_numeric_field() { + let s = + ScenarioV1::parse("Factions=A\nUnit_A_1=Wasp WSP-1A,P,4,5,1,false,NE,3,4\n").unwrap(); + let unit = &s.factions[0].units[0]; + assert_eq!(unit.gender, Some(1)); + assert_eq!(unit.clan, Some(false)); + assert_eq!(unit.facing.as_deref(), Some("NE")); + assert_eq!(unit.position, Some((3, 4))); + } + + #[test] + fn a_gendered_line_of_exactly_seven_fields_is_rejected_as_megamek_rejects_it() { + // i lands on 6, and parts 7 and 8 are not there. The length test + // being on the whole line rather than the remainder is the bug; it + // is reproduced because the server behaves this way. + assert!(ScenarioV1::parse("Factions=A\nUnit_A_1=Wasp WSP-1A,P,4,5,1,false,NE\n").is_err()); + } + + #[test] + fn an_unrecognised_facing_is_kept_but_does_nothing() { + // AFewCrystalsMore.mms places four units facing "W". MegaMek's switch + // has no case for it, so they face north; the token still has to + // survive a rewrite, or re-rendering the file would change it. + let s = ScenarioV1::parse("Factions=A\nUnit_A_1=Wasp WSP-1A,P,4,5,W,3,4\n").unwrap(); + let unit = &s.factions[0].units[0]; + assert_eq!(unit.facing.as_deref(), Some("W")); + assert_eq!(unit.facing_applied(), None); + assert_eq!(unit.position, Some((3, 4))); + assert!(matches!(s.quirks[0], Quirk::UnusedFacing { .. })); + assert!(s.render().contains(",W,3,4")); + } + + #[test] + fn a_recognised_facing_is_normalised_only_when_asked() { + let s = ScenarioV1::parse("Factions=A\nUnit_A_1=Wasp WSP-1A,P,4,5,ne,3,4\n").unwrap(); + assert_eq!(s.factions[0].units[0].facing.as_deref(), Some("ne")); + assert_eq!( + s.factions[0].units[0].facing_applied().as_deref(), + Some("NE") + ); + assert!(s.quirks.is_empty()); + } + + #[test] + fn a_bad_gunnery_is_fatal() { + assert!(ScenarioV1::parse("Factions=A\nUnit_A_1=Wasp WSP-1A,P,x,5\n").is_err()); + } + + #[test] + fn capitalised_team_and_camo_are_recorded_as_unread() { + let s = ScenarioV1::parse("Factions=A\nTeam_A=1\nCamo_A=Clans,Wolf.jpg\n").unwrap(); + assert_eq!(s.factions[0].team, None); + assert_eq!(s.quirks.len(), 2); + assert!(matches!(s.quirks[0], Quirk::UnreadFactionKey { .. })); + } + + #[test] + fn a_lower_case_team_is_the_one_that_reads() { + let s = ScenarioV1::parse("Factions=A\nteam_A=3\n").unwrap(); + assert_eq!(s.factions[0].team, Some(3)); + assert!(s.quirks.is_empty()); + } + + #[test] + fn crithit_is_recorded_as_a_key_megamek_drops() { + let s = ScenarioV1::parse("Factions=A\nUnit_A_1=Wasp WSP-1A,P,4,5\nUnit_A_1_CritHit=1:2\n") + .unwrap(); + assert!(matches!(s.quirks[0], Quirk::UnknownUnitDataKey { .. })); + // Still carried: dropping it here would lose it on a rewrite. + assert_eq!(s.factions[0].units[0].data.len(), 1); + } + + #[test] + fn data_for_a_unit_that_was_never_declared_is_dropped() { + let s = ScenarioV1::parse("Factions=A\nUnit_A_1=Wasp WSP-1A,P,4,5\nUnit_A_9_Damage=2\n") + .unwrap(); + assert_eq!(s.factions[0].units.len(), 1); + assert!(s.factions[0].units[0].data.is_empty()); + } + + #[test] + fn unknown_scenario_keys_survive_a_rewrite() { + let s = + ScenarioV1::parse("Factions=A\nUnit_A_1=Wasp WSP-1A,P,4,5\nGameOptionsFile=o.xml\n") + .unwrap(); + assert_eq!(s.other, vec![("GameOptionsFile".into(), "o.xml".into())]); + assert!(s.render().contains("GameOptionsFile=o.xml")); + } + + #[test] + fn rendering_and_reparsing_gives_back_the_same_scenario() { + let s = ScenarioV1::parse(FIRST_RUN).unwrap(); + assert_eq!(ScenarioV1::parse(&s.render()).unwrap(), s); + } +} diff --git a/crates/mms/src/v2.rs b/crates/mms/src/v2.rs new file mode 100644 index 0000000..a9f0791 --- /dev/null +++ b/crates/mms/src/v2.rs @@ -0,0 +1,179 @@ +//! `MMSVersion: 2`: read far enough to tell what the file is, and no further. +//! +//! V2 is YAML, and a real reader of it is a YAML parser plus MegaMek's whole +//! Jackson schema — trigger trees, victory conditions, per-unit message +//! blocks, Strategic BattleForce. Nothing here launches one: arena plays V1, +//! and what this project generates is V1. +//! +//! So this reads the top-level scalars and stops. That is enough to name a +//! file in a listing, to tell a Total Warfare scenario from an SBF one, and — +//! the actual reason it exists — to let a sweep over MegaMek's scenario +//! library account for every file instead of skipping the ones it cannot +//! read. Anything nested is skipped by indentation rather than guessed at. + +/// The top of a V2 scenario. Absent fields were not at the top level, which is +/// not the same as not being in the file. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct HeaderV2 { + pub name: Option, + pub description: Option, + pub planet: Option, + /// `gametype`, absent for the Total Warfare default. + pub gametype: Option, + /// Top-level keys seen, in order, whether or not they were read. + pub keys: Vec, +} + +impl HeaderV2 { + /// True when this is a game arena does not host — Strategic BattleForce + /// and the rest. MegaMek's own default is Total Warfare. + pub fn is_total_warfare(&self) -> bool { + match self.gametype.as_deref() { + None => true, + Some(g) => g.eq_ignore_ascii_case("TW"), + } + } + + /// Read the top-level mapping. + /// + /// Handles what the shipped scenarios use at that level: `key: value`, + /// `key: >` and `key: |` block scalars, and nested blocks, which are + /// recorded by name and skipped. Quotes around a scalar are stripped; + /// anchors, aliases and flow mappings are not interpreted, so a value + /// using them is kept verbatim rather than misread. + pub fn parse(text: &str) -> HeaderV2 { + let mut header = HeaderV2::default(); + let lines: Vec<&str> = text.lines().collect(); + let mut i = 0; + while i < lines.len() { + let line = lines[i]; + i += 1; + let trimmed = line.trim(); + if trimmed.is_empty() || trimmed.starts_with('#') || trimmed == "---" { + continue; + } + // Top level only: anything indented belongs to the key above it. + if line.starts_with(char::is_whitespace) || line.starts_with('-') { + continue; + } + let Some((key, rest)) = line.split_once(':') else { + continue; + }; + let key = key.trim().to_string(); + let rest = rest.trim(); + header.keys.push(key.clone()); + + let value = if rest == ">" || rest == "|" || rest == ">-" || rest == "|-" { + // A block scalar: every following line indented under it. + let mut block: Vec = Vec::new(); + while i < lines.len() { + let next = lines[i]; + if next.trim().is_empty() { + block.push(String::new()); + i += 1; + continue; + } + if !next.starts_with(char::is_whitespace) { + break; + } + block.push(next.trim().to_string()); + i += 1; + } + while block.last().is_some_and(|l| l.is_empty()) { + block.pop(); + } + // Folded (`>`) joins with spaces, literal (`|`) with + // newlines. Blank lines in a folded block are paragraph + // breaks; none of the shipped files have one. + Some(if rest.starts_with('>') { + block.join(" ") + } else { + block.join("\n") + }) + } else if rest.is_empty() { + // A nested block. Recorded above, skipped here. + None + } else { + Some(unquote(rest).to_string()) + }; + + let Some(value) = value else { continue }; + match key.as_str() { + "name" => header.name = Some(value), + "description" => header.description = Some(value), + "planet" => header.planet = Some(value), + "gametype" => header.gametype = Some(value), + _ => {} + } + } + header + } +} + +fn unquote(value: &str) -> &str { + let bytes = value.as_bytes(); + if bytes.len() >= 2 + && ((bytes[0] == b'"' && bytes[bytes.len() - 1] == b'"') + || (bytes[0] == b'\'' && bytes[bytes.len() - 1] == b'\'')) + { + &value[1..value.len() - 1] + } else { + value + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn reads_the_top_level_scalars() { + let text = "\ +MMSVersion: 2 +name: Test Scenario V2 +planet: Bone Norman +map: Test.board +"; + let h = HeaderV2::parse(text); + assert_eq!(h.name.as_deref(), Some("Test Scenario V2")); + assert_eq!(h.planet.as_deref(), Some("Bone Norman")); + assert!(h.is_total_warfare()); + } + + #[test] + fn folds_a_folded_block_scalar_onto_one_line() { + let text = "name: X\ndescription: >\n one line\n and another\n\nplanet: Y\n"; + let h = HeaderV2::parse(text); + assert_eq!(h.description.as_deref(), Some("one line and another")); + assert_eq!(h.planet.as_deref(), Some("Y")); + } + + #[test] + fn keeps_the_line_breaks_of_a_literal_block_scalar() { + let text = "description: |\n one line\n and another\nplanet: Y\n"; + let h = HeaderV2::parse(text); + assert_eq!(h.description.as_deref(), Some("one line\nand another")); + } + + #[test] + fn skips_a_nested_block_but_records_its_key() { + let text = "name: X\nplanetaryconditions:\n light: dusk\nplanet: Y\n"; + let h = HeaderV2::parse(text); + assert_eq!(h.planet.as_deref(), Some("Y")); + assert!(h.keys.iter().any(|k| k == "planetaryconditions")); + } + + #[test] + fn an_sbf_scenario_is_not_total_warfare() { + let h = HeaderV2::parse("gametype: SBF\nname: X\n"); + assert!(!h.is_total_warfare()); + } + + #[test] + fn strips_quotes_from_a_scalar() { + assert_eq!( + HeaderV2::parse("name: \"Quoted\"\n").name.as_deref(), + Some("Quoted") + ); + } +} diff --git a/crates/mms/tests/corpus.rs b/crates/mms/tests/corpus.rs new file mode 100644 index 0000000..e7053d4 --- /dev/null +++ b/crates/mms/tests/corpus.rs @@ -0,0 +1,192 @@ +//! Read every scenario MegaMek ships and account for all of them. +//! +//! The claim this crate makes is that it reads the real format, not a tidy +//! subset of it, and the only way to hold that claim honest is the whole +//! library: 48 files written by many hands over twenty years, including the +//! ones with factions whose names contain underscores, pilots with a gender +//! field, damage plans, and a scenario whose author put a `#` in the middle of +//! a title. +//! +//! `tests/corpus/` is `data/scenarios/` from the MegaMek 0.51.0 release, minus +//! the three benchmark scenarios arena adds, copied verbatim. Refreshing it is +//! part of bumping the arena image. +//! +//! Note what this does *not* claim. That a file parses is not that MegaMek can +//! launch it: eleven of these name boards or portraits the image does not +//! have, which only loading them in MegaMek finds out. arena's +//! `tests/scenario-load.sh` is that check; this one is about the text. + +use std::fs; +use std::path::{Path, PathBuf}; + +use headquarters_mms::v1::Quirk; +use headquarters_mms::{Scenario, ScenarioV1, Version, detect_version, parse}; + +fn corpus() -> Vec { + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/corpus"); + let mut out = Vec::new(); + collect(&root, &mut out); + out.sort(); + assert!( + out.len() >= 48, + "expected the MegaMek 0.51.0 scenario library, found {} files", + out.len() + ); + out +} + +fn collect(dir: &Path, out: &mut Vec) { + for entry in fs::read_dir(dir).expect("corpus directory is readable") { + let path = entry.expect("corpus entry is readable").path(); + if path.is_dir() { + collect(&path, out); + } else if path.extension().is_some_and(|e| e == "mms") { + out.push(path); + } + } +} + +fn read(path: &Path) -> String { + // Not from_utf8: several of these carry a stray high byte in a pilot name, + // and MegaMek reads them anyway. A parser that refused the file over one + // character would be refusing a scenario that works. + String::from_utf8_lossy(&fs::read(path).expect("scenario is readable")).into_owned() +} + +#[test] +fn every_scenario_declares_a_version_we_know() { + for path in corpus() { + let text = read(&path); + assert!( + detect_version(&text).is_some(), + "{}: no MMSVersion line", + path.display() + ); + } +} + +#[test] +fn every_scenario_parses_as_the_version_it_declares() { + let mut v1 = 0; + let mut v2 = 0; + for path in corpus() { + let text = read(&path); + match parse(&text) { + Ok(Scenario::V1(_)) => v1 += 1, + Ok(Scenario::V2(_)) => v2 += 1, + Err(e) => panic!("{}: {e}", path.display()), + } + } + // The split is a fact about this release, and a bump that changes it is + // worth noticing: upstream is converting scenarios to V2 one at a time. + assert_eq!((v1, v2), (41, 7), "the V1/V2 split of the library changed"); +} + +#[test] +fn every_version_1_scenario_has_factions_and_units() { + for path in corpus() { + let text = read(&path); + if detect_version(&text) != Some(Version::V1) { + continue; + } + let scenario = ScenarioV1::parse(&text).expect("parses"); + assert!( + !scenario.factions.is_empty(), + "{}: no factions", + path.display() + ); + assert!( + scenario.factions.iter().any(|f| !f.units.is_empty()), + "{}: no faction fields a single unit", + path.display() + ); + // A unit whose chassis is empty would be one the entity-line split + // silently mangled. + for faction in &scenario.factions { + for unit in &faction.units { + assert!( + !unit.chassis.trim().is_empty(), + "{}: {} has a unit with no chassis", + path.display(), + faction.name + ); + } + } + } +} + +#[test] +fn every_version_1_scenario_survives_a_render_and_reparse() { + for path in corpus() { + let text = read(&path); + if detect_version(&text) != Some(Version::V1) { + continue; + } + let scenario = ScenarioV1::parse(&text).expect("parses"); + let again = ScenarioV1::parse(&scenario.render()) + .unwrap_or_else(|e| panic!("{}: re-parse failed: {e}", path.display())); + assert_eq!( + scenario, + again, + "{}: rendering changed the scenario", + path.display() + ); + } +} + +#[test] +fn no_scenario_line_is_dropped_for_want_of_an_equals() { + // MegaMek logs and skips a line with no `=`. None of the shipped files + // has one, so any warning here means the reader is splitting lines the + // wrong way rather than that the library is untidy. + for path in corpus() { + let text = read(&path); + if detect_version(&text) != Some(Version::V1) { + continue; + } + let doc = headquarters_mms::Document::parse(&text); + assert!( + doc.warnings.is_empty(), + "{}: {:?}", + path.display(), + doc.warnings + ); + } +} + +/// The library's own quirks, counted. These are all cases where MegaMek reads +/// less than the file says, and they are load-bearing knowledge for anything +/// generating scenarios: a generator that writes `Team_A` because the shipped +/// files do would produce a file whose teams do nothing. +#[test] +fn the_shipped_scenarios_use_keys_megamek_does_not_read() { + let mut unread_faction = 0; + let mut unknown_unit = 0; + let mut unmatchable = 0; + let mut unused_facing = 0; + for path in corpus() { + let text = read(&path); + if detect_version(&text) != Some(Version::V1) { + continue; + } + for quirk in ScenarioV1::parse(&text).expect("parses").quirks { + match quirk { + Quirk::UnreadFactionKey { .. } => unread_faction += 1, + Quirk::UnknownUnitDataKey { .. } => unknown_unit += 1, + Quirk::UnmatchableUnitDataKey { .. } => unmatchable += 1, + Quirk::UnusedFacing { .. } => unused_facing += 1, + } + } + } + // Capitalised Team_/Camo_ lines, which are looked up as team_/camo_ and + // so never read; CritHit, which ScenarioV1 spells CriticalHit; a + // lower-case unit Camo the data-key pattern cannot match; and units + // placed facing E or W, which the facing switch has no case for. + assert!( + unread_faction > 0 && unknown_unit > 0 && unused_facing > 0, + "the library used to contain all of these, and a release that fixed \ + them is worth knowing about: {unread_faction} unread faction keys, \ + {unknown_unit} unknown unit-data keys, {unmatchable} unmatchable, \ + {unused_facing} unused facings" + ); +} diff --git a/crates/mms/tests/corpus/1stSomersetStrikers/1-ClashInTheCanyon.mms b/crates/mms/tests/corpus/1stSomersetStrikers/1-ClashInTheCanyon.mms new file mode 100644 index 0000000..3d314da --- /dev/null +++ b/crates/mms/tests/corpus/1stSomersetStrikers/1-ClashInTheCanyon.mms @@ -0,0 +1,38 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# MegaMek Conversion by Phillip "bullseyesmith" Starke +MMSVersion=1 +Name=CLASH IN THE CANYON (EPISODE 2) +Description=Strikers find two clan meks in a narrow canyon. +BoardWidth=1 +BoardHeight=2 +Maps=scenarios/DustballCanyon1,scenarios/DustballCanyon2 +Factions=1stSomersetStrikers,BlackVision +Location_1stSomersetStrikers=N +Location_BlackVision=any +# Strikers Lance: +Unit_1stSomersetStrikers_1=Axman AXM-2N,Major Adam Steiner,2,2,S,11,06 +Unit_1stSomersetStrikers_2=Mauler MAL-1R,Captain Zachary Miles Hawkins,3,4,S,12,05 +Unit_1stSomersetStrikers_3=Wolfhound WLF-2, Lieutenant Ciro Ramirez,4,5,S,12,06 +# Strikers Air Support: +# Unit_1stSomersetStrikers_4=Banshee BSE-X2, Cadet Katiara Kylie,4,3 +# Black Vision, Falcon Star: +Unit_BlackVision_1=Vulture (Mad Dog) Prime,Star Colonel Kristen Redmond,2,2,SW,07,21 +Unit_BlackVision_2=Hunchback IIC (Standard),MekWarrior Deval,3,4,S,06,20 +Unit_BlackVision_1_Damage=2 +Unit_BlackVision_2_Damage=2 diff --git a/crates/mms/tests/corpus/1stSomersetStrikers/2-DustballShowdown.mms b/crates/mms/tests/corpus/1stSomersetStrikers/2-DustballShowdown.mms new file mode 100644 index 0000000..7011542 --- /dev/null +++ b/crates/mms/tests/corpus/1stSomersetStrikers/2-DustballShowdown.mms @@ -0,0 +1,50 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# MegaMek Conversion by Phillip "bullseyesmith" Starke +MMSVersion=1 +Name=DUSTBALL SHOWDOWN (EPISODE 2) +Description=The Strikers must slow down the Clan forces. +BoardWidth=2 +BoardHeight=1 +Maps=scenarios/blankmapsheet,scenarios/blankmapsheet +Factions=BlackVision,1stSomersetStrikers +Location_BlackVision=W +Location_1stSomersetStrikers=any +# Black Vision, Cluster Command Star: +Unit_BlackVision_1=Vulture (Mad Dog) Prime,Star Colonel Kristen Redmond,2,2,NE,01,15 +Unit_BlackVision_2=Vulture (Mad Dog) Prime,MekWarrior Tanya Pryde,2,3,NE,01,14 +Unit_BlackVision_3=Hunchback IIC (Standard),MekWarrior Deval,3,4,NE,01,16 +Unit_BlackVision_4=Thor (Summoner) M,MekWarrior Gengra,3,4,NE,01,13 +Unit_BlackVision_5=Thor (Summoner) M,MekWarrior Pith,3,4,NE,01,17 +# Black Vision, Alpha Talon: +Unit_BlackVision_6=Mad Cat (Timber Wolf) Prime,Star Commander Cyrill Hazen,2,3,S,01,10 +Unit_BlackVision_7=Mad Cat (Timber Wolf) Prime,MekWarrior Jentry,3,4,S,01,11 +Unit_BlackVision_8=Thor (Summoner) M,MekWarrior Fatha,3,4,S,01,09 +Unit_BlackVision_9=Thor (Summoner) M,MekWarrior Korthan,3,4,S,01,12 +Unit_BlackVision_10=Hunchback IIC (Standard),MekWarrior Damil,3,4,S,01,08 +# Strikers Lance: +Unit_1stSomersetStrikers_1=Axman AXM-2N,Major Adam Steiner,2,2,SW,17,09 +Unit_1stSomersetStrikers_2=Mauler MAL-1R,Captain Zachary Miles Hawkins,3,4,SW,17,08 +Unit_1stSomersetStrikers_3=Centurion CN9-D,Captain Valten Ryder,4,3,SW,17,07 +Unit_1stSomersetStrikers_4=Wolfhound WLF-2, Lieutenant Ciro Ramirez,4,5,SW,17,06 +# Strikers Air Support: +# Unit_1stSomersetStrikers_5=Banshee BSE-X2, Cadet Katiara Kylie,4,3 +Unit_BlackVision_1_Damage=1 +Unit_BlackVision_2_Damage=1 +Unit_BlackVision_3_Damage=1 +Unit_BlackVision_4_Damage=1 diff --git a/crates/mms/tests/corpus/1stSomersetStrikers/3-TheTakingOfWotan.mms b/crates/mms/tests/corpus/1stSomersetStrikers/3-TheTakingOfWotan.mms new file mode 100644 index 0000000..9eb78ea --- /dev/null +++ b/crates/mms/tests/corpus/1stSomersetStrikers/3-TheTakingOfWotan.mms @@ -0,0 +1,49 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# MegaMek Conversion by Phillip "bullseyesmith" Starke +MMSVersion=1 +Name=THE TAKING OF WOTAN (EPISODE 3) +Description=The Falcon's Claws battles the command group of the Wotan Tamar March Militia (TMM). +BoardWidth=1 +BoardHeight=2 +Maps=Map Set 2/16x17 BattleTech,scenarios/citytech(Wotan) +# Place Medium Elevation 2 buildings in the following six hexes: 0727, 0729, 0731, 0926, 0928, 0930 +Factions=FalconsClaws,WotanTMM +Location_FalconsClaws=N +Location_WotanTMM=any +# Falcon's Claws, Command Star: +Unit_FalconsClaws_1=Thor (Summoner) M,Star Colonel Nicolia Malthus,1,2,S,07,03 +Unit_FalconsClaws_2=Mad Cat (Timber Wolf) Prime,MekWarrior Douglas,3,4,S,06,02 +Unit_FalconsClaws_3=Mad Cat (Timber Wolf) Prime,MekWarrior Jala,3,4,S,08,02 +Unit_FalconsClaws_4=Vulture (Mad Dog) Prime,MekWarrior Leopold,3,4,S,05,02 +Unit_FalconsClaws_5=Vulture (Mad Dog) Prime,MekWarrior Erin,3,4,S,09,02 +# Wotan TMM, Command Lance: +Unit_WotanTMM_1=Mauler MAL-1R,Lt. General Ansel Cabrillo,4,3,N,10,34 +Unit_WotanTMM_2=Centurion CN9-A,MekWarrior Wilma Stenson,4,4,N,08,34 +Unit_WotanTMM_3=Centurion CN9-A,MekWarrior Petrov Himmelmann,5,5,N,06,34 +Unit_WotanTMM_4=Centurion CN9-A,MekWarrior Abel Rodriguez,6,6,N,12,34 +# Wotan TMM, Recon Lance: +Unit_WotanTMM_5=Centurion CN9-D,Lieutenant Pamela Genaro,5,4,N,08,18 +Unit_WotanTMM_6=Wolfhound WLF-1,Sergeant Edward Fitgibbons,6,5,N,11,17 +Unit_WotanTMM_7=Wolfhound WLF-1,MekWarrior John Kerouac,6,6,N,05,15 +Unit_WotanTMM_8=Wolfhound WLF-1,MekWarrior Cedric Quentin,6,6,N,03,15 +Unit_FalconsClaws_1_Advantages=tactical_genius +Unit_FalconsClaws_2_Advantages=tactical_genius +Unit_FalconsClaws_3_Advantages=tactical_genius +Unit_FalconsClaws_4_Advantages=tactical_genius +Unit_FalconsClaws_5_Advantages=tactical_genius diff --git a/crates/mms/tests/corpus/1stSomersetStrikers/4-RaceForTheKwaidan.mms b/crates/mms/tests/corpus/1stSomersetStrikers/4-RaceForTheKwaidan.mms new file mode 100644 index 0000000..071d777 --- /dev/null +++ b/crates/mms/tests/corpus/1stSomersetStrikers/4-RaceForTheKwaidan.mms @@ -0,0 +1,43 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# MegaMek Conversion by Phillip "bullseyesmith" Starke +MMSVersion=1 +Name=RACE FOR THE KWAIDAN (EPISODE 4) +Description=The Strikers must retreat to the DropShip Kwaidan. +BoardWidth=1 +BoardHeight=2 +Maps=Map Set 2/16x17 BattleTech,Map Set 2/16x17 BattleTech +Factions=BlackVision,1stSomersetStrikers +Location_BlackVision=any +Location_1stSomersetStrikers=any +# Black Vision, Falcon Star: +Unit_BlackVision_1=Vulture (Mad Dog) Prime,Star Colonel Kristen Redmond,2,2,S,08,04 +Unit_BlackVision_2=Mad Cat (Timber Wolf) Prime,MekWarrior Jentry,3,4,S,01,11 +Unit_BlackVision_3=Mad Cat (Timber Wolf) Prime,MekWarrior Fatha,3,4,S,01,09 +Unit_BlackVision_4=Vulture (Mad Dog) Prime,MekWarrior Korthan,3,4,S,01,12 +Unit_BlackVision_5=Hunchback IIC (Standard),MekWarrior Damil,3,4,S,01,08 +# Strikers Lance: +Unit_1stSomersetStrikers_1=Axman AXM-2N,Major Adam Steiner,2,2,SW,17,09 +Unit_1stSomersetStrikers_2=Mauler MAL-1R,Captain Zachary Miles Hawkins,3,4,SW,17,08 +Unit_1stSomersetStrikers_3=Centurion CN9-D,Captain Valten Ryder,4,3,SW,17,07 +Unit_1stSomersetStrikers_1_DamageSpecific=N0:5,N1:14,R1:3,N2:11,R2:3,N3:11,R3:3,N4:9,N5:9,N6:12,N7:12 +Unit_1stSomersetStrikers_2_DamageSpecific=N0:5,N1:14,R1:5,N2:13,R2:5,N3:13,R3:5,N4:11,N5:11,N6:11,N7:11 +Unit_1stSomersetStrikers_3_DamageSpecific=N0:5,N1:9,R1:4,N2:7,R2:3,N3:7,R3:3,N4:8,N5:8,N6:8,N7:8 +Unit_1stSomersetStrikers_1_SetAmmoTo=3:6-4,3:7-4 +Unit_1stSomersetStrikers_2_SetAmmoTo=2:8-4,2:9-4,2:10-22,3:8-4,3:9-4,3:10-22 +Unit_1stSomersetStrikers_3_SetAmmoTo=2:4-0,2:6-6,2:7-6 diff --git a/crates/mms/tests/corpus/1stSomersetStrikers/5-BattleInTheSwamp.mms b/crates/mms/tests/corpus/1stSomersetStrikers/5-BattleInTheSwamp.mms new file mode 100644 index 0000000..92ebfa6 --- /dev/null +++ b/crates/mms/tests/corpus/1stSomersetStrikers/5-BattleInTheSwamp.mms @@ -0,0 +1,43 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# MegaMek Conversion by Phillip "bullseyesmith" Starke +MMSVersion=1 +Name=BATTLE IN THE SWAMP (EPISODE 7) +Description=Battle in Waldorff's swamps. +BoardWidth=1 +BoardHeight=1 +Maps=Map Set 4/16x17 River Delta 2 +Factions=BlackVision,1stSomersetStrikers +Location_BlackVision=N +Location_1stSomersetStrikers=S +# Black Vision, Falcon Star: +Unit_BlackVision_1=Vulture (Mad Dog) Prime,Star Colonel Kristen Redmond,2,2 +Unit_BlackVision_2=Elemental Battle Armor [Laser],Point Commander Yartak,4,4 +Unit_BlackVision_3=Elemental Battle Armor [Laser],Elemental Harris,4,4 +# Strikers Lance: +Unit_1stSomersetStrikers_1=Axman AXM-2N,Major Adam Steiner,2,2 +Unit_1stSomersetStrikers_2=Mauler MAL-1R,Captain Zachary Miles Hawkins,3,4 +Unit_1stSomersetStrikers_3=Bushwacker BSW-X1,Captain Valten Ryder,4,3 +Unit_1stSomersetStrikers_4=Infiltrator Mk. I Battle Armor 'Waddle', Cadet Katiara Kylie,5,5 +Unit_1stSomersetStrikers_5=Sloth Battle Armor (Standard), Franklin Sakamoto,5,5 +# Initial Damage to Units: +Unit_1stSomersetStrikers_4_DamageSpecific=N1:0,N2:0,N3:0 +Unit_1stSomersetStrikers_5_DamageSpecific=N1:0,N2:0,N3:0 +Unit_BlackVision_2_DamageSpecific=N1:0,N2:0,N3:0,N4:0 +Unit_BlackVision_3_DamageSpecific=N1:0,N2:0,N3:0,N4:0 +Unit_BlackVision_1_CritHit=4:6,5:6 diff --git a/crates/mms/tests/corpus/1stSomersetStrikers/6-EnemyOfMyEnemy.mms b/crates/mms/tests/corpus/1stSomersetStrikers/6-EnemyOfMyEnemy.mms new file mode 100644 index 0000000..d06e02b --- /dev/null +++ b/crates/mms/tests/corpus/1stSomersetStrikers/6-EnemyOfMyEnemy.mms @@ -0,0 +1,46 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# MegaMek Conversion by Phillip "bullseyesmith" Starke +MMSVersion=1 +Name=ENEMY OF MY ENEMY (EPISODE 10) +Description=The Strikers are looking for Adam's brother Andrew. Nicolai Malthus has laid a trap for them. Kristen Redmond has orders to stop Malthus from Galaxy Commander Chistu. +BoardWidth=2 +BoardHeight=1 +Maps=Map Set 3/16x17 Desert Mountain 1,Map Set 3/16x17 Desert Mountain 2 +Factions=MalthusStar,RedmondStar,1stSomersetStrikers +Location_MalthusStar=any +Location_RedmondStar=any +Location_1stSomersetStrikers=any +# Malthus's Star: +Unit_MalthusStar_1=Thor (Summoner) M,Star Colonel Nicolai Malthus,1,2,N,14,12 +Unit_MalthusStar_2=Mad Cat (Timber Wolf) Prime,MekWarrior Ciro,4,5,N,14,11 +Unit_MalthusStar_3=Hunchback IIC (Standard),MekWarrior Ross,3,4,N,14,13 +Unit_MalthusStar_4=Hunchback IIC (Standard),MekWarrior Uri,3,4,N,15,12 +Unit_MalthusStar_5=Hunchback IIC (Standard),MekWarrior Elaine,3,4,N,13,12 +# Redmond's Star: +Unit_RedmondStar_1=Vulture (Mad Dog) Prime,Star Colonel Kristen Redmond,2,2,NE,01,10 +Unit_RedmondStar_2=Hunchback IIC (Standard),MekWarrior Pytor,2,2,NE,01,09 +Unit_RedmondStar_3=Mad Cat (Timber Wolf) Prime,MekWarrior Jentry,3,4,NE,01,11 +Unit_RedmondStar_4=Mad Cat (Timber Wolf) Prime,MekWarrior Elston,3,4,NE,02,10 +Unit_RedmondStar_5=Mad Cat (Timber Wolf) Prime,MekWarrior Althea,3,4,NE,02,09 +# Strikers Lance: +Unit_1stSomersetStrikers_1=Axman AXM-2N,Major Adam Steiner,2,2,S,20,06 +Unit_1stSomersetStrikers_2=Mauler MAL-1R,Captain Zachary Miles Hawkins,3,4,S,20,07 +Unit_1stSomersetStrikers_3=Bushwacker BSW-X1,Captain Valten Ryder,4,3,S,21,06 +Unit_1stSomersetStrikers_4=Packrat LRPV PKR-T5,Ernest (Patch) McGuire,7,7,S,22,07 +Unit_1stSomersetStrikers_4_Advantages=tactical_genius diff --git a/crates/mms/tests/corpus/1stSomersetStrikers/7-ChaosInTheJungle.mms b/crates/mms/tests/corpus/1stSomersetStrikers/7-ChaosInTheJungle.mms new file mode 100644 index 0000000..a42095b --- /dev/null +++ b/crates/mms/tests/corpus/1stSomersetStrikers/7-ChaosInTheJungle.mms @@ -0,0 +1,49 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# MegaMek Conversion by Phillip "bullseyesmith" Starke +MMSVersion=1 +Name=CHAOS IN THE JUNGLE (EPISODE 11) +Description=The Strikers are trapped between two warring factions while trying to free Franklin Sakamoto. +BoardWidth=2 +BoardHeight=1 +Maps=Map Set 4/16x17 Heavy Forest 1,Map Set 4/16x17 Heavy Forest 2 +Factions=ISF,1stSomersetStrikers,Rebels +Location_ISF=any +Location_1stSomersetStrikers=any +Location_Rebels=any +# ISF Lance: +Unit_ISF_1=Centurion CN9-D,Toshiro Okura,2,3,N,08,18 +Unit_ISF_2=Centurion CN9-D,ISF MekWarrior,3,4,N,08,18 +Unit_ISF_3=Centurion CN9-A,ISF MekWarrior,3,4,N,12,34 +Unit_ISF_4=Centurion CN9-A,ISF MekWarrior,3,4,N,12,34 +# 1st Somerset Strikers: +Unit_1stSomersetStrikers_1=Awesome AWS-9Ma,Major Adam Steiner,2,2,SW,32,14 +Unit_1stSomersetStrikers_2=Mauler MAL-1R,Captain Zachary Miles Hawkins,3,4,SW,32,16 +Unit_1stSomersetStrikers_3=Bushwacker BSW-X1,Captain Valten Ryder,4,3,NW,32,03 +Unit_1stSomersetStrikers_4=Hatamoto-Chi HTM-27T,Franklin Sakamoto,3,2,SW,15,09 +# 1st Somerset Strikers Air Support: +# Unit_1stSomersetStrikers_5=Banshee BSE-X2, Cadet Katiara Kylie,4,3 +# Rebel Lance: +Unit_Rebels_1=Wolfhound WLF-1,Rebel Lance Commander,3,4,N,14,07 +Unit_Rebels_2=Wolfhound WLF-1,Rebel MekWarrior,4,5,N,14,10 +Unit_Rebels_3=Wolfhound WLF-1,Rebel MekWarrior,4,5,N,15,07 +Unit_Rebels_4=Wolfhound WLF-1,Rebel MekWarrior,4,5,N,15,11 +Unit_1stSomersetStrikers_1_Advantages=tactical_genius +Unit_ISF_1_Damage=1 +Unit_Rebels_1_Damage=1 +Unit_Rebels_2_Damage=1 diff --git a/crates/mms/tests/corpus/1stSomersetStrikers/8-TheLiberationOfSMA.mms b/crates/mms/tests/corpus/1stSomersetStrikers/8-TheLiberationOfSMA.mms new file mode 100644 index 0000000..ab05496 --- /dev/null +++ b/crates/mms/tests/corpus/1stSomersetStrikers/8-TheLiberationOfSMA.mms @@ -0,0 +1,59 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# MegaMek Conversion by Phillip "bullseyesmith" Starke +MMSVersion=1 +Name=THE LIBERATION OF SOMERSET MILITARY ACADEMY (EPISODE 12) +Description=The Strikers battle the clan garrison on Somerset. The Somerset cadets are armed and join the fight. +BoardWidth=1 +BoardHeight=2 +Maps=scenarios/citytech(SMA),Map Set 2/16x17 BattleTech +# Place Light Elevation 1 buildings in Hexes 0606, 0608, 0610, 1006, 1008, and 1010. Place a Medium Elevation 2 building in Hex 0806. +Factions=1stSomersetStrikers,BlackVision +Location_1stSomersetStrikers=any +Location_BlackVision=any +# Strikers Lance: +Unit_1stSomersetStrikers_1=Awesome AWS-9Ma,Major Adam Steiner,1,1,N,16,08 +Unit_1stSomersetStrikers_2=Mauler MAL-1R,Captain Zachary Miles Hawkins,2,3,N,08,12 +Unit_1stSomersetStrikers_3=Bushwacker BSW-X1,Captain Valten Ryder,3,2,N,16,09 +Unit_1stSomersetStrikers_4=Infiltrator Mk. I Battle Armor 'Waddle',Cadet Katiara Kylie,4,4,N,16,10 +# Somerset Cadet Corps: +Unit_1stSomersetStrikers_5=Axman AXM-2N,Commander Andrew Steiner,2,3,N,07,06 +Unit_1stSomersetStrikers_6=Foot Platoon (Laser),A1,4,4,N,07,12 +Unit_1stSomersetStrikers_7=Foot Platoon (Laser),A2,4,4,N,07,13 +Unit_1stSomersetStrikers_8=Foot Platoon (Laser),A3,4,4,N,08,11 +Unit_1stSomersetStrikers_9=Foot Platoon (Laser),B1,4,4,N,08,13 +Unit_1stSomersetStrikers_10=Foot Platoon (Laser),B2,4,4,N,09,12 +Unit_1stSomersetStrikers_11=Foot Platoon (Laser),B3,4,4,N,09,13 +# Strikers Air Support: +# Unit_1stSomersetStrikers_12=Banshee BSE-X2,Franklin Sakamoto,3,4 +# Black Vision, Redmond's Star: +Unit_BlackVision_1=Vulture (Mad Dog) Prime,Star Colonel Kristen Redmond,2,2,S,08,01 +Unit_BlackVision_2=Hunchback IIC (Standard),MekWarrior Pytor,2,2,S,09,01 +Unit_BlackVision_3=Mad Cat (Timber Wolf) Prime,MekWarrior Jentry,3,4,S,10,01 +Unit_BlackVision_4=Mad Cat (Timber Wolf) Prime,MekWarrior Elston,3,4,S,06,01 +Unit_BlackVision_5=Mad Cat (Timber Wolf) Prime,MekWarrior Althea,3,4,S,07,01 +# Somerset Garrison Infantry: +Unit_BlackVision_6=Elemental Battle Armor [Laser],Elemental,4,4,S,08,06 +Unit_BlackVision_7=Clan Foot Point (Laser),Foot Point,4,4,S,08,08 +Unit_1stSomersetStrikers_1_Advantages=tactical_genius +Unit_1stSomersetStrikers_4_DamageSpecific=N1:0,N2:0,N3:0 +Unit_BlackVision_1_Damage=2 +Unit_BlackVision_2_Damage=1 +Unit_BlackVision_3_Damage=1 +Unit_BlackVision_4_Damage=1 +Unit_BlackVision_5_Damage=1 diff --git a/crates/mms/tests/corpus/1stSomersetStrikers/9-MalthusAttacks.mms b/crates/mms/tests/corpus/1stSomersetStrikers/9-MalthusAttacks.mms new file mode 100644 index 0000000..2db4314 --- /dev/null +++ b/crates/mms/tests/corpus/1stSomersetStrikers/9-MalthusAttacks.mms @@ -0,0 +1,77 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# MegaMek Conversion by Phillip "bullseyesmith" Starke +MMSVersion=1 +Name=MALTHUS ATTACKS (EPISODE 13) +Description=The Strikers battle the clan garrison on Somerset. The Somerset cadets are armed and join the fight. +BoardWidth=1 +BoardHeight=2 +Maps=scenarios/citytech(SMA),Map Set 2/16x17 BattleTech +# Place Light Elevation 1 buildings in Hexes 0606, 0608, 0610, 1006, 1008, and 1010. Place a Medium Elevation 2 building in Hex 0806. +Factions=FalconsClaws,1stSomersetStrikers +Location_FalconsClaws=S +Location_1stSomersetStrikers=any +# Command Star: +Unit_FalconsClaws_1=Thor (Summoner) M,Star Colonel Nicolai Malthus,1,2,N,08,33 +Unit_FalconsClaws_2=Mad Cat (Timber Wolf) Prime,MekWarrior Ciro,4,5,N,07,34 +Unit_FalconsClaws_3=Hunchback IIC (Standard),MekWarrior Ross,3,4,N,09,34 +Unit_FalconsClaws_4=Vulture (Mad Dog) Prime,MekWarrior Uri,3,4,N,06,34 +Unit_FalconsClaws_5=Vulture (Mad Dog) Prime,MekWarrior Elaine,3,4,N,10,34 +# Elemental Star: +Unit_FalconsClaws_6=Elemental Battle Armor [Laser],Commander Natalya,4,4,N,08,32 +Unit_FalconsClaws_7=Elemental Battle Armor [Laser],Point 2,4,4,N,06,33 +Unit_FalconsClaws_8=Elemental Battle Armor [Flamer],Point 3,4,4,N,07,33 +Unit_FalconsClaws_9=Elemental Battle Armor [MG],Point 4,4,4,N,09,33 +Unit_FalconsClaws_10=Elemental Battle Armor [MG],Point 5,4,4,N,10,33 +# Strikers Lance: +Unit_1stSomersetStrikers_1=Awesome AWS-9Ma,Major Adam Steiner,1,1,N,16,08 +Unit_1stSomersetStrikers_2=Mauler MAL-1R,Captain Zachary Miles Hawkins,3,4,N,08,12 +Unit_1stSomersetStrikers_3=Bushwacker BSW-X1,Captain Valten Ryder,4,3,N,16,09 +Unit_1stSomersetStrikers_4=Sloth Battle Armor (Standard), Franklin Sakamoto,5,5,N,16,10 +# Somerset Cadet Corps: +Unit_1stSomersetStrikers_5=Axman AXM-2N,Commander Andrew Steiner,3,4,N,07,06 +Unit_1stSomersetStrikers_6=Foot Platoon (Laser),A1,5,5,N,07,12 +Unit_1stSomersetStrikers_7=Foot Platoon (Laser),A2,5,5,N,07,13 +Unit_1stSomersetStrikers_8=Foot Platoon (Laser),A3,5,5,N,08,11 +Unit_1stSomersetStrikers_9=Foot Platoon (Laser),B1,5,5,N,08,13 +Unit_1stSomersetStrikers_10=Foot Platoon (Laser),B2,5,5,N,09,12 +Unit_1stSomersetStrikers_11=Foot Platoon (Laser),B3,5,5,N,09,13 +# Strikers Replacements: +Unit_1stSomersetStrikers_12=Foot Platoon (Laser),C1,5,5,N,06,10 +Unit_1stSomersetStrikers_13=Foot Platoon (Laser),C2,5,5,N,06,08 +Unit_1stSomersetStrikers_14=Foot Platoon (Laser),C3,5,5,N,10,10 +# Strikers Air Support: +# Unit_1stSomersetStrikers_15=Banshee BSE-X2,Katiara Kylie,4,3 +# Initial Damage to Units: +Unit_1stSomersetStrikers_1_DamageSpecific= +Unit_1stSomersetStrikers_2_DamageSpecific= +Unit_1stSomersetStrikers_3_DamageSpecific= +Unit_1stSomersetStrikers_4_DamageSpecific=N1:0,N2:0,N3:0 +Unit_1stSomersetStrikers_5_DamageSpecific= +Unit_1stSomersetStrikers_6_DamageSpecific=N0:28 +Unit_1stSomersetStrikers_7_DamageSpecific=N0:28 +Unit_1stSomersetStrikers_8_DamageSpecific=N0:28 +Unit_1stSomersetStrikers_9_DamageSpecific=N0:28 +Unit_1stSomersetStrikers_10_DamageSpecific=N0:28 +Unit_1stSomersetStrikers_11_DamageSpecific=N0:28 +# (NOTE: this damage is used to reflect the strafing attacks of Katiara Kylie. If she did not survive the last scenario, omit it using the # before each line.) +Unit_FalconsClaws_1_Damage=1 +Unit_FalconsClaws_2_Damage=1 +Unit_FalconsClaws_3_Damage=1 +Unit_FalconsClaws_4_Damage=1 +Unit_FalconsClaws_5_Damage=1 diff --git a/crates/mms/tests/corpus/AFewCrystalsMore.mms b/crates/mms/tests/corpus/AFewCrystalsMore.mms new file mode 100644 index 0000000..8ae6995 --- /dev/null +++ b/crates/mms/tests/corpus/AFewCrystalsMore.mms @@ -0,0 +1,63 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +# Created by Lee "T-Bolt" Smith (tbolt_irc@yahoo.com)MMSVersion=1 +MMSVersion=1 +Name=A Few Crystals More. +Description=Rhonda's Irregulars Scenario Pack, A Few Crystals More. Page 44. +BoardWidth=1 +BoardHeight=2 +Maps=Map Set 2/16x17 Desert Hills,Map Set 2/16x17 Mountain Lake +Factions=Irregulars,WacosRangers +Location_Irregulars=N +Location_WacosRangers=S +# Irregulars 1st Command Lance: +Unit_Irregulars_1=Archer ARC-2R,Col Cranston Snord,1,2,S,06,01 +Unit_Irregulars_2=Thunderbolt TDR-5S,Lt Deb H'Chu,2,1,S,07,01 +Unit_Irregulars_3=Marauder MAD-3R,David Rowsch,5,3,S,08,01 +Unit_Irregulars_4=Wasp WSP-1A,John Malvinson,4,3,S,09,01 +# Irregulars 2nd Company (Shorty's Scavengers) Command Lance: +Unit_Irregulars_5=Rifleman RFL-3N,Cpt Samual "Shorty" Sneede,1,1,N,14,33 +Unit_Irregulars_6=Warhammer WHM-6R,Lt Jake Walmar,2,2,N,13,33 +Unit_Irregulars_7=Clint CLNT-2-3T,Marleen Danules,5,5,N,05,33 +Unit_Irregulars_8=Phoenix Hawk PXH-1,Carter Malvinson,6,5,N,06,33 +# Irregulars 3rd Company (Rhonda Snords Irregulars) Command Lance: +Unit_Irregulars_9=Shadow Hawk SHD-2H,Cpt Rhonda Snord,2,2,W,01,14 +Unit_Irregulars_10=Phoenix Hawk PXH-1,Lt Terry Malvinson,3,3,NE,01,16 +Unit_Irregulars_11=Griffin GRF-1N,Walker Roche,4,6,W,01,13 +Unit_Irregulars_12=Awesome AWS-8Q,Victoria Rose,4,4,NE,01,17 +# Irregulars 3rd Company, Fire Lance: +Unit_Irregulars_13=Rifleman RFL-3N,Lt Bright Thomlinson,2,1,NW,15,13 +Unit_Irregulars_14=Wolverine WVR-6R,Solomon Storm,2,3,NW,15,14 +Unit_Irregulars_15=Scorpion SCP-1N,Linda Thomlinson,6,3,E,15,15 +Unit_Irregulars_16=Warhammer WHM-6R,Blade Windall,4,4,E,15,16 +#WacosRangers, Command Lance: +Unit_WacosRangers_1=BattleMaster BLR-1G,Col Wayne Waco,1,2,N,07,12 +Unit_WacosRangers_2=Marauder MAD-3R,Cpt Akida Samsun,3,2,NE,09,13 +Unit_WacosRangers_3=Wasp WSP-1A,Cpt Reggie Randall,2,4,NE,09,14 +Unit_WacosRangers_4=Cyclops CP-10-Z,Sgt Lenny Markbright,3,3,N,08,12 +#WacosRangers, Noblle's Command Lance: +Unit_WacosRangers_5=Banshee BNC-3E,Mjr Paulus Noble,1,2,SW,05,14 +Unit_WacosRangers_6=Cyclops CP-10-Z,Cpt Jorge Delphinus,2,3,SW,05,12 +Unit_WacosRangers_7=Quickdraw QKD-4G,Lt Daverius Bunkerara,4,3,SW,06,14 +Unit_WacosRangers_8=Warhammer WHM-6R,Lt Marcus Aeolus Wernke,1,3,SW,06,13 +#WacosRangers, Sanchuie's Scout Lance: +Unit_WacosRangers_9=Javelin JVN-10N,Lt Troy Sanchuie,1,4,NE,10,16 +Unit_WacosRangers_10=Javelin JVN-10N,Sgt Eric Long,4,4,NE,11,16 +Unit_WacosRangers_11=Locust LCT-1V,Jolly Jim Smith,3,4,S,10,17 +Unit_WacosRangers_12=Locust LCT-1V,Anita Michei,2,5,S,09,17 diff --git a/crates/mms/tests/corpus/BrokenArrow.mms b/crates/mms/tests/corpus/BrokenArrow.mms new file mode 100644 index 0000000..5646ce0 --- /dev/null +++ b/crates/mms/tests/corpus/BrokenArrow.mms @@ -0,0 +1,37 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +MMSVersion=1 +Name=Broken Arrow +Description=A Lance of Strike Meks must take down a heavily damaged Assault and its escort +BoardWidth=2 +BoardHeight=1 +BridgeCF=100 +RandomDirs=Map Set 2,Map Set 3,Map Set 4,Map Set 5,Map Set 6,Map Set 7 +Maps=RANDOM +Factions=Strike,Assault +Location_Strike=W +Location_Assault=E +Unit_Strike_1=Wraith TR1,X,3,4 +Unit_Strike_2=Venom SDR-9KA,X,4,3 +Unit_Strike_3=Griffin GRF-3M,X,3,4 +Unit_Strike_4=Nightsky NGS-4S,X,4,4 +Unit_Assault_1=Kingfisher Prime,X,3,3 +Unit_Assault_2=Stalking Spider (Standard),X,4,5 +Unit_Assault_3=Stalking Spider (Standard),X,4,5 +Unit_Assault_1_Damage=12 diff --git a/crates/mms/tests/corpus/DownOnOurLevel/DownOnOurLevel.mms b/crates/mms/tests/corpus/DownOnOurLevel/DownOnOurLevel.mms new file mode 100644 index 0000000..e8e2529 --- /dev/null +++ b/crates/mms/tests/corpus/DownOnOurLevel/DownOnOurLevel.mms @@ -0,0 +1,74 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +MMSVersion=1 +Name=DownOnOurLevel +Description=The Wolves have come looking for a fair fight. Beasts don't deserve such considerations. **RECOMMEND USING the TacOps Double Blind and TacOps Sensor Rules.** + +BoardWidth=1 +BoardHeight=1 +Maps= unofficial/Kajis/16x17 RiverAmbush + +Factions=Drakons,Wolves +Location_Drakons=S +Location_Wolves=N +Team_Drakons=1 +Team_Wolves=2 + +Unit_Drakons_1=Grand Dragon DRG-5K,Kapten Oddbjørn Hultgren,3,4,N,06,17 +Unit_Drakons_2=Wolverine WVR-7K,Kavallerister Chris Yamashita,3,4,SE,02,13 +Unit_Drakons_3=Crab CRB-27,Kavallerister Dwane Holmes,3,4,SE,05,14 +Unit_Drakons_4=Phoenix Hawk PXH-3K,Kavallerister Tsuyoshi Oka,3,4,NW,16,08 +Unit_Drakons_5=Wolf Trap (Tora) WFT-1,Löjtnant Rick Miura,3,4,SW,11,10 +Unit_Drakons_6=Spider SDR-7M,Kavallerister David Solstrand,3,4,S,15,01 +Unit_Drakons_7=Panther PNT-10K,Kavallerister Øystein Andvik,3,4,SE,02,03 +Unit_Drakons_8=Locust LCT-3M,Kavallerister Jamie Henderson,3,4,SE,16,11 +Unit_Wolves_1=Black Hawk (Nova) A,Star Commander Svetlana Kerensky,3,4,NW,09,08 +Unit_Wolves_2=Fenris (Ice Ferret) Prime,MekWarrior Yidir,3,4,S,08,07 +Unit_Wolves_3=Dragonfly (Viper) B,MekWarrior Arja,3,4,S,08,05 +Unit_Wolves_4=Puma (Adder) Prime,MekWarrior Tynan,3,4,S,08,03 +Unit_Wolves_5=Uller (Kit Fox) B, MekWarrior Quincy,3,4,S,08,09 + +# Additional advantages to add to pilots. Most of these require the 'MaxTech +# Level3 Pilot Advantages' game option to be turned on. The possible values +# are: +# dodge_maneuver, maneuvering_ace, melee_specialist, pain_resistance +# Multiple advantages for one pilot are seperated by spaces +# Unit_PlayerA_2_Advantages=melee_specialist pain_resistance +# Unit_PlayerB_2_Advantages=dodge_maneuver + +Unit_Drakons_1_AutoEject=true +Unit_Drakons_2_AutoEject=true +Unit_Drakons_3_AutoEject=true +Unit_Drakons_4_AutoEject=true +Unit_Drakons_5_AutoEject=true +Unit_Drakons_6_AutoEject=true +Unit_Drakons_7_AutoEject=true +Unit_Drakons_8_AutoEject=true +Unit_Wolves_1_AutoEject=false +Unit_Wolves_2_AutoEject=false +Unit_Wolves_3_AutoEject=false +Unit_Wolves_4_AutoEject=false +Unit_Wolves_5_AutoEject=false + +Unit_Drakons_1_Commander=true +Unit_Wolves_1_Commander=true + +GameOptionsFile=DownOnOurLevel.xml + +Camo_Drakons=Free Rasalhague Republic/,3rd Drakons.jpg +Camo_Wolves=Clans/Wolf/,Beta Galaxy.jpg diff --git a/crates/mms/tests/corpus/DragonHunt.mms b/crates/mms/tests/corpus/DragonHunt.mms new file mode 100644 index 0000000..65426a8 --- /dev/null +++ b/crates/mms/tests/corpus/DragonHunt.mms @@ -0,0 +1,35 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +MMSVersion=1 +Name=DragonHunt +Description=A Kuritan Fire Support Lance must defend a key ridge against mixed elements of a Davion Planetary Assault. +BoardWidth=2 +BoardHeight=2 +Maps=scenarios/ridge,scenarios/ridge,Map Set 6/16x17 Woodland,Map Set 6/16x17 Woodland +Factions=Kurita,Davion +Location_Kurita=W +Location_Davion=E +Unit_Kurita_1=Dragon DRG-1C,Takahasi Su,3,3 +Unit_Kurita_2=Dragon DRG-1C,Mey Leu,4,4 +Unit_Kurita_3=Dragon DRG-1C,Tonkawa Chung,4,4 +Unit_Kurita_4=Dragon DRG-1C,Mahito Li,4,3 +Unit_Davion_1=Marauder MAD-3D,Simon Coates,3,3 +Unit_Davion_2=Marauder MAD-3D,Peter Brook,4,4 +Unit_Davion_3=Jenner JR7-F,Pedro Gustavez,3,3 +Unit_Davion_4=Jenner JR7-F,Fitz McAiber,4,3 diff --git a/crates/mms/tests/corpus/Examples/Example.mms b/crates/mms/tests/corpus/Examples/Example.mms new file mode 100644 index 0000000..ea61185 --- /dev/null +++ b/crates/mms/tests/corpus/Examples/Example.mms @@ -0,0 +1,273 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# +# A MegaMek Scenario file +# +# + +# Versionstamp required to be recognized as a Scenario file +MMSVersion=1 + +# Name of the scenario +Name=Example Scenario + +# Scenario description +Description=This is an example scenario to show different scenario features + +# Map Setup ------------------------------------------------------ +# Size of the map in mapboards +BoardWidth=2 +BoardHeight=1 + +# Directories to choose random boards from +RandomDirs=Map Set 2,Map Set 3,Map Set 4,Map Set 5,Map Set 6,Map Set 7 + +# Maps can be specified by name. The order is left-to-right, top-to-bottom +# Any unspecified boards will be set to RANDOM +Maps=RANDOM,RANDOM + +# Game/Rule Options ---------------------------------------------- +# This is an xml file which can be created by copying your +# mmconf/gameoptions.xml +# path is specified relative to the scenario file +# This is one way to set victory conditions +GameOptionsFile=Example_options.xml +# The Game Options can be fixed. In this case the Game Options Dialog shown before the +# scenario starts is skipped. +FixedGameOptions=true + +# Planetary Conditions ------------------------------------------ +# Planetary Conditions can be fixed. In this case the Planetary Conditions Dialog shown before the +# scenario starts is skipped. +FixedPlanetaryConditions=true +# Temperature: Only integer values are allowed +PlanetaryConditionsTemperature=-14 +PlanetaryConditionsGravity=1.12 +# Light: Default = Daylight; 1 = Dusk; 2 = Full Moon Night; 3 = Moonless Night; 4 = Pitch Black +PlanetaryConditionsLight=1 +# Weather: Default = None; 1/2/3/4 = Light/Moderate/Heavy/Gusting Rain; 5 = Downpour; +# 6/7/9 Light/Moderate/Heavy Snow; 8 = Snow Flurries; 10 = Sleet; 11 = Blizzard; +# 12 = Ice Storm; Anything else resolves to default. +PlanetaryConditionsWeather=13 +# Wind: Default = None; 1/2/3 = Light/Moderate/Strong Gale; 4 = Storm; 5 = Tornado F1-3; 6 = Tornado F4 +PlanetaryConditionsWind=4 +# Wind Direction: Default = Random; 0 = N; 1 = NE; 2 = SE; 3 = S; 4 = SW; 5 = NW +PlanetaryConditionsWindDir=2 +# Atmospheric Pressure: Default = Standard; 0 = Vacuum; 1 = Trace; 2 = Thin; 4 = High; 5 = Very High +PlanetaryConditionsAtmosphere=2 +# Fog: Default = No Fog; 1 = Light Fog; 2 = Heavy Fog +PlanetaryConditionsFog=1 +# Shifting Wind +# Strength: Default = off; default min. Wind = 0 (see Wind above); default max. Wind = 6 +# Direction: Default = off; +PlanetaryConditionsWindShiftingStr=true +PlanetaryConditionsWindMin=1 +PlanetaryConditionsWindMax=3 +PlanetaryConditionsWindShiftingDir=true +# Blowing Sand: Default = off +PlanetaryConditionsBlowingSand=true +# EMI: Default = off +PlanetaryConditionsEMI=true +# Allow Terrain Changes: Default = on +PlanetaryConditionsAllowTerrainChanges=false + +# Faction (= Player) list --------------------------------------- +# A scenario can be set to single player style. In this case the first player is +# the human player and all other players are Princess bots. This will skip the +# Player/Camo assignment dialog and the "Host game" dialog and directly connect +# to a localhost Server and use the correct player name. +SinglePlayer=true +# The player name used to log into the server MUST match this name to play as +# that faction. Player names can *not* include spaces. +Factions=PlayerA,PlayerB,PlayerC + +# Faction location +# Determines deployment area +# Valid values are Any,N,NE,E,SE,S,SW,W,NW,CTR,EDG and R (random) +Location_PlayerA=W +Location_PlayerB=E +Location_PlayerC=S + +# Faction Teams +# Determines which players are on what teams. +# Valid values are any positive integer less than 2^31. +Team_PlayerA=1 +Team_PlayerB=2 +Team_PlayerC=2 + +# Faction minefields +# Gives the player minefields to deploy, the first number is conventional, the +# second command-detonated and the last is vibrabombs. +Minefields_PlayerA=2,0,2 +Minefields_PlayerB=1,0,3 + +# Player Camos +# Assigns a camo to a player; advisable in single player scenarios where the player can't do this +# The directory and filename must be separated by a comma and the directory must end in a / +Camo_PlayerA=Clans/Wolf/,Alpha Galaxy.jpg +Camo_PlayerB=Clans/Burrock/,Clan Burrock.jpg + +# Meklist for each faction ------------------------------------------------- +# +# Units are constructed as Unit__<#>, where the faction name +# matches the one listed in the Faction property and the # is a sequential +# numbering starting at 1. If there is a gap in the numbering, any units after +# the gap will be ignored. +# + +# The format is MekRef,PilotName,PilotGunnery,PilotPiloting,facing,x,y +# Facing and coordinates are optional. Facing is one of NW, N, NE, SE, S, SW +# Example: Unit_Irregulars_1=HGN-732,Col Rhonda Snord,2,1,N,01,32 + +Unit_PlayerA_1=Archer ARC-2R,PilotA1,3,4 +Unit_PlayerA_2=Hunchback HBK-4G,PilotA2,4,3 + +Unit_PlayerB_1=Atlas AS7-D,PilotB1,3,3 +Unit_PlayerC_1=Locust LCT-1M,PilotB2,4,5 + +# Additional advantages to add to pilots. Most of these require the 'MaxTech +# Level3 Pilot Advantages' game option to be turned on. The possible values +# are: +# dodge_maneuver, maneuvering_ace, melee_specialist, pain_resistance +# Multiple advantages for one pilot are seperated by spaces +Unit_PlayerA_2_Advantages=melee_specialist pain_resistance +Unit_PlayerB_2_Advantages=dodge_maneuver + +#set autoeject, only for meks +Unit_PlayerA_2_AutoEject=false +Unit_PlayerB_2_AutoEject=true + +#set which units should be commanders (for commander killed VC) +Unit_PlayerA_1_Commander=true +Unit_PlayerB_1_Commander=true +Unit_PlayerC_1_Commander=true + +# Unit Camos +# Assigns a camo to a unit, overriding any player camo +# The directory and filename must be separated by a comma and the directory must end in a / +Unit_PlayerA_1_Camo=Clans/Wolf/,Alpha Galaxy.jpg + +# To initially damage units, you can use a unit armor property, which specifies +# armor and internal values. Values above the unit's nominal value for that +# location will be ignored. +# Armor is specified in this order: +# H,CT,CTR,RT,RTR,LT,LTR,RA,LA,RL,LL,HI,CTI,RTI,LTI,RAI,LAI,RLI,LLI +# Here's an example: +# +# Unit_Kurita_1_Armor=0,30,19,24,20,24,10,24,24,33,33,1,25,17,17,13,13,17,17 +# +# Alternately, if you want more random damage, and want to allow critical +# damage before the game starts, you can use a unit damage property, which +# specifies a number of blocks of 5 damage that will be randomly applied to +# the unit using the standard hit chart. Any internal and critical hits will +# be resolved normally. +# Warning: this can result in the unit being destroyed before the game begins. +# Unit_PlayerB_1_Damage=5 + +# Advanced Dammage Modification +# +# Mek Locations +# HEAD=0,CT=1,RT=2,LT=3,RARM=4,LARM=5,RLEG=6,LLEG=7 +# Example for Meks: +# N0:1 Means Normal Armor Location 0 Set to 1 +# I2:2 Means Internal Armor Location 2 Set to 2 +# R2:3 Means Rear Armor Location 2 Set to 3 +# +# Tank Locations +# Body=0,FRONT=1,RIGHT=2,LEFT=3,REAR=4,TURRET=5 +# +# Infantry Location +# Men = 0 (Will set the number of men in the platoon) +# +# Battle Armor +# Unit#=0(First Unit Number) to Armor +# EG Unit_Kurita_3_DamageSpecific=N2:1,N3:0 +# Will set unit 3 to have 1 Armor Remaning +# while unit 4 Destroyed +# +# Proto Meks +# Head=0,Torso =1,RARM=2,LARM=3,LEGS=4,Main Gun=5 +# +Unit_Kurita_1_DamageSpecific=I1:10,N2:2 +Unit_Kurita_2_DamageSpecific=N0:1 +Unit_Kurita_4_DamageSpecific=N2:1,N2:2 +Unit_Kurita_3_DamageSpecific=N2:1,N3:0 +Unit_Kurita_5_DamageSpecific=N4:1,N1:1 + + +# Critical Hits +# eg Unit_Kurita_1_CritHit=1:8 +# This does a crit hit on location 1 slot 8. +# +# Mek Crit Hits +# HEAD=0,CT=1,RT=2,LT=3,RARM=4,LARM=5,RLEG=6,LLEG=7 +# Slots starting from 1 to the number of filled critical slots +# +# Vehicle Crit Hits +# Location is zero. +# Slots is one of the following: +# 1 = Crew stunned for 3 turns +# 2 = Main weapon jams for 1 turn +# 3 = Engine Destroyed Immobile +# 4 = Crew killed (tank dead) +# 5 = Fuel Tank/Engine Shielding (tank dead) +# 6 = Power Plant Hit (tank dead) +# +# Proto Meks +# Head=0,Torso=1,RARM=2,LARM=3,LEGS=4,Main Gun=5 +# Slots starting from 1 to the number of critical hit boxes +# +# In addition, you can specify whether the Torso weapons +# should be damaged by damaging the following torso "slots": +# Torso Weapon A=5,Torso Weapon B=6 +# +# +Unit_Kurita_1_CritHit=1:3,1:2 +Unit_Kurita_4_CritHit=2:1,1:5 +Unit_Kurita_5_CritHit=0:3 +Unit_Kurita_6_CritHit=2:10 + +# Set Ammo Ammount(Only Works for Meks) +# +# Note will not be able to specifiy a value larger then Inital Ammout +# +# Loc and Slots are the same as Crit Locations +# +# For a Mek this would +# Unit_Kurita_6_SetAmmoTo=2:11-3 +# Would set Ammo at Slot 2:11 to 3 points +# +Unit_Kurita_6_SetAmmoTo=2:11-1 + +# Set Ammo type (works only for 'Meks, too) +# +# Loc and Slots work the same as for critical locations and ammo ammout +# +# Ammo name is the unique string used in the 'Mek files themselves. +# Errors will be logged to the normal log file and the ammo replaced +# by the standard defined for the 'Mek. The same will happen for ammo +# which is illegal according to the specified game rules. +Unit_PlayerA_2_SetAmmoType=3:1-ISAC20 Flak Ammo + +# Set Altitude (works only for aerospace fighters, conventional fighters, +# small craft and dropships) +# +# You can set the altitude of a flying unit at the start of the scenario +# to be anything between 0 and 10. Altitude 0 means the unit deploys landed. +# +# Unit_Kurita_666_Altitude=3 diff --git a/crates/mms/tests/corpus/Examples/SBFTestScenario/TestSBF.mms b/crates/mms/tests/corpus/Examples/SBFTestScenario/TestSBF.mms new file mode 100644 index 0000000..4b9524f --- /dev/null +++ b/crates/mms/tests/corpus/Examples/SBFTestScenario/TestSBF.mms @@ -0,0 +1,73 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +MMSVersion: 2 +gametype: SBF +name: SBF Test Scenario +planet: Braunschweig + +description: > + This test scenario is written in V2 YAML + +map: AGoAC Maps/16x17 Desert 2.board + +options: + #- base_recon + #- base_team_vision + +planetaryconditions: + light: dusk + +factions: +- name: Legion of Vega + camo: Clans/Wolf/Alpha Galaxy/Alpha Galaxy.jpg + + units: + - include: Noble's Company.mmu + at: [7, 5] + id: 3 + + - include: "Example SBF Formation.mmu" + at: [ 10, 4 ] + + - include: "Formation2.mmu" + at: [ 8, 3 ] + + - include: Noble's Company.mmu + at: [8, 5] + id: 3 + + - include: "Example SBF Formation.mmu" + at: [ 4, 4 ] + + - include: Noble's Company.mmu + at: [ 3, 3 ] + + + +- name: OpFor + camo: Draconis Combine/Alshain Avengers/11th Alshain Avengers.jpg + units: + - include: Romy's Company.mmu + at: [8, 9] + id: 7 + + - include: "Example SBF Formation.mmu" + at: [ 12, 3 ] + + - include: Noble's Company.mmu + at: [ 11, 15 ] diff --git a/crates/mms/tests/corpus/Examples/SBFTestScenario/TestSBF2.mms b/crates/mms/tests/corpus/Examples/SBFTestScenario/TestSBF2.mms new file mode 100644 index 0000000..7b631a7 --- /dev/null +++ b/crates/mms/tests/corpus/Examples/SBFTestScenario/TestSBF2.mms @@ -0,0 +1,41 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +MMSVersion: 2 +gametype: SBF +name: SBF Single Side Test Scenario +planet: Braunschweig + +description: > + This test scenario is written in V2 YAML. Only one player + +map: AGoAC Maps/16x17 Grassland 2.board + +factions: +- name: Legion of Vega + camo: Clans/Wolf/Alpha Galaxy/Alpha Galaxy.jpg + + units: + - include: Noble's Company.mmu + at: [7, 5] + id: 3 + + - include: "JumpCompany.mmu" + at: [ 10, 4 ] + + - include: "Formation2.mmu" + at: [ 8, 3 ] diff --git a/crates/mms/tests/corpus/Examples/TWTestScenario/TestV2.mms b/crates/mms/tests/corpus/Examples/TWTestScenario/TestV2.mms new file mode 100644 index 0000000..9b5ba7f --- /dev/null +++ b/crates/mms/tests/corpus/Examples/TWTestScenario/TestV2.mms @@ -0,0 +1,58 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +MMSVersion: 2 +name: Test Scenario V2 +planet: Bone Norman + +description: > + This test scenarion is written in V2 YAML + +map: Test.board + +planetaryconditions: + light: dusk + +factions: +- name: Legion of Vega + deploy: N + camo: Clans/Wolf/Alpha Galaxy/Alpha Galaxy.jpg + + units: + - fullname: Archer ARC-2R + crew: + name: Cpt. Frederic Nguyen + skill: 3/4 + + - fullname: Hunchback HBK-4G + crew: + name: Cpt. Rhonda Snord + skill: 4/4 + +- name: 1st Air Cavalry, Federated Suns + deploy: "E" + camo: TestCamo.jpg + units: + - fullname: Atlas AS7-D + crew: + name: Cpt. Frederic Nguyen + skill: 3/3 + + - fullname: Locust LCT-1M + crew: + name: Cpt. Rhonda Snord + skill: 4/5 diff --git a/crates/mms/tests/corpus/FistAndDragon.mms b/crates/mms/tests/corpus/FistAndDragon.mms new file mode 100644 index 0000000..42cbdb1 --- /dev/null +++ b/crates/mms/tests/corpus/FistAndDragon.mms @@ -0,0 +1,262 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# 3025 Full Regimental Assault +# A MegaMek Scenario file +# Created by Phillip "bullseyesmith" Starke +# Best played Double-Blind! +MMSVersion=1 +Name=Fist and Dragon +Description=Massive Metal Mayham +BoardWidth=8 +BoardHeight=7 +Maps=Map Set 6/16x17 Box Canyon,Map Set 5/16x17 Moonscape 2,Map Set 5/16x17 Deep Canyon 1,Map Set 5/16x17 Deep Canyon 2,Map Set 2/16x17 City Ruins,Map Set 5/16x17 Large Mountain 1,Map Set 4/16x17 Large Lakes 1,Map Set 2/16x17 CityTech,Map Set 2/16x17 Desert Hills,rotate:Map Set 4/16x17 River Delta 2,Map Set 5/16x17 Large Mountain 1,Map Set 2/16x17 Mountain Lake,rotate:Map Set 5/16x17 Large Mountain 1,rotate:Map Set 2/16x17 BattleTech,Map Set 5/16x17 Open Terrain 2,rotate:Map Set 3/16x17 Desert Sinkhole 1,Map Set 3/16x17 Desert Mountain 2,Map Set 5/16x17 Large Mountain 2,Map Set 3/16x17 Desert Mountain 1,rotate:Map Set 3/16x17 Rolling Hills 2,Map Set 5/16x17 Open Terrain 1,Map Set 3/16x17 Desert Sinkhole 2,rotate:Map Set 3/16x17 Desert Mountain 1,rotate:Map Set 4/16x17 Heavy Forest 2,Map Set 4/16x17 City Street Grid-Park 1,Map Set 3/16x17 City Hills Residential 1,Map Set 3/16x17 Rolling Hills 1,Map Set 6/16x17 Woodland,Map Set 5/16x17 Large Mountain 1,rotate:Map Set 4/16x17 Large Lakes 2,Map Set 4/16x17 Heavy Forest 1,Map Set 3/16x17 Rolling Hills 1,Map Set 2/16x17 BattleTech,rotate:Map Set 4/16x17 Heavy Forest 1,rotate:Map Set 3/16x17 Desert Sinkhole 2,Map Set 5/16x17 Large Mountain 2,Map Set 4/16x17 Large Lakes 2,Map Set 6/16x17 Wide River,rotate:Map Set 2/16x17 Scattered Woods,Map Set 3/16x17 Rolling Hills 2,Map Set 3/16x17 Desert Sinkhole 1,Map Set 5/16x17 Moonscape 1,Map Set 6/16x17 BattleForce 2,Map Set 2/16x17 Scattered Woods,Map Set 4/16x17 River Delta 2,Map Set 2/16x17 Lake Area,Map Set 6/16x17 Woodland,rotate:Map Set 3/16x17 City Hills Residential 1,rotate:Map Set 4/16x17 City Street Grid-Park 2,Map Set 3/16x17 City Hills Residential 2,Map Set 2/16x17 River Valley,rotate:Map Set 4/16x17 River Delta 1,Map Set 4/16x17 Large Lakes 1,Map Set 4/16x17 Heavy Forest 2,rotate:Map Set 5/16x17 Large Mountain 2,rotate:Map Set 5/16x17 Large Mountain 2 +Factions=Steiner,Kurita +Location_Steiner=Any +Location_Kurita=Any +# Steiner, Zulu Lance, Rear Guard +Unit_Steiner_1=Bulldog Medium Tank (Standard),Veteran Z1,3,5 +Unit_Steiner_2=Behemoth Heavy Tank (Flamer),Regular Z2,4,4 +Unit_Steiner_3=Atlas AS7-D,Aaron (Da Man) Henderson Z3,3,5 +Unit_Steiner_4=Atlas AS7-D,Christopher (BlameTheName) Conley Z4,5,6 +# Steiner, X-ray Company, Rear Guard +Unit_Steiner_5=Von Luckner Heavy Tank VNL-K65N,Van Von Hunter X1,3,2 +Unit_Steiner_6=Demolisher Heavy Tank (Standard Mk. I),Veteran X2,2,5 +Unit_Steiner_7=Sturmfeur Heavy Tank (Standard),Regular X3,3,6 +Unit_Steiner_8=LRM Carrier (Standard),Regular X4,3,6 +Unit_Steiner_9=Ontos Heavy Tank (Standard),Veteran X5,3,5 +Unit_Steiner_10=SRM Carrier (Standard),Regular X6,4,6 +Unit_Steiner_11=LRM Carrier (Standard),Green X7,4,6 +Unit_Steiner_12=Von Luckner Heavy Tank VNL-K65N,Green X8,5,6 +Unit_Steiner_13=Von Luckner Heavy Tank VNL-K65N,Regular X9,3,4 +Unit_Steiner_14=Demolisher Heavy Tank (Standard Mk. I),Regular X10,3,6 +Unit_Steiner_15=Sturmfeur Heavy Tank (Standard),Regular X11,4,4 +Unit_Steiner_16=LRM Carrier (Standard),Regular X12,4,4 +# Steiner, Yankee Company, Rear Guard +Unit_Steiner_17=Pike Support Vehicle (Standard),Regular Y1,3,5 +Unit_Steiner_18=SRM Carrier (Standard),Regular Y2,3,6 +Unit_Steiner_19=LRM Carrier (Standard),Regular Y3,4,5 +Unit_Steiner_20=Von Luckner Heavy Tank VNL-K65N,Regular Y4,4,6 +Unit_Steiner_21=LRM Carrier (Standard),Regular Y5,3,4 +Unit_Steiner_22=SRM Carrier (Standard),Regular Y6,4,4 +Unit_Steiner_23=LRM Carrier (Standard),Green Y7,4,5 +Unit_Steiner_24=Von Luckner Heavy Tank VNL-K65N,Green Y8,4,6 +Unit_Steiner_25=SRM Carrier (Standard),Green Y9,5,5 +Unit_Steiner_26=Demolisher Heavy Tank (Standard Mk. I),Green Y10,5,6 +Unit_Steiner_27=LRM Carrier (Standard),Green Y11,6,5 +Unit_Steiner_28=Von Luckner Heavy Tank VNL-K65N,Green Y12,6,6 +# Steiner, Alpha Company, Main Force +Unit_Steiner_29=Orion ON1-K,Stan (Stark) Raven A1,2,5 +Unit_Steiner_30=JagerMech JM6-S,Kevin (Rage) Armstrong A2,4,5 +Unit_Steiner_31=Thunderbolt TDR-5S,Dan (Lt) Dunn A3,4,5 +Unit_Steiner_32=Warhammer WHM-6R,James (Treky) Mattoon A4,4,6 +Unit_Steiner_33=Orion ON1-K,Hero McGilicutti A5,3,5 +Unit_Steiner_34=JagerMech JM6-S,Isom Perkins A6,4,6 +Unit_Steiner_35=Thunderbolt TDR-5S,Travis (Cal-Me-EZ) Calmez A7,4,6 +Unit_Steiner_36=Warhammer WHM-6R,Mark (Papa) Scanlan A8,4,7 +Unit_Steiner_37=Orion ON1-K,Chris (Wally) Mendonca A9,4,6 +Unit_Steiner_38=Rifleman RFL-3N,Sheldon (Mo) Moses A10,4,5 +Unit_Steiner_39=Thunderbolt TDR-5S,Robert (QB) Quackenbush A11,3,5 +Unit_Steiner_40=Warhammer WHM-6D,Mike (The Claw) McGhee A12,4,5 +# Steiner, Bravo Company, Main Force +Unit_Steiner_41=Grasshopper GHR-5H,Derrick (Freakzilla) Powell B1,4,4 +Unit_Steiner_42=Catapult CPLT-C1,Andrew (Murdock) Cleary B2,4,4 +Unit_Steiner_43=Blackjack BJ-1,Marcia (M-dud) Milks B3,4,6 +Unit_Steiner_44=Whitworth WTH-1,Jeremy Allen B4,6,6 +Unit_Steiner_45=Victor VTR-9B,George (The Prez) L. Bush B5,3,4 +Unit_Steiner_46=Victor VTR-9S,Will (Mach5) Hartsell B6,3,6 +Unit_Steiner_47=Grasshopper GHR-5H,Jess (Beaker) Nelson B7,4,5 +Unit_Steiner_48=Grasshopper GHR-5H,Alex (Waah) Liang B8,4,6 +Unit_Steiner_49=Zeus ZEU-6T,Aaron (Old Man) Carleton B9,3,4 +Unit_Steiner_50=Marauder MAD-3R,Edwin (Ted) Temple III B10,3,6 +Unit_Steiner_51=Zeus ZEU-6T,Richard (Snooze) Gannon B11,4,5 +Unit_Steiner_52=Quickdraw QKD-4G,Charles (Chuck) Pulitz B12,5,7 +# Steiner, Charlie Company, Main Force +Unit_Steiner_53=Rommel Tank (Standard),Veteran C1,2,5 +Unit_Steiner_54=Patton Tank (Standard),Regular C2,4,4 +Unit_Steiner_55=Goblin Medium Tank (SRM),Regular C3,4,6 +Unit_Steiner_56=Goblin Medium Tank (Standard),Green C4,5,6 +Unit_Steiner_57=Rommel Tank (Standard),Veteran C5,3,5 +Unit_Steiner_58=Bulldog Medium Tank (LRM),Veteran C6,4,5 +Unit_Steiner_59=Manticore Heavy Tank (Standard),Regular C7,4,4 +Unit_Steiner_60=Rommel Tank (Standard),Regular C8,4,6 +Unit_Steiner_61=Rommel Tank (Standard),Veteran C9,3,5 +Unit_Steiner_62=Bulldog Medium Tank (LRM),Veteran C10,4,4 +Unit_Steiner_63=Manticore Heavy Tank (Standard),Regular C11,4,4 +Unit_Steiner_64=Rommel Tank (Standard),Regular C12,4,6 +# Steiner, Delta Company, Main Force +Unit_Steiner_65=Manticore Heavy Tank (Standard),Veteran (Knee Knockers) D1,2,3 +Unit_Steiner_66=Manticore Heavy Tank (Standard),Veteran D2,3,4 +Unit_Steiner_67=Manticore Heavy Tank (Standard),Regular D3,3,6 +Unit_Steiner_68=Manticore Heavy Tank (Standard),Green D4,4,5 +Unit_Steiner_69=Manticore Heavy Tank (Standard),Veteran D5,3,5 +Unit_Steiner_70=Manticore Heavy Tank (Standard),Regular D6,4,4 +Unit_Steiner_71=Manticore Heavy Tank (Standard),Regular D7,3,6 +Unit_Steiner_72=Manticore Heavy Tank (Standard),Regular D8,4,5 +Unit_Steiner_73=Rommel Tank (Standard),Veteran D9,3,4 +Unit_Steiner_74=Rommel Tank (Standard),Regular D10,4,4 +Unit_Steiner_75=Patton Tank (Standard),Regular D11,4,4 +Unit_Steiner_76=Patton Tank (Standard),Green D12,4,6 +# Steiner, Echo Company, Main Force +Unit_Steiner_77=Striker Light Tank (Standard),Veteran E1,3,3 +Unit_Steiner_78=Hunter Light Support Tank (Standard),Regular E2,4,4 +Unit_Steiner_79=Vedette Medium Tank (AC2),Regular E3,4,4 +Unit_Steiner_80=Vedette Medium Tank (Liao),Green E4,5,6 +Unit_Steiner_81=Vedette Medium Tank (Liao),Veteran E5,4,5 +Unit_Steiner_82=Striker Light Tank (LRM),Regular E6,3,6 +Unit_Steiner_83=Vedette Medium Tank (Standard),Green E7,4,6 +Unit_Steiner_84=Vedette Medium Tank (Liao),Green E8,5,6 +Unit_Steiner_85=Hunter Light Support Tank (Standard),Rgular E9,4,6 +Unit_Steiner_86=Striker Light Tank (Standard),Green E10,4,6 +Unit_Steiner_87=Vedette Medium Tank (AC2),Green E11,5,6 +Unit_Steiner_88=Vedette Medium Tank (Liao),Green E12,6,6 +# Steiner, Romeo Company, Airial Support +Unit_Steiner_89=Phoenix Hawk PXH-1,Kimberly (Phoenix) Chandler R1,2,4 +Unit_Steiner_90=Phoenix Hawk PXH-1,Cesar (Senior) Marin R2,4,6 +Unit_Steiner_91=Javelin JVN-10N,Isabel (Izzy) Marin R3,4,6 +Unit_Steiner_92=Javelin JVN-10N,James (Mouse) Muhovich R4,4,6 +Unit_Steiner_93=Clint CLNT-2-3T,Mike Scully R5,4,5 +Unit_Steiner_94=Firestarter FS9-H,Ernesto (Ernie) Alexander R6,4,4 +Unit_Steiner_95=Stinger STG-3R,Scotty (Stoner) Porter R7,4,6 +Unit_Steiner_96=Stinger STG-3R,Julia (Cuzin) Davidson R8,6,6 +# Steiner, Sierra Company, Rapid Assist +Unit_Steiner_97=Maxim Heavy Hover Transport (Standard),Veteran S1,4,3 +Unit_Steiner_98=Maxim Heavy Hover Transport (Standard),Regular S2,3,5 +Unit_Steiner_99=Maxim Heavy Hover Transport (Standard),Regular S3,3,6 +Unit_Steiner_100=Cicada CDA-2A,Jesus (Pepe) Gomez S4,4,5 +Unit_Steiner_101=Packrat LRPV PKR-T5,Regular S5,3,5 +Unit_Steiner_102=Packrat LRPV PKR-T5,Regular S6,4,4 +Unit_Steiner_103=Skulker Wheeled Scout Tank (Standard),Regular S7,3,6 +Unit_Steiner_104=Assassin ASN-21,Natalie (Cat) Glenn S8,4,7 +# Steiner, Tango Lance, Recon +Unit_Steiner_105=Savannah Master Hovercraft (Standard),Regular T1,4,4 +Unit_Steiner_106=Drillson Heavy Hover Tank (Standard),Regular T2,4,4 +Unit_Steiner_107=Drillson Heavy Hover Tank (Standard),Regular T3,4,5 +Unit_Steiner_108=Harasser Laser Platform (Standard),Green T4,4,6 +#Kurita +Unit_Kurita_1=Behemoth Heavy Tank (Standard),Reg,4,5 +Unit_Kurita_2=Demolisher Heavy Tank (Standard Mk. I),Reg,4,4 +Unit_Kurita_3=Demolisher Heavy Tank (Standard Mk. I),Reg,4,4 +Unit_Kurita_4=Demolisher Heavy Tank (Standard Mk. I),Reg,3,4 +Unit_Kurita_5=Demolisher Heavy Tank (Standard Mk. I),Reg,4,5 +Unit_Kurita_6=Demolisher Heavy Tank (Standard Mk. I),Reg,4,6 +Unit_Kurita_7=Demolisher Heavy Tank (Standard Mk. I),Grn,4,6 +Unit_Kurita_8=Von Luckner Heavy Tank VNL-K65N,Vet,3,3 +Unit_Kurita_9=Von Luckner Heavy Tank VNL-K65N,Grn,4,6 +Unit_Kurita_10=Pike Support Vehicle (Standard),Reg,4,5 +Unit_Kurita_11=Pike Support Vehicle (Standard),Reg,4,4 +Unit_Kurita_12=Pike Support Vehicle (Standard),Reg,5,6 +Unit_Kurita_13=Pike Support Vehicle (Standard),Reg,3,5 +Unit_Kurita_14=Pike Support Vehicle (Standard),Grn,3,4 +Unit_Kurita_15=AC/2 Carrier (Standard),Reg,4,5 +Unit_Kurita_16=AC/2 Carrier (Standard),Vet,4,4 +Unit_Kurita_17=AC/2 Carrier (Standard),Vet,3,5 +Unit_Kurita_18=LRM Carrier (Standard),Reg,4,6 +Unit_Kurita_19=LRM Carrier (Standard),Vet,3,3 +Unit_Kurita_20=LRM Carrier (Standard),Reg,4,6 +Unit_Kurita_21=LRM Carrier (Standard),Reg,3,4 +Unit_Kurita_22=LRM Carrier (Standard),Reg,4,6 +Unit_Kurita_23=Laser Carrier (Standard),Reg,3,4 +Unit_Kurita_24=SRM Carrier (Standard),Vet,4,3 +Unit_Kurita_25=SRM Carrier (Standard),Reg,4,6 +Unit_Kurita_26=Marauder MAD-3R,Vet,2,3 +Unit_Kurita_27=Marauder MAD-3R,Grn,5,6 +Unit_Kurita_28=Archer ARC-2K,Grn,4,6 +Unit_Kurita_29=Archer ARC-2K,Reg,3,4 +Unit_Kurita_30=Crusader CRD-3K,Reg,4,6 +Unit_Kurita_31=JagerMech JM6-S,Reg,4,6 +Unit_Kurita_32=JagerMech JM6-S,Elt,3,4 +Unit_Kurita_33=Rifleman RFL-3N,Reg,3,6 +Unit_Kurita_34=Rifleman RFL-3N,Grn,4,6 +Unit_Kurita_35=Hunchback HBK-4G,Grn,5,5 +Unit_Kurita_36=Grasshopper GHR-5H,Grn,6,6 +Unit_Kurita_37=Grasshopper GHR-5H,Reg,4,4 +Unit_Kurita_38=Grasshopper GHR-5H,Reg,3,6 +Unit_Kurita_39=Whitworth WTH-1,Vet,4,5 +Unit_Kurita_40=Whitworth WTH-1,Reg,4,5 +Unit_Kurita_41=Panther PNT-9R,Reg,4,4 +Unit_Kurita_42=Patton Tank (Standard),Reg,3,4 +Unit_Kurita_43=Patton Tank (Standard),Reg,4,5 +Unit_Kurita_44=Bulldog Medium Tank (LRM),Elt,3,2 +Unit_Kurita_45=Bulldog Medium Tank (Standard),Reg,4,4 +Unit_Kurita_46=Bulldog Medium Tank (Standard),Reg,4,6 +Unit_Kurita_47=Bulldog Medium Tank (Standard),Reg,4,6 +Unit_Kurita_48=Bulldog Medium Tank (Standard),Reg,4,4 +Unit_Kurita_49=Bulldog Medium Tank (Standard),Reg,4,6 +Unit_Kurita_50=Manticore Heavy Tank (Standard),Vet,3,4 +Unit_Kurita_51=Manticore Heavy Tank (Standard),Vet,2,3 +Unit_Kurita_52=Manticore Heavy Tank (Standard),Reg,4,5 +Unit_Kurita_53=Manticore Heavy Tank (Standard),Reg,4,4 +Unit_Kurita_54=Manticore Heavy Tank (Standard),Reg,4,4 +Unit_Kurita_55=Manticore Heavy Tank (Standard),Reg,4,6 +Unit_Kurita_56=Goblin Medium Tank (Standard),Reg,3,5 +Unit_Kurita_57=Goblin Medium Tank (Standard),Grn,4,7 +Unit_Kurita_58=Goblin Medium Tank (Standard),Reg,3,4 +Unit_Kurita_59=Hetzer Wheeled Assault Gun (Standard),Reg,4,4 +Unit_Kurita_60=Hetzer Wheeled Assault Gun (Standard),Reg,4,4 +Unit_Kurita_61=Ostroc OSR-2C,Reg,4,5 +Unit_Kurita_62=Ostroc OSR-2C,Grn,6,6 +Unit_Kurita_63=Ostroc OSR-2C,Reg,3,5 +Unit_Kurita_64=Dragon DRG-1C,Reg,3,6 +Unit_Kurita_65=Dragon DRG-1C,Vet,3,3 +Unit_Kurita_66=Dragon DRG-1N,Reg,4,4 +Unit_Kurita_67=Wolverine WVR-6K,Reg,3,4 +Unit_Kurita_68=Wolverine WVR-6K,Reg,4,4 +Unit_Kurita_69=Trebuchet TBT-5N,Reg,3,6 +Unit_Kurita_70=Shadow Hawk SHD-2K,Reg,3,4 +Unit_Kurita_71=Quickdraw QKD-4G,Vet,4,4 +Unit_Kurita_72=Vedette Medium Tank (Standard),Vet,3,5 +Unit_Kurita_73=Vedette Medium Tank (Standard),Reg,4,4 +Unit_Kurita_74=Vedette Medium Tank (Standard),Vet,2,4 +Unit_Kurita_75=Vedette Medium Tank (Standard),Grn,4,6 +Unit_Kurita_76=Vedette Medium Tank (Standard),Reg,4,4 +Unit_Kurita_77=Vedette Medium Tank (Standard),Grn,6,6 +Unit_Kurita_78=Vedette Medium Tank (Standard),Reg,4,4 +Unit_Kurita_79=Vedette Medium Tank (Standard),Reg,3,4 +Unit_Kurita_80=Hunter Light Support Tank (Standard),Reg,4,6 +Unit_Kurita_81=Striker Light Tank (Standard),Reg,4,5 +Unit_Kurita_82=Striker Light Tank (Standard),Reg,4,6 +Unit_Kurita_83=Phoenix Hawk PXH-1,Reg,4,6 +Unit_Kurita_84=Phoenix Hawk PXH-1,Reg,4,5 +Unit_Kurita_85=Phoenix Hawk PXH-1,Reg,4,6 +Unit_Kurita_86=Phoenix Hawk PXH-1,Grn,4,6 +Unit_Kurita_87=Wasp WSP-1A,Reg,3,6 +Unit_Kurita_88=Wasp WSP-1K,Reg,4,4 +Unit_Kurita_89=Jenner JR7-D,Reg,3,4 +Unit_Kurita_90=Jenner JR7-D,Reg,4,5 +Unit_Kurita_91=Locust LCT-1V,Reg,4,4 +Unit_Kurita_92=Banshee BNC-3E,Reg,3,4 +Unit_Kurita_93=Catapult CPLT-C1,Grn,4,5 +Unit_Kurita_94=Charger CGR-SB 'Challenger',Reg,4,4 +Unit_Kurita_95=Dragon DRG-1C,Reg,3,5 +Unit_Kurita_96=Condor Heavy Hover Tank (Standard),Reg,4,4 +Unit_Kurita_97=Maxim Heavy Hover Transport (Standard),Reg,3,5 +Unit_Kurita_98=Maxim Heavy Hover Transport (Standard),Reg,3,6 +Unit_Kurita_99=Maxim Heavy Hover Transport (Standard),Reg,4,6 +Unit_Kurita_100=Maxim Heavy Hover Transport (Standard),Vet,2,5 +Unit_Kurita_101=Saracen Medium Hover Tank (Standard),Grn,4,5 +Unit_Kurita_102=Scimitar Medium Hover Tank (Standard),Reg,4,6 +Unit_Kurita_103=Scimitar Medium Hover Tank (Standard),Grn,5,6 +Unit_Kurita_104=Scimitar Medium Hover Tank (Standard),Grn,4,7 +Unit_Kurita_105=Drillson Heavy Hover Tank (Standard),Grn,5,6 +Unit_Kurita_106=Drillson Heavy Hover Tank (Standard),Grn,4,6 +Unit_Kurita_107=Drillson Heavy Hover Tank (Standard),Reg,4,4 +Unit_Kurita_108=Drillson Heavy Hover Tank (Standard),Grn,6,7 +Unit_Kurita_109=Drillson Heavy Hover Tank (Standard),Grn,6,6 +Unit_Kurita_110=Drillson Heavy Hover Tank (Standard),Vet,3,4 +Unit_Kurita_111=Drillson Heavy Hover Tank (Standard),Reg,4,6 +Unit_Kurita_112=Drillson Heavy Hover Tank (Standard),Reg,3,6 diff --git a/crates/mms/tests/corpus/IceStorm.mms b/crates/mms/tests/corpus/IceStorm.mms new file mode 100644 index 0000000..b0e39ec --- /dev/null +++ b/crates/mms/tests/corpus/IceStorm.mms @@ -0,0 +1,53 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +MMSVersion=1 +Name=The Ice Storm +Description=A Clan Ice Hellion Trinary faces a Binary of heavier units from Clan Fire Mandrill +BoardWidth=3 +BoardHeight=2 +RandomDirs=Map Set 2,Map Set 3,Map Set 4,Map Set 5,Map Set 6,Map Set 7 +Maps=RANDOM +Factions=Hellion,Mandrill +Location_Hellion=W +Location_Mandrill=E +Unit_Hellion_1=Fenris (Ice Ferret) D,X,3,3 +Unit_Hellion_2=Mad Cat (Timber Wolf) D,X,3,4 +Unit_Hellion_3=Thor (Summoner) A,X,3,4 +Unit_Hellion_4=Black Lanner Prime,X,3,4 +Unit_Hellion_5=Linebacker Prime,X,3,4 +Unit_Hellion_6=Koshi (Mist Lynx) A,X,3,4 +Unit_Hellion_7=Ryoken (Stormcrow) B,X,3,3 +Unit_Hellion_8=Dragonfly (Viper) Prime,X,3,4 +Unit_Hellion_9=Fenris (Ice Ferret) D,X,3,4 +Unit_Hellion_10=Black Lanner Prime,X,3,4 +Unit_Hellion_11=Phantom D,X,3,3 +Unit_Hellion_12=Dasher (Fire Moth) Prime,X,3,4 +Unit_Hellion_13=Puma (Adder) C,X,4,4 +Unit_Hellion_14=Hellion B,X,3,4 +Unit_Hellion_15=Uller (Kit Fox) D,X,3,4 +Unit_Mandrill_1=Thor (Summoner) Prime,X,3,4 +Unit_Mandrill_2=Thor (Summoner) Prime,X,3,4 +Unit_Mandrill_3=Turkina A,X,3,3 +Unit_Mandrill_4=Masakari (Warhawk) A,X,3,4 +Unit_Mandrill_5=Masakari (Warhawk) A,X,3,4 +Unit_Mandrill_6=Cauldron-Born (Ebon Jaguar) B,X,3,3 +Unit_Mandrill_7=Cauldron-Born (Ebon Jaguar) B,X,3,4 +Unit_Mandrill_8=Vulture (Mad Dog) Prime,X,3,4 +Unit_Mandrill_9=Man O' War (Gargoyle) A,X,3,4 +Unit_Mandrill_10=Masakari (Warhawk) Prime,X,3,4 diff --git a/crates/mms/tests/corpus/Kell Hounds/DeathOfTheLegion/DeathOfTheLegion.mms b/crates/mms/tests/corpus/Kell Hounds/DeathOfTheLegion/DeathOfTheLegion.mms new file mode 100644 index 0000000..fecdc20 --- /dev/null +++ b/crates/mms/tests/corpus/Kell Hounds/DeathOfTheLegion/DeathOfTheLegion.mms @@ -0,0 +1,240 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# Based on Death of the Legion, FASA's 01652 "Kell Hounds" sourcebook +MMSVersion: 2 +name: Death of the Legion +planet: Mankova +description: | + During a raid on Mankova by Gorman Toth and his pirate unit known as the Legion of Honor, Toth learned that + a Star League depot had been found during strip mining. Thinking he had tricked the Kell Hounds into + taking action elsewhere he went straight for it. + + *Goal: Playing as the Kell Hounds force, destroy the Pirate forces.* + +map: + cols: 2 + boards: + - file: Map Set 2/16x17 River Valley.board + - file: Map Set 2/16x17 BattleTech.board + modify: rotate + +factions: + + - name: Mek Company, Kell Hounds + camo: Mercs/Kell Hounds.jpg + deploy: W + + units: + include: DeathOfTheLegion_units_kell.mmu + + # OpFor + - name: Legion of Honor + camo: Pirates/Tortuga Fusiliers.jpg + deploy: S + + units: + include: DeathOfTheLegion_units_legion.mmu + +messages: + - header: Situation + text: | + # Situation + ## Mankova, Foredam District + ## Free Worlds League + ## November 3017 + + During a raid on Mankova led by Gorman Toth and his pirate band, the Legion of Honor, Toth discovered + that a Star League depot had been unearthed during strip-mining operations in the Foredam District. + Toth had been plundering the planet for nearly a week, having deceived the Kell Hounds into chasing + another pirate, Hassin Hys. Believing he had successfully evaded the Hounds, Toth made his way to the + remote mining district to seize the depot and its contents. Unbeknownst to him, he had walked right + into a carefully orchestrated trap laid by Lieutenant Colonel Patrick Kell, who had fabricated the story + about the Star League depot. + + As Toth's Legion of Honor advanced toward Foredam, Colonel Kell detached his AeroSpace company and + Jump Infantry Company to cut off all potential escape routes. When Major Salome Ward and her Relentless + Wolves engaged Toth's Legion, they had no options but to confront the Kell Hounds in direct battle. + + *This scenario is based on "Death of the Legion", published in the "Kell Hounds" sourcebook, FASA 01652.* + image: deathlegion_splash.png + trigger: + type: gamestart + + - header: Attacker's Task + text: | + ## Attacker's Task + + In this scenario, it is your task to deal with the pirate rabble of the so-called Legion of Honor. Destroy, + cripple or chase off the enemy. + trigger: + type: and + triggers: + - type: phasestart + phase: deployment + - type: round + round: 0 + + - header: Defeat + text: | + ## Defeat + + The pirates sadly proved to be hardier than Patrick Martin Kell had thought. It will take the Kell Hounds + quite some time to recover from today's losses. + + It's a good thing the pirates won't be able to resupply at a Star League depot on this planet. + image: deathlegion_splash.png + trigger: + type: activeunits + modify: [ atend, once ] + units: [ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112 ] + count: 0 + + - header: Victory + text: | + ## Victory! + + Congratulations! With your leadership, the Kell Hounds wiped the pirates from the face of the + planet at almost no losses to themselves. + + Let the opponents tremble when the Kell Hounds arrive. + image: deathlegion_splash.png + trigger: + type: and + modify: [ atend, once ] + triggers: + - type: activeunits + units: [ 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212 ] + count: 0 + - type: killedunits + units: [ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112 ] + atmost: 1 + + - header: Victory + text: | + ## Victory! + + Congratulations! The Kell Hounds have secured the battlefield. While every lost MekWarrior hurts and every lost + Mek is expensive to replace, this victory came at not too great a cost. + + The pirates, as far as they survived will think of this day for a long time. Time to + collect the salvage. + image: deathlegion_splash.png + trigger: + type: and + modify: [ atend, once ] + triggers: + - type: activeunits + units: [ 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212 ] + count: 0 + - type: killedunits + units: [ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112 ] + atLeast: 2 + atmost: 4 + + - header: Victory + text: | + ## Victory! + + The Legion of Honor is no more. But the Kell Hounds are limping off the battlefield. Let's hope that + some of the wreckage can be salvaged. + image: deathlegion_splash.png + trigger: + type: and + modify: [ atend, once ] + triggers: + - type: activeunits + units: [ 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212 ] + count: 0 + - type: killedunits + units: [ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112 ] + atLeast: 5 + atmost: 8 + + - header: Victory + text: | + ## Victory! + + The pirates exacted a heavy toll on the Kell Hounds today. It can only be hoped that the battlefield + salvage and the contract payment can make up for the sustained damage. + image: deathlegion_splash.png + trigger: + type: and + modify: [ atend, once ] + triggers: + - type: activeunits + units: [ 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212 ] + count: 0 + - type: killedunits + units: [ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112 ] + atLeast: 9 + + - header: Pilot Message + text: | + *Sgt. Clarence Wilson:* Sir, reporting the pirate's leader lance is gone. This will surely demoralize them. + image: portraits/Male/MekWarrior/MW_M_103.png + trigger: + type: and + triggers: + - type: activeunits + units: [ 103 ] + - type: battlefieldcontrol + modify: not + - type: activeunits + modify: once + units: [ 201, 202, 203, 204 ] + count: 0 + + - header: Pilot Message + text: | + *Sgt. Clarence Wilson:* Sir, Jump Infantry is reporting several of the pirates who've fled have been caught + and captured or destroyed. + image: portraits/Male/MekWarrior/MW_M_103.png + trigger: + type: and + triggers: + - type: activeunits + units: [ 103 ] + - type: battlefieldcontrol + modify: not + - type: fledunits + modify: once + units: [ 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212 ] + atLeast: 3 + + - header: Pilot Message + text: | + *Lt. Mike Fitzhugh:* One pirate down. + image: portraits/Male/MekWarrior/MW_M_88.png + trigger: + type: and + triggers: + - type: activeunits + units: [ 106 ] + - type: battlefieldcontrol + modify: not + - type: activeunits + modify: once + units: [ 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212 ] + count: 11 + +end: + - trigger: + type: battlefieldcontrol + + + diff --git a/crates/mms/tests/corpus/Kell Hounds/LoweringTheBoom/LoweringTheBoom.mms b/crates/mms/tests/corpus/Kell Hounds/LoweringTheBoom/LoweringTheBoom.mms new file mode 100644 index 0000000..accba2e --- /dev/null +++ b/crates/mms/tests/corpus/Kell Hounds/LoweringTheBoom/LoweringTheBoom.mms @@ -0,0 +1,251 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# Based on Battlecorps Scenario 3011, Lowering the Boom, originally published in FASA's "Kell Hounds" sourcebook +MMSVersion: 2 +name: Lowering the Boom [Flee] +planet: Castor +description: | + Lyran intelligence has found illegal atomic weapons on the Marik world of Castor. Katrina Steiner + has authorized an attack to remove the weapons and provide the Kell Hounds with action. + + *Goal: Playing as the Marik force, escape the Kell Hounds.* + +map: + boardrows: 2 + boards: + - file: Map Set 2/16x17 BattleTech.board + - file: Map Set 2/16x17 Lake Area.board + modify: rotate + postprocess: + - type: convertterrain + terrain: woods + newlevel: 2 + - type: convertterrain + terrain: water + newlevel: 3 + +options: + file: options_3025_basic.xml + off: + - check_victory + +factions: + +- name: Thirtieth Marik Militia + camo: Free Worlds League/Marik Militia/Marik Militia.jpg + deploy: N + + victory: + - trigger: + type: fledunits + units: [ 101, 102, 103, 104, 105, 106 ] + atLeast: 4 + modify: onlyatend + + fleefrom: + border: north + # only for testing: +# all: + + # only for testing bot vs bot + # bot: + # # try to get away + # selfpreservation: 8 + # fallshame: 8 + # hyperaggression: 4 + # herdmentality: 1 + # bravery: 3 + # # Princess respects the edge she is set to flee from + # fleeto: north + # flee: true + + units: + include: LoweringTheBoom_units_marik.mmu + +- name: Kell Hounds, First Battalion + camo: MERC - 1st Kell Hounds.gif + deploy: + edge: S + + bot: + # must be pretty berserk to hunt down the Mariks in this scenario + selfpreservation: 2 + fallshame: 2 + hyperaggression: 9 + herdmentality: 3 + bravery: 8 + forcedwithdrawal: false + + victory: + - modify: onlyatend + trigger: + type: fledunits + units: [ 101, 102, 103, 104, 105, 106 ] + atmost: 2 + + units: + include: LoweringTheBoom_units_kell.mmu + +messages: + - header: Situation + text: | + # Situation + ## Castor + ## Free Worlds League + ## 15 June 3012 + + The idea for a Steiner raid on the Marik world of Castor originated with Cranston Snord, a well-known + eccentric and one of Katrina Steiner's long-serving mercenary commanders. Snord's sources had informed + him of a priceless collection of Fabergé Eggs located on Castor, and he already envisioned them as part + of his private museum on Clinton. + + Upon learning of Snord's plans, Morgan and Patrick Kell saw an opportunity to put their newly formed + unit to the test in battle. With Lyran intelligence uncovering a cache of illegal atomic weapons on + Castor, Katrina Steiner sanctioned the raid. The operation aimed to eliminate the weapons, secure the + eggs for Snord, and provide combat experience for the Kell Hounds. + + Colonel Nage recognized the battle was lost and retreated with his command company, aiming to reach the + atomic weapons and unleash them on the mercenaries. Colonel Kell dispatched Salome Ward and her + Relentless Wolves to intercept him. Ward's company caught up with Nage just as he was nearing the depot. + She launched an attack as the Second Battalion's LAMs bombed the depot. + + *This scenario is based on Battlecorps Scenario 3011, "Lowering the Boom", originally published in + the "Kell Hounds" sourcebook, FASA 01652.* + image: loweringboom_splash.png + trigger: + type: gamestart + + - header: Defender's Task + text: | + ## Defender's Task + + In this scenario, it is your task to save Col. Oliver Nage and his elements of 1st Company, 1st Battalion + from the attacking Kell Hounds. Try to escape by retreating off the north edge of the map with + as many Meks as possible. + + Be careful! Some of your Meks have already sustained damage. + + *This game uses basic game rules.* + image: loweringboom_map.png + trigger: + type: gamestart + + - header: Another Unit Safe + text: Three of your Meks have safely left the battlefield! This game is already considered a draw. + trigger: + type: fledunits + modify: once + units: [ 101, 102, 103, 104, 105, 106 ] + atLeast: 3 + + - header: Decisive Defeat + text: | + ## Decisive Defeat + + Not more than one Marik Mek managed to evade the Kell Hounds forces. The Kell Hounds + have won a decivise victory. + + Ward's Wolves inflicted significant losses on the Marik Meks. Witnessing the smoke rising + from the depot and his units sustaining severe damage, Nage decided to flee, with the relentless + pursuit of the Wolves close behind. + + image: loweringboom_splash.png + trigger: + type: fledunits + modify: atend + units: [ 101, 102, 103, 104, 105, 106 ] + atmost: 1 + + - header: Marginal Defeat + text: | + ## Marginal Defeat + + Only two Marik Meks managed to evade the Kell Hounds forces. + + Ward's Wolves inflicted significant losses on the Marik Meks. Witnessing the smoke rising + from the depot and his units sustaining severe damage, Nage decided to flee, with the relentless + pursuit of the Wolves close behind. + image: loweringboom_splash.png + trigger: + type: fledunits + modify: atend + units: [ 101, 102, 103, 104, 105, 106 ] + count: 2 + + - header: Decisive Marik Victory + text: At least five Marik Meks managed to evade the Kell Hounds forces. The FWL + has won a decivise victory. + + Ward's Wolves failed to inflict significant losses on the Marik Meks. With the atomic weapons supply + depot bombarded, Nage decided to withdraw. + image: loweringboom_splash.png + trigger: + type: fledunits + modify: atend + units: [ 101, 102, 103, 104, 105, 106 ] + atLeast: 5 + + - header: A Draw! + text: Three Marik Meks managed to evade the Kell Hounds forces. This result is considered + a draw. + + Both sides suffered significant losses. With the atomic weapons supply + depot bombarded, Col. Nage decided to withdraw. + image: loweringboom_splash.png + trigger: + type: fledunits + modify: atend + units: [ 101, 102, 103, 104, 105, 106 ] + count: 3 + + - header: Marginal Marik Victory + text: Four Marik Meks managed to evade the Kell Hounds forces. The FWL + has won a marginal victory. + + Ward's Wolves failed to inflict significant losses on the Marik Meks. With the atomic weapons supply + depot bombarded, Col. Nage decided to withdraw. + image: loweringboom_splash.png + trigger: + type: fledunits + modify: atend + units: [ 101, 102, 103, 104, 105, 106 ] + count: 4 + + - header: Pilot Message + text: | + *Deborah Ryan:* They won't be catching me! + image: portraits/Female/MekWarrior/MW_F_83.png + trigger: + type: fledunits + modify: once + units: [ 106 ] + count: 1 + + - header: Pilot Message + text: | + *Col. Oliver Nage:* They got Deborah's Javelin! Let's hope she made it out of there. Keep going! + image: portraits/Male/MekWarrior/MW_M_15.png + trigger: + type: killedunit + modify: once + unit: 106 + + +end: + - trigger: + type: battlefieldcontrol diff --git a/crates/mms/tests/corpus/Kell Hounds/LoweringTheBoom/LoweringTheBoomReverse.mms b/crates/mms/tests/corpus/Kell Hounds/LoweringTheBoom/LoweringTheBoomReverse.mms new file mode 100644 index 0000000..848cccc --- /dev/null +++ b/crates/mms/tests/corpus/Kell Hounds/LoweringTheBoom/LoweringTheBoomReverse.mms @@ -0,0 +1,231 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# Based on Battlecorps Scenario 3011, Lowering the Boom, originally published in FASA's "Kell Hounds" sourcebook +MMSVersion: 2 +name: Lowering the Boom [Hunt] +planet: Castor +description: | + Lyran intelligence has found illegal atomic weapons on the Marik world of Castor. Katrina Steiner + has authorized an attack to remove the weapons and provide the Kell Hounds with action. + + *Goal: Playing as the Kell Hounds force, catch the Marik forces before they can flee.* + +map: + boardrows: 2 + boards: + - file: Map Set 2/16x17 BattleTech.board + - file: Map Set 2/16x17 Lake Area.board + modify: rotate + postprocess: + - type: convertterrain + terrain: woods + newlevel: 2 + - type: convertterrain + terrain: water + newlevel: 3 + +options: + file: gameoptions3025.xml + +factions: + + - name: Kell Hounds, First Battalion + camo: MERC - 1st Kell Hounds.gif + deploy: + edge: S + + victory: + - modify: onlyatend + trigger: + type: fledunits + modify: atend + units: [ 101, 102, 103, 104, 105, 106 ] + atmost: 2 + + units: + include: LoweringTheBoom_units_kell.mmu + + # OPFOR ---------------------------- + - name: Thirtieth Marik Militia + camo: Free Worlds League/Marik Militia/Marik Militia.jpg + deploy: N + + bot: + # try to get away + selfpreservation: 8 + fallshame: 8 + hyperaggression: 4 + herdmentality: 1 + bravery: 3 + # Princess respects the edge she is set to flee from + fleeto: north + flee: true + + victory: + - trigger: + type: fledunits + modify: atend + units: [ 101, 102, 103, 104, 105, 106 ] + atLeast: 4 + modify: onlyatend + + units: + include: LoweringTheBoom_units_marik.mmu + + +messages: + - header: Situation + text: | + # Situation + ## Castor + ## Free Worlds League + ## 7 June 3011 + + The idea for a Steiner raid on the Marik world of Castor originated with Cranston Snord, a well-known + eccentric and one of Katrina Steiner's long-serving mercenary commanders. Snord's sources had informed + him of a priceless collection of Fabergé Eggs located on Castor, and he already envisioned them as part + of his private museum on Clinton. + + Upon learning of Snord's plans, Morgan and Patrick Kell saw an opportunity to put their newly formed + unit to the test in battle. With Lyran intelligence uncovering a cache of illegal atomic weapons on + Castor, Katrina Steiner sanctioned the raid. The operation aimed to eliminate the weapons, secure the + eggs for Snord, and provide combat experience for the Kell Hounds. + + Colonel Nage recognized the battle was lost and retreated with his command company, aiming to reach the + atomic weapons and unleash them on the mercenaries. Colonel Kell dispatched Salome Ward and her + Relentless Wolves to intercept him. Ward's company caught up with Nage just as he was nearing the depot. + She launched an attack as the Second Battalion's LAMs bombed the depot. + + *This scenario is based on Battlecorps Scenario 3011, "Lowering the Boom", originally published in + the "Kell Hounds" sourcebook, FASA 01652.* + image: loweringboom_splash.png + trigger: + type: gamestart + + - header: Defender's Task + text: | + ## Defender's Task + + In this scenario, it is your task to stop Col. Oliver Nage and his elements of 1st Company, 1st Battalion + from reaching the weapons depot which lies beyond the north edge of the map. They will try to reach and + escape from this edge with as many Meks as possible. + + Some of their Meks have already sustained damage. + + *This game uses basic game rules.* + image: loweringboom_map_reverse.png + trigger: + type: and + triggers: + - type: phasestart + phase: movement + - type: round + round: 1 + + - header: One Unit Safe + text: Congratulations, one of your Meks has safely left the battlefield! + trigger: + type: fledunits + modify: once + units: [ 101, 102, 103, 104, 105, 106 ] + count: 1 + + - header: Another Unit Safe + text: Three of your Meks have safely left the battlefield! This game is already considered a draw. + trigger: + type: fledunits + modify: once + units: [ 101, 102, 103, 104, 105, 106 ] + atLeast: 3 + + - header: Decisive Victory + text: | + ## Decisive Victory + + Not more than one Marik Mek managed to evade the Kell Hounds forces. The Kell Hounds + have won a decivise victory. + + Ward's Wolves inflicted significant losses on the Marik Meks. Witnessing the smoke rising + from the depot and his units sustaining severe damage, Nage decided to flee, with the relentless + pursuit of the Wolves close behind. + + image: loweringboom_splash.png + trigger: + type: fledunits + modify: atend + units: [ 101, 102, 103, 104, 105, 106 ] + atmost: 1 + + - header: Marginal Victory + text: | + ## Marginal Victory + + Only two Marik Meks managed to evade the Kell Hounds forces. + + Ward's Wolves inflicted significant losses on the Marik Meks. Witnessing the smoke rising + from the depot and his units sustaining severe damage, Nage decided to flee, with the relentless + pursuit of the Wolves close behind. + image: loweringboom_splash.png + trigger: + type: fledunits + modify: atend + units: [ 101, 102, 103, 104, 105, 106 ] + count: 2 + + - header: A Draw! + text: Three Marik Meks managed to evade the Kell Hounds forces. This result is considered + a draw. + + Both sides suffered significant losses. With the atomic weapons supply + depot bombarded, Col. Nage decided to withdraw. + image: loweringboom_splash.png + trigger: + type: fledunits + modify: atend + units: [ 101, 102, 103, 104, 105, 106 ] + count: 3 + + - header: Marginal Defeat + text: Four Marik Meks managed to evade the Kell Hounds forces. + + Ward's Wolves failed to inflict significant losses on the Marik Meks. With the atomic weapons supply + depot bombarded, Col. Nage decided to withdraw. + image: loweringboom_splash.png + trigger: + type: fledunits + modify: atend + units: [ 101, 102, 103, 104, 105, 106 ] + count: 4 + + - header: Decisive Defeat + text: At least five Marik Meks managed to evade the Kell Hounds forces. The opposing FWL + has won a decivise victory. + + Ward's Wolves failed to inflict significant losses on the Marik Meks. With the atomic weapons supply + depot bombarded, Nage decided to withdraw. + image: loweringboom_splash.png + trigger: + type: fledunits + modify: atend + units: [ 101, 102, 103, 104, 105, 106 ] + atLeast: 5 + +end: + - trigger: + type: battlefieldcontrol + diff --git a/crates/mms/tests/corpus/Kell Hounds/ToSaveAPrince/ToSaveAPrince.mms b/crates/mms/tests/corpus/Kell Hounds/ToSaveAPrince/ToSaveAPrince.mms new file mode 100644 index 0000000..5b44c8c --- /dev/null +++ b/crates/mms/tests/corpus/Kell Hounds/ToSaveAPrince/ToSaveAPrince.mms @@ -0,0 +1,399 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# Based on Battlecorps Scenario 3013, To Save a Prince, originally published in FASA's "Kell Hounds" sourcebook +MMSVersion: 2 +name: To Save a Prince +planet: Mallory's World +description: | + Prince Ian Davion commands the Fourth Davion Guards in an attempt to repel the Combine invasion on + Mallory's World. After Kurita successes against the planetary defenders, the prince summoned a Kell + Hounds Regiment. + + *Goal: Playing as Zakahashi's Zombies, break through the Kell Hounds forces.* + +map: + boardrows: 2 + boards: + - file: Map Set 5/16x17 Open Terrain 1.board + modify: rotate + - file: Map Set 5/16x17 Open Terrain 1.board + postprocess: + - type: settheme + theme: desert + +planetaryconditions: + temperature: 70 + +factions: +- name: Zakahashi's Zombies + camo: Draconis Combine/Dieron Regulars/Dieron Regulars.jpg + deploy: N + + fleefrom: + border: south + + victory: + - trigger: + type: fledunits + units: [ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112 ] + atLeast: 6 + + units: + - fullname: BattleMaster BLR-1G + id: 101 + force: 2nd Sword of Light|21||Zakahashi's Zombies|22||Assault Lance|23 + crew: + name: Tai-i Tendoru Zakahashi + piloting: 3 + gunnery: 3 + + - fullname: Marauder MAD-3R + id: 102 + force: 2nd Sword of Light|21||Zakahashi's Zombies|22||Assault Lance|23 + crew: + name: Anson McMurphy + piloting: 4 + gunnery: 4 + + - fullname: Grasshopper GHR-5H + id: 103 + force: 2nd Sword of Light|21||Zakahashi's Zombies|22||Assault Lance|23 + crew: + name: Franco Jones + callsign: Frito + piloting: 4 + gunnery: 5 + + - fullname: Ostsol OTL-4D + id: 104 + force: 2nd Sword of Light|21||Zakahashi's Zombies|22||Assault Lance|23 + crew: + name: Art Shrett + piloting: 4 + gunnery: 5 + + - fullname: Orion ON1-K + id: 105 + force: 2nd Sword of Light|21||Zakahashi's Zombies|22||Support Lance|24 + crew: + name: Chu-i Susie Elgin + piloting: 3 + gunnery: 4 + + - fullname: Rifleman RFL-3N + id: 106 + force: 2nd Sword of Light|21||Zakahashi's Zombies|22||Support Lance|24 + remaining: + armor: + RTR: 0 + crits: + LA: 5 + crew: + name: Cletus Palmer + piloting: 4 + gunnery: 3 + + - fullname: Dervish DV-6M + id: 107 + force: 2nd Sword of Light|21||Zakahashi's Zombies|22||Support Lance|24 + remaining: + armor: + CT: 10 + crits: + CT: 11 + LL: [ 5, 6 ] + RL: [ 5, 6 ] + crew: + name: Tom Meyer + callsign: Hands + piloting: 3 + gunnery: 5 + + - fullname: Enforcer ENF-4R + id: 108 + force: 2nd Sword of Light|21||Zakahashi's Zombies|22||Support Lance|24 + crew: + name: Charlie Adams + piloting: 4 + gunnery: 4 + + - fullname: Javelin JVN-10N + id: 109 + force: 2nd Sword of Light|21||Zakahashi's Zombies|22||Light Lance|25 + crew: + name: Chu-i Bob Crenshaw + callsign: Texas + piloting: 4 + gunnery: 5 + + - fullname: Stinger STG-3R + id: 110 + force: 2nd Sword of Light|21||Zakahashi's Zombies|22||Light Lance|25 + crew: + name: John Parthan + callsign: Wrinkles + piloting: 5 + gunnery: 3 + + - fullname: Locust LCT-1V + id: 111 + force: 2nd Sword of Light|21||Zakahashi's Zombies|22||Light Lance|25 + crew: + name: Chuck Stork + callsign: Bossman + piloting: 5 + gunnery: 5 + + - fullname: Locust LCT-1V + id: 112 + force: 2nd Sword of Light|21||Zakahashi's Zombies|22||Light Lance|25 + crew: + name: Jack Brockman + callsign: Hun Killer + piloting: 4 + gunnery: 5 + +# OPFOR ----------------------- + +- name: Kell Hounds, Second Battalion + camo: Mercs/Kell Hounds.jpg + deploy: + edge: S + + victory: + - trigger: + type: fledunits + modify: atend + units: [ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112 ] + atmost: 5 + + units: + - fullname: Crusader CRD-3R + id: 201 + force: 2nd Mek Battalion|11||1st Company|12||Assault Lance|13 + deploymentround: 2 + crew: + name: Cpt. Carrol O'Caithan + piloting: 4 + gunnery: 3 + + - fullname: Warhammer WHM-6R + id: 202 + force: 2nd Mek Battalion|11||1st Company|12||Assault Lance|13 + deploymentround: 2 + crew: + name: Michael Kiltartan + piloting: 5 + gunnery: 4 + + - fullname: JagerMech JM6-S + id: 203 + force: 2nd Mek Battalion|11||1st Company|12||Assault Lance|13 + deploymentround: 2 + crew: + name: Rich O'Hare + piloting: 5 + gunnery: 4 + + - fullname: Catapult CPLT-C1 + id: 204 + force: 2nd Mek Battalion|11||1st Company|12||Assault Lance|13 + deploymentround: 2 + crew: + name: Hypatia Donahue + piloting: 4 + gunnery: 3 + + - fullname: Clint CLNT-2-3T + id: 205 + force: 2nd Mek Battalion|11||1st Company|12||Fire Lance|14 + deploymentround: 2 + crew: + name: Lt. Jane Near + piloting: 5 + gunnery: 4 + + - fullname: Vulcan VL-2T + id: 206 + force: 2nd Mek Battalion|11||1st Company|12||Fire Lance|14 + deploymentround: 2 + crew: + name: Julian Goodrich + piloting: 5 + gunnery: 4 + + - fullname: Enforcer ENF-4R + id: 207 + force: 2nd Mek Battalion|11||1st Company|12||Fire Lance|14 + deploymentround: 2 + crew: + name: Robert Cross + piloting: 4 + gunnery: 3 + + - fullname: Dervish DV-6M + id: 208 + force: 2nd Mek Battalion|11||1st Company|12||Fire Lance|14 + deploymentround: 2 + crew: + name: Robin Buckley + piloting: 5 + gunnery: 4 + + - fullname: Jenner JR7-D + id: 209 + force: 2nd Mek Battalion|11||1st Company|12||Recon Lance|15 + crew: + name: Lt. Jim Morrel + piloting: 4 + gunnery: 3 + + - fullname: Valkyrie VLK-QA + id: 210 + force: 2nd Mek Battalion|11||1st Company|12||Recon Lance|15 + crew: + name: Kevin Connor + piloting: 5 + gunnery: 4 + + - fullname: UrbanMech UM-R60 + id: 211 + force: 2nd Mek Battalion|11||1st Company|12||Recon Lance|15 + crew: + name: Estyn Burns + piloting: 5 + gunnery: 4 + + - fullname: Javelin JVN-10N + id: 212 + force: 2nd Mek Battalion|11||1st Company|12||Recon Lance|15 + crew: + name: Erin Finney + piloting: 4 + gunnery: 3 + +messages: + - header: Situation + text: | + # Situation + ## Mallory's World, Desolate Pass + ## Federated Suns + ## October 3013 + + In a wave of assaults by House Kurita forces on Mallory's World, the Second Sword of Light + Regiment, backed by the Twenty-fourth Dieron Regulars, landed and clashed with the defending Seventeenth + Avalon Hussars. Prince Ian Davion, leading the Fourth Davion Guards, swiftly responded to the Combine's + invasion. + + Despite early victories by Davion's forces, the Kurita units inflicted heavy damage on the + Seventeenth Avalon Hussars, exposing the Fourth Guards to significant danger. In response, Prince Ian + called for reinforcements from the Kell Hounds Regiment, stationed ten days away on Mara. + + By the time the Kell Hounds reached the battlefield, the situation for the defenders had grown dire. The + Twenty-fourth Dieron Regulars had severed the Fourth Davion Guards' lines of communication and driven them + into the desert, the Second Sword of Light Regiment in pursuit. At last, at Desolate Pass, Prince Ian turned + his battered Atlas to confront the Warhammer of Yorinaga Kurita, commander of the Second Sword. + + In a desperate last stand Prince Ian Davion bought his regiment precious time to retreat, but did not + survive the fight. Right before other Kurita forces could seize his body, the long-awaited Kell Hounds arrived. + + *This scenario is based on Battlecorps Scenario 3013, "To Save a Prince", originally published in + the "Kell Hounds" sourcebook, FASA 01652.* + image: tosaveaprince_splash.png + trigger: + type: gamestart + + - header: Attacker's Task + text: | + ## Attacker's Task + + In this scenario, it is your task to break through the Kell Hounds defense line and exit at least half your + Meks off the southern map edge by the end of round 15. + + The temperature in this desert area is at 70°C, adding heat to all Meks. + image: tosaveaprince_map.png + trigger: + type: and + triggers: + - type: phasestart + phase: deployment + - type: round + round: 0 + + - header: One Unit Safe + text: Congratulations, one of your Meks has successfully broken through! + trigger: + type: fledunits + modify: once + units: [ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112 ] + atLeast: 1 + atmost: 2 + + - header: Another Unit Safe + text: Three of your Meks have broken through! + trigger: + type: fledunits + modify: once + units: [ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112 ] + count: 3 + + - header: Defeat + text: | + ## Defeat + + The Kell Hounds managed to drive off the Second Sword of Light, enabling the Fourth Davion + Guards to step in reclaim Prince Ian's body. + + Yorinaga Kurita is not pleased. + + image: tosaveaprince_splash.png + trigger: + type: fledunits + modify: [ atend, once ] + units: [ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112 ] + atmost: 5 + + - header: Victory + text: | + ## Victory + + The Second Sword of Light successfully broke through the line of the Davion defenses. They will not + recover their Prince's body. This will be a heavy blow to their morale. + + Yorinaga Kurita commends your performance by not being displeased. + + image: tosaveaprince_splash.png + trigger: + type: fledunits + modify: [ atend, once ] + units: [ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112 ] + atLeast: 6 + +end: + - trigger: + type: battlefieldcontrol + + - trigger: + type: killedunits + units: [ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112 ] + # can't get through with half the force anymore when 7 are killed + atLeast: 7 + + - trigger: + type: roundend + round: 15 diff --git a/crates/mms/tests/corpus/LawyersGunsAndMoney/1-TheMistake.mms b/crates/mms/tests/corpus/LawyersGunsAndMoney/1-TheMistake.mms new file mode 100644 index 0000000..84348df --- /dev/null +++ b/crates/mms/tests/corpus/LawyersGunsAndMoney/1-TheMistake.mms @@ -0,0 +1,68 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# +# A MegaMek Scenario file +# Based on Lawyers, Guns & Money CBT scenario package +# Scenario available at http://www.classicbattletech.com/pdf/Lawyers,_Guns,_and_Money.pdf +# Converted for MegaMek by Steve "ScAvenger001" Simon (ScAvenger004@yahoo.com) + +MMSVersion=1 + + +Name=The Mistake + + +Description=LG&M first mission + + +BoardWidth=1 +BoardHeight=2 + +Maps=Map Set 2/16x17 BattleTech,Map Set 2/16x17 BattleTech + + +Factions=RedfieldsRenegades,LoneStar + +Location_RedfieldsRenegades=S +Location_LoneStar=N + +#XO Command Lance, Mustafa's Marauders +Unit_RedfieldsRenegades_1=Cyclops CP-10-Z,Major Clyde Mustafa,3,3 +Unit_RedfieldsRenegades_2=Zeus ZEU-6S,Nina Reinhart,3,4 +Unit_RedfieldsRenegades_3=Grasshopper GHR-5H,Shani Rand,2,4 +Unit_RedfieldsRenegades_4=Hunchback HBK-4G,Curtis Jenkins,2,4,N,8,18 +#Fire Support Lance, Miguel's Archers +Unit_RedfieldsRenegades_5=Catapult CPLT-C1,Lt. Miguel Ramirez,3,4 +Unit_RedfieldsRenegades_6=Whitworth WTH-1,Andrea Hallock,4,4 +Unit_RedfieldsRenegades_7=Trebuchet TBT-5N,Adam Fike,3,4 +Unit_RedfieldsRenegades_8=Dervish DV-6M,Farid Jajou,3,4 + +#Assault Lance, Vagnozzi's Vanguard +Unit_LoneStar_1=Atlas AS7-D,Ranger-Captain Steve Vagnozzi,3,3 +Unit_LoneStar_2=Awesome AWS-8Q,Carl Porter,3,4 +Unit_LoneStar_3=Banshee BNC-3E,Bill Underhill,3,4 +Unit_LoneStar_4=Dragon DRG-1N,Melissa Lopez,3,2 +#Strike Lance, Davis' Desperadoes +Unit_LoneStar_5=Assassin ASN-21,Deputy James Davis,3,3,S,8,16 +Unit_LoneStar_6=Enforcer ENF-4R,Kasha Lowe,3,4 +Unit_LoneStar_7=Clint CLNT-2-3T,Brian Kolowski,4,5 +Unit_LoneStar_8=Jenner JR7-D,Andrew Thompson,3,4,S,5,10 + +Unit_LoneStar_5_DamageSpecific=N1:0,I1:4,R3:0,I3:8 +Unit_RedfieldsRenegades_4_CritHit=0:2 +Unit_RedfieldsRenegades_4_SetAmmoTo=3:1-4 diff --git a/crates/mms/tests/corpus/LawyersGunsAndMoney/2-ADevelopingSituation.mms b/crates/mms/tests/corpus/LawyersGunsAndMoney/2-ADevelopingSituation.mms new file mode 100644 index 0000000..8c494c9 --- /dev/null +++ b/crates/mms/tests/corpus/LawyersGunsAndMoney/2-ADevelopingSituation.mms @@ -0,0 +1,70 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# +# A MegaMek Scenario file +# Based on Lawyers, Guns & Money CBT scenario package +# Scenario available at http://www.classicbattletech.com/pdf/Lawyers,_Guns,_and_Money.pdf +# Converted for MegaMek by Steve "ScAvenger001" Simon (ScAvenger004@yahoo.com) + +MMSVersion=1 + + +Name=A Developing Situation + + +Description=LG&M second mission + + +BoardWidth=2 +BoardHeight=1 + +Maps=Map Set 7/16x17 Military Base 2,Map Set 7/16x17 Military Base 1 + + +Factions=RedfieldsRenegades,LoneStar + + +Location_RedfieldsRenegades=E +Location_LoneStar=W + + + + +#Redfield's Renegades +#Command Lance, Redfield's Raiders +Unit_RedfieldsRenegades_1=BattleMaster BLR-3M,Colonel Sarah Redfield,2,3 +Unit_RedfieldsRenegades_2=Awesome AWS-9M,Lieutenant Robert Diaz,3,4 +Unit_RedfieldsRenegades_3=Axman AXM-1N,MekWarrior Jeff Bowen,3,4 +Unit_RedfieldsRenegades_4=Archer ARC-5R,MekWarrior Stephanie Amos,2,3 +#Recon Lance, Gessert's Grenadiers +Unit_RedfieldsRenegades_5=Phoenix Hawk PXH-3K,Sergeant Ann Gessert,2,3 +Unit_RedfieldsRenegades_6=Wolfhound WLF-1,MekWarrior C.C. DiPonio,3,4 +Unit_RedfieldsRenegades_7=Jackal JA-KL-1532,MekWarrior Rachael Chirenko,3,4 +Unit_RedfieldsRenegades_8=Locust LCT-3M,MekWarrior Howell Bertsch,3,3 + +#Lone Star Battalion +#Command Lance, The Colonel's Own +Unit_LoneStar_1=Warhammer WHM-7M,Ranger-Colonel Brown,2,2 +Unit_LoneStar_2=Marauder MAD-5D,Ranger-Lieutenant Amy Ghastin,3,3 +Unit_LoneStar_3=Stalker STK-5S,Ranger-Sergeant Caspar Mayfield,2,3 +Unit_LoneStar_4=Wolverine WVR-7K,MekWarrior Patrick Morganthayler,3,3 +#Strike Lance, Jarvis' Jokers +Unit_LoneStar_5=Hatchetman HCT-5S,Ranger-Lieutenant Vernon Jarvis,3,3 +Unit_LoneStar_6=Phoenix Hawk PXH-3D,MekWarrior Shaunda Berquist,3,3 +Unit_LoneStar_7=Griffin GRF-3M,MekWarrior Bryan Fochs,3,3 +Unit_LoneStar_8=Wasp WSP-1D,MekWarrior Jasmine Howell,3,4 diff --git a/crates/mms/tests/corpus/LawyersGunsAndMoney/3-PaybackIsAMedevac.mms b/crates/mms/tests/corpus/LawyersGunsAndMoney/3-PaybackIsAMedevac.mms new file mode 100644 index 0000000..9ccebbf --- /dev/null +++ b/crates/mms/tests/corpus/LawyersGunsAndMoney/3-PaybackIsAMedevac.mms @@ -0,0 +1,67 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# +# A MegaMek Scenario file +# Based on Lawyers, Guns & Money CBT scenario package +# Scenario available at http://www.classicbattletech.com/pdf/Lawyers,_Guns,_and_Money.pdf +# Converted for MegaMek by Steve "ScAvenger001" Simon (ScAvenger004@yahoo.com) + +MMSVersion=1 + + +Name=Payback's a Medevac + + +Description=LG&M third mission + + +BoardWidth=1 +BoardHeight=2 + +Maps=Map Set 3/16x17 Desert Sinkhole 1,Map Set 3/16x17 Desert Sinkhole 2 + + +Factions=RedfieldsRenegades,LoneStar + + +Location_RedfieldsRenegades=N +Location_LoneStar=S + + +#Sweep Lance, Foster's Strikers +Unit_RedfieldsRenegades_1=Thunderbolt TDR-7M,Sergeant Linda Foster,3,4 +Unit_RedfieldsRenegades_2=Shadow Hawk SHD-5M,MekWarrior Josiah Miller,3,4 +Unit_RedfieldsRenegades_3=Rifleman RFL-5M,MekWarrior Solomon Gayle,3,4 +Unit_RedfieldsRenegades_4=Wolverine WVR-7D,MekWarrior Ernie Fowlks,3,3 + +#Harasser Lance, Hankerd's Hitmen +Unit_RedfieldsRenegades_5=Wasp WSP-3M,Sergeant Kechia Hankerd,3,3 +Unit_RedfieldsRenegades_6=Commando COM-5S,MekWarrior Mikhail Blodwin,3,4 +Unit_RedfieldsRenegades_7=Falcon Hawk FNHK-9K1A,MekWarrior Sunil Chow,2,3 +Unit_RedfieldsRenegades_8=Panther PNT-10K,MekWarrior Robert McCulloch,3,3 + +#XO Lance, Davis' Law-Dogs +Unit_LoneStar_1=Hatchetman HCT-5S,Ranger-Major Megan Davis,2,3 +Unit_LoneStar_2=Charger CGR-3K,MekWarrior Alex Popov,2,3 +Unit_LoneStar_3=Snake SNK-1V,MekWarrior Charles Taylor,3,4 +Unit_LoneStar_4=Vindicator VND-3L,MekWarrior Kelley McManus,3,3 +#Pursuit Lance, DeGroot's Deputies +Unit_LoneStar_5=Scorpion SCP-1O,Ranger-Sergeant John DeGroot,2,3 +Unit_LoneStar_6=Jenner JR7-K,MekWarrior Carolyn Hancock,3,4 +Unit_LoneStar_7=Raven RVN-3L,MekWarrior Sarah Kimbrough,3,4 +Unit_LoneStar_8=Spider SDR-7M,MekWarrior Daniel Ling,3,3 diff --git a/crates/mms/tests/corpus/LawyersGunsAndMoney/4-WhatreWeFightinFor.mms b/crates/mms/tests/corpus/LawyersGunsAndMoney/4-WhatreWeFightinFor.mms new file mode 100644 index 0000000..88a6373 --- /dev/null +++ b/crates/mms/tests/corpus/LawyersGunsAndMoney/4-WhatreWeFightinFor.mms @@ -0,0 +1,65 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# +# A MegaMek Scenario file +# Based on Lawyers, Guns & Money CBT scenario package +# Scenario available at http://www.classicbattletech.com/pdf/Lawyers,_Guns,_and_Money.pdf +# Converted for MegaMek by Steve "ScAvenger001" Simon (ScAvenger004@yahoo.com) + +MMSVersion=1 + + +Name=What're We Fightin' For? + + +Description=LG&M fourth mission + + +BoardWidth=1 +BoardHeight=2 + +Maps=Map Set 5/16x17 Deep Canyon 1,Map Set 5/16x17 Deep Canyon 2 + + +Factions=RedfieldsRenegades,LoneStar + +Location_RedfieldsRenegades=N +Location_LoneStar=S + + +#Supression Lance, The Great Wall of Chou +Unit_RedfieldsRenegades_1=Guillotine GLT-5M,Captain Shu-shint Chou,3,3 +Unit_RedfieldsRenegades_2=Tempest TMP-3M,MekWarrior Kenneth McIntyre,3,4 +Unit_RedfieldsRenegades_3=Longbow LGB-7Q,MekWarrior Larry Doyle,3,3 +Unit_RedfieldsRenegades_4=Goliath GOL-3M,MekWarrior Douglas Wesolek,2,3 +#Security Lance, DeMarco's Demolishers +Unit_RedfieldsRenegades_5=Ostsol OTL-5M,Lieutenant Adolph DeMarco,3,4 +Unit_RedfieldsRenegades_6=Griffin GRF-3M,MekWarrior Dale Kempher,3,3 +Unit_RedfieldsRenegades_7=Lineholder KW1-LH2,MekWarrior James Hixson,3,4 +Unit_RedfieldsRenegades_8=Lynx LNX-9Q,MekWarrior G. Eisenlord,3,4 + +#Bodyguard Lance, Fracisco's Guardians +Unit_LoneStar_1=Crusader CRD-5M,Ranger-Lieutenant Churchill Francisco,3,3 +Unit_LoneStar_2=Archer ARC-4M,MekWarrior Hyeoksang Chung,3,4 +Unit_LoneStar_3=Warhammer WHM-7M,MekWarrior Erika Hladki,3,3 +Unit_LoneStar_4=Marauder MAD-5S,MekWarrior Olaf Mills,3,2 +#Attack Lance, Yee's Raiders +Unit_LoneStar_5=Blackjack BJ2-OC,Ranger-Lieutenant Andrew Yee,3,3 +Unit_LoneStar_6=Marshal MHL-X1,MekWarrior Terry Scwartz,2,3 +Unit_LoneStar_7=Helios HEL-3D,MekWarrior Byunghoon Choi,3,3 +Unit_LoneStar_8=Merlin MLN-1B,MekWarrior Nicole Papadopoulos,3,4 diff --git a/crates/mms/tests/corpus/Lucho/Citadel_Blitz_multiplayer.mms b/crates/mms/tests/corpus/Lucho/Citadel_Blitz_multiplayer.mms new file mode 100644 index 0000000..1c6579f --- /dev/null +++ b/crates/mms/tests/corpus/Lucho/Citadel_Blitz_multiplayer.mms @@ -0,0 +1,96 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek scenario file. +# It is 3057, and Clan Jade Falcon has sent a binary to hit the +# firebase protecting the launch facilities. If it falls the spaceport +# will be left defenseless! written by Lucho. Abril 2008. +MMSVersion=1 +Name=Citadel Blitz +Description=A loyal FedCom unit stands its ground against a Jade Falcon raid +BoardWidth=2 +BoardHeight=2 +MapWidth=50 +MapHeight=50 +Maps=unofficial/Lucho/pantanal,unofficial/Lucho/shenandoah,unofficial/Lucho/citadel,unofficial/Lucho/lago_mortis +Factions=AFFC1,AFFC2,AFFC3,JadeFalcon1,JadeFalcon2 +Location_AFFC1=NE +Location_AFFC2=NE +Location_AFFC3=NE +Location_JadeFalcon1=S +Location_JadeFalcon2=S +Team_AFFC1=1 +Team_AFFC2=1 +Team_AFFC3=1 +Team_JadeFalcon1=2 +Team_JadeFalcon2=2 +Unit_AFFC1_1=Atlas AS7-D,Capitao Nascimento,3,3,S,78,46 +Unit_AFFC1_2=Wraith TR1,Tenente Neto,3,4,S,81,48 +Unit_AFFC1_3=Archer ARC-4M,MekWarrior Andre,3,4,S,79,48 +Unit_AFFC1_4=Warhammer WHM-7S,MekWarrior Fabio,3,4,S,77,48 +Unit_AFFC1_5=Marauder MAD-5S,MekWarrior Romerito,3,4,S,83,48 +Unit_AFFC2_1=Heavy LRM Carrier (Standard),regular,4,6,S,78,41 +Unit_AFFC2_2=Heavy LRM Carrier (Standard),regular,4,6,S,78,41 +Unit_AFFC2_3=Heavy LRM Carrier (Standard),regular,4,6,S,80,41 +Unit_AFFC2_4=Heavy LRM Carrier (Standard),regular,4,6,S,80,41 +Unit_AFFC2_5=Savannah Master Hovercraft (Standard),regular,4,4,S,76,50 +Unit_AFFC2_6=Savannah Master Hovercraft (Standard),regular,4,4,S,77,50 +Unit_AFFC2_7=Savannah Master Hovercraft (Standard),regular,4,4,S,77,51 +Unit_AFFC2_8=Savannah Master Hovercraft (Standard),regular,4,4,S,78,50 +Unit_AFFC3_1=Drillson Heavy Hover Tank (Standard),Regular,4,4,SW,63,38 +Unit_AFFC3_2=Drillson Heavy Hover Tank (Standard),Regular,4,4,SW,63,39 +Unit_AFFC3_3=Rakshasa MDG-1A,MekWarrior Caio,3,4,SW,65,38 +Unit_AFFC3_4=Rakshasa MDG-1A,MekWarrior Jorge,3,4,SW,65,39 +Unit_AFFC3_5=Zeus ZEU-6T,Tenente Adriano,3,4,SW,64,38 +Unit_AFFC3_6=Falconer FLC-8R,MekWarrior Matias,3,4,SW,63,37 +Unit_JadeFalcon1_1=Turkina D,Terrence Mattlov,2,2,N,17,99 +Unit_JadeFalcon1_2=Thor (Summoner) D,Delot,2,3,N,15,98 +Unit_JadeFalcon1_3=Cougar H,Sabrina,3,3,N,16,98 +Unit_JadeFalcon1_4=Night Gyr Prime,Edee,3,3,N,18,98 +Unit_JadeFalcon1_5=Gladiator (Executioner) Prime,Jeremiah Hazen,2,3,N,17,98 +Unit_JadeFalcon2_1=Black Hawk (Nova) A,Greer,2,3,N,22,99 +Unit_JadeFalcon2_2=Nobori-nin (Huntsman) B,Francesca,2,3,N,21,99 +Unit_JadeFalcon2_3=Shadow Cat A,Jolene,3,3,N,24,99 +Unit_JadeFalcon2_4=Hellhound (Conjurer) (Standard),Casad,3,3,N,20,99 +Unit_JadeFalcon2_5=Thor (Summoner) A,Gillian,2,3,N,23,99 +Unit_AFFC1_1_AutoEject=false +Unit_AFFC1_2_AutoEject=false +Unit_AFFC1_3_AutoEject=false +Unit_AFFC1_4_AutoEject=false +Unit_AFFC1_5_AutoEject=false +Unit_AFFC3_1_AutoEject=false +Unit_AFFC3_2_AutoEject=false +Unit_AFFC3_3_AutoEject=false +Unit_AFFC3_4_AutoEject=false +Unit_AFFC3_5_AutoEject=false +Unit_AFFC3_6_AutoEject=false +Unit_JadeFalcon1_1_AutoEject=false +Unit_JadeFalcon1_2_AutoEject=false +Unit_JadeFalcon1_3_AutoEject=false +Unit_JadeFalcon1_4_AutoEject=false +Unit_JadeFalcon1_5_AutoEject=false +Unit_JadeFalcon2_1_AutoEject=false +Unit_JadeFalcon2_2_AutoEject=false +Unit_JadeFalcon2_3_AutoEject=false +Unit_JadeFalcon2_4_AutoEject=false +Unit_JadeFalcon2_5_AutoEject=false +Unit_AFFC1_1_Commander=true +Unit_AFFC2_5_Commander=true +Unit_AFFC3_5_Commander=true +Unit_JadeFalcon1_5_Commander=true +Unit_JadeFalcon2_1_Commander=true +GameOptionsFile=Citadel_Defense.xml diff --git a/crates/mms/tests/corpus/Lucho/Citadel_Blitz_oneonone.mms b/crates/mms/tests/corpus/Lucho/Citadel_Blitz_oneonone.mms new file mode 100644 index 0000000..421dc22 --- /dev/null +++ b/crates/mms/tests/corpus/Lucho/Citadel_Blitz_oneonone.mms @@ -0,0 +1,93 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek scenario file. +# It is 3057, and Clan Jade Falcon has sent a binary to hit the +# firebase protecting the launch facilities. If it falls the spaceport +# will be left defenseless! written by Lucho. Abril 2008. +MMSVersion=1 +Name=Citadel Blitz +Description=A loyal FedCom unit stands its ground against a Jade Falcon raid +BoardWidth=2 +BoardHeight=2 +MapWidth=50 +MapHeight=50 +Maps=unofficial/Lucho/pantanal,unofficial/Lucho/shenandoah,unofficial/Lucho/citadel,unofficial/Lucho/lago_mortis +Factions=AFFC,JadeFalcon +Location_AFFC=NE +Location_JadeFalcon=S +Unit_AFFC_1=Atlas AS7-D,Capitao Nascimento,3,3,S,78,46 +Unit_AFFC_2=Wraith TR1,Tenente Neto,3,4,S,81,48 +Unit_AFFC_3=Archer ARC-4M,MekWarrior Andre,3,4,S,79,48 +Unit_AFFC_4=Warhammer WHM-7S,MekWarrior Fabio,3,4,S,77,48 +Unit_AFFC_5=Marauder MAD-5S,MekWarrior Romerito,3,4,S,83,48 +Unit_AFFC_6=Heavy LRM Carrier (Standard),regular,4,6,S,78,41 +Unit_AFFC_7=Heavy LRM Carrier (Standard),regular,4,6,S,78,41 +Unit_AFFC_8=Heavy LRM Carrier (Standard),regular,4,6,S,80,41 +Unit_AFFC_9=Heavy LRM Carrier (Standard),regular,4,6,S,80,41 +Unit_AFFC_10=Savannah Master Hovercraft (Standard),regular,4,4,S,76,50 +Unit_AFFC_11=Savannah Master Hovercraft (Standard),regular,4,4,S,77,50 +Unit_AFFC_12=Savannah Master Hovercraft (Standard),regular,4,4,S,77,51 +Unit_AFFC_13=Savannah Master Hovercraft (Standard),regular,4,4,S,78,50 +Unit_AFFC_14=Drillson Heavy Hover Tank (Standard),Regular,4,4,SW,63,38 +Unit_AFFC_15=Drillson Heavy Hover Tank (Standard),Regular,4,4,SW,63,39 +Unit_AFFC_16=Rakshasa MDG-1A,MekWarrior Caio,3,4,SW,65,38 +Unit_AFFC_17=Rakshasa MDG-1A,MekWarrior Jorge,3,4,SW,65,39 +Unit_AFFC_18=Zeus ZEU-6T,Tenente Adriano,3,4,SW,64,38 +Unit_AFFC_19=Falconer FLC-8R,MekWarrior Matias,3,4,SW,63,37 +Unit_JadeFalcon_1=Turkina D,Terrence Mattlov,2,2,N,17,99 +Unit_JadeFalcon_2=Thor (Summoner) D,Delot,2,3,N,15,98 +Unit_JadeFalcon_3=Cougar H,Sabrina,3,3,N,16,98 +Unit_JadeFalcon_4=Night Gyr Prime,Edee,3,3,N,18,98 +Unit_JadeFalcon_5=Gladiator (Executioner) Prime,Jeremiah Hazen,2,3,N,17,98 +Unit_JadeFalcon_6=Black Hawk (Nova) A,Greer,2,3,N,22,99 +Unit_JadeFalcon_7=Nobori-nin (Huntsman) B,Francesca,2,3,N,21,99 +Unit_JadeFalcon_8=Shadow Cat A,Jolene,3,3,N,24,99 +Unit_JadeFalcon_9=Hellhound (Conjurer) (Standard),Casad,3,3,N,20,99 +Unit_JadeFalcon_10=Thor (Summoner) A,Gillian,2,3,N,23,99 +Unit_AFFC_1_AutoEject=false +Unit_AFFC_2_AutoEject=false +Unit_AFFC_3_AutoEject=false +Unit_AFFC_4_AutoEject=false +Unit_AFFC_5_AutoEject=false +Unit_AFFC_6_AutoEject=false +Unit_AFFC_7_AutoEject=false +Unit_AFFC_8_AutoEject=false +Unit_AFFC_9_AutoEject=false +Unit_AFFC_10_AutoEject=false +Unit_AFFC_11_AutoEject=false +Unit_AFFC_12_AutoEject=false +Unit_AFFC_13_AutoEject=false +Unit_AFFC_14_AutoEject=false +Unit_AFFC_15_AutoEject=false +Unit_AFFC_16_AutoEject=false +Unit_AFFC_17_AutoEject=false +Unit_AFFC_18_AutoEject=false +Unit_AFFC_19_AutoEject=false +Unit_JadeFalcon_1_AutoEject=false +Unit_JadeFalcon_2_AutoEject=false +Unit_JadeFalcon_3_AutoEject=false +Unit_JadeFalcon_4_AutoEject=false +Unit_JadeFalcon_5_AutoEject=false +Unit_JadeFalcon_6_AutoEject=false +Unit_JadeFalcon_7_AutoEject=false +Unit_JadeFalcon_8_AutoEject=false +Unit_JadeFalcon_9_AutoEject=false +Unit_JadeFalcon_10_AutoEject=false +Unit_AFFC_1_Commander=true +Unit_JadeFalcon_5_Commander=true +GameOptionsFile=Citadel_Defense.xml diff --git a/crates/mms/tests/corpus/Lucho/Last_Stand_at_Guanabara.mms b/crates/mms/tests/corpus/Lucho/Last_Stand_at_Guanabara.mms new file mode 100644 index 0000000..379d50f --- /dev/null +++ b/crates/mms/tests/corpus/Lucho/Last_Stand_at_Guanabara.mms @@ -0,0 +1,105 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek scenario file. +# During the FedCom civil war the Jade Falcons decide to stage +# a raid in the Lyran Alliance.Control of the world falls to the +# remains of the battered remains of the lyran defenders and a +# falcon unit hungry for glory. + +MMSVersion=1 +Name=Last Stand at Guanabara +Description=Clan Jade Falcon stages a raid on a lyran world +BoardWidth=2 +BoardHeight=2 +MapWidth=50 +MapHeight=50 +Maps=unofficial/Lucho/shenandoah,unofficial/Lucho/litoral,rotate:unofficial/Lucho/citadel,unofficial/Lucho/pantanal +Factions=LAAF,JadeFalcon +Location_LAAF=E +Location_JadeFalcon=S +Unit_LAAF_1=Atlas AS7-D,Kommandant Nascimento,3,3,S,56,31 +Unit_LAAF_2=Archer ARC-4M,MekWarrior Andre,4,5,S,58,30 +Unit_LAAF_3=Warhammer WHM-7S,MekWarrior Fabio,3,4,SW,54,32 +Unit_LAAF_4=Marauder MAD-5S,MekWarrior Romerito,3,4,S,55,33 +Unit_LAAF_5=Drillson Heavy Hover Tank (Standard),regular,4,4,SW,71,39 +Unit_LAAF_6=Drillson Heavy Hover Tank (Standard),regular,4,4,SW,72,39 +Unit_LAAF_7=Drillson Heavy Hover Tank (Standard),regular,4,4,SW,73,40 +Unit_LAAF_8=Drillson Heavy Hover Tank (Standard),regular,4,4,SW,74,40 +Unit_LAAF_9=Savannah Master Hovercraft (Standard),regular,4,4,SW,67,37 +Unit_LAAF_10=Savannah Master Hovercraft (Standard),regular,4,4,SW,67,38 +Unit_LAAF_11=Savannah Master Hovercraft (Standard),regular,4,4,SW,69,39 +Unit_LAAF_12=Savannah Master Hovercraft (Standard),regular,4,4,SW,68,38 +Unit_LAAF_13=Neptune Submarine (Standard),Regular,4,5,SW,66,47 +Unit_LAAF_14=Neptune Submarine (Standard),Regular,4,5,SW,66,49 +Unit_LAAF_15=Neptune Submarine (Standard),Regular,4,5,SE,72,47 +Unit_LAAF_16=Neptune Submarine (Standard),Regular,4,5,SE,72,49 +Unit_LAAF_17=Rakshasa MDG-1A,MekWarrior Caio,4,5,SW,33,38 +Unit_LAAF_18=Rakshasa MDG-1A,MekWarrior Jorge,4,5,SW,32,37 +Unit_LAAF_19=Zeus ZEU-6T,Tenente Adriano,3,3,SW,35,38 +Unit_LAAF_20=Falconer FLC-8R,MekWarrior Matias,3,4,S,37,41 +Unit_LAAF_21=Banshee BNC-5S,Kommandant Rafferty,3,3,SW,37,37 +Unit_LAAF_22=Caesar CES-4S,MekWarrior Cruz,3,4,SW,38,36 +Unit_LAAF_23=Dragon Fire DGR-4F,MekWarrior Jones,3,4,SW,36,36 +Unit_LAAF_24=Gallowglas GAL-1GLS,MekWarrior Murphy,3,3,SW,35,37 +Unit_JadeFalcon_1=Turkina D,Terrence Mattlov,2,2,N,31,100 +Unit_JadeFalcon_2=Thor (Summoner) D,Delot,2,3,N,30,99 +Unit_JadeFalcon_3=Grendel (Mongrel) D,Ethne,3,4,N,31,98 +Unit_JadeFalcon_4=Night Gyr A,Edee,3,3,N,31,99 +Unit_JadeFalcon_5=Thor (Summoner) H,Gillian,2,3,N,32,99 +Unit_JadeFalcon_6=Gladiator (Executioner) A,Jeremiah Hazen,2,3,N,38,97 +Unit_JadeFalcon_7=Uller (Kit Fox) S,Greer,2,3,N,37,99 +Unit_JadeFalcon_8=Pinion (Standard),Francesca,3,3,N,39,99 +Unit_JadeFalcon_9=Cougar H,Sabrina,3,4,N,37,98 +Unit_JadeFalcon_10=Hellhound (Conjurer) (Standard),Casad,3,4,N,39,99 +Unit_LAAF_1_AutoEject=true +Unit_LAAF_2_AutoEject=true +Unit_LAAF_3_AutoEject=true +Unit_LAAF_4_AutoEject=true +Unit_LAAF_5_AutoEject=true +Unit_LAAF_6_AutoEject=true +Unit_LAAF_7_AutoEject=true +Unit_LAAF_8_AutoEject=true +Unit_LAAF_9_AutoEject=true +Unit_LAAF_10_AutoEject=true +Unit_LAAF_11_AutoEject=true +Unit_LAAF_12_AutoEject=true +Unit_LAAF_13_AutoEject=true +Unit_LAAF_14_AutoEject=true +Unit_LAAF_15_AutoEject=true +Unit_LAAF_16_AutoEject=true +Unit_LAAF_17_AutoEject=true +Unit_LAAF_18_AutoEject=true +Unit_LAAF_19_AutoEject=true +Unit_LAAF_20_AutoEject=true +Unit_LAAF_21_AutoEject=true +Unit_LAAF_22_AutoEject=true +Unit_LAAF_23_AutoEject=true +Unit_LAAF_24_AutoEject=true +Unit_JadeFalcon_1_AutoEject=false +Unit_JadeFalcon_2_AutoEject=false +Unit_JadeFalcon_3_AutoEject=false +Unit_JadeFalcon_4_AutoEject=false +Unit_JadeFalcon_5_AutoEject=false +Unit_JadeFalcon_6_AutoEject=false +Unit_JadeFalcon_7_AutoEject=false +Unit_JadeFalcon_8_AutoEject=false +Unit_JadeFalcon_9_AutoEject=false +Unit_JadeFalcon_10_AutoEject=false +Unit_LAAF_1_Commander=true +Unit_JadeFalcon_1_Commander=true +GameOptionsFile=Citadel_Defense.xml diff --git a/crates/mms/tests/corpus/Lucho/Uninvited_Guests.mms b/crates/mms/tests/corpus/Lucho/Uninvited_Guests.mms new file mode 100644 index 0000000..0c4006f --- /dev/null +++ b/crates/mms/tests/corpus/Lucho/Uninvited_Guests.mms @@ -0,0 +1,80 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +MMSVersion=1 +Name=Uninvited Guests +Description=On an unnamed world, forces loyal to Katrina Steiner-Davion face a raiding party loyal to Victor Steiner-Davion +BoardWidth=1 +BoardHeight=2 +MapWidth=50 +MapHeight=50 +Maps=rotate:unofficial/Lucho/boca_del_diablo,rotate:unofficial/Lucho/Stronghold +Factions=FedSuns,LyranA,LyranB +Location_FedSuns=N +Location_LyranA=Any +Location_LyranB=Any +Team_FedSuns=1 +Team_LyranA=2 +Team_LyranB=2 + +Unit_FedSuns_1=Templar TLR1-OC,Capitao Nascimento,3,3,SE,05,01 +Unit_FedSuns_2=Sagittaire SGT-8R,Tenente Neto,3,3,S,07,02 +Unit_FedSuns_3=Cataphract CTF-3D,Tenente Andre,3,3,S,07,03 +Unit_FedSuns_4=Warhammer WHM-9D,MekWarrior Fabio,3,3,SE,08,02 +Unit_FedSuns_5=Falconer FLC-8R,MekWarrior Romerito,3,4,S,05,07 +Unit_FedSuns_6=Gallowglas GAL-1GLS,Tenente Bertrand,3,3,S,06,07 +Unit_FedSuns_7=Enforcer ENF-5D,MekWarrior Marc,3,4,S,06,06 +Unit_FedSuns_8=Falconer FLC-8R,MekWarrior Nikolas,3,4,S,07,07 +Unit_FedSuns_9=Rakshasa MDG-1A,MekWarrior Jackson,3,4,S,08,04 +Unit_FedSuns_10=Rakshasa MDG-1A,MekWarrior Roberts,3,4,S,07,04 +Unit_FedSuns_11=Argus AGS-4D,Tenente Gibson,3,4,S,08,05 +Unit_FedSuns_12=Dervish DV-8D,MekWarrior Thompson,3,4,S,07,05 +Unit_LyranA_1=Drillson Heavy Hover Tank (Standard),regular,4,4,N,21,55 +Unit_LyranA_2=Drillson Heavy Hover Tank (Standard),regular,4,4,N,20,55 +Unit_LyranA_3=Drillson Heavy Hover Tank (Standard),regular,4,4,N,22,55 +Unit_LyranA_4=Drillson Heavy Hover Tank (Standard),regular,4,4,N,21,56 +Unit_LyranA_5=Neptune Submarine (Standard),regular,4,5,N,07,46 +Unit_LyranA_6=Neptune Submarine (Standard),regular,4,5,N,07,46 +Unit_LyranA_7=Neptune Submarine (Standard),regular,4,5,N,12,47 +Unit_LyranA_8=Neptune Submarine (Standard),regular,4,5,N,12,47 +Unit_LyranA_9=Karnov UR Transport (Standard),regular,4,5,N,24,70 +Unit_LyranA_10=Karnov UR Transport (Standard),regular,4,4,N,24,75 +Unit_LyranA_11=Karnov UR Transport (Standard),regular,4,4,N,27,74 +Unit_LyranA_12=Karnov UR Transport (Standard),regular,4,4,N,27,72 +Unit_LyranA_13=Motorized Platoon (Laser),regular,4,5,N,29,73 +Unit_LyranA_14=Motorized Platoon (Laser),regular,4,5,N,29,73 +Unit_LyranA_15=Motorized Platoon (Laser),regular,4,5,N,29,73 +Unit_LyranA_16=Motorized Platoon (Laser),regular,4,5,N,29,73 +Unit_LyranA_17=Motorized Platoon (Laser),regular,4,5,N,25,71 +Unit_LyranA_18=Motorized Platoon (Laser),regular,4,5,N,25,71 +Unit_LyranA_19=Motorized Platoon (Laser),regular,4,5,N,25,71 +Unit_LyranA_20=Motorized Platoon (Laser),regular,4,5,N,25,71 +Unit_LyranA_21=Motorized Platoon (Laser),regular,4,5,N,25,75 +Unit_LyranA_22=Motorized Platoon (Laser),regular,4,5,N,25,75 +Unit_LyranA_23=Motorized Platoon (Laser),regular,4,5,N,25,75 +Unit_LyranA_24=Motorized Platoon (Laser),regular,4,5,N,25,75 +Unit_LyranB_1=Banshee BNC-5S,Kommandant Richmond,3,3,N,29,60 +Unit_LyranB_2=Zeus ZEU-9S,MekWarrior Greer,4,4,N,29,57 +Unit_LyranB_3=Barghest BGS-1T,MekWarrior Geist,3,4,N,29,58 +Unit_LyranB_4=Dragon Fire DGR-4F,MekWarrior Cicioli,3,4,N,29,59 +#Unit_FedSuns_AutoEject=true +#Unit_LyranA_AutoEject=true +#Unit_LyranB_AutoEject=true +Unit_FedSuns_1_Commander=true +#Unit_LyranA_1_Commander=true +Unit_LyranB_1_Commander=true +GameOptionsFile=Uninvited_Guests.xml diff --git a/crates/mms/tests/corpus/MonteDiablo/MonteDiablo-Four_Players.mms b/crates/mms/tests/corpus/MonteDiablo/MonteDiablo-Four_Players.mms new file mode 100644 index 0000000..4124cc3 --- /dev/null +++ b/crates/mms/tests/corpus/MonteDiablo/MonteDiablo-Four_Players.mms @@ -0,0 +1,53 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +MMSVersion=1 +Name=The Battle of Monte Diablo +Description=Elements from the 2nd and 7th Donegal Guards converge in on the 4th Crucis Lancers headquarters company +BoardWidth=3 +BoardHeight=1 +Maps=Map Set 2/16x17 BattleTech,Map Set 5/16x17 Large Mountain 1,scenarios/openterrain1(Lv6) +Factions=Steiner2nd,Steiner7th,Davion,ComStar +Location_Steiner7th=W +Location_Steiner2nd=E +Location_Davion=Any +Location_ComStar=Any +Unit_Steiner2nd_1=Fafnir FNR-5,Hauptmann Susan Reeves,3,3 +Unit_Steiner2nd_2=Rakshasa MDG-1A,Leutnant Will Barnes,3,4 +Unit_Steiner2nd_3=Falconer FLC-8R,Leutnant Parker Giraldi,3,4 +Unit_Steiner2nd_4=Barghest BGS-3T,Sergeant Mary Coronado,4,4 +Unit_Steiner2nd_5=Avatar AV1-OC,First Leutnant Joshua Ben-Gabriel,3,3 +Unit_Steiner2nd_6=Thanatos TNS-4S,Leutnant Yolanda Ruiz,3,4 +Unit_Steiner2nd_7=Javelin JVN-11D,Sergeant Scott Cowan,3,4 +Unit_Steiner2nd_8=Clint CLNT-5U,Sergeant Charles Helm,3,3 +Unit_Steiner7th_1=Uziel UZL-3S,Hauptmann Seth Carmen,3,3 +Unit_Steiner7th_2=Lynx LNX-9Q,First Leutnant Nicolas Orazco,4,4 +Unit_Steiner7th_3=Commando COM-7S,Leutnant Raymond Capazzutto,3,4 +Unit_Steiner7th_4=Razorback RZK-9S,Sergeant Sam Grant,3,3 +Unit_Davion_1=Sagittaire SGT-8R,Leftenant-General Andrew Giggins,2,2 +Unit_Davion_2=Argus AGS-4D,Captain John Smallberries,3,3 +Unit_Davion_3=Osiris OSR-3D,Sergeant Major Richard Jablonski,3,3 +Unit_Davion_4=Stiletto STO-4A,Sergeant Tina Rosche,4,4 +Unit_Davion_5=Templar TLR1-OB,Captain Brian Stull,3,4 +Unit_Davion_6=Hellspawn HSN-7D,Leftenant Grace Suithall,3,3 +Unit_Davion_7=Enfield END-6J,Sergeant Mack Acuna,3,3 +Unit_Davion_8=Bushwacker BSW-L1,Sergeant Kevin Bagwell,3,4 +Unit_ComStar_1=Thug THG-12E,Demi-Precentor VI Demetria Lonis,3,3 +Unit_ComStar_2=Tessen TSN-1C,Adept I John Rutter,4,4 +Unit_ComStar_3=Exterminator EXT-5E,Acolyte II Michael Albano,4,3 +Unit_ComStar_4=Hussar HSR-500-D,Adept III Mariah Freeman,4,3 diff --git a/crates/mms/tests/corpus/MonteDiablo/MonteDiablo-Two_Players.mms b/crates/mms/tests/corpus/MonteDiablo/MonteDiablo-Two_Players.mms new file mode 100644 index 0000000..621cdad --- /dev/null +++ b/crates/mms/tests/corpus/MonteDiablo/MonteDiablo-Two_Players.mms @@ -0,0 +1,51 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +MMSVersion=1 +Name=The Battle of Monte Diablo +Description=Elements from the 2nd and 7th Donegal Guards converge in on the 4th Crucis Lancers headquarters company +BoardWidth=3 +BoardHeight=1 +Maps=Map Set 2/16x17 BattleTech,Map Set 5/16x17 Large Mountain 1,scenarios/openterrain1(Lv6) +Factions=Steiner,Davion +Location_Steiner=Any +Location_Davion=Any +Unit_Steiner_1=Fafnir FNR-5,Hauptmann Susan Reeves,3,3 +Unit_Steiner_2=Rakshasa MDG-1A,Leutnant Will Barnes,3,4 +Unit_Steiner_3=Falconer FLC-8R,Leutnant Parker Giraldi,3,4 +Unit_Steiner_4=Barghest BGS-3T,Sergeant Mary Coronado,4,4 +Unit_Steiner_5=Avatar AV1-OC,First Leutnant Joshua Ben-Gabriel,3,3 +Unit_Steiner_6=Thanatos TNS-4S,Leutnant Yolanda Ruiz,3,4 +Unit_Steiner_7=Javelin JVN-11D,Sergeant Scott Cowan,3,4 +Unit_Steiner_8=Clint CLNT-5U,Sergeant Charles Helm,3,3 +Unit_Steiner_9=Uziel UZL-3S,Hauptmann Seth Carmen,3,3 +Unit_Steiner_10=Lynx LNX-9Q,First Leutnant Nicolas Orazco,4,4 +Unit_Steiner_11=Commando COM-7S,Leutnant Raymond Capazzutto,3,4 +Unit_Steiner_12=Razorback RZK-9S,Sergeant Sam Grant,3,3 +Unit_Davion_1=Sagittaire SGT-8R,Leftenant-General Andrew Giggins,2,2 +Unit_Davion_2=Argus AGS-4D,Captain John Smallberries,3,3 +Unit_Davion_3=Osiris OSR-3D,Sergeant Major Richard Jablonski,3,3 +Unit_Davion_4=Stiletto STO-4A,Sergeant Tina Rosche,4,4 +Unit_Davion_5=Templar TLR1-OB,Captain Brian Stull,3,4 +Unit_Davion_6=Hellspawn HSN-7D,Leftenant Grace Suithall,3,3 +Unit_Davion_7=Enfield END-6J,Sergeant Mack Acuna,3,3 +Unit_Davion_8=Bushwacker BSW-L1,Sergeant Kevin Bagwell,3,4 +Unit_Davion_9=Thug THG-12E,Demi-Precentor VI Demetria Lonis,3,3 +Unit_Davion_10=Tessen TSN-1C,Adept I John Rutter,4,4 +Unit_Davion_11=Exterminator EXT-5E,Acolyte II Michael Albano,4,3 +Unit_Davion_12=Hussar HSR-500-D,Adept III Mariah Freeman,4,3 diff --git a/crates/mms/tests/corpus/Northwind Highlanders/NWH2_InstantFame.mms b/crates/mms/tests/corpus/Northwind Highlanders/NWH2_InstantFame.mms new file mode 100644 index 0000000..217716e --- /dev/null +++ b/crates/mms/tests/corpus/Northwind Highlanders/NWH2_InstantFame.mms @@ -0,0 +1,61 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# +# Entered by Slade +# + +MMSVersion=1 + +Name=Instant Fame: Northwind Highlanders +Description=This scenario visits a fight of McCormack's Fusiliers in the campaign on Bellatrix against Ajax's Avengers. The retreating Avengers tried desperately to shake the Highlanders as they fled east across the southern continent of Bellatrix, but to no avail. Exhausted and bogged down by bad weather, the Avengers had no choice but to stand and fight. + +#FullDescription=The campaign on Bellatrix against Ajax's Avengers brought McCormack's Fusiliers instant fame. Arriving to find themselves outnumbered three-to-one, the Fusiliers quickly used their superior grasp of tactics and their ferocity to smash the defending unit. The retreating Avengers tried desperately to shake the Highlanders as they fled east across the southern continent of Bellatrix, but to no avail. Exhausted and bogged down by bad weather, the Avengers had no choice but to stand and fight. + +BoardWidth=2 +BoardHeight=1 + +Maps=Map Set 5/16x17 Open Terrain 1,Map Set 5/16x17 Open Terrain 2 + +Factions=Highlanders,Avengers + +Location_Highlanders=W +Location_Avengers=E + +Team_Highlanders=1 +Team_Avengers=2 + +Unit_Highlanders_1=Striker STC-2D,Lieutenant-Colonel Lucile Ferrel,3,4 +Unit_Highlanders_2=Orion ON1-M,Lieutenant Tim McBride,3,4 +Unit_Highlanders_3=Dervish DV-7D,Linda Steen,3,4 +Unit_Highlanders_4=Griffin GRF-1DS,Lectricia Green,3,5 +Unit_Highlanders_5=Archer ARC-4M,Captain Donald McCormack,4,4 +Unit_Highlanders_6=Rifleman RFL-5D,Lieutenant Jay Stein,3,4 +Unit_Highlanders_7=Ostroc OSR-2D,Gregory Bradley,4,4 +Unit_Highlanders_8=Shadow Hawk SHD-5M,Michal Digioa,4,5 +Unit_Highlanders_9=Clint CLNT-2-3U,Lieutenant Keri Rankin,3,4 +Unit_Highlanders_10=Commando COM-5S,Douglas Steffen,3,4 +Unit_Highlanders_11=Firestarter FS9-S,Laura Garger,4,5 + +Unit_Avengers_1=Warhammer WHM-7M,Captain Abel Lozano,3,3 +Unit_Avengers_2=Zeus ZEU-9S,Lieutenant Anne Jackson,3,4 +Unit_Avengers_3=Hunchback HBK-5M,Robert Langfelder,4,4 +Unit_Avengers_4=Warhammer WHM-7S,Jerome Morrissey,3,4 +Unit_Avengers_5=Stalker STK-5M,Lieutenant Gerald Dusca,3,4 +Unit_Avengers_6=Victor VTR-9K,David Payne,3,4 +Unit_Avengers_7=Warhammer WHM-7S,Ellen Gallagher,4,4 +Unit_Avengers_8=Thunderbolt TDR-9SE,Tosha Sanders,3,4 diff --git a/crates/mms/tests/corpus/OntheProwl/OntheProwl.mms b/crates/mms/tests/corpus/OntheProwl/OntheProwl.mms new file mode 100644 index 0000000..6aa564b --- /dev/null +++ b/crates/mms/tests/corpus/OntheProwl/OntheProwl.mms @@ -0,0 +1,49 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +MMSVersion=1 + +Name=On the Prowl +Description=A lone Smoke Jaguar attempts to cut a swathe through his enemies leaving their mangled wrecks in his wake. + +BoardWidth=1 +BoardHeight=3 +Maps=Map Set 3\16x17 Rolling Hills 2,Map Set 5\16x17 Open Terrain 2,Map Set 6\16x17 Woodland, + +Factions=Mistweaver,Genyosha +Location_Mistweaver=S +Location_Genyosha=N +Team_Mistweaver=1 +Team_Genyosha=2 + +Unit_Mistweaver_1=Masakari (Warhawk) C,MekWarrior Seth,3,4 +Unit_Genyosha_1=Grand Dragon DRG-5K,Gunjin Yoson Saito,4,4,S,10,20 +Unit_Genyosha_2=Archer ARC-5R,Gunjin Will Yamada,4,5,S,08,18 +Unit_Genyosha_3=Marauder MAD-5D,Kashira Ryuichi Head,3,3 +Unit_Genyosha_4=Atlas AS7-K,Chu-i Jabber Ozawa,3,4, + +Unit_Genyosha_3_DeploymentRound=4 +Unit_Genyosha_4_DeploymentRound=8 + +Unit_Mistweaver_1_AutoEject=false +Unit_Genyosha_1_AutoEject=true +Unit_Genyosha_2_AutoEject=false +Unit_Genyosha_3_AutoEject=false +Unit_Genyosha_4_AutoEject=true + +Camo_Mistweaver=Clans/Smoke Jaguar/,Beta Galaxy.jpg +Camo_Genyosha=Kurita/Genyosha/,1st Genyosha.jpg diff --git a/crates/mms/tests/corpus/SwordAndDragon.mms b/crates/mms/tests/corpus/SwordAndDragon.mms new file mode 100644 index 0000000..0ed817d --- /dev/null +++ b/crates/mms/tests/corpus/SwordAndDragon.mms @@ -0,0 +1,36 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +MMSVersion=1 +Name=The Sword and The Dragon +Description=A DCMS Support Lance is engaged by FedCom defenders during a Planetary Assault +BoardWidth=2 +BoardHeight=2 +RandomDirs=Map Set 2,Map Set 3,Map Set 4,Map Set 5,Map Set 6,Map Set 7 +Maps=RANDOM,RANDOM,RANDOM,RANDOM +Factions=Kurita,Davion +Location_Kurita=SE +Location_Davion=NW +Unit_Kurita_1=Awesome AWS-8Q,Kaneda Smythe,3,3 +Unit_Kurita_2=Dragon DRG-1C,Elliot Marlin,4,4 +Unit_Kurita_3=Panther PNT-9R,Hideyoshi Watanabe,4,4 +Unit_Kurita_4=Panther PNT-9R,Susan Takeda,4,3 +Unit_Davion_1=Griffin GRF-1N,Peter Fizer,3,3 +Unit_Davion_2=Crusader CRD-3D,Maverick,4,4 +Unit_Davion_3=Hunchback HBK-4J,Candice Coyle,4,5 +Unit_Davion_4=Wolverine WVR-6K,Gordon Lopez,4,4 diff --git a/crates/mms/tests/corpus/TheGreatRefusal/1-SmokeJaguarVsSteinerDavion.mms b/crates/mms/tests/corpus/TheGreatRefusal/1-SmokeJaguarVsSteinerDavion.mms new file mode 100644 index 0000000..ac300f6 --- /dev/null +++ b/crates/mms/tests/corpus/TheGreatRefusal/1-SmokeJaguarVsSteinerDavion.mms @@ -0,0 +1,31 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +MMSVersion=1 +Name=The Great Refusal - Battle 1 +Description=Strana Mechty, 23rd April 3060. Clan Smoke Jaguar V Federated Commonwealth +BoardWidth=1 +BoardHeight=1 +Maps=Map Set 3/16x17 Desert Mountain 2 +Factions=Jaguar,FedCom +Location_Jaguar=N +Location_FedCom=S +Unit_Jaguar_1=Elemental Battle Armor [Laser],Lincoln Osis,2,3 +Unit_Jaguar_2=Masakari (Warhawk) C,Kit Furey,1,2 +Unit_FedCom_1=Daishi (Dire Wolf) 'Prometheus',Victor Steiner-Davion,2,4 +Unit_FedCom_2=Falconer FLC-8R,Danai Centrella,3,4 diff --git a/crates/mms/tests/corpus/TheGreatRefusal/2-WolfVsIvesCompact.mms b/crates/mms/tests/corpus/TheGreatRefusal/2-WolfVsIvesCompact.mms new file mode 100644 index 0000000..47d3b35 --- /dev/null +++ b/crates/mms/tests/corpus/TheGreatRefusal/2-WolfVsIvesCompact.mms @@ -0,0 +1,31 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +MMSVersion=1 +Name=The Great Refusal - Battle 2 +Description=Strana Mechty, 23rd April 3060. Clan Wolf V St. Ives Compact +BoardWidth=1 +BoardHeight=1 +Maps=Map Set 5/16x17 Open Terrain 1 +Factions=Wolf,Compact +Location_Wolf=N +Location_Compact=S +Unit_Wolf_1=Mad Cat (Timber Wolf) Prime,Vlad Ward,1,2 +Unit_Wolf_2=Man O' War (Gargoyle) A,Marialle Radick,2,3 +Unit_Compact_1=Ryoken (Stormcrow) B,Kai Allard-Liao,0,0 +Unit_Compact_2=Pillager PLG-3Z,Adele Tsang,3,3 diff --git a/crates/mms/tests/corpus/TheGreatRefusal/3-BloodSpiritVsKurita.mms b/crates/mms/tests/corpus/TheGreatRefusal/3-BloodSpiritVsKurita.mms new file mode 100644 index 0000000..a8c11b9 --- /dev/null +++ b/crates/mms/tests/corpus/TheGreatRefusal/3-BloodSpiritVsKurita.mms @@ -0,0 +1,31 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +MMSVersion=1 +Name=The Great Refusal - Battle 3 +Description=Strana Mechty, 23rd April 3060. Clan Blood Spirit V Draconis Combine +BoardWidth=1 +BoardHeight=1 +Maps=Map Set 5/16x17 Deep Canyon 2 +Factions=BloodSpirit,DraconisCombine +Location_BloodSpirit=N +Location_DraconisCombine=S +Unit_BloodSpirit_1=Blood Kite (Standard),Karianna Schmitt,0,1 +Unit_BloodSpirit_2=Battle Cobra B,Troy Boques,1,2 +Unit_DraconisCombine_1=Daishi (Dire Wolf) (Hohiro),Hohiro Kurita,2,3 +Unit_DraconisCombine_2=No-Dachi NDA-1K,Takasuima Neril,3,4 diff --git a/crates/mms/tests/corpus/TheGreatRefusal/4-JadeFalconVsComstar.mms b/crates/mms/tests/corpus/TheGreatRefusal/4-JadeFalconVsComstar.mms new file mode 100644 index 0000000..1c91504 --- /dev/null +++ b/crates/mms/tests/corpus/TheGreatRefusal/4-JadeFalconVsComstar.mms @@ -0,0 +1,31 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +MMSVersion=1 +Name=The Great Refusal - Battle 4 +Description=Strana Mechty, 23rd April 3060. Clan Jade Falcon V ComStar +BoardWidth=1 +BoardHeight=1 +Maps=Map Set 2/16x17 Mountain Lake, +Factions=JadeFalcon,ComStar +Location_JadeFalcon=N +Location_Comstar=S +Unit_JadeFalcon_1=Thor (Summoner) Prime,Marthe Pryde,2,2 +Unit_JadeFalcon_2=Black Hawk (Nova) Prime,Samantha Clees,3,2 +Unit_Comstar_1=Black Knight BL-6-KNT,Harris Harvison,2,3 +Unit_Comstar_2=Shootist ST-8A,Julius Havanna,2,4 diff --git a/crates/mms/tests/corpus/TheGreatRefusal/5-StarAdderVsMarik.mms b/crates/mms/tests/corpus/TheGreatRefusal/5-StarAdderVsMarik.mms new file mode 100644 index 0000000..f6137d9 --- /dev/null +++ b/crates/mms/tests/corpus/TheGreatRefusal/5-StarAdderVsMarik.mms @@ -0,0 +1,31 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +MMSVersion=1 +Name=The Great Refusal - Battle 5 +Description=Strana Mechty, 23rd April 3060. Clan Star Adder V Free Worlds League +BoardWidth=1 +BoardHeight=1 +Maps=Map Set 4/16x17 River Delta 2 +Factions=StarAdder,FreeWorldsLeague +Location_StarAdder=N +Location_FreeWorldsLeague=S +Unit_StarAdder_1=Kingfisher D,Cassius N'Buta,2,3 +Unit_StarAdder_2=Cauldron-Born (Ebon Jaguar) Prime,Tabitha Paik,1,2 +Unit_FreeWorldsLeague_1=Perseus P1B,Dana Briggs,2,3 +Unit_FreeWorldsLeague_2=Cerberus MR-5M,Nisa Gellar,2,4 diff --git a/crates/mms/tests/corpus/TheGreatRefusal/6-FireMandrilVsLiao.mms b/crates/mms/tests/corpus/TheGreatRefusal/6-FireMandrilVsLiao.mms new file mode 100644 index 0000000..dc66245 --- /dev/null +++ b/crates/mms/tests/corpus/TheGreatRefusal/6-FireMandrilVsLiao.mms @@ -0,0 +1,31 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +MMSVersion=1 +Name=The Great Refusal - Battle 6 +Description=Strana Mechty, 23rd April 3060. Clan Fire Mandrill V Capellan Confederation +BoardWidth=1 +BoardHeight=1 +Maps=Map Set 3/16x17 Rolling Hills 1, +Factions=Mandrill,Capellan +Location_Mandrill=N +Location_Capellan=S +Unit_Mandrill_1=Masakari (Warhawk) A,Amanda Carrol,2,2 +Unit_Mandrill_2=Man O' War (Gargoyle) C,Garret Sainze,2,3 +Unit_Capellan_1=Emperor EMP-6A,Andre Elias,2,3 +Unit_Capellan_2=Thunder THR-1L,Chu Duk-Lo,3,3 diff --git a/crates/mms/tests/corpus/TheGreatRefusal/7-HellsHorsesVsRasalhague.mms b/crates/mms/tests/corpus/TheGreatRefusal/7-HellsHorsesVsRasalhague.mms new file mode 100644 index 0000000..8f1e25d --- /dev/null +++ b/crates/mms/tests/corpus/TheGreatRefusal/7-HellsHorsesVsRasalhague.mms @@ -0,0 +1,31 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +MMSVersion=1 +Name=The Great Refusal - Battle 7 +Description=Strana Mechty, 23rd April 3060. Clan Hells Horses V Free Rasalhague Republic +BoardWidth=1 +BoardHeight=1 +Maps=Map Set 2/16x17 Desert Hills, +Factions=HellsHorses,Republic +Location_HellsHorses=N +Location_Republic=S +Unit_HellsHorses_1=Gnome Battle Armor (Standard),Malavai Fletcher,2,3 +Unit_HellsHorses_2=Man O' War (Gargoyle) C,Tanya Delaurel,2,3 +Unit_Republic_1=Spartan SPT-N2,Joan Dahlstrom,3,3 +Unit_Republic_2=Flashman FLS-8K,Stephan Igursson,3,3 diff --git a/crates/mms/tests/corpus/TrainingScenarios/1-FirstRun.mms b/crates/mms/tests/corpus/TrainingScenarios/1-FirstRun.mms new file mode 100644 index 0000000..7a70cf2 --- /dev/null +++ b/crates/mms/tests/corpus/TrainingScenarios/1-FirstRun.mms @@ -0,0 +1,32 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +# MegaMek Conversion by Phillip "bullseyesmith" Starke +MMSVersion=1 +Name=First Run +Description=Battletech 3rd Edition Training Scenario #1, pg. 36 +BoardWidth=2 +BoardHeight=1 +Maps=Map Set 2/16x17 BattleTech,Map Set 2/16x17 BattleTech +Factions=TraineeA,TraineeB +Location_TraineeA=S +Location_TraineeB=N +Unit_TraineeA_1=Wasp WSP-1A,Trainee A1,4,0 +Unit_TraineeA_2=Griffin GRF-1N,Trainee A2,4,0 +Unit_TraineeB_1=Stinger STG-3R,Trainee B1,4,0 +Unit_TraineeB_2=Wolverine WVR-6R,Trainee B2,4,0 diff --git a/crates/mms/tests/corpus/TrainingScenarios/2-FinalExam.mms b/crates/mms/tests/corpus/TrainingScenarios/2-FinalExam.mms new file mode 100644 index 0000000..93f9911 --- /dev/null +++ b/crates/mms/tests/corpus/TrainingScenarios/2-FinalExam.mms @@ -0,0 +1,30 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +# MegaMek Conversion by Phillip "bullseyesmith" Starke +MMSVersion=1 +Name=Final Exam +Description=Battletech 3rd Edition Training Scenario #2, pg. 37 +BoardWidth=2 +BoardHeight=1 +Maps=Map Set 2/16x17 BattleTech,rotate:Map Set 2/16x17 BattleTech +Factions=TraineeA,TraineeB +Location_TraineeA=Any +Location_TraineeB=Any +Unit_TraineeA_1=Thunderbolt TDR-5S,Trainee A,4,0 +Unit_TraineeB_1=Crusader CRD-3R,Trainee B,4,0 diff --git a/crates/mms/tests/corpus/TrainingScenarios/3-TrialByFire.mms b/crates/mms/tests/corpus/TrainingScenarios/3-TrialByFire.mms new file mode 100644 index 0000000..ebaa2ec --- /dev/null +++ b/crates/mms/tests/corpus/TrainingScenarios/3-TrialByFire.mms @@ -0,0 +1,33 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +# MegaMek Conversion by Phillip "bullseyesmith" Starke +MMSVersion=1 +Name=Trial By Fire +Description=Battletech 3rd Edition Training Scenario #3, pg. 38 +BoardWidth=1 +BoardHeight=2 +Maps=Map Set 2/16x17 BattleTech,Map Set 2/16x17 BattleTech +Factions=Defender,Attacker +Location_Defender=N +Location_Attacker=Any +Unit_Defender_1=BattleMaster BLR-1G,Lone MekWarrior,4,5 +Unit_Attacker_1=Locust LCT-1V,MekWarrior Light,3,5 +Unit_Attacker_2=Phoenix Hawk PXH-1,MekWarrior Medium,4,4 +Unit_Attacker_3=Rifleman RFL-3N,MekWarrior Heavy,3,4 +Unit_Defender_1_DamageSpecific=N1:27,N2:18,N3:18,N4:16,N5:16,N6:21,N7:21 diff --git a/crates/mms/tests/corpus/TrainingScenarios/4-DivideAndConquer.mms b/crates/mms/tests/corpus/TrainingScenarios/4-DivideAndConquer.mms new file mode 100644 index 0000000..279409d --- /dev/null +++ b/crates/mms/tests/corpus/TrainingScenarios/4-DivideAndConquer.mms @@ -0,0 +1,32 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +# MegaMek Conversion by Phillip "bullseyesmith" Starke +MMSVersion=1 +Name=Divide and Conquer +Description=Battletech 3rd Edition Training Scenario #4, pg. 39 +BoardWidth=1 +BoardHeight=2 +Maps=rotate:Map Set 2/16x17 BattleTech,Map Set 2/16x17 BattleTech +Factions=Defender,Attacker +Location_Defender=Any +Location_Attacker=Any +Unit_Defender_1=Archer ARC-2R,MekWarrior,3,5 +Unit_Defender_2=Warhammer WHM-6R,MekWarrior,4,4 +Unit_Attacker_1=Marauder MAD-3R,MekWarrior,3,5 +Unit_Attacker_2=Shadow Hawk SHD-2H,MekWarrior,4,4 diff --git a/crates/mms/tests/corpus/TrialOfPosition.mms b/crates/mms/tests/corpus/TrialOfPosition.mms new file mode 100644 index 0000000..795bcd8 --- /dev/null +++ b/crates/mms/tests/corpus/TrialOfPosition.mms @@ -0,0 +1,37 @@ +# MegaMek Data (C) 2025 by The MegaMek Team is licensed under CC BY-NC-SA 4.0. +# To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ +# +# NOTICE: The MegaMek organization is a non-profit group of volunteers +# creating free software for the BattleTech community. +# +# MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks +# of The Topps Company, Inc. All Rights Reserved. +# +# Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of +# InMediaRes Productions, LLC. +# +# MechWarrior Copyright Microsoft Corporation. MegaMek Data was created under +# Microsoft's "Game Content Usage Rules" +# and it is not endorsed by or +# affiliated with Microsoft. + +# A MegaMek Scenario file +MMSVersion=1 +Name=Trial of Position +# A promising young MekWarrior has earned the right to try for Star Commander. However jealousy and politics work against him. +# Sabotage has caused the Battle Roms to record damage on the 2nd and 3rd Meks allowing them into the fray early. +# 1 Kill = Stay in Rank, 2 Kills = Advance in rank, 3 Kills = Advance 2 ranks. +Description=A Jade Falcon MekWarrior take his trial of position. Adjust skill levels as needed. Works good against a bot. +BoardWidth=1 +BoardHeight=2 +RandomDirs=Map Set 2,Map Set 3,Map Set 4,Map Set 5,Map Set 6,Map Set 7 +Maps=RANDOM +Factions=Jade_Falcon,Jade_Falcon_ToP +Location_Jade_Falcon_ToP=N +Location_Jade_Falcon=S +Unit_Jade_Falcon_ToP_1=Black Hawk (Nova) Prime,MekWarrior Simone,4,5 +Unit_Jade_Falcon_ToP_2=Black Hawk (Nova) Prime,MekWarrior Krist,2,4 +Unit_Jade_Falcon_ToP_3=Pouncer Prime,MekWarrior Munroe,3,4 +Unit_Jade_Falcon_1=Ryoken (Stormcrow) Prime,MekWarrior Hawls,1,2 +Unit_Jade_Falcon_ToP_2_Damage=2 +Unit_Jade_Falcon_ToP_3_Damage=2 diff --git a/plan/scenarios.md b/plan/scenarios.md index 0bce951..1ea9d7f 100644 --- a/plan/scenarios.md +++ b/plan/scenarios.md @@ -84,3 +84,24 @@ for this epic, since the filter is a property of the catalog. that names a unit with no art is now a failing test rather than a blank hex, which makes the gap visible; it does not make the catalog generated. + +- [x] **A reader for the format.** `crates/mms` (`headquarters-mms`) parses and + writes `MMSVersion=1` and identifies `MMSVersion: 2`, following + `ScenarioV1.java` from the pinned MegaMek rather than a guess at the + format. `crates/mms/tests/corpus/` is that release's own 48 scenario + files, and the test over them is what says the reader reads the format + and not a subset: every file parses, and every V1 file survives a render + and re-parse unchanged. + + The quirks are recorded rather than smoothed over. MegaMek reads less + than the shipped files say — `Team_X` is looked up as `team_X` and so + never read, the loader spells `CritHit` as `CriticalHit`, and a unit + placed facing `E` or `W` faces north. A generator that set teams the way + the shipped scenarios do would write a file whose teams do nothing, so + each is reported as a `Quirk`. + + Parsing is not launching: a file can be perfect and still name a board + the image lacks, which only MegaMek can tell you. That check lives in + arena — `ScenarioReport` loads a scenario for real and reports each + side's battle value, and `tests/scenario-load.sh` holds the library to a + known-bad list. Eleven of fifty-one do not load today. diff --git a/prek.toml b/prek.toml index 53f9d9f..f6628f7 100644 --- a/prek.toml +++ b/prek.toml @@ -4,6 +4,12 @@ # get it. Naming it here means a plain `prek install` installs both. default_install_hook_types = ["pre-commit", "commit-msg"] +# crates/mms/tests/corpus/ is excluded from the whitespace hooks throughout. +# It is MegaMek's scenario library copied verbatim, and the tests over it are +# a claim about reading *their* files: normalising the line endings or adding +# a final newline would edit the evidence, and one of the things worth knowing +# is that the reader copes with a library written on three decades of +# editors. [[repos]] repo = "https://github.com/pre-commit/pre-commit-hooks" rev = "v6.0.0" @@ -13,9 +19,9 @@ hooks = [ { id = "check-toml" }, { id = "check-yaml" }, { id = "check-json" }, - { id = "mixed-line-ending", args = ["--fix=lf"] }, - { id = "end-of-file-fixer" }, - { id = "trailing-whitespace" }, + { id = "mixed-line-ending", args = ["--fix=lf"], exclude = '^crates/mms/tests/corpus/' }, + { id = "end-of-file-fixer", exclude = '^crates/mms/tests/corpus/' }, + { id = "trailing-whitespace", exclude = '^crates/mms/tests/corpus/' }, ] [[repos]] diff --git a/services/api/Dockerfile b/services/api/Dockerfile index 5fd5478..2ce7ed4 100644 --- a/services/api/Dockerfile +++ b/services/api/Dockerfile @@ -49,6 +49,10 @@ ENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc \ CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc COPY Cargo.toml Cargo.lock ./ COPY services ./services +# Every workspace member, not just the one being built: cargo loads all of +# their manifests before it builds anything, so a missing one fails the build +# rather than being skipped. +COPY crates ./crates # Cache mounts persist the registry and build artifacts across builds, keyed # per target so concurrent amd64 and arm64 builds don't contend for one cargo # lock. The binary is copied out because cache mounts are not part of the