diff --git a/src/cli/main.rs b/src/cli/main.rs index 81eb1c84..0cd418f9 100644 --- a/src/cli/main.rs +++ b/src/cli/main.rs @@ -171,8 +171,9 @@ fn run(opt: &Opt, command: &Command, context: &mut Context) { grid: map.z_level(z), min: (min.x - 1, min.y - 1), max: (max.x - 1, max.y - 1), + render_passes: &render_passes, }; - let image = minimap::generate(minimap_context, &mut context.icon_cache, &render_passes).unwrap(); + let image = minimap::generate(minimap_context, &mut context.icon_cache).unwrap(); let outfile = format!("{}/{}-{}.png", output, path.file_stem().unwrap().to_string_lossy(), 1 + z); println!(" saving {}", outfile); image.to_file(outfile.as_ref()).unwrap(); diff --git a/src/tools/minimap.rs b/src/tools/minimap.rs index bcfed217..0526cb5b 100644 --- a/src/tools/minimap.rs +++ b/src/tools/minimap.rs @@ -24,17 +24,17 @@ pub struct Context<'a> { pub grid: Grid<'a>, pub min: (usize, usize), pub max: (usize, usize), + pub render_passes: &'a [Box], } pub fn generate( ctx: Context, icon_cache: &mut HashMap, - render_passes: &[Box], ) -> Result { use rand::Rng; flame!("minimap"); - let Context { objtree, map, grid, .. } = ctx; + let Context { objtree, map, grid, render_passes, .. } = ctx; // transform min/max from bottom-left-based to top-left-based // probably doesn't belong here @@ -51,7 +51,7 @@ pub fn generate( if y < min_y || y > max_y { continue } for (x, e) in row.iter().enumerate() { if x < ctx.min.0 || x > ctx.max.0 { continue } - for mut atom in get_atom_list(objtree, &map.dictionary[e], (x as u32, y as u32)) { + for mut atom in get_atom_list(objtree, &map.dictionary[e], (x as u32, y as u32), render_passes) { // icons which differ from their map states let p = &atom.type_.path; if p == "/obj/structures/table/wood/fancy/black" { @@ -76,6 +76,10 @@ pub fn generate( } } + for pass in render_passes { + pass.adjust_vars(&mut atom, &objtree); + } + // overlays and underlays macro_rules! add_overlay { ($icon:expr) => {{ @@ -173,6 +177,10 @@ pub fn generate( add_overlay!("overlay_clear"); } + for pass in render_passes { + pass.overlays(&mut atom, objtree, &mut atoms, &mut overlays); + } + // smoothing time handle_smooth(&mut atoms, ctx, atom, !0); atoms.extend(overlays.drain(..)); @@ -267,14 +275,21 @@ pub fn generate( Ok(map_image) } -pub fn get_atom_list<'a>(objtree: &'a ObjectTree, prefabs: &'a [Prefab], loc: (u32, u32)) -> Vec> { +pub fn get_atom_list<'a>( + objtree: &'a ObjectTree, + prefabs: &'a [Prefab], + loc: (u32, u32), + render_passes: &[Box], +) -> Vec> { flame!("get_atom_list"); let mut result = Vec::new(); - for fab in prefabs { - if subtype(&fab.path, "/area/") { continue } - let spawner = subtype(&fab.path, "/obj/effect/spawner/structure/"); - if subtype(&fab.path, "/obj/effect/spawner/") && !spawner { continue } + 'fab: for fab in prefabs { + for pass in render_passes { + if !pass.path_filter(&fab.path) { + continue 'fab; + } + } // look up the type let atom = match Atom::from_prefab(objtree, fab, loc) { @@ -285,43 +300,19 @@ pub fn get_atom_list<'a>(objtree: &'a ObjectTree, prefabs: &'a [Prefab], loc: (u } }; - // invisible objects and syndicate balloons are not to show - if atom.get_var("invisibility", objtree).to_float().unwrap_or(0.) > 60. { - continue; - } - if atom.get_var("icon", objtree).eq_resource("icons/obj/items_and_weapons.dmi") && - atom.get_var("icon_state", objtree).eq_string("syndballoon") - { - continue + for pass in render_passes { + if !pass.early_filter(&atom, objtree) { + continue 'fab; + } } // convert structure spanwers to their structures - if spawner { - match atom.get_var("spawn_list", objtree) { - &Constant::List(ref elements) => { - for &(ref key, _) in elements { - // TODO: use a more civilized lookup method - let mut type_key = String::new(); - let reference; - match key { - &Constant::String(ref s) => reference = s, - &Constant::Prefab(ref fab) => { - for each in fab.path.iter() { - use std::fmt::Write; - let _ = write!(type_key, "{}{}", each.0, each.1); - } - reference = &type_key; - } - _ => continue, - } - result.push(Atom::from_type(objtree, reference, loc).unwrap()); - } - } - _ => {} // TODO: complain? + for pass in render_passes { + if pass.expand(&atom, objtree, &mut result) { + continue 'fab; } - } else { - result.push(atom); } + result.push(atom); } result @@ -337,11 +328,11 @@ pub struct Atom<'a> { type_: &'a Type, prefab: Option<&'a Vars>, vars: Vars, - loc: (u32, u32), + pub loc: (u32, u32), } impl<'a> Atom<'a> { - fn from_prefab(objtree: &'a ObjectTree, fab: &'a Prefab, loc: (u32, u32)) -> Option { + pub fn from_prefab(objtree: &'a ObjectTree, fab: &'a Prefab, loc: (u32, u32)) -> Option { objtree.find(&fab.path).map(|type_| Atom { type_, prefab: Some(&fab.vars), @@ -350,7 +341,7 @@ impl<'a> Atom<'a> { }) } - fn from_type(objtree: &'a ObjectTree, path: &str, loc: (u32, u32)) -> Option { + pub fn from_type(objtree: &'a ObjectTree, path: &str, loc: (u32, u32)) -> Option { objtree.find(path).map(|type_| Atom { type_, prefab: None, @@ -518,7 +509,7 @@ fn find_type_in_direction<'a>(ctx: Context, source: &Atom, direction: i32, flags // TODO: make this not call get_atom_list way too many times let atom_list = get_atom_list(ctx.objtree, &ctx.map.dictionary[&ctx.grid[ndarray::Dim([new_loc.1 as usize, new_loc.0 as usize])]], - new_loc); + new_loc, ctx.render_passes); match source.get_var("canSmoothWith", ctx.objtree) { &Constant::List(ref elements) => if flags & SMOOTH_MORE != 0 { // smooth with canSmoothWith + subtypes @@ -630,7 +621,7 @@ fn diagonal_smooth<'a>(output: &mut Vec>, ctx: Context<'a>, source: &At // TODO: make this not call get_atom_list way too many times let atom_list = get_atom_list(ctx.objtree, &ctx.map.dictionary[&ctx.grid[ndarray::Dim([new_loc.1 as usize, new_loc.0 as usize])]], - new_loc); + new_loc, ctx.render_passes); for mut atom in atom_list { if subtype(&atom.type_.path, "/turf/open/") { atom.loc = source.loc; diff --git a/src/tools/render_passes/mod.rs b/src/tools/render_passes/mod.rs index 46143145..b37be367 100644 --- a/src/tools/render_passes/mod.rs +++ b/src/tools/render_passes/mod.rs @@ -1,4 +1,5 @@ use dm::objtree::*; +use dm::constants::Constant; use minimap::Atom; /// A map rendering pass. @@ -7,6 +8,11 @@ use minimap::Atom; /// appear here. #[allow(unused_variables)] pub trait RenderPass { + /// Filter atoms based solely on their typepath. + fn path_filter(&self, + path: &str, + ) -> bool { true } + /// Filter atoms at the beginning of the process. /// /// Return `false` to discard the atom. @@ -18,24 +24,24 @@ pub trait RenderPass { /// Expand atoms, such as spawners into the atoms they spawn. /// /// Return `true` to consume the original atom. - fn expand(&self, - atom: &Atom, - objtree: &ObjectTree, - output: &mut Vec, + fn expand<'a>(&self, + atom: &Atom<'a>, + objtree: &'a ObjectTree, + output: &mut Vec>, ) -> bool { false } /// Adjust the variables of an atom. - fn adjust_vars(&self, - atom: &mut Atom, - objtree: &ObjectTree, + fn adjust_vars<'a>(&self, + atom: &mut Atom<'a>, + objtree: &'a ObjectTree, ) {} /// Apply overlays and underlays to an atom, in the form of pseudo-atoms. - fn overlays(&self, - atom: &mut Atom, - objtree: &ObjectTree, - underlays: &mut Vec, - overlays: &mut Vec, + fn overlays<'a>(&self, + atom: &mut Atom<'a>, + objtree: &'a ObjectTree, + underlays: &mut Vec>, + overlays: &mut Vec>, ) {} /// Filter atoms at the end of the process. @@ -66,6 +72,9 @@ macro_rules! pass { pub const RENDER_PASSES: &[RenderPassInfo] = &[ pass!(HideSpace, "hide-space", "Do not render space tiles, instead leaving transparency.", true), + pass!(HideAreas, "hide-areas", "Do not render area icons.", true), + pass!(HideInvisible, "hide-invisible", "Do not render invisible or ephemeral objects such as mapping helpers.", true), + pass!(Spawners, "spawners", "Replace object spawners with their spawned objects.", true), ]; pub fn configure(include: &str, exclude: &str) -> Vec> { @@ -101,3 +110,70 @@ impl RenderPass for HideSpace { !atom.istype("/turf/open/space/") } } + +#[derive(Default)] +pub struct HideAreas; +impl RenderPass for HideAreas { + fn path_filter(&self, path: &str) -> bool { + !subpath(path, "/area/") + } +} + +#[derive(Default)] +pub struct HideInvisible; +impl RenderPass for HideInvisible { + fn early_filter(&self, atom: &Atom, objtree: &ObjectTree) -> bool { + // invisible objects and syndicate balloons are not to show + if atom.get_var("invisibility", objtree).to_float().unwrap_or(0.) > 60. { + return false; + } + if atom.get_var("icon", objtree).eq_resource("icons/obj/items_and_weapons.dmi") && + atom.get_var("icon_state", objtree).eq_string("syndballoon") && + !atom.istype("/obj/item/toy/syndicateballoon/") + { + return false; + } + true + } +} + +#[derive(Default)] +pub struct Spawners; +impl RenderPass for Spawners { + fn path_filter(&self, path: &str) -> bool { + subpath(path, "/obj/effect/spawner/structure/") || !subpath(path, "/obj/effect/spawner/") + } + + fn expand<'a>(&self, + atom: &Atom<'a>, + objtree: &'a ObjectTree, + output: &mut Vec>, + ) -> bool { + if !atom.istype("/obj/effect/spawner/structure/") { + return false; + } + match atom.get_var("spawn_list", objtree) { + &Constant::List(ref elements) => { + for &(ref key, _) in elements { + // TODO: use a more civilized lookup method + let mut type_key = String::new(); + let reference; + match key { + &Constant::String(ref s) => reference = s, + &Constant::Prefab(ref fab) => { + for each in fab.path.iter() { + use std::fmt::Write; + let _ = write!(type_key, "{}{}", each.0, each.1); + } + reference = &type_key; + } + _ => continue, + } + output.push(Atom::from_type(objtree, reference, atom.loc).unwrap()); + } + true // don't include the original atom + } + _ => { false } // TODO: complain? + } + } +}