alex/beta-minecraft-rust
Minecraft Beta 1.7.3 Rewrite
BRANCH 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
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
}