mindustry logic execution, map- and schematic- parsing and rendering
-rw-r--r--Cargo.toml2
-rw-r--r--src/block/logic.rs139
-rw-r--r--src/block/mod.rs6
-rw-r--r--src/data/renderer.rs47
-rw-r--r--src/data/schematic.rs1
-rw-r--r--src/exe/draw.rs10
6 files changed, 170 insertions, 35 deletions
diff --git a/Cargo.toml b/Cargo.toml
index a4de119..9245bcb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "mindus"
-version = "1.1.3"
+version = "1.1.5"
edition = "2021"
description = "A library for working with mindustry data formats (eg schematics) (fork of plandustry)"
authors = [
diff --git a/src/block/logic.rs b/src/block/logic.rs
index 79941c4..8a6b447 100644
--- a/src/block/logic.rs
+++ b/src/block/logic.rs
@@ -2,9 +2,12 @@
use std::borrow::Cow;
use std::string::FromUtf8Error;
+use image::{Rgb, RgbImage};
+
use crate::block::simple::*;
use crate::block::*;
use crate::data::dynamic::DynType;
+
use crate::data::{self, CompressError, DataRead, DataWrite};
make_simple!(LogicBlock);
@@ -20,13 +23,147 @@ make_register! {
"memory-bank" => LogicBlock::new(2, true, cost!(Copper: 30, Graphite: 80, Silicon: 80, PhaseFabric: 30));
"logic-display" => LogicBlock::new(3, true, cost!(Lead: 100, Metaglass: 50, Silicon: 50));
"large-logic-display" => LogicBlock::new(6, true, cost!(Lead: 200, Metaglass: 100, Silicon: 150, PhaseFabric: 75));
- // todo canvas (cost!(Silicon: 30, Beryllium: 10))
+ "canvas" => CanvasBlock::new(2, true, cost!(Silicon: 30, Beryllium: 10), 12);
// editor only
"world-processor" => LogicBlock::new(1, true, &[]);
"world-message" => MessageLogic::new(1, true, &[]);
"world-cell" => LogicBlock::new(1, true, &[]);
}
+pub struct CanvasBlock {
+ size: u8,
+ symmetric: bool,
+ build_cost: BuildCost,
+ canvas_size: u8,
+}
+
+macro_rules! h {
+ ($x:literal) => {
+ Rgb(color_hex::color_from_hex!($x))
+ };
+}
+const PALETTE: &[Rgb<u8>; 8] = &[
+ h!("#362944"),
+ h!("#c45d9f"),
+ h!("#e39aac"),
+ h!("#f0dab1"),
+ h!("#6461c2"),
+ h!("#2ba9b4"),
+ h!("#93d4b5"),
+ h!("#f0f6e8"),
+];
+
+impl CanvasBlock {
+ #[must_use]
+ pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost, canvas_size: u8) -> Self {
+ assert!(size != 0, "invalid size");
+ assert!(canvas_size != 0, "invalid size");
+ Self {
+ canvas_size,
+ size,
+ symmetric,
+ build_cost,
+ }
+ }
+
+ state_impl!(pub RgbImage);
+}
+
+impl BlockLogic for CanvasBlock {
+ impl_block!();
+
+ fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> {
+ Ok(DynData::Empty)
+ }
+
+ fn deserialize_state(&self, data: DynData) -> Result<Option<State>, DeserializeError> {
+ match data {
+ DynData::ByteArray(b) => {
+ let mut p = RgbImage::new(self.canvas_size as u32, self.canvas_size as u32);
+ for i in 0..(self.canvas_size * self.canvas_size) as usize {
+ let offset = i * 3;
+ let mut n = 0;
+ for i in 0..3 {
+ let word = (i + offset) >> 3;
+ n |= (((b[word] & (1 << ((i + offset) & 7))) != 0) as u8) << i;
+ }
+ p.put_pixel(
+ i as u32 % self.canvas_size as u32,
+ i as u32 / self.canvas_size as u32,
+ PALETTE[n as usize],
+ )
+ }
+ Ok(Some(Self::create_state(p)))
+ }
+ _ => Err(DeserializeError::InvalidType {
+ have: data.get_type(),
+ expect: DynType::String,
+ }),
+ }
+ }
+
+ fn clone_state(&self, state: &State) -> State {
+ Box::new(Self::get_state(state).clone())
+ }
+
+ fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError> {
+ let mut o = vec![0; self.canvas_size as usize * self.canvas_size as usize * 3];
+ let p = Self::get_state(state);
+ for i in 0..(self.canvas_size * self.canvas_size) as usize {
+ let color = p.get_pixel(
+ i as u32 % self.canvas_size as u32,
+ i as u32 / self.canvas_size as u32,
+ );
+ let index = PALETTE.iter().position(|v| v == color).unwrap();
+ let offset = i * 3;
+ for i in 0..3 {
+ let word = (i + offset) >> 3;
+ if index >> i & 1 == 0 {
+ o[word] &= !(1 << ((i + offset) & 7));
+ } else {
+ o[word] |= 1 << ((i + offset) & 7);
+ }
+ }
+ }
+ Ok(DynData::ByteArray(o))
+ }
+
+ /// i thought about drawing the borders and stuff but it felt like too much work
+ fn draw(
+ &self,
+ c: &str,
+ n: &str,
+ state: Option<&State>,
+ _: Option<&RenderingContext>,
+ ) -> Option<ImageHolder> {
+ if let Some(state) = state {
+ let state = self.clone_state(state);
+ let p = state.downcast::<RgbImage>().unwrap();
+ // SAFETY: canvas_size cannot be 0, so width & height musnt be 0, and size cannot be 0
+ let p = unsafe {
+ DynamicImage::from(
+ RgbImage::from_raw(
+ self.canvas_size as u32,
+ self.canvas_size as u32,
+ p.into_raw(),
+ )
+ .unwrap(),
+ )
+ .into_rgba8()
+ .scale((self.size as u32 * 32) - 14)
+ };
+ let mut borders = load(c, n).unwrap().to_owned();
+ borders.overlay(&p, 7, 7);
+ return Some(ImageHolder::from(borders));
+ }
+
+ Some(ImageHolder::from(RgbaImage::new(
+ self.size as u32 * 32,
+ self.size as u32 * 32,
+ )))
+ }
+}
+
pub struct MessageLogic {
size: u8,
symmetric: bool,
diff --git a/src/block/mod.rs b/src/block/mod.rs
index 5b5936e..0afe8f6 100644
--- a/src/block/mod.rs
+++ b/src/block/mod.rs
@@ -47,9 +47,11 @@ pub trait BlockLogic {
fn clone_state(&self, state: &State) -> State;
- fn mirror_state(&self, state: &mut State, horizontally: bool, vertically: bool);
+ #[allow(unused_variables)]
+ fn mirror_state(&self, state: &mut State, horizontally: bool, vertically: bool) {}
- fn rotate_state(&self, state: &mut State, clockwise: bool);
+ #[allow(unused_variables)]
+ fn rotate_state(&self, state: &mut State, clockwise: bool) {}
fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError>;
diff --git a/src/data/renderer.rs b/src/data/renderer.rs
index 7f280b3..cb6d88d 100644
--- a/src/data/renderer.rs
+++ b/src/data/renderer.rs
@@ -411,8 +411,13 @@ impl Renderable for Schematic<'_> {
impl Renderable for Map<'_> {
fn render(&self) -> RgbaImage {
load_zip();
- let mut floor = RgbaImage::new(self.width as u32 * 8, self.height as u32 * 8);
- let mut top = RgbaImage::new(self.width as u32 * 8, self.height as u32 * 8);
+ let scale = if self.width + self.height < 2000 {
+ 8
+ } else {
+ 4
+ };
+ let mut floor = RgbaImage::new(self.width as u32 * scale, self.height as u32 * scale);
+ let mut top = RgbaImage::new(self.width as u32 * scale, self.height as u32 * scale);
for (x, y, j, tile) in self.tiles.iter().enumerate().map(|(j, t)| {
(
(j % self.width),
@@ -425,9 +430,9 @@ impl Renderable for Map<'_> {
if tile.build().is_none() {
floor.overlay(
// SAFETY: [`load_raw`] forces nonzero image size
- unsafe { &tile.image(None).own().scale(8) },
- x as u32 * 8,
- y as u32 * 8,
+ unsafe { &tile.image(None).own().scale(scale) },
+ x as u32 * scale,
+ y as u32 * scale,
);
} else {
let s = if let Some(build) = &tile.build() {
@@ -456,9 +461,14 @@ impl Renderable for Map<'_> {
})();
top.overlay(
// SAFETY: tile.size can never be 0, and [`load_raw`] forces nonzero.
- unsafe { &tile.image(ctx.as_ref()).own().scale(tile.size() as u32 * 8) },
- x as u32 * 8,
- y as u32 * 8,
+ unsafe {
+ &tile
+ .image(ctx.as_ref())
+ .own()
+ .scale(tile.size() as u32 * scale)
+ },
+ x as u32 * scale,
+ y as u32 * scale,
);
}
}
@@ -475,19 +485,14 @@ fn all_blocks() {
let reg = crate::block::build_registry();
for t in 19..Type::WorldMessage as u16 {
let t = Type::try_from(t).unwrap();
- if matches!(
- t,
- // TODO canvas
- Type::Canvas
- | Type::Empty
- | Type::SlagCentrifuge
- | Type::HeatReactor
- | Type::LegacyMechPad
- | Type::LegacyUnitFactory
- | Type::LegacyUnitFactoryAir
- | Type::LegacyUnitFactoryGround
- | Type::CommandCenter
- ) {
+ if matches!(t, |Type::Empty| Type::SlagCentrifuge
+ | Type::HeatReactor
+ | Type::LegacyMechPad
+ | Type::LegacyUnitFactory
+ | Type::LegacyUnitFactoryAir
+ | Type::LegacyUnitFactoryGround
+ | Type::CommandCenter)
+ {
continue;
}
diff --git a/src/data/schematic.rs b/src/data/schematic.rs
index c6648bd..5be24b5 100644
--- a/src/data/schematic.rs
+++ b/src/data/schematic.rs
@@ -747,6 +747,7 @@ mod test {
"bXNjaAF4nD1TDUxTVxS+r6+vr30triCSVjLXiulKoAjMrJRkU8b4qSgLUAlIZ1rah7yt9L31h1LMMCNbRAQhYrKwOnEslhCcdmzJuohL1DjZT4FJtiVsoG5LFtzmGgGngHm790mam7x77ne+c945934HKIAcB2K3vYUGScXmWvM+TQ3jYhysG8idtNfhYTgfAw8ASFz2RtrlBaKG12VA6X1KMjg8fgfT6KLBJi7osfsYH21oYdpoD6A4NkB7DG7WSQOJl/X4IPYM426loeU0bABSv9vF2p3I1cI4PKyB87AO2gu9gGi1+10+kMTCiCYXGzActvtoWEY+ABhcIgzaOBCJ4EZICYDx6xAV86vCdx2IAS5QJJAEIRkQ4XAjAHSIIITBUCCGRwIuESCgheEIkwgYIpEAF4I3wSw9bWccTpvNmVkZy5raWT1p3r+vajJ2odyQb+HAW9HxvV556vfvpNy4oVUfDyq36Kyqe73xsdemprMyv52uAreYwcXzJaPU+aDp8fFM24nuzUvVqYo9yr7CjFT/aDDzUUz8S8W7g+X3VCpVnargblNubl4kI1q6J+cFPH2HS6VSF5xzZWhCyYCKO2FjqAEprB9WRsJbwNFFoLKhITRCQheBbByQCMAQQwow1I8M9oPJ2870npqvvq5RvvfFyYE3hjrLmst3TixrV0XSN08Uax/UrMSeHdmKDdj8uhh3Pef2Wa+qDljrj82pK+aM300sl0eTrC/rL3zzZKZhRWFMq+mLvvTZb0bbweGZL/85ywwnl4RLzR9MBdIGy0LJowOWHxoOY2EiaJ/7s7ZP0Tg2wjWb3y6Lm3IPRNonw/0yT/+lZsdFy/LmUEp2RojHl68B41zDx43WJ/qANkwdVOvGtxjzpgo/keUURn2XK6zerz9Km10w3Vb8Ww/t/UdmHyx7fXwEcPiP0w1Xx9f+/m/X/d13Wiees8yPnk69ePlS9Yuf9sQf1dvVB27mm68U+51Fj7emzS+mzw1jzwuvTKFXHoK30l9EXctVlozIiSPdpk5djrW965BmV1XW4qsp8kNXmtWztdklXXTa0u6lO0d1+GS3TV/Q95O+17+S23Hs5sIfP4e/uqvd9oo+p7u0cYiPb4+9f/L+Qn3PmuXDdDai/ev0ts69I9nuNTOXp9HfOmoy/a5Y9D2cYYsebq+cKgB1V9vXdYFfOz7vWiVCLNnUUVkLOGO9umVN0jl2KoIjYSINEzgUORoDBKAnJwSLTLikQOBSAoC0ABBAbMgDWYIuBBeFRE7CbBCXCAwxFBAJPRgCSAFADBlykokcZCKHFAkPbSRKRaFUUsRGUyZLTJksMWWyjSlDJKhfFALZmFAJdFPo1+gkQVKXw/EW8/zToeZ5fh0t/H+V6k8+";
"bXNjaAF4nGNgZ2BjZmDJS8xNZRByrkzOyc9LdctJLEpVT1F4v3AaA3dKanFyUWZBSWZ+HgMDA1tOYlJqTjEDU3QsFwN/eWJJapFuakVJUWJySX4RA3tSYglQpJKBPRliEgNXQX45UElefkoqA39SUWZKeqpucn5eWWolSDmQlVKaWcIgkpyfm1RaDLJDNz01L7UoEWQaf3JRZX5aTmlmim5uZkUqUCA3M7koX7egKD85tbgYqIIlIzEzB+gqQQYwYGYEEkwgxMjAAuQCKSYOZgam//8ZWP7/+/+HgZGZBSTPDlEGVMEKVssAooAMNqAWBpA0SCdQKTMDMzvEZAZGRhCXBZXLyv7///8cIC4AKgZaCOKGAHEh0DBWBpAKIAW0hQNkAR9Q16+KOXtDbhfNNhBQneu5XNV+o/0FSYFCtbrHC+dm3v/MnK3TnKUYGCv0+X3uygksNwzr3jbr755T/O3NuiOxk+7YX7lSoNb3oW3CUq7vKxq4bn1rUKqJsqldfsLX2KkoICQ679L8bW8fCLaY3K+LfGLIe6hoXlaW3iMvrsUq7Hc9Mq1W/OrydlRf+VK9+Ov1iSmsK1deCPKVPF69dG+I5RQ3qSf2PLmlH2bkLwgT4Q3V5+3qnBDPqcG1dNuqZfETim+6G0UqT9b5bGsUznqqb7GZxoxc5eUMT/JvvC79UdlruPvxJis9XhbeTZXLN+68WFB41+DkNRv7uZEGOr/2rvPPu8ZfyXRLI+zoUnmLXRu3+nz0EnP1Omq39TLX3c23cleZ8F62FSnMVCviO2H6tWXB7z2LpA02vleTOXiJK20BT+ADsencfQd0tlqrfQuoWut5dHaX1OP/KwIY5r66Zp4T9+2p241L0XvPfu5w/Zu3bNX77V89kt42zOLf9jJ9vk+msV1vy/Knlywv7Lh4NEY7fvHay0t3Sxo+2q918+je/O23P+50/qEWe45XqGzaR1vh0h1idRwBYZter2DKPJv559VDhbXSHzgin2x8PeXIKsvmtRIVB5V5b/1zcBP+f7VpfuP1OLcJKiePP7n8paroYrul0uF88dp5619+MN8Z7WT0p7DTUqftYOt3XqN51hGf+IVDH0UwcDKwAZMFMwCWiVNe";
"bXNjaAF4nGNgZGBkZmDJS8xNZeBMrShIzSvOLEtl4E5JLU4uyiwoyczPY2BgYMtJTErNKWZgio5lZODPzUwuytctKMpPTi0uzi8CyjMygAAfA4PQ+Yo5by9u9GxmZGB9GME502nTzKW+Aht/FJq1ez+o8nzYGn5n+wHR70VVf23t9tutu58/Xbm+qr5t/v+PAa93zIn+L1BpFbXfY17fNf1Jyxd/7X7yMuOv0qjQqNCo0KjQqNCo0KjQqNCo0KjQqNCo0KjQqNCo0KjQqNCoEJWFHp987V9uXjv/9y4GAOhu6pc=";
+ "bXNjaAF4nGNgY2BjZmDJS8xNZWBLTswrSyxm4E5JLU4uyiwoyczPY2BgYMtJTErNKWZgio5lhKthYOBkAAE+IDZjIB8wUWoAC2UGMFHqBSaoF1QYGTycJjFMUFHxVPBkmpQyiYXhpAonQ4OnEAPDJBVWBhXPW0wek7bkTlRhvLXNk4khdzYLQ8M2sAEUeoGFUi+wUBoLLJR5AQDzuCAp";
}
#[test]
diff --git a/src/exe/draw.rs b/src/exe/draw.rs
index 1b7830c..3352af0 100644
--- a/src/exe/draw.rs
+++ b/src/exe/draw.rs
@@ -8,25 +8,15 @@ use crate::print_err;
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 args {
match ss.deserialize_base64(&curr) {
Ok(s) => {
- if !first || need_space {
- println!();
- }
s.render().save("x.png").unwrap();
}
// continue processing literals & maybe interactive mode
Err(e) => {
- if need_space {
- println!();
- }
- first = false;
- need_space = false;
print_err!(e, "Could not read schematic");
}
}