GeezerVison is showing github.com — Merge pull request #257 from feather-rs/block-entities · BuggStream/feather@3e7570e
File treeExpand file treeCollapse file tree Open diff view settings Some content is hiddenLarge Commits have some content hidden by default. Use the searchbox below for content that may be hidden. Dismiss bannerExpand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: Cargo.lock +25-15Lines changed: 25 additions & 15 deletions Collapse file Copy file name to clipboardExpand all lines: Cargo.toml +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -25,6 +25,7 @@ members = [ | 25 | 25 |
| 26 | 26 | "server", | 27 | 27 | "server/template", | | 28 | + "server/block", | 28 | 29 | "server/chat", | 29 | 30 | "server/chunk", | 30 | 31 | "server/commands", | |
Collapse file Copy file name to clipboardExpand all lines: codegen/src/lib.rs +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -76,7 +76,7 @@ lazy_static! { | 76 | 76 | m.insert("f32", PacketParameterType::F32); | 77 | 77 | m.insert("f64", PacketParameterType::F64); | 78 | 78 | m.insert("Uuid", PacketParameterType::Uuid); | 79 | | - m.insert("NbtTag", PacketParameterType::Nbt); | | 79 | + m.insert("Blob", PacketParameterType::Nbt); | 80 | 80 | m.insert("Slot", PacketParameterType::Slot); | 81 | 81 | m.insert("EntityMetadata", PacketParameterType::EntityMetadata); | 82 | 82 |
| |
Collapse file Copy file name to clipboardExpand all lines: core/anvil/Cargo.toml +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -16,7 +16,7 @@ thiserror = "1.0" | 16 | 16 | tokio = { version = "0.2", features = ["full"] } | 17 | 17 | serde = "1.0" | 18 | 18 | uuid = "0.8" | 19 | | -hematite-nbt = { git = "https://github.com/feather-rs/hematite_nbt" } | | 19 | +hematite-nbt = "0.4" | 20 | 20 | byteorder = "1.3" | 21 | 21 | bitvec = "0.17" | 22 | 22 | anyhow = "1.0" | |
Collapse file Copy file name to clipboard +191Lines changed: 191 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +use crate::player::InventorySlot; | | 2 | +use nbt::Value; | | 3 | +use serde::{Deserialize, Serialize}; | | 4 | +use std::collections::HashMap; | | 5 | + | | 6 | +/// A block entity loaded or saved to the Anvil format. | | 7 | +/// Should be serialized using NBT. | | 8 | +/// | | 9 | +/// https://minecraft.gamepedia.com/Chunk_format#Block_entity_format | | 10 | +#[derive(Debug, Clone, Serialize, Deserialize)] | | 11 | +pub struct BlockEntityData { | | 12 | + #[serde(flatten)] | | 13 | + pub base: BlockEntityBase, | | 14 | + #[serde(flatten)] | | 15 | + pub kind: BlockEntityKind, | | 16 | +} | | 17 | + | | 18 | +impl BlockEntityData { | | 19 | + pub fn into_nbt_value(self) -> Value { | | 20 | + let mut compound = HashMap::new(); | | 21 | + | | 22 | + compound.insert(String::from("x"), Value::Int(self.base.x)); | | 23 | + compound.insert(String::from("y"), Value::Int(self.base.y)); | | 24 | + compound.insert(String::from("z"), Value::Int(self.base.z)); | | 25 | + | | 26 | + let id = match self.kind { | | 27 | + BlockEntityKind::Chest { items, .. } => { | | 28 | + compound.insert( | | 29 | + String::from("Items"), | | 30 | + Value::List( | | 31 | + items | | 32 | + .into_iter() | | 33 | + .map(InventorySlot::into_nbt_value) | | 34 | + .collect(), | | 35 | + ), | | 36 | + ); | | 37 | + "minecraft:chest" | | 38 | + } | | 39 | + _ => todo!("implement block entity into_nbt_value"), | | 40 | + }; | | 41 | + compound.insert(String::from("id"), Value::String(String::from(id))); | | 42 | + | | 43 | + Value::Compound(compound) | | 44 | + } | | 45 | +} | | 46 | + | | 47 | +/// Data common to all block entities. | | 48 | +#[derive(Debug, Clone, Serialize, Deserialize)] | | 49 | +pub struct BlockEntityBase { | | 50 | + /// X coordinate in global coordinate space. | | 51 | + pub x: i32, | | 52 | + /// Y coordinate in global space. | | 53 | + pub y: i32, | | 54 | + /// Z coordinate in global space. | | 55 | + pub z: i32, | | 56 | +} | | 57 | + | | 58 | +/// Kind of a block entity. | | 59 | +#[derive(Debug, Clone, Serialize, Deserialize)] | | 60 | +#[serde(tag = "id")] | | 61 | +#[serde(rename_all = "PascalCase")] | | 62 | +pub enum BlockEntityKind { | | 63 | + #[serde(rename = "minecraft:beacon")] | | 64 | + Beacon { | | 65 | + levels: i32, | | 66 | + primary: i32, | | 67 | + secondary: i32, | | 68 | + }, | | 69 | + #[serde(rename = "minecraft:bed")] | | 70 | + Bed, // empty in JE | | 71 | + #[serde(rename = "minecraft:brewing_stand")] | | 72 | + BrewingStand { | | 73 | + items: Vec<InventorySlot>, | | 74 | + brew_time: i16, | | 75 | + fuel: i8, | | 76 | + }, | | 77 | + #[serde(rename = "minecraft:cauldron")] | | 78 | + Cauldron { | | 79 | + items: Vec<InventorySlot>, | | 80 | + potion_id: i16, | | 81 | + splash_potion: bool, | | 82 | + is_movable: bool, | | 83 | + }, | | 84 | + #[serde(rename = "minecraft:chest")] | | 85 | + Chest { | | 86 | + #[serde(rename = "Items")] | | 87 | + items: Vec<InventorySlot>, | | 88 | + loot_table: Option<String>, | | 89 | + loot_table_seed: Option<i64>, | | 90 | + }, | | 91 | + #[serde(rename = "minecraft:comparator")] | | 92 | + Comparator { output_signal: i32 }, | | 93 | + #[serde(rename = "minecraft:command_block")] | | 94 | + CommandBlock { | | 95 | + custom_name: Option<String>, | | 96 | + command: String, | | 97 | + success_count: i32, | | 98 | + last_output: String, | | 99 | + track_output: bool, | | 100 | + powered: bool, | | 101 | + auto: bool, | | 102 | + condition_met: bool, | | 103 | + update_last_execution: bool, | | 104 | + last_execution: i64, | | 105 | + }, | | 106 | + #[serde(rename = "minecraft:daylight_detector")] | | 107 | + DaylightDetector, // empty | | 108 | + #[serde(rename = "minecraft:dispenser")] | | 109 | + Dispenser { items: Vec<InventorySlot> }, | | 110 | + #[serde(rename = "minecraft:dropper")] | | 111 | + Dropper { items: Vec<InventorySlot> }, | | 112 | + #[serde(rename = "minecraft:enchanting_table")] | | 113 | + EnchantingTable, | | 114 | + #[serde(rename = "minecraft:ender_chest")] | | 115 | + EnderChest, | | 116 | + #[serde(rename = "minecraft:end_gateway")] | | 117 | + EndGateway { age: i64, exact_teleport: bool }, | | 118 | + #[serde(rename = "minecraft:end_portal")] | | 119 | + EndPortal, | | 120 | + #[serde(rename = "minecraft:furnace")] | | 121 | + Furnace { | | 122 | + items: Vec<InventorySlot>, | | 123 | + burn_time: i16, | | 124 | + cook_time: i16, | | 125 | + cook_time_total: i16, | | 126 | + }, | | 127 | + #[serde(rename = "minecraft:hopper")] | | 128 | + Hopper { | | 129 | + items: Vec<InventorySlot>, | | 130 | + transfer_cooldown: i32, | | 131 | + }, | | 132 | + #[serde(rename = "minecraft:jigsaw")] | | 133 | + Jigsaw { | | 134 | + target_pool: String, | | 135 | + final_state: String, | | 136 | + /// spelled "attachement" on the wiki, | | 137 | + /// but mispelling is probably a mistake? | | 138 | + attachment_type: String, | | 139 | + }, | | 140 | + #[serde(rename = "minecraft:jukebox")] | | 141 | + Jukebox { record_item: InventorySlot }, | | 142 | + // TODO: a few more | | 143 | +} | | 144 | + | | 145 | +impl BlockEntityKind { | | 146 | + pub fn variant(&self) -> BlockEntityVariant { | | 147 | + match self { | | 148 | + BlockEntityKind::Beacon { .. } => BlockEntityVariant::Beacon, | | 149 | + BlockEntityKind::Bed { .. } => BlockEntityVariant::Bed, | | 150 | + BlockEntityKind::BrewingStand { .. } => BlockEntityVariant::BrewingStand, | | 151 | + BlockEntityKind::Cauldron { .. } => BlockEntityVariant::Cauldron, | | 152 | + BlockEntityKind::Comparator { .. } => BlockEntityVariant::Comparator, | | 153 | + BlockEntityKind::CommandBlock { .. } => BlockEntityVariant::CommandBlock, | | 154 | + BlockEntityKind::Chest { .. } => BlockEntityVariant::Chest, | | 155 | + BlockEntityKind::DaylightDetector { .. } => BlockEntityVariant::DaylightDetector, | | 156 | + BlockEntityKind::Dispenser { .. } => BlockEntityVariant::Dispenser, | | 157 | + BlockEntityKind::Dropper { .. } => BlockEntityVariant::Dropper, | | 158 | + BlockEntityKind::EnchantingTable { .. } => BlockEntityVariant::EnchantingTable, | | 159 | + BlockEntityKind::EnderChest { .. } => BlockEntityVariant::EnderChest, | | 160 | + BlockEntityKind::EndGateway { .. } => BlockEntityVariant::EndGateway, | | 161 | + BlockEntityKind::EndPortal { .. } => BlockEntityVariant::EndPortal, | | 162 | + BlockEntityKind::Furnace { .. } => BlockEntityVariant::Furnace, | | 163 | + BlockEntityKind::Hopper { .. } => BlockEntityVariant::Hopper, | | 164 | + BlockEntityKind::Jigsaw { .. } => BlockEntityVariant::Jigsaw, | | 165 | + BlockEntityKind::Jukebox { .. } => BlockEntityVariant::Jukebox, | | 166 | + } | | 167 | + } | | 168 | +} | | 169 | + | | 170 | +/// Variant of a `BlockEntityKind`. | | 171 | +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] | | 172 | +pub enum BlockEntityVariant { | | 173 | + Beacon, | | 174 | + Bed, | | 175 | + BrewingStand, | | 176 | + Cauldron, | | 177 | + Chest, | | 178 | + Comparator, | | 179 | + CommandBlock, | | 180 | + DaylightDetector, | | 181 | + Dispenser, | | 182 | + Dropper, | | 183 | + EnchantingTable, | | 184 | + EnderChest, | | 185 | + EndGateway, | | 186 | + EndPortal, | | 187 | + Furnace, | | 188 | + Hopper, | | 189 | + Jigsaw, | | 190 | + Jukebox, | | 191 | +} |
Collapse file Copy file name to clipboardExpand all lines: core/anvil/src/entity.rs +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -149,6 +149,7 @@ impl BaseEntityData { | 149 | 149 | String::from("Motion"), | 150 | 150 | Value::List(self.velocity.iter().copied().map(Value::Double).collect()), | 151 | 151 | ); | | 152 | + map.insert(String::from("Health"), Value::Float(self.health)); | 152 | 153 | } | 153 | 154 | } | 154 | 155 |
| |
Collapse file Copy file name to clipboardExpand all lines: core/anvil/src/lib.rs +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 2 | 2 | //! world saves. Currently includes region file loading, | 3 | 3 | //! player data loading, and level data loading. | 4 | 4 |
| | 5 | +pub mod block_entity; | 5 | 6 | pub mod entity; | 6 | 7 | pub mod level; | 7 | 8 | pub mod player; | |
Collapse file Copy file name to clipboardExpand all lines: core/anvil/src/player.rs +15-1Lines changed: 15 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -4,8 +4,12 @@ use feather_inventory::player_constants::{ | 4 | 4 | SLOT_INVENTORY_OFFSET, SLOT_OFFHAND, | 5 | 5 | }; | 6 | 6 | use feather_items::{Item, ItemStack}; | | 7 | +use nbt::Value; | 7 | 8 | use serde::{Deserialize, Serialize}; | 8 | | -use std::path::{Path, PathBuf}; | | 9 | +use std::{ | | 10 | + collections::HashMap, | | 11 | + path::{Path, PathBuf}, | | 12 | +}; | 9 | 13 | use tokio::io::AsyncWriteExt; | 10 | 14 | use tokio::prelude::{AsyncRead, AsyncWrite}; | 11 | 15 | use uuid::Uuid; | @@ -88,6 +92,16 @@ impl InventorySlot { | 88 | 92 | None | 89 | 93 | } | 90 | 94 | } | | 95 | + | | 96 | + pub fn into_nbt_value(self) -> Value { | | 97 | + let mut compound = HashMap::new(); | | 98 | + | | 99 | + compound.insert(String::from("Count"), Value::Byte(self.count)); | | 100 | + compound.insert(String::from("id"), Value::String(self.item)); | | 101 | + compound.insert(String::from("Slot"), Value::Byte(self.slot)); | | 102 | + | | 103 | + Value::Compound(compound) | | 104 | + } | 91 | 105 | } | 92 | 106 |
| 93 | 107 | async fn load_from_file<R: AsyncRead + Unpin>(mut reader: R) -> Result<PlayerData, nbt::Error> { | |
Collapse file Copy file name to clipboardExpand all lines: core/anvil/src/region/blob.rs +7Lines changed: 7 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -77,7 +77,13 @@ fn level_to_value(level: ChunkLevel) -> Value { | 77 | 77 | entities.push(entity.into_nbt_value()); | 78 | 78 | }); | 79 | 79 |
| | 80 | + let mut block_entities = Vec::with_capacity(level.block_entities.len()); | | 81 | + level.block_entities.into_iter().for_each(|entity| { | | 82 | + block_entities.push(entity.into_nbt_value()); | | 83 | + }); | | 84 | + | 80 | 85 | map.insert(String::from("Entities"), Value::List(entities)); | | 86 | + map.insert(String::from("TileEntities"), Value::List(block_entities)); | 81 | 87 |
| 82 | 88 | Value::Compound(map) | 83 | 89 | } | @@ -150,6 +156,7 @@ mod tests { | 150 | 156 | }], | 151 | 157 | biomes: vec![10], | 152 | 158 | entities: vec![], | | 159 | + block_entities: vec![], | 153 | 160 | heightmaps: vec![], | 154 | 161 | }, | 155 | 162 | }; | |
|