mindustry logic execution, map- and schematic- parsing and rendering
Implement schematic resizing
| -rw-r--r-- | src/data/schematic.rs | 104 | ||||
| -rw-r--r-- | src/exe/edit.rs | 113 |
2 files changed, 215 insertions, 2 deletions
diff --git a/src/data/schematic.rs b/src/data/schematic.rs index a073eff..c0ae97d 100644 --- a/src/data/schematic.rs +++ b/src/data/schematic.rs @@ -429,6 +429,56 @@ impl<'l> Schematic<'l> } } + pub fn resize(&mut self, dx: i16, dy: i16, w: u16, h: u16) -> Result<(), ResizeError> + { + if w > MAX_DIMENSION + { + return Err(ResizeError::TargetWidth(w)); + } + if h > MAX_DIMENSION + { + return Err(ResizeError::TargetHeight(h)); + } + if dx <= -(w as i16) || dx >= self.width as i16 + { + return Err(ResizeError::XOffset{dx, old_w: self.width, new_w: w}); + } + if dy <= -(h as i16) || dy >= self.height as i16 + { + return Err(ResizeError::YOffset{dy, old_h: self.height, new_h: h}); + } + // check that all blocks fit into the new bounds + let mut right = 0u16; + let mut top = 0u16; + let mut left = 0u16; + let mut bottom = 0u16; + let right_bound = dx + w as i16 - 1; + let top_bound = dy + h as i16 - 1; + let left_bound = dx; + let bottom_bound = dy; + for Placement{pos, block, ..} in self.blocks.iter() + { + let sz = block.get_size() as u16; + let (x0, y0, x1, y1) = (pos.0 - (sz - 1) / 2, pos.1 - (sz - 1) / 2, pos.0 + sz / 2, pos.1 + sz / 2); + if (x1 as i16) > right_bound && x1 - right_bound as u16 > right {right = x1 - right_bound as u16;} + if (y1 as i16) > top_bound && y1 - top_bound as u16 > top {top = y1 - top_bound as u16;} + if (x0 as i16) < left_bound && left_bound as u16 - x0 > left {left = left_bound as u16 - x0;} + if (y0 as i16) < bottom_bound && bottom_bound as u16 - y0 > bottom {bottom = bottom_bound as u16 - y0;} + } + if left > 0 || top > 0 || left > 0 || bottom > 0 + { + return Err(ResizeError::Truncated{right, top, left, bottom}); + } + self.width = w; + self.height = h; + for Placement{pos, ..} in self.blocks.iter_mut() + { + pos.0 = (pos.0 as i16 + dx) as u16; + pos.1 = (pos.1 as i16 + dy) as u16; + } + Ok(()) + } + pub fn rotate_180(&mut self) { self.mirror(true, true); @@ -541,6 +591,60 @@ impl Error for PlaceError } } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ResizeError +{ + TargetWidth(u16), + TargetHeight(u16), + XOffset{dx: i16, old_w: u16, new_w: u16}, + YOffset{dy: i16, old_h: u16, new_h: u16}, + Truncated{right: u16, top: u16, left: u16, bottom: u16}, +} + +impl fmt::Display for ResizeError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + match self + { + Self::TargetWidth(w) => write!(f, "invalid target width ({w})"), + Self::TargetHeight(w) => write!(f, "invalid target height ({w})"), + Self::XOffset{dx, old_w, new_w} => write!(f, "horizontal offset {dx} not in [-{new_w}, {old_w}]"), + Self::YOffset{dy, old_h, new_h} => write!(f, "vertical offset {dy} not in [-{new_h}, {old_h}]"), + Self::Truncated{right, top, left, bottom} => + { + macro_rules!fmt_dir + { + ($f:ident, $first:ident, $name:expr, $value:expr) => + { + if $value != 0 + { + if $first + { + f.write_str(" (")?; + $first = false; + } + else {f.write_str(", ")?;} + write!(f, "{}: {}", $name, $value)?; + } + }; + } + + f.write_str("truncated blocks")?; + let mut first = true; + fmt_dir!(f, first, "right", *right); + fmt_dir!(f, first, "top", *top); + fmt_dir!(f, first, "left", *left); + fmt_dir!(f, first, "bottom", *bottom); + if !first {f.write_char(')')?;} + Ok(()) + }, + } + } +} + +impl Error for ResizeError {} + impl<'l> fmt::Display for Schematic<'l> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result diff --git a/src/exe/edit.rs b/src/exe/edit.rs index 051394b..d24108f 100644 --- a/src/exe/edit.rs +++ b/src/exe/edit.rs @@ -6,7 +6,7 @@ use std::fs; use crate::block::{BlockRegistry, build_registry, Rotation}; use crate::data::dynamic::DynData; use crate::data::{DataRead, Serializer, DataWrite, base64}; -use crate::data::schematic::{Schematic, SchematicSerializer}; +use crate::data::schematic::{ResizeError, Schematic, SchematicSerializer}; use crate::exe::print::print_schematic; use crate::exe::print_err; use crate::exe::args::{self, ArgCount, ArgOption, OptionHandler}; @@ -227,7 +227,7 @@ impl<'l> Tokenizer<'l> enum Command { - Help, New, Input, Load, Place, Rotate, Mirror, Remove, Print, Dump, Save, Quit + Help, New, Input, Load, Place, Rotate, Mirror, Move, Resize, Remove, Print, Dump, Save, Quit } impl Command @@ -243,6 +243,8 @@ impl Command Self::Place => println!("{:<indent$}Places a block if enough space is available", "\"place\":"), Self::Rotate => println!("{:<indent$}Rotates the schematic (CCW) in increments of 90 degrees", "\"rotate\":"), Self::Mirror => println!("{:<indent$}Mirrors the schematic horizontally or vertically", "\"mirror\":"), + Self::Move => println!("{:<indent$}Moves all blocks by a certain offset", "\"move\":"), + Self::Resize => println!("{:<indent$}Resizes the schematic and offsets it", "\"resize\":"), Self::Remove => println!("{:<indent$}Removes blocks at a position or within a region", "\"remove\":"), Self::Print => println!("{:<indent$}Prints the schematic in a visual representation", "\"print\":"), Self::Dump => println!("{:<indent$}Prints the schematic as a base-64 encoded string", "\"dump\":"), @@ -267,6 +269,8 @@ impl Command }, Self::Rotate => println!(r#"{:indent$} Usage: "rotate" <angle>"#, ""), Self::Mirror => println!(r#"{:indent$} Usage: "mirror" <axis>"#, ""), + Self::Move => println!(r#"{:indent$} Usage: "move" <dx> <dy>"#, ""), + Self::Resize => println!(r#"{:indent$} Usage: "resize" <width> <height> [<dx> <dy>]"#, ""), Self::Remove => println!(r#"{:indent$} Usage: "remove" <x0> <y0> [<x1> <y1>]"#, ""), Self::Print | Self::Dump => (), Self::Save => println!(r#"{:indent$} Usage: "save" <save path>"#, ""), @@ -598,6 +602,111 @@ fn interpret(state: &mut State, cmd: &str) schematic.mirror(x, y); state.unsaved = true; }, + Some("move") => + { + let Some(ref mut schematic) = state.schematic + else + { + eprintln!(r#"Command "move" requires an active schematic (see "help")"#); + return; + }; + let dx = parse_num!(Move, tokens, "dx", i16); + let dy = parse_num!(Move, tokens, "dy", i16); + if tokens.remainder().is_some() + { + eprintln!(r#"Too many parameters for "move""#); + Command::Move.print_usage(0); + return; + } + if dx != 0 && dy != 0 + { + if let Err(e) = schematic.resize(dx, dy, schematic.get_width(), schematic.get_height()) + { + match e + { + ResizeError::XOffset{dx, old_w, new_w} => + { + debug_assert_eq!(old_w, new_w); + eprintln!("Invalid horizontal move {dx} not in ]-{old_w}, {new_w}["); + }, + ResizeError::YOffset{dy, old_h, new_h} => + { + debug_assert_eq!(old_h, new_h); + eprintln!("Invalid vertical move {dy} not in ]-{old_h}, {new_h}["); + }, + ResizeError::Truncated{right, top, left, bottom} => + { + eprint!("Move would truncate schematic: "); + let mut first = true; + if right > 0 + { + eprint!("{}right: {right}", if first {""} else {", "}); + first = false; + } + if top > 0 + { + eprint!("{}top: {top}", if first {""} else {", "}); + first = false; + } + if left > 0 + { + eprint!("{}left: {left}", if first {""} else {", "}); + first = false; + } + if bottom > 0 + { + eprint!("{}bottom: {bottom}", if first {""} else {", "}); + first = false; + } + if first {eprintln!("<unknown>");} else {eprintln!();} + }, + _ => print_err!(e, "Unexpected resize error (for {dx} / {dy})") + } + } + else {state.unsaved = true;} + } + }, + Some("resize") => + { + let Some(ref mut schematic) = state.schematic + else + { + eprintln!(r#"Command "resize" requires an active schematic (see "help")"#); + return; + }; + let w = parse_num!(Resize, tokens, "width", u16); + if w == 0 + { + eprintln!("Schematic width must be positive"); + return; + } + let h = parse_num!(Resize, tokens, "height", u16); + if h == 0 + { + eprintln!("Schematic height must be positive"); + return; + } + let (dx, dy) = if let arg @ Some(..) = tokens.next() + { + let dx = parse_num!(Resize, "dx", <i16>::from(arg)); + let dy = parse_num!(Resize, tokens, "dy", i16); + (dx, dy) + } + else {(0, 0)}; + if tokens.remainder().is_some() + { + eprintln!(r#"Too many parameters for "resize""#); + Command::Resize.print_usage(0); + return; + } + if w != schematic.get_width() || h != schematic.get_height() || dx != 0 || dy != 0 + { + if let Err(e) = schematic.resize(dx, dy, w, h) + { + print_err!(e, "Could not resize schematic"); + } + } + }, Some("remove") => { let Some(ref mut schematic) = state.schematic |