mindustry logic execution, map- and schematic- parsing and rendering
Implement sub-region editing
| -rw-r--r-- | src/exe/edit.rs | 687 |
1 files changed, 651 insertions, 36 deletions
diff --git a/src/exe/edit.rs b/src/exe/edit.rs index d24108f..bc1d7dd 100644 --- a/src/exe/edit.rs +++ b/src/exe/edit.rs @@ -5,8 +5,8 @@ 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::{ResizeError, Schematic, SchematicSerializer}; +use crate::data::{base64, DataRead, Serializer, DataWrite, GridPos}; +use crate::data::schematic::{Placement, ResizeError, Schematic, SchematicSerializer}; use crate::exe::print::print_schematic; use crate::exe::print_err; use crate::exe::args::{self, ArgCount, ArgOption, OptionHandler}; @@ -17,6 +17,7 @@ struct State<'l> reg: &'l BlockRegistry<'l>, schematic: Option<Schematic<'l>>, unsaved: bool, + subregion: Option<Schematic<'l>>, quit: bool, } @@ -33,7 +34,7 @@ pub fn main(mut args: Args, arg_off: usize) // try to load a schematic from the file argument or as base64 let reg = build_registry(); let mut ss = SchematicSerializer(®); - let mut state = State{reg: ®, schematic: None, unsaved: false, quit: false}; + let mut state = State{reg: ®, schematic: None, unsaved: false, subregion: None, quit: false}; if let Some(path) = handler.get_value(opt_file).get_value() { match fs::read(path) @@ -227,7 +228,7 @@ impl<'l> Tokenizer<'l> enum Command { - Help, New, Input, Load, Place, Rotate, Mirror, Move, Resize, Remove, Print, Dump, Save, Quit + Help, New, Input, Load, Place, Rotate, Mirror, Move, Resize, Remove, Sub, Print, Dump, Save, Quit } impl Command @@ -246,12 +247,13 @@ impl Command 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::Sub => println!("{:<indent$}Various commands for editing subregions", "\"sub\":"), 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\":"), Self::Save => println!("{:<indent$}Saves the schematic to a file", "\"save\":"), Self::Quit => println!("{:<indent$}Offers to save unsaved work and exits the program", "\"quit\":"), } - self.print_usage(12); + self.print_usage(indent); } fn print_usage(&self, indent: usize) @@ -272,6 +274,7 @@ impl Command 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::Sub => println!(r#"{:indent$} Usage: "sub" ... (see "sub help")"#, ""), Self::Print | Self::Dump => (), Self::Save => println!(r#"{:indent$} Usage: "save" <save path>"#, ""), Self::Quit => (), @@ -281,14 +284,14 @@ impl Command macro_rules!parse_num { - ($cmd:ident, $name:expr, <$type:ty>::from($val:expr)) => + ($cmd:path, $name:expr, <$type:ty>::from($val:expr)) => { match $val { None => { eprintln!("Missing argument: {}", $name); - Command::$cmd.print_usage(0); + $cmd.print_usage(0); return; }, Some(s) => @@ -299,14 +302,14 @@ macro_rules!parse_num Err(e) => { print_err!(e, "Could not parse {}", $name); - Command::$cmd.print_usage(0); + $cmd.print_usage(0); return; }, } }, } }; - ($cmd:ident, $tokens:expr, $name:expr, $type:ty) => + ($cmd:path, $tokens:expr, $name:expr, $type:ty) => { parse_num!($cmd, $name, <$type>::from($tokens.next())) } @@ -321,18 +324,20 @@ fn interpret(state: &mut State, cmd: &str) Some("help") => { println!(r#"List of available commands:"#); - Command::Help.print_help(12); - Command::New.print_help(12); - Command::Input.print_help(12); - Command::Load.print_help(12); - Command::Place.print_help(12); - Command::Rotate.print_help(12); - Command::Mirror.print_help(12); - Command::Remove.print_help(12); - Command::Print.print_help(12); - Command::Dump.print_help(12); - Command::Save.print_help(12); - Command::Quit.print_help(12); + const INDENT: usize = 12; + Command::Help.print_help(INDENT); + Command::New.print_help(INDENT); + Command::Input.print_help(INDENT); + Command::Load.print_help(INDENT); + Command::Place.print_help(INDENT); + Command::Rotate.print_help(INDENT); + Command::Mirror.print_help(INDENT); + Command::Remove.print_help(INDENT); + Command::Sub.print_help(INDENT); + Command::Print.print_help(INDENT); + Command::Dump.print_help(INDENT); + Command::Save.print_help(INDENT); + Command::Quit.print_help(INDENT); println!(); println!("Legend: \"literal (excluding quotes)\" <required> [<optional>]"); println!("Arguments are delimited by whitespace, unless surrounded with ' or \""); @@ -347,13 +352,13 @@ fn interpret(state: &mut State, cmd: &str) }, Some("new") => { - let width = parse_num!(New, tokens, "width", u16); + let width = parse_num!(Command::New, tokens, "width", u16); if width == 0 { eprintln!("Schematic width must be positive"); return; } - let height = parse_num!(New, tokens, "height", u16); + let height = parse_num!(Command::New, tokens, "height", u16); if height == 0 { eprintln!("Schematic height must be positive"); @@ -450,8 +455,8 @@ fn interpret(state: &mut State, cmd: &str) eprintln!(r#"Command "place" requires an active schematic (see "help")"#); return; }; - let x = parse_num!(Place, tokens, "x", u16); - let y = parse_num!(Place, tokens, "y", u16); + let x = parse_num!(Command::Place, tokens, "x", u16); + let y = parse_num!(Command::Place, tokens, "y", u16); if x >= schematic.get_width() || y >= schematic.get_height() { eprintln!("Invalid coordinate ({x} / {y}) out of bounds ({} / {})", schematic.get_width(), schematic.get_height()); @@ -535,7 +540,7 @@ fn interpret(state: &mut State, cmd: &str) eprintln!(r#"Command "rotate" requires an active schematic (see "help")"#); return; }; - let angle = parse_num!(Rotate, tokens, "angle", i32); + let angle = parse_num!(Command::Rotate, tokens, "angle", i32); if angle % 90 != 0 { eprintln!("Rotation angle must be a multiple of 90 degrees"); @@ -610,8 +615,8 @@ fn interpret(state: &mut State, cmd: &str) 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); + let dx = parse_num!(Command::Move, tokens, "dx", i16); + let dy = parse_num!(Command::Move, tokens, "dy", i16); if tokens.remainder().is_some() { eprintln!(r#"Too many parameters for "move""#); @@ -674,13 +679,13 @@ fn interpret(state: &mut State, cmd: &str) eprintln!(r#"Command "resize" requires an active schematic (see "help")"#); return; }; - let w = parse_num!(Resize, tokens, "width", u16); + let w = parse_num!(Command::Resize, tokens, "width", u16); if w == 0 { eprintln!("Schematic width must be positive"); return; } - let h = parse_num!(Resize, tokens, "height", u16); + let h = parse_num!(Command::Resize, tokens, "height", u16); if h == 0 { eprintln!("Schematic height must be positive"); @@ -688,8 +693,8 @@ fn interpret(state: &mut State, cmd: &str) } 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); + let dx = parse_num!(Command::Resize, "dx", <i16>::from(arg)); + let dy = parse_num!(Command::Resize, tokens, "dy", i16); (dx, dy) } else {(0, 0)}; @@ -715,8 +720,8 @@ fn interpret(state: &mut State, cmd: &str) eprintln!(r#"Command "remove" requires an active schematic (see "help")"#); return; }; - let x0 = parse_num!(Remove, tokens, "x0", u16); - let y0 = parse_num!(Remove, tokens, "y0", u16); + let x0 = parse_num!(Command::Remove, tokens, "x0", u16); + let y0 = parse_num!(Command::Remove, tokens, "y0", u16); if x0 >= schematic.get_width() || y0 >= schematic.get_height() { eprintln!("Invalid coordinate ({x0} / {y0}) out of bounds ({} / {})", schematic.get_width(), schematic.get_height()); @@ -724,8 +729,8 @@ fn interpret(state: &mut State, cmd: &str) } let (x0, y0, x1, y1) = if let arg @ Some(..) = tokens.next() { - let x1 = parse_num!(Remove, "x1", <u16>::from(arg)); - let y1 = parse_num!(Remove, tokens, "y1", u16); + let x1 = parse_num!(Command::Remove, "x1", <u16>::from(arg)); + let y1 = parse_num!(Command::Remove, tokens, "y1", u16); if x1 >= schematic.get_width() || y1 >= schematic.get_height() { eprintln!("Invalid coordinate ({x1} / {y1}) out of bounds ({} / {})", schematic.get_width(), schematic.get_height()); @@ -768,6 +773,7 @@ fn interpret(state: &mut State, cmd: &str) } } }, + Some("sub") => interpret_sub(state, &mut tokens), Some("print") => { let Some(ref schematic) = state.schematic @@ -848,3 +854,612 @@ fn interpret(state: &mut State, cmd: &str) Some(unknown) => eprintln!("Unknown command {unknown:?}"), } } + +enum SubCommand +{ + Help, Input, Copy, Cut, Paste, Place, Rotate, Mirror, Move, Resize, Remove, Print, Dump +} + +impl SubCommand +{ + fn print_help(&self, indent: usize) + { + match self + { + Self::Help => println!("{:<indent$}Prints a list of available commands", "\"sub\" \"help\":"), + Self::Input => println!("{:<indent$}Loads a new subregion from a base-64 encoded string", "\"sub\" \"input\":"), + Self::Copy => println!("{:<indent$}Replaces the current subregion by copying from the schematic", "\"sub\" \"copy\":"), + Self::Cut => println!("{:<indent$}Replaces the current subregion by removing from the schematic", "\"sub\" \"cut\":"), + Self::Paste => println!("{:<indent$}Places a copy of the current subregion into the schematic", "\"sub\" \"paste\":"), + Self::Place => println!("{:<indent$}Places a block if enough space is available", "\"sub\" \"place\":"), + Self::Rotate => println!("{:<indent$}Rotates the current subregion (CCW) in increments of 90 degrees", "\"sub\" \"rotate\":"), + Self::Mirror => println!("{:<indent$}Mirrors the current subregion horizontally or vertically", "\"sub\" \"mirror\":"), + Self::Move => println!("{:<indent$}Moves all blocks by a certain offset", "\"sub\" \"move\":"), + Self::Resize => println!("{:<indent$}Resizes the current subregion and offsets it", "\"sub\" \"resize\":"), + Self::Remove => println!("{:<indent$}Removes blocks at a position or within a region", "\"sub\" \"remove\":"), + Self::Print => println!("{:<indent$}Prints the current subregion in a visual representation", "\"sub\" \"print\":"), + Self::Dump => println!("{:<indent$}Prints the current subregion as a base-64 encoded string", "\"sub\" \"dump\":"), + } + self.print_usage(indent); + } + + fn print_usage(&self, indent: usize) + { + match self + { + Self::Help => (), + Self::Input => println!(r#"{:indent$} Usage: "sub" "input" <base64>"#, ""), + Self::Copy => + { + println!(r#"{:indent$} Usage: "sub" "copy" <x0> <y0> <x1> <y1> [<strict>]"#, ""); + println!(r#"{:indent$} Strictness ignores blocks which are not entirely within the bounds"#, ""); + }, + Self::Cut => + { + println!(r#"{:indent$} Usage: "sub" "cut" <x0> <y0> <x1> <y1> [<strict>]"#, ""); + println!(r#"{:indent$} Strictness ignores blocks which are not entirely within the bounds"#, ""); + }, + Self::Paste => println!(r#"{:indent$} Usage: "sub" "paste" <x> <y>"#, ""), + Self::Place => + { + println!(r#"{:indent$} Usage: "sub" "place" <x> <y> <block name> [<rotation> [<replace>]]"#, ""); + println!(r#"{:indent$} Rotation is one of right, up, left, down or compass angles"#, "") + }, + Self::Rotate => println!(r#"{:indent$} Usage: "sub" "rotate" <angle>"#, ""), + Self::Mirror => println!(r#"{:indent$} Usage: "sub" "mirror" <axis>"#, ""), + Self::Move => println!(r#"{:indent$} Usage: "sub" "move" <dx> <dy>"#, ""), + Self::Resize => println!(r#"{:indent$} Usage: "sub" "resize" <width> <height> [<dx> <dy>]"#, ""), + Self::Remove => println!(r#"{:indent$} Usage: "sub" "remove" <x0> <y0> [<x1> <y1>]"#, ""), + Self::Print | Self::Dump => (), + } + } +} + +fn interpret_sub(state: &mut State, tokens: &mut Tokenizer) +{ + match tokens.next() + { + None => println!(r#"Empty "sub" command, type "sub help" for more info"#), + Some("help") => + { + const INDENT: usize = 20; + println!(r#"List of available commands for \"sub\":"#); + SubCommand::Help.print_help(INDENT); + SubCommand::Input.print_help(INDENT); + SubCommand::Copy.print_help(INDENT); + SubCommand::Cut.print_help(INDENT); + SubCommand::Paste.print_help(INDENT); + SubCommand::Place.print_help(INDENT); + SubCommand::Rotate.print_help(INDENT); + SubCommand::Mirror.print_help(INDENT); + SubCommand::Move.print_help(INDENT); + SubCommand::Resize.print_help(INDENT); + SubCommand::Remove.print_help(INDENT); + SubCommand::Print.print_help(INDENT); + SubCommand::Dump.print_help(INDENT); + if tokens.remainder().is_some() + { + eprintln!("Extra arguments are considered an error"); + } + }, + Some("input") => + { + let subregion = match tokens.next() + { + None => + { + eprintln!("Missing argument: base64"); + SubCommand::Input.print_usage(0); + return; + }, + Some(b64) => + { + match SchematicSerializer(state.reg).deserialize_base64(b64) + { + Ok(s) => s, + Err(e) => + { + print_err!(e, "Could not deserialize schematic"); + return; + }, + } + }, + }; + if tokens.remainder().is_some() + { + eprintln!(r#"Too many parameters for "sub input""#); + SubCommand::Input.print_usage(0); + return; + } + state.subregion = Some(subregion); + }, + Some(op @ ("copy" | "cut")) => + { + let modify_original = op == "cut"; + let Some(ref mut schematic) = state.schematic + else + { + eprintln!(r#"Command "sub {op}" requires an active schematic (see "help")"#); + return; + }; + let x0 = if modify_original {parse_num!(SubCommand::Cut, tokens, "x0", u16)} else {parse_num!(SubCommand::Copy, tokens, "x0", u16)}; + let y0 = if modify_original {parse_num!(SubCommand::Cut, tokens, "y0", u16)} else {parse_num!(SubCommand::Copy, tokens, "y0", u16)}; + if x0 >= schematic.get_width() || y0 >= schematic.get_height() + { + eprintln!("Invalid lower coordinate ({x0} / {y0}) out of bounds ({} / {})", schematic.get_width(), schematic.get_height()); + return; + } + let x1 = if modify_original {parse_num!(SubCommand::Cut, tokens, "x1", u16)} else {parse_num!(SubCommand::Copy, tokens, "x1", u16)}; + let y1 = if modify_original {parse_num!(SubCommand::Cut, tokens, "y1", u16)} else {parse_num!(SubCommand::Copy, tokens, "y1", u16)}; + if x1 < x0 || y1 < y0 + { + eprintln!("Invalid upper coordinate ({x1} / {y1}) too low (lower bound {x0} / {y0})"); + return; + } + if x1 >= schematic.get_width() || y1 >= schematic.get_height() + { + eprintln!("Invalid upper coordinate ({x1} / {y1}) out of bounds ({} / {})", schematic.get_width(), schematic.get_height()); + return; + } + let strict = match tokens.next() + { + None => false, + Some("true") | Some("yes") => true, + Some("false") | Some("no") => false, + Some(strict) => + { + eprintln!("Invalid strictness {strict:?}"); + return; + }, + }; + if tokens.remainder().is_some() + { + eprintln!(r#"Too many parameters for "sub {op}""#); + SubCommand::Copy.print_usage(0); + return; + } + let mut true_x0 = x0; + let mut true_y0 = y0; + let mut true_x1 = x1; + let mut true_y1 = y1; + let mut targets = Vec::new(); + for p in schematic.block_iter() + { + let sz = p.get_block().get_size() as u16; + let pos0 = (p.get_pos().0 - (sz - 1) / 2, p.get_pos().1 - (sz - 1) / 2); + let pos1 = (p.get_pos().0 + sz / 2, p.get_pos().1 + sz / 2); + let include = if strict {pos0.0 >= x0 && pos0.1 >= y0 && pos1.0 <= x1 && pos1.1 <= y1} + else {pos0.0 <= x1 && pos0.1 <= y1 && pos1.0 >= x0 && pos1.1 >= y0}; + if include + { + targets.push(p.get_pos()); + if !strict + { + if pos0.0 < true_x0 {true_x0 = pos0.0;} + if pos0.1 < true_y0 {true_y0 = pos0.1;} + if pos1.0 > true_x1 {true_x1 = pos1.0;} + if pos1.1 > true_y1 {true_y1 = pos1.1;} + } + } + } + let mut subregion = Schematic::new(true_x1 - true_x0 + 1, true_y1 - true_y0 + 1); + if !targets.is_empty() + { + if modify_original {state.unsaved = true;} + for GridPos(x, y) in targets + { + let mut original: Option<Placement> = None; + let place = if modify_original + { + original = schematic.take(x, y).unwrap(); + original.as_ref().unwrap() + } + else {schematic.get(x, y).unwrap().unwrap()}; + let data = match place.get_state() + { + None => DynData::Empty, + Some(d) => place.get_block().serialize_state(d).unwrap(), + }; + subregion.set(place.get_pos().0 - true_x0, place.get_pos().1 - true_y0, place.get_block(), data, place.get_rotation()).unwrap(); + drop(original); + } + } + state.subregion = Some(subregion); + }, + Some("paste") => + { + let Some(ref mut schematic) = state.schematic + else + { + eprintln!(r#"Command "sub paste" requires an active schematic (see "help")"#); + return; + }; + let Some(ref subregion) = state.subregion + else + { + eprintln!(r#"Command "sub paste" requires an active subregion (see "sub help")"#); + return; + }; + let x = parse_num!(SubCommand::Paste, tokens, "x", u16); + let y = parse_num!(SubCommand::Paste, tokens, "y", u16); + if subregion.get_width() > schematic.get_width() || subregion.get_height() > schematic.get_height() + { + eprintln!("Subregion ({} / {}) is larger than schematic ({} / {})", subregion.get_width(), subregion.get_height(), + schematic.get_width(), schematic.get_height()); + return; + } + if x >= schematic.get_width() - subregion.get_width() + { + let x1 = x + subregion.get_width() - 1; + let y1 = y + subregion.get_height() - 1; + eprintln!("Invalid coordinate ({x} / {y} to {x1} / {y1}) out of bounds ({} / {})", schematic.get_width(), schematic.get_height()); + return; + } + if tokens.remainder().is_some() + { + eprintln!(r#"Too many parameters for "sub paste""#); + SubCommand::Paste.print_usage(0); + return; + } + state.unsaved = true; + for p in subregion.block_iter() + { + let data = match p.get_state() + { + None => DynData::Empty, + Some(d) => p.get_block().serialize_state(d).unwrap(), + }; + schematic.replace(x + p.get_pos().0, y + p.get_pos().1, p.get_block(), data, p.get_rotation(), false).unwrap(); + } + }, + Some("place") => + { + let Some(ref mut subregion) = state.subregion + else + { + eprintln!(r#"Command "sub place" requires an active subregion (see "sub help")"#); + return; + }; + let x = parse_num!(SubCommand::Place, tokens, "x", u16); + let y = parse_num!(SubCommand::Place, tokens, "y", u16); + if x >= subregion.get_width() || y >= subregion.get_height() + { + eprintln!("Invalid coordinate ({x} / {y}) out of bounds ({} / {})", subregion.get_width(), subregion.get_height()); + return; + } + let block = match tokens.next() + { + None => + { + eprintln!("Missing argument: block name"); + SubCommand::Place.print_usage(0); + return; + }, + Some(name) => + { + match state.reg.get(name) + { + None => + { + eprintln!("No such block {name:?}"); + return; + }, + Some(b) => b, + } + }, + }; + let rot = match tokens.next() + { + None => None, + Some("right") | Some("east") => Some(Rotation::Right), + Some("up") | Some("north") => Some(Rotation::Up), + Some("left") | Some("west") => Some(Rotation::Left), + Some("down") | Some("south") => Some(Rotation::Down), + Some(rot) => + { + eprintln!("Invalid rotation {rot:?}"); + return; + }, + }; + let replace = if rot.is_some() + { + match tokens.next() + { + None => None, + Some("true") | Some("yes") => Some(true), + Some("false") | Some("no") => Some(false), + Some(replace) => + { + eprintln!("Invalid replacement {replace:?}"); + return; + }, + } + } + else {None}; + if tokens.remainder().is_some() + { + eprintln!(r#"Too many parameters for "sub place""#); + SubCommand::Place.print_usage(0); + return; + } + let rot = rot.unwrap_or(Rotation::Right); + let result = if replace.unwrap_or(false) + { + subregion.replace(x, y, block, DynData::Empty, rot, false).err() + } + else + { + subregion.set(x, y, block, DynData::Empty, rot).err() + }; + if let Some(e) = result + { + print_err!(e, "Failed to place block at {x} / {y}"); + return; + } + }, + Some("rotate") => + { + let Some(ref mut subregion) = state.subregion + else + { + eprintln!(r#"Command "sub rotate" requires an active subgregion (see "sub help")"#); + return; + }; + let angle = parse_num!(SubCommand::Rotate, tokens, "angle", i32); + if angle % 90 != 0 + { + eprintln!("Rotation angle must be a multiple of 90 degrees"); + return; + } + if tokens.remainder().is_some() + { + eprintln!(r#"Too many parameters for "sub rotate""#); + SubCommand::Rotate.print_usage(0); + return; + } + match (angle / 90) % 4 + { + 0 => (), + 1 | -3 => subregion.rotate(false), + 2 | -2 => subregion.rotate_180(), + 3 | -1 => subregion.rotate(true), + a => unreachable!("angle {angle} -> {a}"), + } + }, + Some("mirror") => + { + let Some(ref mut subregion) = state.subregion + else + { + eprintln!(r#"Command "sub mirror" requires an active subregion (see "sub help")"#); + return; + }; + let (x, y) = match tokens.next() + { + None => + { + eprintln!("Missing argument: axis"); + SubCommand::Mirror.print_usage(0); + return; + }, + Some("x") | Some("h") | Some("horizontal") | Some("horizontally") => (true, false), + Some("y") | Some("v") | Some("vertical") | Some("vertically") => (false, true), + Some("both") | Some("all") => (true, true), + Some(axis) => + { + eprintln!("Invalid mirroring axis: {axis:?}"); + return; + }, + }; + if tokens.remainder().is_some() + { + eprintln!(r#"Too many parameters for "sub mirror""#); + SubCommand::Mirror.print_usage(0); + return; + } + subregion.mirror(x, y); + }, + Some("move") => + { + let Some(ref mut subregion) = state.subregion + else + { + eprintln!(r#"Command "sub move" requires an active subregion (see "sub help")"#); + return; + }; + let dx = parse_num!(SubCommand::Move, tokens, "dx", i16); + let dy = parse_num!(SubCommand::Move, tokens, "dy", i16); + if tokens.remainder().is_some() + { + eprintln!(r#"Too many parameters for "sub move""#); + SubCommand::Move.print_usage(0); + return; + } + if dx != 0 && dy != 0 + { + if let Err(e) = subregion.resize(dx, dy, subregion.get_width(), subregion.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 subregion: "); + 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})") + } + } + } + }, + Some("resize") => + { + let Some(ref mut subregion) = state.subregion + else + { + eprintln!(r#"Command "sub resize" requires an active subregion (see "sub help")"#); + return; + }; + let w = parse_num!(SubCommand::Resize, tokens, "width", u16); + if w == 0 + { + eprintln!("Subregion width must be positive"); + return; + } + let h = parse_num!(SubCommand::Resize, tokens, "height", u16); + if h == 0 + { + eprintln!("Subregion height must be positive"); + return; + } + let (dx, dy) = if let arg @ Some(..) = tokens.next() + { + let dx = parse_num!(SubCommand::Resize, "dx", <i16>::from(arg)); + let dy = parse_num!(SubCommand::Resize, tokens, "dy", i16); + (dx, dy) + } + else {(0, 0)}; + if tokens.remainder().is_some() + { + eprintln!(r#"Too many parameters for "sub resize""#); + SubCommand::Resize.print_usage(0); + return; + } + if w != subregion.get_width() || h != subregion.get_height() || dx != 0 || dy != 0 + { + if let Err(e) = subregion.resize(dx, dy, w, h) + { + print_err!(e, "Could not resize subregion"); + } + } + }, + Some("remove") => + { + let Some(ref mut subregion) = state.subregion + else + { + eprintln!(r#"Command "sub remove" requires an active subregion (see "sub help")"#); + return; + }; + let x0 = parse_num!(SubCommand::Remove, tokens, "x0", u16); + let y0 = parse_num!(SubCommand::Remove, tokens, "y0", u16); + if x0 >= subregion.get_width() || y0 >= subregion.get_height() + { + eprintln!("Invalid coordinate ({x0} / {y0}) out of bounds ({} / {})", subregion.get_width(), subregion.get_height()); + return; + } + let (x0, y0, x1, y1) = if let arg @ Some(..) = tokens.next() + { + let x1 = parse_num!(SubCommand::Remove, "x1", <u16>::from(arg)); + let y1 = parse_num!(SubCommand::Remove, tokens, "y1", u16); + if x1 >= subregion.get_width() || y1 >= subregion.get_height() + { + eprintln!("Invalid coordinate ({x1} / {y1}) out of bounds ({} / {})", subregion.get_width(), subregion.get_height()); + return; + } + (x0.min(x1), y0.min(y1), x0.max(x1), y0.max(y1)) + } + else {(x0, y0, x0, y0)}; + if tokens.remainder().is_some() + { + eprintln!(r#"Too many parameters for "sub remove""#); + SubCommand::Remove.print_usage(0); + return; + } + if x1 > x0 || y1 > y0 + { + let mut cnt = 0u32; + for y in y0..=y1 + { + for x in x0..=x1 + { + // position was already checked while parsing + if subregion.take(x, y).unwrap().is_some() {cnt += 1;} + } + } + println!("Removed {cnt} blocks in {x0} / {y0} to {x1} / {y1}"); + } + else + { + // position was already checked while parsing + match subregion.take(x0, y0).unwrap() + { + None => (), + Some(p) => println!("Removed block {} from {x0} / {y0}", p.get_block().get_name()), + } + } + }, + Some("print") => + { + let Some(ref subregion) = state.subregion + else + { + eprintln!(r#"Command "sub print" requires an active subregion (see "sub help")"#); + return; + }; + if tokens.remainder().is_some() + { + eprintln!(r#"Too many parameters for "sub print""#); + SubCommand::Print.print_usage(0); + return; + } + print_schematic(subregion); + }, + Some("dump") => + { + let Some(ref subregion) = state.subregion + else + { + eprintln!(r#"Command "sub dump" requires an active subregion (see "sub help")"#); + return; + }; + if tokens.remainder().is_some() + { + eprintln!(r#"Too many parameters for "sub dump""#); + SubCommand::Dump.print_usage(0); + return; + } + let b64 = match SchematicSerializer(state.reg).serialize_base64(subregion) + { + Ok(b64) => b64, + Err(e) => + { + print_err!(e, "Could not serialize subregion"); + return; + }, + }; + println!("Subregion: {}", b64); + }, + Some(unknown) => eprintln!("Unknown command \"sub\" {unknown:?}"), + } +} |