Theme

alex/beta-minecraft-rust

Minecraft Beta 1.7.3 Rewrite

BRANCH main
main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
Download file

root / crates / common / src / world / tile_entity.rs

use super::Block;
use crate::item::{self, ItemStack};
use crate::nbt::Compound;

pub const CHEST_SIZE: usize = 27;
pub const DISPENSER_SIZE: usize = 9;
pub const FURNACE_SIZE: usize = 3;

/// Block-attached state, mirroring the `TileEntity` subclasses.
#[derive(Clone, Debug, PartialEq)]
pub enum TileEntity {
    Chest(Box<[Option<ItemStack>; CHEST_SIZE]>),
    Dispenser(Box<[Option<ItemStack>; DISPENSER_SIZE]>),
    Furnace {
        items: [Option<ItemStack>; FURNACE_SIZE],
        burn_time: i16,
        cook_time: i16,
        /// Burn time of the fuel being used, for the flame gauge.
        item_burn_time: i16,
    },
    Sign([String; 4]),
    /// `TileEntityNote`: the pitch, 0 to 24, and whether it was powered at the last update.
    Note {
        note: i8,
        powered: bool,
    },
    /// `TileEntityRecordPlayer`: the record item id, 0 if empty.
    Jukebox(i32),
    MobSpawner {
        entity_id: String,
        delay: i16,
    },
    /// `TileEntityPiston`: a block moved by a piston.
    Piston {
        block: i32,
        metadata: i32,
        facing: i32,
        progress: f32,
        last_progress: f32,
        extending: bool,
        /// `shouldHeadBeRendered`: the retracting piston base itself.
        source: bool,
    },
}

impl TileEntity {
    pub fn inventory(&self) -> Option<&[Option<ItemStack>]> {
        match self {
            Self::Chest(items) => Some(&items[..]),
            Self::Dispenser(items) => Some(&items[..]),
            Self::Furnace { items, .. } => Some(items),
            _ => None,
        }
    }

    pub fn inventory_mut(&mut self) -> Option<&mut [Option<ItemStack>]> {
        match self {
            Self::Chest(items) => Some(&mut items[..]),
            Self::Dispenser(items) => Some(&mut items[..]),
            Self::Furnace { items, .. } => Some(items),
            _ => None,
        }
    }

    /// Whether the world updates it every tick (`TileEntity.updateEntity` does something).
    pub fn ticks(&self) -> bool {
        matches!(
            self,
            Self::Furnace { .. } | Self::MobSpawner { .. } | Self::Piston { .. }
        )
    }

    pub fn chest() -> Self {
        Self::Chest(Box::new([None; CHEST_SIZE]))
    }

    pub fn dispenser() -> Self {
        Self::Dispenser(Box::new([None; DISPENSER_SIZE]))
    }

    pub fn furnace() -> Self {
        Self::Furnace {
            items: [None; FURNACE_SIZE],
            burn_time: 0,
            cook_time: 0,
            item_burn_time: 0,
        }
    }

    pub fn sign() -> Self {
        Self::Sign(Default::default())
    }

    pub fn note() -> Self {
        Self::Note {
            note: 0,
            powered: false,
        }
    }

    /// `BlockPistonMoving.getTileEntity`.
    pub fn piston(block: Block, metadata: u8, facing: u8, extending: bool, source: bool) -> Self {
        Self::Piston {
            block: i32::from(block.0),
            metadata: i32::from(metadata),
            facing: i32::from(facing),
            progress: 0.0,
            last_progress: 0.0,
            extending,
            source,
        }
    }

    pub fn mob_spawner() -> Self {
        Self::MobSpawner {
            entity_id: "Pig".into(),
            delay: 20,
        }
    }

    /// The id string of `TileEntity.addMapping`.
    pub fn id(&self) -> &'static str {
        match self {
            Self::Chest(_) => "Chest",
            Self::Dispenser(_) => "Trap",
            Self::Furnace { .. } => "Furnace",
            Self::Sign(_) => "Sign",
            Self::Note { .. } => "Music",
            Self::Jukebox(_) => "RecordPlayer",
            Self::MobSpawner { .. } => "MobSpawner",
            Self::Piston { .. } => "Piston",
        }
    }

    /// `TileEntity.writeToNBT` with the block position.
    pub fn write_nbt(&self, [x, y, z]: [i32; 3]) -> Compound {
        let mut nbt = Compound::default();
        nbt.set_string("id", self.id());
        nbt.set_int("x", x);
        nbt.set_int("y", y);
        nbt.set_int("z", z);
        match self {
            Self::Chest(items) => nbt.set_list("Items", item::write_slots(&items[..])),
            Self::Dispenser(items) => nbt.set_list("Items", item::write_slots(&items[..])),
            Self::Furnace {
                items,
                burn_time,
                cook_time,
                ..
            } => {
                nbt.set_short("BurnTime", *burn_time);
                nbt.set_short("CookTime", *cook_time);
                nbt.set_list("Items", item::write_slots(items));
            }
            Self::Sign(lines) => {
                for (i, line) in lines.iter().enumerate() {
                    nbt.set_string(&format!("Text{}", i + 1), line.clone());
                }
            }
            Self::Note { note, .. } => nbt.set_byte("note", *note),
            Self::Jukebox(record) => {
                if *record > 0 {
                    nbt.set_int("Record", *record);
                }
            }
            Self::MobSpawner { entity_id, delay } => {
                nbt.set_string("EntityId", entity_id.clone());
                nbt.set_short("Delay", *delay);
            }
            Self::Piston {
                block,
                metadata,
                facing,
                last_progress,
                extending,
                ..
            } => {
                nbt.set_int("blockId", *block);
                nbt.set_int("blockData", *metadata);
                nbt.set_int("facing", *facing);
                nbt.set_float("progress", *last_progress);
                nbt.set_bool("extending", *extending);
            }
        }
        nbt
    }

    /// `TileEntity.createAndLoadEntity`: the tile entity and its position, or `None` for unknown ids.
    pub fn read_nbt(nbt: &Compound) -> Option<(Self, [i32; 3])> {
        let position = [nbt.int("x"), nbt.int("y"), nbt.int("z")];
        let tile_entity = match nbt.string("id") {
            "Chest" => Self::Chest(Box::new(read_items(nbt))),
            "Trap" => Self::Dispenser(Box::new(read_items(nbt))),
            "Furnace" => {
                let items: [_; FURNACE_SIZE] = read_items(nbt);
                Self::Furnace {
                    item_burn_time: items[1].map_or(0, |fuel| item::burn_time(fuel.item)),
                    items,
                    burn_time: nbt.short("BurnTime"),
                    cook_time: nbt.short("CookTime"),
                }
            }
            "Sign" => Self::Sign(std::array::from_fn(|i| {
                nbt.string(&format!("Text{}", i + 1))
                    .chars()
                    .take(15)
                    .collect()
            })),
            "Music" => Self::Note {
                note: nbt.byte("note").clamp(0, 24),
                powered: false,
            },
            "RecordPlayer" => Self::Jukebox(nbt.int("Record")),
            "MobSpawner" => Self::MobSpawner {
                entity_id: nbt.string("EntityId").to_owned(),
                delay: nbt.short("Delay"),
            },
            "Piston" => Self::Piston {
                block: nbt.int("blockId"),
                metadata: nbt.int("blockData"),
                facing: nbt.int("facing"),
                progress: nbt.float("progress"),
                last_progress: nbt.float("progress"),
                extending: nbt.bool("extending"),
                source: false,
            },
            _ => return None,
        };
        Some((tile_entity, position))
    }
}

fn read_items<const N: usize>(nbt: &Compound) -> [Option<ItemStack>; N] {
    let mut items = [None; N];
    item::read_slots(nbt, "Items", &mut items);
    items
}