mindustry logic execution, map- and schematic- parsing and rendering
Diffstat (limited to 'src/block/mod.rs')
| -rw-r--r-- | src/block/mod.rs | 642 |
1 files changed, 642 insertions, 0 deletions
diff --git a/src/block/mod.rs b/src/block/mod.rs new file mode 100644 index 0000000..15b6d97 --- /dev/null +++ b/src/block/mod.rs @@ -0,0 +1,642 @@ +//! deal with blocks. +//! +//! categorized as mindustry categorizes them in its assets folder, for easy drawing. +//! +//! with the exception of sandbox, that is. +use bobbin_bits::U4::{self, B0000, B0001, B0010, B0100, B1000}; +use std::error::Error; +use std::fmt; + +use crate::data::dynamic::{DynData, DynType}; +use crate::data::map::{Build, EntityMapping}; +use crate::data::{self, renderer::*, CompressError}; +use crate::data::{DataRead, GridPos, ReadError as DataReadError}; +use crate::item::storage::ItemStorage; +use crate::registry::RegistryEntry; + +macro_rules! mods { + ($($mod:ident)*) => { + $(pub mod $mod;)* + + pub mod all { + $(pub use crate::block::$mod::*;)* + pub use super::simple::BasicBlock; + } + } +} + +mods! { + campaign content defense distribution drills environment liquid logic payload power production storage turrets walls units +} + +mod simple; + +macro_rules! disp { + ($($k:ident,)+) => { + use all::{$($k,)+}; + #[enum_dispatch::enum_dispatch] + pub(crate) enum BlockLogicEnum { + $($k,)+ + } + #[const_trait] + pub trait ConstFrom<T>: Sized { + fn fro(value: T) -> Self; + } + $( + impl const ConstFrom<$k> for BlockLogicEnum { + fn fro(v: $k) -> Self { + BlockLogicEnum::$k(v) + } + } + )+ + + /*impl std::fmt::Debug for BlockLogicEnum { + fn fmt(&self, w: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + $(BlockLogicEnum::$k { .. } => write!(w, stringify!($k)),)+ + } + } + }*/ + } +} + +disp! { + BasicBlock, + WallBlock, + DuctBlock, + BridgeBlock, + ItemBlock, + ProductionBlock, + SeparatorBlock, + StackConveyor, + HeatCrafter, + ConnectorBlock, + ItemTurret, + ConveyorBlock, + WallDrillBlock, + DrillBlock, + NuclearGeneratorBlock, + GeneratorBlock, + ConduitBlock, + HeatedBlock, + RadarBlock, + ShieldBlock, + PointDefenseTurret, + JunctionBlock, + Turret, + MemoryBlock, + MessageLogic, + ConstructorBlock, + AssemblerBlock, + UnitFactory, + SimpleDuctBlock, + SurgeRouter, + SimplePayloadBlock, + PayloadConveyor, + ImpactReactorBlock, + Neoplasia, + DiodeBlock, + HeatConduit, + ContinousTurret, + TractorBeamTurret, + AssemblerModule, + RepairTurret, + FluidBlock, + CanvasBlock, + SwitchLogic, + ProcessorLogic, + PayloadBlock, + LampBlock, + DoorBlock, +} + +pub trait Cast { + fn downcast_ref(state: &State) -> Option<&Self>; + fn downcast_mut(state: &mut State) -> Option<&mut Self>; +} + +macro_rules! stater { + ($($k: ident($v: ty),)+) => { + #[derive(Debug, Clone)] + pub enum State { + $($k($v),)+ + } + + $( + impl From<$v> for State { + fn from(v: $v) -> State { State::$k(v) } + } + + impl Cast for $v { + fn downcast_ref(state: &State) -> Option<&Self> { + match state { + State::$k(v) => Some(v), + _ => None, + } + } + fn downcast_mut(state: &mut State) -> Option<&mut Self> { + match state { + State::$k(v) => Some(v), + _ => None, + } + } + } + )+ + } +} + +stater! { + // TODO deoptionize + String(String), + Item(Option<crate::item::Type>), + Fluid(Option<crate::fluid::Type>), + Image(Image<Vec<u8>, 1>), + Point(Option<(i32, i32)>), + Bool(bool), + Processor(logic::ProcessorState), + Payload(payload::Payload), + Power(Vec<(i16, i16)>), + Color(power::Rgba), + Command(Option<crate::data::command::UnitCommand>), + Unit(Option<crate::unit::Type>), +} + +impl State { + pub fn downcast_ref<T: Cast>(&self) -> Option<&T> { + T::downcast_ref(self) + } + + pub fn downcast_mut<T: Cast>(&mut self) -> Option<&mut T> { + T::downcast_mut(self) + } + + pub fn new<T: Into<Self>>(from: T) -> Self { + from.into() + } +} + +#[enum_dispatch::enum_dispatch(BlockLogicEnum)] +pub trait BlockLogic { + /// mindustry blocks are the same width and height + fn get_size(&self) -> u8; + + fn is_symmetric(&self) -> bool; + + fn create_build_cost(&self) -> Option<ItemStorage>; + + fn data_from_i32(&self, config: i32, pos: GridPos) -> Result<DynData, DataConvertError>; + + fn deserialize_state(&self, data: DynData) -> Result<Option<State>, DeserializeError>; + + #[allow(unused_variables)] + fn mirror_state(&self, state: &mut State, horizontally: bool, vertically: bool) {} + + #[allow(unused_variables)] + fn rotate_state(&self, state: &mut State, clockwise: bool) {} + + fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError>; + + #[allow(unused_variables)] + fn draw( + &self, + name: &str, + state: Option<&State>, + context: Option<&RenderingContext>, + rot: Rotation, + scale: Scale, + ) -> ImageHolder<4> { + unimplemented!("{name}") + } + + #[allow(unused_variables)] + fn read( + &self, + build: &mut Build, + reg: &BlockRegistry, + mapping: &EntityMapping, + buff: &mut DataRead, + ) -> Result<(), DataReadError> { + Ok(()) + } +} + +// i wish i could derive +macro_rules! impl_block { + () => { + fn get_size(&self) -> u8 { + self.size + } + + fn is_symmetric(&self) -> bool { + self.symmetric + } + + fn create_build_cost(&self) -> Option<$crate::item::storage::ItemStorage> { + if self.build_cost.is_empty() { + None + } else { + let mut storage = crate::item::storage::Storage::new(); + for (ty, cnt) in self.build_cost { + storage.add(*ty, *cnt, u32::MAX); + } + Some(storage) + } + } + }; +} +pub(crate) use impl_block; + +#[derive(Debug, thiserror::Error)] +pub enum DataConvertError { + #[error(transparent)] + Custom(#[from] Box<dyn Error + Sync + Send>), +} + +impl DataConvertError { + pub fn forward<T, E: Error + Sync + Send + 'static>(result: Result<T, E>) -> Result<T, Self> { + match result { + Ok(v) => Ok(v), + Err(e) => Err(Self::Custom(Box::new(e))), + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum DeserializeError { + #[error(transparent)] + DecompressError(#[from] data::DecompressError), + #[error("expected type {expect:?} but got {have:?}")] + InvalidType { have: DynType, expect: DynType }, + #[error(transparent)] + Custom(#[from] Box<dyn Error + Sync + Send>), +} + +impl DeserializeError { + pub fn forward<T, E: Error + Sync + Send + 'static>(result: Result<T, E>) -> Result<T, Self> { + match result { + Ok(v) => Ok(v), + Err(e) => Err(Self::Custom(Box::new(e))), + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum SerializeError { + #[error(transparent)] + Custom(#[from] Box<dyn Error + Sync + Send>), + #[error(transparent)] + Compress(#[from] CompressError), +} + +impl SerializeError { + pub fn forward<T, E: Error + Sync + Send + 'static>(result: Result<T, E>) -> Result<T, Self> { + match result { + Ok(v) => Ok(v), + Err(e) => Err(Self::Custom(Box::new(e))), + } + } +} + +/// a block. put it in stuff! +pub struct Block { + image: Option<[Image<&'static [u8], 4>; 3]>, + name: &'static str, + logic: BlockLogicEnum, +} + +impl PartialEq for Block { + fn eq(&self, rhs: &Block) -> bool { + self.name == rhs.name + } +} + +impl Block { + /// create a new block + #[must_use] + #[inline] + pub(crate) const fn new( + name: &'static str, + logic: BlockLogicEnum, + image: Option<[Image<&'static [u8], 4>; 3]>, + ) -> Self { + Self { image, name, logic } + } + + /// this blocks name + /// ``` + /// assert!(mindus::block::distribution::DISTRIBUTOR.name() == "distributor") + /// ``` + #[must_use] + #[inline] + pub const fn name(&self) -> &'static str { + self.name + } + + /// should you send context to [`image`]? + #[must_use] + #[inline] + pub fn wants_context(&self) -> bool { + use BlockLogicEnum::*; + matches!( + self.logic, + ConveyorBlock(..) | DuctBlock(..) | StackConveyor(..) | ConduitBlock(..) + ) + } + + /// draw this block, with this state + #[must_use] + #[inline] + pub fn image( + &self, + state: Option<&State>, + context: Option<&RenderingContext>, + rot: Rotation, + scale: Scale, + ) -> ImageHolder<4> { + if let Some(imgs) = &self.image { + return ImageHolder::from((imgs[scale as usize]).copy()); + } + self.logic.draw(self.name, state, context, rot, scale) + } + + /// size. + #[must_use] + #[inline] + pub fn get_size(&self) -> u8 { + self.logic.get_size() + } + + /// does it matter if its rotated + #[must_use] + #[inline] + pub fn is_symmetric(&self) -> bool { + self.logic.is_symmetric() + } + + /// cost + #[must_use] + #[inline] + pub fn get_build_cost(&self) -> Option<ItemStorage> { + self.logic.create_build_cost() + } + + pub(crate) fn data_from_i32( + &self, + config: i32, + pos: GridPos, + ) -> Result<DynData, DataConvertError> { + self.logic.data_from_i32(config, pos) + } + + pub(crate) fn deserialize_state( + &self, + data: DynData, + ) -> Result<Option<State>, DeserializeError> { + self.logic.deserialize_state(data) + } + + pub(crate) fn mirror_state(&self, state: &mut State, horizontally: bool, vertically: bool) { + self.logic.mirror_state(state, horizontally, vertically); + } + + pub(crate) fn rotate_state(&self, state: &mut State, clockwise: bool) { + self.logic.rotate_state(state, clockwise); + } + + pub(crate) fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError> { + self.logic.serialize_state(state) + } + + #[inline] + pub(crate) fn read( + &self, + build: &mut Build, + reg: &BlockRegistry, + mapping: &EntityMapping, + buff: &mut DataRead, + ) -> Result<(), DataReadError> { + self.logic.read(build, reg, mapping, buff) + } +} + +impl fmt::Debug for Block { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Block<{:?}>", self.name) + } +} + +impl RegistryEntry for Block { + fn get_name(&self) -> &str { + self.name + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +/// the possible rotation states of a object +#[repr(u8)] +pub enum Rotation { + Up, + Right, + Down, + Left, +} + +impl Rotation { + #[must_use] + /// count rotations + pub const fn count(self) -> u8 { + self as u8 + } + + #[must_use] + /// mask + pub const fn mask(self) -> U4 { + match self { + Rotation::Up => B1000, + Rotation::Right => B0100, + Rotation::Down => B0010, + Rotation::Left => B0001, + } + } + + #[must_use] + /// character of this rot (Right => >, Up => ^, Left => <, Down => v) + pub const fn ch(self) -> char { + match self { + Rotation::Right => '>', + Rotation::Up => '^', + Rotation::Left => '<', + Rotation::Down => 'v', + } + } + + #[must_use] + /// mirror the directions. + pub const fn mirrored(self, horizontally: bool, vertically: bool) -> Self { + match self { + Self::Right => { + if horizontally { + Self::Left + } else { + Self::Right + } + } + Self::Up => { + if vertically { + Self::Down + } else { + Self::Up + } + } + Self::Left => { + if horizontally { + Self::Right + } else { + Self::Left + } + } + Self::Down => { + if vertically { + Self::Up + } else { + Self::Down + } + } + } + } + + /// mirror in place + pub fn mirror(&mut self, horizontally: bool, vertically: bool) { + *self = self.mirrored(horizontally, vertically); + } + + #[must_use] + /// rotate the rotation + pub const fn rotated(self, clockwise: bool) -> Self { + match self { + Self::Right => { + if clockwise { + Self::Down + } else { + Self::Up + } + } + Self::Up => { + if clockwise { + Self::Right + } else { + Self::Left + } + } + Self::Left => { + if clockwise { + Self::Up + } else { + Self::Down + } + } + Self::Down => { + if clockwise { + Self::Left + } else { + Self::Right + } + } + } + } + + /// rotate the rotation in place + pub fn rotate(&mut self, clockwise: bool) { + *self = self.rotated(clockwise); + } + + #[must_use] + /// rotate 180 + pub const fn rotated_180(self) -> Self { + match self { + Self::Right => Self::Left, + Self::Up => Self::Down, + Self::Left => Self::Right, + Self::Down => Self::Up, + } + } + + /// rotate 180 in place + pub fn rotate_180(&mut self) { + *self = self.rotated_180(); + } +} + +impl From<u8> for Rotation { + fn from(val: u8) -> Self { + match val & 3 { + 0 => Self::Right, + 1 => Self::Up, + 2 => Self::Left, + _ => Self::Down, + } + } +} + +impl From<Rotation> for u8 { + fn from(rot: Rotation) -> Self { + match rot { + Rotation::Right => 0, + Rotation::Up => 1, + Rotation::Left => 2, + Rotation::Down => 3, + } + } +} + +pub type BlockRegistry<'l> = crate::registry::Registry<'l, Block>; +pub type RegisterError<'l> = crate::registry::RegisterError<'l, Block>; + +macro_rules! make_register { + ($($field:literal $op:tt $logic:expr;)+) => { paste::paste! { + $( + $crate::block::make_register!(impl $field $op $logic); + )+ + + pub(crate) fn register(reg: &mut $crate::block::BlockRegistry<'_>) { + // get the static we make + $(assert!(reg.register(&[<$field:snake:upper>]).is_ok());)+ + } + }}; + (impl $field: literal => $logic: expr) => { + paste::paste! { pub static [<$field:snake:upper>]: $crate::block::Block = $crate::block::Block::new( + $field, <crate::block::BlockLogicEnum as crate::block::ConstFrom<_>>::fro($logic), None + ); } + }; + (impl $field: literal -> $logic: expr) => { + paste::paste! { pub static [<$field:snake:upper>]: $crate::block::Block = $crate::block::Block::new( + $field, <crate::block::BlockLogicEnum as crate::block::ConstFrom<_>>::fro($logic), Some(crate::data::renderer::load!($field)) + ); } + } +} +pub(crate) use make_register; + +#[must_use] +/// create a block registry +pub fn build_registry() -> BlockRegistry<'static> { + let mut reg = BlockRegistry::default(); + register(&mut reg); + reg +} + +fn register(reg: &mut BlockRegistry<'_>) { + turrets::register(reg); + drills::register(reg); + distribution::register(reg); + storage::register(reg); + liquid::register(reg); + power::register(reg); + defense::register(reg); + production::register(reg); + payload::register(reg); + campaign::register(reg); + logic::register(reg); + walls::register(reg); + environment::register(reg); + units::register(reg); +} |