mindustry logic execution, map- and schematic- parsing and rendering
map deserialization (#2)
100 files changed, 1528 insertions, 679 deletions
@@ -1,6 +1,6 @@ [package] name = "mindus" -version = "1.0.8" +version = "1.1.0" edition = "2021" description = "A library for working with mindustry data formats (eg schematics) (fork of plandustry)" authors = [ @@ -21,6 +21,12 @@ image = { version = "0.24.6", features = ["png"], default-features = false } const-str = "0.5.5" color-hex = "0.2.0" zip = { version = "0.6.6", features = ["zstd"], default-features = false } +tinyrand = "0.5.0" +seq-macro = "0.3.4" +tinyrand-std = "0.5.0" +dashmap = "5.4.0" +fast_image_resize = "2.7.3" +thiserror = "1.0.41" [build-dependencies] zip = { version = "0.6.6", features = ["zstd"], default-features = false } @@ -34,3 +40,4 @@ path = "src/exe/mod.rs" debug = 2 opt-level = 3 lto = true +incremental = true diff --git a/assets/blocks/environment/arkyic-boulder1.png b/assets/blocks/environment/arkyic-boulder1.png Binary files differnew file mode 100644 index 0000000..bec5753 --- /dev/null +++ b/assets/blocks/environment/arkyic-boulder1.png diff --git a/assets/blocks/environment/arkyic-boulder2.png b/assets/blocks/environment/arkyic-boulder2.png Binary files differnew file mode 100644 index 0000000..a9ecf43 --- /dev/null +++ b/assets/blocks/environment/arkyic-boulder2.png diff --git a/assets/blocks/environment/arkyic-boulder3.png b/assets/blocks/environment/arkyic-boulder3.png Binary files differnew file mode 100644 index 0000000..9e9f5cc --- /dev/null +++ b/assets/blocks/environment/arkyic-boulder3.png diff --git a/assets/blocks/environment/basalt-boulder1.png b/assets/blocks/environment/basalt-boulder1.png Binary files differnew file mode 100644 index 0000000..f88209e --- /dev/null +++ b/assets/blocks/environment/basalt-boulder1.png diff --git a/assets/blocks/environment/basalt-boulder2.png b/assets/blocks/environment/basalt-boulder2.png Binary files differnew file mode 100644 index 0000000..7c19c92 --- /dev/null +++ b/assets/blocks/environment/basalt-boulder2.png diff --git a/assets/blocks/environment/beryllic-boulder1.png b/assets/blocks/environment/beryllic-boulder1.png Binary files differnew file mode 100644 index 0000000..275c90b --- /dev/null +++ b/assets/blocks/environment/beryllic-boulder1.png diff --git a/assets/blocks/environment/beryllic-boulder2.png b/assets/blocks/environment/beryllic-boulder2.png Binary files differnew file mode 100644 index 0000000..f5d2444 --- /dev/null +++ b/assets/blocks/environment/beryllic-boulder2.png diff --git a/assets/blocks/environment/boulder1.png b/assets/blocks/environment/boulder1.png Binary files differnew file mode 100644 index 0000000..ae54a00 --- /dev/null +++ b/assets/blocks/environment/boulder1.png diff --git a/assets/blocks/environment/boulder2.png b/assets/blocks/environment/boulder2.png Binary files differnew file mode 100644 index 0000000..6e6e310 --- /dev/null +++ b/assets/blocks/environment/boulder2.png diff --git a/assets/blocks/environment/carbon-boulder1.png b/assets/blocks/environment/carbon-boulder1.png Binary files differnew file mode 100644 index 0000000..6efee1f --- /dev/null +++ b/assets/blocks/environment/carbon-boulder1.png diff --git a/assets/blocks/environment/carbon-boulder2.png b/assets/blocks/environment/carbon-boulder2.png Binary files differnew file mode 100644 index 0000000..f717be1 --- /dev/null +++ b/assets/blocks/environment/carbon-boulder2.png diff --git a/assets/blocks/environment/crystal-blocks1.png b/assets/blocks/environment/crystal-blocks1.png Binary files differnew file mode 100644 index 0000000..f49567c --- /dev/null +++ b/assets/blocks/environment/crystal-blocks1.png diff --git a/assets/blocks/environment/crystal-blocks2.png b/assets/blocks/environment/crystal-blocks2.png Binary files differnew file mode 100644 index 0000000..3a6dea7 --- /dev/null +++ b/assets/blocks/environment/crystal-blocks2.png diff --git a/assets/blocks/environment/crystal-blocks3.png b/assets/blocks/environment/crystal-blocks3.png Binary files differnew file mode 100644 index 0000000..95b2e66 --- /dev/null +++ b/assets/blocks/environment/crystal-blocks3.png diff --git a/assets/blocks/environment/crystal-cluster1.png b/assets/blocks/environment/crystal-cluster1.png Binary files differnew file mode 100644 index 0000000..5adf9a9 --- /dev/null +++ b/assets/blocks/environment/crystal-cluster1.png diff --git a/assets/blocks/environment/crystal-cluster2.png b/assets/blocks/environment/crystal-cluster2.png Binary files differnew file mode 100644 index 0000000..40a0353 --- /dev/null +++ b/assets/blocks/environment/crystal-cluster2.png diff --git a/assets/blocks/environment/crystal-cluster3.png b/assets/blocks/environment/crystal-cluster3.png Binary files differnew file mode 100644 index 0000000..d9ba7e9 --- /dev/null +++ b/assets/blocks/environment/crystal-cluster3.png diff --git a/assets/blocks/environment/crystal-orbs1.png b/assets/blocks/environment/crystal-orbs1.png Binary files differnew file mode 100644 index 0000000..21bd356 --- /dev/null +++ b/assets/blocks/environment/crystal-orbs1.png diff --git a/assets/blocks/environment/crystal-orbs2.png b/assets/blocks/environment/crystal-orbs2.png Binary files differnew file mode 100644 index 0000000..04a98d7 --- /dev/null +++ b/assets/blocks/environment/crystal-orbs2.png diff --git a/assets/blocks/environment/crystal-orbs3.png b/assets/blocks/environment/crystal-orbs3.png Binary files differnew file mode 100644 index 0000000..bad75c0 --- /dev/null +++ b/assets/blocks/environment/crystal-orbs3.png diff --git a/assets/blocks/environment/crystalline-boulder1.png b/assets/blocks/environment/crystalline-boulder1.png Binary files differnew file mode 100644 index 0000000..95d583a --- /dev/null +++ b/assets/blocks/environment/crystalline-boulder1.png diff --git a/assets/blocks/environment/crystalline-boulder2.png b/assets/blocks/environment/crystalline-boulder2.png Binary files differnew file mode 100644 index 0000000..d94d92b --- /dev/null +++ b/assets/blocks/environment/crystalline-boulder2.png diff --git a/assets/blocks/environment/dacite-boulder1.png b/assets/blocks/environment/dacite-boulder1.png Binary files differnew file mode 100644 index 0000000..ffddeef --- /dev/null +++ b/assets/blocks/environment/dacite-boulder1.png diff --git a/assets/blocks/environment/dacite-boulder2.png b/assets/blocks/environment/dacite-boulder2.png Binary files differnew file mode 100644 index 0000000..aa25894 --- /dev/null +++ b/assets/blocks/environment/dacite-boulder2.png diff --git a/assets/blocks/environment/ferric-boulder1.png b/assets/blocks/environment/ferric-boulder1.png Binary files differnew file mode 100644 index 0000000..63685cb --- /dev/null +++ b/assets/blocks/environment/ferric-boulder1.png diff --git a/assets/blocks/environment/ferric-boulder2.png b/assets/blocks/environment/ferric-boulder2.png Binary files differnew file mode 100644 index 0000000..93e19a3 --- /dev/null +++ b/assets/blocks/environment/ferric-boulder2.png diff --git a/assets/blocks/environment/glowblob1.png b/assets/blocks/environment/glowblob1.png Binary files differdeleted file mode 100644 index 404c2bd..0000000 --- a/assets/blocks/environment/glowblob1.png +++ /dev/null diff --git a/assets/blocks/environment/pack.json b/assets/blocks/environment/pack.json deleted file mode 100644 index 8a0d47c..0000000 --- a/assets/blocks/environment/pack.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - duplicatePadding: true, - combineSubdirectories: true, - flattenPaths: true, - maxWidth: 2048, - maxHeight: 2048, - fast: true, - stripWhitespaceCenter: false -} diff --git a/assets/blocks/environment/pur-bush-bot.png b/assets/blocks/environment/pur-bush-bot.png Binary files differnew file mode 100644 index 0000000..5704eb7 --- /dev/null +++ b/assets/blocks/environment/pur-bush-bot.png diff --git a/assets/blocks/environment/pur-bush.png b/assets/blocks/environment/pur-bush.png Binary files differnew file mode 100644 index 0000000..98b5faf --- /dev/null +++ b/assets/blocks/environment/pur-bush.png diff --git a/assets/blocks/environment/red-ice-boulder1.png b/assets/blocks/environment/red-ice-boulder1.png Binary files differnew file mode 100644 index 0000000..6a37193 --- /dev/null +++ b/assets/blocks/environment/red-ice-boulder1.png diff --git a/assets/blocks/environment/red-ice-boulder2.png b/assets/blocks/environment/red-ice-boulder2.png Binary files differnew file mode 100644 index 0000000..6491366 --- /dev/null +++ b/assets/blocks/environment/red-ice-boulder2.png diff --git a/assets/blocks/environment/red-ice-boulder3.png b/assets/blocks/environment/red-ice-boulder3.png Binary files differnew file mode 100644 index 0000000..ebb2cb6 --- /dev/null +++ b/assets/blocks/environment/red-ice-boulder3.png diff --git a/assets/blocks/environment/red-stone-boulder1.png b/assets/blocks/environment/red-stone-boulder1.png Binary files differnew file mode 100644 index 0000000..27fdd14 --- /dev/null +++ b/assets/blocks/environment/red-stone-boulder1.png diff --git a/assets/blocks/environment/red-stone-boulder2.png b/assets/blocks/environment/red-stone-boulder2.png Binary files differnew file mode 100644 index 0000000..40d7a7b --- /dev/null +++ b/assets/blocks/environment/red-stone-boulder2.png diff --git a/assets/blocks/environment/red-stone-boulder3.png b/assets/blocks/environment/red-stone-boulder3.png Binary files differnew file mode 100644 index 0000000..e85c09e --- /dev/null +++ b/assets/blocks/environment/red-stone-boulder3.png diff --git a/assets/blocks/environment/red-stone-boulder4.png b/assets/blocks/environment/red-stone-boulder4.png Binary files differnew file mode 100644 index 0000000..9cf450c --- /dev/null +++ b/assets/blocks/environment/red-stone-boulder4.png diff --git a/assets/blocks/environment/redweed1.png b/assets/blocks/environment/redweed1.png Binary files differnew file mode 100644 index 0000000..2953862 --- /dev/null +++ b/assets/blocks/environment/redweed1.png diff --git a/assets/blocks/environment/redweed2.png b/assets/blocks/environment/redweed2.png Binary files differnew file mode 100644 index 0000000..30243ff --- /dev/null +++ b/assets/blocks/environment/redweed2.png diff --git a/assets/blocks/environment/redweed3.png b/assets/blocks/environment/redweed3.png Binary files differnew file mode 100644 index 0000000..a8bec50 --- /dev/null +++ b/assets/blocks/environment/redweed3.png diff --git a/assets/blocks/environment/rhyolite-boulder1.png b/assets/blocks/environment/rhyolite-boulder1.png Binary files differnew file mode 100644 index 0000000..888aeed --- /dev/null +++ b/assets/blocks/environment/rhyolite-boulder1.png diff --git a/assets/blocks/environment/rhyolite-boulder2.png b/assets/blocks/environment/rhyolite-boulder2.png Binary files differnew file mode 100644 index 0000000..7b6f910 --- /dev/null +++ b/assets/blocks/environment/rhyolite-boulder2.png diff --git a/assets/blocks/environment/rhyolite-boulder3.png b/assets/blocks/environment/rhyolite-boulder3.png Binary files differnew file mode 100644 index 0000000..25059fd --- /dev/null +++ b/assets/blocks/environment/rhyolite-boulder3.png diff --git a/assets/blocks/environment/sand-boulder1.png b/assets/blocks/environment/sand-boulder1.png Binary files differnew file mode 100644 index 0000000..4c2cbd9 --- /dev/null +++ b/assets/blocks/environment/sand-boulder1.png diff --git a/assets/blocks/environment/sand-boulder2.png b/assets/blocks/environment/sand-boulder2.png Binary files differnew file mode 100644 index 0000000..902a0d7 --- /dev/null +++ b/assets/blocks/environment/sand-boulder2.png diff --git a/assets/blocks/environment/shale-boulder1.png b/assets/blocks/environment/shale-boulder1.png Binary files differnew file mode 100644 index 0000000..6d280cc --- /dev/null +++ b/assets/blocks/environment/shale-boulder1.png diff --git a/assets/blocks/environment/shale-boulder2.png b/assets/blocks/environment/shale-boulder2.png Binary files differnew file mode 100644 index 0000000..5ba4033 --- /dev/null +++ b/assets/blocks/environment/shale-boulder2.png diff --git a/assets/blocks/environment/snow-boulder1.png b/assets/blocks/environment/snow-boulder1.png Binary files differnew file mode 100644 index 0000000..c7feac2 --- /dev/null +++ b/assets/blocks/environment/snow-boulder1.png diff --git a/assets/blocks/environment/snow-boulder2.png b/assets/blocks/environment/snow-boulder2.png Binary files differnew file mode 100644 index 0000000..5f148d7 --- /dev/null +++ b/assets/blocks/environment/snow-boulder2.png diff --git a/assets/blocks/environment/spore-cluster1.png b/assets/blocks/environment/spore-cluster1.png Binary files differnew file mode 100644 index 0000000..50475fa --- /dev/null +++ b/assets/blocks/environment/spore-cluster1.png diff --git a/assets/blocks/environment/spore-cluster2.png b/assets/blocks/environment/spore-cluster2.png Binary files differnew file mode 100644 index 0000000..4d19d2d --- /dev/null +++ b/assets/blocks/environment/spore-cluster2.png diff --git a/assets/blocks/environment/spore-cluster3.png b/assets/blocks/environment/spore-cluster3.png Binary files differnew file mode 100644 index 0000000..3802836 --- /dev/null +++ b/assets/blocks/environment/spore-cluster3.png diff --git a/assets/blocks/environment/tendrils1.png b/assets/blocks/environment/tendrils1.png Binary files differdeleted file mode 100644 index 90345f2..0000000 --- a/assets/blocks/environment/tendrils1.png +++ /dev/null diff --git a/assets/blocks/environment/tendrils2.png b/assets/blocks/environment/tendrils2.png Binary files differdeleted file mode 100644 index b234cc4..0000000 --- a/assets/blocks/environment/tendrils2.png +++ /dev/null diff --git a/assets/blocks/environment/tendrils3.png b/assets/blocks/environment/tendrils3.png Binary files differdeleted file mode 100644 index e998b41..0000000 --- a/assets/blocks/environment/tendrils3.png +++ /dev/null diff --git a/assets/blocks/environment/vibrant-crystal-cluster1.png b/assets/blocks/environment/vibrant-crystal-cluster1.png Binary files differnew file mode 100644 index 0000000..33cad73 --- /dev/null +++ b/assets/blocks/environment/vibrant-crystal-cluster1.png diff --git a/assets/blocks/environment/vibrant-crystal-cluster2.png b/assets/blocks/environment/vibrant-crystal-cluster2.png Binary files differnew file mode 100644 index 0000000..441acd6 --- /dev/null +++ b/assets/blocks/environment/vibrant-crystal-cluster2.png diff --git a/assets/blocks/environment/vibrant-crystal-cluster3.png b/assets/blocks/environment/vibrant-crystal-cluster3.png Binary files differnew file mode 100644 index 0000000..c259fa9 --- /dev/null +++ b/assets/blocks/environment/vibrant-crystal-cluster3.png diff --git a/assets/blocks/environment/white-tree-dead.png b/assets/blocks/environment/white-tree-dead.png Binary files differnew file mode 100644 index 0000000..cfb6d65 --- /dev/null +++ b/assets/blocks/environment/white-tree-dead.png diff --git a/assets/blocks/environment/white-tree.png b/assets/blocks/environment/white-tree.png Binary files differnew file mode 100644 index 0000000..91f17de --- /dev/null +++ b/assets/blocks/environment/white-tree.png diff --git a/assets/blocks/environment/yellow-stone-boulder1.png b/assets/blocks/environment/yellow-stone-boulder1.png Binary files differnew file mode 100644 index 0000000..3f7bc89 --- /dev/null +++ b/assets/blocks/environment/yellow-stone-boulder1.png diff --git a/assets/blocks/environment/yellow-stone-boulder2.png b/assets/blocks/environment/yellow-stone-boulder2.png Binary files differnew file mode 100644 index 0000000..c075836 --- /dev/null +++ b/assets/blocks/environment/yellow-stone-boulder2.png diff --git a/assets/blocks/environment/yellowcoral-center.png b/assets/blocks/environment/yellowcoral-center.png Binary files differdeleted file mode 100644 index 7ca539b..0000000 --- a/assets/blocks/environment/yellowcoral-center.png +++ /dev/null diff --git a/assets/blocks/environment/yellowcoral.png b/assets/blocks/environment/yellowcoral.png Binary files differdeleted file mode 100644 index efbd52a..0000000 --- a/assets/blocks/environment/yellowcoral.png +++ /dev/null diff --git a/assets/blocks/production/cultivator-middle.png b/assets/blocks/production/cultivator-middle.png Binary files differindex f74af42..6c1b20f 100644 --- a/assets/blocks/production/cultivator-middle.png +++ b/assets/blocks/production/cultivator-middle.png diff --git a/src/block/content.rs b/src/block/content.rs index d07eb05..55d5545 100644 --- a/src/block/content.rs +++ b/src/block/content.rs @@ -1,5 +1,5 @@ //! everything -use crate::content::content_enum; +use crate::content::{content_enum, Content}; content_enum! { pub enum Type / Block for u16 | TryFromU16Error @@ -418,3 +418,9 @@ content_enum! { "world-message", } } +use crate::block::*; +impl Type { + pub fn to<'l>(&self, reg: &'l BlockRegistry) -> Option<&'l Block> { + reg.get(self.get_name()) + } +} diff --git a/src/block/distribution.rs b/src/block/distribution.rs index c68a851..69db26b 100644 --- a/src/block/distribution.rs +++ b/src/block/distribution.rs @@ -2,23 +2,39 @@ use std::error::Error; use std::fmt; -use image::RgbaImage; - -use crate::block::make_register; -use crate::block::simple::{cost, make_simple, state_impl}; +use crate::block::simple::*; +use crate::block::*; use crate::content; use crate::data::dynamic::DynType; -use crate::data::renderer::load; +use crate::data::renderer::{load, ImageHolder}; use crate::item; +use crate::utils::ImageUtils; make_simple!(ConveyorBlock); +make_simple!( + JunctionBlock, + |_, _, _, _| None, + |_, _, _, _, _, buff: &mut crate::data::DataRead| { + // format: + // - iterate 4 + // - u8 + // - iterate u8 + // - i64 + for _ in 0..4 { + let _ = buff.read_u8()?; + let n = buff.read_u8()? as usize; + buff.skip(n * 8)?; + } + Ok(()) + } +); make_register! { "conveyor" => ConveyorBlock::new(1, false, cost!(Copper: 1)); "titanium-conveyor" => ConveyorBlock::new(1, false, cost!(Copper: 1, Lead: 1, Titanium: 1)); "plastanium-conveyor" => ConveyorBlock::new(1, false, cost!(Graphite: 1, Silicon: 1, Plastanium: 1)); "armored-conveyor" => ConveyorBlock::new(1, false, cost!(Metaglass: 1, Thorium: 1, Plastanium: 1)); - "junction" => ConveyorBlock::new(1, true, cost!(Copper: 2)); + "junction" => JunctionBlock::new(1, true, cost!(Copper: 2)); "bridge-conveyor" => BridgeBlock::new(1, false, cost!(Copper: 6, Lead: 6), 4, true); "phase-conveyor" => BridgeBlock::new(1, false, cost!(Lead: 10, Graphite: 10, Silicon: 7, PhaseFabric: 5), 12, true); "sorter" => ItemBlock::new(1, true, cost!(Copper: 2, Lead: 2)); @@ -106,28 +122,27 @@ impl BlockLogic for ItemBlock { } } - fn draw(&self, category: &str, name: &str, state: Option<&State>) -> Option<RgbaImage> { + fn draw(&self, category: &str, name: &str, state: Option<&State>) -> Option<ImageHolder> { if !matches!( name, "unloader" | "item-source" | "sorter" | "inverted-sorter" ) { return None; } - let mut p = load(category, name).unwrap(); + let mut p = load(category, name).unwrap().clone(); if let Some(state) = state { if let Some(s) = Self::get_state(state) { - let mut top = load(category, "center").unwrap(); - crate::utils::image::tint(&mut top, s.color()); - image::imageops::overlay(&mut p, &top, 0, 0); - return Some(p); + let mut top = load(category, "center").unwrap().clone(); + p.overlay(top.tint(s.color()), 0, 0); + return Some(ImageHolder::from(p)); } } if name == "unloader" { - return Some(p); + return Some(ImageHolder::from(p)); } - let mut null = load("distribution", "cross-full").unwrap(); - image::imageops::overlay(&mut null, &p, 0, 0); - Some(null) + let mut null = load("distribution", "cross-full").unwrap().clone(); + null.overlay(&p, 0, 0); + Some(ImageHolder::from(null)) } } @@ -294,6 +309,29 @@ impl BlockLogic for BridgeBlock { Some((dx, dy)) => Ok(DynData::Point2(*dx, *dy)), } } + + /// format: + /// - out: `i32` + /// - warmup: `f32` + /// - iterate `links<u8>` + /// - in+: `i32` + /// - moved: `bool` + fn read( + &self, + _: &str, + _: &str, + _: &super::BlockRegistry, + _: &crate::data::map::EntityMapping, + buff: &mut crate::data::DataRead, + ) -> Result<(), crate::data::ReadError> { + buff.read_i32()?; + buff.read_f32()?; + for _ in 0..buff.read_u8()? { + buff.read_i32()?; + } + buff.read_bool()?; + Ok(()) + } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] diff --git a/src/block/drills.rs b/src/block/drills.rs index eb50b87..67d72f4 100644 --- a/src/block/drills.rs +++ b/src/block/drills.rs @@ -1,13 +1,14 @@ //! extraction of raw resources (mine part) use crate::block::make_register; use crate::block::simple::{cost, make_simple}; -use crate::data::renderer::read_with; -make_simple!(DrillBlock, |_, _, name, _| { - if name == "cliff-crusher" { +use crate::data::renderer::*; + +make_simple!(DrillBlock, |me: &DrillBlock, _, name, _| { + if matches!(name, "cliff-crusher" | "large-plasma-bore" | "plasma-bore") { const SFX: &[&str; 3] = &["", "-top", "-rotator"]; - return Some(read_with("drills", "cliff-crusher", SFX, 2u16)); + return Some(ImageHolder::Own(read_with("drills", name, SFX, me.size))); } - None + Some(ImageHolder::Borrow(load("drills", name).unwrap())) }); make_register! { @@ -16,7 +17,6 @@ make_register! { "laser-drill" => DrillBlock::new(3, true, cost!(Copper: 35, Graphite: 30, Titanium: 20, Silicon: 30)); "blast-drill" => DrillBlock::new(4, true, cost!(Copper: 65, Titanium: 50, Thorium: 75, Silicon: 60)); "water-extractor" => DrillBlock::new(2, true, cost!(Copper: 30, Lead: 30, Metaglass: 30, Graphite: 30)); - "cultivator" => DrillBlock::new(2, true, cost!(Copper: 25, Lead: 25, Silicon: 10)); "oil-extractor" => DrillBlock::new(3, true, cost!(Copper: 150, Lead: 115, Graphite: 175, Thorium: 115, Silicon: 75)); "vent-condenser" => DrillBlock::new(3, true, cost!(Graphite: 20, Beryllium: 60)); "cliff-crusher" => DrillBlock::new(2, false, cost!(Beryllium: 100, Graphite: 40)); diff --git a/src/block/environment.rs b/src/block/environment.rs new file mode 100644 index 0000000..9eee40c --- /dev/null +++ b/src/block/environment.rs @@ -0,0 +1,184 @@ +//! grass +use crate::block::make_register; +use crate::block::simple::make_simple; +use crate::data::renderer::*; +use tinyrand::{Rand, RandRange, Seeded, StdRand}; +use tinyrand_std::clock_seed::ClockSeed; + +macro_rules! register_env { + ($($field:literal: $size:literal @ $variations:literal;)+) => { + make_register!( + $($field => EnvironmentBlock::new($size, true, &[]);)* + ); + + make_simple!(EnvironmentBlock, |_, _, name, _| { + let mut rand = StdRand::seed(ClockSeed::default().next_u64()); + match name { + $($field => { + #[allow(clippy::reversed_empty_ranges)] + if $variations == 1 { Some(ImageHolder::Borrow(load("environment", $field).unwrap())) } + else if $variations == 0 { return None } + else { Some(ImageHolder::Borrow(load("environment", &format!("{}{}", $field, rand.next_range(1usize..$variations))).unwrap())) } + },)* + _ => { unreachable!() } + } + }); + }; +} + +register_env! { + "build1": 1@0; + "build2": 1@0; + "build3": 1@0; + "arkycite-floor": 1@1; + "arkyic-stone": 1@3; + "arkyic-vent": 3@2; + "arkyic-wall-large": 2@1; + "arkyic-wall": 1@3; + "basalt": 1@3; + "beryllic-stone-wall-large": 2@1; + "beryllic-stone-wall": 1@2; + "beryllic-stone": 1@4; + "bluemat": 1@3; + "carbon-stone": 1@4; + "carbon-vent": 3@2; + "carbon-wall-large": 2@1; + "carbon-wall": 1@2; + "char": 1@3; + "cliff": 1@7; + "core-zone": 1@1; + "crater-stone": 1@6; + "crystal-floor": 1@4; + "crystalline-stone-wall-large": 2@1; + "crystalline-stone-wall": 1@4; + "crystalline-stone": 1@5; + "crystalline-vent": 3@2; + "dacite-wall-large": 2@1; + "dacite-wall": 1@2; + "dacite": 1@3; + "dark-metal-large": 2@1; + "dark-metal": 1@2; + "dark-panel-1": 1@1; + "dark-panel-2": 1@1; + "dark-panel-3": 1@1; + "dark-panel-4": 1@1; + "dark-panel-5": 1@1; + "dark-panel-6": 1@1; + "metal-floor": 1@1; + "metal-floor-2": 1@1; + "metal-floor-3": 1@1; + "metal-floor-4": 1@1; + "metal-floor-5": 1@1; + "metal-floor-damaged": 1@3; + "darksand-tainted-water": 1@1; + "darksand-water": 1@1; + "darksand": 1@3; + "deep-tainted-water": 1@1; + "deep-water": 1@1; + "dense-red-stone": 1@4; + "dirt-wall-large": 2@1; + "dirt-wall": 1@2; + "dirt": 1@3; + "dune-wall-large": 2@1; + "dune-wall": 1@2; + "ferric-craters": 1@3; // ferris section + "ferric-stone-wall-large": 2@1; + "ferric-stone-wall": 1@2; + "ferric-stone": 1@4; + "graphite-wall-large": 2@1; + "graphite-wall": 1@3; + "grass": 1@3; + "hotrock": 1@3; + "ice-snow": 1@3; + "ice-wall-large": 2@1; + "ice-wall": 1@2; + "ice": 1@3; + "magmarock": 1@3; + "molten-slag": 1@1; + "moss": 1@3; + "mud": 1@3; + "ore-beryllium": 1@3; + "ore-coal": 1@3; + "ore-copper": 1@3; + "ore-crystal-thorium": 1@3; + "ore-lead": 1@3; + "ore-scrap": 1@3; + "ore-thorium": 1@3; + "ore-titanium": 1@3; + "ore-tungsten": 1@3; + "ore-wall-beryllium": 1@3; + "ore-wall-thorium": 1@3; + "ore-wall-tungsten": 1@3; + "pebbles": 1@3; + "pine": 1@1; + "pooled-cryofluid": 1@1; + "red-diamond-wall": 1@3; + "red-ice-wall-large": 2@1; + "red-ice": 1@3; + "red-stone-vent": 3@2; + "red-stone-wall-large": 2@1; + "red-stone-wall": 1@3; + "red-stone": 1@4; + "redmat": 1@3; + "regolith-wall-large": 2@1; + "regolith": 1@3; + "rhyolite-crater": 1@3; + "rhyolite-vent": 3@2; + "rhyolite-wall-large": 2@1; + "rhyolite-wall": 1@2; + "rhyolite": 1@3; + "rough-rhyolite": 1@3; + "salt-wall-large": 2@1; + "salt-wall": 1@2; + "salt": 1@1; + "sand-floor": 1@3; + "sand-wall-large": 2@1; + "sand-wall": 1@2; + "sand-water": 1@1; + "shale-wall-large": 2@1; + "shale-wall": 1@2; + "shale": 1@3; + "shallow-water": 1@1; + "shrubs-large": 2@1; + "shrubs": 1@2; + "snow-pine": 1@1; + "snow-wall-large": 2@1; + "snow-wall": 1@2; + "snow": 1@3; + "space": 1@1; + "spawn": 1@1; + "spore-moss": 1@3; + "spore-pine": 1@1; + "spore-wall-large": 2@1; + "spore-wall": 1@2; + "stone-wall-large": 2@1; + "stone-wall": 1@2; + "stone": 1@3; + "tainted-water": 1@1; + "tar": 1@1; + "yellow-stone-plates": 1@3; + "yellow-stone-vent": 3@2; + "yellow-stone-wall-large": 2@1; + "yellow-stone-wall": 1@2; + "yellow-stone": 1@3; + // props + "snow-boulder": 1@2; + "shale-boulder": 1@2; + "arkyic-boulder": 1@3; + "basalt-boulder": 1@2; + "beryllic-boulder": 1@2; + "boulder": 1@2; + "carbon-boulder": 1@2; + "crystalline-boulder": 1@2; + "dacite-boulder": 1@2; + "ferric-boulder": 1@2; + "red-ice-boulder": 1@3; + "red-stone-boulder": 1@4; + "rhyolite-boulder": 1@3; + "sand-boulder": 1@2; + "yellow-sand-boulder": 1@2; + // these are tall but uh + "crystal-blocks": 1@3; + "crytal-cluster": 1@3; + "crystal-orbs": 1@3; +} diff --git a/src/block/liquid.rs b/src/block/liquid.rs index f007747..888bd0d 100644 --- a/src/block/liquid.rs +++ b/src/block/liquid.rs @@ -3,12 +3,13 @@ use std::error::Error; use std::fmt; use crate::block::distribution::BridgeBlock; -use crate::block::make_register; -use crate::block::simple::{cost, make_simple, state_impl}; +use crate::block::simple::*; +use crate::block::*; use crate::content; use crate::data::dynamic::DynType; use crate::data::renderer::load; use crate::fluid; +use crate::utils::ImageUtils; make_simple!(LiquidBlock); @@ -101,19 +102,18 @@ impl BlockLogic for FluidBlock { } } - fn draw(&self, category: &str, name: &str, state: Option<&State>) -> Option<image::RgbaImage> { - let mut p = load(category, name).unwrap(); + fn draw(&self, category: &str, name: &str, state: Option<&State>) -> Option<ImageHolder> { + let mut p = load(category, name).unwrap().clone(); if let Some(state) = state { if let Some(s) = Self::get_state(state) { - let mut top = load("distribution", "center").unwrap(); - crate::utils::image::tint(&mut top, s.color()); - image::imageops::overlay(&mut p, &top, 0, 0); - return Some(p); + let mut top = load("distribution", "center").unwrap().clone(); + p.overlay(top.tint(s.color()), 0, 0); + return Some(ImageHolder::Own(p)); } } - let mut null = load("distribution", "cross-full").unwrap(); - image::imageops::overlay(&mut null, &p, 0, 0); - Some(null) + let mut null = load("distribution", "cross-full").unwrap().clone(); + null.overlay(&p, 0, 0); + Some(ImageHolder::Own(null)) } } diff --git a/src/block/logic.rs b/src/block/logic.rs index 76aa440..24c7b2c 100644 --- a/src/block/logic.rs +++ b/src/block/logic.rs @@ -9,15 +9,14 @@ use flate2::{ FlushDecompress, Status, }; -use crate::block::make_register; -use crate::block::simple::{cost, make_simple, state_impl}; +use crate::block::simple::*; +use crate::block::*; use crate::data::dynamic::DynType; use crate::data::{self, DataRead, DataWrite}; make_simple!(LogicBlock); make_register! { - // todo reinforced proc "reinforced-message" => MessageLogic::new(1, true, cost!(Graphite: 10, Beryllium: 5)); "message" => MessageLogic::new(1, true, cost!(Copper: 5, Graphite: 5)); "switch" => SwitchLogic::new(1, true, cost!(Copper: 5, Graphite: 5)); @@ -28,6 +27,11 @@ 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)) + // editor only + "world-processor" => LogicBlock::new(1, true, &[]); + "world-message" => MessageLogic::new(1, true, &[]); + "world-cell" => LogicBlock::new(1, true, &[]); } pub struct MessageLogic { diff --git a/src/block/mod.rs b/src/block/mod.rs index ca20821..642518b 100644 --- a/src/block/mod.rs +++ b/src/block/mod.rs @@ -1,8 +1,8 @@ //! deal with blocks. //! -//! categorized as mindustry categorizes them in its assets folder, for easy drawing -//! with the exception of sandbox. -use image::RgbaImage; +//! categorized as mindustry categorizes them in its assets folder, for easy drawing. +//! +//! with the exception of sandbox, that is. use std::any::Any; use std::borrow::Cow; use std::error::Error; @@ -10,8 +10,10 @@ use std::fmt; use crate::access::BoxAccess; use crate::data::dynamic::{DynData, DynType}; -use crate::data::GridPos; -use crate::item::storage::Storage as ItemStorage; +use crate::data::map::EntityMapping; +use crate::data::renderer::ImageHolder; +use crate::data::{DataRead, GridPos, ReadError as DataReadError}; +use crate::item::storage::ItemStorage; use crate::registry::RegistryEntry; pub mod campaign; @@ -19,6 +21,7 @@ pub mod content; pub mod defense; pub mod distribution; pub mod drills; +pub mod environment; pub mod liquid; pub mod logic; pub mod payload; @@ -50,9 +53,21 @@ pub trait BlockLogic { fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError>; - fn draw(&self, _category: &str, _name: &str, _state: Option<&State>) -> Option<RgbaImage> { + fn draw(&self, _category: &str, _name: &str, _state: Option<&State>) -> Option<ImageHolder> { None } + // TODO: use data + #[allow(unused_variables)] + fn read( + &self, + category: &str, + name: &str, + reg: &BlockRegistry, + mapping: &EntityMapping, + buff: &mut DataRead, + ) -> Result<(), DataReadError> { + Ok(()) + } } // i wish i could derive @@ -66,7 +81,7 @@ macro_rules! impl_block { self.symmetric } - fn create_build_cost(&self) -> Option<$crate::item::storage::Storage> { + fn create_build_cost(&self) -> Option<$crate::item::storage::ItemStorage> { if self.build_cost.is_empty() { None } else { @@ -213,16 +228,16 @@ impl Block { } /// draw this block, with this state - pub fn image(&self, state: Option<&State>) -> RgbaImage { - if let Some(p) = self - .logic - .as_ref() - .draw(&self.category, &self.name, state) - { + pub fn image(&self, state: Option<&State>) -> ImageHolder { + if let Some(p) = self.logic.as_ref().draw(&self.category, &self.name, state) { return p; } use crate::data::renderer::read; - read(&self.category, &self.name, self.get_size()) + ImageHolder::Own(read(&self.category, &self.name, self.get_size())) + } + + pub fn has_building(&self) -> bool { + &self.category != "environment" } /// size. @@ -270,6 +285,17 @@ impl Block { pub(crate) fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError> { self.logic.serialize_state(state) } + + #[doc(hidden)] + pub fn read( + &self, + buff: &mut DataRead, + reg: &BlockRegistry, + mapping: &EntityMapping, + ) -> Result<(), DataReadError> { + self.logic + .read(&self.category, &self.name, reg, mapping, buff) + } } impl fmt::Debug for Block { @@ -455,4 +481,5 @@ fn register(reg: &mut BlockRegistry<'_>) { campaign::register(reg); logic::register(reg); walls::register(reg); + environment::register(reg); } diff --git a/src/block/payload.rs b/src/block/payload.rs index 1cf52fe..c743934 100644 --- a/src/block/payload.rs +++ b/src/block/payload.rs @@ -2,12 +2,17 @@ use std::error::Error; use std::fmt; -use crate::block::simple::{cost, make_simple, state_impl}; -use crate::block::{self, distribution::BridgeBlock, make_register}; -use crate::content; +use crate::block::content::Type as BlockEnum; +use crate::block::distribution::BridgeBlock; +use crate::block::simple::*; +use crate::block::{self, *}; +use crate::content::{self, Content}; use crate::data::dynamic::DynType; +use crate::data::ReadError; use crate::unit; +use super::BlockRegistry; + make_simple!(ConstructorBlock); const GROUND_UNITS: &[unit::Type] = &[unit::Type::Dagger, unit::Type::Crawler, unit::Type::Nova]; @@ -240,6 +245,50 @@ impl BlockLogic for PayloadBlock { Payload::Unit(unit) => Ok(DynData::Content(content::Type::Unit, (*unit).into())), } } + + /// format: + /// - exists: `bool` + /// - if !exists: ok + /// - type: `u8` + /// - if type == 1 (payload block): + /// - block: `u16` + /// - version: `u8` + /// - [`crate::block::Block::read`] (recursion :ferrisHmm:), + /// - if type == 2 (paylood unit): + /// - id: `u8` + /// - unit read???????? TODO + fn read( + &self, + _: &str, + _: &str, + reg: &BlockRegistry, + entity_mapping: &crate::data::map::EntityMapping, + buff: &mut crate::data::DataRead, + ) -> Result<(), crate::data::ReadError> { + if !buff.read_bool()? { + return Ok(()); + } + let t = buff.read_u8()?; + const BLOCK: u8 = 1; + const UNIT: u8 = 0; + match t { + BLOCK => { + let b = buff.read_u16()?; + let b = BlockEnum::try_from(b).unwrap_or(BlockEnum::Router); + let b = reg.get(b.get_name()).unwrap(); + b.read(buff, reg, entity_mapping)?; + } + UNIT => { + let u = buff.read_u8()?; + let Some(_u) = entity_mapping.get(&u) else { + return Err(ReadError::Expected("map entry")); + }; + // unit::Type::try_from(u).unwrap_or(unit::Type::Alpha).read(todo!()); + } + _ => return Err(ReadError::Expected("0 | 1")), + } + Ok(()) + } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] diff --git a/src/block/power.rs b/src/block/power.rs index 2df3e14..f5048c6 100644 --- a/src/block/power.rs +++ b/src/block/power.rs @@ -2,8 +2,8 @@ use std::error::Error; use std::fmt; -use crate::block::make_register; -use crate::block::simple::{cost, make_simple, state_impl}; +use crate::block::simple::*; +use crate::block::*; use crate::data::dynamic::DynType; make_simple!(GeneratorBlock); diff --git a/src/block/production.rs b/src/block/production.rs index 7484ab0..7d8e7df 100644 --- a/src/block/production.rs +++ b/src/block/production.rs @@ -3,6 +3,7 @@ use crate::block::make_register; use crate::block::simple::{cost, make_simple}; make_register! { + "cultivator" => ProductionBlock::new(2, true, cost!(Copper: 25, Lead: 25, Silicon: 10)); "graphite-press" => ProductionBlock::new(2, true, cost!(Copper: 75, Lead: 30)); "multi-press" => ProductionBlock::new(3, true, cost!(Lead: 100, Graphite: 50, Titanium: 100, Silicon: 25)); "silicon-smelter" => ProductionBlock::new(2, true, cost!(Copper: 30, Lead: 25)); @@ -24,9 +25,9 @@ make_register! { "silicon-arc-furnace" => ProductionBlock::new(3, true, cost!(Beryllium: 70, Graphite: 80)); "electrolyzer" => ProductionBlock::new(3, true, cost!(Silicon: 50, Graphite: 40, Beryllium: 130, Tungsten: 80)); "atmospheric-concentrator" => ProductionBlock::new(3, true, cost!(Oxide: 60, Beryllium: 180, Silicon: 150)); - "oxidation-chamber" => ProductionBlock::new(3, true, cost!(Tungsten: 120, Graphite: 80, Silicon: 100, Beryllium: 120)); - "electric-heater" => ProductionBlock::new(2, false, cost!(Tungsten: 30, Oxide: 30)); - "slag-heater" => ProductionBlock::new(3, false, cost!(Tungsten: 50, Oxide: 20, Beryllium: 20)); + "oxidation-chamber" => HeatCrafter::new(3, true, cost!(Tungsten: 120, Graphite: 80, Silicon: 100, Beryllium: 120)); + "electric-heater" => HeatCrafter::new(2, false, cost!(Tungsten: 30, Oxide: 30)); + "slag-heater" => HeatCrafter::new(3, false, cost!(Tungsten: 50, Oxide: 20, Beryllium: 20)); "phase-heater" => ProductionBlock::new(2, false, cost!(Oxide: 30, Carbide: 30, Beryllium: 30)); "heat-redirector" => ProductionBlock::new(3, false, cost!(Tungsten: 10, Graphite: 10)); "heat-router" => ProductionBlock::new(3, false, cost!(Tungsten: 15, Graphite: 10)); @@ -41,4 +42,30 @@ make_register! { "heat-source" => ProductionBlock::new(1, false, &[]); } -make_simple!(ProductionBlock); +make_simple!( + ProductionBlock, + |_, _, _, _| None, + |_, _, _, _, _, buff: &mut crate::data::DataRead| { + // format: + // - progress: `f32` + // - warmup: `f32` + buff.read_f32()?; + buff.read_f32()?; + Ok(()) + } +); + +make_simple!( + HeatCrafter, + |_, _, _, _| None, + |_, _, _, _, _, buff: &mut crate::data::DataRead| { + // format: + // - progress: `f32` + // - warmup: `f32` + // - heat: f32 + buff.read_f32()?; + buff.read_f32()?; + buff.read_f32()?; + Ok(()) + } +); diff --git a/src/block/simple.rs b/src/block/simple.rs index c78eb0e..2dca7fd 100644 --- a/src/block/simple.rs +++ b/src/block/simple.rs @@ -23,15 +23,19 @@ macro_rules! state_impl { pub(crate) use state_impl; macro_rules! make_simple { - ($name: ident, $draw: expr) => { + ($name: ident, $draw: expr, $read: expr) => { pub struct $name { size: u8, symmetric: bool, - build_cost: BuildCost, + build_cost: crate::block::simple::BuildCost, } impl $name { #[must_use] - pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self { + pub const fn new( + size: u8, + symmetric: bool, + build_cost: crate::block::simple::BuildCost, + ) -> Self { assert!(size != 0, "invalid size"); Self { size, @@ -41,51 +45,69 @@ macro_rules! make_simple { } } - use crate::block::{ - impl_block, simple::BuildCost, BlockLogic, DataConvertError, DeserializeError, - SerializeError, State, - }; - use crate::data::dynamic::DynData; - use crate::data::GridPos; - impl BlockLogic for $name { - impl_block!(); - - fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> { - Ok(DynData::Empty) + impl crate::block::BlockLogic for $name { + crate::block::impl_block!(); + + fn data_from_i32( + &self, + _: i32, + _: crate::data::GridPos, + ) -> Result<crate::DynData, crate::block::DataConvertError> { + Ok(crate::DynData::Empty) } - fn deserialize_state(&self, _: DynData) -> Result<Option<State>, DeserializeError> { + fn deserialize_state( + &self, + _: crate::DynData, + ) -> Result<Option<crate::block::State>, crate::block::DeserializeError> { Ok(None) } - fn clone_state(&self, _: &State) -> State { + fn clone_state(&self, _: &crate::block::State) -> crate::block::State { panic!("{} has no custom state", stringify!($name)) } - fn mirror_state(&self, _: &mut State, _: bool, _: bool) { + fn mirror_state(&self, _: &mut crate::block::State, _: bool, _: bool) { panic!("{} has no custom state", stringify!($name)); } - fn rotate_state(&self, _: &mut State, _: bool) { + fn rotate_state(&self, _: &mut crate::block::State, _: bool) { panic!("{} has no custom state", stringify!($name)); } - fn serialize_state(&self, _: &State) -> Result<DynData, SerializeError> { - Ok(DynData::Empty) + fn serialize_state( + &self, + _: &crate::block::State, + ) -> Result<crate::DynData, crate::block::SerializeError> { + Ok(crate::DynData::Empty) } fn draw( &self, category: &str, name: &str, - state: Option<&State>, - ) -> Option<image::RgbaImage> { + state: Option<&crate::block::State>, + ) -> Option<crate::data::renderer::ImageHolder> { $draw(self, category, name, state) } + + fn read( + &self, + category: &str, + name: &str, + reg: &crate::block::BlockRegistry, + entity_mapping: &crate::data::map::EntityMapping, + buff: &mut crate::data::DataRead, + ) -> Result<(), crate::data::ReadError> { + $read(self, category, name, reg, entity_mapping, buff) + } } }; + ($name: ident, $draw: expr) => { + crate::block::simple::make_simple!($name, $draw, |_, _, _, _, _, _| Ok(())); + }; ($name: ident) => { - crate::block::simple::make_simple!($name, |_, _, _, _| { None }); + crate::block::simple::make_simple!($name, |_, _, _, _| None, |_, _, _, _, _, _| { Ok(()) }); }; } pub(crate) use make_simple; diff --git a/src/block/turrets.rs b/src/block/turrets.rs index 5aeaeaa..160c991 100644 --- a/src/block/turrets.rs +++ b/src/block/turrets.rs @@ -1,4 +1,4 @@ -//! idk why its not in the [crate::block::defense] module +//! idk why its not in the [`crate::block::defense`] module use crate::block::make_register; use crate::block::simple::cost; @@ -32,15 +32,15 @@ make_register! { "malign" => TurretBlock::new(5, true, cost!(Carbide: 400, Beryllium: 2000, Silicon: 800, Graphite: 800, PhaseFabric: 300)); } -use crate::data::renderer::load; +use crate::data::renderer::*; +use crate::utils::ImageUtils; crate::block::simple::make_simple!(TurretBlock, |me: &Self, _, name, _| { let path = match name { "breach" | "diffuse" | "sublimate" | "titan" | "disperse" | "afflict" | "lustre" | "scathe" | "malign" => format!("bases/reinforced-block-{}", me.size), _ => format!("bases/block-{}", me.size), }; - let mut base = load("turrets", &path).unwrap(); - let top = load("turrets", name).unwrap(); - image::imageops::overlay(&mut base, &top, 0, 0); - Some(base) + let mut base = load("turrets", &path).unwrap().value().clone(); + base.overlay(load("turrets", name).unwrap().value(), 0, 0); + Some(ImageHolder::from(base)) }); diff --git a/src/block/walls.rs b/src/block/walls.rs index 1b6f25a..d746cdf 100644 --- a/src/block/walls.rs +++ b/src/block/walls.rs @@ -1,15 +1,17 @@ //! walls -use crate::block::make_register; -use crate::block::simple::{cost, make_simple, state_impl}; +use crate::block::simple::*; +use crate::block::*; use crate::data::dynamic::DynType; -use crate::data::renderer::{load, read_with, TOP}; +use crate::data::renderer::{load, read_with, ImageHolder, TOP}; make_simple!(WallBlock, |_, _, name, _| { if name == "thruster" { const SFX: &[&str; 1] = &[TOP]; - return Some(read_with("turrets", "thruster", SFX, 4u32)); + return Some(ImageHolder::Own(read_with( + "turrets", "thruster", SFX, 4u32, + ))); } - Some(load("walls", name).unwrap()) + Some(ImageHolder::Borrow(load("walls", name).unwrap())) }); make_register! { diff --git a/src/data/dynamic.rs b/src/data/dynamic.rs index b44e68c..b901725 100644 --- a/src/data/dynamic.rs +++ b/src/data/dynamic.rs @@ -1,6 +1,5 @@ //! variable type -use std::error::Error; -use std::fmt; +use thiserror::Error; use crate::content; use crate::data::command::{self, UnitCommand}; @@ -351,102 +350,46 @@ impl Serializer<DynData> for DynSerializer { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Error)] pub enum ReadError { - Underlying(data::ReadError), + #[error("failed to read from buffer")] + Underlying(#[from] data::ReadError), + #[error("invalid dynamic data type ({0})")] Type(u8), - ContentType(content::TryFromU8Error), + #[error("content type not found")] + ContentType(#[from] content::TryFromU8Error), + #[error("integer array too long ({0})")] IntArrayLen(i16), + #[error("point2 array too long ({0})")] Point2ArrayLen(i8), + #[error("invalid logic field ({0})")] LogicField(u8), + #[error("byte array too long ({0})")] ByteArrayLen(i32), - UnitCommand(command::TryFromU8Error), + #[error("unit command not found")] + UnitCommand(#[from] command::TryFromU8Error), + #[error("boolean array too long ({0}")] BoolArrayLen(i32), + #[error("vec2 array too long ({0})")] Vec2ArrayLen(i16), } -impl From<data::ReadError> for ReadError { - fn from(err: data::ReadError) -> Self { - Self::Underlying(err) - } -} - -impl From<content::TryFromU8Error> for ReadError { - fn from(err: content::TryFromU8Error) -> Self { - Self::ContentType(err) - } -} - -impl From<command::TryFromU8Error> for ReadError { - fn from(err: command::TryFromU8Error) -> Self { - Self::UnitCommand(err) - } -} - -impl fmt::Display for ReadError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Underlying(..) => f.write_str("failed to read from buffer"), - Self::Type(id) => write!(f, "invalid dynamic data type ({id})"), - Self::ContentType(..) => f.write_str("content type not found"), - Self::IntArrayLen(len) => write!(f, "integer array too long ({len})"), - Self::Point2ArrayLen(len) => write!(f, "point2 array too long ({len})"), - Self::LogicField(id) => write!(f, "invalid logic field ({id})"), - Self::ByteArrayLen(len) => write!(f, "byte array too long ({len})"), - Self::UnitCommand(..) => f.write_str("unit command not found"), - Self::BoolArrayLen(len) => write!(f, "boolean array too long ({len})"), - Self::Vec2ArrayLen(len) => write!(f, "vec2 array too long ({len})"), - } - } -} - -impl Error for ReadError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Underlying(e) => Some(e), - _ => None, - } - } -} - -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Error)] pub enum WriteError { - Underlying(data::WriteError), + #[error("failed to write to buffer")] + Underlying(#[from] data::WriteError), + #[error("integer array too long ({0})")] IntArrayLen(usize), + #[error("point2 array too long ({0})")] Point2ArrayLen(usize), + #[error("byte array too long ({0})")] ByteArrayLen(usize), + #[error("boolean array too long ({0})")] BoolArrayLen(usize), + #[error("vec2 array too long ({0})")] Vec2ArrayLen(usize), } -impl From<data::WriteError> for WriteError { - fn from(err: data::WriteError) -> Self { - Self::Underlying(err) - } -} - -impl fmt::Display for WriteError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Underlying(..) => f.write_str("failed to write to buffer"), - Self::IntArrayLen(len) => write!(f, "integer array too long ({len})"), - Self::Point2ArrayLen(len) => write!(f, "point2 array too long ({len})"), - Self::ByteArrayLen(len) => write!(f, "byte array too long ({len})"), - Self::BoolArrayLen(len) => write!(f, "boolean array too long ({len})"), - Self::Vec2ArrayLen(len) => write!(f, "vec2 array too long ({len})"), - } - } -} - -impl Error for WriteError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Underlying(e) => Some(e), - _ => None, - } - } -} - #[cfg(test)] mod test { use super::*; diff --git a/src/data/map.rs b/src/data/map.rs new file mode 100644 index 0000000..4b1e5bd --- /dev/null +++ b/src/data/map.rs @@ -0,0 +1,462 @@ +//! the map module +//! ### format +//! note: utf = `len<u16>` + utf8(read(len)) +//! +//! note: each section has a `u32` denoting its length +//! +//! key: `: T` and `x<T>` both mean read T, `iterate T` means iterate `read_T()` times +//! +//! ZLIB compressed stream contains: +//! - header: 4b = `MSCH` +//! - version: `u32` (should be 7) +//! - tag section `<u32>` +//! - 1 byte of idk (skip) +//! - string map (`u16` for map len, iterate each, read `utf`) +//! - 4 bytes of idk (skip) +//! - content header section `<u32>`: +//! - iterate `i8` (should = `8`)'//! - the type: `i8` (0: item, block: 1, liquid: 4, status: 5, unit: 6, weather: 7, sector: 9, planet: 13//! - item count: `u1\'6` (item: 22, block: 412, liquid: 11, status: 21, unit: 66, weather: 6, sector: 35, planet: 7) +//! - these types all have their own modules: [`crate::item`], [`crate::block::content`], [`crate::fluid`], [`crate::modifier`], [`crate::unit`], [`crate::data::weather`], [`crate::data::sector`], [`crate::data::planet`] +//! - iterate `u16` +//! - name: `utf` +//! - map section `<u32>` +//! - width: `u16`, height: `u16` +//! - floor and tiles: +//! - for `i` in `w * h` +//! - `x = i % w`, `y = i / w` +//! - floor id: `u16` +//! - overlay id: `u16` +//! - consecutives: `u8` +//! - iterate `(i + 1)..(i + 1 + consecutives)` +//! - `x = j % w`, `y = j / w` +//! - i += consecutives +//! - blocks +//! - for `i` in `w * h` +//! - block id: `u16` +//! - packed?: `i8` +//! - entity = `(packed & 1) not 0` +//! - data = `(packed & 2) not 0` +//! - if entity: central: `bool` +//! - if entity: +//! - if central: +//! - chunk len: `u16` +//! - if block == building: +//! - revision: `i8` +//! - tile.build.readAll +//! - else skip `chunk len` +//! - or data +//! - data: `i8` +//! - else +//! - consecutives: `u8` +//! - iterate `(i + 1)..(i + 1 + consecutives)` +//! - same block +//! - i += consecutives +//! - entities section `<u32>` +//! - entity mapping +//! - iterate `u16` +//! - id: `i16`, name: `utf` +//! - team build plans +//! - for t in `teams<u32>` +//! - team = `team#<u32>` +//! - iterate `plans<u32>` +//! - x: `u16`, y: `u16`, rot: `u16`, id: `u16` +//! - o: `DynData` (refer to [crate::data::dynamic::DynSerializer]) +//! - world entities +//! - iterate `u32` +//! - len: `u16` +//! - type: `u8` +//! - if !mapping\[type\] +//! - skip(len - 1) +//! - continue +//! - id: `u32` +//! - entity read +use std::collections::HashMap; +use thiserror::Error; + +use crate::block::content::Type as BlockEnum; +use crate::block::{Block, BlockRegistry, Rotation}; +use crate::data::dynamic::DynSerializer; +use crate::data::renderer::*; +use crate::data::DataRead; +use crate::fluid::Type as Fluid; +use crate::item::storage::Storage; +use crate::item::Type as Item; +use crate::team::Team; + +use super::GridPos; +use super::Serializer; +use crate::content::Content; +use crate::utils::image::ImageUtils; + +/// a tile in a map +pub struct Tile<'l> { + pub pos: GridPos, + pub floor: &'l Block, + pub ore: Option<&'l Block>, + pub build: Option<Build<'l>>, +} + +pub type EntityMapping = HashMap<u8, Box<dyn Content>>; +impl<'l> Tile<'l> { + fn set_block(&mut self, block: &'l Block) { + self.build = Some(Build { + block, + items: Storage::new(), + liquids: Storage::new(), + rotation: Rotation::Up, + team: crate::team::SHARDED, + data: 0, + }); + } + + /// check if this tile contains a building. + pub fn has_building(&self) -> bool { + if let Some(b) = &self.build { + return b.block.has_building(); + } + false + } + + /// size of this tile + /// + /// ._. + /// + /// dont think about it too much + pub fn size(&self) -> u8 { + if let Some(b) = &self.build { + return b.block.get_size(); + } + 1 + } + + pub fn image(&self) -> ImageHolder { + // building covers floore + let i = if let Some(b) = &self.build { + b.image() + } else { + let mut i = self.floor.image(None).own(); + if let Some(ore) = self.ore { + i.overlay(ore.image(None).borrow(), 0, 0); + } + ImageHolder::from(i) + }; + i + } +} + +impl std::fmt::Debug for Tile<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Tile<{},{}>@{}+{}{}", + self.pos.0, + self.pos.1, + self.floor.name(), + if let Some(ore) = &self.ore { + ore.name() + } else { + "" + }, + if let Some(build) = &self.build { + format!(":{}", build.block.name()) + } else { + "".to_string() + } + ) + } +} + +/// a build on a tile in a map +#[derive(Debug)] +pub struct Build<'l> { + pub block: &'l Block, + pub items: Storage<Item>, + pub liquids: Storage<Fluid>, + // pub health: f32, + pub rotation: Rotation, + pub team: Team, + pub data: i8, +} + +impl Build<'_> { + pub fn image(&self) -> ImageHolder { + self.block.image(None) + } + + pub fn read( + &mut self, + buff: &mut DataRead<'_>, + reg: &BlockRegistry, + map: &EntityMapping, + ) -> Result<(), ReadError> { + // health + let _ = buff.read_f32()?; // 4 + let rot = dbg!(buff.read_u8()?); // 5 + self.rotation = Rotation::try_from(rot & 127).unwrap_or(Rotation::Up); + if (rot & 128) == 0 { + return Err(ReadError::Version(rot & 128)); + } + + let _t = dbg!(buff.read_u8()?); // 6 + let _v = dbg!(buff.read_u8()?); // 7 + let mask = dbg!(buff.read_u8()?); // 8 + if dbg!((mask & 1) != 0) { + self.items.clear(); + // 10 + for _ in 0..dbg!(buff.read_u16()?) { + let item = buff.read_u16()?; + let amount = buff.read_u32()?; + if let Ok(item) = Item::try_from(item) { + self.items.set(item, amount); + } + } + } + if mask & 2 == 0 { + let n = buff.read_u16()? as usize; + buff.skip((n * 4) + 1)?; + } + if mask & 4 == 0 { + self.liquids.clear(); + for _ in 0..buff.read_u16()? { + let fluid = buff.read_u16()?; + let amount = buff.read_f32()?; + if let Ok(fluid) = Fluid::try_from(fluid) { + self.liquids.set(fluid, (amount * 100.0) as u32); + } + } + } + // "efficiency"? + let _ = buff.read_u8()?; + let _ = buff.read_u8()?; + // visible flags + let _ = buff.read_i64()?; + // "overriden by subclasses" + self.block.read(buff, reg, map)?; + Ok(()) + } +} + +/// a map +#[derive(Debug)] +pub struct Map<'l> { + pub width: u32, + pub height: u32, + pub tags: HashMap<String, String>, + pub tiles: Vec<Tile<'l>>, +} + +const MAP_HEADER: [u8; 4] = [b'M', b'S', b'A', b'V']; + +/// error ocurring when reading a map fails +#[derive(Debug, Error)] +pub enum ReadError { + #[error("failed to read from buffer")] + Read(#[from] super::ReadError), + #[error("incorrect header ({0:?})")] + Header([u8; 4]), + #[error("unsupported version ({0})")] + Version(u8), + #[error("unknown block {0:?}")] + NoSuchBlock(String), + #[error("failed to read block data")] + ReadState(#[from] super::dynamic::ReadError), +} + +/// serde map +pub struct MapSerializer<'l>(pub &'l BlockRegistry<'l>); +impl<'l> Serializer<Map<'l>> for MapSerializer<'l> { + type ReadError = ReadError; + type WriteError = (); + /// deserialize a map + /// + /// notes: + /// - does not deserialize data + /// - does not deserialize entities + fn deserialize(&mut self, buff: &mut DataRead<'_>) -> Result<Map<'l>, Self::ReadError> { + let buff = buff.deflate()?; + let mut buff = DataRead::new(&buff); + { + let mut b = [0; 4]; + buff.read_bytes(&mut b)?; + if b != MAP_HEADER { + return Err(ReadError::Header(b)); + } + } + let version = buff.read_u32()?; + if version != 7 { + return Err(ReadError::Version(version.try_into().unwrap_or(0))); + } + let mut tags = HashMap::new(); + buff.read_chunk(|buff| { + buff.skip(1)?; + for _ in 0..buff.read_u8()? { + let key = buff.read_utf()?; + let value = buff.read_utf()?; + tags.insert(key.to_owned(), value.to_owned()); + } + Ok::<(), super::ReadError>(()) + })?; + buff.read_chunk(|buff| { + // we skip these (just keep the respective modules updated) + for _ in 0..buff.read_i8()? { + // let _ty = buff.read_u8()?; + // for _ in 0..buff.read_i16()? { + // let name = dbg!(buff.read_utf()?); + // } + buff.skip(1)?; + for _ in 0..buff.read_u16()? { + let n = buff.read_u16()?; + buff.skip(n as usize)?; + } + } + Ok::<(), super::ReadError>(()) + })?; + + // map section + let mut w = 0; + let mut h = 0; + let mut tiles = vec![]; + buff.read_chunk(|buff| { + w = buff.read_u16()? as u32; + h = buff.read_u16()? as u32; + let count = w * h; + let mut i = 0; + while i < count { + let x = (i % w) as u16; + let y = (i / w) as u16; + let floor_id = buff.read_u16()?; + let overlay_id = buff.read_u16()?; + let floor = BlockEnum::try_from(floor_id) + .unwrap_or(BlockEnum::Stone) + .to(self.0) + .unwrap_or(&crate::block::environment::STONE); + let ore = BlockEnum::try_from(overlay_id) + .unwrap_or(BlockEnum::Air) + .to(self.0); + debug_assert!( + x < w as u16 && y < h as u16, + "{x} or {y} out of bounds ({floor:?} {ore:?})" + ); + tiles.push(Tile { + floor, + ore, + pos: GridPos(x, y), + build: None, + }); + let consecutives = buff.read_u8()? as u32; + if consecutives > 0 { + for i in (i + 1)..(i + 1 + consecutives) { + let x = (i % w) as u16; + let y = (i / w) as u16; + tiles.push(Tile { + floor, + ore, + pos: GridPos(x, y), + build: None, + }) + } + i += consecutives; + } + i += 1; + } + let mut i = 0usize; + while i < count as usize { + let block_id = buff.read_u16()?; + let packed = buff.read_u8()?; + let entity = (packed & 1) != 0; + let data = (packed & 2) != 0; + let central = if entity { buff.read_bool()? } else { false }; + let block = BlockEnum::try_from(block_id) + .map_err(|_| ReadError::NoSuchBlock(block_id.to_string()))?; + let block = if block != BlockEnum::Air { + Some( + self.0 + .get(block.get_name()) + .ok_or(ReadError::NoSuchBlock(block.to_string()))?, + ) + } else { + None + }; + if central { + if let Some(block) = block { + tiles[i].set_block(block); + } + } + if entity { + if central { + // TODO: actually read + let n = buff.read_u16()?; + buff.skip(n as usize)?; + // let _ = buff.read_i8()?; + // tiles[i] + // .build + // .as_mut() + // .unwrap() + // // map not initialized yet + // .read(&mut buff, self.0, &HashMap::new())?; + } + } else if data { + if let Some(block) = block { + tiles[i].set_block(block); + } + tiles[i].build.as_mut().unwrap().data = buff.read_i8()?; + } else { + let consecutives = buff.read_u8()? as usize; + for tile in tiles.iter_mut().take(consecutives).skip(i + 1) { + if let Some(block) = block { + tile.set_block(block); + } + } + i += consecutives; + } + i += 1 + } + Ok::<(), ReadError>(()) + })?; + let mut mapping = EntityMapping::new(); + buff.read_chunk(|buff| { + for _ in 0..buff.read_u16()? { + let id = buff.read_i16()? as u8; + let nam = buff.read_utf()?; + dbg!(nam); + mapping.insert(id, Box::new(Item::Copper)); + // mapping.push(content::Type::get_name(nam)); + } + for _ in 0..buff.read_u32()? { + buff.skip(4)?; + for _ in 0..buff.read_u32()? { + buff.skip(8usize)?; + let _ = DynSerializer::deserialize(&mut DynSerializer, buff)?; + } + } + for _ in 0..buff.read_u32()? { + let len = buff.read_u16()? as usize; + let ty = buff.read_u8()?; + if !mapping.contains_key(&ty) { + buff.skip(len - 1)?; + continue; + } + let _id = buff.read_u32()?; + // TODO + } + Ok::<(), ReadError>(()) + })?; + // skip custom chunks + buff.skip_chunk()?; + Ok(Map { + width: w, + height: h, + tags, + tiles, + }) + } + + /// serialize a map (todo) + /// panics: always + fn serialize( + &mut self, + _: &mut super::DataWrite<'_>, + _: &Map<'_>, + ) -> Result<(), Self::WriteError> { + todo!() + } +} diff --git a/src/data/mod.rs b/src/data/mod.rs index 2426f44..589a33c 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -7,16 +7,29 @@ use std::collections::HashMap; use std::error::Error; use std::fmt; use std::str::Utf8Error; +use thiserror::Error; mod base64; mod command; pub mod dynamic; +pub mod map; +pub mod planet; pub mod renderer; pub mod schematic; +pub mod sector; +pub mod weather; #[derive(Debug)] pub struct DataRead<'d> { data: &'d [u8], + // used with read_chunk + read: usize, +} + +impl fmt::Display for DataRead<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", String::from_utf8_lossy(self.data)) + } } macro_rules! make_read { @@ -32,6 +45,7 @@ macro_rules! make_read { let mut output = [0u8; LEN]; output.copy_from_slice(&self.data[..LEN]); self.data = &self.data[LEN..]; + self.read += LEN; Ok(<$type>::from_be_bytes(output)) } }; @@ -40,7 +54,7 @@ macro_rules! make_read { impl<'d> DataRead<'d> { #[must_use] pub fn new(data: &'d [u8]) -> Self { - Self { data } + Self { data, read: 0 } } pub fn read_bool(&mut self) -> Result<bool, ReadError> { @@ -75,6 +89,7 @@ impl<'d> DataRead<'d> { } let result = std::str::from_utf8(&self.data[..end])?; self.data = &self.data[end..]; + self.read += end; Ok(result) } @@ -87,9 +102,48 @@ impl<'d> DataRead<'d> { } dst.copy_from_slice(&self.data[..dst.len()]); self.data = &self.data[dst.len()..]; + self.read += dst.len(); Ok(()) } + pub fn skip(&mut self, n: usize) -> Result<(), ReadError> { + if self.data.len() < n { + return Err(ReadError::Underflow { + need: n, + have: self.data.len(), + }); + } + self.data = &self.data[n..]; + self.read += n; + Ok(()) + } + + pub fn skip_chunk(&mut self) -> Result<usize, ReadError> { + let len = self.read_u32()? as usize; + self.skip(len)?; + Ok(len) + } + + pub fn read_chunk<E>(&mut self, f: impl FnOnce(&mut DataRead) -> Result<(), E>) -> Result<(), E> + where + E: Error + From<ReadError>, + { + let len = self.read_u32()? as usize; + self.read = 0; + let r = f(self); + match r { + Err(e) => { + // skip this chunk + let n = len - self.read; + if n != 0 { + self.skip(n)?; + }; + Err(e) + } + Ok(_) => Ok(()), + } + } + pub fn read_vec(&mut self, dst: &mut Vec<u8>, len: usize) -> Result<(), ReadError> { if self.data.len() < len { return Err(ReadError::Underflow { @@ -99,6 +153,7 @@ impl<'d> DataRead<'d> { } dst.extend_from_slice(&self.data[..len]); self.data = &self.data[len..]; + self.read += len; Ok(()) } @@ -137,56 +192,31 @@ impl<'d> DataRead<'d> { raw.reserve(1024); } assert_eq!(dec.total_out() as usize, raw.len()); + self.read = 0; Ok(raw) } } -#[derive(Debug)] +#[derive(Debug, Error)] pub enum ReadError { + #[error("decompressor stalled before completion")] DecompressStall, - Decompress(DecompressError), + #[error("zlib decompession failed")] + Decompress(#[from] DecompressError), + #[error("buffer underflow (expected {need} but got {have})")] Underflow { need: usize, have: usize }, - Utf8(Utf8Error), + #[error("expected {0}")] + Expected(&'static str), + #[error("malformed utf8 in string")] + Utf8 { + #[from] + source: Utf8Error, + }, } impl PartialEq for ReadError { fn eq(&self, _: &Self) -> bool { - return false; - } -} - -impl From<DecompressError> for ReadError { - fn from(value: DecompressError) -> Self { - Self::Decompress(value) - } -} - -impl From<Utf8Error> for ReadError { - fn from(err: Utf8Error) -> Self { - Self::Utf8(err) - } -} - -impl fmt::Display for ReadError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Underflow { need, have } => { - write!(f, "buffer underflow (expected {need} but got {have})") - } - Self::Decompress(..) => f.write_str("zlib decompression failed"), - Self::DecompressStall => f.write_str("decompressor stalled before completion"), - Self::Utf8(..) => f.write_str("malformed utf-8 in string"), - } - } -} - -impl Error for ReadError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Utf8(e) => Some(e), - Self::Decompress(e) => Some(e), - _ => None, - } + false } } @@ -281,7 +311,9 @@ impl<'d> DataWrite<'d> { pub fn inflate(self, to: &mut DataWrite) -> Result<(), WriteError> { // compress into the provided buffer - let WriteBuff::Vec( raw) = self.data else { unreachable!("write buffer not owned") }; + let WriteBuff::Vec(raw) = self.data else { + unreachable!("write buffer not owned") + }; let mut comp = Compress::new(Compression::default(), true); // compress the immediate buffer into a temp buffer to copy it to buff? no thanks match to.data { @@ -337,50 +369,26 @@ impl Default for DataWrite<'static> { } } -#[derive(Debug)] +#[derive(Debug, Error)] pub enum WriteError { + #[error("buffer overflow (expected {need} but got {have})")] Overflow { need: usize, have: usize }, + #[error("string too long ({len} bytes of {})", u16::MAX)] TooLong { len: usize }, - Compress(CompressError), + #[error("zlib compression failed")] + Compress { + #[from] + source: CompressError, + }, + #[error("compression overflow with {0} bytes of input remaining")] CompressEof(usize), + #[error("compressor stalled before completion")] CompressStall, } -impl From<CompressError> for WriteError { - fn from(value: CompressError) -> Self { - Self::Compress(value) - } -} - impl PartialEq for WriteError { fn eq(&self, _: &Self) -> bool { - return false; - } -} - -impl fmt::Display for WriteError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Overflow { need, have } => { - write!(f, "buffer overflow (expected {need} but got {have})") - } - Self::Compress(..) => f.write_str("zlib compression failed"), - Self::CompressEof(remain) => write!( - f, - "compression overflow with {remain} bytes of input remaining" - ), - Self::CompressStall => f.write_str("compressor stalled before completion"), - Self::TooLong { len } => write!(f, "string too long ({len} bytes of {})", u16::MAX), - } - } -} - -impl Error for WriteError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Compress(e) => Some(e), - _ => None, - } + false } } diff --git a/src/data/planet.rs b/src/data/planet.rs new file mode 100644 index 0000000..ff20bcd --- /dev/null +++ b/src/data/planet.rs @@ -0,0 +1,10 @@ +//! planets +//! +//! [source](https://github.com/Anuken/Mindustry/blob/master/core/src/mindustry/content/Planets.java) +use crate::content::numeric_enum; + +numeric_enum! { + pub enum Planet for u8 | TryFromU8Error { + Sun, Erekir, Gier, Notva, Tantros, Serpulo, Verlius + } +} diff --git a/src/data/renderer.rs b/src/data/renderer.rs index 05486d8..a1af661 100644 --- a/src/data/renderer.rs +++ b/src/data/renderer.rs @@ -1,24 +1,86 @@ //! schematic drawing -use std::io::{BufReader, Cursor}; -use std::path::Path; - +use dashmap::mapref::one::Ref; +use dashmap::DashMap; use image::codecs::png::PngDecoder; -use image::imageops::overlay; use image::{DynamicImage, RgbaImage}; +use std::io::{BufReader, Cursor}; +use std::path::{Path, PathBuf}; +use std::sync::OnceLock; use zip::ZipArchive; +use crate::block::environment::METAL_FLOOR; +use crate::data::map::Tile; use crate::team::SHARDED; -use crate::utils::image::*; +use crate::utils::ImageUtils; +use crate::Map; +pub use std::borrow::Borrow; use super::schematic::Schematic; -pub(crate) fn load(category: &str, name: &str) -> Option<RgbaImage> { - let mut p = Path::new("blocks").join(category).join(name); - p.set_extension("png"); - load_raw(p) +type Cache = DashMap<PathBuf, RgbaImage>; +fn cache() -> &'static Cache { + CACHE.get_or_init(Cache::new) +} + +pub enum ImageHolder { + Borrow(Ref<'static, PathBuf, RgbaImage>), + Own(RgbaImage), +} + +impl ImageHolder { + pub fn own(self) -> RgbaImage { + match self { + Self::Own(x) => x, + Self::Borrow(x) => x.clone(), + } + } } -pub(crate) fn load_raw(f: impl AsRef<Path>) -> Option<RgbaImage> { +impl Borrow<RgbaImage> for ImageHolder { + fn borrow(&self) -> &RgbaImage { + match self { + Self::Own(x) => x, + Self::Borrow(x) => x.value(), + } + } +} + +impl From<Option<Ref<'static, PathBuf, RgbaImage>>> for ImageHolder { + fn from(value: Option<Ref<'static, PathBuf, RgbaImage>>) -> Self { + Self::Borrow(value.unwrap()) + } +} + +impl From<Ref<'static, PathBuf, RgbaImage>> for ImageHolder { + fn from(value: Ref<'static, PathBuf, RgbaImage>) -> Self { + Self::Borrow(value) + } +} + +impl From<RgbaImage> for ImageHolder { + fn from(value: RgbaImage) -> Self { + Self::Own(value) + } +} + +static CACHE: OnceLock<Cache> = OnceLock::new(); +pub(crate) fn load(category: &str, name: &str) -> Option<Ref<'static, PathBuf, RgbaImage>> { + let key = Path::new("blocks").join(category).join(name); + let mut p = key.clone(); + use dashmap::mapref::entry::Entry::*; + Some(match cache().entry(key) { + Occupied(v) => v.into_ref().downgrade(), + Vacant(entry) => { + p.set_extension("png"); + let Some(i) = load_raw(p) else { + return None; + }; + entry.insert(i).downgrade() + } + }) +} + +fn load_raw(f: impl AsRef<Path>) -> Option<RgbaImage> { let f = std::fs::File::open(Path::new("target/out").join(f)).ok()?; let r = PngDecoder::new(BufReader::new(f)).unwrap(); Some(DynamicImage::from_decoder(r).unwrap().into_rgba8()) @@ -55,11 +117,12 @@ where { let mut c = RgbaImage::new(size.into() * 32, size.into() * 32); for suffix in suffixes { - if let Some(mut p) = load(category, &format!("{name}{suffix}")) { + if let Some(p) = load(category, &format!("{name}{suffix}")) { if suffix == &"-team" { - tint(&mut p, SHARDED.color()); + c.overlay(p.clone().tint(SHARDED.color()), 0, 0); + continue; } - image::imageops::overlay(&mut c, &p, 0, 0); + c.overlay(&p, 0, 0); } } c @@ -76,17 +139,49 @@ impl<'l> Renderer { /// s.put(0, 0, &block::distribution::DISTRIBUTOR); /// s.put(0, 3, &block::distribution::ROUTER); /// s.put(1, 3, &block::walls::COPPER_WALL); - /// let output /*: RgbaImage */ = Renderer::render(&s); + /// let output /*: RgbaImage */ = Renderer::render_schematic(&s); /// ``` - pub fn render(s: &'l Schematic<'_>) -> RgbaImage { + pub fn render_schematic(s: &'l Schematic<'_>) -> RgbaImage { load_zip(); let mut canvas = RgbaImage::new((s.width * 32).into(), (s.height * 32).into()); // fill background - repeat(&mut canvas, &load("environment", "metal-floor").unwrap()); + canvas.repeat(METAL_FLOOR.image(None).borrow()); for tile in s.block_iter() { - let x = (tile.pos.0 - ((tile.block.get_size() - 1) / 2) as u16) as i64; - let y = (s.height - tile.pos.1 - ((tile.block.get_size() / 2) + 1) as u16) as i64; - overlay(&mut canvas, &tile.image(), x * 32, y * 32); + let x = (tile.pos.0 - ((tile.block.get_size() - 1) / 2) as u16) as u32; + let y = (s.height - tile.pos.1 - ((tile.block.get_size() / 2) + 1) as u16) as u32; + canvas.overlay(tile.image().borrow(), x * 32, y * 32); + } + canvas + } + + pub fn render_map(m: &'l Map<'_>) -> RgbaImage { + load_zip(); + let mut canvas = RgbaImage::new(m.width * 8, m.height * 8); + const VEC: Vec<&Tile<'_>> = vec![]; + let mut layers = [VEC; 2]; + for tile in m.tiles.iter() { + if tile.has_building() { + layers[1].push(tile) + } else { + layers[0].push(tile) + } + } + for tiles in layers { + for tile in tiles { + let s = if let Some(build) = &tile.build { + build.block.get_size() + } else { + 1 + }; + let x = (tile.pos.0 - ((s - 1) / 2) as u16) as u32; + let y = (m.height as u16 - tile.pos.1 - ((s / 2) + 1) as u16) as u32; + canvas.overlay( + // SAFETY: surely not 0. (tile.size can never be 0). im not sure if you can load a 0 sized image.. but you might be able to. + unsafe { &tile.image().own().scale(tile.size() as u32 * 8) }, + x * 8, + y * 8, + ); + } } canvas } diff --git a/src/data/schematic.rs b/src/data/schematic.rs index 19026f2..75378ba 100644 --- a/src/data/schematic.rs +++ b/src/data/schematic.rs @@ -1,24 +1,22 @@ //! schematic parsing use std::collections::hash_map::Entry; use std::collections::HashMap; -use std::error::Error; use std::fmt::{self, Write}; use std::iter::FusedIterator; use std::slice::Iter; - -use image::RgbaImage; +use thiserror::Error; use crate::block::{self, Block, BlockRegistry, Rotation, State}; use crate::data::base64; use crate::data::dynamic::{self, DynData, DynSerializer}; use crate::data::{self, DataRead, DataWrite, GridPos, Serializer}; -use crate::item::storage::Storage as ItemStorage; +use crate::item::storage::ItemStorage; use crate::registry::RegistryEntry; /// biggest schematic -pub const MAX_DIMENSION: u16 = 128; +pub const MAX_DIMENSION: u16 = 256; /// most possible blocks -pub const MAX_BLOCKS: u32 = 128 * 128; +pub const MAX_BLOCKS: u32 = 256 * 256; /// a placement in a schematic pub struct Placement<'l> { @@ -47,7 +45,7 @@ impl<'l> Placement<'l> { } /// draws this placement in particular - pub fn image(&self) -> RgbaImage { + pub fn image(&self) -> crate::data::renderer::ImageHolder { self.block.image(self.get_state()) } @@ -602,12 +600,12 @@ impl<'l> Schematic<'l> { } } if left > 0 || top > 0 || right > 0 || bottom > 0 { - return Err(ResizeError::Truncated { + return Err(TruncatedError { right, top, left, bottom, - }); + })?; } self.width = w; self.height = h; @@ -664,25 +662,16 @@ impl<'l> Schematic<'l> { } /// error created by creating a new schematic -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Error)] pub enum NewError { + #[error("invalid schematic width ({0})")] Width(u16), + #[error("invalid schematic height ({0})")] Height(u16), } - -impl fmt::Display for NewError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Width(w) => write!(f, "invalid schematic width ({w})"), - Self::Height(h) => write!(f, "invalid schematic height ({h})"), - } - } -} - -impl Error for NewError {} - /// error created by doing stuff out of bounds -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Error)] +#[error("position {x} / {y} out of bounds {w} / {h}")] pub struct PosError { pub x: u16, pub y: u16, @@ -690,23 +679,9 @@ pub struct PosError { pub h: u16, } -impl fmt::Display for PosError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "position {x} / {y} out of bounds {w} / {h}", - x = self.x, - y = self.y, - w = self.w, - h = self.h - ) - } -} - -impl Error for PosError {} - -#[derive(Debug)] +#[derive(Debug, Error)] pub enum PlaceError { + #[error("invalid block placement {x} / {y} (size {sz}) within {w} / {h}")] Bounds { x: u16, y: u16, @@ -714,111 +689,63 @@ pub enum PlaceError { w: u16, h: u16, }, - Overlap { - x: u16, - y: u16, - }, - Deserialize(block::DeserializeError), -} - -impl From<block::DeserializeError> for PlaceError { - fn from(value: block::DeserializeError) -> Self { - PlaceError::Deserialize(value) - } + #[error("overlapping an existing block at {x} / {y}")] + Overlap { x: u16, y: u16 }, + #[error("block state deserialization failed")] + Deserialize(#[from] block::DeserializeError), } -impl fmt::Display for PlaceError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Bounds { x, y, sz, w, h } => write!( - f, - "invalid block placement {x} / {y} (size {sz}) within {w} / {h}" - ), - Self::Overlap { x, y } => write!(f, "overlapping an existing block at {x} / {y}"), - Self::Deserialize(..) => f.write_str("block state deserialization failed"), - } - } -} - -impl Error for PlaceError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - PlaceError::Deserialize(e) => Some(e), - _ => None, - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Debug, Error)] pub enum ResizeError { + #[error("invalid target width ({0})")] TargetWidth(u16), + #[error("invalid target height ({0})")] TargetHeight(u16), - XOffset { - dx: i16, - old_w: u16, - new_w: u16, - }, - YOffset { - dy: i16, - old_h: u16, - new_h: u16, - }, - Truncated { - right: u16, - top: u16, - left: u16, - bottom: u16, - }, + #[error("horizontal offset {dx} not in [-{new_w}, {old_w}]")] + XOffset { dx: i16, old_w: u16, new_w: u16 }, + #[error("vertical offset {dy} not in [-{new_h}, {old_h}]")] + YOffset { dy: i16, old_h: u16, new_h: u16 }, + #[error(transparent)] + Truncated(#[from] TruncatedError), +} + +#[derive(Error, Debug)] +pub struct TruncatedError { + right: u16, + top: u16, + left: u16, + bottom: u16, } -impl fmt::Display for ResizeError { +impl fmt::Display for TruncatedError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::TargetWidth(w) => write!(f, "invalid target width ({w})"), - Self::TargetHeight(w) => write!(f, "invalid target height ({w})"), - Self::XOffset { dx, old_w, new_w } => { - write!(f, "horizontal offset {dx} not in [-{new_w}, {old_w}]") - } - Self::YOffset { dy, old_h, new_h } => { - write!(f, "vertical offset {dy} not in [-{new_h}, {old_h}]") - } - Self::Truncated { - right, - top, - left, - bottom, - } => { - macro_rules! fmt_dir { - ($f:ident, $first:ident, $name:expr, $value:expr) => { - if $value != 0 { - if $first { - f.write_str(" (")?; - $first = false; - } else { - f.write_str(", ")?; - } - write!(f, "{}: {}", $name, $value)?; - } - }; + macro_rules! fmt_dir { + ($f:ident, $first:ident, $name:expr, $value:expr) => { + if $value != 0 { + if $first { + f.write_str(" (")?; + $first = false; + } else { + f.write_str(", ")?; + } + write!(f, "{}: {}", $name, $value)?; } + }; + } - f.write_str("truncated blocks")?; - let mut first = true; - fmt_dir!(f, first, "right", *right); - fmt_dir!(f, first, "top", *top); - fmt_dir!(f, first, "left", *left); - fmt_dir!(f, first, "bottom", *bottom); - if !first { - f.write_char(')')?; - } - Ok(()) - } + f.write_str("truncated blocks")?; + let mut first = true; + fmt_dir!(f, first, "right", self.right); + fmt_dir!(f, first, "top", self.top); + fmt_dir!(f, first, "left", self.left); + fmt_dir!(f, first, "bottom", self.bottom); + if !first { + f.write_char(')')?; } + Ok(()) } } -impl Error for ResizeError {} - impl fmt::Debug for Schematic<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self, f) @@ -1011,8 +938,8 @@ impl<'l> Serializer<Schematic<'l>> for SchematicSerializer<'l> { if version > 1 { return Err(ReadError::Version(version)); } - let mut buff = buff.deflate()?; - let mut buff = DataRead::new(&mut buff); + let buff = buff.deflate()?; + let mut buff = DataRead::new(&buff); let w = buff.read_i16()?; let h = buff.read_i16()?; if w < 0 || h < 0 || w as u16 > MAX_DIMENSION || h as u16 > MAX_DIMENSION { @@ -1028,7 +955,7 @@ impl<'l> Serializer<Schematic<'l>> for SchematicSerializer<'l> { block_table.reserve(num_table as usize); for _ in 0..num_table { let name = buff.read_utf()?; - match self.0.get(&name) { + match self.0.get(name) { None => return Err(ReadError::NoSuchBlock(name.to_owned())), Some(b) => block_table.push(b), } @@ -1113,122 +1040,44 @@ impl<'l> Serializer<Schematic<'l>> for SchematicSerializer<'l> { } } -#[derive(Debug)] +#[derive(Debug, Error)] pub enum ReadError { - Read(data::ReadError), + #[error("failed to read from buffer")] + Read(#[from] data::ReadError), + #[error("incorrect header ({0:08X})")] Header(u32), + #[error("unsupported version ({0})")] Version(u8), + #[error("invalid schematic dimensions ({0} * {1})")] Dimensions(i16, i16), + #[error("invalid block table size ({0})")] TableSize(i8), + #[error("unknown block {0:?}")] NoSuchBlock(String), + #[error("invalid total block count ({0})")] BlockCount(i32), + #[error("invalid block index ({0} / {1})")] BlockIndex(i8, usize), - BlockConfig(block::DataConvertError), - ReadState(dynamic::ReadError), - Placement(PlaceError), -} - -impl From<data::ReadError> for ReadError { - fn from(value: data::ReadError) -> Self { - Self::Read(value) - } + #[error("block config conversion failed")] + BlockConfig(#[from] block::DataConvertError), + #[error("failed to read block data")] + ReadState(#[from] dynamic::ReadError), + #[error("deserialized block could not be placed")] + Placement(#[from] PlaceError), } -impl From<dynamic::ReadError> for ReadError { - fn from(value: dynamic::ReadError) -> Self { - Self::ReadState(value) - } -} - -impl From<block::DataConvertError> for ReadError { - fn from(value: block::DataConvertError) -> Self { - Self::BlockConfig(value) - } -} - -impl From<PlaceError> for ReadError { - fn from(value: PlaceError) -> Self { - Self::Placement(value) - } -} - -impl fmt::Display for ReadError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Read(..) => f.write_str("failed to read from buffer"), - Self::Header(hdr) => write!(f, "incorrect header ({hdr:08X})"), - Self::Version(ver) => write!(f, "unsupported version ({ver})"), - Self::Dimensions(w, h) => write!(f, "invalid schematic dimensions ({w} * {h})"), - Self::TableSize(cnt) => write!(f, "invalid block table size ({cnt})"), - Self::NoSuchBlock(name) => write!(f, "unknown block {name:?}"), - Self::BlockCount(cnt) => write!(f, "invalid total block count ({cnt})"), - Self::BlockIndex(idx, cnt) => write!(f, "invalid block index ({idx} / {cnt})"), - Self::BlockConfig(..) => f.write_str("block config conversion failed"), - Self::ReadState(..) => f.write_str("failed to read block data"), - Self::Placement(..) => f.write_str("deserialized block could not be placed"), - } - } -} - -impl Error for ReadError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Read(e) => Some(e), - Self::BlockConfig(e) => Some(e), - Self::ReadState(e) => Some(e), - Self::Placement(e) => Some(e), - _ => None, - } - } -} - -#[derive(Debug)] +#[derive(Debug, Error)] pub enum WriteError { - Write(data::WriteError), + #[error("failed to write data to buffer")] + Write(#[from] data::WriteError), + #[error("tag list too long ({0})")] TagCount(usize), + #[error("block table too long ({0})")] TableSize(usize), - StateSerialize(block::SerializeError), - WriteState(dynamic::WriteError), -} - -impl From<data::WriteError> for WriteError { - fn from(value: data::WriteError) -> Self { - Self::Write(value) - } -} - -impl From<block::SerializeError> for WriteError { - fn from(value: block::SerializeError) -> Self { - Self::StateSerialize(value) - } -} - -impl From<dynamic::WriteError> for WriteError { - fn from(value: dynamic::WriteError) -> Self { - Self::WriteState(value) - } -} - -impl fmt::Display for WriteError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Write(..) => f.write_str("failed to write data to buffer"), - Self::TagCount(len) => write!(f, "tag list too long ({len})"), - Self::TableSize(len) => write!(f, "block table too long ({len})"), - Self::StateSerialize(e) => e.fmt(f), - Self::WriteState(..) => f.write_str("failed to write block data"), - } - } -} - -impl Error for WriteError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Write(e) => Some(e), - Self::StateSerialize(e) => e.source(), - _ => None, - } - } + #[error(transparent)] + StateSerialize(#[from] block::SerializeError), + #[error("failed to write block data")] + WriteState(#[from] dynamic::WriteError), } impl<'l> SchematicSerializer<'l> { @@ -1265,76 +1114,20 @@ impl<'l> SchematicSerializer<'l> { } } -#[derive(Debug)] +#[derive(Debug, Error)] pub enum R64Error { - Base64(base64::DecodeError), - Content(ReadError), + #[error("base-64 decoding failed")] + Base64(#[from] base64::DecodeError), + #[error(transparent)] + Content(#[from] ReadError), } -impl From<base64::DecodeError> for R64Error { - fn from(value: base64::DecodeError) -> Self { - Self::Base64(value) - } -} - -impl From<ReadError> for R64Error { - fn from(value: ReadError) -> Self { - Self::Content(value) - } -} - -impl fmt::Display for R64Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Base64(..) => f.write_str("base-64 decoding failed"), - Self::Content(e) => e.fmt(f), - } - } -} - -impl Error for R64Error { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Base64(e) => Some(e), - Self::Content(e) => e.source(), - } - } -} - -#[derive(Debug)] +#[derive(Debug, Error)] pub enum W64Error { - Base64(base64::EncodeError), - Content(WriteError), -} - -impl From<base64::EncodeError> for W64Error { - fn from(value: base64::EncodeError) -> Self { - Self::Base64(value) - } -} - -impl From<WriteError> for W64Error { - fn from(value: WriteError) -> Self { - Self::Content(value) - } -} - -impl fmt::Display for W64Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Base64(..) => f.write_str("base-64 encoding failed"), - Self::Content(e) => e.fmt(f), - } - } -} - -impl Error for W64Error { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Base64(e) => Some(e), - Self::Content(e) => e.source(), - } - } + #[error("base-64 encoding failed")] + Base64(#[from] base64::EncodeError), + #[error(transparent)] + Content(#[from] WriteError), } pub struct PosIter { diff --git a/src/data/sector.rs b/src/data/sector.rs new file mode 100644 index 0000000..ac8c471 --- /dev/null +++ b/src/data/sector.rs @@ -0,0 +1,44 @@ +//! sectors +//! +//! [source](https://github.com/Anuken/Mindustry/blob/master/core/src/mindustry/content/SectorPresets.java) +use crate::content::numeric_enum; + +numeric_enum! { + pub enum Sector for u8 | TryFromU8Error { + GroundZero, + SaltFlats, + FrozenForest, + BiomassFacility, + Craters, + RuinousShores, + WindsweptIslands, + StainedMountains, + ExtractionOutpost, + Coastline, + NavalFortress, + FungalPass, + Overgrowth, + TarFields, + Impact0078, + DesolateRift, + NuclearComplex, + PlanetaryTerminal, + Onset, + Aegis, + Lake, + Intersect, + Atlas, + Split, + Basin, + Marsh, + Peaks, + Ravine, + CalderaErekir, + Stronghold, + Crevice, + Siege, + Crossroads, + Karst, + Origin, + } +} diff --git a/src/data/weather.rs b/src/data/weather.rs new file mode 100644 index 0000000..8df9f59 --- /dev/null +++ b/src/data/weather.rs @@ -0,0 +1,10 @@ +//! weathers +//! +//! [source](https://github.com/Anuken/Mindustry/blob/master/core/src/mindustry/content/Weathers.java) +use crate::content::numeric_enum; + +numeric_enum! { + pub enum Weather for u8 | TryFromU8Error { + Snow, Rain, Sandstorm, Sporestorm, Fog, SuspendParticles + } +} diff --git a/src/exe/draw.rs b/src/exe/draw.rs index 6a061d6..0a95634 100644 --- a/src/exe/draw.rs +++ b/src/exe/draw.rs @@ -18,7 +18,7 @@ pub fn main(args: Args) { if !first || need_space { println!(); } - Renderer::render(&s).save("x.png").unwrap(); + Renderer::render_schematic(&s).save("x.png").unwrap(); } // continue processing literals & maybe interactive mode Err(e) => { diff --git a/src/exe/map.rs b/src/exe/map.rs new file mode 100644 index 0000000..13955cd --- /dev/null +++ b/src/exe/map.rs @@ -0,0 +1,22 @@ +use mindus::data::DataRead; +use mindus::{build_registry, Renderer}; +use mindus::{MapSerializer, Serializer}; +use std::env::Args; + +use super::print_err; +pub fn main(args: Args) { + let reg = build_registry(); + let mut ms = MapSerializer(®); + + // process schematics from command line + for curr in args { + if let Ok(s) = std::fs::read(curr) { + match ms.deserialize(&mut DataRead::new(&s)) { + Err(e) => print_err!(e, "fail"), + Ok(m) => { + Renderer::render_map(&m).save("x.png").unwrap(); + } + } + } + } +} diff --git a/src/exe/mod.rs b/src/exe/mod.rs index 0159b96..e56922a 100644 --- a/src/exe/mod.rs +++ b/src/exe/mod.rs @@ -1,4 +1,5 @@ mod draw; +mod map; mod print; macro_rules! print_err { @@ -30,6 +31,7 @@ fn main() { None => eprintln!("Not enough arguments, valid commands are: draw, print"), Some(s) if s == "print" => print::main(args), Some(s) if s == "draw" => draw::main(args), + Some(s) if s == "map" => map::main(args), Some(s) => eprintln!("Unknown argument {s}, valid commands are: draw, print"), } } diff --git a/src/fluid/mod.rs b/src/fluid/mod.rs index 45254b9..7437744 100644 --- a/src/fluid/mod.rs +++ b/src/fluid/mod.rs @@ -1,3 +1,6 @@ +//! fluids +//! +//! [source](https://github.com/Anuken/Mindustry/blob/master/core/src/mindustry/content/Liquids.java) use crate::content::color_content_enum; color_content_enum! { diff --git a/src/item/mod.rs b/src/item/mod.rs index 863d3b0..5d29176 100644 --- a/src/item/mod.rs +++ b/src/item/mod.rs @@ -1,4 +1,6 @@ //! the different kinds of items +//! +//! [source](https://github.com/Anuken/Mindustry/blob/master/core/src/mindustry/content/Items.java) pub mod storage; use crate::content::color_content_enum; color_content_enum! { diff --git a/src/item/storage.rs b/src/item/storage.rs index 590c399..45397f5 100644 --- a/src/item/storage.rs +++ b/src/item/storage.rs @@ -1,45 +1,98 @@ use std::error::Error; use std::fmt; use std::iter::{Enumerate, FusedIterator}; +use std::marker::PhantomData; use std::slice; use crate::item; #[derive(Clone, Debug, Eq)] -/// holds item counts -pub struct Storage { +/// stores data +pub struct Storage<T> { base: Vec<u32>, total: u64, + holds: PhantomData<T>, } -impl Storage { - #[must_use] - pub const fn new() -> Self { +pub type ItemStorage = Storage<item::Type>; + +impl<T> Default for Storage<T> { + fn default() -> Self { Self { - base: Vec::new(), + base: Vec::default(), total: 0, + holds: Default::default(), } } +} +impl<T> Storage<T> +where + u16: From<T>, +{ #[must_use] - pub fn is_empty(&self) -> bool { - self.total == 0 + /// create a new storage + /// + /// ``` + /// # use mindus::item::storage::ItemStorage; + /// // ItemStorage is a alias to Storage<Item> + /// let s = ItemStorage::new(); + /// ``` + pub fn new() -> Self { + Self::default() } #[must_use] - pub fn get_total(&self) -> u64 { - self.total + /// check if its empty + /// + /// ``` + /// # use mindus::item::storage::ItemStorage; + /// # use mindus::item; + /// + /// let mut s = ItemStorage::new(); + /// assert!(s.is_empty()); + /// s.set(item::Type::Copper, 500); + /// assert!(!s.is_empty()); + /// s.sub(item::Type::Copper, 500, 0); + /// assert!(s.is_empty()); + /// ``` + pub fn is_empty(&self) -> bool { + self.total == 0 } + /// get item count of certain element + /// + /// ``` + /// # use mindus::item::storage::ItemStorage; + /// # use mindus::item; + /// + /// let mut s = ItemStorage::new(); + /// assert!(s.get(item::Type::Coal) == 0); + /// s.set(item::Type::Coal, 500); + /// assert!(s.get(item::Type::Titanium) == 0); + /// assert!(s.get(item::Type::Coal) == 500); + /// s.sub(item::Type::Coal, 500, 0); + /// assert!(s.get(item::Type::Coal) == 0); + /// ``` #[must_use] - pub fn get(&self, ty: item::Type) -> u32 { + pub fn get(&self, ty: T) -> u32 { match self.base.get(u16::from(ty) as usize) { None => 0, Some(cnt) => *cnt, } } - - pub fn set(&mut self, ty: item::Type, count: u32) -> u32 { + /// set item count of certain element + /// + /// ``` + /// # use mindus::item::storage::ItemStorage; + /// # use mindus::item; + /// + /// let mut s = ItemStorage::new(); + /// s.set(item::Type::Coal, 500); + /// s.set(item::Type::Copper, 500); + /// assert!(s.get(item::Type::Copper) == 500); + /// ``` + pub fn set(&mut self, ty: T, count: u32) -> u32 { let idx = u16::from(ty) as usize; match self.base.get_mut(idx) { None => { @@ -57,7 +110,21 @@ impl Storage { } } - pub fn add(&mut self, ty: item::Type, add: u32, max: u32) -> (u32, u32) { + /// add to a certain elements item count, capping. + /// + /// ``` + /// # use mindus::item::storage::ItemStorage; + /// # use mindus::item; + /// + /// let mut s = ItemStorage::new(); + /// s.add(item::Type::Coal, 500, 500); + /// assert!(s.get(item::Type::Coal) == 500); + /// s.add(item::Type::Coal, 500, 10000); + /// assert!(s.get(item::Type::Coal) == 1000); + /// s.add(item::Type::Coal, 500, 1250); + /// assert!(s.get(item::Type::Coal) == 1250); + /// ``` + pub fn add(&mut self, ty: T, add: u32, max: u32) -> (u32, u32) { let idx = u16::from(ty) as usize; match self.base.get_mut(idx) { None => { @@ -80,12 +147,8 @@ impl Storage { } } - pub fn try_add( - &mut self, - ty: item::Type, - add: u32, - max: u32, - ) -> Result<(u32, u32), TryAddError> { + /// like [`Storage::add`] but fails + pub fn try_add(&mut self, ty: T, add: u32, max: u32) -> Result<(u32, u32), TryAddError> { let idx = u16::from(ty) as usize; match self.base.get_mut(idx) { None => { @@ -95,12 +158,7 @@ impl Storage { self.total += u64::from(add); Ok((add, add)) } else { - Err(TryAddError { - ty, - have: 0, - add, - max, - }) + Err(TryAddError { have: 0, add, max }) } } Some(curr) => { @@ -110,7 +168,6 @@ impl Storage { Ok((add, *curr)) } else { Err(TryAddError { - ty, have: *curr, add, max, @@ -120,7 +177,7 @@ impl Storage { } } - pub fn sub(&mut self, ty: item::Type, sub: u32, min: u32) -> (u32, u32) { + pub fn sub(&mut self, ty: T, sub: u32, min: u32) -> (u32, u32) { match self.base.get_mut(u16::from(ty) as usize) { None => (0, 0), Some(curr) => { @@ -136,20 +193,10 @@ impl Storage { } } - pub fn try_sub( - &mut self, - ty: item::Type, - sub: u32, - min: u32, - ) -> Result<(u32, u32), TrySubError> { + pub fn try_sub(&mut self, ty: T, sub: u32, min: u32) -> Result<(u32, u32), TrySubError> { let idx = u16::from(ty) as usize; match self.base.get_mut(idx) { - None => Err(TrySubError { - ty, - have: 0, - sub, - min, - }), + None => Err(TrySubError { have: 0, sub, min }), Some(curr) => { if *curr >= min && *curr - min >= sub { *curr -= sub; @@ -157,7 +204,6 @@ impl Storage { Ok((sub, *curr)) } else { Err(TrySubError { - ty, have: *curr, sub, min, @@ -167,9 +213,9 @@ impl Storage { } } - pub fn add_all(&mut self, other: &Storage, max_each: u32) -> (u64, u64) { + pub fn add_all(&mut self, other: &Storage<T>, max_each: u32) -> (u64, u64) { let mut added = 0u64; - if max_each > 0 && other.get_total() > 0 { + if max_each > 0 && other.total > 0 { let mut iter = other.base.iter().enumerate(); // resize our vector only once and if necessary let (last, add_last) = iter.rfind(|(_, n)| **n != 0).unwrap(); @@ -198,9 +244,9 @@ impl Storage { (added, self.total) } - pub fn pull_all(&mut self, other: &mut Storage, max_each: u32) -> (u64, u64, u64) { + pub fn pull_all(&mut self, other: &mut Storage<T>, max_each: u32) -> (u64, u64, u64) { let mut added = 0u64; - if max_each > 0 && other.get_total() > 0 { + if max_each > 0 && other.total > 0 { let mut iter = other.base.iter_mut().enumerate(); // resize our vector only once and if necessary let (last, add_last) = iter.rfind(|(_, n)| **n != 0).unwrap(); @@ -232,9 +278,9 @@ impl Storage { (added, self.total, other.total) } - pub fn sub_all(&mut self, other: &Storage, min_each: u32) -> (u64, u64) { + pub fn sub_all(&mut self, other: &Storage<T>, min_each: u32) -> (u64, u64) { let mut subbed = 0u64; - if self.get_total() > 0 && other.get_total() > 0 { + if self.total > 0 && other.total > 0 { // no need for resizing, we only remove // process items by increasing ID for (idx, sub) in other.base.iter().enumerate() { @@ -254,9 +300,9 @@ impl Storage { (subbed, self.total) } - pub fn diff_all(&mut self, other: &mut Storage, min_each: u32) -> (u64, u64, u64) { + pub fn diff_all(&mut self, other: &mut Storage<T>, min_each: u32) -> (u64, u64, u64) { let mut subbed = 0u64; - if self.get_total() > 0 && other.get_total() > 0 { + if self.total > 0 && other.total > 0 { // no need for resizing, we only remove // consider only indexes present in both let end = self.base.len().min(other.base.len()); @@ -300,7 +346,7 @@ impl Storage { } // manual because padding with zeros doesn't affect equality -impl PartialEq for Storage { +impl<T> PartialEq for Storage<T> { fn eq(&self, other: &Self) -> bool { let mut li = self.base.iter().fuse(); let mut ri = other.base.iter().fuse(); @@ -317,16 +363,17 @@ impl PartialEq for Storage { } } -impl fmt::Display for Storage { +impl<T> fmt::Display for Storage<T> +where + u16: From<T>, +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut first = true; - for (ty, cnt) in self.iter_nonzero() { - if first { - first = false; - } else { - f.write_str(", ")?; - } + let mut iter = self.iter_nonzero(); + if let Some((ty, cnt)) = iter.next() { write!(f, "{cnt} {ty}")?; + for (ty, cnt) in iter { + write!(f, ", {cnt} {ty}")?; + } } Ok(()) } @@ -334,7 +381,6 @@ impl fmt::Display for Storage { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct TryAddError { - pub ty: item::Type, pub have: u32, pub add: u32, pub max: u32, @@ -344,8 +390,8 @@ impl fmt::Display for TryAddError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "adding {} {:?} to current {} would exceed {}", - self.add, self.ty, self.have, self.max + "adding {:?} to current {} would exceed {}", + self.add, self.have, self.max ) } } @@ -354,7 +400,6 @@ impl Error for TryAddError {} #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct TrySubError { - pub ty: item::Type, pub have: u32, pub sub: u32, pub min: u32, @@ -364,8 +409,8 @@ impl fmt::Display for TrySubError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "removing {} {:?} from current {} would drop below {}", - self.sub, self.ty, self.have, self.min + "removing {} from current {} would drop below {}", + self.sub, self.have, self.min ) } } @@ -3,16 +3,17 @@ mod access; pub mod block; mod content; pub mod data; -mod fluid; +pub mod fluid; pub mod item; mod logic; -mod modifier; +pub mod modifier; mod registry; mod team; -mod unit; +pub mod unit; mod utils; pub use block::build_registry; pub use data::dynamic::DynData; +pub use data::map::{Map, MapSerializer}; pub use data::renderer::Renderer; pub use data::schematic::{Schematic, SchematicSerializer}; pub use data::Serializer; diff --git a/src/logic/mod.rs b/src/logic/mod.rs index 967ca22..b8a98fc 100644 --- a/src/logic/mod.rs +++ b/src/logic/mod.rs @@ -23,7 +23,8 @@ macro_rules!match_select } impl LogicField { - #[must_use] pub fn is_readable(&self) -> bool { + #[must_use] + pub fn is_readable(&self) -> bool { match_select!( self, LogicField, @@ -73,7 +74,8 @@ impl LogicField { ) } - #[must_use] pub fn is_writable(&self) -> bool { + #[must_use] + pub fn is_writable(&self) -> bool { match_select!(self, LogicField, Enabled, Shoot, ShootP, Config, Color) } } diff --git a/src/modifier.rs b/src/modifier.rs index 0459ce2..bf1d867 100644 --- a/src/modifier.rs +++ b/src/modifier.rs @@ -1,4 +1,6 @@ //! modifiers units can be afflicted with +//! +//! [source](https://github.com/Anuken/Mindustry/blob/master/core/src/mindustry/content/SectorPresets.java) use crate::content::content_enum; content_enum! { diff --git a/src/team.rs b/src/team.rs index f25f7a4..596798f 100644 --- a/src/team.rs +++ b/src/team.rs @@ -121,14 +121,14 @@ impl Team { Rgb(color_hex::color_from_hex!($x)) }; } - match self { - &SHARDED => h!("ffd37f"), - &DERELICT => h!("4d4e58"), - &CRUX => h!("f25555"), - &MALIS => h!("a27ce5"), - &GREEN => h!("54d67d"), - &BLUE => h!("6c87fd"), - &NEOPLASTIC => h!("e05438"), + match *self { + SHARDED => h!("ffd37f"), + DERELICT => h!("4d4e58"), + CRUX => h!("f25555"), + MALIS => h!("a27ce5"), + GREEN => h!("54d67d"), + BLUE => h!("6c87fd"), + NEOPLASTIC => h!("e05438"), _ => h!("a9a9a9"), } } diff --git a/src/unit/mod.rs b/src/unit/mod.rs index c063d2a..603b376 100644 --- a/src/unit/mod.rs +++ b/src/unit/mod.rs @@ -1,3 +1,6 @@ +//! units +//! +//! [source](https://github.com/Anuken/Mindustry/blob/master/core/src/mindustry/content/UnitTypes.java) use crate::content::content_enum; content_enum! { diff --git a/src/utils/image.rs b/src/utils/image.rs index 867d1fc..de6cf6b 100644 --- a/src/utils/image.rs +++ b/src/utils/image.rs @@ -1,27 +1,71 @@ +use fast_image_resize as fr; use image::{Rgb, Rgba, RgbaImage}; +use std::num::NonZeroU32; +pub trait ImageUtils { + fn tint(&mut self, color: Rgb<u8>) -> &mut Self; -pub fn tint(image: &mut RgbaImage, color: Rgb<u8>) { - let [tr, tg, tb] = [ - color[0] as f32 / 255.0, - color[1] as f32 / 255.0, - color[2] as f32 / 255.0, - ]; - for Rgba([r, g, b, _]) in image.pixels_mut() { - *r = (*r as f32 * tr) as u8; - *g = (*g as f32 * tg) as u8; - *b = (*b as f32 * tb) as u8; - } + fn repeat(&mut self, with: &RgbaImage) -> &mut Self; + + fn overlay(&mut self, with: &RgbaImage, x: u32, y: u32) -> &mut Self; + + unsafe fn scale(self, to: u32) -> Self; } -pub fn repeat(to: &mut RgbaImage, from: &RgbaImage) { - for x in 0..(to.width() / from.width()) { - for y in 0..(to.height() / from.height()) { - image::imageops::overlay( - to, - from, - (x * from.width()).into(), - (y * from.height()).into(), - ); +impl ImageUtils for RgbaImage { + fn tint(&mut self, color: Rgb<u8>) -> &mut Self { + let [tr, tg, tb] = [ + color[0] as f32 / 255.0, + color[1] as f32 / 255.0, + color[2] as f32 / 255.0, + ]; + for Rgba([r, g, b, _]) in self.pixels_mut() { + *r = (*r as f32 * tr) as u8; + *g = (*g as f32 * tg) as u8; + *b = (*b as f32 * tb) as u8; + } + self + } + + fn repeat(&mut self, with: &RgbaImage) -> &mut Self { + for x in 0..(self.width() / with.width()) { + for y in 0..(self.height() / with.height()) { + self.overlay(with, x * with.width(), y * with.height()); + } } + self + } + + fn overlay(&mut self, with: &RgbaImage, x: u32, y: u32) -> &mut Self { + for j in 0..with.height() { + for i in 0..with.width() { + let get = with.get_pixel(i, j); + if get[3] > 5 { + self.put_pixel(i + x, j + y, *get); + } + } + } + self + } + + /// scales a image + /// + /// SAFETY: to and width and height cannot be 0. + unsafe fn scale(self, to: u32) -> Self { + debug_assert_ne!(to, 0); + debug_assert_ne!(self.width(), 0); + debug_assert_ne!(self.height(), 0); + let to = NonZeroU32::new_unchecked(to); + let src = fr::Image::from_vec_u8( + NonZeroU32::new_unchecked(self.width()), + NonZeroU32::new_unchecked(self.height()), + self.into_vec(), + fr::PixelType::U8x4, + ) + .unwrap(); + let mut dst = fr::Image::new(to, to, fr::PixelType::U8x4); + fr::Resizer::new(fr::ResizeAlg::Nearest) + .resize(&src.view(), &mut dst.view_mut()) + .unwrap(); + RgbaImage::from_raw(to.get(), to.get(), dst.into_vec()).unwrap() } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 14995d4..4d12b15 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1 +1,2 @@ pub mod image; +pub use self::image::ImageUtils; |