mindustry logic execution, map- and schematic- parsing and rendering
Diffstat (limited to 'src/data/map.rs')
-rw-r--r--src/data/map.rs462
1 files changed, 462 insertions, 0 deletions
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!()
+ }
+}