mindustry logic execution, map- and schematic- parsing and rendering
bendn 2023-06-25
parent d928daa · commit f9253eb
-rw-r--r--.github/example.pngbin0 -> 787 bytes
-rw-r--r--Cargo.toml13
-rw-r--r--README.md46
-rw-r--r--src/access.rs3
-rw-r--r--src/block/base.rs1
-rw-r--r--src/block/content.rs1
-rw-r--r--src/block/defense.rs1
-rw-r--r--src/block/distribution.rs11
-rw-r--r--src/block/drills.rs1
-rw-r--r--src/block/liquid.rs1
-rw-r--r--src/block/logic.rs2
-rw-r--r--src/block/mod.rs48
-rw-r--r--src/block/payload.rs1
-rw-r--r--src/block/power.rs1
-rw-r--r--src/block/production.rs6
-rw-r--r--src/block/simple.rs4
-rw-r--r--src/block/storage.rs1
-rw-r--r--src/block/turrets.rs1
-rw-r--r--src/content.rs1
-rw-r--r--src/data/base64.rs4
-rw-r--r--src/data/dynamic.rs2
-rw-r--r--src/data/mod.rs7
-rw-r--r--src/data/renderer.rs16
-rw-r--r--src/data/schematic.rs117
-rw-r--r--src/exe/args.rs433
-rw-r--r--src/exe/draw.rs19
-rw-r--r--src/exe/mod.rs5
-rw-r--r--src/exe/print.rs130
-rw-r--r--src/item/mod.rs1
-rw-r--r--src/item/storage.rs1
-rw-r--r--src/lib.rs21
-rw-r--r--src/modifier.rs1
-rw-r--r--src/registry.rs3
-rw-r--r--src/team.rs12
-rw-r--r--strconv/Cargo.toml12
-rw-r--r--strconv/src/lib.rs29
36 files changed, 255 insertions, 701 deletions
diff --git a/.github/example.png b/.github/example.png
new file mode 100644
index 0000000..41d900d
--- /dev/null
+++ b/.github/example.png
Binary files differ
diff --git a/Cargo.toml b/Cargo.toml
index d7269c0..c440e2a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/README.md b/README.md
index a7fd38c..37debda 100644
--- a/README.md
+++ b/README.md
@@ -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(&reg);
+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.
+![image](https://raw.githubusercontent.com/bend-n/mindus/master/.github/example.png) \ 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(&reg);
+ /// 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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",
- "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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(&reg);
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(&reg);
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,
diff --git a/src/lib.rs b/src/lib.rs
index 300937d..64bdae6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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
-}