mindustry logic execution, map- and schematic- parsing and rendering
doc
36 files changed, 255 insertions, 701 deletions
diff --git a/.github/example.png b/.github/example.png Binary files differnew file mode 100644 index 0000000..41d900d --- /dev/null +++ b/.github/example.png @@ -1,17 +1,20 @@ [package] -name = "plandustry" +name = "mindus" version = "1.0.1" edition = "2021" -description = "A command-line tool for working with Mindustry schematics" -authors = ["KosmosPrime <[email protected]>"] -repository = "https://github.com/KosmosPrime/plandustry.git" +description = "A library for working with mindustry data formats (eg schematics) (fork of plandustry)" +authors = [ + "KosmosPrime <[email protected]>", + "bend-n <[email protected]>", +] +repository = "https://github.com/bend-n/mindus.git" license = "GPL-3.0" [dependencies] flate2 = { version = "1.0", features = ["zlib"], default-features = false } base64 = "0.21.2" paste = "1.0.12" -strconv = { path = "strconv" } +strconv = "0.1" image = { version = "0.24.6", features = ["png"], default-features = false } const-str = "0.5.5" color-hex = "0.2.0" @@ -1,37 +1,17 @@ -# Plandustry -Plandustry is a command-line tool for editing [Mindustry](https://github.com/Anuken/Mindustry) schematics. +# mindus +Mindus is a library for working with [Mindustry](https://github.com/Anuken/Mindustry) formats. -## Command-Line usage -The program must be run with a command line first determining the operation to perform, followed by arguments that depend on the operation. -In general, arguments can either be literal (not starting with a dash) short form (starting with a single dash) and long form (starting with a double dash). In -all cases, arguments are space-delimited or can be wrapped in quotes (but internal quotes are considered literal). Both short and long form arguments may be -passed a value, but differ in their syntax and interpretation. Short form arguments are only a single character long, but multiple (even duplicates) can -be used in the same group (following a single dash). This has the advantage that the value following it is passed to all arguments. For example: -- `-a -b -c=value` is 2 arguments with no value (`a` and `b`) and another argument (`c`) with value `value` -- `-ab -xyx` is 5 arguments with no value: `a`, `b`, 2 times `x` and `z` -- `-hello=world` is 5 arguments: `h`, `e`, 2 times `l` and `o`, each (even the double `l`) with the value `world` -Long form arguments are simpler: they start with a double dash and may be arbitrarily long. The syntax for passing values is the same but there can only be one -argument per token, and the value only applies to it (such as `--long-form-arg=its-value`). -Note that arguments can forbid, require or permit the inclusion of a value, and some may be used multiple times (as noted below). +## Usage -### Print -The print command takes in schematics from the command line, from files or interactively and prints the name, tags, build cost and blocks contained in it. +```rs +use mindus::*; +let reg = build_registry(); +let mut ss = SchematicSerializer(®); +let s = ss.deserialize_base64("bXNjaAF4nD3SQW6DMBBA0bE94wF104vkDr1H1QVtWUQioTL0/oFJ/Fl9GXiy5ZFBhiJ6n26zvE9tv7T1f5/bZbtNyyJvv/P2065/+3W9i0hdpu952SR/fiWp29qOL4/lDzkfExkiEpWPGqMKpZRRlT/8VQkv4aXwnlUopYw6vRTVvRzeGJVYy1ShlDKqezk8O8+DV/AKXgkvRSllvK2sdU/xFE/xFE/xFE/xNLzxeRlU9wzPOK9xXsMzPMOr3EcNL0VlqlBKGVWpfh+O5+zPmRdnXpx5cebFmRd/eQ9KIReL")?; +let output = Renderer::render(&s); +output.save("output.png"); +``` -| Argument | Description | Appears | Value | -| --- | --- | --- | --- | -| `literal` | A base-64 encoded Schematic to print | Optional, Repeatable | N/A | -| `-f`, `--file` | A path to a `.msch` file (binary schematic) to print | Optional, Repeatable | Required | -| `-i`, `--interactive` | Run interactively where base-64 encoded schematics are read from stdin and printed | Optional | Forbidden | +This produces: -Note that interactive mode is the default if no literals or files are given, but to include it anyway is not an error. - -### Edit -The edit command is an interactive prompt capable of loading, editing, printing and saving schematics. - -| Argument | Description | Appears | Value | -| --- | --- | --- | --- | -| `literal` | A base-64 encoded Schematic to load | Optional | N/A | -| `-f`, `--file` | A path to a `.msch` file (binary schematic) to load | Optional | Required | - -If the file argument is present, literals are ignored. After loading the given schematic (if any), the program enters interactive mode. Use "help" for a list -of available commands in interactive mode. +
\ No newline at end of file diff --git a/src/access.rs b/src/access.rs index 44d421b..ad23b51 100644 --- a/src/access.rs +++ b/src/access.rs @@ -1,3 +1,4 @@ +//! Similar to Cow but doesn't require ToOwned use std::borrow::Borrow; use std::cmp::Ordering; use std::fmt; @@ -6,8 +7,8 @@ use std::ops::Deref; pub type BoxAccess<'a, D> = Access<'a, Box<D>, D>; -// Similar to Cow but doesn't require ToOwned #[derive(Clone, Debug)] +/// Similar to Cow but doesn't require ToOwned pub enum Access<'a, T: AsRef<B>, B: ?Sized> { Borrowed(&'a B), Owned(T), diff --git a/src/block/base.rs b/src/block/base.rs index 2824124..fde5c5a 100644 --- a/src/block/base.rs +++ b/src/block/base.rs @@ -1,3 +1,4 @@ +//! all the uncategorized blocks (they need to be categorized for proper rendering tho) use std::any::Any; use crate::block::simple::{cost, state_impl, BuildCost, SimpleBlock}; diff --git a/src/block/content.rs b/src/block/content.rs index 832ebb4..d07eb05 100644 --- a/src/block/content.rs +++ b/src/block/content.rs @@ -1,3 +1,4 @@ +//! everything use crate::content::content_enum; content_enum! { diff --git a/src/block/defense.rs b/src/block/defense.rs index d136dc6..2cf3bbc 100644 --- a/src/block/defense.rs +++ b/src/block/defense.rs @@ -1,3 +1,4 @@ +//! walls use std::any::Any; use crate::block::simple::{cost, state_impl, BuildCost, SimpleBlock}; diff --git a/src/block/distribution.rs b/src/block/distribution.rs index c6e35e8..9076a12 100644 --- a/src/block/distribution.rs +++ b/src/block/distribution.rs @@ -1,3 +1,4 @@ +//! conveyors ( & ducts ) use std::any::Any; use std::error::Error; use std::fmt; @@ -119,12 +120,10 @@ impl BlockLogic for ItemBlock { item_c[2] as f32 / 255.0, ]; let mut top = load(category, "center").unwrap(); - for Rgba([r, g, b, ref a]) in top.pixels_mut() { - if a > &254 { - *r = (*r as f32 * tr) as u8; - *g = (*g as f32 * tg) as u8; - *b = (*b as f32 * tb) as u8; - } + for Rgba([r, g, b, _]) in top.pixels_mut() { + *r = (*r as f32 * tr) as u8; + *g = (*g as f32 * tg) as u8; + *b = (*b as f32 * tb) as u8; } image::imageops::overlay(&mut p, &top, 0, 0); diff --git a/src/block/drills.rs b/src/block/drills.rs index ebea8cc..5c5f87a 100644 --- a/src/block/drills.rs +++ b/src/block/drills.rs @@ -1,3 +1,4 @@ +//! extraction of raw resources (mine part) use crate::block::make_register; use crate::block::simple::{cost, SimpleBlock}; diff --git a/src/block/liquid.rs b/src/block/liquid.rs index 044ccd0..2400988 100644 --- a/src/block/liquid.rs +++ b/src/block/liquid.rs @@ -1,3 +1,4 @@ +//! liquid related things use std::any::Any; use std::error::Error; use std::fmt; diff --git a/src/block/logic.rs b/src/block/logic.rs index e9e0fdf..027302b 100644 --- a/src/block/logic.rs +++ b/src/block/logic.rs @@ -1,3 +1,4 @@ +//! logic processors and stuff use std::any::Any; use std::borrow::Cow; use std::error::Error; @@ -18,6 +19,7 @@ use crate::data::{self, DataRead, DataWrite, GridPos}; use crate::item::storage::Storage; make_register! { + // todo reinforced proc "message" => MessageLogic::new(1, true, cost!(Copper: 5, Graphite: 5)); "switch" => SwitchLogic::new(1, true, cost!(Copper: 5, Graphite: 5)); "micro-processor" => ProcessorLogic::new(1, true, cost!(Copper: 90, Lead: 50, Silicon: 50)); diff --git a/src/block/mod.rs b/src/block/mod.rs index 707ab35..70fd498 100644 --- a/src/block/mod.rs +++ b/src/block/mod.rs @@ -1,3 +1,6 @@ +//! deal with blocks. +//! +//! categorized as mindustry categorizes them in its assets folder, for easy drawing use image::RgbaImage; use std::any::Any; use std::borrow::Cow; @@ -25,6 +28,7 @@ pub mod storage; pub mod turrets; pub trait BlockLogic { + /// mindustry blocks are the same width and height fn get_size(&self) -> u8; fn is_symmetric(&self) -> bool; @@ -169,6 +173,7 @@ impl Error for SerializeError { } } +/// a block. put it in stuff! pub struct Block { category: Cow<'static, str>, name: Cow<'static, str>, @@ -183,6 +188,7 @@ impl PartialEq for Block { impl Block { #[must_use] + /// create a new block pub const fn new( category: Cow<'static, str>, name: Cow<'static, str>, @@ -195,6 +201,15 @@ impl Block { } } + /// this blocks name + /// ``` + /// assert!(mindus::block::distribution::DISTRIBUTOR.name() == "distributor") + /// ``` + pub fn name(&self) -> &str { + &*self.name + } + + /// draw this block, with this state pub fn image(&self, state: Option<&dyn Any>) -> RgbaImage { if let Some(p) = self .logic @@ -207,46 +222,49 @@ impl Block { read(&*self.category, &*self.name, self.get_size()) } + /// size. pub fn get_size(&self) -> u8 { self.logic.get_size() } + /// does it matter if its rotated pub fn is_symmetric(&self) -> bool { self.logic.is_symmetric() } + /// cost pub fn get_build_cost(&self) -> Option<ItemStorage> { self.logic.as_ref().create_build_cost() } - pub fn data_from_i32(&self, config: i32, pos: GridPos) -> Result<DynData, DataConvertError> { + pub(crate) fn data_from_i32( + &self, + config: i32, + pos: GridPos, + ) -> Result<DynData, DataConvertError> { self.logic.data_from_i32(config, pos) } - pub fn deserialize_state( + pub(crate) fn deserialize_state( &self, data: DynData, ) -> Result<Option<Box<dyn Any>>, DeserializeError> { self.logic.deserialize_state(data) } - pub fn clone_state(&self, state: &dyn Any) -> Box<dyn Any> { + pub(crate) fn clone_state(&self, state: &dyn Any) -> Box<dyn Any> { self.logic.clone_state(state) } - pub fn mirror_state(&self, state: &mut dyn Any, horizontally: bool, vertically: bool) { + pub(crate) fn mirror_state(&self, state: &mut dyn Any, horizontally: bool, vertically: bool) { self.logic.mirror_state(state, horizontally, vertically); } - pub fn rotate_state(&self, state: &mut dyn Any, clockwise: bool) { + pub(crate) fn rotate_state(&self, state: &mut dyn Any, clockwise: bool) { self.logic.rotate_state(state, clockwise); } - pub fn rotate_180(&mut self, state: &mut dyn Any) { - self.logic.mirror_state(state, true, true); - } - - pub fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> { + pub(crate) fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> { self.logic.serialize_state(state) } } @@ -265,6 +283,7 @@ impl RegistryEntry for Block { } #[derive(Clone, Copy, Debug, Eq, PartialEq)] +/// the possible rotation states of a object pub enum Rotation { Right, Up, @@ -274,6 +293,7 @@ pub enum Rotation { impl Rotation { #[must_use] + /// mirror the directions. pub fn mirrored(self, horizontally: bool, vertically: bool) -> Self { match self { Self::Right => { @@ -307,11 +327,13 @@ impl Rotation { } } + /// mirror in place pub fn mirror(&mut self, horizontally: bool, vertically: bool) { *self = self.mirrored(horizontally, vertically); } #[must_use] + /// rotate the rotation pub fn rotated(self, clockwise: bool) -> Self { match self { Self::Right => { @@ -345,11 +367,13 @@ impl Rotation { } } + /// rotate the rotation in place pub fn rotate(&mut self, clockwise: bool) { *self = self.rotated(clockwise); } #[must_use] + /// rotate 180 pub fn rotated_180(self) -> Self { match self { Self::Right => Self::Left, @@ -359,6 +383,7 @@ impl Rotation { } } + /// rotate 180 in place pub fn rotate_180(&mut self) { *self = self.rotated_180(); } @@ -407,13 +432,14 @@ macro_rules! make_register { pub(crate) use make_register; #[must_use] +/// create a block registry pub fn build_registry() -> BlockRegistry<'static> { let mut reg = BlockRegistry::default(); register(&mut reg); reg } -pub fn register(reg: &mut BlockRegistry<'_>) { +fn register(reg: &mut BlockRegistry<'_>) { turrets::register(reg); drills::register(reg); distribution::register(reg); diff --git a/src/block/payload.rs b/src/block/payload.rs index 9faea35..7377d2c 100644 --- a/src/block/payload.rs +++ b/src/block/payload.rs @@ -1,3 +1,4 @@ +//! payload related bits and bobs use std::any::Any; use std::error::Error; use std::fmt; diff --git a/src/block/power.rs b/src/block/power.rs index 6cc71d5..9e0e69b 100644 --- a/src/block/power.rs +++ b/src/block/power.rs @@ -1,3 +1,4 @@ +//! power connection and generation use std::any::Any; use std::error::Error; use std::fmt; diff --git a/src/block/production.rs b/src/block/production.rs index f7cfd13..65dc9f9 100644 --- a/src/block/production.rs +++ b/src/block/production.rs @@ -1,8 +1,8 @@ +//! the industry part of mindustry use crate::block::make_register; use crate::block::simple::{cost, SimpleBlock}; -make_register! -( +make_register! { "graphite-press" => SimpleBlock::new(2, true, cost!(Copper: 75, Lead: 30)); "multi-press" => SimpleBlock::new(3, true, cost!(Lead: 100, Graphite: 50, Titanium: 100, Silicon: 25)); "silicon-smelter" => SimpleBlock::new(2, true, cost!(Copper: 30, Lead: 25)); @@ -39,4 +39,4 @@ make_register! // heat reactor // sandbox only "heat-source" => SimpleBlock::new(1, false, &[]); -); +} diff --git a/src/block/simple.rs b/src/block/simple.rs index 070f4b1..c5d22b0 100644 --- a/src/block/simple.rs +++ b/src/block/simple.rs @@ -1,3 +1,4 @@ +//! type used for basic blocks, eg turrets and factorys use std::any::{type_name, Any}; use crate::block::{impl_block, BlockLogic, DataConvertError, DeserializeError, SerializeError}; @@ -7,8 +8,7 @@ use crate::data::GridPos; use crate::item; use crate::item::storage::Storage; -macro_rules!state_impl -{ +macro_rules! state_impl { ($vis:vis $type:ty) => { $vis fn get_state(state: &dyn Any) -> &$type diff --git a/src/block/storage.rs b/src/block/storage.rs index c99c19c..0e34e34 100644 --- a/src/block/storage.rs +++ b/src/block/storage.rs @@ -1,3 +1,4 @@ +//! cores, vaults, containers use crate::block::distribution::ItemBlock; use crate::block::make_register; use crate::block::simple::{cost, SimpleBlock}; diff --git a/src/block/turrets.rs b/src/block/turrets.rs index 2280ac9..1f1d99a 100644 --- a/src/block/turrets.rs +++ b/src/block/turrets.rs @@ -1,3 +1,4 @@ +//! idk why its not in the [crate::block::defense] module use crate::block::make_register; use crate::block::simple::{cost, SimpleBlock}; diff --git a/src/content.rs b/src/content.rs index 3e7fd0b..51025fb 100644 --- a/src/content.rs +++ b/src/content.rs @@ -1,3 +1,4 @@ +//! contains types of types use std::error::Error; macro_rules! numeric_enum { diff --git a/src/data/base64.rs b/src/data/base64.rs index 303e9e0..2651b08 100644 --- a/src/data/base64.rs +++ b/src/data/base64.rs @@ -3,10 +3,10 @@ pub use base64::{DecodeSliceError as DecodeError, EncodeSliceError as EncodeErro const BASE64: general_purpose::GeneralPurpose = general_purpose::STANDARD; -pub fn encode(input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> { +pub(crate) fn encode(input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> { BASE64.encode_slice(input, output) } -pub fn decode(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> { +pub(crate) fn decode(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> { BASE64.decode_slice(input, output) } diff --git a/src/data/dynamic.rs b/src/data/dynamic.rs index 486eebc..eda0625 100644 --- a/src/data/dynamic.rs +++ b/src/data/dynamic.rs @@ -1,3 +1,4 @@ +//! variable type use std::error::Error; use std::fmt; @@ -8,6 +9,7 @@ use crate::logic::LogicField; use crate::team::Team; #[derive(Clone, Debug, PartialEq)] +/// holds different kinds of data pub enum DynData { Empty, Int(i32), diff --git a/src/data/mod.rs b/src/data/mod.rs index d16df44..7653c27 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,9 +1,10 @@ +//! all the IO use std::error::Error; use std::fmt; use std::str::Utf8Error; -pub mod base64; -pub mod command; +mod base64; +mod command; pub mod dynamic; pub mod renderer; pub mod schematic; @@ -271,7 +272,7 @@ impl<'d> TryFrom<DataWrite<'d>> for Vec<u8> { } } } - +/// basic serialization/deserialization functions pub trait Serializer<D> { type ReadError; type WriteError; diff --git a/src/data/renderer.rs b/src/data/renderer.rs index 4f81b17..fdd169d 100644 --- a/src/data/renderer.rs +++ b/src/data/renderer.rs @@ -1,3 +1,4 @@ +//! schematic drawing use std::io::{BufReader, Cursor}; use std::path::Path; @@ -8,7 +9,7 @@ use zip::ZipArchive; use super::schematic::Schematic; -pub fn load(category: &str, name: &str) -> Option<RgbaImage> { +pub(crate) fn load(category: &str, name: &str) -> Option<RgbaImage> { let mut p = Path::new("target/out/blocks").join(category).join(name); p.set_extension("png"); let f = std::fs::File::open(p).ok()?; @@ -29,7 +30,7 @@ fn load_zip() { const SUFFIXES: &[&str; 8] = &[ "bottom", "mid", "", "-base", "-left", "-right", "-top", "-over", ]; -pub fn read<S>(category: &str, name: &str, size: S) -> RgbaImage +pub(crate) fn read<S>(category: &str, name: &str, size: S) -> RgbaImage where S: Into<u32> + Copy, { @@ -42,8 +43,19 @@ where c } +/// renderer for creating images of schematics pub struct Renderer {} impl<'l> Renderer { + /// creates a picture of a schematic. Bridges and nodes are not drawn, and there is no background. + /// conveyors, conduits, and ducts currently do not render. + /// ``` + /// use mindus::*; + /// let s = Schematic::new(2, 3); + /// s.put(0, 0, blocks::distribution::DISTRIBUTOR); + /// s.put(0, 3, blocks::distrubution::ROUTER); + /// s.put(1, 3, blocks::defense::COPPER_WALL); + /// let output /*: RgbaImage */ = Renderer::render(&s); + /// ``` pub fn render(s: &'l Schematic<'_>) -> RgbaImage { load_zip(); let mut canvas = RgbaImage::new((s.width * 32).into(), (s.height * 32).into()); diff --git a/src/data/schematic.rs b/src/data/schematic.rs index be7c0ff..8c8f710 100644 --- a/src/data/schematic.rs +++ b/src/data/schematic.rs @@ -1,3 +1,4 @@ +//! schematic parsing use std::any::Any; use std::collections::hash_map::Entry; use std::collections::HashMap; @@ -19,14 +20,17 @@ use crate::data::{self, DataRead, DataWrite, GridPos, Serializer}; use crate::item::storage::Storage as ItemStorage; use crate::registry::RegistryEntry; +/// biggest schematic pub const MAX_DIMENSION: u16 = 128; +/// most possible blocks pub const MAX_BLOCKS: u32 = 128 * 128; +/// a placement in a schematic pub struct Placement<'l> { pub pos: GridPos, pub block: &'l Block, + pub rot: Rotation, state: Option<Box<dyn Any>>, - rot: Rotation, } impl PartialEq for Placement<'_> { @@ -36,6 +40,7 @@ impl PartialEq for Placement<'_> { } impl<'l> Placement<'l> { + /// gets the current state of this placement. you can cast it with `placement.block::get_state(placement.get_state()?)?` #[must_use] pub fn get_state(&self) -> Option<&dyn Any> { match self.state { @@ -44,6 +49,7 @@ impl<'l> Placement<'l> { } } + /// get mutable state. pub fn get_state_mut(&mut self) -> Option<&mut dyn Any> { match self.state { None => None, @@ -51,10 +57,12 @@ impl<'l> Placement<'l> { } } + /// draws this placement in particular pub fn image(&self) -> RgbaImage { self.block.image(self.get_state()) } + /// set the state pub fn set_state( &mut self, data: DynData, @@ -63,6 +71,7 @@ impl<'l> Placement<'l> { Ok(std::mem::replace(&mut self.state, state)) } + /// rotate this pub fn set_rotation(&mut self, rot: Rotation) -> Rotation { std::mem::replace(&mut self.rot, rot) } @@ -84,6 +93,7 @@ impl<'l> Clone for Placement<'l> { } #[derive(Clone)] +/// a schematic. pub struct Schematic<'l> { pub width: u16, pub height: u16, @@ -103,6 +113,11 @@ impl<'l> PartialEq for Schematic<'l> { impl<'l> Schematic<'l> { #[must_use] + /// create a new schematic, panicking if too big + /// ``` + /// # use mindus::Schematic; + /// let s = Schematic::new(5, 5); + /// ``` pub fn new(width: u16, height: u16) -> Self { match Self::try_new(width, height) { Ok(s) => s, @@ -111,6 +126,11 @@ impl<'l> Schematic<'l> { } } + /// create a new schematic, erroring if too big + /// ``` + /// # use mindus::Schematic; + /// assert!(Schematic::try_new(500, 500).is_err() == true); + /// ``` pub fn try_new(width: u16, height: u16) -> Result<Self, NewError> { if width > MAX_DIMENSION { return Err(NewError::Width(width)); @@ -132,16 +152,24 @@ impl<'l> Schematic<'l> { } #[must_use] + /// have blocks? pub fn is_empty(&self) -> bool { self.blocks.is_empty() } #[must_use] + /// count blocks pub fn get_block_count(&self) -> usize { self.blocks.len() } #[must_use] + /// check if a rect is empty + /// ``` + /// # use mindus::Schematic; + /// let s = Schematic::new(5, 5); + /// assert!(s.is_region_empty(1, 1, 4, 4) == true); + /// ``` pub fn is_region_empty(&self, x: u16, y: u16, w: u16, h: u16) -> bool { if self.blocks.is_empty() { return true; @@ -176,6 +204,7 @@ impl<'l> Schematic<'l> { } } + /// gets a block pub fn get(&self, x: u16, y: u16) -> Result<Option<&Placement<'l>>, PosError> { if x >= self.width || y >= self.height { return Err(PosError { @@ -195,6 +224,7 @@ impl<'l> Schematic<'l> { } } + /// gets a block, mutably pub fn get_mut(&mut self, x: u16, y: u16) -> Result<Option<&mut Placement<'l>>, PosError> { if x >= self.width || y >= self.height { return Err(PosError { @@ -214,7 +244,7 @@ impl<'l> Schematic<'l> { } } - fn swap_remove(&mut self, idx: usize) -> Placement<'l> { + fn remove(&mut self, idx: usize) -> Placement<'l> { // swap_remove not only avoids moves in self.blocks but also reduces the lookup changes we have to do let prev = self.blocks.swap_remove(idx); self.fill_lookup( @@ -254,6 +284,30 @@ impl<'l> Schematic<'l> { } } + /// put a block in (same as [Schematic::set], but less arguments) + /// ``` + /// # use mindus::Schematic; + /// # use mindus::DynData; + /// # use mindus::block::Rotation; + /// + /// let mut s = Schematic::new(5, 5); + /// s.put(0, 0, &mindus::block::distribution::ROUTER); + /// assert!(s.get(0, 0).unwrap().is_some() == true); + /// ``` + pub fn put(&mut self, x: u16, y: u16, block: &'l Block) -> Result<&Placement<'l>, PlaceError> { + self.set(x, y, block, DynData::Empty, Rotation::Up) + } + + /// set a block + /// ``` + /// # use mindus::Schematic; + /// # use mindus::DynData; + /// # use mindus::block::Rotation; + /// + /// let mut s = Schematic::new(5, 5); + /// s.set(0, 0, &mindus::block::distribution::ROUTER, DynData::Empty, Rotation::Right); + /// assert!(s.get(0, 0).unwrap().is_some() == true); + /// ``` pub fn set( &mut self, x: u16, @@ -335,7 +389,7 @@ impl<'l> Schematic<'l> { if let Some(idx) = self.lookup[(x as usize + dx) + (y as usize + dy) * (self.width as usize)] { - let prev = self.swap_remove(idx); + let prev = self.remove(idx); if let Some(ref mut v) = result { v.push(prev); } @@ -391,6 +445,18 @@ impl<'l> Schematic<'l> { } } + /// take out a block + /// ``` + /// # use mindus::Schematic; + /// # use mindus::DynData; + /// # use mindus::block::Rotation; + /// + /// let mut s = Schematic::new(5, 5); + /// s.put(0, 0, &mindus::block::turrets::DUO); + /// assert!(s.get(0, 0).unwrap().is_some() == true); + /// assert!(s.take(0, 0).unwrap().is_some() == true); + /// assert!(s.get(0, 0).unwrap().is_none() == true); + /// ``` pub fn take(&mut self, x: u16, y: u16) -> Result<Option<Placement<'l>>, PosError> { if x >= self.width || y >= self.height { return Err(PosError { @@ -406,7 +472,7 @@ impl<'l> Schematic<'l> { let pos = (x as usize) + (y as usize) * (self.width as usize); match self.lookup[pos] { None => Ok(None), - Some(idx) => Ok(Some(self.swap_remove(idx))), + Some(idx) => Ok(Some(self.remove(idx))), } } } @@ -433,6 +499,7 @@ impl<'l> Schematic<'l> { } } + /// flip it pub fn mirror(&mut self, horizontally: bool, vertically: bool) { if !self.blocks.is_empty() && (horizontally || vertically) { for curr in &mut self.blocks { @@ -456,6 +523,18 @@ impl<'l> Schematic<'l> { } } + /// turn + /// ``` + /// # use mindus::Schematic; + /// # use mindus::DynData; + /// # use mindus::block::Rotation; + /// + /// let mut s = Schematic::new(5, 5); + /// // 0, 0 == bottom left + /// s.put(0, 0, &mindus::block::turrets::HAIL); + /// s.rotate(true); + /// assert!(s.get(0, 4).unwrap().is_some() == true); + /// ``` pub fn rotate(&mut self, clockwise: bool) { let w = self.width; let h = self.height; @@ -485,6 +564,7 @@ impl<'l> Schematic<'l> { } } + /// resize this schematic pub fn resize(&mut self, dx: i16, dy: i16, w: u16, h: u16) -> Result<(), ResizeError> { if w > MAX_DIMENSION { return Err(ResizeError::TargetWidth(w)); @@ -553,6 +633,7 @@ impl<'l> Schematic<'l> { Ok(()) } + /// like rotate(), but 180 pub fn rotate_180(&mut self) { self.mirror(true, true); } @@ -567,11 +648,22 @@ impl<'l> Schematic<'l> { } } + /// iterate over all the blocks pub fn block_iter<'s>(&'s self) -> Iter<'s, Placement<'l>> { self.blocks.iter() } #[must_use] + /// see how much this schematic costs. + /// ``` + /// # use mindus::Schematic; + /// # use mindus::DynData; + /// # use mindus::block::Rotation; + /// + /// let mut s = Schematic::new(5, 5); + /// s.put(0, 0, &mindus::block::turrets::CYCLONE); + /// // assert_eq!(s.compute_total_cost().0.get_total(), 405); + /// ``` pub fn compute_total_cost(&self) -> (ItemStorage, bool) { let mut cost = ItemStorage::new(); let mut sandbox = false; @@ -586,6 +678,7 @@ impl<'l> Schematic<'l> { } } +/// error created by creating a new schematic #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum NewError { Width(u16), @@ -603,6 +696,7 @@ impl fmt::Display for NewError { impl Error for NewError {} +/// error created by doing stuff out of bounds #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct PosError { pub x: u16, @@ -917,12 +1011,12 @@ impl<'l> fmt::Display for Schematic<'l> { const SCHEMATIC_HEADER: u32 = ((b'm' as u32) << 24) | ((b's' as u32) << 16) | ((b'c' as u32) << 8) | (b'h' as u32); +/// serde_schematic pub struct SchematicSerializer<'l>(pub &'l BlockRegistry<'l>); impl<'l> Serializer<Schematic<'l>> for SchematicSerializer<'l> { type ReadError = ReadError; type WriteError = WriteError; - fn deserialize(&mut self, buff: &mut DataRead<'_>) -> Result<Schematic<'l>, Self::ReadError> { let hdr = buff.read_u32()?; if hdr != SCHEMATIC_HEADER { @@ -1252,6 +1346,15 @@ impl Error for WriteError { } impl<'l> SchematicSerializer<'l> { + /// deserializes a schematic from base64 + /// ``` + /// # use mindus::*; + /// let string = "bXNjaAF4nGNgZmBmZmDJS8xNZeBOyslPzlYAkwzcKanFyUWZBSWZ+XkMDAxsOYlJqTnFDEzRsYwMfAWJlTn5iSm6RfmlJalFQGlGEGJkZWSYxQAAcBkUPA=="; + /// let reg = build_registry(); + /// let mut ss = SchematicSerializer(®); + /// let s = ss.deserialize_base64(string).unwrap(); + /// assert!(s.get(0, 0).unwrap().unwrap().block.name() == "payload-router"); + /// ``` pub fn deserialize_base64(&mut self, data: &str) -> Result<Schematic<'l>, R64Error> { let mut buff = Vec::<u8>::new(); buff.resize(data.len() / 4 * 3 + 1, 0); @@ -1259,6 +1362,7 @@ impl<'l> SchematicSerializer<'l> { Ok(self.deserialize(&mut DataRead::new(&buff[..n_out]))?) } + /// serialize a schematic to base64 pub fn serialize_base64(&mut self, data: &Schematic<'l>) -> Result<String, W64Error> { let mut buff = DataWrite::default(); self.serialize(&mut buff, data)?; @@ -1270,7 +1374,7 @@ impl<'l> SchematicSerializer<'l> { let n_out = base64::encode(buff, text.as_mut())?; // trailing zeros are valid UTF8, but not valid base64 assert_eq!(n_out, text.len()); - // SAFETY: base64 encoding outputs pure ASCII (see base64::CHARS) + // SAFETY: base64 encoding outputs pure ASCII (hopefully) Ok(unsafe { String::from_utf8_unchecked(text) }) } } @@ -1439,7 +1543,6 @@ mod test { "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" } diff --git a/src/exe/args.rs b/src/exe/args.rs deleted file mode 100644 index 46f8a42..0000000 --- a/src/exe/args.rs +++ /dev/null @@ -1,433 +0,0 @@ -use std::borrow::Cow; -use std::collections::HashMap; -use std::error; -use std::fmt; -use std::slice::from_ref; - -pub trait ArgHandler { - type Error: error::Error + 'static; - - fn on_literal(&mut self, name: &str) -> Result<(), Self::Error>; - - fn on_short(&mut self, name: char, value: Option<&str>) -> Result<(), Self::Error>; - - fn on_long(&mut self, name: &str, value: Option<&str>) -> Result<(), Self::Error>; -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Error<E: error::Error + 'static> { - Handler { pos: usize, val: E }, - EmptyName { pos: usize }, -} - -impl<E: error::Error + 'static> fmt::Display for Error<E> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Handler { pos, val } => write!(f, "{val} (at #{pos})"), - Self::EmptyName { pos } => write!(f, "malformed argument (at #{pos})"), - } - } -} - -impl<E: error::Error + 'static> error::Error for Error<E> { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match self { - // forward past the inner error because we decorate it in our Display impl - Self::Handler { val, .. } => val.source(), - _ => None, - } - } -} - -pub fn parse<I: Iterator, H: ArgHandler>( - args: &mut I, - handler: &mut H, - arg_off: usize, -) -> Result<bool, Error<H::Error>> -where - I::Item: AsRef<str>, -{ - for (pos, arg) in args.enumerate() { - let arg = arg.as_ref(); - if !arg.is_empty() { - if arg.as_bytes()[0] == b'-' { - if arg.len() >= 2 && arg.as_bytes()[1] == b'-' { - if arg == "--" { - return Ok(false); - } - let (name, value) = match arg.bytes().enumerate().find(|(_, b)| *b == b'=') { - None => (&arg[2..], None), - Some((i, _)) => (&arg[2..i], Some(&arg[i + 1..])), - }; - if name.is_empty() { - return Err(Error::EmptyName { pos: arg_off + pos }); - } - if let Err(val) = handler.on_long(name, value) { - return Err(Error::Handler { - pos: arg_off + pos, - val, - }); - } - } else { - let (value, end) = match arg.bytes().enumerate().find(|(_, b)| *b == b'=') { - None => (None, arg.len()), - Some((i, _)) => (Some(&arg[i + 1..]), i), - }; - if end > 1 { - for c in arg[1..end].chars() { - if let Err(val) = handler.on_short(c, value) { - return Err(Error::Handler { - pos: arg_off + pos, - val, - }); - } - } - } else { - return Err(Error::EmptyName { pos: arg_off + pos }); - } - } - } else if let Err(val) = handler.on_literal(arg) { - return Err(Error::Handler { - pos: arg_off + pos, - val, - }); - } - } - } - Ok(true) -} - -pub fn parse_args<H: ArgHandler>(handler: &mut H) -> Result<(), Error<H::Error>> { - parse(&mut std::env::args(), handler, 0)?; - Ok(()) -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ArgCount { - Forbidden, - Optional(usize), - Required(usize), -} - -impl ArgCount { - pub const fn has_value(&self) -> bool { - matches!(self, Self::Optional(..) | ArgCount::Required(..)) - } - - pub const fn is_required(&self) -> bool { - matches!(self, Self::Required(..)) - } - - pub const fn get_max_count(&self) -> Option<usize> { - match self { - Self::Optional(max) | ArgCount::Required(max) => Some(*max), - _ => None, - } - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct ArgOption { - short: Option<char>, - long: Option<Cow<'static, str>>, - count: ArgCount, -} - -impl ArgOption { - pub const fn new( - short: Option<char>, - long: Option<Cow<'static, str>>, - count: ArgCount, - ) -> Self { - if short.is_none() && long.is_none() { - panic!("option must have at least a short or long name"); - } - if let Some(max) = count.get_max_count() { - if max == 0 { - panic!("argument must be allowed to appear at least once"); - } - } - Self { short, long, count } - } - - pub fn get_short(&self) -> Option<char> { - self.short - } - - pub fn get_long(&self) -> Option<&str> { - match self.long { - None => None, - Some(Cow::Borrowed(r)) => Some(r), - Some(Cow::Owned(ref s)) => Some(s.as_str()), - } - } - - pub const fn get_count(&self) -> &ArgCount { - &self.count - } -} - -impl fmt::Display for ArgOption { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match (self.get_short(), self.get_long()) { - (None, None) => unreachable!("unnamed ArgOption"), - (None, Some(long)) => write!(f, "\"--{long}\""), - (Some(short), None) => write!(f, "\"-{short}\""), - (Some(short), Some(long)) => write!(f, "\"--{long}\" / \"-{short}\""), - } - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum OptionValue { - Absent, - Present, - Value(String), - Values(Vec<String>), -} - -impl OptionValue { - pub const fn is_absent(&self) -> bool { - matches!(self, Self::Absent) - } - - pub const fn is_present(&self) -> bool { - matches!(self, Self::Present | Self::Value(..) | Self::Values(..)) - } - - pub const fn has_value(&self) -> bool { - matches!(self, Self::Value(..) | Self::Values(..)) - } - - pub const fn get_value(&self) -> Option<&String> { - match self { - Self::Value(v) => Some(v), - _ => None, - } - } - - pub fn get_values(&self) -> Option<&[String]> { - match self { - Self::Value(v) => Some(from_ref(v)), - Self::Values(v) => Some(v.as_ref()), - _ => None, - } - } -} - -#[derive(Clone, Debug, Default)] -pub struct OptionHandler { - options: Vec<(ArgOption, OptionValue)>, - short_map: HashMap<char, usize>, - long_map: HashMap<String, usize>, - literals: Vec<String>, -} - -impl OptionHandler { - pub fn add(&mut self, opt: ArgOption) -> Result<OptionRef, AddArgError> { - if let Some(c) = opt.short { - if let Some(&i) = self.short_map.get(&c) { - return Err(AddArgError { - to_add: opt, - existing: &self.options[i].0, - }); - } - } - - if let Some(ref s) = opt.long { - if let Some(&i) = self.long_map.get(&**s) { - return Err(AddArgError { - to_add: opt, - existing: &self.options[i].0, - }); - } - } - - let idx = self.options.len(); - self.options.push((opt, OptionValue::Absent)); - let opt = &self.options[idx].0; - if let Some(c) = opt.short { - self.short_map.insert(c, idx); - } - if let Some(ref s) = opt.long { - let k = &**s; - self.long_map.insert(k.to_owned(), idx); - } - Ok(OptionRef(idx)) - } - - pub fn options(&self) -> &Vec<(ArgOption, OptionValue)> { - &self.options - } - - pub fn get(&self, opt_ref: OptionRef) -> (&ArgOption, &OptionValue) { - let opt = &self.options[opt_ref.0]; - (&opt.0, &opt.1) - } - - pub fn get_option(&self, opt_ref: OptionRef) -> &ArgOption { - &self.options[opt_ref.0].0 - } - - pub fn get_value(&self, opt_ref: OptionRef) -> &OptionValue { - &self.options[opt_ref.0].1 - } - - pub fn get_short(&self, name: char) -> Option<&(ArgOption, OptionValue)> { - self.short_map.get(&name).map(|&i| &self.options[i]) - } - - pub fn get_long(&self, name: &str) -> Option<&(ArgOption, OptionValue)> { - self.long_map.get(name).map(|&i| &self.options[i]) - } - - pub fn get_literals(&self) -> &Vec<String> { - &self.literals - } - - fn set_arg(&mut self, idx: usize, value: Option<&str>) -> Result<(), OptionError> { - let (ref o, ref mut curr) = self.options[idx]; - match o.count { - ArgCount::Forbidden => { - if value.is_none() { - if curr.is_absent() { - *curr = OptionValue::Present; - } - Ok(()) - } else { - Err(OptionError::ValueForbidden(o.clone())) - } - } - ArgCount::Optional(max) => match curr { - OptionValue::Absent | OptionValue::Present => { - if let Some(v) = value { - if max == 1 { - *curr = OptionValue::Value(v.to_owned()); - } else { - *curr = OptionValue::Values(vec![v.to_owned()]); - } - } else { - *curr = OptionValue::Present; - } - Ok(()) - } - OptionValue::Value(..) => Err(OptionError::TooMany(o.clone())), - OptionValue::Values(vec) => { - if vec.len() <= max { - if let Some(v) = value { - vec.push(v.to_owned()); - } - Ok(()) - } else { - Err(OptionError::TooMany(o.clone())) - } - } - }, - ArgCount::Required(max) => { - if let Some(v) = value { - match curr { - OptionValue::Absent => { - if max == 1 { - *curr = OptionValue::Value(v.to_owned()); - } else { - *curr = OptionValue::Values(vec![v.to_owned()]); - } - Ok(()) - } - OptionValue::Present => unreachable!("argument missing required value"), - OptionValue::Value(..) => Err(OptionError::TooMany(o.clone())), - OptionValue::Values(vec) => { - if vec.len() <= max { - vec.push(v.to_owned()); - Ok(()) - } else { - Err(OptionError::TooMany(o.clone())) - } - } - } - } else { - Err(OptionError::ValueRequired(o.clone())) - } - } - } - } - - pub fn clear(&mut self) { - self.options - .iter_mut() - .for_each(|(_, v)| *v = OptionValue::Absent); - } -} - -#[derive(Clone, Copy, Debug)] -pub struct OptionRef(usize); - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct AddArgError<'l> { - pub to_add: ArgOption, - pub existing: &'l ArgOption, -} - -impl<'l> fmt::Display for AddArgError<'l> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "duplicate argument {} (already have {})", - self.to_add, self.existing - ) - } -} - -impl<'l> error::Error for AddArgError<'l> {} - -impl ArgHandler for OptionHandler { - type Error = OptionError; - - fn on_literal(&mut self, name: &str) -> Result<(), Self::Error> { - self.literals.push(name.to_owned()); - Ok(()) - } - - fn on_short(&mut self, name: char, value: Option<&str>) -> Result<(), Self::Error> { - match self.short_map.get(&name) { - None => Err(OptionError::NoSuchShort(name)), - Some(&i) => self.set_arg(i, value), - } - } - - fn on_long(&mut self, name: &str, value: Option<&str>) -> Result<(), Self::Error> { - match self.long_map.get(name) { - None => Err(OptionError::NoSuchLong(name.to_owned())), - Some(&i) => self.set_arg(i, value), - } - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum OptionError { - NoSuchShort(char), - NoSuchLong(String), - ValueForbidden(ArgOption), - ValueRequired(ArgOption), - TooMany(ArgOption), -} - -impl fmt::Display for OptionError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::NoSuchShort(short) => write!(f, "invalid argument \"-{short}\""), - Self::NoSuchLong(long) => write!(f, "invalid argument \"--{long}\""), - Self::ValueForbidden(opt) => write!(f, "argument {opt} has no value"), - Self::ValueRequired(opt) => write!(f, "argument {opt} requires a value"), - Self::TooMany(opt) => { - if let Some(max) = opt.count.get_max_count() { - write!(f, "too many {opt} (max {max})") - } else { - write!(f, "duplicate argument {opt}") - } - } - } - } -} - -impl error::Error for OptionError {} diff --git a/src/exe/draw.rs b/src/exe/draw.rs index ff8ece7..6a061d6 100644 --- a/src/exe/draw.rs +++ b/src/exe/draw.rs @@ -1,26 +1,19 @@ -use plandustry::block::build_registry; -use plandustry::data::renderer::Renderer; -use plandustry::data::schematic::SchematicSerializer; +use mindus::build_registry; +use mindus::Renderer; +use mindus::SchematicSerializer; use std::env::Args; -use crate::args::{self, OptionHandler}; use crate::print_err; -pub fn main(mut args: Args, arg_off: usize) { - let mut handler = OptionHandler::default(); - if let Err(e) = args::parse(&mut args, &mut handler, arg_off) { - print_err!(e, "Command error"); - return; - } - +pub fn main(args: Args) { let reg = build_registry(); let mut ss = SchematicSerializer(®); let mut first = true; let mut need_space = false; // process schematics from command line - for curr in handler.get_literals() { - match ss.deserialize_base64(curr) { + for curr in args { + match ss.deserialize_base64(&curr) { Ok(s) => { if !first || need_space { println!(); diff --git a/src/exe/mod.rs b/src/exe/mod.rs index de92095..0159b96 100644 --- a/src/exe/mod.rs +++ b/src/exe/mod.rs @@ -1,4 +1,3 @@ -mod args; mod draw; mod print; @@ -29,8 +28,8 @@ fn main() { args.next().unwrap(); // path to executable match args.next() { None => eprintln!("Not enough arguments, valid commands are: draw, print"), - Some(s) if s == "print" => print::main(args, 1), - Some(s) if s == "draw" => draw::main(args, 1), + Some(s) if s == "print" => print::main(args), + Some(s) if s == "draw" => draw::main(args), Some(s) => eprintln!("Unknown argument {s}, valid commands are: draw, print"), } } diff --git a/src/exe/print.rs b/src/exe/print.rs index 525bf9a..25af8a4 100644 --- a/src/exe/print.rs +++ b/src/exe/print.rs @@ -1,85 +1,18 @@ -use std::borrow::Cow; use std::env::Args; -use std::fs; -use std::io::{self, Write}; -use plandustry::block::build_registry; -use plandustry::data::schematic::{Schematic, SchematicSerializer}; -use plandustry::data::{DataRead, Serializer}; +use mindus::build_registry; +use mindus::{Schematic, SchematicSerializer}; -use crate::args::{self, ArgCount, ArgOption, OptionHandler}; use crate::print_err; -pub fn main(mut args: Args, arg_off: usize) { - let mut handler = OptionHandler::default(); - let opt_file = handler - .add(ArgOption::new( - Some('f'), - Some(Cow::Borrowed("file")), - ArgCount::Required(usize::MAX), - )) - .unwrap(); - let opt_interact = handler - .add(ArgOption::new( - Some('i'), - Some(Cow::Borrowed("interactive")), - ArgCount::Forbidden, - )) - .unwrap(); - if let Err(e) = args::parse(&mut args, &mut handler, arg_off) { - print_err!(e, "Command error"); - return; - } - +pub fn main(args: Args) { let reg = build_registry(); let mut ss = SchematicSerializer(®); let mut first = true; let mut need_space = false; - // process the files if any - let file = match handler.get_value(opt_file).get_values() { - None => false, - Some(paths) => { - for path in paths { - match fs::read(path) { - Ok(data) => { - match ss.deserialize(&mut DataRead::new(&data)) { - Ok(s) => { - if !first || need_space { - println!(); - } - first = false; - need_space = true; - println!("Schematic: @{path}"); - print_schematic(&s); - } - // continue processing files, literals & maybe interactive mode - Err(e) => { - if need_space { - println!(); - } - first = false; - need_space = false; - print_err!(e, "Could not read schematic from {path}"); - } - } - } - // continue processing files, literals & maybe interactive mode - Err(e) => { - if need_space { - println!(); - } - first = false; - need_space = false; - print_err!(e, "Could not read file {path:?}"); - } - } - } - true - } - }; // process schematics from command line - for curr in handler.get_literals() { - match ss.deserialize_base64(curr) { + for curr in args { + match ss.deserialize_base64(&curr) { Ok(s) => { if !first || need_space { println!(); @@ -100,59 +33,6 @@ pub fn main(mut args: Args, arg_off: usize) { } } } - // if --interactive or no schematics: continue parsing from console - if handler.get_value(opt_interact).is_present() || (!file && handler.get_literals().is_empty()) - { - if need_space { - println!(); - } - need_space = false; - println!("Entering interactive mode, paste schematic to print details."); - let mut buff = String::new(); - let stdin = io::stdin(); - loop { - buff.clear(); - if need_space { - println!(); - } - need_space = false; - print!("> "); - if let Err(e) = io::stdout().flush() { - // what the print & println macros would do - panic!("failed printing to stdout: {e}"); - } - match stdin.read_line(&mut buff) { - Ok(..) => { - let data = buff.trim(); - if data.is_empty() { - break; - } - match ss.deserialize_base64(data) { - Ok(s) => { - println!(); - need_space = true; - print_schematic(&s) - } - // continue interactive mode, typos are especially likely here - Err(e) => { - if need_space { - println!(); - } - need_space = false; - print_err!(e, "Could not read schematic"); - } - } - } - Err(e) => { - if need_space { - println!(); - } - print_err!(e, "Failed to read next schematic"); - break; - } - } - } - } } pub fn print_schematic(s: &Schematic) { diff --git a/src/item/mod.rs b/src/item/mod.rs index 4b4bac0..6a4f8fc 100644 --- a/src/item/mod.rs +++ b/src/item/mod.rs @@ -1,3 +1,4 @@ +//! the different kinds of items use image::Rgb; pub mod storage; diff --git a/src/item/storage.rs b/src/item/storage.rs index e6ec368..590c399 100644 --- a/src/item/storage.rs +++ b/src/item/storage.rs @@ -6,6 +6,7 @@ use std::slice; use crate::item; #[derive(Clone, Debug, Eq)] +/// holds item counts pub struct Storage { base: Vec<u32>, total: u64, @@ -1,11 +1,16 @@ -pub mod access; +//! crate for dealing with mindustry +mod access; pub mod block; -pub mod content; +mod content; pub mod data; -pub mod fluid; +mod fluid; pub mod item; -pub mod logic; -pub mod modifier; -pub mod registry; -pub mod team; -pub mod unit; +mod logic; +mod modifier; +mod registry; +mod team; +mod unit; +pub use block::build_registry; +pub use data::dynamic::DynData; +pub use data::renderer::Renderer; +pub use data::schematic::{Schematic, SchematicSerializer}; diff --git a/src/modifier.rs b/src/modifier.rs index 7e86d47..0459ce2 100644 --- a/src/modifier.rs +++ b/src/modifier.rs @@ -1,3 +1,4 @@ +//! modifiers units can be afflicted with use crate::content::content_enum; content_enum! { diff --git a/src/registry.rs b/src/registry.rs index 938836a..9603f59 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -28,7 +28,8 @@ impl<'l, E: RegistryEntry + fmt::Debug + 'static> Registry<'l, E> { } } - #[must_use] pub fn get(&self, name: &str) -> Option<&'l E> { + #[must_use] + pub fn get(&self, name: &str) -> Option<&'l E> { self.by_name.get(name).copied() } } diff --git a/src/team.rs b/src/team.rs index 1c0e4f1..f431bd2 100644 --- a/src/team.rs +++ b/src/team.rs @@ -7,11 +7,13 @@ use crate::content::{Content, Type}; pub struct Team(u8); impl Team { - #[must_use] pub fn of(id: u8) -> Self { + #[must_use] + pub fn of(id: u8) -> Self { Self(id) } - #[must_use] pub fn is_base(&self) -> bool { + #[must_use] + pub fn is_base(&self) -> bool { self.0 < 6 } } @@ -110,9 +112,15 @@ impl Content for Team { } } +#[allow(dead_code)] pub const DERELICT: Team = Team(0); +#[allow(dead_code)] pub const SHARDED: Team = Team(1); +#[allow(dead_code)] pub const CRUX: Team = Team(2); +#[allow(dead_code)] pub const MALIS: Team = Team(3); +#[allow(dead_code)] pub const GREEN: Team = Team(4); +#[allow(dead_code)] pub const BLUE: Team = Team(5); diff --git a/strconv/Cargo.toml b/strconv/Cargo.toml deleted file mode 100644 index 29b6981..0000000 --- a/strconv/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "strconv" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -proc-macro = true - -[dependencies] -syn = "2.0.15" diff --git a/strconv/src/lib.rs b/strconv/src/lib.rs deleted file mode 100644 index 4b9d626..0000000 --- a/strconv/src/lib.rs +++ /dev/null @@ -1,29 +0,0 @@ -use proc_macro::TokenStream; -use syn::{parse_macro_input, LitStr}; - -#[proc_macro] -pub fn kebab2title(input: TokenStream) -> TokenStream { - let input_str = parse_macro_input!(input as LitStr).value(); - - let converted = kebab2title_impl(&input_str); - format!("\"{converted}\"").parse().unwrap() -} - -fn kebab2title_impl(data: &str) -> String { - let mut result = String::with_capacity(data.len()); - let mut capitalize_next = true; - - for c in data.chars() { - if c == '-' { - result.push(' '); - capitalize_next = true; - } else if capitalize_next { - result.push(c.to_ascii_uppercase()); - capitalize_next = false; - } else { - result.push(c); - } - } - - result -} |