mindustry logic execution, map- and schematic- parsing and rendering
Diffstat (limited to 'src/block/distribution.rs')
| -rw-r--r-- | src/block/distribution.rs | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/src/block/distribution.rs b/src/block/distribution.rs new file mode 100644 index 0000000..c6e35e8 --- /dev/null +++ b/src/block/distribution.rs @@ -0,0 +1,320 @@ +use std::any::Any; +use std::error::Error; +use std::fmt; + +use image::{Rgba, RgbaImage}; + +use crate::block::simple::{cost, state_impl, BuildCost, SimpleBlock}; +use crate::block::{ + impl_block, make_register, BlockLogic, DataConvertError, DeserializeError, SerializeError, +}; +use crate::content; +use crate::data::dynamic::{DynData, DynType}; +use crate::data::renderer::load; +use crate::data::GridPos; +use crate::item; +use crate::item::storage::Storage; + +make_register! { + "conveyor" => SimpleBlock::new(1, false, cost!(Copper: 1)); + "titanium-conveyor" => SimpleBlock::new(1, false, cost!(Copper: 1, Lead: 1, Titanium: 1)); + "plastanium-conveyor" => SimpleBlock::new(1, false, cost!(Graphite: 1, Silicon: 1, Plastanium: 1)); + "armored-conveyor" => SimpleBlock::new(1, false, cost!(Metaglass: 1, Thorium: 1, Plastanium: 1)); + "junction" => SimpleBlock::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)); + "inverted-sorter" => ItemBlock::new(1, true, cost!(Copper: 2, Lead: 2)); + "router" => SimpleBlock::new(1, true, cost!(Copper: 3)); + "distributor" => SimpleBlock::new(2, true, cost!(Copper: 4, Lead: 4)); + "overflow-gate" => SimpleBlock::new(1, true, cost!(Copper: 4, Lead: 2)); + "underflow-gate" => SimpleBlock::new(1, true, cost!(Copper: 4, Lead: 2)); + "mass-driver" => BridgeBlock::new(3, true, cost!(Lead: 125, Titanium: 125, Thorium: 50, Silicon: 75), 55, false); + "duct" => SimpleBlock::new(1, false, cost!(Beryllium: 1)); + "armored-duct" => SimpleBlock::new(1, false, cost!(Beryllium: 2, Tungsten: 1)); + "duct-router" => ItemBlock::new(1, true, cost!(Beryllium: 10)); + "overflow-duct" => SimpleBlock::new(1, true, cost!(Graphite: 8, Beryllium: 8)); + "underflow-duct" => SimpleBlock::new(1, true, cost!(Graphite: 8, Beryllium: 8)); + "duct-bridge" => BridgeBlock::new(1, true, cost!(Beryllium: 20), 3, true); + "duct-unloader" => ItemBlock::new(1, true, cost!(Graphite: 20, Silicon: 20, Tungsten: 10)); + "surge-conveyor" => SimpleBlock::new(1, false, cost!(SurgeAlloy: 1, Tungsten: 1)); + "surge-router" => SimpleBlock::new(1, false, cost!(SurgeAlloy: 5, Tungsten: 1)); // not symmetric + "unit-cargo-loader" => SimpleBlock::new(3, true, cost!(Silicon: 80, SurgeAlloy: 50, Oxide: 20)); + "unit-cargo-unload-point" => ItemBlock::new(2, true, cost!(Silicon: 60, Tungsten: 60)); + // sandbox only + "item-source" => ItemBlock::new(1, true, &[]); + "item-void" => SimpleBlock::new(1, true, &[]); +} + +pub struct ItemBlock { + size: u8, + symmetric: bool, + build_cost: BuildCost, +} + +impl ItemBlock { + #[must_use] + pub const fn new(size: u8, symmetric: bool, build_cost: BuildCost) -> Self { + assert!(size != 0, "invalid size"); + Self { + size, + symmetric, + build_cost, + } + } + + state_impl!(pub Option<item::Type>); +} + +impl BlockLogic for ItemBlock { + impl_block!(); + + fn data_from_i32(&self, config: i32, _: GridPos) -> Result<DynData, DataConvertError> { + if config < 0 || config > i32::from(u16::MAX) { + return Err(DataConvertError::Custom(Box::new(ItemConvertError(config)))); + } + Ok(DynData::Content(content::Type::Item, config as u16)) + } + + fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> { + match data { + DynData::Empty => Ok(Some(Self::create_state(None))), + DynData::Content(content::Type::Item, id) => Ok(Some(Self::create_state(Some( + ItemDeserializeError::forward(item::Type::try_from(id))?, + )))), + DynData::Content(have, ..) => Err(DeserializeError::Custom(Box::new( + ItemDeserializeError::ContentType(have), + ))), + _ => Err(DeserializeError::InvalidType { + have: data.get_type(), + expect: DynType::Content, + }), + } + } + + fn clone_state(&self, state: &dyn Any) -> Box<dyn Any> { + let state = Self::get_state(state); + Box::new(Self::create_state(*state)) + } + + fn mirror_state(&self, _: &mut dyn Any, _: bool, _: bool) {} + + fn rotate_state(&self, _: &mut dyn Any, _: bool) {} + + fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> { + match Self::get_state(state) { + None => Ok(DynData::Empty), + Some(item) => Ok(DynData::Content(content::Type::Item, (*item).into())), + } + } + + fn draw(&self, category: &str, name: &str, state: Option<&dyn Any>) -> Option<RgbaImage> { + let mut p = load(category, name).unwrap(); + if let Some(state) = state { + if let Some(s) = Self::get_state(state) { + let item_c = s.color(); + let [tr, tg, tb] = [ + item_c[0] as f32 / 255.0, + item_c[1] as f32 / 255.0, + item_c[2] as f32 / 255.0, + ]; + let mut top = load(category, "center").unwrap(); + for Rgba([r, g, b, ref a]) in top.pixels_mut() { + if a > &254 { + *r = (*r as f32 * tr) as u8; + *g = (*g as f32 * tg) as u8; + *b = (*b as f32 * tb) as u8; + } + } + + image::imageops::overlay(&mut p, &top, 0, 0); + return Some(p); + } + } + if name == "unloader" { + return Some(p); + } + let mut null = load("distribution", "cross-full").unwrap(); + image::imageops::overlay(&mut null, &p, 0, 0); + Some(null) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ItemConvertError(pub i32); + +impl fmt::Display for ItemConvertError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "invalid config ({}) for item", self.0) + } +} + +impl Error for ItemConvertError {} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ItemDeserializeError { + ContentType(content::Type), + NotFound(item::TryFromU16Error), +} + +impl ItemDeserializeError { + pub fn forward<T, E: Into<Self>>(result: Result<T, E>) -> Result<T, DeserializeError> { + match result { + Ok(v) => Ok(v), + Err(e) => Err(DeserializeError::Custom(Box::new(e.into()))), + } + } +} + +impl From<item::TryFromU16Error> for ItemDeserializeError { + fn from(err: item::TryFromU16Error) -> Self { + Self::NotFound(err) + } +} + +impl fmt::Display for ItemDeserializeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::ContentType(have) => write!( + f, + "expected content {:?} but got {have:?}", + content::Type::Item + ), + Self::NotFound(..) => f.write_str("target item not found"), + } + } +} + +impl Error for ItemDeserializeError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::NotFound(e) => Some(e), + _ => None, + } + } +} + +pub struct BridgeBlock { + size: u8, + symmetric: bool, + build_cost: BuildCost, + range: u16, + ortho: bool, +} + +type Point2 = (i32, i32); + +impl BridgeBlock { + #[must_use] + pub const fn new( + size: u8, + symmetric: bool, + build_cost: BuildCost, + range: u16, + ortho: bool, + ) -> Self { + assert!(size != 0, "invalid size"); + assert!(range != 0, "invalid range"); + Self { + size, + symmetric, + build_cost, + range, + ortho, + } + } + + state_impl!(pub Option<Point2>); +} + +impl BlockLogic for BridgeBlock { + impl_block!(); + + fn data_from_i32(&self, config: i32, pos: GridPos) -> Result<DynData, DataConvertError> { + let (x, y) = ((config >> 16) as i16, config as i16); + if x < 0 || y < 0 { + return Err(DataConvertError::Custom(Box::new(BridgeConvertError { + x, + y, + }))); + } + let dx = i32::from(x) - i32::from(pos.0); + let dy = i32::from(y) - i32::from(pos.1); + Ok(DynData::Point2(dx, dy)) + } + + fn deserialize_state(&self, data: DynData) -> Result<Option<Box<dyn Any>>, DeserializeError> { + match data { + DynData::Empty => Ok(Some(Self::create_state(None))), + DynData::Point2(dx, dy) => { + if self.ortho { + // the game uses (-worldX, -worldY) to indicate no target + // likely because the absolute target being (0, 0) means it's unlinked + if dx != 0 && dy != 0 { + return Ok(Some(Self::create_state(None))); + } + if dx > i32::from(self.range) || dx < -i32::from(self.range) { + return Ok(Some(Self::create_state(None))); + } + } + // can't check range otherwise, it depends on the target's size + Ok(Some(Self::create_state(Some((dx, dy))))) + } + _ => Err(DeserializeError::InvalidType { + have: data.get_type(), + expect: DynType::Point2, + }), + } + } + + fn clone_state(&self, state: &dyn Any) -> Box<dyn Any> { + let state = Self::get_state(state); + Box::new(Self::create_state(*state)) + } + + fn mirror_state(&self, state: &mut dyn Any, horizontally: bool, vertically: bool) { + match Self::get_state_mut(state) { + None => (), + Some((dx, dy)) => { + if horizontally { + *dx = -*dx; + } + if vertically { + *dy = -*dy; + } + } + } + } + + fn rotate_state(&self, state: &mut dyn Any, clockwise: bool) { + match Self::get_state_mut(state) { + None => (), + Some((dx, dy)) => { + let (cdx, cdy) = (*dx, *dy); + *dx = if clockwise { cdy } else { -cdy }; + *dy = if clockwise { -cdx } else { cdx }; + } + } + } + + fn serialize_state(&self, state: &dyn Any) -> Result<DynData, SerializeError> { + match Self::get_state(state) { + None => Ok(DynData::Empty), + Some((dx, dy)) => Ok(DynData::Point2(*dx, *dy)), + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct BridgeConvertError { + pub x: i16, + pub y: i16, +} + +impl fmt::Display for BridgeConvertError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "invalid coordinate ({} / {}) for bridge", self.x, self.y) + } +} + +impl Error for BridgeConvertError {} |