mindustry logic execution, map- and schematic- parsing and rendering
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/block/logic.rs | 139 | ||||
| -rw-r--r-- | src/block/mod.rs | 6 | ||||
| -rw-r--r-- | src/data/renderer.rs | 47 | ||||
| -rw-r--r-- | src/data/schematic.rs | 1 | ||||
| -rw-r--r-- | src/exe/draw.rs | 10 |
6 files changed, 170 insertions, 35 deletions
@@ -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(®); - 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"); } } |