//! Which mission should the next unit harvest fly? //! //! Unit definitions are instantiated per stage, so coverage grows by visiting //! missions — but only if the mission fields units we have not read yet. This //! ranks every stage by how many of its roster units are missing from the //! harvested CSV. //! //! Usage: roster_target use sylpheed_formats::hash::name_hash; use sylpheed_formats::idxd::IdxdObject; use sylpheed_formats::pak::PakArchive; use std::collections::{BTreeMap, BTreeSet}; fn main() { let a: Vec = std::env::args().collect(); let pak = PakArchive::open(format!("{}/dat/GP_MAIN_GAME_E.pak", a[1])).expect("pak"); let have: BTreeSet = std::fs::read_to_string(&a[2]) .expect("csv") .lines() .skip(1) .filter_map(|l| l.split(',').next().map(str::to_string)) .collect(); // Label each roster by STAGE: the TOC stores a hash of the original path, and // the table names are known (`EnumUnit_SNN.tbl`), so hashing the candidates // maps entries back to stages. Most rosters carry no `UN_S_` prop id, which // is all `UnitRoster::stage` can infer from. let mut by_hash: BTreeMap = BTreeMap::new(); // The TOC hashes a PATH, and these tables live under a directory — try the // known schemes (see hash::TOC_NAME_SCHEMES) rather than the bare name. for i in 1..=29 { let stage = format!("S{i:02}"); for pre in ["", "unit\\", "battle\\", "stage\\", "enemy\\"] { for suf in [".tbl", ""] { by_hash.insert(name_hash(&format!("{pre}EnumUnit_{stage}{suf}")), stage.clone()); } } } let mut rows: Vec<(usize, String, Vec)> = Vec::new(); for e in pak.entries() { let Some(stage) = by_hash.get(&e.name_hash).cloned() else { continue }; let Ok(b) = pak.read(e) else { continue }; let Ok(o) = IdxdObject::parse(&b) else { continue }; let mut units: Vec = Vec::new(); for id in o.tokens().iter().filter(|s| s.starts_with("UN_")) { let prop = id.contains("Asteroid") || id.contains("cmesh") || id.contains("_Box"); if !prop && !units.iter().any(|u| u == id) { units.push(id.clone()); } } let missing: Vec = units.iter().filter(|u| !have.contains(*u)).cloned().collect(); rows.push((missing.len(), stage, missing)); } rows.sort_by_key(|(n, _, _)| std::cmp::Reverse(*n)); println!("units already harvested: {}\n", have.len()); println!("stage missing roster units not yet read"); for (n, stage, missing) in rows.iter().take(12) { let s: Vec<&str> = missing.iter().take(6).map(|s| s.as_str()).collect(); println!("{stage:<8} {n:>5} {}", s.join(", ")); } }